private static void PySceneDetect() { // Skip Scene Detect if the file already exist if (File.Exists(Path.Combine(Global.temp_path, Global.temp_path_folder, "splits.txt")) == false) { Helpers.Logging("Scene Detection with PySceneDetect"); // Detects the Scenes with PySceneDetect Process pySceneDetect = new Process(); ProcessStartInfo startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Hidden, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, FileName = "cmd.exe", Arguments = "/C scenedetect -i " + '\u0022' + Global.Video_Path + '\u0022' + " -o " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder) + '\u0022' + " detect-content list-scenes" }; pySceneDetect.StartInfo = startInfo; pySceneDetect.Start(); pySceneDetect.WaitForExit(); PySceneDetectParse(); } }
public static bool pcm_bluray_4 = false; // PCM_BluRay Check Track Four public static void Encode() { // Skips Audio Encoding if the audio file already exist if (File.Exists(Path.Combine(Global.temp_path, Global.temp_path_folder, "Audio", "audio.mkv")) == false) { //Creates Audio Directory in the temp dir if (!Directory.Exists(Path.Combine(Global.temp_path, Global.temp_path_folder, "Audio"))) { Directory.CreateDirectory(Path.Combine(Global.temp_path, Global.temp_path_folder, "Audio")); } string audio_command = ""; int end_index = 0; if (trackOne) { audio_command += MultipleTrackCommandGenerator(audioBitrateTrackOne, 0, end_index, audioCodecTrackOne, audioChannelsTrackOne, trackOneLanguage, trackOneName, pcm_bluray_1); end_index += 1; } if (trackTwo) { audio_command += MultipleTrackCommandGenerator(audioBitrateTrackTwo, 1, end_index, audioCodecTrackTwo, audioChannelsTrackTwo, trackTwoLanguage, trackTwoName, pcm_bluray_2); end_index += 1; } if (trackThree) { audio_command += MultipleTrackCommandGenerator(audioBitrateTrackThree, 2, end_index, audioCodecTrackThree, audioChannelsTrackThree, trackThreeLanguage, trackThreeName, pcm_bluray_3); end_index += 1; } if (trackFour) { audio_command += MultipleTrackCommandGenerator(audioBitrateTrackFour, 3, end_index, audioCodecTrackFour, audioChannelsTrackFour, trackFourLanguage, trackFourName, pcm_bluray_4); } if (audioCodecTrackOne != "Copy Audio" && audioCodecTrackTwo != "Copy Audio" && audioCodecTrackThree != "Copy Audio" && audioCodecTrackFour != "Copy Audio") { audio_command += " -af aformat=channel_layouts=" + '\u0022' + "7.1|5.1|stereo|mono" + '\u0022' + " "; } // ══════════════════════════════════════ Audio Encoding ══════════════════════════════════════ string ffmpegAudioCommands = "/C ffmpeg.exe -y -i " + '\u0022' + Global.Video_Path + '\u0022' + " -map_metadata -1 -vn -sn -dn " + audio_command + " " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Audio", "audio.mkv") + '\u0022'; Helpers.Logging("Encoding Audio: " + ffmpegAudioCommands); SmallFunctions.ExecuteFfmpegTask(ffmpegAudioCommands); // ════════════════════════════════════════════════════════════════════════════════════════════ } }
public static void Encode() { // Main Encoding Function // Creates a new Thread Pool using (SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(Worker_Count)) { // Creates a tasks list List <Task> tasks = new List <Task>(); // Iterates over all args in VideoChunks list foreach (var command in Global.Video_Chunks) { concurrencySemaphore.Wait(); var task = Task.Factory.StartNew(() => { try { if (!SmallFunctions.Cancel.CancelAll) { // We need the index of the command in the array var index = Array.FindIndex(Global.Video_Chunks, row => row.Contains(command)); // Logic for resume mode - skips already encoded files if (File.Exists(Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split" + index.ToString("D5") + ".ivf" + "_finished.log")) == false) { // One Pass Encoding Process ffmpegProcess = new Process(); ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.UseShellExecute = true; startInfo.FileName = "cmd.exe"; startInfo.WorkingDirectory = Global.FFmpeg_Path; if (!Show_Terminal) { startInfo.WindowStyle = ProcessWindowStyle.Hidden; } string InputVideo = ""; if (Splitting.split_type >= 1) { // FFmpeg Scene Detect or PySceneDetect InputVideo = " -i " + '\u0022' + Global.Video_Path + '\u0022' + " " + command; } else if (Splitting.split_type == 0) { // Chunk based splitting InputVideo = " -i " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", command) + '\u0022'; } // Saves encoder progress to log file string ffmpeg_progress = " -progress " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Progress", "split" + index.ToString("D5") + "_progress.log") + '\u0022'; string ffmpeg_input = InputVideo + " " + MainWindow.FilterCommand + Pixel_Format + " " + MainWindow.VSYNC + " "; // Process Exit Code int exit_code = 0; // Logic to skip first pass encoding if "_finished" log file exists if (File.Exists(Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split" + index.ToString("D5") + "_stats.log" + "_finished.log")) == false) { string encoderCMD = ""; if (MainWindow.OnePass) { // One Pass Encoding encoderCMD = " -y " + Final_Encoder_Command + " "; encoderCMD += '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split" + index.ToString("D5") + ".webm") + '\u0022'; } else { // Two Pass Encoding - First Pass encoderCMD = " -y " + Final_Encoder_Command + " -pass 1 -passlogfile "; encoderCMD += '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split" + index.ToString("D5") + "_stats.log") + '\u0022' + " -f webm NUL"; } startInfo.Arguments = "/C ffmpeg.exe " + ffmpeg_progress + ffmpeg_input + encoderCMD; Helpers.Logging("Encoding Video: " + startInfo.Arguments); ffmpegProcess.StartInfo = startInfo; ffmpegProcess.Start(); // Sets the process priority if (!Process_Priority) { ffmpegProcess.PriorityClass = ProcessPriorityClass.BelowNormal; } // Get launched Process ID int temp_pid = ffmpegProcess.Id; // Add Process ID to Array, inorder to keep track / kill the instances Global.Launched_PIDs.Add(temp_pid); ffmpegProcess.WaitForExit(); // Get Exit Code exit_code = ffmpegProcess.ExitCode; if (exit_code != 0) { Helpers.Logging("Chunk " + command + " Failed with Exit Code: " + exit_code.ToString()); } // Remove PID from Array after Exit Global.Launched_PIDs.RemoveAll(i => i == temp_pid); if (MainWindow.OnePass == false && SmallFunctions.Cancel.CancelAll == false && exit_code == 0) { // Writes log file if first pass is finished, to be able to skip them later if in resume mode Helpers.WriteToFileThreadSafe("", Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split" + index.ToString("D5") + "_stats.log" + "_finished.log")); } } if (!MainWindow.OnePass) { // Creates a different progress file for the second pass (avoids negative frame progressbar) ffmpeg_progress = " -progress " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Progress", "split" + index.ToString("D5") + "_progress_2nd.log") + '\u0022'; string encoderCMD = " -pass 2 " + Final_Encoder_Command; encoderCMD += " -passlogfile " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split" + index.ToString("D5") + "_stats.log") + '\u0022'; encoderCMD += " " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split" + index.ToString("D5") + ".webm") + '\u0022'; startInfo.Arguments = "/C ffmpeg.exe " + ffmpeg_progress + ffmpeg_input + encoderCMD; Helpers.Logging("Encoding Video: " + startInfo.Arguments); ffmpegProcess.StartInfo = startInfo; ffmpegProcess.Start(); // Sets the process priority if (!Process_Priority) { ffmpegProcess.PriorityClass = ProcessPriorityClass.BelowNormal; } // Get launched Process ID int temp_pid = ffmpegProcess.Id; // Add Process ID to Array, inorder to keep track / kill the instances Global.Launched_PIDs.Add(temp_pid); ffmpegProcess.WaitForExit(); // Get Exit Code exit_code = ffmpegProcess.ExitCode; if (exit_code != 0) { Helpers.Logging("Chunk " + command + " Failed with Exit Code: " + exit_code.ToString()); } // Remove PID from Array after Exit Global.Launched_PIDs.RemoveAll(i => i == temp_pid); } if (SmallFunctions.Cancel.CancelAll == false && exit_code == 0) { // This function will write finished encodes to a log file, to be able to skip them if in resume mode Helpers.WriteToFileThreadSafe("", Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split" + index.ToString("D5") + ".ivf" + "_finished.log")); } } } } finally { concurrencySemaphore.Release(); } }); tasks.Add(task); } Task.WaitAll(tasks.ToArray()); } }
private static void FFmpegSceneDetect() { // Skip Scene Detect if the file already exist if (File.Exists(Path.Combine(Global.temp_path, Global.temp_path_folder, "splits.txt")) == false) { Helpers.Logging("Scene Detection with FFmpeg"); List <string> scenes = new List <string>(); // Starts FFmpeg Process Process FFmpegSceneDetect = new Process(); ProcessStartInfo startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, CreateNoWindow = true, WorkingDirectory = Global.FFmpeg_Path, RedirectStandardError = true, FileName = "cmd.exe", Arguments = "/C ffmpeg.exe -i " + '\u0022' + Global.Video_Path + '\u0022' + " -hide_banner -loglevel 32 -filter_complex select=" + '\u0022' + "gt(scene\\," + FFmpeg_Threshold + "),select=eq(key\\,1),showinfo" + '\u0022' + " -an -f null -" }; FFmpegSceneDetect.StartInfo = startInfo; FFmpegSceneDetect.Start(); // Reads Standard Err from FFmpeg Output string stream = FFmpegSceneDetect.StandardError.ReadToEnd(); FFmpegSceneDetect.WaitForExit(); // Splits the Console Output by spaces string[] array = stream.Split(' '); // Searches for pts_time, if found it removes "pts_time:" to get only values foreach (string value in array) { if (value.Contains("pts_time:")) { scenes.Add(value.Remove(0, 9)); } } // Temporary value for Arg creation string previousScene = "0.000"; // Clears the Args List to avoid conflicts in Batch Encode Mode FFmpegArgs.Clear(); // Creates the seeking args for ffmpeg piping foreach (string sc in scenes) { FFmpegArgs.Add("-ss " + previousScene + " -to " + sc); previousScene = sc; } // Argument for seeking until the end of the video FFmpegArgs.Add("-ss " + previousScene); // Writes splitting arguments to text file foreach (string line in FFmpegArgs) { using (StreamWriter sw = File.AppendText(Path.Combine(Global.temp_path, Global.temp_path_folder, "splits.txt"))) { sw.WriteLine(line); sw.Close(); } } if (File.Exists(Path.Combine(Global.temp_path, Global.temp_path_folder, "splits.txt"))) { Global.Video_Chunks = File.ReadAllLines(Path.Combine(Global.temp_path, Global.temp_path_folder, "splits.txt")); // Reads the split file for VideoEncode() function } } }
private static void FFmpegChunking() { // Skips Splitting of already existent if (!File.Exists(Path.Combine(Global.temp_path, Global.temp_path_folder, "splitting.log"))) { // Create Chunks Folder Helpers.Create_Temp_Folder(Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks")); // Generate Command string ffmpeg_command = "/C ffmpeg.exe"; ffmpeg_command += " -y -i " + '\u0022' + Global.Video_Path + '\u0022'; // Video Input ffmpeg_command += " -reset_timestamps 1 -map_metadata -1 -sn -an"; // Remove unnecessary metadata etc if (skip_reencode) { ffmpeg_command += " -c:v copy"; } else { if (encode_method == 0) { ffmpeg_command += " -c:v libx264 -preset ultrafast -crf 0"; // Re-Encoding - Needed because else it WILL loose frames } else if (encode_method == 1) { ffmpeg_command += " -c:v ffv1 -level 3 -threads 4 -coder 1 -context 1 -slicecrc 0 -slices 4"; // Re-Encoding - Needed because else it WILL loose frames } else if (encode_method == 2) { ffmpeg_command += " -c:v utvideo"; // Re-Encoding - Needed because else it WILL loose frames } // Hardsub if (MainWindow.subHardSubEnabled) { ffmpeg_command += " " + MainWindow.hardsub_command; } // Filters ffmpeg_command += MainWindow.FilterCommand; // Reset Filter Command so it is not being used later by encoders MainWindow.FilterCommand = ""; ffmpeg_command += " -sc_threshold 0 -g " + chunking_length; // Make Splitting more accurate ffmpeg_command += " -force_key_frames " + '\u0022' + "expr:gte(t, n_forced * " + chunking_length + ")" + '\u0022'; // Make Splitting more accurate } ffmpeg_command += " -segment_time " + chunking_length + " -f segment " + '\u0022'; // Segmenting ffmpeg_command += Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "split%6d.mkv") + '\u0022'; // Video Output Helpers.Logging("Equal Chunking: " + ffmpeg_command); // Start Splitting Process chunking_process = new Process(); ProcessStartInfo startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Hidden, FileName = "cmd.exe", WorkingDirectory = Global.FFmpeg_Path, Arguments = ffmpeg_command }; chunking_process.StartInfo = startInfo; // Start Process chunking_process.Start(); // Get launched Process ID int temp_pid = chunking_process.Id; // Add Process ID to Array, inorder to keep track / kill the instances Global.Launched_PIDs.Add(temp_pid); // Wait for Exit chunking_process.WaitForExit(); // Get Exit Code int exit_code = chunking_process.ExitCode; // Remove PID from Array after Exit Global.Launched_PIDs.RemoveAll(i => i == temp_pid); // Write Save Point Helpers.WriteToFileThreadSafe("", Path.Combine(Global.temp_path, Global.temp_path_folder, "splitting.log")); } // Add Chunks to Array Global.Video_Chunks = Directory.GetFiles(Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks"), "*mkv", SearchOption.AllDirectories).Select(x => Path.GetFileName(x)).ToArray(); }
public static async Task Concat() { // ══════════════════════════════════════ Chunk Parsing ══════════════════════════════════════ // Writes all ivf files into chunks.txt for later concat IOrderedEnumerable <string> sorted = null; if (MainWindow.EncodeMethod <= 4) { sorted = Directory.GetFiles(Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks"), "*.webm").OrderBy(f => f); } else { // rav1e external only supports ivf sorted = Directory.GetFiles(Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks"), "*.ivf").OrderBy(f => f); } using (StreamWriter outputFile = new StreamWriter(Path.Combine(Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks"), "chunks.txt"))) { foreach (var fileTemp in sorted) { string tempName = fileTemp.Replace("'", "'\\''"); outputFile.WriteLine("file '" + tempName + "'"); } } bool audio = EncodeAudio.trackOne || EncodeAudio.trackTwo || EncodeAudio.trackThree || EncodeAudio.trackFour; bool vfr = MainWindow.VFRVideo; bool sub = MainWindow.subSoftSubEnabled; string ffmpegCommand; // Replace ' with "'", else muxing will fail with single quotes in filename Global.temp_path_folder.Replace("'", "\"'\""); if (!audio && !vfr && !sub) { ffmpegCommand = "/C ffmpeg.exe -y -f concat -safe 0 -i " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "chunks.txt") + '\u0022' + " -c copy " + '\u0022' + Global.Video_Output + '\u0022'; Helpers.Logging("Muxing: " + ffmpegCommand); await Task.Run(() => SmallFunctions.ExecuteFfmpegTask(ffmpegCommand)); } else { // First Concats the video to a temp.mkv file ffmpegCommand = "/C ffmpeg.exe -y -f concat -safe 0 -i " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Chunks", "chunks.txt") + '\u0022' + " -c copy " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "temp.mkv") + '\u0022'; Helpers.Logging("Muxing: " + ffmpegCommand); await Task.Run(() => SmallFunctions.ExecuteFfmpegTask(ffmpegCommand)); } if (audio) { if (!sub) { if (!vfr) { // Muxes Video & Audio together (required for MP4 output) ffmpegCommand = "/C ffmpeg.exe -y -i " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "temp.mkv") + '\u0022' + " -i " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Audio", "audio.mkv") + '\u0022' + " -map 0:v -map 1:a -c copy " + '\u0022' + Global.Video_Output + '\u0022'; Helpers.Logging("Muxing: " + ffmpegCommand); await Task.Run(() => SmallFunctions.ExecuteFfmpegTask(ffmpegCommand)); } else { // Run mkvmerge command - only supports mkv / webm string mkvmergeCommand = "/C mkvmerge.exe --output " + '\u0022' + Global.Video_Output + '\u0022' + " " + MainWindow.VFRCMD + " --language 0:und --default-track 0:yes " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "temp.mkv") + '\u0022' + " --default-track 0:yes " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Audio", "audio.mkv") + '\u0022'; Helpers.Logging("Muxing: " + mkvmergeCommand); await Task.Run(() => SmallFunctions.ExecuteMKVMergeTask(mkvmergeCommand)); } } else { // Muxes Video & Audio & Subtitles together - MP4 not supported - also supports VFR string mkvmergeCommand = "/C mkvmerge.exe --output " + '\u0022' + Global.Video_Output + '\u0022' + " " + MainWindow.VFRCMD + " --language 0:und --default-track 0:yes " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "temp.mkv") + '\u0022' + " --default-track 0:yes " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "Audio", "audio.mkv") + '\u0022' + " " + MainWindow.subCommand; Helpers.Logging("Muxing: " + mkvmergeCommand); await Task.Run(() => SmallFunctions.ExecuteMKVMergeTask(mkvmergeCommand)); } } else if (!audio) { if (!sub) { if (vfr) { // Run mkvmerge command with VFR Support string mkvmergeCommand = "/C mkvmerge.exe --output " + '\u0022' + Global.Video_Output + '\u0022' + " " + MainWindow.VFRCMD + " --language 0:und --default-track 0:yes " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "temp.mkv") + '\u0022'; Helpers.Logging("Muxing: " + mkvmergeCommand); await Task.Run(() => SmallFunctions.ExecuteMKVMergeTask(mkvmergeCommand)); } } else { // Muxes Video & Subtitles together string mkvmergeCommand = "/C mkvmerge.exe --output " + '\u0022' + Global.Video_Output + '\u0022' + " " + MainWindow.VFRCMD + " --language 0:und --default-track 0:yes " + '\u0022' + Path.Combine(Global.temp_path, Global.temp_path_folder, "temp.mkv") + '\u0022' + " " + MainWindow.subCommand; Helpers.Logging("Muxing: " + mkvmergeCommand); await Task.Run(() => SmallFunctions.ExecuteMKVMergeTask(mkvmergeCommand)); } } }