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();
        }
Beispiel #6
0
        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));
                }
            }
        }