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.

Preferiré un millón de veces el "Tú por Tú" que la interacción virtual.

Siguiente
« Prev Post
Anterior
Next Post »
0 Comentarios