Xwork's Blog

The lord is waiting to take your hand.

Un pequeño receso.

Hola a todos, solo escribo el día de hoy para avisar que el blog se toma un pequeño tiempo fuera, reanudaremos el día jueves 6 de octubre del 2016 con un post doble (o triple), les agradezco a todos por su paciencia.
Además aprovecho para recordarles que el reto de Xwork aun sigue abierto, hay algunos que me han mandado las respuesta al primer (y por ahora único) desafío de la pagina, pero hasta ahora ninguno a acertado en el. Si tu logras descifrarlo, te ganarás un lugar en el salón de la fama de Xwork.
Actualmente tengo una carga de trabajo brutal, pero si me desocupo antes, publicaré el numero 16 de la sección: "Vamos a programar".

Por ahora es todo, gracias y 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.


Vamos a programar #14 - Un poco de MatLab

Hola de nuevo a todos, el día de hoy vamos a hacer una pausa a VEncoder y al código de C# para ver un poco de otro lenguaje.
MATLAB (abreviatura de MATrix LABoratory, "laboratorio de matrices") es una herramienta de software matemático que ofrece un entorno de desarrollo integrado (IDE) con un lenguaje de programación propio (lenguaje M). Wikipedia/MatLab


Recientemente una persona me pidió ayuda para hacer algunos scripts que le pedían en la universidad, a pesar de que nunca antes lo había manejado, resulto relativamente fácil empezar a hacer los scripts básicos con los que generalmente se empieza en cualquier lenguaje de programación (llámese "hola mundo").
Lo bueno del scripting de MatLab es que la sintaxis es muy similar a C y VB, de hecho desde mi punto de vista es una mezcla de ambos, entonces eso me facilito un poco comprender como hacer lo sencillo. Cosas especificas de MatLab requieren un poco más de tiempo. MatLab es una herramienta realmente potente y sin ningún esfuerzo se puede empezar con lo básico, pero eso si, para dominarlo tendrás que dedicarlo un buen rato.

Ahora vayamos a lo que quería mostrarles. Hace algunos días publiqué esto:

Si miras la imagen (ignorando la parte de pokemon), notarás que es un script que sirve para calcular los años, meses y días que han transcurrido desde una fecha dada hasta la fecha actual que MatLab obtenga del sistema en el que se ejecuta.
Cuando me lo plantearon, sabia que este tipo de problemas representan un problema para aquellos que apenas inician en el mundo de la programación, este tipo de problemas puede ser un poco "truculento" y cuando le pedí a muchos que los resolvieran, siempre cometían el mismo error.

Resolviendo el problema.

El código en MatLab para calcular las diferencia entre dos fechas es el siguiente:

%%Código por Xwork
%Calcula la edad
clear all
clc
BornDay = input('Que dia naciste?\n');
BornMonth = input('Que numero de mes naciste?\n');
BornYear = input('De que año?\n');
if mod(BornYear,4) == 0
days = [31 29 31 30 31 30 31 31 30 31 30 31];
else
days = [31 28 31 30 31 30 31 31 30 31 30 31];
end
%AA MM DD
ActualDate = fix(clock);

%Calcular años
TotalYear = ActualDate(1) - BornYear;
%Calcular los meses
TotalMonth = ActualDate(2) - BornMonth;
%calcular los dias
TotalDays = ActualDate(3) - BornDay;
if TotalDays < 0
    TotalMonth = TotalMonth - 1;
    if TotalMonth < 0
        TotalYear = TotalYear - 1;
        TotalMonth = TotalMonth + 12;
    end
    TotalDays = days(BornMonth) + TotalDays;
end
if TotalMonth < 0
   TotalYear = TotalYear - 1;
   TotalMonth = TotalMonth + 12;
end
fprintf('Tienes %g Años(s), ',TotalYear)
fprintf('%g Mes(es), ',TotalMonth)
fprintf('%g Dia(s)',TotalDays)

El código es realmente simple, primero le pedimos al usuario que introduzca la información requerida en formato numérico; es decir: se usara el numero de mes en lugar del nombre; ahora supongamos una fecha importante 28/12/1993; se introducirá de la siguiente manera:

  • Que dia naciste?
  • 28
  • Que numero de mes naciste?
  • 12
  • De que año?
  • 1993
Con los datos introducidos, el script avanzará y primeramente encontrará un if que sirve para determinar si un año es bisiesto o no, esto se logra dividiendo el año de nacimiento entre 4 (solo por si acaso, recuerda que los años bisiestos ocurren cada 4 años y se le agrega un día mas a febrero), esto solo lo hacemos para agregar un poco mas de precisión. Después se crea una matriz que contiene la cantidad de días para cada mes; enero tiene 31 días, febrero tiene 28 días normalmente, pero en año bisiesto 29, incluso se podía crear la matriz afuera del if y ya dentro solo editar los días para febrero, pero preferí dejarlo así para obviarlo un poco.
Luego obtenemos la fecha actual con la función clock, pero ademas usamos la función fix para que los valores dentro de la matriz no estén en decimales.
Si llamamos a la función clock esta devolverá la siguiente matriz

   1.0e+03 *
    2.0160    0.0090    0.0050    0.0140    0.0060    0.0513

La primera linea nos indica que todos los valores dentro de la matriz se deben de multiplicar por 1.0e+03, luego los valores de la matriz en orden de izquierda a derecha representan:

  • Año.
  • Mes.
  • Día.
  • Hora
  • Minuto
  • Segundo
Al usar la función fix en la matriz obtenemos los siguiente:

ans =
2016           9           5          14           6          51

Luego vienen los "cálculos". en primer lugar restamos directamente el año de nacimiento (o de la fecha que se quiera) y esto nos daría la diferencia en años que hay entre las dos fechas. Antes de proseguir, debo de mencionar que asi como esta, es propenso a errores, suponiendo que es el año 2016 y alguien pone que nació en el año 2020 obviamente no es posible, pero el codigo asi como esta ahorita devolvería -4, que técnicamente está bien, pero para términos de edad; "menos algo" no es válido.
Para los meses hacemos exactamente lo mismo que con los años, hacemos la resta directamente entre la fecha actual y la fecha de nacimiento (o de lo que sea), en este caso si es posible que nos resulte un numero negativo, supongamos que el mes actual es el mes 10 y el mes de nacimiento es el 12; al hacer nuevamente el calculo obtenemos un valor de -2, por ahora conservaremos ese valor asi como esta.
Luego hacemos el calculo de los días,para eso repetimos lo anterior y al igual que los meses, podemos esperar un resultado negativo, luego procedemos a ajustar los días.
Primero comprobamos si la cantidad de días son menores que 0; si es así restamos a la cantidad de meses que teníamos almacenados y dentro de esa misma comprobación si al momento de restarle a la cantidad de meses, está resulta en un numero negativo, procederemos a restarle 1 a la cantidad de años. Finalmente obtenemos los días agregando los días que contiene el mes al calculo de días que teníamos almacenado.
Luego volvemos a hacer otra comprobación para la cantidad de meses, ya que en el if anterior primero se comprueban los días pero no necesariamente va a entrar a esa condición y como tenemos almacenados los meses directamente como los dio el resultado del calculo previo. Para evitar números negativos le agregamos 12 (cantidad de meses que tiene el año).
Y ya solo resta mostrar el resultado al usuario.

Una explicación un tanto mas humana.

De nuevo tomemos una fecha, la misma que para la explicación anterior: 28/12/1993 y el dia actual es 5/9/2016

  1. 2016-1993=23
  2. 9-12=-3
  3. 5-28=-23
  4. Como el resultado de la resta de los días es negativo, restamos 1 a los meses y nos queda -4. Cómo el resultado es negativo; a los años le restamos 1 y queda 22, luego sumamos 12 a la cantidad de meses que tenemos almacenados y resulta 8. Hacemos la suma de los días que contiene el mes de nacimiento y el numero que tenemos almacenado para los días; diciembre tiene 31 días +(-23)=8
  5. El resultado: 22 años, 8 meses y 8 días.
Hay que entender como usamos los datos, para el caso de los días veamos lo siguiente. para nuestro caso, el dia es 28 de diciembre, este mes tiene 31 días, para "cerrar" al mes, faltan 3 días + 5 que han transcurrido del mes, esto nos da 8, lo mismo para lo meses.

Y bueno por ahora es todo. Dudas o bugs que quieras reportar, no dudes en hacerlo.

Los leo luego.

Vamos a programar #13 - El código de VEncoder 2

El día de hoy vamos a continuar con la revisión del código de VEncoder,

Funcion DoWork.

El Código de la función DoWork es el siguiente:

/// <summary>
         /// Inicia la conversion de los archivos
         /// </summary>
         /// <param name="Params">Una lista del tipo string que contiene las lineas de comandos a usarse</param>
         /// <param name="Durations">Una lista del tipo int que contiene las duraciones de los archivos</param>
         
         private void DoWork(List<string>  Params, List<int> Durations)
         {
             Thread DoConversion = new Thread(new ThreadStart(() => {
                                                                  int CD = 0;
                                                                  foreach (string Param in Params)
                                                                  {
                                                                      if (RequestForCancel == true)
                                                                      {
                                                                          break;
                                                                      }
                                                                      
                                                                      int TotalPercentConvert = CD * 100;
                                                                      int CurrentDuration = Durations[CD];
                                                                      Process Proc = new Process();
                                                                      StreamReader FFResult;
                                                                      string CurrentResult;
                                                                      ProcessStartInfo FFProcess = new ProcessStartInfo("ffmpeg.exe");
                                                                      FFProcess.Arguments = Param;
                                                                      FFProcess.UseShellExecute = false;
                                                                      FFProcess.WindowStyle = ProcessWindowStyle.Hidden;
                                                                      FFProcess.RedirectStandardError = true;
                                                                      FFProcess.RedirectStandardOutput = true;
                                                                      FFProcess.CreateNoWindow = true;
                                                                      Proc.StartInfo = FFProcess;
                                                                      Proc.Start();
                                                                      PBProgress.BeginInvoke(new Action(() => PBProgress.Maximum = CurrentDuration));
                                                                      PBGlobalProgress.BeginInvoke(new Action(() => PBGlobalProgress.Maximum = Durations.Count * 100));
                                                                      FFResult = Proc.StandardError;
                                                                      do
                                                                      {
                                                                          if (RequestForCancel == true)
                                                                          {
                                                                              Proc.Kill();
                                                                              break;
                                                                          }
                                                                          
                                                                          CurrentResult = FFResult.ReadLine();
                                                                          Debug.Print(CurrentResult);
                                                                          this.BeginInvoke(new Action(() => {
                                                                                                          this.lblProgressInfo.Text = TimeSpan.FromSeconds(GetPositionFromFFline(CurrentResult)).ToString();
                                                                                                          this.PBProgress.Value = (int)GetPositionFromFFline(CurrentResult);
                                                                                                          this.PBGlobalProgress.Value = TotalPercentConvert + (int)Porcentaje(GetPositionFromFFline(CurrentResult),CurrentDuration);
                                                                                                          TaskbarProgress.SetValue(this.Handle, this.PBGlobalProgress.Value, this.PBGlobalProgress.Maximum);
                                                                                                      }));
                                                                      }while (Proc.HasExited == false & string.Compare(CurrentResult, "") == 1);
                                                                      CD = CD + 1;
                                                                      ElapsedTimeCurrent = 0;
                                                                  }
                                                                  IsConvertingSomething = false;
                                                                  RequestForCancel = false;
                                                                  ElapsedTimeCurrent = 0;
                                                                  ElapsedTimeGlobal = 0;
                                                                  this.BeginInvoke(new Action(() => {
                                                                                                  this.lblProgressInfo.Text = "Esperando";
                                                                                                  this.PBProgress.Value = 0;
                                                                                                  this.PBGlobalProgress.Value = 0;
                                                                                                  TaskbarProgress.SetState(this.Handle, TaskbarProgress.TaskbarStates.NoProgress);
                                                                                              }));
}));

DoConversion.Start();

}



La función DoWork inicia la ejecución de FFMpeg indicándole todos los parámetros para realizar la conversión de todos los archivos que se contenían en la lista, recibe dos parámetros: el primero, una lista del tipo string; que contiene todas las lineas de comandos generadas previamente con la función BuildCommandLine junto con la funcion PrepareWork.
El segundo parámetro es una lista del tipo int, que contiene la duración de los archivos en segundos.
Esta función se ejecuta dentro de la funcion PrepareWork  si se pasa el parámetro booleano HardWork cómo true.
Para la ejecución de FFMpeg se usa un Thread y se implementa de forma básica, dentro de la ejecución de este hilo, creamos un proceso que sera el encargado de abrir FFMpeg, ademas de que le diremos que todo la salida la redireccione a nuestro programa para poder extraer los datos que necesitamos.
Debido a que la conversión se hace en lotes, se usa un ciclo for para recorrer todos los elementos contenidos en la lista "Params" y ya dentro de ese mismo ciclo, se usa un bucle while que estará activo mientras nuestro proceso (ffmpeg) no haya finalizado o mientras el proceso no devuelva una cadena vacía.
Para cancelar enteramente el proceso usamos dos comprobaciones, una de ellas en el ciclo for y otra en el bucle while, esto para poder detener en cualquier parte. Si la interrupción se da cuando se esta ejecutando el bucle while, al salir de este continuara en el ciclo for y nuevamente iniciaría el bucle while si aun quedan elementos en la lista Params, entonces para evitar esto, lo único que hacemos es crear las interrupciones en ambos lugares.
Todas las instrucciones "invoke" se usan para interactuar con la interfaz principal, siempre le resulta al usuario útil saber cuanto es el progreso que se lleva.
En esta función se usa de forma muy básica todas las características de la clase Thread, en realidad es muy potente, pero para fines prácticos solo usamos lo mas importante: iniciar y detener Threads, analizando todos los elementos contenidos en esta clase, podemos encontrar que se puede tener un control más preciso de cada Thread: su estado, si responde o no, etc.
Lo mismo sucede con la clase Procces, pero por ahora solo usamos los métodos que están implementados en la función.

Funcion SetInfo.

El codigo de la funcion SetInfo es el siguiente:


         /// <summary>
         /// Obtiene la informacion de los archivos de entrada
         /// </summary>
         /// <param name="Files">Archivos de los que se obtendra la informacion.</param>
         
         private void SetInfo(List<string> Files)
         {
             Thread GetInfo = new Thread(new ThreadStart(() => {
                                                             foreach (string Archivo in Files)
                                                             {
                                                                 ListViewItem InfoItem = new ListViewItem(Archivo);
                                                                 MediaFile mFile = new MediaFile(Archivo);
                                                                 InfoItem.SubItems.AddRange(new string[] { mFile.Video[0].FrameSize.ToString(),mFile.Video[0].FrameRate.ToString(),
                                                                                                mFile.Video[0].DurationString,mFile.Video[0].Bitrate.ToString(),mFile.Audio.Count.ToString(),
                                                                                                mFile.Audio[0].SamplingRate.ToString(),mFile.Audio[0].Bitrate.ToString(),mFile.Audio[0].Channels.ToString()});
                                                                 lvfilesin.BeginInvoke(new Action(() => lvfilesin.Items.Add(InfoItem)));
                                                             }
                                                             this.BeginInvoke(new Action(() => TaskbarProgress.SetState(this.Handle,TaskbarProgress.TaskbarStates.NoProgress)));
                                                         }));
             GetInfo.Start();
         }

La función SetInfo se encarga de obtener la información de los archivos. La información incluye: Tamaño, resolución, cuadros por segundo, aspecto, velocidad de bits, etc.
Recibe como parámetro una lista del tipo string que contiene las rutas de los archivos de los cuales se quiere obtener la información.
Al igual que en la función anterior, se usa un Thread para hacer este proceso, esto debido a que cuando se procesan algunos archivos, dependiendo de sus características, puede tomar un poco de tiempo para obtener la información, y esto causaría que la interfaz principal se bloquee, para evitar esto, se inicia un thread diferente y se usa el TaskBar de windows para mostrar el progreso. Hay resaltar que debido a que se usan características disponibles a partir de Windows vista, no es posible hacer compatible la aplicación con versiones anteriores a vista.
Finalmente esta función agrega toda la información a lvfilesin, que es un ListView de la interfaz principal.

Y bien, esas son todas las funciones que componen a VEncoder, como verás, no es tan complejo y el resultado es muy útil, en el siguiente Post vamos a implementar una nueva función que apagará el equipo cuando todas la conversiones se hayan realizado.

Por ahora es todo, nuevamente les agradezco por el interés en el código de VEncoder y por el apoyo recibido en el post anterior.

Los leo luego.

El sabor de la decepción.

Hola a todos, el día de hoy voy a hacer una pausa a la revisión del código de VEncoder, que por cierto, quiero agradecerles por la aceptación de los post. Quiero contarles algo que recientemente me paso (de hecho fue la gran decepción para un gran numero de personas, no solo para mi).
Usualmente nunca suelo entusiasmarme mucho con nada, la mayoría de las cosas que los otros encuentran divertidas, a mi me resultan aburridas, un ejemplo muy claro es el fútbol, en mi país la mayor parte de la gente es fanática de ese deporte, yo nunca entendí porque el hecho de ver triunfar a alguien más, podía darnos una gran satisfacción.

La explicación es realmente simple y tiene que ver con cuestiones psicológicas-sociales, pero para no ponerme muy profundo, la explicación breve es la siguiente: el hecho de ver a alguien más siendo capaz de hacer algo que nosotros pudiéramos considerar imposible, resulta satisfactorio, de alguna forma nos da esa sensación de "yo puedo hacerlo". Pero la cosa no solo aplica para los deportes; yo siempre me pregunte ¿cómo le hizo Einstein?, ¿cómo le hizo Sir Isaac Newton? Eso me llevo a estudiarlos un poco más y tras horas y horas de leer, llegué a la simple conclusión de que yo quería ser como ellos, al menos siquiera aspirar a lograr algo como lo que ellos hicieron, después de todo; si ellos lo lograron, ¿por que yo no?.
Pero bueno, retomando lo original, nunca me sentí entusiasmado por nada de lo "común", pero hace poco (algunos años en realidad), surgió algo que nunca me esperé, Yo iba en la vida haciendo lo que siempre hacia sin darme cuenta de que ese "algo" de forma sutil, se empezaba a arraigar cada vez más. El tiempo siguió pasando y pasando, cada vez empecé a notar que me preguntaba: ¿que pasaría si...?
Cada vez más pensaba en aquello de lo que hace poco era ajeno a mi. Una vez que me di cuenta de que ya estaba tan "contaminado" de eso, había llegado el momento de actuar, ¿que debía de hacer?, no era algo que aportara o restara cosas útiles a mi vida, tenerlo o no, no resultaba tan importante en ese momento.
No le di tanta importancia, si no lo pensaba tanto, la respuesta mas obvia, seria la que escogería, de forma inconsciente. Y así fue cómo sucedió, de forma tan progresiva y lenta, sin darme cuenta, empece a estar al tanto de "eso". Tanto fue lo que penetro en mi mente , que sin darme cuenta ya estaba especulando sobre lo que podría pasar, inventando escenarios y creando mundos ideales en los que todo esto podía acabar.
Pero no todo en la vida es perfecto, empezaron a ocurrir situaciones que resultaban un tanto molestas. La primera de ellas ocurrió hace un par de años, con antelación mencionaron un evento que marcaría un antes y después en todos los eventos relacionados, y cómo ocurre con toda primera vez, trate de no entusiasmarme demasiado, lo tome con calma. Todo ocurrió de forma un tanto confusa, a pesar de que lo que pasó no era lo que esperaba, o al menos no lo que yo mismo hube imaginado, lo deje pasar, después de todo resultaba en algo nuevo, fuera de lo normal para los estándares que siempre manejaba.
Casi un año después ocurrió algo similar, pero en esa ocasión ya estaba preparado para lo de siempre. Ocurrió lo mismo de la vez anterior, mas en esa ocasión, era lo que esperaba, lo que imagine en su momento.
Siempre he dicho que nunca hay que asumir, el resultado de un suceso aleatorio siempre es.... Aleatorio, siempre estoy consiente de eso, pero siempre hay alguien que se encargara de hacerte romper tus propios esquemas.


Empecé a notar que siempre esperaba lo mejor, todo entró en una etapa de altibajos, llegó una época de incertidumbre, pero el hecho de asumir, y que las cosas que se asumen ocurran, nunca es bueno. Ahora imagina, hay una tómbola con pelotas negras, rojas, verdes y azules, revuelves un poco la tómbola y en la primera ronda sale una bola roja, luego en la segunda ronda, también sale una roja, y así se mantiene hasta la cuarta ronda, lo más obvio que una persona haría, sería escoger la bola roja, y así pasa, en la cuarta ronda sale una bola roja y en la quinta, sexta, séptima; ya envalentonado, decides apostar todo en la ronda 8 y ganas, decides no retirarte y en la ronda 9 pones todo lo que tienes e incluso parte de lo que no. El resultado, perdiste todo porque asumiste que tu habías encontrado un patrón, cuando lo descubriste, pensaste que se mantendría y no fue así, creíste que te robaron, pero en realidad solo descubriste de forma amarga que lo único seguro en las cosas aleatorias es precisamente eso: todo es aleatorio. Y que pasa cuando hay alguien detrás de todo, un nombre y un rostro al cual culpar cuando todo sale mal, bueno eso ya depende de cada uno.
El ejemplo que mostré antes se adapta muy bien a lo que pasó, hubo tantos indicios que indicaban que el resultado que yo quera iba a pasar, que sin darme cuenta ya asumía que el resultado era auto-evidente. Yo que tantas veces le dije a los demás que nunca deberían de asumir, terminé por engañarme a mi mismo, imaginando cosas que yo quería que pasaran sin tomar en cuenta todo el contexto.
Aun confiado de lo que yo creía, espere una vez más a que las cosas pasaran, y finalmente pasó. No podía creer el resultado, todo era exactamente lo contrario a lo que imaginé, estaba profundamente decepcionado, era la primera vez en que algo me resultó tan extraño, ajeno a mi, a pesar de todo, en esencia era lo mismo, siempre lo fue, de hecho las cosas que yo imaginé en su momento, en realidad eran las cosas extrañas, que si hubieran pasado, simplemente le hubieran quitado lo que originalmente es.
Y bien , el resultado ya lo asimilé un poco y creo que fue lo mejor, aun queda un poco de esperanza, pero prefiero hacer lo que siempre hago: esperar a que ocurra lo de siempre y ya si pasa algo extraordinario, siempre será bienvenido. Ademas, a la mala logre entender a toda la "fanaticada" (a lo que sea)

Para no ponerme en evidencia, solo diré lo siguiente: El 18 de agosto fue un antes y un después.


Por ahora es todo, si lograste llegar hasta aquí, gracias por leer, significa un poco para mi el saber que a alguien más este ahí leyendo lo que pienso.

En el siguiente Post continuaremos con el código de VEncoder 2 y muy probablemente en unos días, invite a alguien más para que haga publicaciones en el blog.

Los leo luego.

Vamos a programar #12 - El código de VEncoder 2

El día de hoya vamos a continuar con la revisión del código de vencoder, en los post anterior ya vimos como es que el programa interactua con FFMpeg, el día de hoy veremos un par de funciones que también lo hacen.


Funcion PrepareWork



El código de la función PrepareWork es el siguiente:

/// <summary>
/// Prepara la linea de comandos y lo necesario para hacer la conversion
/// </summary>
/// <param name="HardWork">Parametro booleano que indica como se debe de hacer el trabajo</param>
/// <remarks>Si HardWork es true, todos lo parametros se pasaran a la conversion; en caso contrario,
/// solo se genera la linea de comandos para usarse de forma externa con FFMPEG</remarks>


private void PrepareWork(bool HardWork = false)

{

List<string> Lote = new List<string>();

List<int> DLote = new List<int>();

if (CHKAll.Checked == true)

{

foreach (ListViewItem LV in lvfilesin.Items)

{

if (LV.Checked)

{

if (CHK2pass.Checked == true)

{

Lote.Add(BuildCommandLine(LV.Text, CBOCodec.Text, CboResolucion.Text,

TXTVBit.Text, CBORatio.Text, CBOProfile.Text, "23.974", CBOBitsA.Text, CboFreq.Text, CBOAChannels.Text, TXTSavePath.Text, true, false, false));

DLote.Add(FixTimeDuration((int)TimeSpan.Parse(LV.SubItems[3].Text).TotalSeconds));

Lote.Add(BuildCommandLine(LV.Text, CBOCodec.Text, CboResolucion.Text,

TXTVBit.Text, CBORatio.Text, CBOProfile.Text, "23.974", CBOBitsA.Text, CboFreq.Text, CBOAChannels.Text, TXTSavePath.Text, false, true, false));

DLote.Add((int)TimeSpan.Parse(LV.SubItems[3].Text).TotalSeconds + 1);

}else{

Lote.Add(BuildCommandLine(LV.Text, CBOCodec.Text, CboResolucion.Text, TXTVBit.Text, CBORatio.Text, CBOProfile.Text, CboFreq.Text, CBOBitsA.Text, "44100", CBOAChannels.Text, TXTSavePath.Text, false, false, true));

DLote.Add(FixTimeDuration((int)TimeSpan.Parse(LV.SubItems[3].Text).TotalSeconds));

}

}

}

}else{

foreach (ListViewItem LV in lvfilesin.Items)

{

if (CHK2pass.Checked == true)

{

Lote.Add(BuildCommandLine(LV.Text, CBOCodec.Text, CboResolucion.Text,

TXTVBit.Text, CBORatio.Text, CBOProfile.Text, "23.974", CBOBitsA.Text,CboFreq.Text, CBOAChannels.Text, TXTSavePath.Text, true, false, false));

DLote.Add(FixTimeDuration((int)TimeSpan.Parse(LV.SubItems[3].Text).TotalSeconds));

Lote.Add(BuildCommandLine(LV.Text, CBOCodec.Text, CboResolucion.Text,

TXTVBit.Text, CBORatio.Text, CBOProfile.Text, "23.974", CBOBitsA.Text, CboFreq.Text, CBOAChannels.Text, TXTSavePath.Text, false, true, false));

DLote.Add(FixTimeDuration((int)TimeSpan.Parse(LV.SubItems[3].Text).TotalSeconds));

}else{

Lote.Add(BuildCommandLine(LV.Text, CBOCodec.Text, CboResolucion.Text,TXTVBit.Text, CBORatio.Text, CBOProfile.Text, CboFreq.Text, CBOBitsA.Text, "44100", CBOAChannels.Text, TXTSavePath.Text,false,false,true));

DLote.Add(FixTimeDuration((int)TimeSpan.Parse(LV.SubItems[3].Text).TotalSeconds));

}

}

}

if (HardWork)

{

TimerElapsed.Enabled = true;

DoWork(Lote, DLote);

}else{

foreach (String LineCom in Lote)

{

RTBCommandLine.Text = RTBCommandLine.Text + LineCom + "\n";

}

}

}

La función PrepareWork lo que hace es construir la linea de comandos para cada archivo que se encuentre en la lista, dependiendo si se le indicó al programa o no si se quiere hacer la conversion en dos pasadas, crea la linea de comandos lista para usarse.
La función PrepareWork recibe un parámetro del tipo booleano que indica que es lo que se debe de hacer después de crear todas las lineas de comandos. Si el valor del parametro HardWork se establece en true; al terminar de procesar todos los ajustes, llama a la funcón DoWork que es la encargada de iniciar la ejecución FFmpeg.
La construcción de la linea de comandos se realiza a la "mala"; es decir va recorriendo uno a uno los controles para obtener sus propiedades (Text por ejemplo). Ademas de que por ejemplo la cantidad de cuadros por segundo está fija, para evitar eso, en lugar de usar 23.974, podemos usar un TextBox en lugar de 23.974.
Esta función solo "recolecta" todos los datos y lo pone juntos, pero la función que realmente hace el trabajo sucio es la funcion BuildCommandLine.

Funcion BuildCommandLine.

El código de la funcion BuildCommandLine es el siguiente:


 /// <summary>
 /// Genera la linea de comados para usarse con FFMPEG
 /// </summary>
 /// <param name="InFile">Archivo de entrada</param>
 /// <param name="VCodec">Codec de video a usarse</param>
 /// <param name="VideoSize">Resolucion del video</param>
 /// <param name="VideoBit">Velocidad de bits del video</param>
 /// <param name="VAspect">Ratio para el video</param>
 /// <param name="VProfile">Perfil para usarse en el video</param>
 /// <param name="FrameRate">Cantidad de cuadros por segundo para el video</param>
 /// <param name="AudioBit">Veocidad de Bits para el audio</param>
 /// <param name="AudioFreq">Frecuencia de muestreo para el audio</param>
 /// <param name="AChannels">Numero de canales para el audio</param>
 /// <param name="OutPath">Donde se gurdara la salida; debe de ser una ruta a un carpeta</param>
 /// <param name="ForFirstPass">La linea de comandos se hará para la primera pasada de dos</param>
 /// <param name="AfterPass1">La Linea de comandos se hará para la segunda pasada</param>
 /// <param name="SinglePass">La linea de comandos se hara para una unica pasada</param>
 
 private string BuildCommandLine(string InFile,string VCodec,string VideoSize,string VideoBit,string VAspect,string VProfile,string FrameRate,string AudioBit,
                                string AudioFreq, string AChannels,string OutPath,bool ForFirstPass= false,bool AfterPass1=false,bool SinglePass= true)
 {
 if (ForFirstPass){
 return string.Concat("-y -i ","\"",InFile,"\""," -c:v ",VCodec," -s ",VideoSize, " -b:v ",VideoBit,"k -aspect ",VAspect," -profile:v ",VProfile," -threads 0 -r ",FrameRate,
                     " -level 4.1 -crf 22 -f mp4 -pass 1 -an NUL");
 }
 if(AfterPass1){
 return string.Concat("-y -i ","\"",InFile,"\""," -c:v ",VCodec," -s ",VideoSize, " -b:v ",VideoBit,"k -aspect ",VAspect," -profile:v ",VProfile," -threads 0 -r ",FrameRate,
                     " -level 4.1 -crf 22 -f mp4 -pass 2 -c:a libvo_aacenc -b:a ",AudioBit,"k -ar ", AudioFreq," -ac ",AChannels," \"",OutPath,"\\",GetFileName(InFile),".mp4","\"");
 }
 if (SinglePass) {
 return string.Concat("-y -i ","\"",InFile,"\""," -c:v ",VCodec," -s ",VideoSize, " -b:v ",VideoBit,"k -aspect ",VAspect," -profile:v ",VProfile," -threads 0 -r ",FrameRate,
                     " -level 4.1 -crf 22 -f mp4 -c:a libvo_aacenc -b:a ",AudioBit,"k -ar ", AudioFreq," -ac ",AChannels," \"",OutPath,"\\",GetFileName(InFile),".mp4","\"");
 }else{
 return "Test";
 }
 }

La funcion BuildCommandLine se encarga de crear la linea de comandos para usarse con FFMpeg, recibe 14 parametros y regresa un valor del tipo string con los datos listos para usarse.

  1. InFile. Es el archivo para el cual queremos crear la linea de comandos.
  2. VCodec. Es el codec que vamos a usar para el archivo, aunque ahora solo esta configurado para que la linea de comandos sea compatible  con el codec X264, mas adelante la modificaremos para agregar soporte a otros codecs (Xvid,MPEG4, etc)
  3. VideosSize. Es la resolución que tendrá el vídeo, puede ser cualquier valor que tenga la forma WWWWxHHHH, donde W es el largo y H es el ancho (1280x720 es un valor valido).
  4. VideoBit. Es la cantidad de bits que se usaran por segundo.
  5. VAspect. Indica el aspecto que deberá tener el vídeo (16:9).
  6. VProfile. Es el tipo de perfil que debe de tener el vídeo.
  7. FrameRate. Sirve para indicar la cantidad de cuadros por segundo que se deben de usar.
  8. AudioBit.Cantidad en bits para el audio
  9. AudioFreq. La frecuencia del audio en Hz.
  10. AChannels. Cantidad de canales de audio que debe de tener el vídeo.
  11. OutPath. La ruta en donde se guardaran los vídeos.
  12. ForFirstPass. Este parámetro indica como se crea la linea de comandos, si el parámetro es true, se crea la linea de comandos para la primer pasada
  13. AfterPass1. Este parámetro indica como se crea la linea de comandos, si el parámetro es true, se crea la linea de comandos para la segunda pasada
  14. SinglePass. Crea la linea de comandos para proceso de una sola pasada.
Al igual que la función anterior está diseñada para hacer la linea de comando por un archivo a la vez, la función anterior, usaba está como base y creaba la linea de comandos para todos los archivos. Al usar la función, se debe de tener cuidado con los valores que se usan al final (parametros), por ejemplo si se usa lña linea de comandos para hacer la conversion de un video en una sola pasada, los 3 parametros finales, no pueden establecerse en true, ya que modificaría valores que ya se tenían previamente.
La forma mas adecuada para manejar para que tipo de "pasada" la linea de comandos es crear una enumeracion y definir solo los casos posibles por ejemplo:

private enum CommandType

{
ForPass1,
ForPass2,
ForSinglePass
}


Y solo restaría adaptar la condiciones para cada caso.
Bien, por ahora es todo, recomiendo revisar el código para que entiendas de lo que hablo.
Los leo luego.

Vamos a programar #11 - El código de VEncoder 2

El día de hoy vamos a continuar revisando el código de VEncoder2, la vez anterior ya habíamos revisado el código de las primeras 4 funciones. Normalmente vemos las funciones de 2 en 2 pero hoy, vamos a ver un poco mas debido a que algunas de las funciones son realmente simples.


Funcion Porcentaje

El código de la funcion porcentaje es el siguiente:

/// <summary>
/// Calcula porcentaje
/// </summary>
/// <param name="ValorActual">Valor actual para hacer el calculo</param>
/// <param name="ValorTotal">Valor total para hacer el calculo</param>
/// <returns>Un numero que representa el valor del porcentaje</returns>
private Single Porcentaje(Single ValorActual, Single ValorTotal)

{
return (ValorActual / ValorTotal) * 100;
}

Lo único que hace esta función es regresar el valor equivalente al porcentaje actual, recibe dos parámetros del tipo Single ValorActual y ValorTotal, y después solo hace la división entre ValorActual y ValorTotal, luego lo multiplica por 100, el valor resultante es el porcentaje de ValorActual con respecto a ValorTotal.


Funcion GetFileName

El código de la función GetFileName es el siguiente:
/// <summary>
/// Obtiene el nombre de archivo desde una ruta completa
/// </summary>
/// <param name="FileName">La ruta de la cual se quiere obtener el nombre de archivo</param>
/// <returns>Un string con el equivalente al nombre de archivo</returns>
/// <remarks>El valor devuelto no incluye la extension</remarks>

private string GetFileName(string FileName)
{
string[] TMPData = FileName.Split((char)92);
return TMPData[TMPData.Length - 1];
}


La funcion GetFileName sirve para obtener el nombre de archivo de una ruta completa.es decir, si tenemos la ruta a  un archivo cómo la siguiente: "c:\users\Ra\Desktop\Mi video.mp4" al usar la función obtenemos "Mi video.mp4". Esta función recibe un parámetro del tipo string, después la separa usando la función "split", la función split recibe como parámetro del tipo char en este caso es "(char)92", lo que hace es convertir el numero 92 a el tipo char y es equivalente a "\" (backslash). Se usa la variable TMPData  para almacenar el resultado de la función split que aplicamos sobre el parámetro de entrada. Finalmente solo regresa el ultimo valor de la matriz.
A lo mejor puedes pensar que haciendo eso nos complicamos demasiado porque dentro de las fucniones que vienen incluidas en .NET, dentro del espacio de nombre System.IO hay una funcion que hace exactamente lo mismo. Path.GetFileName hace lo mismo, pero debido a que es codigo que vengo arrastrando desde VB6 (solamente adaptado a C#), preferi incluirlo, pero sientete libre de usar la función que mas quieras.

Funcion GetPositionFronFFLine.

El código de la funcion GetPositionFromFFLine es el siguiente:

         /// <summary>
         /// Obtiene el valor de tiempo desde una linea de resultado de ffmpeg
         /// </summary>
         /// <param name="FFText">Linea proveniente de FFMPEG</param>
         /// <returns>Regresa un numero con el valor en segundos</returns>
         
         private Single GetPositionFromFFline(string FFText)
         {
             try
             {
                 if (FFText.StartsWith("FRAME", StringComparison.OrdinalIgnoreCase)) {
                     string PreResult;
                     PreResult = FFText.Split((char)61)[5].ToString().Trim();
                     return (Single)TimeSpan.Parse(PreResult.Remove(PreResult.Length - 7, 7).Trim()).TotalSeconds;
                 }else{
                     return 0;
                 }
             } catch (Exception) {
                 return 1;
             }
         }

La función GetPositionFromFFLine extrae el tiempo actual de la conversion, para entender como funciona, primero echemos un vistazo a una linea de resultado de FFmpeg.

 frame= 2528 fps=167 q=27.0 size=    5246kB time=00:01:43.27 bitrate= 416.1kbits/s 
La anterior es una linea de ffmpeg cuando esta haciendo una conversión, ahora si la observamos bien, vemos que esta compuesta por 6 partes:

  1. frame= 2528, indica el frame actual de la conversión.
  2. fps=167, cantidad de cuadros por segundo a la que se hace la conversión.
  3. q=27.0, calidad
  4. size = 5246, tamaño en Kbytes.
  5. time = 00:01:43.27, tiempo actual de la conversión
  6. bitrate= 416.1kbits/s , bitrate actual.
La parte que nos interesa es la parte que marca la posicion del tiempo en la que se encuentra la conversión. para eso lo que hacemos es pasar la linea de ffmpeg (mas adelante vermos como redirigir la salida de ffmpeg a nuestro programa) cómo parámetro a la función GetPositionFromFFLine.
Primero comprueba que la línea empiece con "FRAME", luego, separa la parte del tiempo, en este caso al usar la funcion split con parametro "(char)61" (simbolo de igual "="), luego convierte el tiempo a segundos y lo regresa como resultado.
Obviamente esta funcion no es a prueba de errores, de hecho aumimos que el tiempo estará formateado de la forma HH.MM.SS.CC, pero sei en algun momento alguien intentara convertir un vide de 100 horas de duracion, VEncoder simplemente marcaría un error.
Igual que la funcion anterior, no esta optimizada, lo ideal seria usar expresiones regulares, pero por ahora esto, más adelante actualizaremos todas las funciones.

Por ahora es todo. Los leo luego.

Vamos a programar #10 - El código de VEncoder 2

El día de hoy vamos a continuar con el código fuente de VEncoder 2, la última vez, analizamos las primeras dos funciones, estas se encargaban de cargar y guardar los archivos VEPX, han pasado algunos días, y si no has revisado el código fuente aún, sugiero que lo revises antes de continuar con el resto.

Función FixTimeDuration

El código de la funcion FixDuraction es el siguiente:

private int FixTimeDuration(int Duration)
{
  if (Duration < 3600)
  {
    return Duration + 1;
    }else{
    return Duration + 60;
  }
}


Cuando obtenemos el tiempo usando la librería MediaInfoNet, a veces regresa el tiempo de forma redondeada hacia abajo, por ejemplo; un vídeo que dura 01:05:35.21, en algunas ocasiones si es que el vídeo no tiene la información correcta, regresara solo el valor "entero" de los segundos; tómese el ejemplo anterior, el tiempo 01:05:35.21 al pedirle a MediaInfoNet regresaría el valor de 01:05:35.00; al asignar las propiedades a los controles ProgressBar, sin el ajuste al pasar del tiempo 01:05:35.00 a 01:05:35.01, provocaría un error.

La función FixTimeDuration, corrige el error agregando 1 o 60 segundos, si la duración del vídeo es menor a una hora, solo agrega un segundo; en caso contrario agrega 60 segundos a la duración total del vídeo.
Ahora te preguntaras: ¿por que 60, por que 1?, debido a que pierde precisión cuando el tiempo pasa de una hora y redondea los minutos hacia abajo; es decir, el tiempo 01:01:40.32, directamente MediaInfoNet, lo devuelve como 01:01:00.00. Entonces para evitar eso, usamos esta función.

La función recibe un parámetro del tipo int que es equivalente a la duración del vídeo en segundos. Regresa un valor del tipo int que es el equivalente a la duración del vídeo en segundos +1 o +60 dependiendo del caso.

La forma de solucionarlo es obtener primero el tiempo usando el mismo FFMpeg, analizar la línea en donde nos proporciona el tiempo y solo asignarlo, pero los cambios los veremos después, ahora pasemos a la siguiente función.

Funcion TiempoRestante

El codigo de la funcion TiempoRestante es el siguiente:

         private Single TiempoRestante(Single Porciento,int TransTime)
         {
             Single Z;
             //z = tiempo gastado por cada 1%
             if (Porciento == 0)
             {
                 Porciento = 1;
             }
             Z = TransTime / Porciento;
             return (100 - Porciento) * Z;
         }


Esta funcion se encarga de obtener el tiempo restante para un proceso, para usarlo, se le pasan dos argumentos; uno del tipo Single y una del tipo int. Es realmente simple, pero hablemos un poco más acerca de esto. como muchos sabrán, cuando hablamos de cálculos de tiempo, en computación no suelen ser tan fáciles. Y para este caso no es la excepción.
Para empezar analicemos un poco cual es la "ecuación" para calcular el tiempo restante. Supongamos que tenemos hambre y nos da por comer manzanas, comemos 1 y nos sentimos 20% satisfechos, para saber cuantas manzanas necesitamos comer, lo que debemos de hacer es dividir lo que nos falta para saciarnos entre la cantidad que nos llena una manzana; retomando la metáfora anterior, si una manzana nos llenó en un 20%, significa que nos resta el 80%, dividiendo queda que necesitamos 4 manzanas más para saciarnos.
Para la función es relativamente lo mismo, lo primero que hacemos es obtener cuanto tiempo nos llevo avanzar un 1%, después lo que hacemos es descontar el porcentaje actual del 100% y lo multiplicamos por el tiempo que lleva cada 1%,  ¡Simple!., lo sería pero no todo en la vida es fácil, al igual que  si tratáramos de determinar cuantas manzanas bastaran para saciar tu hambre, podemos decir un estimado, pero no una cantidad exacta, lo mismo pasa con el calculo del tiempo para procesos de este tipo, podemos estimar el tiempo aproximado restante, pero no tendremos una certeza del 100%. Ademas cuando se hacen las conversiones; en el caso concreto de VEencoder2, se le puede indicar al programa que queremos hacer la conversión en dos pasos. Y siempre la primera es hasta 10 veces más rápida, entonces determinar el tiempo necesario para todo el proceso de conversión en un solo archivo resulta difícil, ahora para 10 archivos será peor. Entonces esta funcion cumple su objetivo ya que devuelve el tiempo estimado del proceso que se ejecuta, en nuestra aplicación, regresa el tiempo para cada "pasada". Resulta en un "placebo" para el usuario, pero sirve ya que en mi caso (supongo que no soy el unico) saber más o menos cuanto tiempo falta para algo, ayuda un poco.

Bueno por ahora es todo en el siguiente post continuaremos con las revisiones a las funciones de VEncoder2, recuerda que recomiendo revisar el código fuente para que resulte más fácil entender de lo que hablo.

Los leo luego

Vamos a programar #9 - El Código de VEncoder 2.

El día de hoy; y como lo prometí hace algunos días, les traigo el código fuente de VEncoder 2.
Las entregas se van a repartir en las siguientes:

  1. El código de VEncoder2.
  2. Porqué funciona cómo funciona.
  3. Agregando funciones que necesito.
  4. Errores, Intentando arreglarlos.

El Código de VEncoder2.

Lo primero que vamos a hacer es echarle un vistazo al código de fuente, tratare de explicar un poco que es lo que hace cada parte del código. Algo importante que debo mencionar es que el código ya tiene "sus ayeres", originalmente estaba planeado para uso personal, así que el código puede estar "sucio". Si eres algo curioso, de seguro te preguntaras ¿VEncoder2, donde esta el 1?, la respuesta es simple, nunca vio la luz del sol, y no porque fuera malo u otra cosa, simplemente no se me ocurrió que le resultara interesante a alguien más a parte de mí, de hecho la idea de hacer un blog surgió a partir de alguien a quien siempre le agradeceré... Pero eso es otra historia, sigamos en los que estábamos.

Inicialmente estaba pensado para convertir los vídeos y que fueran compatibles con el PSP y PSvita

Abriendo el proyecto.

Lo primero que debemos de hacer es descargar el proyecto (aquí) y abrirlo con #Develop o visual studio. Para poder usrlos, primero debemos de agregar un par de referencias a los siguientes ensamblados: MediainfoNet.dll y ListviewEx.dll (incluidas en la descarga). La primer librería solo se trata de un "wrapper" para poder usar la librería MediaInfo.dll que sirve para obtener la información detallada de los archivos multimedia (Resolucion, BitRate, etc) y la segunda es una versión extendida del ListView, posee algunas funciones extra a un ListView normal, estas las veremos mas adelante.
Al inicio, puede aparecer este mensaje de error.
Para agregar las referencias solo hay que ir a "Proyecto>Agregar referencia" y en la ficha "Examinar", buscar la carpeta debug que viene incluida en el archivo que descargaste y seleccionar "ListViewEx.dll y MediaInfoNet.dll"


Analizando un poco el código.

El código está dividido en dos regiones:
  1. Declaraciones (globales)
  2. Funciones
El resto del código que no está dentro de ninguna de estas, es código propio del formulario o de los controles; eventos constructores y todo lo demás no tienen ninguna Region definida.

Para VEncoder2 solo usaremos 4 variables globales

    #region Declaraciones
       private bool IsConvertingSomething = false;
       private bool RequestForCancel = false;
       private int ElapsedTimeGlobal = 0;
       private int ElapsedTimeCurrent = 0;
    #endregion

Los nombres son realmene explicitos, entonces creo que no hace falta mencionar cada una, de todos modos sobre la marcha, veremos que tipo de valores se le asignan a cada una.

Función SaveProfile

Para no hacer muy largo el post,el día de hoy solo analizaremos las primeras dos funciones que nos encontraremos al abrir el proyecto. La primera funcion es la que se encarga de abrir un archivo VEPX con los ajustes para la conversion.


/// <summary>
/// Funcion que se encarga de guardar loa ajustes en archivo VEPX
/// </summary>
/// <param name="XmlPath">Ruta en donde se guardara</param>
/// <param name="ProfileName">Nombre que se usara para identificar el archivo</param>

private void SaveProfile(string XmlPath, string ProfileName)
{
  XmlWriterSettings settings = new XmlWriterSettings();
  settings.Indent = true;
  settings.IndentChars = "\t";
  using (XmlWriter XWriter = XmlWriter.Create(XmlPath, settings))
  {
    XWriter.WriteStartDocument();
    XWriter.WriteComment("Archivo de preset para VEncoder 2");
    XWriter.WriteStartElement("Perfil");
    XWriter.WriteAttributeString("v", "1");
    XWriter.WriteAttributeString("appname", "VEncoder2");
    XWriter.WriteElementString("Name", ProfileName);
    XWriter.WriteElementString("WH", CboResolucion.Text);
    XWriter.WriteElementString("VBit", TXTVBit.Text);
    XWriter.WriteElementString("Ratio", CBORatio.Text);
    XWriter.WriteElementString("Profile", CBOProfile.Text);
    XWriter.WriteElementString("Nivel", CboLevel.Text);
    XWriter.WriteElementString("ASample", CboFreq.Text);
    XWriter.WriteElementString("ABit2", CBOBitsA.Text);
    XWriter.WriteElementString("Channel", CBOAChannels.Text);
    XWriter.WriteElementString("ExtraParams", TXTExtraparams.Text);
    XWriter.WriteEndElement();
    XWriter.WriteEndDocument();
  }
}


Lo que va a hacer es leer los controles y guardar sus valores en un archivo XML, para eso, usamos XMLWritter. De hecho lo hacemos de forma inpractica, pero debido a que siempre van a ser los valores de los mismos controles, ademas de que no son muchos, entonces podemos hacer de forma explicita, indicando el nombre del control cuyo valor queremos obtener y simplemente guardarlo. Por ejemplo,  XWriter.WriteElementString("WH", CboResolucion.Text); lo que hace es guardar en un elemento XML llamado "WH" el texto contenido en CboResolution. Para poder entender un poco mejor a que se hace referencia, recomiendo echarle un vistazo al proyecto, cada control tiene su nombre, entonces una vez que te familiarices con los nombres, sera más rápido entender el código.

Funcion LoadProfile

La siguiente función hace lo contrario, en lugar de guardar, está lee desde un archivo existente.
/// <summary>
/// Carga un archivo XML con los ajustes para realizar la cadificacion.
/// </summary>
/// <param name="InXMLFile">Ruta al archivo que se debe de leer</param>
private void LoadProfile(string InXMLFile)
{
using (XmlReader XMLR = new XmlTextReader(InXMLFile))
 {
 XMLR.ReadToFollowing("Name");
 lnklblLoadProfile.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("WH");
 CboResolucion.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("VBit");
 TXTVBit.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("Ratio");
 CBORatio.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("Profile");
 CBOProfile.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("Nivel");
 CboLevel.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("ASample");
 CboFreq.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("ABit2");
 CBOBitsA.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("Channel");
 CBOAChannels.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("ExtraParams");
 TXTExtraparams.Text = XMLR.ReadElementContentAsString();
 }
}


Verás que el código es realmente similar al anterior, de hecho lo es, pero para poder entenderlo es necesario estar familiarizado con los nombres de los controles.

Creo que por ahora es todo, sirve que le echas in vistazo al código y ya despues seguiremos con la explicación a lo demás.

Los leo luego.

El vídeo correcto #2 - Cómo funciona VEncoder.

El día de hoy le quiero mostrar como es que funciona VEncoder 2, esto en lo que arreglo un poquito el código fuente para poder publicarlo y que cada quien cree su propia versión.

Paso 1. Abriendo los archivos.

El primer paso y el más obvio es agregar los archivos que se quieren convertir. Cómo estamos usando FFMpeg, el tipo de de archivos que podemos convertir es muy alto, pero para no complicarnos veremos los más comunes.
Los archivos mas comunes, suelen venir con extensiones:
  • MP4
  • MKV
  • WMV
  • AVI
  • MPEG
Y para no complicarnos mas vamos a decir que FFMPeg es capaz de convertir cualquiera de los anteriores a MP4 que es el mas usado (en realidad es MP4 usando codec X264 para el video y AAC para el audio). Entonce si tienes un video que no se ve y quieres convertirlo, puedes usar este programa.
Para agregarlos hay que usar el botón que dice "Abrir archivos" se abrirá la ventana de selección de archivos. Solo tenemos que seleccionar los que queramos convertir.
En mi caso voy a convertir algunos vídeos que grabe con una cámara relativamente vieja y que no son compatibles con ninguna de las pantallas que tengo.
La intefaz está dividida en dos partes, la primera esta diseñada para poder gestionar los archivos que se agregan, es posible quitar algunos de los archivos (si cambias de opinion), seleccionar solo algunos para convertirlos en ese momento o simplemente quitar todo.
La parte del control de archivos consiste en 5 botones y una casilla de verificación.
De forma corta, que es lo que hace cada botón:
  • "Abrir Archivos", muestra el dialogo de selección de archivos y los agrega a la lista de conversión.
  • "Quitar todos", limpia toda la lista de archivos.
  • "Quitar marcados", quita de la lista solo los archivos que tengan marca de selección.
  • "Marcar todos", le pone marca de selección a todos los archivos.
  • "Invertir selección", Si un archivo de la lista tiene marca de selección, la quita; en caso contrario se la pone.
  • "Convertir solo los marcados", cuando se le dice la programa que empiece ala conversion, solo hará la de aquellos que tienen marca selección.

"Ajustando" los ajustes.

Para realizar la conversión, primero hay que poner los valores adecuados para que al final el vídeo sea compatible.
Una configuración compatible con la mayoría de las pantallas del mercado es la siguiente;
Los ajustes como los de la imagen funcionan.

Y darán como resultado lo siguiente:

-y -i "C:\MI Video.mp4" -c:v libx264 -s 1280x720 -b:v 4000k -aspect 16:9 -profile:v high -threads 0 -r 23.974 -level 4.1 -crf 22 -f mp4 -pass 1 -an NUL
-y -i "C:\Mi video.mp4" -c:v libx264 -s 1280x720 -b:v 4000k -aspect 16:9 -profile:v high -threads 0 -r 23.974 -level 4.1 -crf 22 -f mp4 -pass 2 -c:a libvo_aacenc -b:a 128k -ar 44100 -ac 2 "C:\Users\Ra\Desktop\MI Video.mp4"

La linea de comandos solo es ilustrativa. Cuando ya se modificaron los ajustes a los que cada uno necesita, solo basta con presionar el botón "Empezar".

Agilizando el trabajo.

Cómo lo mas seguro es que vas a realizar mas de una vez el mismo tipo de conversión, debe de haber una forma de guardar una configuración para poder usarla después. La solución fue crear archivos de "perfil", en el se guardan las configuraciones para solo poder cargarlas.

Si descargaste VEncoder, al momento de extraer el paquete, de seguro viste un par de archivos con extensión VEPX. Estoso son configuraciones guardadas, para usarlas, en la pantalla principal, deberás de hacer clic en en las letras azules que por default dicen:"Archivo de perfil: ninguno", se abrirá un dialogo con filtro para los archivos VEPX, solo debes de escoger uno válido para usarlo.
Las ventajas son simples, ahorra el trabajo de cambiar los ajustes cada vez.

Pero que son los archivos VEPX.

Los archivos VEPX en realidad son archivos XML con los ajustes, el código de VEncoder se encarga de asignar cada valor al correspondiente para la conversión.
De hecho si abres el archivo con Notepad++ se verá similar a este.
<?xml version="1.0" encoding="utf-8"?>
<!--Archivo de preset para VEncoder 2-->
<Perfil v="1" appname="VEncoder2">
 <Name>HD Medium</Name>
 <WH>1280x720</WH>
 <VBit>4096</VBit>
 <Ratio>16:9</Ratio>
 <Profile>high</Profile>
 <Nivel>41</Nivel>
 <ASample>44100</ASample>
 <ABit2>128</ABit2>
 <Channel>2</Channel>
 <ExtraParams />
</Perfil>

La parte programacional la veremos en el siguiente Post, Solo como recordatorio, el programa aun es BETA, por lo que los fallos aun estan, pero no te preocupes, en el siguiente post empezaremos a analizar y a depurar el codigo de VEncoder2.

Por ahora es todo, los leo luego.