Xwork's Blog

The lord is waiting to take your hand.

Vamos a programar #85 - VEncoder 2.1

No hay comentarios.
Hola de nuevo a todos, el día de hoy vamos a ver una pequeña actualizacion en el código de VEncoder, ahora pasando de la version 2.0.1 a la version 2.1.

La semana pasada vimos cómo hacer un par de "cosillas" en FFMpeg, de forma ideal, deberíamos de ser capaces de aplicarlas en VEncoder, pero aquí es donde surge el dilema. Tratar de aumentar las opciones, también aumentaría la complejidad del programa y la idea principal (y para la cual fue hecha VEncoder), fue tratar de reducir lo complejo y poder convertir los archivos usando pocos clics. Para mantener un poco el balance entre la usabilidad y la complejidad, la única opción que encontré fue abrir la posibilidad de usar parámetros adicionales. Antes de continuar, veamos el código de VENcoder.

namespace VEncoder_2
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Diagnostics;
	using System.Drawing;
	using System.IO;
	using System.Resources;
	using System.Text.RegularExpressions;
	using System.Threading;
	using System.Windows.Forms;
	using System.Xml;
	using MediaInfoNET;
	using System.Text;

	/// <summary>
	/// Formulario principal de VEncoder 2
	/// </summary>
	public partial class FrmMain : Form
	{
		
		public FrmMain()
		{
			InitializeComponent();
		}
		
		#region Declaraciones
		
		private bool IsConvertingSomething = false;
		private bool RequestForCancel = false;
		private int ElapsedTimeGlobal = 0;
		private int ElapsedTimeCurrent = 0;
		
		#endregion
		
		#region Funciones
		
		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();
			}
		}
		
		/// <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))
			{
				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);
				}

			}
		}
		
		/// <summary>
		/// Funcion para arreglar la duracion de un archivo
		/// </summary>
		/// <param name="Duration">La duracion en segundos</param>
		/// <returns>Regresa un entero con la duracion total del archivo</returns>
		/// <remarks>Debido a que mediainfo.dll no es precisa con la duracion; es el metodo facil</remarks>
		
		private int FixTimeDuration(int Duration)
		{
			if (Duration <= 3600)
			{
				return Duration + 1;
			}else{
				return Duration + 60;
			}
		}
		
		/// <summary>
		/// Calcula cuanto tiempo flata para terminar un proceso.
		/// </summary>
		/// <param name="Porciento">Porcentaje actual completo</param>
		/// <param name="TransTime">Tiempo trasncurrido</param>
		/// <returns>Regresa lel tiempo restante calculado con los datos proporcionados</returns>
		
		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;
		}
		
		/// <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;
		}
		
		/// <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];
		}
		
		/// <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)
		{
			// (\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";
			}
		}

		/// <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, CboLevel.Text, CboFPS.Text, CBOCodecA.Text, CBOBitsA.Text, CboFreq.Text,
													  CBOAChannels.Text, TXTSavePath.Text,TXTExtraParamsVideo.Text,TxtExtraParamsAudio.Text,TxtExtraParamsGen.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, CboLevel.Text, CboFPS.Text, CBOCodecA.Text, CBOBitsA.Text, CboFreq.Text,
													  CBOAChannels.Text, TXTSavePath.Text, TXTExtraParamsVideo.Text, TxtExtraParamsAudio.Text, TxtExtraParamsGen.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, CboLevel.Text, CboFPS.Text, CBOCodecA.Text, CBOBitsA.Text, CboFreq.Text,
													  CBOAChannels.Text, TXTSavePath.Text, TXTExtraParamsVideo.Text, TxtExtraParamsAudio.Text, TxtExtraParamsGen.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, CboLevel.Text, CboFPS.Text, CBOCodecA.Text, CBOBitsA.Text, CboFreq.Text,
												  CBOAChannels.Text, TXTSavePath.Text, TXTExtraParamsVideo.Text, TxtExtraParamsAudio.Text, TxtExtraParamsGen.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, CboLevel.Text, CboFPS.Text, CBOCodecA.Text, CBOBitsA.Text, CboFreq.Text,
												  CBOAChannels.Text, TXTSavePath.Text, TXTExtraParamsVideo.Text, TxtExtraParamsAudio.Text, TxtExtraParamsGen.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, CboLevel.Text, CboFPS.Text, CBOCodecA.Text, CBOBitsA.Text, CboFreq.Text,
												  CBOAChannels.Text, TXTSavePath.Text, TXTExtraParamsVideo.Text, TxtExtraParamsAudio.Text, TxtExtraParamsGen.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";
				}
			}
		}

		private string BuildExtraCommands(string ExtraParams)
		{
			return  " " + ExtraParams;
		}

		private string BuildAudioCommand( string AudioCodec, string AudioBitRate = "", string AudioFreq = "", string Channels = "", string ExtraParams = "")
		{
			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;

				TempCommand +=  " " + ExtraParams;

				return TempCommand;
			}

		}
		private string BuildVideoCommand(string VCodec,string VSize = "", string VBit = "", string VAspect = "",string VProfile = "", string VLevel = "", string VFPS = "",
			string ExtraParams = "")
		{
			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;

				TempCommand += " " + ExtraParams;

				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 , string VidExtraParams, string AudExtraParams, string  GenExtraParams,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, VidExtraParams), " -pass 1 -an ", GenExtraParams, " \"", OutPath, "\\", GetFileName(InFile), ".mp4", "\"");
			}
			if(AfterPass1){
				return string.Concat("-y -i ", "\"", InFile, "\"", BuildVideoCommand(VCodec, VideoSize, VideoBit, VAspect, VProfile, VLevel, FrameRate, VidExtraParams), " -f mp4 -pass 2 ", GenExtraParams ,BuildAudioCommand(ACodec, AudioBit, AudioFreq, AChannels, AudExtraParams), " \"", OutPath, "\\", GetFileName(InFile), ".mp4", "\"");
			}
			if (SinglePass) {
				return string.Concat("-y -i ", "\"", InFile, "\"", BuildVideoCommand(VCodec, VideoSize, VideoBit, VAspect, VProfile, VLevel, FrameRate, VidExtraParams), " -f mp4 ", GenExtraParams , BuildAudioCommand(ACodec, AudioBit, AudioFreq, AChannels, AudExtraParams), " \"", OutPath, "\\", GetFileName(InFile), ".mp4", "\"");
			}else{
				return "Test";
			}
		}
		
		/// <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.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);
			                                                 		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.lblProgressInfo.Text = GetSpeedFromFFLine(CurrentResult);
			                                                 			                            	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();
		}
		
		/// <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();
		}
		
		#endregion
		void FrmMainLoad(object sender, EventArgs e)
		{
			CBOCodec.SelectedIndex = 0;
			CboResolucion.SelectedIndex = 3;
			CBORatio.SelectedIndex = 0;
			CBOBitsA.SelectedIndex = 4;
			CBOProfile.SelectedIndex = 1;
			CBOAChannels.SelectedIndex = 1;
			CBOCodecA.SelectedIndex = 1;
			if (Environment.Is64BitOperatingSystem)
				TSLblInfo.Text = "64 Bits";
			else
				TSLblInfo.Text = "32 Bits";
			if (System.IO.File.Exists("ffmpeg.exe") == false)
			{
				switch (MessageBox.Show("No se puede encontrar FFMPEG.exe\n¿Quieres buscarlo?", "VEncoder 2.0", MessageBoxButtons.YesNo
				                        , MessageBoxIcon.Error))
				{
					case DialogResult.Yes:
						using (OpenFileDialog OpDiag = new OpenFileDialog())
						{
							OpDiag.Title = "Buscar FFMPEG " + TSLblInfo.Text;
							OpDiag.Filter = "FFMpeg|FFmpeg.exe|ffmpeg32|ffmpeg32.exe|ffmpeg64|ffmpeg64.exe|Aplicaciones|*.exe";
							DialogResult DR = OpDiag.ShowDialog();
							if (DR == DialogResult.OK)
							{
								System.IO.File.Copy(OpDiag.FileName, "FFMPEG.EXE", true);
							}
						}
						break;
					case DialogResult.No:
						BtnDo.Enabled = false;
						break;
					default:
						throw new Exception("Invalid value for DialogResult");
				}
			}
		}

		private void TSMIopen_Click(object sender, EventArgs e)
		{
			OpenFileDialog OpDiag = new OpenFileDialog();
			OpDiag.Multiselect = true;
			OpDiag.Filter = "Todos los archivos.|*.*";
			OpDiag.ShowDialog(this);
			if (OpDiag.FileNames.Length > 0)
			{
				List<string> Archivos = new List<string>(OpDiag.FileNames);
				TaskbarProgress.SetState(this.Handle, TaskbarProgress.TaskbarStates.Indeterminate);
				SetInfo(Archivos);
			}
		}
		
		void BtnFolderSelClick(object sender, EventArgs e)
		{
			FolderBrowserDialog FolderSelect = new FolderBrowserDialog();
			FolderSelect.ShowNewFolderButton = true;
			DialogResult DR = FolderSelect.ShowDialog(this);
			if (DR == DialogResult.OK)
			{
				TXTSavePath.Text = FolderSelect.SelectedPath;
			}
		}
		
		void BtnDoClick(object sender, EventArgs e)
		{
			if (lvfilesin.Items.Count < 1)
			{
				MessageBox.Show("No hay archivos", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				return;
			}
			if (TXTSavePath.Text.Length < 3)
			{
				BtnFolderSelClick(null, null);
				return;
			}
			TaskbarProgress.SetState(this.Handle, TaskbarProgress.TaskbarStates.Normal);
			PrepareWork(true);
			IsConvertingSomething = true;
			BtnDo.Enabled = false;
		}
		
		void BtnRemoveClick(object sender, EventArgs e)
		{
			lvfilesin.Items.Clear();
		}
		
		void BtnRemoveCheckClick(object sender, EventArgs e)
		{
			foreach (ListViewItem LV in lvfilesin.CheckedItems)
			{
				lvfilesin.Items.Remove(LV);
			}
		}
		
		void BtnCheckAllClick(object sender, EventArgs e)
		{
			for (int i = 0; i < lvfilesin.Items.Count; i++)
			{
				lvfilesin.Items[i].Checked = true;
			}
		}
		
		void BtnInverSelClick(object sender, EventArgs e)
		{
			for (int i = 0; i < lvfilesin.Items.Count; i++)
			{
				lvfilesin.Items[i].Checked =! lvfilesin.Items[i].Checked;
			}
		}
		
		void FrmMainFormClosing(object sender, FormClosingEventArgs e)
		{
			if (IsConvertingSomething == true)
			{
				switch (MessageBox.Show("Hay un trabajo en progreso. ¿Realmente quieres salir?",
				                        "Pregunta", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation)) {
					case DialogResult.None:
						e.Cancel = true;
						break;
					case DialogResult.Yes:
						break;
					case DialogResult.No:
						e.Cancel = true;
						break;
					default:
						throw new Exception("Invalid value for DialogResult");
				}
			}
		}
		
		void TimerElapsedTick(object sender, EventArgs e)
		{
			ElapsedTimeGlobal = ElapsedTimeGlobal + 1;
			ElapsedTimeCurrent = ElapsedTimeCurrent + 1;
			TSLBLElapsedTime.Text = "Transcurrido: " + TimeSpan.FromSeconds((double)ElapsedTimeGlobal).ToString();
			lblPercentProgress.Text = Porcentaje((float)PBProgress.Value, (float)PBProgress.Maximum).ToString() + "%";
			TSLBLRemainAc.Text = "Tiempo restante: " + TimeSpan.FromSeconds(
				(int)TiempoRestante(Porcentaje((float)PBProgress.Value, (float)PBProgress.Maximum),
				                    ElapsedTimeCurrent)).ToString();
			if (IsConvertingSomething == false)
			{
				TimerElapsed.Enabled = false;
				BtnDo.Enabled = true;
				TSLBLElapsedTime.Text = "En espera";
				TSLBLRemainAc.Text = "En Espera";
			}
		}
		
		void BtnCancelClick(object sender, EventArgs e)
		{

			if (IsConvertingSomething == true)
			{
				switch (MessageBox.Show("Quieres cancelar el trabajo actual",
				                        "Cancelar?",
				                        MessageBoxButtons.YesNo,
				                        MessageBoxIcon.Exclamation))
				{
					case DialogResult.Yes:
						RequestForCancel = true;
						break;
					case DialogResult.None:
					case DialogResult.No:
						break;
					default:
						throw new Exception("Invalid value for DialogResult");
				}
			}
		}
		
		void TSMIMakeCommandClick(object sender, EventArgs e)
		{
			if (lvfilesin.Items.Count < 1)
			{
				MessageBox.Show("No hay archivos", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				return;
			}
			if (TXTSavePath.Text.Length < 3)
			{
				BtnFolderSelClick(null, null);
				return;
			}
			RTBCommandLine.Clear();
			PrepareWork(false);
		}
		
		void TSMIExitClick(object sender, EventArgs e)
		{
			Application.Exit();
		}
		
		void LnklblLoadProfileLinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{

		}
		
		void TSMISaveProfileClick(object sender, EventArgs e)
		{
			using (SaveFileDialog SavDiag = new SaveFileDialog())
			{
				SavDiag.Title = "Guardar perfil";
				SavDiag.Filter = "Perfil de VEncoder|*.vepx|Archivo XML|*.xml";
				if (SavDiag.ShowDialog() == DialogResult.OK)
				    SaveProfile(SavDiag.FileName, "Test0005");
			}
		}

		private void TSSLblProfile_Click(object sender, EventArgs e)
		{
			using (OpenFileDialog OpDiag = new OpenFileDialog())
			{
				OpDiag.Title = "Cargar perfil";
				OpDiag.Filter = "Archivo de perfil VEncoder|*.vepx|XML|*.XML;*.vepx";
				if (OpDiag.ShowDialog() == DialogResult.OK)
					LoadProfile(OpDiag.FileName);

			}
		}
	}
}


Las funciones que han cambiado son "BuildVideoCommand()" y "BuildAudioCommand()", además se ha agregado la función "BuildExtraCommands(). Las primeras dos funciones ya estaban hechas y solo se actualizaron un poco.

Pero antes, cual es la idea? si el chiste era crear una interfaz para que el usuario no tuviera que escribir nada; y así es, la idea de usar los parámetros extra, es que los usuarios que requieran hacer algo que no está programado en VEncoder, puedan hacerlo y aquellos que requieran algo "más", también puedan hacerlo. Si no se usa ningún parámetro adicional, el programa funcionara cómo venia haciéndolo, pero el hecho de poder "extender" la usabilidad, abre la opción a que la gente experimente. Una vez dicho lo anterior, continuemos con la explicacion.

Las funciones "BuildAudioCommand()" y "BuildVideoCommand()"; ambas recibían parámetros del tipo "string" y su objetivo era construir la linea de comandos. Para hacer uso de los parámetros adicionales, simplemente agregaremos al final de los parámetros que ya teníamos y las funciones devuelven la linea de comandos más los parámetros adicionales si es que se agregan.

La función "BuildExtraCommands()" simplemente toma el texto de una caja de texto y lo agrega al final de la linea de comandos (al igual que las anteriores, si está vacía no pasa nada).

Para agregar los parámetros adicionales, ahora se ha agregado una nueva pestaña llamada "Parámetros Adicionales", y tiene un conjunto de subpestañas en la cual es posible agregar los parámetros.

Tomemos cómo ejemplo las siguiente linea de comandos:

ffmpeg.exe -y -i "X:\VIDEOS\Pokemon Pelicula 20.mkv" -c:v libx264 -s 480x272 -b:v 1200k -aspect 16:9 -level 31 -f mp4 -threads 0 -c:a aac -b:a 128k -ar 44100 -ac 2  "X:\Dropbox\Pokemon Pelicula 20.mkv.mp4"

Ahora, si agregamos en el apartado de Video de los parametros adicionales "-tune animation" y en el apartado general "-t 600 -ss 140", la linea de comados resultante será la siguiente:

ffmpeg.exe -y -i "X:\VIDEOS\Pokemon Pelicula 20.mkv" -c:v libx264 -s 480x272 -b:v 1200k -aspect 16:9 -level 31 -tune animation -f mp4 -threads 0 -t 600 -ss 140 -c:a aac -b:a 128k -ar 44100 -ac 2  "X:\Dropbox\Pokemon Pelicula 20.mkv.mp4"

Cómo podras observar, ahora funcionan otras funciones, si quieres saber cuales están disponibles para el formato mp4 usando el codificador X264, puesde ver la guia de la buena codificacion (en inglés).

A partir de ahora podemos hacer uso extensivo de VEncoder, pero eso trae un par de detalles que aun se deben de resolver, pero el problema es el mismo, cómo aumentar la funcionalidad sin aumentar la complejidad. Cómo de costumbre el código lo puedes descargar de mi dropbox para que lo uses y lo modifiques. Aun queda trabajo pendiente, pero por ahora es todo.

Los leo luego.

No hay comentarios. :

Publicar un comentario

Vamos a programar #84 - Camino hacía VEncoder 2.1 - Parametros para FFMpeg.

No hay comentarios.
Hola de nuevo a todos, el día de hoy vamos ver el código actualizado de VEncoder, ahora pasando de la versión 2.00.1 a la versión 2.1; por lo que es una actualización menor pero un poco significativa.


Pero antes que nada, vamos a ver un poco del trasfondo para todo esto. Cómo de costumbre mientras veia los comentarios en twitter, hubo mucha gente que decia que al realizar la conversión de un archivo, muchas veces el resultado no era compatible, o se veia raro o el audio perdía sincronía. Una de las principales razones para crear VEncoder fue mantener las cosas simples. Hace no mucho, bastaba con proporcionar los parámetros que se muestran en la interfaz, de ahi, ffmpeg realizaba la conversión y el resultado era compatible en el 90 de los casos. Pero cómo las cosas avanzan, la complejidad del programa aumento y es capaz de hacer muchas cosas y muy complejas. Cómo es dificil cubrir todas las posibilidades (al menos sin aumentar la complejidad al usuario), primero veremos cómo es que se resuelven algunos de los problemas mencionados usando la linea de comandos y después (en otro post); veremos cómo es que se pueden aplicar las soluciones en VEncoder.

Algunas de las situaciones que me han llegado en los comentarios, es que muchas veces tratan de usar una pista de audio externa, o incluso que dentro del mismo video existen dos o tres pistas de audio (generalmente una por cada idioma) y que al realiza la conversión con la aplicación; por default, siempre tomará la primera de ellas y no necesariamente será la deseada.
Para poder usar otra pista de audio, si la tenemos de forma externa (en otro archivo), podremos usar una línea de comandos cómo la que sigue:

ffmpeg.exe -y -i "X:\DOCS\CAMERA\videoplayback.mp4" -i "X:\DOCS\CAMERA\test.m4a" -c:v copy -threads 0 -f mp4 -c:a copy "x:\Videos\tetst.mp4"

Con ello indicamos que vamos a “ingresar”  dos archivos y que la pista de audio; en lugar de usar la que está contenida en el video (si es que la tiene);  deberá de usar la que usamos cómo segundo archivo de entrada (por lo general archivos mp3, aac, flac, etc.).

Pero al usar pistas de diferentes fuentes, lo más seguro es que no estén adecuadamente sincronizadas, por lo que será necesario hacer los ajustes para que coincidan los tiempos.
Supongamos que tenemos una situación en donde primero se escucha la voz y después se ve el movimiento de la boca, eso quiere decir que el audio está adelantado. Para solucionarlo, podemos usar un línea de comandos cómo la que sigue:

ffmpeg.exe -y -itsoffset -0.50 -i "X:\DOCS\CAMERA\videoplayback.mp4" -i "X:\DOCS\CAMERA\AV.mp4" -c:v copy -threads 0 -f mp4 -c:a copy -map 0:1 -map 1:0 "x:\Videos\TEST.mp4"


Con eso haremos que el audio empiece un poco después. Ahora, si se da el caso contrario, basta con usar el tiempo invertido, algo cómo lo que sigue:

ffmpeg.exe -y -itsoffset 0.50 -i "X:\DOCS\CAMERA\videoplayback.mp4" -i "X:\DOCS\CAMERA\AV.mp4" -c:v copy -threads 0 -f mp4 -c:a copy -map 0:1 -map 1:0 "x:\Videos\TEST.mp4"

Otro de los errores más comunes es que al querer hacer una conversión, el video resultante dice que el perfil que se usa es “High 10@L5.1”. Hace algún tiempo se empezó a emplear el perfil alto de 10bits, y FFMpeg tenia una versión en donde el codificador x264 solamente tenia la capacidad de hacer la conversión en 10 bits (por ahora solo lo diremos de está forma para no entrar en detalles). Y una versión diferente para la conversión de 8 bits.
El problema surge cuando hacemos la conversión de un video que viene codificado usando 10 bits, al intentar hacer la conversión el codificador nos mostrará un error diciendo: “x264 [error]: high profile doesn't support a bit depth of 10

Para solucionarlo, podemos usar una línea de comandos cómo la que sigue:

ffmpeg -y -i "X:\VIDEOS\Pokemon Pelicula 20.mkv"  -s 1280x720 -c:v libx264 -b:v 2048k -pix_fmt yuv420p -aspect 16:9 -profile:v high -level 4.1 -crf 10 -threads 0  -c:a copy  "X:\\Pokemon Pelicula 20.mkv"

Con ella ahora la salida de video será de 8Bits en lugar de 10.
Si es el caso contrario y queremos usar la codificación de 10 Bits, podemos usar una línea de comandos cómo la que sigue:

ffmpeg -y -i "X:\VIDEOS\Pokemon Pelicula 20.mkv"  -s 1280x720 -c:v libx264 -b:v 2048k -pix_fmt yuv420p10le -aspect 16:9 -profile:v high -level 4.1 -crf 10 -threads 0  -c:a copy  "X:\\Pokemon Pelicula 20.mkv"


Que pasa si queremos convertir solamente una fracción de nuestro video, en ffmpeg podemos indicar que solamente queremos convertir una parte de un archivo, para poder hacerlo, hay opciones. La primera de ellas es asignar cuanto tiempo es lo que queremos convertir, el parámetro “-t” sirve para indicar cuanto tiempo queremos convertir, y se le puede asignar el tiempo en formato HH:MM:SS.CC o en segundos usando el formato SS.CC, por lo que si queremos convertir un video y que su duración sea de solamente nueve minutos y medio podemos pasar el tiempo cómo: “00:09:30.00” o “570.00”.
Si queremos que la conversión no empiece desde el inicio y en su lugar queremos que empiece en una posición arbitraria, debemos de usar el parámetro “-ss” y al igual que el parámetro anterior, le podemos indicar el tiempo donde queremos que empiece la conversión usando cualquiera de los formatos de tiempo. Al realizar la conversión, se empezará de la posición dada, hasta el final del archivo.
Si queremos que la conversión se haga desde una posición determinada, pero que NO sea hasta el final de nuestro archivo, bastará con usar los dos parámetros anteriores indicando la posición de inicio (con -ss) e indicando cuanto tiempo queremos que dure (con el parámetro -t).
Para lograr una conversión optima, FFmpeg ofrece muchas opciones, pero aquí es donde la cosa se empieza a complicar, ya que al  aplicar algunos parámetros, afectaran a otros directamente, entonces siempre es importante probar cuales producen el resultado más óptimo para nuestros archivos.
Y  bien, por ahora es todo, estos fueron los problemas mas reportados por los usuarios a mi cuenta de twitter @mpm88g (no es necesario que me sigas para poder escribirme tus dudas). En el siguiente post, veremos cómo implementar todos estos parámetro en VEncoder.

Los leo luego

No hay comentarios. :

Publicar un comentario