private async void ButtonOpen_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(textBoxURL.Text)) { return; } buttonOpen.IsEnabled = false; if (buttonOpen.Content.ToString() == "Open URL") { string url = textBoxURL.Text; if (url.StartsWith("http")) { if (!url.Contains("reddit") && !url.Contains("youtu") && !url.Contains("twitter") && !url.Contains("facebook") && !url.Contains("instagram")) { labelTitle.Content = "Opening network stream..."; try { MediaStream = await MediaInfo.Open(url); } catch (Exception) { new MessageBoxWindow("Failed to open media from the url\n" + url, "Error opening url").ShowDialog(); MediaStream = null; } Close(); } else { VideoUrlParser videoUrlParser = null; if (url.Contains("reddit")) { host = "Reddit"; labelTitle.Content = "Fetching Reddit post info..."; videoUrlParser = new RedditParser(); } else if (url.Contains("youtu")) { host = "Youtube"; labelTitle.Content = "Fetching Youtube video info..."; videoUrlParser = new YouTubeParser(); } else if (url.Contains("twitter")) { host = "Twitter"; labelTitle.Content = "Fetching Twitter status info..."; videoUrlParser = new TwitterParser(); comboBoxAudioQuality.Visibility = Visibility.Hidden; textBlockAudioQuality.Visibility = Visibility.Hidden; } else if (url.Contains("facebook")) { host = "Facebook"; labelTitle.Content = "Fetching Facebook post info..."; videoUrlParser = new FacebookParser(); comboBoxAudioQuality.Visibility = Visibility.Hidden; textBlockAudioQuality.Visibility = Visibility.Hidden; } else if (url.Contains("instagram")) { host = "Instagram"; labelTitle.Content = "Fetching Instagram post info..."; videoUrlParser = new InstagramParser(); } try { foreach (var item in await videoUrlParser.GetVideoList(url)) { if (!item.IsAudio) { comboBoxQuality.Items.Add(item); } else { comboBoxAudioQuality.Items.Add(item); } labelTitle.Content = item.Title; } } catch (Exception) { new MessageBoxWindow("Failed to retreive media info from the url\n" + url, "Error parsing url").ShowDialog(); buttonOpen.IsEnabled = true; return; } comboBoxAudioQuality.Items.Add("[No Audio]"); comboBoxQuality.SelectedIndex = 0; comboBoxAudioQuality.SelectedIndex = 0; buttonOpen.Content = "Open selected quality"; if (comboBoxQuality.Items.Count == 1 && comboBoxAudioQuality.Items.Count == 1) //Only one option -> automatically select that option { ButtonOpen_Click(null, null); } else { buttonOpen.IsEnabled = true; Storyboard storyboard = FindResource("ChoseQualityAnimation") as Storyboard; storyboard.Begin(); } } } } else { buttonOpen.IsEnabled = false; labelTitle.Content = $"Loading {host} video..."; StreamInfo selectedVideo = (StreamInfo)comboBoxQuality.SelectedItem; MediaStream = await MediaInfo.Open(selectedVideo.Url); if (comboBoxAudioQuality.SelectedItem.ToString() != "[No Audio]") { StreamInfo selectedAudio = (StreamInfo)comboBoxAudioQuality.SelectedItem; MediaStream.AudioSource = selectedAudio.Url; MediaStream.AudioCodec = selectedAudio.Codec; } MediaStream.Title = selectedVideo.Title; Close(); } }
public async void Convert(MediaInfo sourceInfo, string destination, ConversionOptions conversionOptions) { progressData = new ProgressData(); progressData.IsFastCut = false; if (conversionOptions.End != TimeSpan.Zero) { progressData.TotalTime = conversionOptions.End - conversionOptions.Start; } else { progressData.TotalTime = sourceInfo.Duration - conversionOptions.Start; } string filters = ""; if (conversionOptions.Resolution.HasValue() && conversionOptions.CropData.HasValue()) { filters = $" -vf \"scale={conversionOptions.Resolution.Width}:{conversionOptions.Resolution.Height}," + $" crop=in_w-{conversionOptions.CropData.Left + conversionOptions.CropData.Right}:in_h-{conversionOptions.CropData.Top + conversionOptions.CropData.Bottom}:{conversionOptions.CropData.Left}:{conversionOptions.CropData.Top}\""; } else if (conversionOptions.Resolution.HasValue()) { filters = $" -vf \"scale={conversionOptions.Resolution.Width}:{conversionOptions.Resolution.Height}\""; } else if (conversionOptions.CropData.HasValue()) { filters = $" -vf \"crop=in_w-{conversionOptions.CropData.Left + conversionOptions.CropData.Right}:in_h-{conversionOptions.CropData.Top + conversionOptions.CropData.Bottom}:{conversionOptions.CropData.Left}:{conversionOptions.CropData.Top}\""; } StringBuilder sb = new StringBuilder("-y"); sb.Append($" -ss {conversionOptions.Start} "); sb.Append($" -i \"{sourceInfo.Source}\""); if (!String.IsNullOrEmpty(sourceInfo.AudioSource)) { sb.Append($" -i \"{sourceInfo.AudioSource}\""); } if (conversionOptions.End != TimeSpan.Zero) { sb.Append($" -t {conversionOptions.End - conversionOptions.Start}"); } sb.Append(" -c:v " + (conversionOptions.Encoder == Encoder.H264 ? "libx264" : "libx265")); sb.Append(" -movflags faststart -preset " + PRESETS[conversionOptions.Preset]); sb.Append(" -crf " + conversionOptions.Crf); if (conversionOptions.Framerate > 0) { sb.Append(" -r" + conversionOptions.Framerate); } sb.Append(filters); sb.Append(conversionOptions.SkipAudio ? " -an" : " -c:a copy"); sb.Append($" \"{destination}\" -hide_banner"); convertProcess.StartInfo.Arguments = sb.ToString(); convertProcess.Start(); convertProcess.BeginErrorReadLine(); await Task.Run(() => { convertProcess.WaitForExit(); errorWaitHandle.WaitOne(); }); convertProcess.CancelErrorRead(); int exitCode = convertProcess.ExitCode; if (exitCode == 0) //conversion not aborted { ConversionCompleted?.Invoke(progressData); } }
public async void Convert(MediaInfo sourceInfo, string outputPath, ConversionOptions conversionOptions) { progressData = new ProgressData(); previousProgressData = new ProgressData(); //Capture the Synchronization Context of the caller, in order to invoke the events in its original thread synchronizationContext = SynchronizationContext.Current; //Duration if (conversionOptions.EncodeSections.Count > 0) { progressData.TotalTime = conversionOptions.EncodeSections.TotalDuration; } else { progressData.TotalTime = sourceInfo.Duration; } progressData.EncodingMode = conversionOptions.EncodingMode; partialProgress = false; //2-pass mode if (conversionOptions.EncodingMode == EncodingMode.AverageBitrate_FirstPass) { await RunConversionProcess(BuildArgumentsString(sourceInfo, outputPath, conversionOptions), false).ConfigureAwait(false); if (!stopped) { progressData.EncodingMode = EncodingMode.AverageBitrate_SecondPass; conversionOptions.EncodingMode = EncodingMode.AverageBitrate_SecondPass; await RunConversionProcess(BuildArgumentsString(sourceInfo, outputPath, conversionOptions)).ConfigureAwait(false); } //Remove 2pass log files foreach (var file in Directory.GetFiles(Environment.CurrentDirectory).Where(x => x.Contains("log"))) { File.Delete(file); } } //Single pass mode else { if (conversionOptions.EncodeSections.Count == 0) { await RunConversionProcess(BuildArgumentsString(sourceInfo, outputPath, conversionOptions)).ConfigureAwait(false); } else if (conversionOptions.EncodeSections.Count == 1) { await RunConversionProcess(BuildArgumentsString(sourceInfo, outputPath, conversionOptions, conversionOptions.EncodeSections.ActualStart, conversionOptions.EncodeSections.ActualEnd, conversionOptions.FadeEffect, conversionOptions.FadeEffect)).ConfigureAwait(false); } else { partialProgress = true; string outputDirectory = Path.GetDirectoryName(outputPath); string outputFileName = Path.GetFileNameWithoutExtension(outputPath); for (int i = 0; i < conversionOptions.EncodeSections.Count; i++) { bool fadeStart = conversionOptions.FadeEffect && i != 0; bool fadeEnd = conversionOptions.FadeEffect && i < conversionOptions.EncodeSections.Count - 1; string destination = $"{outputDirectory}\\{outputFileName}_part_{i}.mp4"; string arguments = BuildArgumentsString(sourceInfo, destination, conversionOptions, conversionOptions.EncodeSections[i].Start, conversionOptions.EncodeSections[i].End, fadeStart, fadeEnd); await RunConversionProcess(arguments, false).ConfigureAwait(false); if (stopped) { break; } previousProgressData = progressData; File.AppendAllText("concat.txt", $"file '{destination}'\n"); } if (!stopped) { await RunConversionProcess($"-y -f concat -safe 0 -i concat.txt -c copy \"{outputPath}\"", false).ConfigureAwait(false); } File.Delete("concat.txt"); for (int i = 0; i < conversionOptions.EncodeSections.Count; i++) { File.Delete($"{outputDirectory}\\{outputFileName}_part_{i}.mp4"); } if (!stopped) { synchronizationContext.Post(new SendOrPostCallback((o) => { ConversionCompleted?.Invoke(progressData); }), null); } } } }
private async void ButtonOpen_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(textBoxURL.Text)) { return; } buttonOpen.IsEnabled = false; if (buttonOpen.Content.ToString() == "Open URL") { string url = textBoxURL.Text; if (url.StartsWith("http")) { if (!url.Contains("reddit") && !url.Contains("youtu") && !url.Contains("twitter") && !url.Contains("facebook") && !url.Contains("instagram")) { labelTitle.Content = "Opening network stream..."; try { MediaStream = await MediaInfo.Open(url); } catch (Exception) { new MessageBoxWindow("Failed to open media from the url\n" + url, "Error opening url").ShowDialog(); MediaStream = null; } Close(); } else { VideoUrlParser videoUrlParser = null; if (url.Contains("reddit")) { host = "Reddit"; labelTitle.Content = "Fetching Reddit post info..."; videoUrlParser = new RedditParser(); } else if (url.Contains("youtu")) { host = "Youtube"; labelTitle.Content = "Fetching Youtube video info..."; videoUrlParser = new YouTubeParser(); } else if (url.Contains("twitter")) { host = "Twitter"; labelTitle.Content = "Fetching Twitter status info..."; videoUrlParser = new TwitterParser(); comboBoxAudioQuality.Visibility = Visibility.Hidden; textBlockAudioQuality.Visibility = Visibility.Hidden; } else if (url.Contains("facebook")) { host = "Facebook"; labelTitle.Content = "Fetching Facebook post info..."; videoUrlParser = new FacebookParser(); comboBoxAudioQuality.Visibility = Visibility.Hidden; textBlockAudioQuality.Visibility = Visibility.Hidden; } else if (url.Contains("instagram")) { host = "Instagram"; labelTitle.Content = "Fetching Instagram post info..."; videoUrlParser = new InstagramParser(); } try { foreach (var item in await videoUrlParser.GetVideoList(url)) { if (!item.IsAudio) { comboBoxQuality.Items.Add(item); } else { comboBoxAudioQuality.Items.Add(item); } labelTitle.Content = item.Title; } if (videoUrlParser is YouTubeParser) { PlayerSource = await((YouTubeParser)videoUrlParser).GetMuxedSource(url); } } catch (Exception) { new MessageBoxWindow("Failed to retreive media info from the url\n" + url, "Error parsing url").ShowDialog(); buttonOpen.IsEnabled = true; return; } comboBoxQuality.SelectedIndex = 0; if (comboBoxAudioQuality.Items.Count > 0) { comboBoxAudioQuality.SelectedIndex = 0; comboBoxAudioQuality.Visibility = Visibility.Visible; } else { comboBoxAudioQuality.Visibility = Visibility.Hidden; } buttonOpen.Content = "Open selected quality"; if (comboBoxQuality.Items.Count == 1 && comboBoxAudioQuality.Items.Count == 1) //Only one option -> automatically select that option { ButtonOpen_Click(null, null); } else { buttonOpen.IsEnabled = true; Storyboard storyboard = FindResource("ChoseQualityAnimation") as Storyboard; storyboard.Begin(); } } } else { new MessageBoxWindow("Enter a valid url", "FF Video Converter").ShowDialog(); } } else { buttonOpen.IsEnabled = false; labelTitle.Content = $"Loading {host} video..."; StreamInfo selectedVideo = (StreamInfo)comboBoxQuality.SelectedItem; try { if (comboBoxAudioQuality.Items.Count > 0) { StreamInfo selectedAudio = (StreamInfo)comboBoxAudioQuality.SelectedItem; MediaStream = await MediaInfo.Open(selectedVideo.Url, selectedAudio.Url); //new MessageBoxWindow("The audio stream relative to this video is separate, therefore the integrated player will play the video without audio.\nWhen converting or downloading this video however, audio and video will be muxed toghether", "Info").ShowDialog(); } else { PlayerSource = null; MediaStream = await MediaInfo.Open(selectedVideo.Url); } } catch (Exception ex) { new MessageBoxWindow(ex.Message, "Error opening selected url").ShowDialog(); Close(); return; } MediaStream.Title = selectedVideo.Title; Close(); } }
public string BuildArgumentsString(MediaInfo sourceInfo, string destination, ConversionOptions conversionOptions) { return(BuildArgumentsString(sourceInfo, destination, conversionOptions, TimeSpan.Zero, TimeSpan.Zero)); }
public string BuildArgumentsString(MediaInfo sourceInfo, string destination, ConversionOptions conversionOptions, TimeSpan start, TimeSpan end, bool fadeStart = false, bool fadeEnd = false) { StringBuilder sb = new StringBuilder("-y -progress -"); bool changeVolume = false; //Start time if (start != TimeSpan.Zero) { sb.Append($" -ss {start}"); } //Input path sb.Append($" -i \"{sourceInfo.Source}\""); //External audio source if (sourceInfo.HasExternalAudio && conversionOptions.EncodingMode != EncodingMode.AverageBitrate_FirstPass) { if (start != TimeSpan.Zero) { sb.Append($" -ss {start}"); } sb.Append($" -i \"{sourceInfo.ExternalAudioSource}\""); } //Duration if (end != TimeSpan.Zero) { sb.Append($" -t {end - start}"); } //Main video track sb.Append($" -map 0:v:0"); //Add or skip audio tracks if (!conversionOptions.SkipAudio && conversionOptions.EncodingMode != EncodingMode.AverageBitrate_FirstPass) { foreach (var audioTrack in sourceInfo.AudioTracks) { if (audioTrack.Enabled) { sb.Append($" -map {(sourceInfo.HasExternalAudio ? "1" : "0")}:{audioTrack.StreamIndex}"); sb.Append($" -disposition:{audioTrack.StreamIndex} {(audioTrack.Default ? "default" : "none")}"); } if (audioTrack.Volume != 100) { changeVolume = true; } } } //Subtitles sb.Append(" -map 0:s?"); //Encoder command line sb.Append(" -movflags faststart -c:v " + conversionOptions.Encoder.GetFFMpegCommand(conversionOptions.EncodingMode)); //Framerate and filters if (conversionOptions.EncodingMode != EncodingMode.AverageBitrate_FirstPass) { if (conversionOptions.Framerate > 0) { sb.Append(" -r " + conversionOptions.Framerate); } //Video filters if (conversionOptions.Resolution.HasValue() || conversionOptions.CropData.HasValue() || conversionOptions.Rotation.HasValue() || fadeStart || fadeEnd) { string fadeStartFilter = fadeStart ? "fade=t=in:d=0.5" : ""; string fadeEndFilter = fadeEnd ? $"fade=t=out:d=0.5:st={(end.TotalSeconds - start.TotalSeconds - 0.5).ToString(CultureInfo.InvariantCulture)}" : ""; sb.Append(" -vf " + ConcatFilters(conversionOptions.Resolution.FilterString, conversionOptions.CropData.FilterString, conversionOptions.Rotation.FilterString, fadeStartFilter, fadeEndFilter)); } //Audio filters if (changeVolume) { var volumeFilters = sourceInfo.AudioTracks.Where(x => x.Volume != 100).Select(x => "volume=" + (x.Volume / 100).ToString("0.##", CultureInfo.InvariantCulture)); sb.Append(" -af " + ConcatFilters(volumeFilters.ToArray())); } } //Audio encoder if (conversionOptions.SkipAudio || conversionOptions.EncodingMode == EncodingMode.AverageBitrate_FirstPass) { sb.Append(" -an"); } else if (changeVolume) { sb.Append($" -c:a aac -b:a 192k"); } else { sb.Append(" -c:a copy"); } //When cutting without encoding, this flag allows to cut at the nearest keyframe before the start position; without this flag audio would be cut at the start position, but video would start playing only after the next keyframe if (conversionOptions.Encoder is NativeEncoder) { sb.Append($" -avoid_negative_ts make_zero"); } //Output path if (conversionOptions.EncodingMode != EncodingMode.AverageBitrate_FirstPass) { sb.Append($" \"{destination}\" -hide_banner"); } else { sb.Append($" -f null NUL -hide_banner"); } return(sb.ToString()); }
private async void ButtonOpen_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(textBoxURL.Text)) { return; } buttonOpen.IsEnabled = false; if (buttonOpen.Content.ToString() == "Open URL") { string url = textBoxURL.Text; if (url.StartsWith("http")) { if (!url.Contains("reddit") && !url.Contains("youtu") && !url.Contains("twitter") && !url.Contains("facebook") && !url.Contains("instagram")) { titleBar.Text = "Opening network stream..."; try { MediaStream = await MediaInfo.Open(url); } catch (Exception) { new MessageBoxWindow("Failed to open media from the url\n" + url, "Error opening url").ShowDialog(); MediaStream = null; } Close(); } else { VideoUrlParser videoUrlParser = null; if (url.Contains("reddit")) { host = "Reddit"; titleBar.Text = "Fetching Reddit post info..."; videoUrlParser = new RedditParser(); } else if (url.Contains("youtu")) { host = "Youtube"; titleBar.Text = "Fetching Youtube video info..."; videoUrlParser = new YouTubeParser(); } else if (url.Contains("twitter")) { host = "Twitter"; titleBar.Text = "Fetching Twitter status info..."; videoUrlParser = new TwitterParser(); comboBoxAudioQuality.Visibility = Visibility.Hidden; textBlockAudioQuality.Visibility = Visibility.Hidden; } else if (url.Contains("facebook")) { host = "Facebook"; titleBar.Text = "Fetching Facebook post info..."; videoUrlParser = new FacebookParser(); comboBoxAudioQuality.Visibility = Visibility.Hidden; textBlockAudioQuality.Visibility = Visibility.Hidden; } else if (url.Contains("instagram")) { host = "Instagram"; titleBar.Text = "Fetching Instagram post info..."; videoUrlParser = new InstagramParser(); } GetVideoList(videoUrlParser, url); } } else { new MessageBoxWindow("Enter a valid url", "FF Video Converter").ShowDialog(); } } else { OpenVideo((StreamInfo)comboBoxQuality.SelectedItem); } }
public async void Convert(MediaInfo sourceInfo, string destination, ConversionOptions conversionOptions) { progressData = new ProgressData(); //Captures the Synchronization Context of the caller, in order to invoke the events on its original thread synchronizationContext = SynchronizationContext.Current; //Duration if (conversionOptions.End != TimeSpan.Zero) { progressData.TotalTime = conversionOptions.End - conversionOptions.Start; } else { progressData.TotalTime = sourceInfo.Duration - conversionOptions.Start; } //Filters string filters = ""; if (conversionOptions.Resolution.HasValue() || conversionOptions.CropData.HasValue() || conversionOptions.Rotation.HasValue()) { filters = " -vf " + ConcatFilters(conversionOptions.Resolution.FilterString, conversionOptions.CropData.FilterString, conversionOptions.Rotation.FilterString); } //Get nearest before keyframe var keyframes = await sourceInfo.GetNearestBeforeAndAfterKeyFrames(conversionOptions.Start.TotalSeconds).ConfigureAwait(false); TimeSpan startTime = TimeSpan.FromSeconds(keyframes.before); //FFMpeg command string StringBuilder sb = new StringBuilder("-y"); sb.Append($" -ss {startTime}"); sb.Append($" -i \"{sourceInfo.Source}\""); if (sourceInfo.HasExternalAudio) { sb.Append($" -ss {startTime}"); sb.Append($" -i \"{sourceInfo.ExternalAudioSource}\""); } if (conversionOptions.End != TimeSpan.Zero) { sb.Append($" -t {conversionOptions.End - conversionOptions.Start}"); } sb.Append($" -map 0:v:0"); if (!conversionOptions.SkipAudio) { foreach (var audioTrack in sourceInfo.AudioTracks) { if (audioTrack.Enabled) { sb.Append($" -map {(sourceInfo.HasExternalAudio ? "1" : "0")}:{audioTrack.StreamIndex}"); sb.Append($" -disposition:{audioTrack.StreamIndex} {(audioTrack.Default ? "default" : "none")}"); } } } sb.Append(" -map 0:s?"); sb.Append(" -movflags faststart -c:v " + conversionOptions.Encoder.GetFFMpegCommand()); if (conversionOptions.Framerate > 0) { sb.Append(" -r " + conversionOptions.Framerate); } sb.Append(filters); sb.Append(conversionOptions.SkipAudio ? " -an" : " -c:a copy"); sb.Append($" -ss {conversionOptions.Start - startTime}"); sb.Append($" -avoid_negative_ts 1 \"{destination}\" -hide_banner"); StartConversionProcess(sb.ToString()); }
public void Convert(MediaInfo sourceInfo, string outputPath, ConversionOptions conversionOptions) { progressData = new ProgressData(); previousProgressData = new ProgressData(); errorLine = ""; // Capture the Synchronization Context of the caller, in order to invoke the events in its original thread synchronizationContext = SynchronizationContext.Current; // Duration if (conversionOptions.EncodeSections.Count > 0) { progressData.TotalTime = conversionOptions.EncodeSections.TotalDuration; } else { progressData.TotalTime = sourceInfo.Duration; } // Gets the total number of output frames Filters.FpsFilter fpsFilter = conversionOptions.Filters.FirstOrDefault(f => f is Filters.FpsFilter) as Filters.FpsFilter; double outputFps = fpsFilter?.Framerate ?? sourceInfo.Framerate; progressData.TotalFrames = System.Convert.ToInt32(progressData.TotalTime.TotalSeconds * outputFps); // Get the total output file size (if using constant bitrate) if (conversionOptions.EncodingMode == EncodingMode.AverageBitrate_FirstPass || conversionOptions.EncodingMode == EncodingMode.AverageBitrate_SinglePass) { progressData.TotalByteSize = conversionOptions.Encoder.Bitrate.Bps / 8 * System.Convert.ToInt64(progressData.TotalTime.TotalSeconds); foreach (var audioTrack in sourceInfo.AudioTracks) { if (conversionOptions.AudioConversionOptions.ContainsKey(audioTrack.StreamIndex)) { progressData.TotalByteSize += conversionOptions.AudioConversionOptions[audioTrack.StreamIndex].Encoder.Bitrate.Bps / 8 * System.Convert.ToInt64(progressData.TotalTime.TotalSeconds); } else { progressData.TotalByteSize += audioTrack.Bitrate.Bps / 8 * System.Convert.ToInt64(progressData.TotalTime.TotalSeconds); } } } partialProgress = false; progressData.EncodingMode = conversionOptions.EncodingMode; stopped = false; // Starts the conversion task Task <bool> conversionTask; if (conversionOptions.EncodeSections.Count == 0) { conversionTask = Convert_SingleSegment(sourceInfo, outputPath, conversionOptions, TimeSpan.Zero, TimeSpan.Zero); } else if (conversionOptions.EncodeSections.Count == 1) { conversionTask = Convert_SingleSegment(sourceInfo, outputPath, conversionOptions, conversionOptions.EncodeSections.ActualStart, conversionOptions.EncodeSections.ActualEnd); } else { conversionTask = Convert_MultipleSegments(sourceInfo, outputPath, conversionOptions); } // Reports the conversion result to the original caller conversionTask.ContinueWith(result => ReportCompletition(result.Result)); }
private string BuildArgumentsString(MediaInfo sourceInfo, string destination, ConversionOptions conversionOptions, TimeSpan start, TimeSpan end, string twopasslogfile = "", bool fadeStart = false, bool fadeEnd = false) { StringBuilder sb = new StringBuilder("-y -progress -"); // Start time if (start != TimeSpan.Zero) { sb.Append($" -ss {start}"); } // Input path sb.Append($" -i \"{sourceInfo.Source}\""); // Duration if (end != TimeSpan.Zero) { sb.Append($" -t {end - start}"); } // External audio source if (sourceInfo.HasExternalAudio && !conversionOptions.NoAudio && conversionOptions.EncodingMode != EncodingMode.AverageBitrate_FirstPass) { if (start != TimeSpan.Zero) { sb.Append($" -ss {start}"); } sb.Append($" -i \"{sourceInfo.ExternalAudioSource}\""); if (end != TimeSpan.Zero) { sb.Append($" -t {end - start}"); } } // Main video track sb.Append($" -map 0:v:0"); // Add or skip audio tracks var audioTracks = sourceInfo.AudioTracks.Where(at => at.Enabled == true).ToList(); if (!conversionOptions.NoAudio && conversionOptions.EncodingMode != EncodingMode.AverageBitrate_FirstPass) { foreach (var audioTrack in audioTracks) { sb.Append($" -map {(sourceInfo.HasExternalAudio ? "1" : $"0:{audioTrack.StreamIndex}")}"); sb.Append($" -disposition:{(sourceInfo.HasExternalAudio ? "1" : audioTrack.StreamIndex)} {(audioTrack.Default ? "default" : "none")}"); if (conversionOptions.AudioConversionOptions.ContainsKey(audioTrack.StreamIndex)) { sb.Append($" -metadata:s:{(sourceInfo.HasExternalAudio ? "1" : audioTrack.StreamIndex)} title=\"{conversionOptions.AudioConversionOptions[audioTrack.StreamIndex].Title}\""); sb.Append($" -metadata:s:{(sourceInfo.HasExternalAudio ? "1" : audioTrack.StreamIndex)} handler_name=\"{conversionOptions.AudioConversionOptions[audioTrack.StreamIndex].Title}\""); } } } // Subtitles if (conversionOptions.EncodingMode != EncodingMode.NoEncoding) { sb.Append(" -map 0:s?"); } // Filters if (conversionOptions.EncodingMode != EncodingMode.AverageBitrate_FirstPass) { Filtergraph filtergraph = new Filtergraph(); // Video filters if (conversionOptions.Filters.Count > 0) { filtergraph.AddFilters(conversionOptions.Filters, 0, 0); } if (fadeStart) { filtergraph.AddFilter(new Filters.FadeFilter(Filters.FadeMode.In, 0.2), 0, 0); } if (fadeEnd) { TimeSpan actualEnd = end != TimeSpan.Zero ? end : sourceInfo.Duration; filtergraph.AddFilter(new Filters.FadeFilter(Filters.FadeMode.Out, 0.2, actualEnd.TotalSeconds - start.TotalSeconds - 0.2), 0, 0); } // Audio filters /* Currently disabled as it requires dedicated mapping which is not supported * foreach (var item in conversionOptions.AudioConversionOptions) * { * if (item.Value.Filters.Count > 0) * { * filtergraph.AddFilters(item.Value.Filters, sourceInfo.HasExternalAudio ? 1 : 0, item.Key); * } * }*/ if (filtergraph.Count > 0) { sb.Append($" -filter_complex \"{filtergraph.GenerateFiltergraph()}\""); } } // Video encoder if (conversionOptions.EncodingMode != EncodingMode.NoEncoding) { sb.Append(" -c:v " + conversionOptions.Encoder.GetFFMpegCommand(conversionOptions.EncodingMode)); if ((conversionOptions.EncodingMode == EncodingMode.AverageBitrate_FirstPass || conversionOptions.EncodingMode == EncodingMode.AverageBitrate_SecondPass) && twopasslogfile != "") { sb.Append(" -passlogfile " + twopasslogfile); } // When cutting without encoding, "-avoid_negative_ts make_zero" allows to cut at the nearest keyframe before the start position // Without this flag audio would be cut at the start position, but video would start playing only after the next keyframe sb.Append(" -avoid_negative_ts make_zero"); } // Audio encoders if (conversionOptions.NoAudio || conversionOptions.EncodingMode == EncodingMode.AverageBitrate_FirstPass) { sb.Append(" -an"); } else { foreach (var audioTrack in audioTracks) { if (conversionOptions.AudioConversionOptions.ContainsKey(audioTrack.StreamIndex)) { AudioConversionOptions audioConversionOptions = conversionOptions.AudioConversionOptions[audioTrack.StreamIndex]; sb.Append($" -c:{(sourceInfo.HasExternalAudio ? "1" : audioTrack.StreamIndex)} {audioConversionOptions.Encoder.GetFFMpegCommand(sourceInfo.HasExternalAudio ? 1 : audioTrack.StreamIndex)}"); sb.Append($" -ac:{(sourceInfo.HasExternalAudio ? "1" : audioTrack.StreamIndex)} {audioConversionOptions.Channels}"); } else { sb.Append($" -c:{(sourceInfo.HasExternalAudio ? "1" : audioTrack.StreamIndex)} copy"); } } } // MP4 specific stuff if (Path.GetExtension(destination) == "mp4") { // Subtitle encoder (necessary to convert mkv subtitles to mp4 format) sb.Append(" -c:s mov_text"); // Moves moov atom to the beginning of the file; ignored by the mkv muxer (does it do the same thing automatically? No answer on the web...) sb.Append(" -movflags +faststart"); } // Output path if (conversionOptions.EncodingMode != EncodingMode.AverageBitrate_FirstPass) { // max_muxing_queue_size is necessary to allow ffmpeg to allocate more memory to muxing, so that it's enough for very big streams sb.Append($" -max_muxing_queue_size 2048 \"{destination}\" -loglevel error -stats"); } else { sb.Append($" -f null NUL -loglevel error -stats"); } #if DEBUG Thread thread = new Thread(() => System.Windows.Clipboard.SetText(sb.ToString())); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); #endif return(sb.ToString()); }
private async Task <bool> Convert_MultipleSegments(MediaInfo sourceInfo, string outputPath, ConversionOptions conversionOptions) { bool success = false; partialProgress = true; string outputDirectory = Path.GetDirectoryName(outputPath); string outputFileName = Path.GetFileNameWithoutExtension(outputPath); if (conversionOptions.EncodingMode == EncodingMode.AverageBitrate_FirstPass) { for (int i = 0; i < conversionOptions.EncodeSections.Count; i++) { string arguments = BuildArgumentsString(sourceInfo, outputPath, conversionOptions, conversionOptions.EncodeSections[i].Start, conversionOptions.EncodeSections[i].End, "segment" + i.ToString()); success = await RunConversionProcess(arguments).ConfigureAwait(false); if (stopped || !success) { return(success); } } progressData.EncodingMode = EncodingMode.AverageBitrate_SecondPass; conversionOptions.EncodingMode = EncodingMode.AverageBitrate_SecondPass; previousProgressData.CurrentTime = TimeSpan.Zero; previousProgressData.CurrentFrames = 0; } for (int i = 0; i < conversionOptions.EncodeSections.Count; i++) { bool fadeStart = conversionOptions.FadeEffect && i != 0; bool fadeEnd = conversionOptions.FadeEffect && i < conversionOptions.EncodeSections.Count - 1; string destination = $"{outputDirectory}\\{outputFileName}_part_{i}.mp4"; string arguments = BuildArgumentsString(sourceInfo, destination, conversionOptions, conversionOptions.EncodeSections[i].Start, conversionOptions.EncodeSections[i].End, "segment" + i.ToString(), fadeStart, fadeEnd); success = await RunConversionProcess(arguments).ConfigureAwait(false); if (stopped || !success) { break; } File.AppendAllText("concat.txt", $"file '{destination}'\n"); } // Joins all segments toghether if (!stopped && success) { success = await RunConversionProcess($"-y -f concat -safe 0 -i concat.txt -c copy \"{outputPath}\"").ConfigureAwait(false); File.Delete("concat.txt"); } // Removes segments for (int i = 0; i < conversionOptions.EncodeSections.Count; i++) { string partName = $"{outputDirectory}\\{outputFileName}_part_{i}.mp4"; if (File.Exists(partName)) { File.Delete(partName); } } // Removes 2pass log files, if they exist foreach (var file in Directory.GetFiles(Environment.CurrentDirectory).Where(x => x.Contains("log"))) { File.Delete(file); } return(success); }