Vamos a programar #77 - VEncoder 2.0.1

Hola de nuevo a todos, el día de hoy vamos a ver un poco mas sobre VEncoder en su versión 2.0.1.

Hace algún tiempo que tenía ganas de actualizar la aplicación, pero por una u otra cosa, no podía hacerlo, pero hace no mucho, conseguí un PS3 y cómo es costumbre, al intentar reproducir un vídeo de los que ya tengo guardados, la consola simplemente se rehusó a hacerlo porque "el archivo de medios no es compatible".

Si hacemos un poco de memoria, el pretexto para empezar con las expresiones regulares fue precisamente VEncoder 2, pero además, en uno de los muchos post dedicados al programa, alguien mencionó que no podía hacer la conversión de sus archivos.

¿2017 fue hace un año, verdad?

Para solucionar ese problema, decidí actualizar un poco la aplicación a la versión 2.1, no son muchos los cambios, pero ahora  por lo menos serás capaz de realizar la conversión de archivos e incluiré un par de archivos con las configuraciones compatibles para el PS3.

Ahora veamos un poco del código que cambió.

private void LoadProfile(string InXMLFile)
{
	using (XmlReader XMLR = new XmlTextReader(InXMLFile))
	{
		try
		{
			XMLR.ReadToFollowing("Name");
			TSSLblProfile.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();

		}
		catch
		{
			MessageBox.Show("Parece que el XML no es valido", "VEncoder 2.0.1", MessageBoxButtons.OK, MessageBoxIcon.Error);
		}

	}
}

private Single GetPositionFromFFline(string FFText)
{
	// (\d+)\.\d+x find speed
	try
	{
		if (FFText.StartsWith("FRAME", StringComparison.OrdinalIgnoreCase)) {
			Single CurrentValue = 0;
			MatchCollection wordColl = Regex.Matches(FFText, @"(\d+):(\d+):(\d+)?(.\d+)");			
			CurrentValue = (Single)TimeSpan.Parse(wordColl[0].ToString()).TotalSeconds;
			return CurrentValue;
		}else{
			return 1;
		}
	} catch (Exception) {
		return 1;
	}
}

private string GetSpeedFromFFLine(string FFText)
{
	try
	{
		if (FFText.StartsWith("FRAME", StringComparison.OrdinalIgnoreCase))
		{
			string CurrentValue = "0x";
			MatchCollection wordColl = Regex.Matches(FFText, @"((\d+)(\.\d+)?)x");
			CurrentValue = wordColl[0].ToString();
			return CurrentValue;
		}
		else
		{
			return "???x";
		}
	}
	catch (Exception)
	{
		return "!!!x";
	}
}

private string BuildAudioCommand( string AudioCodec, string AudioBitRate = "", string AudioFreq = "", string Channels = "")
{
	if (string.Compare(AudioCodec,"copy",true) == 0){
		return " -c:a copy ";
	}else{
		string TempCommand = " -c:a " + AudioCodec;
		if (string.Compare(AudioBitRate, "copy", true) == -1)
			TempCommand += " -b:a " + AudioBitRate + "k";

		if (string.Compare(AudioFreq, "copy", true) == -1)
			TempCommand += " -ar " + AudioFreq ;

		if (string.Compare(Channels, "copy", true) == -1)
			TempCommand += " -ac " + Channels;
		return TempCommand;
	}

}
private string BuildVideoCommand(string VCodec,string VSize = "", string VBit = "", string VAspect = "",string VProfile = "", string VLevel = "", string VFPS = "")
{
	if (string.Compare(VCodec, "copy", true) == 0)
	{
		return " -c:v copy";
	}
	else
	{
		string TempCommand = " -c:v " + VCodec;
		if (string.Compare(VSize, "copy", true) == -1)
			TempCommand += " -s " + VSize;

		if (string.Compare(VBit, "copy", true) == -1)
			TempCommand += " -b:v " + VBit +"k";

		if (string.Compare(VAspect, "copy", true) == -1)
			TempCommand += " -aspect " + VAspect;

		if (string.Compare(VProfile, "copy", true) == -1)
			TempCommand += " -profile:v " + VProfile;

		if (string.Compare(VLevel, "copy", true) == -1)
			TempCommand += " -level " + VLevel;

		if (string.Compare(VFPS, "copy", true) == -1)
			TempCommand += " -r " + VFPS;

		return TempCommand;
	}
}

private string BuildCommandLine(string InFile,string VCodec,string VideoSize,string VideoBit,string VAspect,string VProfile, string VLevel,string FrameRate, string ACodec ,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, "\"", BuildVideoCommand(VCodec, VideoSize, VideoBit, VAspect, VProfile, VLevel, FrameRate), " -threads 0  -pass 1 -an ", " \"", OutPath, "\\", GetFileName(InFile), ".mp4", "\"");
	}
	if(AfterPass1){
		return string.Concat("-y -i ", "\"", InFile, "\"", BuildVideoCommand(VCodec, VideoSize, VideoBit, VAspect, VProfile, VLevel, FrameRate), " -threads 0 -f mp4 -pass 2", BuildAudioCommand(ACodec, AudioBit, AudioFreq, AChannels), " \"", OutPath, "\\", GetFileName(InFile), ".mp4", "\"");
	}
	if (SinglePass) {
		return string.Concat("-y -i ", "\"", InFile, "\"", BuildVideoCommand(VCodec, VideoSize, VideoBit, VAspect, VProfile, VLevel, FrameRate), " -threads 0 -f mp4", BuildAudioCommand(ACodec, AudioBit, AudioFreq, AChannels), " \"", OutPath, "\\", GetFileName(InFile), ".mp4", "\"");
	}else{
		return "Test";
	}
}

El primer procedimiento en ser modificado fue "LoadProfile()", se le hizo una pequeña corrección, ya que si se cargaba un archivo XML no valido, hacia que la aplicación se detuviera, para arreglarlo, simplemente se agregaron los bloques "try" y "catch", ahora sí se carga un archivo no valido, simplemente se notifica y la aplicación continúa con normalidad.

La siguiente función en ser modificada fue "GetPositionFromFFline" aquí es donde hacemos uso de las expresiones regulares. Cuando FFMpeg está realizando una conversión, informa cual es el progreso usando una linea con aspecto similar al que sigue:
"frame=  553 fps=9.8 q=0.0 size=  160256kB time=00:00:18.53 bitrate=70848.2kbits/s dup=111 drop=0 speed=0.329x".


Y el tiempo estará formateado de la siguiente manera: "HH: MM:SS.CC", pero hay que tener en cuenta que las centésimas de segundo, solo se mostraran si son significativas, por lo que se tenemos algo cómo 00:00:22.10 se mostrará así: 00:00:22.1. Y si tenemos algo cómo 01:01:22.00 se mostrará así: 01:01:22. Tomando en cuenta las consideraciones anteriores, podemos crear una expresión regular cómo la que sigue:
(\d+):(\d+):(\d+)?(.\d+)

Con eso podremos leer el tiempo de manera un poco mas precisa.

Una nueva función que agregué es "GetSpeedFromFFLine()", cómo su nombre lo indica, sirve para leer a que velocidad se está realizando la conversión. Para encontrar el valor, hacemos uso de la siguiente expresion regular:

((\d+)(\.\d+)?)x

El patron anterior tratara de encontrar cualquier cadena que tenga la siguiente forma: D.DDx, pero al igual que el tiempo, solo se muestran los valores más significativos, por lo que si tenemos 10.10x de velocidad, FFMPeg solo devolverá 10.1x. Cuando la se excede cierta velocidad, el valor estará en notación científica, por ejemplo 1e3x, pero no lo implemente ya que no logre ver este tipo de notación cuando realizaba las conversiones (a mi tostadora con FX4100 ya le cuesta), por lo que si notas que el programa devuelve "!!!x" o "???x" muy probablemente sea a esto.

Otro de los cambios que se hicieron fue en las funciones que se usan para crear la linea de comandos, anteriormente estaba limitada (y ahora también pero no tanto) y tareas que requerían solo codificar el stream de video pero no el de audio (y viceversa) no se podían hacer. Ahora gracias a las dos nuevas funciones "BuildVideoCommand" y "BuildAudioCommand"  en adición a la función que ya teníamos "BuildCommandLine" hacen que la conversión se realice aun si no todos los parámetros son requeridos. Imaginemos que tenemos un video con el audio de buena calidad y no queremos convertirlo porque probablemente pierda calidad o no sabemos cuantos canales posee, ahora cuando encontramos una situación similar, simplemente podemos escoger o escribir "copy" en el cuadro de texto del parámetro y las funciones ignoraran ese parámetro. FFMpeg en algunos casos por default escoge los mismos valores que el origen, así si no sabemos o no queremos cambiar ese valor, ya no tendremos que preocuparnos y todo eso aplica para los parámetros que NO sean: "Velocidad de bits (Video)", "Velocidad de bits (audio)", "Codec de video" y "Codec de audio". Para el caso de la velocidad de bits de video y de audio, se tiene que asignar un valor, ya que en el caso del video, cuando no se especifica un valor, el valor es 200k (que hace parecer cualquier cosa una película hecha con bloques; por no decir una marca) y para el caso del audio es igual. Cuando se escoge "copy" en los codecs, simplemente se leerá el stream y se pasara al archivo de salida, es decir no se realizara ninguna conversion y todos los otros parámetros  serán ignorados. Esto es especialmente útil cuando tenemos archivos MKV que sabemos que son compatibles.

Y bien, por ahora es todo, en los siguientes post continuaremos con más de FFMpeg y cómo agregar mas funcionalidades a VEncoder, actualmente hay un par de errores que son vitales corregir (pero que no son tan comunes), cualquier error que encuentres siéntete libre comentarlo. Por ahora solo esta disponible el ejecutable para la descarga (cómo de costumbre desde mi dropbox), el código lo publicaré cuando soluciones los "detalles", pero bastará con agregar y reemplazar la funciones con las que se publicaron en este post al código viejo.

Los leo luego.

No hay comentarios.