Vamos a programar #85 - VEncoder 2.1
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.
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:
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:
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.
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.