El Video (AUDIO) Correcto #7 - Un poco mas sobre el audio.
Hola de nuevo a todos, el día de hoy vamos a ver un poco mas sobre los archivos FLAC.
![]() |
| imagen tomada de: https://www.revistagadgets.com/2017/04/04/audio-hi-res-la-nueva-linea-audifonos-pioneer/ |
Cómo muchos de ustedes sabrán, me gusta mucho oír música (música de verdad y no "regeton"), pero siempre había usado equipo de "baja calidad" (y solo por nombrarlo de alguna manera). Pero no hace mucho tiempo, visité un lugar donde se exponían diferente sistemas de audio, desde los mas sencillos; cómo bocina bluetooth; hasta los mas complejos; llámese audio de estudio.
Dentro del mismo lugar había un apartado para los audífonos, de inmediato fui a revisar, y había varios para probar. Cada uno tenia la ficha que describía cuales eran sus características, pero hacían un fuerte énfasis en la frecuencia de respuesta de cada uno.
Primero probé los que ofrecían una respuesta de 20 hasta 20000Hz, justo cómo la mayor parte de audífonos son, la calidad era aceptable, mucho de los audífonos que he tenido cumplían con esa frecuencia y todo parecía aceptable. Los siguientes tenían una respuesta de 20 hasta 32000Hz, la misma calidad que los audífonos que siempre usaba, y justo cómo los anteriores, todo funcionaba bien, y a mi gusto, nada podía superar la calidad. En la siguiente mesa había unos audífonos con una respuesta de 20 hasta 40000Hz... había vivido engañado todo el tiempo, siempre me habían dicho que el oído humano no era capaz de oír frecuencias mas allá de los 32000Hz, pero al momento de probar esos, de inmediato me di cuenta que, si bien es cierto que el oído humano está limitado a ciertas frecuencias, el hecho es que cada persona es diferente y hay quienes alcanzan un rango auditivo mayor al de otros. En mi caso es muy difícil decir hasta "donde puedo escuchar", ya que nunca he hecho un estudio que sirva para eso, pero no había duda de que esos audífonos "sonaban mejor" que los otros, por lo que decidí conseguir unos.
Finalmente cosegui unos audifonos edifier w820nb plus que cuestan poco y tienen la calidad superior a los que ya tenía.
Pero para poder disfrutar de un buen audio, no solo basta con tener los audífonos, también hay que tener un dispositivo compatible y los archivos de audio adecuados. Por fortuna para mi, aun soy de la vieja escuela y tengo mi colección de discos guardados. Normalmente lo que hacia era utilizar mi reproductor de CD (si, en 2023 aun sigo usándolo), pero el inconveniente que encontré, es que la única salida que tiene, es el plug tradicional de 3.5 mm, mientras que los audífonos soportan bluetooth y USB-C.Vamos a programar #78 - Un vistazo a Python.
Hace poco mientras revisaba twitter, alguien me pregunto la razón por la que no había portado los programas que ya tenia a Python y tras analizar, llegué a la conclusión que simplemente lo olvide (por mucho tiempo al parecer).
Entonces para echarle un vistazo rápido al lenguaje, el día de hoy vamos portar uno de los programas que servia para extraer las imágenes en los archivos mp3.
Primero que nada, vamos a tomar un par de cosas en cuenta. Primero: vamos a hacer una portación de la version que hicimos en LUA, asi que puedes (y recomiendo mucho) visitar el post. Y esto se debe principalmente a que LUA y Python son lenguajes interpretados.
Segundo: No te olvides de revisar la documentación en la página de ID3.org y leer la documentación además de los post que hay aquí en el blog.
Tercero: este post será una versión reducida y el programa solo extraerá la imagen del archivo mp3 si la versión de los TAG's es la 2.3, si quieres hacerlo para las otras versiones o extraer más información, puedes usar cómo ejemplo la versión en LUA.
Con lo anterior dicho, pasemos a ver el código en Python que sirve para extraer una imagen embebida en un archivo MP3.
# ---------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>
#Abrimos el archivo
InputFile = input('Ingresa la ruta de un MP3\n')
Rdata = open(InputFile, 'rb+')
#Buscamos que version ID3 es
Rdata.seek(3)
Version = ord(Rdata.read(1))
#escribimos que version es
print(Version)
if Version == 3:
print('Tag Version 2.3')
Rdata.seek(0)
Idata = Rdata.read(1024)
Found = Idata.find(b'APIC')
if Found > -1:
GotPNG = Idata.find(b"\x89\x50\x4e\x47")
Rdata.seek(Found + 4);
b1 = ord(Rdata.read(1))
b1 = b1 * 256 * 256 *256
b2 = ord(Rdata.read(1))
b2 = b2 * 256 *256
b3 = ord(Rdata.read(1))
b3 = b3 * 256
b4 = ord(Rdata.read(1))
bt = b1 + b2 + b3 + b4 + 10
print(bt)
if GotPNG > -1:
PPicInit = Idata.find(b'\x89PNG')
Rdata.seek(PPicInit)
IMGData = Rdata.read(bt - (PPicInit - Found))
OutPath = input('En que carpeta guardar la imagen\n')
ResultImage = open(OutPath + '/TestImage.png','wb')
ResultImage.write(IMGData)
ResultImage.close()
else:
JPicInit = Idata.find(b'\xFF\xD8\xFF')
Rdata.seek(JPicInit)
IMGData = Rdata.read(bt - (JPicInit - Found))
OutPath = input('En que carpeta guardar la imagen\n')
ResultImage = open(OutPath + '/TestImage.png','wb')
ResultImage.write(IMGData)
ResultImage.close()
else:
print('Not Found')
Rdata.close()
input()
Cómo podrás ver el código es realmente similar al de LUA, primero preguntamos por la ubicación de un archivo MP3, para un ejemplo insertamos "X:/Dropbox/MUSIC/American Idiot.mp3" y lo abrimos para su lectura en modo binario usando la función "open()"; la función "open()" recibe dos parámetros, el primero es una cadena de texto con la ruta del archivo a abrir y el segundo parámetro es una cadena de texto que indica el modo en el cual el archivo se abrirá, la cadena de texto "rb+" indica que abriremos el archivo en modo de lectura binario. Todo el "stream" lo asignaremos a la variable "Rdata". Al igual que en lua podemos desplazarnos en el archivo con la función "seek()" que recibe un parámetro que será un número que indicará a que lugar del archivo queremos desplazarnos, este puede ir de 0 a la longitud del archivo. Cómo la versión de los TAG's se indica en el byte 3, nos moveremos ahi y lo leeremos (e imprimimos en pantalla que versión es, no es necesario pero ayuda a tener una idea de lo que está sucediendo).
Luego si la versión de los TAG's es la versión 3, nos desplazamos de vuelta al byte 0 (o inicio del archivo) y leemos un KByte de datos que asignaremos a la variable "Idata". Con los datos asignados, procedemos a buscar el frame que nos interesa (APIC en este caso) con la función "bytes.find()", está función regresa -1 si la cadena de texto a buscar no se encuentra, en caso contrario nos devuelve el lugar en donde se encontró el valor.
Luego buscamos el encabezado de las imágenes PNG y leemos los bytes que nos indican la longitud del frame (4 bytes). Si NO se encontró el encabezado que previamente buscamos, asumimos que la imagen es jpg (asumir es malo ;) ) y buscamos el encabezado de los archivo JPG. Luego leemos los datos de la imagen y preguntamos en que carpeta se quiere guardar la imagen resultante, por ahora el nombre es TESTImage.???. Hacemos lo mismo para la imagen PNG
Y el resultado:
Y bien, por ahora es todo. Cómo de costumbre puedes bajar el programa completo de mi dropbox para probarlo o modificarlo. Además puedes bajar el interprete de la página oficial de Python. En la descarga se incluye un manual bastante extenso del lenguaje con ejemplos y lo necesario para portar lo programas que hemos hecho en el blog.
Los leo luego
Vamos a programar #54 - ID3 y LUA.
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.
-----------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.
El Video Correcto #5 -Convertir archivos FLAC a MP3..
La forma básica de hacerlo. Utilizando un bloc de notas cómo notepad++ vamos a escribir el nombre del programa; en este caso FFMPEG.exe, luego agregaremos el parámetro "-i" que sirve para indicar que queremos "ingresar" un archivo. FFMPEG acepta múltiples archivos de entrada, pero en este caso solo usaremos uno.
Luego debemos de indicar cual es el codec que vamos a usar; cómo queremos convertir a MP3, usaremos el codec que FFMPEG nos ofrece; el codec libmp3 lame.
Luego estableceremos la velocidad de bits que se usarán, para mayor calidad, usaremos el máximo disponible para MP3, que es 320Kb/s y finalmente pondremos el nombre al archivo de salida.
Suponiendo que tenemos el archivo "Infile.flac" y lo queremos convertir a "Output.mp3", podemos crear la sigueinte liena de comando:
ffmpeg.exe -i "InFile.flac" -codec:a libmp3lame -b:a 320k output.mp3 pause
Con lo anterior, podemos crear un archivo MP3 de la mejor calidad (dependiendo de la calidad del archivo FLAC mismo).
A tomar en consideración.
ffmpeg.exe -i "InFile.flac" -codec:a libmp3lame -b:a 320k -ar 48000 output.mp3 pause
ffmpeg.exe -i "InFile.flac" -codec:a libmp3lame -b:a 320k -ar 48000 -ac 1 output.mp3 pause
El Video Correcto #4 - Flac Vs MP3 #2.
Una vez que tenemos lo anterior, abriremos nuestro editor de audio e importaremos las pistas de audio.
Lo primero que haremos, es alinear las pistas, de tal modo que ambas empiecen en exactamente el mismo momento, si descargaste las canciones de muestra, en el archivo MP3 hay que cortar un poco y con ayuda del zoom, buscar que ambas inicien en el mismo instante.
Una vez alineadas ambas pistas, seleccionaremos la pista que corresponde al archivo mp3 y le aplicaremos el efecto de "inversión", no hay que confundirlo con "revertir", ya que uno lo que hace, es invertir "de arriba a abajo", mientras que el otro efecto lo hace "de izquierda a derecha", o mas técnicamente, uno invierte las onda por lo que los valles pasan a ser crestas (y viceversa).
En las ondas cuando sumamos dos ondas que son iguales pero invertidas, tenemos que se cancelan entres si, si aplicamos eso a nuestro audio, al mezclar las pistas, obtendremos, que los "sonidos" existentes en ambas se cancelaran y solo quedaran aquellos que estén ausentes en cualquiera de las pistas. En el caso de audacity, bastará reproducir las dos canciones al mismo tiempo y notarás de inmediato que solo se escucha una versión reducida y aguda de la canción original. Esto que escuchas, es todo lo que se pierde al momento de convertir a mp3.
Las siguientes imágenes muestran cómo se "ve" cada formato:
![]() |
| Formato MP3 |
![]() |
| Formato FLAC |
![]() |
| Residuo al "sumar" las dos canciones. |
El video correcto #3 -FLAC Vs MP3
Hace poco, mi computadora murió y junto con ella, todos los archivos que se encontraban en el disco duro, una perdida considerable, pero no tan grave, puesto que tengo un respaldo de las cosas importantes en un disco duro externo. Cómo no tenia el presupuesto necesario para poder comprar un disco duro de 8TB, opté por comprar lo primero que encontrará, lo que conseguí, fue un disco duro de 80GB. Podría parecer bastante, pero suponiendo que todos los IDE's de desarrollo que uso, ocupan en conjunto 20 GB + 10GB del sistema operativo + los archivos que requiero utilizar, el espacio restante, fue realmente pequeño.
Actualmente no poseo mucha música, pero la poca que tengo, al estar en formato FLAC, ocupa un espacio considerable (en comparación al tradicional y compacto MP3).
Antes de seguir, el formato FLAC es el siguiente:
FLAC (son las siglas de Free Lossless Audio Codec) es un códec de audio que permite que el audio digital sea comprimido sin pérdidas de tal manera que el tamaño del archivo de audio se reduce sin que se pierda ningún tipo de información. El audio digital comprimido por el algoritmo de FLAC típicamente se puede reducir de 50 a 60% de su tamaño original,3 y se descomprime en una copia idéntica de los datos de audio originales.Por la calidad superior que estos poseen, es que los prefiero, pero ante la limitada capacidad del disco duro, decidí hacer una versión en MP3 de los archivos, para ver un ejemplo de cuantas es la diferencia entre uno y otro, veamos la información de la siguiente canción:
![]() |
| Archivo MP3 de la mejor calidad, 8 MB |
![]() |
| Archivo FLAC con calidad buena (no superior) 26MB |
En archivo MP3 se mutilan algunas frecuencias, que por lo general el oído no logra captar, pero en el archivo FLAC, todas las frecuencias se mantienen, y se comprimen, logrando que el sonido sea mucho más fiel al original.
Al deteriorarse mi oído, debo admitir que realmente, en el 60% de los casos ya no escucho ninguna diferencia entre un MP3 de alta calidad y un FLAC por lo que decidí crear una versión liviana. Lo importante de esto, es que podemos hacer una versión MP3 de un archivo FLAC; pero no el reves, crear un archivo FLAC de un MP3, es un desperdicio, cómo el MP3 literalmente fue "cortado", esas frecuencias faltantes ya no se pueden recuperar y estariamos haciendo un archivo pesado que realmente no valdría la pena.
Si quieres oír la diferencia entre FLAC y MP3, dejo una canción de muestra (que deberás de eliminar una vez que termines de comparar).
El archivo flac, lo puedes descargar de aqui.
El archivo mp3, lo puedes descargar de aqui.
Por ahora es todo, en el próximo post, veremos cómo crear un MP3 desde un archivo FLAC usando FFMPEG.
Los leo luego.
Vamos a programar #19 - Extraer una imagen embebida en un MP3 - ArtView
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.
Vamos a programar #18 - Extraer una imagen embebida en un MP3.
El día de hoy vamos a implementar todo el programa y ademas haremos una pequeñas optimizaciones al código que ya teníamos previamente hecho.
Mejorando el código previo.
private int ByteArrToSize(byte[] Data)
{
int Result = 0;
if (Data.Length == 4)
{
Result = Data[0] * 256 * 256 * 256 + Data[1] * 256 * 256 + Data[2] * 256 + Data[3];
return Result;
}
if (Data.Length == 3)
{
Result = Data[0] * 256 * 256 + Data[1] * 256 + Data[2];
return Result;
}
else return 0;
}
Si recordamos en otro código, es exactamente lo mismo, solo que lo separamos para poder usarlo de manera rápida. Esta función recibe una matriz de bytes que contiene los 4 o 3 bytes después de la etiqueta "PIC" o "APIC". cómo resultado, devuelve un numero del tipo int que contiene la longitud de la etiqueta. Para usarla solo hay que sustituir la parte de los calculos por la llamada a la funcion.
Código anterior:
//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";
Código nuevo implementando la función ByteArrToSize:
TotalLenght = ByteArrToSize(BinRead.ReadBytes(4)); 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";
y cómo es un cálculo que lo vamos a hacer más de una vez, lo mejor es usar la función, originalmente la hice de la primer forma debido a que quería mostrar lo que estábamos haciendo a mano, pero para fines más prácticos, usamos la función, no hace falta ser experto para ver que en ambos casos se hace exactamente lo mismo, solo que la función determina; hasta cierto punto; que es lo que va a hacer, si usáramos el código original, debido a que es diferente como esta descrito el tamaño de la etiqueta. Primero había que comprobar para que caso íbamos a hacer el cálculo, y de leer un byte, multiplicar y sumar 3 o 4veces dependiendo del caso. En la función solo pedimos los datos y dependiendo de el numero de bytes que contenga el argumento, seguirá las condiciones pertinentes para devolver el valor correcto.
Otro de los procesos que decidí volver en una función; fue el de obtener la imagen, si recordamos, en el código original, una vez que ya teníamos definidos todos los datos de la imagen (tipo y longitud), en cada una de las versiones de las versiones, leíamos los datos, creábamos un MemoryStream y creábamos una imagen a partir de el.
El codigo es el siguiente:
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();
}
El siguiente código es la versión en función del primer código.
private Image StreamToImage(byte[] Data, string SaveLocation)
{
using (MemoryStream MS = new MemoryStream(Data))
{
Image TempImage;
using (FileStream file = new FileStream(SaveLocation, FileMode.Create, FileAccess.Write))
{
MS.WriteTo(file);
file.Flush();
file.Close();
TempImage = Image.FromStream(MS);
}
MS.Close();
return TempImage;
}
}
Al igual que en el caso anterior, tomamos todo el código que ya teníamos y lo usamos tal cual estaba, solo que este caso, devolvemos un valor del tipo Image que contiene la imagen que extrajimos. La función recibe dos parámetros: uno del tipo byte[] que es el que contiene todos los datos leídos previamente desde el Stream donde se manda a llamar esta función y un valor del tipo string que contiene la ruta en donde se guardará la imagen (en adición a la imagen que devuelve la función).
El código nuevo.
La solucion es un tanto sencilla, solo debemos de tomar cómo nombre de archivo algo que tengan en comun los archivos que no sea el artista. El album es un buen identificador, porque bastará que extraigamos una sola imagen para cubrir los 20 archivos.
Para poder usar el nombre del album cómo nombre de archivo para la imagen, primero debemos de saber cual es el nombre del album. Igual que con la imagen, dentro del tag ID3 hay un frame que describe la información para el album. En el caso de la version 2.3 es la siguiente:
Text information frames The text information frames are the most important frames, containing information like artist, album and more. There may only be one text information frame of its kind in an tag. If the textstring is followed by a termination ($00 (00)) all the following information should be ignored and not be displayed. All text frame identifiers begin with "T". Only text frame identifiers begin with "T", with the exception of the "TXXX" frame. All the text information frames have the following format:Y para la version 2 es la siguiente:
<Header for 'Text information frame', ID: "T000" - "TZZZ", excluding "TXXX" described in 4.2.2.>
Text encoding $xx Information
text string according to encoding
The text information frames are the most important frames, containing information like artist, album and more. There may only be one text information frame of its kind in an tag. If the textstring is followed by a termination ($00 (00)) all the following information should be ignored and not be displayed. All the text information frames have the following format:Entonces sabiendo lo anterior podemos crear una código cómo el que sigue:
Text information identifier "T00" - "TZZ" , excluding "TXX", described in 4.2.2.
Frame size $xx xx xx
Text encoding $xx
Information <textstring>
private string DataToAlbum(byte[] Data)
{
if (Data[0] == 0)
return Encoding.ASCII.GetString(Data, 1, Data.Length - 1);
if (Data[0] == 1)
return Encoding.Unicode.GetString(Data, 1, Data.Length - 1);
else
return null;
}
La función se debe de mandar a llamar cuando ya tenemos separados los datos de este frame, es decir, primero hay que determinar que tipo de tag ID3 es, luego buscar "TALB" o "TAL" dependiendo de la versión y leer los datos de la misma forma que lo hicimos para el frame "APIC" o" PIC", esto se puede hacer dentro del mismo código que lee los datos para buscar la imagen y ya una vez que se encuentran, simplemente le pasamos la información a la función.
![]() |
| Es importante usar la codificación correcta. Si no lo hiciéramos, no podríamos leer "ドリドリ" que es el álbum de esta canción. |
Es importante leer la codificación, porque para este caso, los caracteres del japones, no están disponibles en el estandar ASCII, Cuando el byte que nos indica que codificación se usa es 1, hay que recordar que la codificación es UNICODE, cuando es 0 es ASCII.
Y bien, por ahora solo queda pendiente la extracción por lotes, pero será en el siguiente post.
Los leo luego.
Vamos a programar #17 - Extraer una imagen embebida en un 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>
El código para la versión 2.
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();
}
}
}
}
![]() |
| 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!!!}) |
Los leo luego.
Vamos a programar #15 - Extraer una imagen embebida en un MP3
- Nombre de la canción.
- Álbum.
- Artista.
- Año.
- Genero.
Etiquetas 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.2Lo 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í.
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>
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.




































