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