Xwork's Blog

The lord is waiting to take your hand.

Mostrando las entradas con la etiqueta FLAC. Mostrar todas las entradas
Mostrando las entradas con la etiqueta FLAC. Mostrar todas las entradas

Vamos a programar #107 - Agregando imagenes a archivos FLAC usando C# (Parte 1).

Hola de nuevo a todos. El día de hoy vamos a continuar con mas de los archivos FLAC. En post anteriores vimos cómo insertar la imagen usando solo un editor hexadecimal, pero hoy, vamos a usar C# para crear un programa que lo haga por nosotros.


 Antes de continuar te recomiendo que leas los post relacionados:

 Una vez leidos los post anteriores, vamos a ver el código que hace funcionar el programa. Algo importante que debo aclarar, es que por primera vez en la historia del blog, voy a usar visual studio 2022 y .Net 8.0, la mayor parte del código es retro-compatible, pero en c# 7.0 se agregaron algunas caracteristicas que hacen las cosas mas sencillas, por lo que si quieres probar el código deberías de instalar al menos esa version de visual studio.Además el código es mas que nada una prueba de concepto por lo que se puede optimizar, pero además, todo se hace para que sea lo mas similar al proceso que se hizo a mano (Vamos a programar #102) por lo que si quieres optimizarlo, puedes hacerlo sin ningun problema.

 

Una vez dicho eso,  el código es el siguiente:

using System.Diagnostics.Eventing.Reader;
using System.Runtime.InteropServices;
using System.Text;
using System.Drawing.Imaging;
using System.Net.Http.Headers;
using System.ComponentModel;

namespace FlacPictureWriter
{
public partial class Form1 : Form

{

///Picture type:
/// $00 Other
/// $01 32x32 pixels 'file icon' (PNG only)
/// $02 Other file icon
/// $03 Cover (front)
/// $04 Cover (back)
/// $05 Leaflet page
/// $06 Media (e.g. lable side of CD)
/// $07 Lead artist/lead performer/soloist
/// $08 Artist/performer
/// $09 Conductor
/// $0A Band/Orchestra
/// $0B Composer
/// $0C Lyricist/text writer
/// $0D Recording Location
/// $0E During recording
/// $0F During performance
/// $10 Movie/video screen capture
/// $11 A bright coloured fish
/// $12 Illustration
/// $13 Band/artist logotype
/// $14 Publisher/Studio logotype
private enum APICType
{
/// <summary>
/// Imagen génerica
/// </summary>
APIC_TYPE_Other = 0,
/// <summary>
/// ícono (solo PNG)
/// </summary>
APIC_TYPE_FileIcon,
/// <summary>
/// Otro ícono
/// </summary>
APIC_TYPE_OtherFile_Icon,
/// <summary>
/// Cubierta frontal del disco
/// </summary>
APIC_TYPE_CoverFront,
/// <summary>
/// Cubierta trasera del disco
/// </summary>
APIC_TYPE_CoverBack,
/// <summary>
/// Folleto
/// </summary>
APIC_TYPE_LeafletPage,
/// <summary>
/// Otra ímagen
/// </summary>
APIC_TYPE_Media,
/// <summary>
/// Artista principal
/// </summary>
APIC_TYPE_LeadArtist,
/// <summary>
/// Artista
/// </summary>
APIC_TYPE_Artist,
/// <summary>
/// Conductor
/// </summary>
APIC_TYPE_Conductor,
/// <summary>
/// Banda
/// </summary>
APIC_TYPE_Band,
/// <summary>
/// Compositor
/// </summary>
APIC_TYPE_Composer,
/// <summary>
/// Letrista
/// </summary>
APIC_TYPE_Lyricist,
/// <summary>
/// Lugar de la grabacion
/// </summary>
APIC_TYPE_RecordingLocation,
/// <summary>
/// Captura durante la grabacion
/// </summary>
APIC_TYPE_DuringRecording,
/// <summary>
/// Captura durante concierto
/// </summary>
APIC_TYPE_DuringPerformance,
/// <summary>
/// Captura de videclip
/// </summary>
APIC_TYPE_VideoScreenCapture,
/// <summary>
/// ???
/// </summary>
APIC_TYPE_ABrightColouredFish,
/// <summary>
/// Ilustracion
/// </summary>
APIC_TYPE_Illustration,
/// <summary>
/// Logotipo de la banda/artista
/// </summary>
APIC_TYPE_ArtistLogotype,
/// <summary>
/// Logotipo de la discografica
/// </summary>
APIC_TYPE_PublisherLogotype,
APIC_TYPE_NoValid

}


// 0 : STREAMINFO 0000000
// 1 : PADDING 0000001
// 2 : APPLICATION 0000010
// 3 : SEEKTABLE 0000011
// 4 : VORBIS_COMMENT 0000100
// 5 : CUESHEET 0000101
// 6 : PICTURE 0000110
// 7-126 : reserved 1111000 - Invalid
// 127 : invalid, to avoid confusion with a frame sync code
/// <summary>
/// Enumeración con los posibles bloques contenidos en un archivo FLAC
/// </summary>
private enum BlocksTypes
{
/// <summary>
/// Bloque mandatorio con la información del Stream
/// </summary>
Block_Type_StreamInfo = 0,
/// <summary>
/// Bloque de Padding
/// </summary>
Block_Type_Padding = 1,
/// <summary>
/// Bloque con la informacion de la aplicación
/// </summary>
Block_Type_Application = 2,
/// <summary>
/// Bloque Seektable
/// </summary>
Block_Type_SeekTable = 3,
/// <summary>
/// Bloque con metadatos
/// </summary>
Block_Type_VorbisComment = 4,
/// <summary>
/// Bloque con cuesheet
/// </summary>
Block_Type_CueSheet = 5,
/// <summary>
/// Bloque con imagen
/// </summary>
Block_Type_Picture = 6,
/// <summary>
/// Bloque no válido
/// </summary>
Block_Type_NoValid = 7

}
public Form1()
{
InitializeComponent();
}


List<UInt32> Direcciones;
bool TieneImagen = false;
UInt32 ImageSize = 0;
UInt32 LastBLockPosition = 0;
UInt32 VorbisCommentPosition = 0;
UInt32 ImagePosition = 0;

private byte[] NormalizeToFourBytes(UInt32 Value, bool Reverse = false)
{
byte[] buff = BitConverter.GetBytes(Value);

if (Reverse)
Array.Reverse(buff);

return buff;
}
/// <summary>
/// Obtiene el "Mime Type" basado en la extension de un archivo
/// </summary>
/// <param name="FilePath">Ruta de la cual se va a extraer la información</param>
/// <returns></returns>
private string GetMIMEType(string FilePath)
{

FileInfo FI = new(FilePath);
if (string.Compare(FI.Extension, ".jpg", StringComparison.OrdinalIgnoreCase) == 0)
{
return @"image/jpeg";
}
else if (string.Compare(FI.Extension, ".png", StringComparison.OrdinalIgnoreCase) == 0)
{
return @"image/PNG";
}
else
{
return "No valido";
}
}


/// <summary>
/// Crea una imagen a partir de una secuencia de bytes
/// </summary>
/// <param name="bytesArr">Matriz de bytes que contiene los datos de la imagen</param>
/// <returns>Regresa una imagen</returns>
public Image ByteArrayToImage(byte[] bytesArr)
{
using (MemoryStream memstr = new(bytesArr))
{
Image img = Image.FromStream(memstr);
return img;
}
}

// 4 4 n*4 4 n*4 4 4 4 4 !4 !n*4
//<32>,<32>,<n*8>,<32>,<n*8>,<32>,<32>,<32>,<32>,<32>,<n*8>
// 1 2 3 4 5 6 7 8 9 10 11
/// <summary>
/// Lee todo los datos del bloque PICTURE y extrae la imagen contenida
/// </summary>
/// <param name="TheData">Arreglos bytes que contiene todo el bloque PICTURE</param>
/// <returns>Regresa una imagen</returns>
private Image ReadPictureData(byte[] TheData)
{

uint CurrentPos = 4;//1
uint CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += 4;//2
string MimeType = Encoding.ASCII.GetString(TheData, (int)CurrentPos, (int)CurrentSize);

CurrentPos += CurrentSize;//3

CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += (CurrentSize + (4 * 5));//4-9

CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += 4;

byte[] TempImage = new byte[CurrentSize];
Array.Copy(TheData, CurrentPos, TempImage, 0, CurrentSize);
return ByteArrayToImage(TempImage);


}

/// <summary>
/// Actualiza la imagen de un archivo FLAC.
/// </summary>
/// <param name="ImagePath">Ruta de la imagen JPG</param>
private byte[] BuildAPICFrame(string ImagePath, int PictureType, string MimeType, UInt32 ImageSize)
{

List<byte> Data = new List<byte>();
byte[] Buff = new byte[ImageSize];

using (FileStream FS = new FileStream(ImagePath, FileMode.Open))
{
using (BinaryReader BR = new(FS))
{
Buff = BR.ReadBytes((int)ImageSize);
}
}

//Tipo de Imagen APIC
Data.AddRange(NormalizeToFourBytes((UInt32)PictureType, true));
//Tamaño del tipo de contenido
Data.AddRange(NormalizeToFourBytes((UInt32)MimeType.Length, true));
//Tipo de contenido
Data.AddRange(Encoding.ASCII.GetBytes(MimeType));
//Descripcion del tipo de contenido
Data.AddRange(NormalizeToFourBytes((UInt32)MimeType.Length, true));
//Tamaño de la descripcion del contenido
Data.AddRange(Encoding.ASCII.GetBytes(MimeType));
//Ancho de la imagen
Data.AddRange(NormalizeToFourBytes(0, true));
//Alto de la imagen
Data.AddRange(NormalizeToFourBytes(0, true));
//Profundidad del color
Data.AddRange(NormalizeToFourBytes(0, true));
//Indice de la paleta de colores
Data.AddRange(NormalizeToFourBytes(0, true));
//Tamaño de la imagen en bytes
Data.AddRange(NormalizeToFourBytes(ImageSize, true));
//Datos de la Imagen
Data.AddRange(Buff);



return Data.ToArray();
}
/// <summary>
/// Construye el bloque de imagen
/// </summary>
/// <param name="APICData"></param>
/// <returns></returns>
private byte[] BuildPictureBlock(byte[] APICData)
{
byte[] Buff = new byte[APICData.Length + 4];
byte[] PICSize = new byte[4];

PICSize = NormalizeToFourBytes((UInt32)APICData.Length, true);
PICSize.CopyTo(Buff, 0);


//siempre pondremos la imagen al final de los metadatos
//Establecemos el marcador de ultimo bloque y el tipo de bloque en 6 (Block_Type_Picture)
//Quedando 10000110b o 134
Buff[0] = 134;
APICData.CopyTo(Buff, 4);

return Buff;
}


private List<UInt32> GetOffsets(string FileName)
{
byte[] DataBuff;
List<UInt32> offsets = new List<UInt32>();
bool LastFrame = false;


using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
offsets.Add((UInt32)FS.Position);
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
DataBuff = BR.ReadBytes(4);
if (string.Equals(Encoding.ASCII.GetString(DataBuff), "fLaC", StringComparison.Ordinal))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;
BlocksTypes CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));


while (!LastFrame)
{
//primer bloque siempre ser� Block_Type_StreamInfo


offsets.Add((UInt32)FS.Position);
DataBuff = BR.ReadBytes(4);
LastFrame = IsLastFrame(DataBuff[0]);
CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));
CurrentBlockSize = GetBlockSize(DataBuff);
FS.Seek(FS.Position + CurrentBlockSize, SeekOrigin.Begin);


if (CurrentBlockType == BlocksTypes.Block_Type_VorbisComment)
{
VorbisCommentPosition = offsets[offsets.Count - 1];
}


if (CurrentBlockType == BlocksTypes.Block_Type_Picture)
{
TieneImagen = true;
ImagePosition = offsets[offsets.Count - 1];
}


if (LastFrame)
LastBLockPosition = offsets[offsets.Count - 1];
}
}
else
{
MessageBox.Show("Archivo Flac no v�lido");
}


}
}


return offsets;
}


/// <summary>
/// Construye el encabezado de un bloque de informacion
/// </summary>
/// <param name="Number">Valor que indica la longitud del bloque en bytes</param>
/// <param name="BlockType">Valor que indica el tipo de bloque de acuerdo a <c>BlockTypes</c></param>
/// <param name="IsLastFrame">Indica si es el ultimo bloque de informacion</param>
/// <returns>Regresa un arreglo de cuatro bytes que representan el encabezado del bloque</returns>
private byte[] BuildHeader(uint Number, BlocksTypes BlockType, bool IsLastFrame)
{
byte[] HeaderBuff = new byte[4];

HeaderBuff = BitConverter.GetBytes(Number);



HeaderBuff[3] = (byte)BlockType;
if (IsLastFrame)
{
HeaderBuff[3] |= 128;
}

Array.Reverse(HeaderBuff);

return HeaderBuff;
}
/// <summary>
/// Convierte un número en su valor equivalente al tipo de bloque
/// </summary>
/// <param name="Data">
/// Entero sin signo con la representacion del bloque</param>
/// <returns>Regresa un valor de la enumeración <typeparamref name="BlockTypes"/>BlockTypes</returns>
private BlocksTypes NumberToBlockType(uint Data)
{
switch (Data)
{
case 0:
return BlocksTypes.Block_Type_StreamInfo;
case 1:
return BlocksTypes.Block_Type_Padding;
case 2:
return BlocksTypes.Block_Type_Application;
case 3:
return BlocksTypes.Block_Type_SeekTable;
case 4:
return BlocksTypes.Block_Type_VorbisComment;
case 5:
return BlocksTypes.Block_Type_CueSheet;
case 6:
return BlocksTypes.Block_Type_Picture;
default:
return BlocksTypes.Block_Type_NoValid;
}
}

/// <summary>
/// Lee el bloque con la informacion estructurada
/// </summary>
/// <param name="TheData">Arreglo de bytes del cual se extraera la información</param>
/// <returns>Regresa un arreglo del tipo string con todos los campos que se encontraron</returns>
private string[] ReadVorbisData(byte[] TheData)
{

uint NumberOfFields = 0;
uint CurrentField = 1;
uint CurrentPosition = 0;
//Vendor
uint CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;
byte[] CurrentChunk = new byte[CurrentSize];
Array.Copy(TheData, CurrentPosition, CurrentChunk, 0, CurrentSize);
string Currenttext = Encoding.UTF8.GetString(CurrentChunk);

CurrentPosition += CurrentSize;
NumberOfFields = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;

string[] Fields = new string[NumberOfFields];
//CommentField
while (CurrentField <= NumberOfFields)
{
CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;
byte[] CurrenUserCommentList = new byte[CurrentSize];
Array.Copy(TheData, CurrentPosition, CurrenUserCommentList, 0, CurrentSize);
Currenttext = Encoding.UTF8.GetString(CurrenUserCommentList);
CurrentPosition += CurrentSize;
Fields[CurrentField - 1] = Currenttext;
CurrentField += 1;
}

return Fields;
}

/// <summary>
/// Obtiene el valor del bloque desde un byte
/// </summary>
/// <param name="Data">byte del cual se va a obtener la información</param>
/// <returns>Regresa un valor que es equivalente al tipo de bloque</returns>
private uint GetBlockType(byte Data)
{
Data <<= 3;
Data >>= 3;

return Data;
}

/// <summary>
/// Obtiene si el bloque es el último de la serie
/// </summary>
/// <param name="Data">byte del cual se obtendra la información</param>
/// <returns>true si el bloque es el último, false en caso contrario</returns>
private bool IsLastFrame(byte Data)
{
Data >>= 7;

if (Data == 1)
return true;
else
return false;
}

/// <summary>
/// Obtiene el tamaño de un bloque
/// </summary>
/// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param>
/// <returns>Regresa el tamaño del bloque actual</returns>
private uint GetBlockSize(byte[] Data)
{

byte[] CurrentData = Data;
//Ponemos el primer byte en 0 porque se usa para identificar el bloque
//a la hora de convertir a UInt32 se esperan 4 bytes, pero este siempre será 0 u otro valor no relevante
//para el tamaño del bloque
CurrentData[0] = 0;
Array.Reverse(CurrentData);

return BitConverter.ToUInt32(CurrentData, 0);
}

/// <summary>
/// Obtiene el tamaño de un bloque
/// </summary>
/// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param>
/// <param name="StartIndex">Indica la posicion en donde se empezará a leer</param>
/// <param name="Size">Indica el tamaño en bytes que se van a leer</param>
/// <returns>Regresa el tamaño del bloque actual</returns>

private uint GetBlockSize(byte[] Data, int StartIndex, int Size)
{
byte[] CurrentData = new byte[4];
Array.Copy(Data, StartIndex, CurrentData, 0, Size);
Array.Reverse(CurrentData);

return BitConverter.ToUInt32(CurrentData, 0);
}

/// <summary>
/// Obtiene una imagen desde un archivo
/// </summary>
private void GetImageFromFile()
{

using OpenFileDialog OpDiag = new()
{
Filter = "Imagenes|*.jpg;*.png|Archivos JPG|*.jpg|Imagenes PNG|*.png",
Multiselect = false
};

if (OpDiag.ShowDialog() == DialogResult.OK)
{
TxtInputPic.Text = OpDiag.FileName;
PicInput.ImageLocation = TxtInputPic.Text;
PicInput.SizeMode = PictureBoxSizeMode.StretchImage;
FileInfo FileSize = new FileInfo(TxtInputPic.Text);
ImageSize = (UInt32)FileSize.Length;
LblPicInfo.Text = "Tamaño de la imagen en bytes " + ImageSize.ToString() + " bytes";
}
}

/// <summary>
/// Obtiene un bloque de datos de un archivo a partir de una posicion definida
/// </summary>
/// <param name="FileName">Archivo del cual se va a extraer el bloque</param>
/// <param name="StartPosition">Posicion des la cual se va a obtener el bloque relativo al inicio del archivo</param>
/// <returns>Regresa un arreglo de bytes con el bloque completo</returns>
private byte[] GetBlockData(string FileName, int StartPosition)
{
byte[] DataBuff;
using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;


FS.Seek(StartPosition, SeekOrigin.Begin);
DataBuff = BR.ReadBytes(4);

CurrentBlockSize = GetBlockSize(DataBuff);
FS.Seek(StartPosition, SeekOrigin.Begin);

return (BR.ReadBytes((int)(CurrentBlockSize + 4)));
}
}
}

/// <summary>
/// Obtiene los datos de un bloque
/// </summary>
/// <param name="FileName">Nombre del archivo del cual se obtendra</param>
/// <param name="TypeOfBlock">Tipo de bloque que se va a buscar</param>
private void GetBlock(string FileName, BlocksTypes TypeOfBlock, int StartPosition)
{
byte[] DataBuff;
using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;
BlocksTypes CurrentBlockType = BlocksTypes.Block_Type_NoValid;


FS.Seek(StartPosition, SeekOrigin.Begin);
DataBuff = BR.ReadBytes(4);


CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));


if (CurrentBlockType == TypeOfBlock)
{
if (TypeOfBlock == BlocksTypes.Block_Type_VorbisComment)
{
string[] Fields;
CurrentBlockSize = GetBlockSize(DataBuff);
Fields = ReadVorbisData(BR.ReadBytes((int)CurrentBlockSize));
TxtInfo.Clear();


for (int i = 0; i < Fields.Length; i++)
{
TxtInfo.Text += Fields[i] + " | ";
}
}
else if (TypeOfBlock == BlocksTypes.Block_Type_Picture)
{


CurrentBlockSize = GetBlockSize(DataBuff);
PicFlac.Image = ReadPictureData(BR.ReadBytes((int)CurrentBlockSize));
}
}
else
{
return;
}
}
}
}


private void BtnOpen_Click(object sender, EventArgs e)
{
TieneImagen = false;
VorbisCommentPosition = 0;
ImagePosition = 0;

OpenFileDialog OpDiag = new()
{
Filter = "Archivos Flac|*.flac|Todos los arvhivos|*.*",
Multiselect = false
};

if (OpDiag.ShowDialog() == DialogResult.OK)
{
txtInputFlac.Text = OpDiag.FileName;
PicFlac.SizeMode = PictureBoxSizeMode.StretchImage;
Direcciones = GetOffsets(txtInputFlac.Text);

GetBlock(txtInputFlac.Text, BlocksTypes.Block_Type_VorbisComment, (int)VorbisCommentPosition);

if (TieneImagen)
GetBlock(txtInputFlac.Text, BlocksTypes.Block_Type_Picture, (int)ImagePosition);
}
}

private void BtnOpenPic_Click(object sender, EventArgs e)
{
ImageSize = 0;
GetImageFromFile();
}

private void BtnUpdateImage_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtInputFlac.Text))
{
MessageBox.Show("No hay arhivo de entrada", "Flac Image Writer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}else if (string.IsNullOrEmpty(TxtInputPic.Text))
{
MessageBox.Show("No hay imagen de entrada", "Flac Image Writer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}

string SaveFile;
bool HasImage = false;
List<UInt32> Direcciones = GetOffsets(txtInputFlac.Text);
int CurrentSize = (int)Direcciones[Direcciones.Count - 1];
int bufferSize = 1024 * 1024;

using (SaveFileDialog SavDiag = new SaveFileDialog())
{
SavDiag.Filter = "Archivos FLAC|*.flac";
SavDiag.AddExtension = true;
SavDiag.OverwritePrompt = true;
if (SavDiag.ShowDialog() == DialogResult.OK)
SaveFile = SavDiag.FileName;
else
return;
}

using (FileStream FSWrite = new FileStream(SaveFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
{
using (BinaryWriter BW = new(FSWrite))
{

BW.Write(Encoding.ASCII.GetBytes("fLaC"));

for (int x = 1; x < Direcciones.Count; x++)
{
byte[] FileBuff = GetBlockData(txtInputFlac.Text, (int)Direcciones[x]);

BlocksTypes CurrentBlockType = BlocksTypes.Block_Type_NoValid;
CurrentBlockType = NumberToBlockType(GetBlockType(FileBuff[0]));

if (CurrentBlockType == BlocksTypes.Block_Type_Picture)
HasImage = true;


if (x == Direcciones.Count - 1)
{
if (!HasImage)
{
CurrentSize += FileBuff.Length;
BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, false));
}
else
{
CurrentSize += FileBuff.Length;
BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, true));
}

}
else
{

BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, false));
}

BW.Write(FileBuff, 4, FileBuff.Length - 4);

}

if (HasImage == false)
BW.Write(BuildPictureBlock(BuildAPICFrame(TxtInputPic.Text, CboPictureType.SelectedIndex, GetMIMEType(TxtInputPic.Text), ImageSize)));



FileStream fs = new FileStream(txtInputFlac.Text, FileMode.Open, FileAccess.ReadWrite);

fs.Position = CurrentSize;
int bytesRead = -1;
byte[] bytes = new byte[bufferSize];

while ((bytesRead = fs.Read(bytes, 0, bufferSize)) > 0)
{
BW.Write(bytes, 0, bytesRead);
}

fs.Close();

}
}
}

private void Form1_Load(object sender, EventArgs e)
{
CboPictureType.SelectedIndex = 3;
}

private void PicFlac_DoubleClick(object sender, EventArgs e)
{
try
{


if (TieneImagen)
{
using (SaveFileDialog SavDiag = new SaveFileDialog())
{
SavDiag.Filter = "Arhivo Jpeg|*.jpg";

if (SavDiag.ShowDialog() == DialogResult.OK)
PicFlac.Image.Save(SavDiag.FileName);
}
//PicFlac.Image.Save
}
}
catch
{
MessageBox.Show("Error al guardar la imagen","Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
}

}


private void button1_Click(object sender, EventArgs e)
{
}
}
}

 

Ahora veamos las funciones. 

Primero empezamos con la enumeración "APICType", en ella se definen los posibles valores para el tipo de imagen que se va incrustarde acuerdo a lo establecido en la documentacion. Esto solo se hace para poder identificar las cosas de la forma mas humana posible, no es del todo necesario, pero ayuda bastante.

La primera funcion es  "NormalizeToFourBytes()". Esta función recibe dos parámetros, el primero, es un valor del tipo UInt32 llamado "Value" y el otro es un valor del tipo bool llamado "Reverse". Esta función se encarga de convertir un numero en su equivalente en una secuencia de bytes, pero además tiene la opción de invertir el orden de la secuencia de bytes, dependiendo de la maquina en la que se trabaje o del idioma, los números se pueden representar de forma distinta, por ejemplo, si tenemos el número hexadecimal 0x3259 , puede representarse cómo 0x32 0x59 o 0x59 0x32. La función devuelve un arreglo de bytes con la representación del número.

La siguiente función es "GetMIMEType()". Esta función recibe un valor del tipo string con una cadena de texto. Se espera solo la extensión de las imágenes  compatibles, es decir solo .jpg o .png. Regresa el Mime Type basado en la cadena de texto.

La siguiente funcion es "ByteArrayToImage()" Esta funcion recibe un arreglo de bytes, se espera que la secuencia de bytes en si conformen una imagen. Regresa un valor del tipo Image que contiene la imagen de la secuencia de bytes.

Y bien, por ahora es todo, las siguientes funciones merecen una explicacion mas detallada para poder entender mejor cada una. el código del formulario principal lo puedes copiar y pegar para probarlo, pero al final cuando todo este listo lo publicaré en mi dropbox para que lo puedas descargar y probar.

Los leo luego 

 

Vamos a programar #106 - Agregando imagenes a archivos FLAC parte 2.

 Hola de nuevo a todos, el día de hoy vamos a continuar con mas de los archivos FLAC.

En el post anterior vimos cómo es que esta estructurado la parte que conforma la imagen en un archivo FLAC. Hoy, al igual que en otras ocasiones, antes de escribir el programa en c#, vamos a insertar la imagen "a mano" solo usando un editor Hexadecimal; el que siempre uso es XVI32 que puedes descargar de su sitio oficial. Cómo archivo de prueba voy a usar una canción en formato FLAC, para que sea sencillo seguir los pasos, te recomiendo usar el mismo archivo que puedes descargar aquí. Pero al igual que las otras canciones, si no posees el original, debes de eliminarlo al terminar de hacer las pruebas. La caratula del álbum también la puedes descargar de aquí.

Una vez todo listo, procederemos a abrir el archivo FLAC con XVI32. Hay que tomar en cuenta que a partir de este momento, cuando escribamos un numero en decimal simplemente será el numero, cuando hagamos referencia a un numero en binario, será bxxxxxxxx y en hexadecimal sera 0xXX


 Una vez abierto, vamos a desplazarnos por el archivo. Si hacemos memoria, los primeros cuatro bytes, corresponden al identificador del archivo que es "fLaC" inmediatamente después de este, viene el bloque con la información del stream. este tiene un tamaño fijo de 34 bytes (en bits son: 16+16+24+24+20+3+5+36+128 = 272 bits = 34 bytes), que es lo que nos indica el byte en la dirección 0x05 hasta 0x07 que es la secuencia 0x00, 0x00, 0x22 que al convertirlo a decimal nos da treinta y cuatro, hay que recordar que en el tamaño no se incluye el encabezado. En XVI32, nos podemos desplazar cierto numero de bytes. Al terminar de leer el encabezado del bloque con la información del stream, deberíamos de estar en la dirección 0x07 posicionamos el cursor en el byte siguiente y presionamos la tecla de  "control" y la tecla "b", eso le indicará a XVI32 que estamos marcando el inicio de lo que queremos seleccionar, luego vamos al menú "edit>>block <n> chars" e introducimos tantos bytes cómo nos dijo el encabezado del bloque, en este caso fueron treinta y cuatro.

 


Con esto estará resaltado todo el bloque. Esto solo lo hacemos con el fin de que todo resulte mas claro, pero este bloque no lo vamos a modificar, asi que avanzamos al siguiente.

Se puede apreciar todo el bloque.  
 

Inmediatamente después de la selección, viene el siguiente bloque. Por ahora los que nos interesan son o el bloque de la imagen (aunque en este ejemplo no existe) o el bloque de "padding". De cualquier forma vamos a leer los bloques hasta que encontremos alguno. Para eso simplemente repetimos el paso anterior.

En nuestra secuencia, leemos  0x04, 0x00, 0x01 y 0x58 (el tamaño es 0x0158) que al convertirlo a decimal nos da 344, el primer byte nos dice que este es el bloque "Vorbis Comment", nuevamente marcamos esa cantidad de bytes después del encabezado.

 

Algo importante que debemos de recordar, es que debemos de repetir todo hasta que encontremos el marcador del ultimo bloque. Si continuamos leyendo, tenemos la secuencia 0x81, 0x00, 0x1E y 0xD0, esta es relevante porque al convertir el primer byte a binario, nos da b10000001 y justamente el primer bit (de izquierda a derecha) nos dice que este es el último bloque y además que es del tipo "padding" y que tiene una longitud de 7,888 bytes, procedemos a marcarlos.

Con esto ya tenemos toda la información que necesitamos. antes que nada, necesitamos ver si el tamaño reservado (padding) es suficiente para nuestra imagen, si descargaste la imagen al inicio, estas deben de ser sus propiedades.


 

Para poder inscrustar la imagen necesitamos un espacio 180,677 bytes mas el encabezado, el espacio que tenemos es de 7,888 y claramente es insuficiente, por lo que vamos a reemplazar el bloque en su totalidad.

Preparando la imagen.

Vamos a usar Xvi32 para crear el bloque completo. Primero preparamos el bit que describe el bloque. EN este caso, va a ser el ultimo bloque, pero ademas sera del tipo "PICTURE", el primer bit es b1, seguido de la descripción que es b110, al unirlos para formar el byte tenemos b10000110 que es 134 en decimal o 0x86. En XVI32 podemos insertar directamente los dígitos hexadecimales en el panel de lado izquierdo, si queremos insertar 134 decimal simplemente lo convertimos a hexadecimal y lo escribimos tal cual. Antes de continuar, guardamos lo que llevamos con el nombre de "Picture block.bin".


 para calcular el tamaño total, primero vamos a construir al bloque APIC.

  •  Tipo de imagen por ahora usaremos 0x03 (cubierta frontal del disco) usa 4 bytes.
    • 0x00, 0x00, 0x00, 0x03, en XVI32 vamos al menú "Edit>>Inser string"
  •  El tamaño del tipo MIME , usaremos "image/jpeg" que tiene una longitud de 10 bytes.
    •  0x00, 0x00, 0x00, 0x0A.
  • La cadena de texto  "image/jpeg".
    •  Usando el comando insertar escribimos literal  image/jpeg.
  • El tamaño de la descripción , usaremos la misma secuencia "image/jpeg" que tiene una longitud de 10 bytes.
    •  0x00, 0x00, 0x00, 0x0A.
  • La cadena de texto  "image/jpeg".
    • Usando el comando insertar escribimos literal  image/jpeg.
  • El ancho de la imagen
    • 0x00, 0x00, 0x00, 0x00 
  • El alto de la imagen
    • 0x00, 0x00, 0x00, 0x00
  • La profundidad de color
    • 0x00, 0x00, 0x00, 0x00
  • El indice de colores
    • 0x00, 0x00, 0x00, 0x00
  • El tamaño de la imagen en bytes de la imagen 
    • la imagen pesa  180,677 bytes que es  0x02C1C5 que queda para insertarlo cómo 0x00, 0x02, 0xC1, 0xC5
  • Los datos de la imagen.
    • Abrimos la imagen con XVI32, seleccionamos todos los bytes, los copiamos y los pegamos inmediatamente después de nuestro último byte.
 
Así debería de lucir nuestra secuencia antes de insertar los datos de la imagen. 

Guardamos el archivo como APIC.bin e inspeccionamos sus propiedades para obtener el tamaño.




 

 Volvemos a abrir "Picture block.bin" e insertamos el tamaño de "APIC.bin", en este caso 180,729 es 0x02C1F9 y en la secuencia es: 0x02, 0xC1, 0xF9. Una vez escrito, Abrimos "APIC.bin" y seleccionamos todos los datos, los copiamos y pegamos en "picture block.bin" inmediatamente después de la secuencia actual.

Con esto ya tenemos todo listo para unir las partes.Para eso, seleccionaremos y copiaremos todos los datos previos al bloque que vamos a sustituir (en nuestro archivo FLAC). Los copiamos y pegamos, luego el bloque que creamos y finalmente el resto del archivo FLAC.

 Para simplifica un tanto las cosas, y si quieres ir directo al grano, de nuestro archivo original, primero seleccionaremos los primeros 390 bytes, los copiamos y los pegamos en un nuevo archivo, luego abrimos "picture block.bin", seleccionamos todo y lo insertamos en el archivo que creamos, luego en nuestro archivo original, nos desplazamos a la posición 8,282, usando el comando "go to" en el menú "address" y de ahí seleccionamos todo el resto del archivo, lo copiamos y lo insertamos en el nuevo, si todo fue bien deberíamos de tener nuestro archivo FLAC con su caratula.

Al inspeccionarlo tenemos que todo fue como es debido.


 

 

 

Y bien, por ahora es todo, el objetivo de hacer los pasos a mano, es para ayudar a comprender que es lo que el programa va a hacer. En el siguiente post escribiremos el código en C# que nos ayudara.

Los leo luego. 

Vamos a programar #105 - Agregando imagenes a archivos FLAC parte 1.

Hola de nuevo a todos. Tiempo ha pasado desde la última vez que un post se publicó. Cómo no es bueno dejar cosas pendiente, el día de hoy vamos a continuar con mas programación.

En alguno de los post anterior, vimos cómo leer los metadatos de los archivos FLAC, vimos cómo recuperar información importante: álbum, artista, título de la canción (solo por mencionar algunos), pero además, vimos cómo leer la imagen del archivo; eso si contiene una. El día de hoy, nos concentraremos en cómo está estructurado solo esta parte y servirá de preámbulo para hacer un programa en c# que escriba la imagen en el archivo.


Antes que nada, recordemos cómo esta estructurada la imagen. Hay que recordar que sigue los lineamientos de la etiqueta APIC de las etiquetas ID3v2. Desde este momento, toda la notación estará en bytes (al menos que se indique lo contrario).

  1. <4> El tipo de imagen que va a representar.
    • 0 - Other
    • 1 - 32x32 pixels 'file icon' (PNG only)
    • 2 - Other file icon
    • 3 - Cover (front)
    • 4 - Cover (back)
    • 5 - Leaflet page
    • 6 - Media (e.g. label side of CD)
    • 7 - Lead artist/lead performer/soloist
    • 8 - Artist/performer
    • 9 - Conductor
    • 10 - Band/Orchestra
    • 11 - Composer
    • 12 - Lyricist/text writer
    • 13 - Recording Location
    • 14 - During recording
    • 15 - During performance
    • 16 - Movie/video screen capture
    • 17 - A bright coloured fish
    • 18 - Illustration
    • 19 - Band/artist logotype
    • 20 - Publisher/Studio logotype
  2.  <4> El tamaño de el tipo de archivo (MIME Type).
    •  <N> La cadena de texto con el tipo de archivo (Solo usaremos "image/jpeg" o "image/png" ).
  3. <4> El tamaño de la descripción de la imagen.
    • <N> La cadena de texto con la descripción de la imagen.
  4. <4> El ancho de la imagen en píxeles.
  5. <4> El alto de la imagen en píxeles.
  6. <4> La profundidad de colo de la imagen.
  7. <4> Para imágenes con indice de color, el número de colores a usar.
  8. <4> El tamaño de la imagen en bytes
    • <N> Los datos de la imagen.

En esta imagen se puede apreciar cada apartado (si no se ve bien hay que darle zoom).


 Lo relevante de todo esto, y si miras bien la imagen, es los apartados cuatro, cinco, seis, siete; son totalmente innecesarios y se pueden dejar en cero o 0x00, 0x00, 0x00, 0x00; aun sin se establecen estas propiedades, en la documentacion oficial, no se aconseja hacer uso de ellas al momento de mostrar la imagen.

Habiendo descrito lo anterior, solo queda escribir código en c# para agregar la imagen.

 Y bien, por ahora es todo. cómo de costumbre, hago la promesa de que trataré de publicar mas seguido. realmente agradezco a todos los que se toman la molestia de darse una vuelta por acá.

Los sigo leyendo. 

Vamos a programar #104 - Leyendo metadatos de archivos FLAC (Pt. 3 Ver. C#)

 Hola de nuevo a todos, el día de hoy vamos a continuar con la última parte de FlacTagReader.


En el post anterior vimos cómo es que funcionan algunas funciones (valga la redundancia) y hoy veremos las faltantes.

La siguiente función es: "NumberToBlockType()". Esta función sirve para convertir un número en su equivalente al de la numeración "BlockTypes". Recibe un parámetro del tipo "uint" que es el número que queremos convertir. Regresa un valor del tipo enumeración "BlockTypes" equivalente. Hay que recordar que nos basamos en la de los tipos de bloques de la documentación.

La siguiente función es: "ReadVorbisData()". Cómo su nombre lo podría sugerir, esta función sirve para leer los datos de tipo vorbis ¿Qué es esto? te preguntaras, este bloque en particular es el que contiene los metadatos tales cómo: álbum, artista, año, etc. Siguen la estructura para los comentarios en archivos ogg vorbis.

La estructura de los comentarios es cómo la que sigue:

  • Tamaño de la descripción del proveedor (32 bits).
  • Cadena de texto para describir al proveedor.
  • Tamaño de la lista de comentarios del usuario (32 bits).
  • Tamaño de la lista de comentarios.
    • Tamaño del elemento actual.
    • Cadena de texto en formato UTF-8
  • Marcador de final del bloque (si es verdadero o no hay se marca error o fin del bloque).
Cada elemento de la lista anterior consta de 3 partes:
  • Nombre del campo.
  • = (símbolo de igual o símbolo ASCII 0x3D).
  • Descripción del campo.

Aunque no es mandatorio seguir una estructura particular para cada nombre de campo, si se recomiendan los siguientes:
  • Los nombres de cada campo no deben de contener símbolos que escapen del estándar ASCII de caracteres imprimibles, es decir: de 0x20 a 0x7D (excluyendo el símbolo de igual "=" o 0x3D) y de 0x41 a 0x5A.
  • Se pueden usar los siguientes nombres de campo:
    • TITLE
    • VERSION
    • ALBUM
    • TRACKNUMBER
    • ARTIST
    • PERFORMER
    • COPYRIGHT
    • LICENSE
    • ORGANIZATION
    • DESCRIPTION
    • GENRE
    • DATE
    • LOCATION
    • CONTACT
    • ISRC
  • Aunque los campos anteriores son sugeridos, podemos usar los propios (sin abusar de ello) y por ejemplo, un campo llamado BEATPERMINUTE, es totalmente válido.
Una vez que hemos visto cómo esta estructurado los comentarios para los archivos OGG Vorbis, analicemos la función "ReadVorbisData()". La función "ReadVorbisData()" sirve para leer un bloque que contenga los datos para los comentarios de los archivos ogg (y FLAC). Recibe un arreglo del tipo "byte" que contiene el bloque completo con los datos.
Primero creamos las variables del tipo "uint" llamadas: "NumberOfFields", "CurrentField" y "CurrentPosition"; las inicializamos con los valores de cero, uno y cero respectivamente. La variable "NumberOfFields" servirá para almacenar la cantidad de comentarios contenidos en el bloque; la variable "CurrentField" llevará la cuenta de cual es el número de comentario que se procesa al momento; la variable "CurrentPosition" llevará la posición en la que nos ubicamos dentro de la secuencia.
Luego procedemos a leer los datos, si recordamos la descripción, lo primero que debemos de buscar es la secuencia que contiene el tamaño de la descripción del proveedor y deberían de ser los primeros cuatro bytes de la secuencia. Creamos una variable del tipo "uint" llamada "CurrentSize" y tal cómo su nombre los indica, nos servirá para ir almacenando el tamaño del comentario actual, procedemos a hacer la lectura y tendremos el tamaño de la cadena de texto siguiente, por lo que podemos desplazarnos en el bloque cuatro bytes, por lo que a la variable "CurrentPosition" la aumentaremos en cuatro. Para almacenar la cadena de texto que vamos a leer, creamos una nueva variable llamada "CurrentChunk" del tipo "byte[]" y reservaremos espacio de acuerdo al tamaño actual del comentario por lo que la asignamos el tamaño de "CurrentSize". Después copiamos del arreglo "TheData" (que es parámetro que se paso a la función) al arreglo "CurrentChunk" la cantidad de bytes marcada por "CurrentSize", creamos una nueva variable del tipo "string" llamada "CurrentText" y la asignaremos con el resultado de la llamada a la función "Encoding.UTF8.GetString()" con el parámetro "CurrentChunk".


Nuevamente nos desplazamos la cantidad de bytes que nos diga "CurrentSize" y nos encontraremos en la posición que contiene la cantidad de campos por la cual está compuesto el bloque por lo que asignamos la variable "CurrentText" con el resultado de mandar a llamar a la función "BitConverter.ToUInt32()". Ahora que tenemos este dato, crearemos un arreglo del tipo "string" y reservaremos tantos espacios cómo "NumberOfFields" nos diga.
Para leer los campos, simplemente crearemos un bucle que haga mas o menos lo mismo que hicimos ahorita: leeremos el tamaño, leemos la cadena de texto, desplazamos el marcador posición y repetimos hasta que leamos todos los campos. 
La función recibe un parámetro del tipo arreglo de "byte" y devuelve una un arreglo del tipo "string" que contiene todos los campos encontrados.

La siguiente función es "GetBlockType". Esta función lo que hace es "convertir" el valor de un solo byte a su equivalente a la descripción del tipo de bloque. Para hacerlo, simplemente desplazamos los bits del byte tres lugares a la izquierda y luego a la derecha, hay que recordad que el primer byte contiene el marcador por si es el ultimo bloque, pero además el tipo de bloque por lo que podríamos encontrar algo cómo sigue:10000110 , si lo desplazamos tres lugares a la izquierda, tendríamos: 00110000, y si lo desplazamos tres lugares a la derecha tendríamos 00000110 o seis decimal que marcaria un bloque del tipo "PICTURE". La función recibe un valor del tipo "byte" que es el byte que se va a convertir y regresa el resultado del desplazamiento.

La siguiente función es "IsLastFrame". Esta sirve para determinar si el bloque actual es el último (de acuerdo a la descripción), esta función es similar a la anterior, cómo queremos aislar solo el primer bit (de izquierda a derecha), simplemente desplazamos los bits siete lugares a la derecha y comprobamos, si el resultado del desplazamiento es uno, la función devuelve "true" en caso contrario devuelve false.

Las siguiente función es "GetBlockSize()" + 1 sobrecarga. Esta función se encarga de obtener el tamaño de un bloque. la primera recibe un parámetro y es un arreglo del tipo "byte" que contiene los datos. Para hacerlo, simplemente invertimos los datos del arreglo y regresamos el valor de mandar a llamar a la función "BitConverter.ToUInt32()". La sobrecarga recibe dos parámetros mas, el primero, un valor del tipo "int" que sirve cómo marcador de posición de donde se empezará a leer la secuencia de bytes, esta también se diferencia de la otra porque esta espera una secuencia de bytes "completa" o donde el primer grupo de cuatro bytes no es la secuencia que esperamos, por lo que es importante saber "donde empezar a leer", el tercer parámetro sirve para indica la longitud de bytes a leer. Ambas funciones devuelven un valor del tipo "uint" que representa el tamaño del bloque.

El último procedimiento es "GetBlock()" este sirve para ejecutar la funciones encargadas de buscar los bloques. recibe dos parámetros, el primero un valor del tipo "string" que es la ruta del archivo del cual se quiere obtener información. El segundo es un valor de la enumeración "BlockTypes" que sirve para indicar el tipo de bloque que se va a buscar. Esta función fue la primera que hice y al principio solo la hice para buscar la secuencia "fLaC" por lo que puede parecer repetitiva, pero igual lo deje para dar mayor claridad a lo hicimos.

Y bien, por ahora es todo, cómo de costumbre puedes descargar el proyecto completo de mi dropbox para que lo pruebes por ti mismo. Cabe resaltar que al igual cómo paso con los archivos MP3, aun falta mucho, pero algo que si haré en el corto plazo será crear un programa que agregue la caratulas de los discos.

Los leo luego.

Vamos a programar #103 - Leyendo metadatos de archivos FLAC (pt 2. Ver c#)

Hola de nuevo a todos. El día de hoy vamos a continuar con mas de los archivos FLAC y sus metadatos.

En el post anterior vimos cómo es que está estructurado cada bloque de metadatos, incluso vimos cómo es que se hace la lectura de cada uno (y leímos el bloque "STREAMINFO"). Ahora que sabemos cómo, solo nos queda automatizarlo y para eso vamos a usar el lenguaje de programación C#.
Para empezar, veamos el código (sin ningún orden particular en las funciones):


using System; using System.Drawing; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace FLACTagReader { public partial class Form1 : Form { // 0 : STREAMINFO 0000000 // 1 : PADDING 0000001 // 2 : APPLICATION 0000010 // 3 : SEEKTABLE 0000011 // 4 : VORBIS_COMMENT 0000100 // 5 : CUESHEET 0000101 // 6 : PICTURE 0000110 // 7-126 : reserved 1111000 - Invalid // 127 : invalid, to avoid confusion with a frame sync code /// <summary> /// Enumeración con los posibles bloques contenidos en un archivo FLAC /// </summary> private enum BlocksTypes { /// <summary> /// Bloque mandatorio con la información del Stream /// </summary> Block_Type_StreamInfo = 0, /// <summary> /// Bloque de Padding /// </summary> Block_Type_Padding = 1, /// <summary> /// Bloque con la informacion de la aplicación /// </summary> Block_Type_Application = 2, /// <summary> /// Bloque Seektable /// </summary> Block_Type_SeekTable = 3, /// <summary> /// Bloque con metadatos /// </summary> Block_Type_VorbisComment = 4, /// <summary> /// Bloque con cuesheet /// </summary> Block_Type_CueSheet = 5, /// <summary> /// Bloque con imagen /// </summary> Block_Type_Picture = 6, /// <summary> /// Bloque no válido /// </summary> Block_Type_NoValid = 7 } public Form1() { InitializeComponent(); } /// <summary> /// Crea una imagen a partir de una secuencia de bytes /// </summary> /// <param name="bytesArr">Matriz de bytes que contiene los datos de la imagen</param> /// <returns>Regresa una imagen</returns> public Image ByteArrayToImage(byte[] bytesArr) { using (MemoryStream memstr = new MemoryStream(bytesArr)) { Image img = Image.FromStream(memstr); return img; } } // 4 4 n*4 4 n*4 4 4 4 4 !4 !n*4 //<32>,<32>,<n*8>,<32>,<n*8>,<32>,<32>,<32>,<32>,<32>,<n*8> // 1 2 3 4 5 6 7 8 9 10 11 /// <summary> /// Lee todo los datos del bloque PICTURE y extrae la imagen contenida /// </summary> /// <param name="TheData">Arreglos bytes que contiene todo el bloque PICTURE</param> /// <returns>Regresa una imagen</returns> private Image ReadPictureData(byte[] TheData) { uint CurrentPos = 4;//1 uint CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4); CurrentPos += 4;//2 string MimeType = Encoding.ASCII.GetString(TheData, (int)CurrentPos, (int)CurrentSize); CurrentPos += CurrentSize;//3 CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4); CurrentPos += (CurrentSize + (4 * 5));//4-9 CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4); CurrentPos += 4; byte[] TempImage = new byte[CurrentSize]; Array.Copy(TheData, CurrentPos, TempImage, 0, CurrentSize); return ByteArrayToImage(TempImage); } /// <summary> /// Convierte un número en su valor equivalente al tipo de bloque /// </summary> /// <param name="Data"> /// Entero sin signo con la representacion del bloque</param> /// <returns>Regresa un valor de la enumeración <typeparamref name="BlockTypes"/>BlockTypes</returns> private BlocksTypes NumberToBlockType(uint Data) { switch (Data) { case 0: return BlocksTypes.Block_Type_StreamInfo; case 1: return BlocksTypes.Block_Type_Padding; case 2: return BlocksTypes.Block_Type_Application; case 3: return BlocksTypes.Block_Type_SeekTable; case 4: return BlocksTypes.Block_Type_VorbisComment; case 5: return BlocksTypes.Block_Type_CueSheet; case 6: return BlocksTypes.Block_Type_Picture; default: return BlocksTypes.Block_Type_NoValid; } } /// <summary> /// Lee el bloque con la informacion estructurada /// </summary> /// <param name="TheData">Arreglo de bytes del cual se extraera la información</param> /// <returns>Regresa un arreglo del tipo string con todos los campos que se encontraron</returns> private string[] ReadVorbisData(byte[] TheData) { uint NumberOfFields = 0; uint CurrentField = 1; uint CurrentPosition = 0; //Vendor uint CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition); CurrentPosition += 4; byte[] CurrentChunk = new byte[CurrentSize]; Array.Copy(TheData, CurrentPosition, CurrentChunk, 0, CurrentSize); string Currenttext = Encoding.UTF8.GetString(CurrentChunk); CurrentPosition += CurrentSize; NumberOfFields = BitConverter.ToUInt32(TheData, (int)CurrentPosition); CurrentPosition += 4; string[] Fields = new string[NumberOfFields]; //CommentField while (CurrentField <= NumberOfFields) { CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition); CurrentPosition += 4; byte[] CurrenUserCommentList = new byte[CurrentSize]; Array.Copy(TheData, CurrentPosition, CurrenUserCommentList, 0, CurrentSize); Currenttext = Encoding.UTF8.GetString(CurrenUserCommentList); CurrentPosition += CurrentSize; Fields[CurrentField - 1] = Currenttext; CurrentField += 1; } return Fields; } /// <summary> /// Obtiene el valor del bloque desde un byte /// </summary> /// <param name="Data">byte del cual se va a obtener la información</param> /// <returns>Regresa un valor que es equivalente al tipo de bloque</returns> private UInt32 GetBlockType(byte Data) { Data <<= 3; Data >>= 3; return Data; } /// <summary> /// Obtiene si el bloque es el último de la serie /// </summary> /// <param name="Data">byte del cual se obtendra la información</param> /// <returns>true si el bloque es el último, false en caso contrario</returns> private bool IsLastFrame(byte Data) { Data >>= 7; if (Data == 1) return true; else return false; } /// <summary> /// Obtiene el tamaño de un bloque /// </summary> /// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param> /// <param name="StartIndex">Indica la posicion en donde se empezará a leer</param> /// <param name="Size">Indica el tamaño en bytes que se van a leer</param> /// <returns>Regresa el tamaño del bloque actual</returns> private UInt32 GetBlockSize(byte[] Data, int StartIndex, int Size) { byte[] CurrentData = new byte[4]; Array.Copy(Data, StartIndex, CurrentData, 0, Size); Array.Reverse(CurrentData); return BitConverter.ToUInt32(CurrentData, 0); } /// <summary> /// Obtiene el tamaño de un bloque /// </summary> /// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param> /// <returns>Regresa el tamaño del bloque actual</returns> private UInt32 GetBlockSize(byte[] Data) { byte[] CurrentData = Data; //Ponemos el primer byte en 0 porque se usa para identificar el bloque //a la hora de convertir a UInt32 se esperan 4 bytes, pero este siempre será 0 u otro valor no relevante //para el tamaño del bloque CurrentData[0] = 0; Array.Reverse(CurrentData); return BitConverter.ToUInt32(CurrentData, 0); } /// <summary> /// Obtiene los datos de un bloque /// </summary> /// <param name="FileName">Nombre del archivo del cual se obtendra</param> /// <param name="TypeOfBlock">Tipo de bloque que se va a buscar</param> private void GetBlock(string FileName, BlocksTypes TypeOfBlock) { byte[] DataBuff; bool LastFrame = false; FileStream FS = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.Read); using (BinaryReader BR = new BinaryReader(FS, Encoding.ASCII)) { DataBuff = BR.ReadBytes(4); if (string.Equals(Encoding.ASCII.GetString(DataBuff), "fLaC", StringComparison.Ordinal)) { UInt32 CurrentBlockSize; UInt32 CurrentPosition = (UInt32)FS.Position; BlocksTypes CurrentBlockType = BlocksTypes.Block_Type_NoValid; while (!LastFrame) { //primer bloque siempre será Block_Type_StreamInfo DataBuff = BR.ReadBytes(4); LastFrame = IsLastFrame(DataBuff[0]); CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0])); if (CurrentBlockType == TypeOfBlock) { if (TypeOfBlock == BlocksTypes.Block_Type_VorbisComment) { string[] Fields; CurrentBlockSize = GetBlockSize(DataBuff); Fields = ReadVorbisData(BR.ReadBytes((int)CurrentBlockSize)); textBox1.Clear(); for (int i = 0; i < Fields.Length; i++) { textBox1.Text += Fields[i] + " | "; } } else if (TypeOfBlock == BlocksTypes.Block_Type_Picture) { CurrentBlockSize = GetBlockSize(DataBuff); pictureBox1.Image = ReadPictureData(BR.ReadBytes((int)CurrentBlockSize)); } } else { CurrentBlockSize = GetBlockSize(DataBuff); FS.Seek(FS.Position + CurrentBlockSize, SeekOrigin.Begin); } } } else { MessageBox.Show("Archivo Flac no válido"); } } } private void button1_Click(object sender, EventArgs e) { using (OpenFileDialog OpDiag = new OpenFileDialog()) { OpDiag.Filter = "Archivos FLAC|*.flac|Todos los arhivos|*.*"; if (OpDiag.ShowDialog() == DialogResult.OK) { GetBlock(OpDiag.FileName, BlocksTypes.Block_Type_VorbisComment); GetBlock(OpDiag.FileName, BlocksTypes.Block_Type_Picture); pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage; } } } } }
Lo primero que hacemos es crear una enumeración llamada "BlockTypes" que contiene las posibles enumeraciones para cualquier tipo de bloque. Si bien la especificación dice que los valores de 0 hasta 127 son validos (aunque no tienen uso todos, al menos  al momento de escribir esto), decidí declarar cómo no validos a los que superan al 6 y por eso, después de ahí, su nombre es "Block_Type_NoValid". Se podrían usar solo números, pero me pareció mas fácil de identificar cada uno de esta forma.

La primera función es "ByteArrayToImage()" del tipo "Image". Esta función, cómo su nombre lo pudiera indicar, se encarga de convertir una secuencia de bytes en una imagen (si los datos son válidos), recibe cómo parámetro un arreglo del tipo "byte[]" que contiene la secuencia de bytes de la imagen. Regresa un valor del tipo "Image" que es la imagen dentro del archivo (si es que hay).

La siguiente función es "ReadPictureData()". Esta función es la encargada de procesar los datos del bloque  "PICTURE". Recibe cómo parámetro un arreglo del tipo "byte[]" que contiene la secuencia de bytes del bloque "PICTURE" completo. Para procesarlo, se siguen las siguientes:
  • <32> El tipo de imagen de acuerdo a la descripción de la etiqueta ID3 "APIC".
  • <32> El tamaño de la descripción del tipo de archivo o MIME type.
  • <n*8> Cadena de texto con la descripción del tipo de archivo.
  • <32> El tamaño de la descripción de la imagen.
  • <n*8> La descripción de la imagen en UTF8
  • <32> El ancho de la imagen en píxeles
  • <32> El alto de la imagen en pixeles.
  • <32> La profundidad del color de la imagen en bits por pixel.
  • <32> Para las imágenes con índice de color, el número de colores usados, para el resto 0.
  • <32> El tamaño de los datos de la imagen.
  • <n*8> Los datos de la imagen.
Para realizar la lectura de la imagen se asume que solo se pasan los datos del bloque completos, solo omitiendo el tamaño del bloque mismo. Y en este caso, cómo solo nos interesa obtener la imagen "tal cual" solo nos concentraremos en leer las ultimas dos secuencias, pero en el código lo hacemos paso a paso para poder apreciar mejor lo que esta ocurriendo y para simplificarlo, lo podemos apreciar mejor en el siguiente diagrama:


Todo este bloque se representa en 11 partes (parte de abajo de la imagen), cada una de la partes ya tiene su tamaño en bits establecido (parte media), que es lo mismo en bytes (parte superior). Si bien hace un momento mencione que vamos a ignorar todo salvo la última y penúltima parte, no podemos ignorar del todo a los otros datos, sobre todo a los que son variables. Ahora vamos leyendo el código.
Primero creamos una variable del tipo "uint" llamada "CurrentPos", esta la vamos a ir usando para saber en que parte del bloque estamos, si observaste bien, te darás cuenta que esta inicializada con un valor de 4, al hacer esto, estaremos brincando la primer secuencia de 32 bits 

Seguido, creamos una variable llamada "CurrentSize" del tipo "uint" y la asignamos con el valor de la llamada a la función "GetBlockSIze()" (que veremos pronto), con esto tendremos el tamaño de la siguiente secuencia (3) por lo que podemos avanzar 4 bytes mas el tamaño que nos dio con la función anterior 
.
Aunque C# nos da muchas facilidades, a la hora de tratar de hacer lo mismo en otro lenguaje las cosas se pueden complicar, este bloque es una cadena de texto con la descripción del tipo de archivo por lo que es importante tenerlo en cuenta, para eso creamos una variable llamada "MimeType" del tipo "string" y se le asigna el valor de la llamada a la función "Encoding.ASCII.GetString()", esta recibe tres parámetro, el primero, un arreglo del tipo "byte[]" con los datos de la cadena ASCII que esperamos obtener; el segundo es un valor del tipo "int" que sirve cómo indicador de que parte del arreglo es donde se va a empezar a leer; el tercero es un valor del tipo "int" que sirve para indicar cuantos bytes de la secuencia se van a leer. Al realizar la "conversión" de una secuencia de "bytes" a "string", tendremos algo cómo lo de la imagen que sigue:

Luego sumamos la posición actual y el tamaño de la secuencia anterior. Para las dos siguientes partes, repetimos lo mismo: obtenemos el tamaño del bloque, lo sumamos a la posición actual y avanzamos cuatro bytes mas.

Las siguientes 4 secuencias son iguales en tamaño, por lo que podemos brincar 4*4 (en el código es 4*5 porque brinque la secuencia anterior tambien).

Una vez leído las partes anteriores, solo queda leer los datos de la imagen por lo que repetimos nuevamente: obtenemos el tamaño de los datos de la imagen, creamos una nueva variable del tipo "byte[]" llamada "TempImage" y al mismo tiempo le asignamos el tamaño de la secuencia actual, luego copiamos la porción del bloque al arreglo que acabamos de crear y devolvemos el resultado de mandar a llamar la función "ByteArrayToImage()".

Y bien, por ahora es todo, en el siguiente post publicaré el proyecto completo y terminaremos de revisar las funciones que no vimos.

Los leo luego.