Xwork's Blog

The lord is waiting to take your hand.

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

Vamos a programar #54 - ID3 y LUA.

Hola de nuevo a todos, el día de hoy vamos a ver código en LUA que sirve para extraer las imágenes embebidas en un MP3. No es lo lo más optimizado, pero a veces lo que cuenta es conseguir nuestro objetivo.


Antes de continuar, es recomendable que leas uno de los post antiguos del blog en cual se explica cómo hacer de manera "artesanal" todo el procedimiento, a veces ayuda un poco saber exactamente que es lo que vamos a hacer para comprender.

Para poder probar el código vamos a necesitar un interprete de LUA para windows (o para PC con cualquier sistema operativo). Si usas windows al igual que yo, recomiendo usar ZeroBrane Studio, es gratuito para todos pero el autor muestra links para donaciones (si puedes apoyarlo hazlo, siempre le cae bien una bebida a cualquier programador independiente). ZeroBrane además de contener el interprete de lua, es un IDE que sirve para depurar código. Es muy importante instalar localmente una copia del interprete para poder abrir los archivos desde nuestro disco duro.

El código.

Con el interprete ya instalado veamos el código.

-----------De la documentación disponible en http://www.id3.org para la versión 2---------------
--[[
Para la version 2.2
	Header for 'Attached picture', ID: "APIC">
	Text encoding   $xx
	MIME type       <text string> $00
	Picture type    $xx
	Description     <text string according to encoding> $00 (00)
	Picture data    <binary data>
Para la version 2.0
	Attached picture   "PIC"
	Frame size         $xx xx xx
	Text encoding      $xx
	Image format       $xx xx xx
	Picture type       $xx
	Description        <textstring> $00 (00)
	Picture data       <binary data>
]]
function WriteImage(NombreDeLaCancion,NombreDeLaImagen)
--Abrimos el archivo en modo de lectura binario
	local Rdata = io.open(NombreDeLaCancion,"rb")
--Ponemos el puntero al inicio del archivo
	Rdata:seek("set")
--Ahora leemos los primeros 1024b del archivo, si no encontramos aquí la etiqueta "APIC" o "PIC" asumimos que el archivo no tiene imagen embebida
	local Sdata = Rdata:read(1024)
--En esta variable Guardaremos la ubicación del La etiqueta "APIC"
	local GotIt = string.find(Sdata,"APIC")
--Si GotIt es nil en este punto, buscamos la etiqueta PIC
	if GotIt == nil then
		GotIt = string.find(Sdata,"PIC")
	end
--
--Si la variable GotIt tiene cualquier valor diferente de nil; procedemos a buscar el tipo de imagen que este embebida.
	if GotIt ~= nil then
		local GotPNG =string.find(Sdata,string.char(137)..string.char(80)..string.char(78)..string.char(71))
	--Obtenemos la versión
		local GetVer = string.find(Sdata,"APIC")
--La comprobación es simple ya llegados a este punto; si uno es falso el otro debe de ser verdadero.		
--Regresamos el puntero a la posición en donde se encontró la etiqueta APIC y leemos la longitud de la etiqueta
		local b1,b2,b3,b4
		local TagSize	
		if GetVer ~= nil then
			Rdata:seek("set",GotIt+3)
			b1=string.byte(Rdata:read(1))*256*256*256
			b2=string.byte(Rdata:read(1))*256*256
			b3=string.byte(Rdata:read(1))*256
			b4=string.byte(Rdata:read(1))
			TagSize = b1+b2+b3+b4+10
		else
			Rdata:seek("set",GotIt+2)
			b1=string.byte(Rdata:read(1))*256*256
			b2=string.byte(Rdata:read(1))*256
			b3=string.byte(Rdata:read(1))
			TagSize = b1+b2+b3+5
		end
--Si los datos dicen que la imagen es JPG
		if GotPNG  == nil then
			local JPicInit = string.find(Sdata,string.char(255)..string.char(216)..string.char(255))
			Rdata:seek("set",JPicInit-1)
			IMGData = Rdata:read(TagSize-(JPicInit-GotIt))
			local MPImg = io.open(NombreDeLaImagen..".jpg","wb")
			MPImg:write(IMGData)
			MPImg:flush()
			MPImg:close()
			MPImg=nil
		else
			local PPicInit = string.find(Sdata,string.char(137).."PNG")
			local TagDMP
			if GetVer then
				TagDMP=8
			else
				TagDMP=6
			end
			Rdata:seek("set",PPicInit-1)
			local IMGData = Rdata:read(TagSize-(PPicInit-GotIt))
			MPImg = io.open(NombreDeLaImagen..".png","wb")
			MPImg:write(IMGData)
			MPImg:flush()
			MPImg:close()
			MPImg=nil
		end
	end
	Rdata:close()
	Rdata = nil
end

print("Inserta la ubicacion de un mp3")
local FILE = io.read();
print("Inserta un nombre de archivo")
local SavePlace = io.read();
WriteImage(FILE, SavePlace);

Primero que nada, LUA dispone de funciones de lectura y escritura bastante similares a los de C. Para usar un lector, debemos de iniciarlo pasando dos parámetros, uno de ellos es el archivo que queremos leer (o nuestra canción) y el segundo parámetro es el modo en cual se abrirá; en este caso, usamos "rb" que indicará que leeremos el archivo de forma "binaria".

Al igual que en C, disponemos de una función que sirve para desplazarnos en el archivo que tenemos abierto. Antes de continuar, hay que aclarar algo; LUA no es un lenguaje orientado a objetos pero tiene una sintaxis que ayuda a simularlo y para el código anterior es lo mismo "Rdata:seek("set");" que "Rdata = io.seek("set");" y lo mismo para todos los procedimiento provenientes de cualquier "clase" así que una vez que se inicializa una variable del tipo "io", podemos usar el nombre que le dimos seguido de dos puntos para acceder a los sub-métodos. Entonces, usando "seek" sin ningún parámetro adicional a "set", le indicamos que queremos mover el puntero al inicio del archivo (byte cero)

Cuando ya iniciamos todo, leemos los primeros 1024 bytes del archivo y al igual que en C#, vamos a buscar cualquiera de las etiquetas que se usan para indicar una imagen; es decir: "APIC" o "PIC". Algo que probablemente olvide decir en el primer post sobre imágenes en los MP3's es la importancia de buscar primero "APIC" a algunos les resultará obvio cual es la razón pero a otros no. La razón es simple si buscamos PIC (que corresponde a la versión 2), el resultado de la búsqueda sera verdadero si existe "APIC" porque esta contiene toda la cadena de caracteres que buscamos, en cambio si buscamos "APIC" primero, si no se encuentra, eso no significa que necesariamente no exista "PIC".

Para realizar la búsqueda de la etiqueta, una vez que leímos todos los bytes en los cuales podría estar, creamos un flag llamado "GotIt" del tipo "boolean" y a esta le asignamos el resultado de la función "string.find()" usando cómo parámetros "Sdata" que es una copia de "Rdata" (los 1024 bytes leídos) y es en donde buscará la cadena de texto que le pasamos en el segundo parámetro.

Repetimos lo anterior 4 veces, la primera es para buscar "APIC", la segunda para buscar "PIC" luego si la variable "GotIt" resulta "true" en cualquiera de las dos funciones, hacemos una nueva búsqueda pero ahora en lugar de las etiquetas de imagen, buscamos el tipo de imagen; ya sea PNG o JPG, pero para hacerlo, hacemos uso de de los caracteres que sirven de "encabezado" para los archivos PNG, cómo ya estamos dentro de la condición en la cual si se encontró una etiqueta de imagen, solo hacemos una comprobación y si no encontramos al "encabezado" del archivo PNG entonces la imagen es JPG

Para calcular el tamaño de las imágenes, hay que recordar que seguido de la etiqueta, los cuatro bytes siguientes  para la versión tres y los tres bytes siguiente en la versión dos, indican cual es el tamaño de total de la etiqueta, para poder saber cual es el valor, bastará con multiplicar cada byte por potencias de 256, empezando de izquierda a derecha por 256^3 para la versión 3 y 256^2 para la versión dos.

Finalmente  guardamos la imagen haciendo uso de un escritor de archivos que se usa igual al de C, es decir creamos el archivo e indicamos cual es el modo en el cual se trabajara, cuando usamos "wb" indicamos que abriremos un archivo pra escribir de forma binaria, si el archivo no existe, se creara uno nuevo.

Para poder usar la función, en ZeroBrane presionaremos F6 y en la parte inferior, el programa no preguntará por la ubicación de una canción seguido de la ubicación que usaremos para guardar la imagen, para este ultimo es importante solo indicar la ruta y el nombre del archivo sin extensión, dentro del programa al determinar si la imagen es jpg o png automáticamente agrega la extensión adecuada.

Si observas bien, el archivo de imagen solo tiene la ruta y el nombre, pero no la extensión.


Y bien por ahora es todo, el código lo puedes copiar y pegar para probarlo.

Los leo luego.

Vamos a programar #19 - Extraer una imagen embebida en un MP3 - ArtView

El día de hoy vamos a terminar (ahora si no es broma) el código para extraer las imágenes que se encuentran embebidas en los archivos MP3.


En el post anterior implementamos algunas partes del código y lo pasamos a funciones, antes de avanzar hay algo que quiero decir de una de las funciones anteriores. Una persona que visitó el blog me dijo que la función DataToAlbum no funcionaba bien, a pesar de que yo mostré imágenes en la que parecía que todo funcionaba bien, pero no era asi, al analizar todos los bytes que componían al string (solo la versión unicode), había un carácter no visible. El carácter es 0xFF, eso se debia a que también le pasábamos a la función GetString los bytes adicionales que se usan para indicar que se trata de un string unicote, para agregarlo, solo hay que movernos hasta el tercer byte. Recordemos que la función que hicimos recibía cómo parámetro una matriz de bytes donde el primero, era el byte que nos indicaba cual era el tipo de codificación; uno para unicode y cero para ASCII. En el caso de la codificacion ASCII no existía mayor problema porque seguido de ese byte, inmediatamente va lo que vendría siendo el string en sí, pero en el caso del string unicode, inmediatamente después del primer byte, siguen dos bytes que sirven para indicar que tipo de codificación se usa (en lectores de texto por ejemplo,0xFF 0xFE para unicode).

private string DataToAlbum(byte[] Data)
{
 if (Data[0] == 0)
  return Encoding.ASCII.GetString(Data,1,Data.Length-1).TrimEnd((char)0).TrimStart((char)0);
 if (Data[0] == 1)
  return Encoding.Unicode.GetString(Data,3,Data.Length-3).TrimEnd((char)0).TrimStart((char)0);
 else
  return "Desconocido";
}

Procesando por lotes.

El chiste de todo esto era extraer las imágenes debido a que cuando se convertían entre una versión a otra, por lo que me dijeron, algunos programas omiten la imagen, entonces lo ideal es extraer primero la imagen y después insertarla de nuevo, eso si, solo después de haber realizado la conversión a la versión de destino.

Para eso creamos la función BatchProcess cuyo código es el siguiente:

private void BatchProccess(List<string> Files, string SaveFolder)
{
 int Current = 0;
 PBProgress.Maximum = Files.Count;
 Thread DoWork = new Thread (new ThreadStart(() =>
 {
  foreach (string Item in Files)
  {
   ImageContent(Item,SaveFolder);
   Current = Current + 1;
   UpdateProgress(Current);
   if (Current == Files.Count) {
    IsWorking =false;
   }
  }
 }
 ));
 DoWork.Start();
}


Está función recibe cómo parámetros una lista del tipo string que contiene todos los archivo de los cuales queremos extraerles la imágenes, el otro parámetro es un string que contiene la ruta (fólder) en donde guardáremos las imágenes. Después creamos un thread para no bloquear la interfaz principal. De hecho cuando pruebes el código, al agregar los archivos, si agregas muchos, notarás que la aplicación se congela un poco (dependiendo de cuantos agregues y de que tan potente sea tu equipo), esto lo deje así para que notes la diferenta entre usar un thread aparte y cuando no.
El uso del thread (hilo a partir de este momento). Para esta aplicación es realmente sencillo, ya que no hacemos uso de las funciones para controlarla como es debido, de hecho para saber si aun sigue ocupado ese hilo usamos el flag IsWorking. Para poder saber el progreso del proceso usamos un delegado que se encargara de recibir el valor actual del progreso, después llamará a la funcion UpdateProgress que se encargará de actualizar la propiedad Value del ProgressBar.

El código de la funcion UpdateProgress (incluido el delegado) es el siguiente:

private delegate void UpdateProgressDelegate(int Value);

private void UpdateProgress(int Value)
{
 if (PBProgress.InvokeRequired)
 {
  PBProgress.Invoke(new UpdateProgressDelegate(UpdateProgress), Value);
 }
 else
 {
  PBProgress.Value = Value;
 }
}

El uso un poco más avanzado de los hilos lo veremos en el siguiente Post.
Y ya solo que da el código que se encargará de hacer el trabajo. El uso de un flag para determinar si hay o no un proceso es importante, ya que podemos cerrar la ventana principal y algun proceso todavía se puede estar ejecutando, suponiendo que eres cómo yo y rara vez le pide a Windows que te dé permiso para sacar tu dispositivo, si lo extraes en medio de un proceso de lectura/escritura, puede ser que se dañe tu USB.

El código completo lo puedes descargar aquí, Dudas o comentarios no dudes en decirlo. Antes de terminar quiero agradecer al que reportó el error, preferiría que no usaran perfiles anónimos para poder dar el reconocimiento que se debe.

Por ahora es todo, los leo luego.
P.D, “Arceus existe ;)”

Vamos a programar #17 - Extraer una imagen embebida en un MP3

Hola a todos de nuevo. El día de hoy vamos a terminar con el temas de las imágenes embebidas en los archivos MP3.
En el post anterior, hicimos una parte del código de C# encargado de hacer la extracción. Había quedado pendiente el trabajo para la versión 2 de las etiquetas ID3.

Hay que recordar que la esctructura del la etiqueta "PIC" es la siguiente:

Attached picture "PIC"
Frame size $xx xx xx
Text encoding $xx
Image format $xx xx xx
Picture type $xx
Description <textstring> $00 (00)
Picture data <binary data>
Basados en la información anterior crearemos el código necesario.

El código para la versión 2.

Retomando el codigo anterior, solo hay que agregar lo que nos hace falta. La función ImageContent constaba de 3 condiciones if importantes (ademas de algunos secundarios), el primero y principal consiste en verficar si hay una etiqueta ID3 valida, si la había, el siguiente if comprobaba que la version fuera la 2.3, el siguiente comprobaba para la version 2. Está fue la que quedo pendiente, el siguiente codigo va inmediatamente despues del if que comprobaba para la version 2.3.


 if (MyTagType == TagVersion.TagVer22)
                    {
                        {
                            //PIC
                            BinRead.BaseStream.Position = PicTagPos + 3;
                            //Tamaño
                            TotalLenght = TotalLenght + BinRead.ReadByte() * 256 * 256;
                            TotalLenght = TotalLenght + BinRead.ReadByte() * 256;
                            TotalLenght = TotalLenght + BinRead.ReadByte();
                            txtInfo.Text = txtInfo.Text + "\n La etiqueta PIC esta en la dirección: " + PicTagPos.ToString() + "\n";
                            txtInfo.Text = txtInfo.Text + "con longitud de" + TotalLenght.ToString() + " bytes\n";
                            //TextEncoding
                            BinRead.ReadByte();
                            //Type JPG o PNG
                            string MType = "";
                            MType = MType + string.Concat((char)BinRead.ReadByte());
                            MType = MType + string.Concat((char)BinRead.ReadByte());
                            MType = MType + string.Concat((char)BinRead.ReadByte());
                            txtInfo.Text = txtInfo.Text + "\nDel tipo " + MType;
                            //Tipo de imagen
                            BinRead.ReadByte();
                            //Reciclamos el flag anterior
                            IsNotZero = true;
                            string ImgDescription = "";
                            //Descripcion de la imagen
                            while (IsNotZero == false)
                            {
                                byte Val = BinRead.ReadByte();
                                if (Val != 0x00)
                                {
                                    ImgDescription = ImgDescription + string.Concat((char)Val);
                                    IsNotZero = false;
                                }
                                else
                                {
                                    IsNotZero = true;
                                }
                            }
                            long ImageFileBegin = BinRead.BaseStream.Position;
                            int ImageLenght = (TotalLenght - 1 - ((int)ImageFileBegin - PicTagPos - 6));
                            txtInfo.Text = txtInfo.Text + " La imagen empieza en " + ImageFileBegin.ToString();
txtInfo.Text = txtInfo.Text + "Y mide " + ImageLenght.ToString();
                            //Creamos una imagen desde el stream y la asignamos al picbox
                            //Ponemos el puntero al inicio de la imagen
                            BinRead.BaseStream.Position = ImageFileBegin + 1;
                            using (MemoryStream MS = new MemoryStream(BinRead.ReadBytes(ImageLenght)))
                            {
                                PicArt.Image = Image.FromStream(MS);
                                using (FileStream file = new FileStream(SaveLocation + "." + ImageType(MType), FileMode.Create, FileAccess.Write))
                                {
                                    MS.WriteTo(file);
                                    file.Flush();
                                    file.Close();
                                }
                                MS.Close();
                            }
                        }
                    }
}

Si revisamos el código de forma detenida veremos que es mu similar al código anterior. Entonces te preguntarás ¿No es un desperdicio? Si y no, el código está pensado para hacer una demostración de lo que hicimos en el primer post? Y la respuesta es: NO, lo que trate de hacer con estos post es demostrar como es que se puede portar una situación al código así "tal cual", porque de hecho lo que hicimos aquí, fue lo mismo que hicimos con el programa XVI32. Es decir lo que primero hicimos a mano, luego lo plasmamos en el código.

Información para una canción con etiqueta PIC (Ver 2)

Información para una canción con etiqueta APIC (Ver 2.3 {Lo admito soy un Pokefan!!!})
 En el próximo post crearemos una función para procesar por lotes y liberaré la aplicación completa, por ahora es todo.

Los leo luego.

Vamos a programar #16 - Extraer una imagen embebida en un MP3

Hola de nuevo a todos, han pasado algunos días desde la ultima vez que hice un post y finalmente le traigo uno nuevo. Cómo recordaran, en el post anterior vimos como extraer una imagen embebida en un mp3, pero en esa ocasión lo hicimos manualmente. En este post vamos a hacer un programa en c# que se encargue de hacer de forma automática ese proceso.

El código en c#.

Primero que nada haremos un pequeño repaso a que es lo que vamos a hacer.
  1. Abrimos un archivo mp3.
  2. Leemos una fracción del archivo para determinar si existe o no la etiqueta "APIC" o "PIC".
  3. Si existe Busacmos su posicion.
  4. Leemos el tamaño total de la etiqueta.
  5. Leemos los datos adicionales (tipo, codificacion, etc).
  6. Leemos el archivo de la imagen.
  7. Guardamos el archivo de imagen.
  8. Listo.
Primero crearemos una nueva aplicación de windows forms, yo en este caso usare #Develop
Ahora agregaremos solo 5 controles:
  • TextBox
    • (name) = TxtPath
  • Button
    • (name) = BtnOpen
    • Text = "Abrir"
  • Button
    • (name) = BtnExtract
    • Text = Extraer
  • TextBox
    • (name) = TxtInfo
    • ScrollBars = Vertical
    • Multiline = True
  • PictureBox
    • (name) = PicArt

Ya con los controles vamos a agregar código para hacer funcionar las cosas. En primer lugar, hay que recordar que hay más de una version de las etiquetas ID3, hasta el momento en que se creo este post existian las versiones: 1, 2, 2.3 y 2.4, nosotros solo nos ocuparemos para los casos de las versiones 2 y 2.3 (y 2.4 ya que para el caso de las etiquetas no hay cambios tan grandes con respecto a la versión anterior).
Basados en lo anterior, lo primero que debemos de hacer es identificar en que versión estamos trabajando, para eso crearemos una función que se encargue de devolver la versión. Para evitar que se usen valores desconocidos, en primer lugar crearemos una enumeración que contenga solo los valores posibles para nuestro proyecto.

private enum TagVersion
{
 TagVer22,
 TagVer23,
 TagVerUnknow
}

En la enumeración anterior se definen situaciones para la versión 2 y 2.3, cualquier otra versión devolverá el valor  "TagVerUnknow". Teniendo una definición para los valores que podemos esperar, ahora si creamos una función que devuelva uno de los valores de la enumeración.


private TagVersion GetId3Ver(byte[] Data)
{//49 44 33
	if (Data[0] == 0x49 && Data[1] == 0x44 && Data[2] == 0x33)
	{
		if (Data[3] == 2) return TagVersion.TagVer22;
		if (Data[3] == 3) return TagVersion.TagVer23;
		else return TagVersion.TagVerUnknow;
	}
	else
	{
		return TagVersion.TagVerUnknow;
	}
}

La funcion GetId3Ver recibe cómo parámetro una matriz de bytes, después lee los primeros 3 valores y compra si corresponden a "ID3", si si lo son, después leen el cuarto byte y regresan el la versión de acuerdo al valor de este. hay que recordar que cuando hay una etiqueta ID3, los primero bytes describen la etiqueta en si y el cuarto byte nos dice que versión es; 2 para la versión 2, 3 para la versión 2.3 y 4 para la versión 2.4.

Ahora vamos a crear la función que se encargará de leer toda la información y guardarla en un archivo a parte.

private void ImageContent(string FilePath,string SaveLocation)
{
	Byte[] BinSearchData;
	Stream FS = new FileStream(FilePath, FileMode.Open, FileAccess.Read,FileShare.Read);
	using(BinaryReader BinRead = new BinaryReader(FS,Encoding.Default))
	{
		BinSearchData = BinRead.ReadBytes(1024);
		TagVersion MyTagType;
		string MyTag;
		switch (GetId3Ver(BinSearchData))
		{
			case TagVersion.TagVer22:
				MyTag = "PIC";
				MyTagType = TagVersion.TagVer22;
				break;
			case TagVersion.TagVer23:
				MyTag = "APIC";
				MyTagType = TagVersion.TagVer23;
				break;
			default:
				MyTag = "Error";
				MyTagType = TagVersion.TagVerUnknow;
				break;
		}
		Match FindTag = Regex.Match(Encoding.ASCII.GetString(BinSearchData), MyTag);
		if (FindTag.Success)
		{
			int TotalLenght = 0;
			int PicTagPos = FindTag.Index;
			if (MyTagType == TagVersion.TagVer23)
			{
				bool IsNotZero = false;
				BinRead.BaseStream.Position = PicTagPos + 4;
				//Size
				TotalLenght = TotalLenght + BinRead.ReadByte() * 256 * 256 * 256;
				TotalLenght = TotalLenght + BinRead.ReadByte() * 256 * 256;
				TotalLenght = TotalLenght + BinRead.ReadByte() * 256;
				TotalLenght = TotalLenght + BinRead.ReadByte();
				txtInfo.Text = txtInfo.Text + "\n La etiqueta APIC esta en la dirección: " + PicTagPos.ToString() + "\n";
				txtInfo.Text = txtInfo.Text + "con longitud de" + TotalLenght.ToString() + " bytes\n";
				//Flags, por ahora no nos interesan
				BinRead.ReadByte();
				BinRead.ReadByte();
				//TextEncoding
				BinRead.ReadByte();
				//Type
				string MType= "";
				while (IsNotZero == false)
				{
					byte Val = BinRead.ReadByte();
					if (Val != 0x00)
					{
						IsNotZero = false;
						MType = MType + string.Concat((char)Val);
					}
					else
					{
						IsNotZero = true;
					}
				}
				txtInfo.Text = txtInfo.Text + "\nDel tipo " + MType;
				//Tipo de imagen
				BinRead.ReadByte();
				//Reciclamos el flag anterior
				IsNotZero = true;
				string ImgDescription = "";
				//Descripcion de la imagen
				while (IsNotZero == false)
				{
					byte Val = BinRead.ReadByte();
					if (Val != 0x00)
					{
						ImgDescription = ImgDescription + string.Concat((char)Val);
						IsNotZero = false;
					}
					else
					{
						IsNotZero = true;
					}
				}
				long ImageFileBegin = BinRead.BaseStream.Position;
				int ImageLenght = (TotalLenght- 1 - ((int)ImageFileBegin - PicTagPos - 10));
				txtInfo.Text = txtInfo.Text + " La imagen empieza en " + ImageFileBegin.ToString();
				txtInfo.Text = txtInfo.Text + "Y mide " + ImageLenght.ToString();
				//Creamos una imagen desde el stream y la asignamos al picbox
				//Ponemos el puntero al inicio de la imagen
				BinRead.BaseStream.Position = ImageFileBegin + 1;
				using (MemoryStream MS = new MemoryStream(BinRead.ReadBytes(ImageLenght)))
				{
					PicArt.Image = Image.FromStream(MS);
					using (FileStream file = new FileStream(SaveLocation + "." + ImageType(MType), FileMode.Create, FileAccess.Write))
					{
						MS.WriteTo(file);
						file.Flush();
						file.Close();
					}
					MS.Close();
				}
			}
			if (MyTagType == TagVersion.TagVer22)
			{
				//Lo mismo que para la anterior, solo ajustando los datos, queda de tarea
			}
		}
		else
		{
			ErrPr.SetError(BtnOpen, "No hay etiqueta APIC");
		}
	}
	FS.Close();
}

Hacemos exactamente lo que hicimos cuando extrajimos la imagen a mano, primero leeremos el archivo, buscamos la etiqueta y luego leemos el archivo, determinamos el tipo de imagen que es, para eso usaremos la siguiente funcion:
private string ImageType(string MIMEType)
{
	//PNG or JPG
	//image/png or image/jpeg
	if (MIMEType == "PNG" || MIMEType == "image/png")
		return "png";
	if (MIMEType == "JPG" || MIMEType == "image/jpeg")
		return "jpg";
	else
		return "bin";
}

Está función solo devuelve "PNG" o "JPG" y se usa al momento de guardar el resultado; es decir al momento de construir el nombre de archivo.

Finalmente solo queda mandar a llamar a la función "ImageContent" desde el botón BtnExtract, la función guardara la imagen con el mismo nombre de archivo que el del MP3 que usemos y también mostrará la imagen en el picture box "PicArt"

Ahora solo queda hacer todo el procedimiento para la versión 2, pero eso lo haremos en el siguiente Post, ademas crearemos una función para agregar muchos archivos y realizar la extracción por lotes. Te recomiendo que le eches un vistazo a la documentación parta que sea más fácil entender cómo es que funciona.

Por el momento es todo, los leo luego.

Vamos a programar #15 - Extraer una imagen embebida en un MP3

Hace algunos días estaba hablando con un amigo y me comento un problema que le pasó. Hace algunos días quiso organizar su colección de música en formato MP3. Todas las canciones poseen los meta-datos o "TAGS" que sirven para identificar a una canción. La información básica que suele incluirse es:

  1. Nombre de la canción.
  2. Álbum.
  3. Artista.
  4. Año.
  5. Genero.
Adicionalmente los archivos MP3's suelen tener una imagen que representa la portada del disco (algunos incluso incluyen varias imágenes). El problema que me comento mi amigo fue que al convertir todas las canciones para que la información esté en la misma versión en todas las canciones, la imagen se perdía. cuando se hacia una conversion de la version ID3 2.2 a la versión 2.3.

Etiquetas ID3.

Antes que nada veamos que es una etiqueta ID3.
ID3 es un estándar de facto para incluir meta-datos (etiquetas) en un archivo contenedor audiovisual, tales como álbum, título o artista. Se utiliza principalmente en ficheros sonoros como MP3. Wikipedia/ID3.
Básicamente es eso. Para identificar cada una de las partes de la información se usan etiquetas ya predefinidas, es decir: si se quiere saber el autor se deberá de buscar una etiqueta en especifico.
La lista completa de etiquetas se puede encontrar en ID3/V2 (para la version 2.2.0) y en ID3V2.3(para la version 2.3).
Por ahora solo vamos a revisar la etiquetas que nos interesan:

Para la version 2.3
Header for 'Attached picture', ID: "APIC"
MIME type <text string> $00
Picture type $xx
Description <text string according to encoding> $00 (00)
Picture data <binary data>
Para la version 2.2
Attached picture "PIC"
Frame size $xx xx xx
Text encoding $xx
Image format $xx xx xx
Picture type $xx
Description <textstring> $00 (00)
Picture data <binary data>
Lo que haremos es buscar "PIC" o "APIC" para determinar si hay imagen o no, si la hay, después leeremos la longitud, luego el tipo (usualmente solo se usa PNG o JPG) y finalmente leeremos los datos correspondientes a la imagen en sí.

Vista previa.

Antes que nada, primero vamos a hacer una pequeña prueba "en crudo" y trataremos de seguir las instrucciones a mano, es decir solo usando un lector hexadecimal, extraeremos la imagen. Para eso necesitarás un editor hexadecimal, hay muchos disponibles actualmente, pero yo uso XVI32, puedes usar el de tu preferencia, pero el programa que escojas deberá de tener las funciones de lectura y escritura.
También recomiendo que leas las definiciones para las etiquetas antes de continuar.

Primero abriremos el archivo del cual queremos obtener la imagen. Una vez abierto procederemos a buscar directamente los datos que nos interesan. Para nuestro caso solo requerimos saber si hay una etiqueta "PIC" o "APIC". Entonces, con nuestro archivo ya abierto buscaremos eso.

Al terminar la búsqueda, si existe, el cursor se moverá a la posición en donde se encuentra el inicio del lo que buscamos.
Si hacemos memoria recordaremos que después de la etiqueta "APIC", los siguientes 4 bytes indican el tamaño total de la etiqueta y dos bytes más son usados cómo flags (no nos fijaremos en esto), entonces los datos que nos interesan son lo que estan en el recuadro rojo de la siguiente imagen:
Para este caso, la longitud de esta etiqueta es 0x00027225, hay que recordar que esta longitud no incluye los 10 bytes que se usan para definir la etiqueta, el tamaño y los flag. Si seleccionamos los 0x00027225 bytes, estaríamos seleccionando los datos que describen la imagen, el tipo de imagen (icono, cubierta frontal, etc) y formato de imagen (jpg o png).
Para poder guardar la imagen, seleccionaremos tantos bytes como nos indica, pero le restaremos 14 bytes que corresponden a los frames que describen para el tag APIC.
Si convertimos el numero 0x00027225 a decimal,obtenemos el numero decimal 160,293 restandole 14 obtenemos 160,279.
En nuestro editor primero pondremos el cursor al final de todos los descriptores de la etiqueta. y si tiene disponible la opcion de seleccionar una cantidad "n" de caracteres, usaremos esa indicando la cantidad de bytes que obtuvimos al hacer la resta.
Finalmente solo guardaremos la parte seleccionada (si el editor lo soporta).


 Hay algo importante por remarcar, según la descripción en ID3.org es preferible usar solo dos tipos de imágenes, puede ser Jpeg o PNG, dependiendo de cual se use, el descriptor que se usa es diferente. En este ejemplo al ser una imagen jpg la que está incrustada en el mp3, al ver la descripción tenemos: "image/jpeg" pero si la imagen esta en png tendremos algo como: image/png. Eso causaría que si solo restamos a la longitud 14 bytes, la imagen no la obtendríamos completa, por eso es necesario revisar el tipo de imagen, ademas de que hay información adicional que en este caso no la hay.


Y bien es todo por hoy, en el siguiente post continuaremos ahora si con el código de C# para extraer la imágenes dentro de los mp3. Este post solo trata de exponer como es que funciona y de hecho ya hiciste a mano lo que C# haría internamente.
Si solo quieres seguir los pasos tal  como están en el post. La canción que use como prueba está aquí.

Los leo luego.