/// <summary>
        /// Encodes specified audio file according to settings. The script file must already be written.
        /// </summary>
        /// <param name="settings">An object containing the encoding settings.</param>
        /// <returns>The endoding completion status..</returns>
        public static CompletionStatus EncodeAudio(MediaEncoderSettings settings)
        {
            CompletionStatus    Result  = CompletionStatus.Success;
            string              WavFile = PathManager.GetAudioFile(settings.JobIndex, AudioActions.Wav);
            ProcessStartOptions Options = new ProcessStartOptions(settings.JobIndex, "Exporting Audio", false).TrackProcess(settings);

            if (!File.Exists(WavFile))
            {
                EncoderBusiness.SaveAudioToWav(settings, WavFile, Options);
                if (settings.CompletionStatus == CompletionStatus.Cancelled)
                {
                    File.Delete(WavFile);
                    return(CompletionStatus.Cancelled);
                }
                if (!File.Exists(WavFile))
                {
                    settings.Cancel();
                    return(CompletionStatus.Error);
                }
            }

            string DestFile = PathManager.GetAudioFile(settings.JobIndex, settings.AudioAction);

            if (!File.Exists(DestFile))
            {
                Options.Title = "Encoding Audio";
                if (settings.AudioAction == AudioActions.Opus)
                {
                    string        Args   = string.Format(@"--bitrate {0} ""{1}"" ""{2}""", settings.AudioQuality, WavFile, DestFile);
                    FFmpegProcess Worker = new FFmpegProcess(Options);
                    Result = Worker.Run("Encoder\\opusenc.exe", Args);
                }
                else if (settings.AudioAction == AudioActions.Aac || settings.AudioAction == AudioActions.Flac)
                {
                    Result = MediaEncoder.Encode(WavFile, null,
                                                 settings.AudioAction == AudioActions.Flac ? "flac" : "aac",
                                                 string.Format("-b:a {0}k", settings.AudioQuality),
                                                 DestFile,
                                                 Options);
                }
            }

            if (Result != CompletionStatus.Success || !File.Exists(DestFile))
            {
                File.Delete(DestFile);
                settings.Cancel();
            }
            return(Result);
        }
        public async Task PreparePreviewFile(MediaEncoderSettings settings, bool overwrite, bool calcAutoCrop) {
            if (string.IsNullOrEmpty(settings.FilePath))
                return;

            if (overwrite) {
                File.Delete(PathManager.PreviewSourceFile);
                // Select default open method.
                if (settings.FilePath.ToLower().EndsWith(".avi"))
                    settings.ConvertToAvi = false;
                else {
                    FFmpegProcess FileInfo = await Task.Run(() => MediaInfo.GetFileInfo(settings.FilePath));
                    if (settings.ConvertToAvi && FileInfo?.VideoStream?.Height >= 720)
                        settings.ConvertToAvi = false;
                }
            }

            bool AviFileReady = File.Exists(PathManager.PreviewSourceFile);
            if (!AviFileReady && settings.ConvertToAvi)
                AviFileReady = await Task.Run(() => MediaEncoder.ConvertToAvi(settings.FilePath, PathManager.PreviewSourceFile, true, new ProcessStartOptions(FFmpegDisplayMode.Interface, "Converting to AVI"))) == CompletionStatus.Success;

            if (AviFileReady && settings.ConvertToAvi)
                await GetMediaInfo(PathManager.PreviewSourceFile, settings);
            else {
                settings.ConvertToAvi = false;
                await GetMediaInfo(settings.FilePath, settings);
            }

            // Auto-calculate crop settings.
            if (calcAutoCrop) {
                if (settings.CropLeft == 0 && settings.CropTop == 0 && settings.CropRight == 0 && settings.CropBottom == 0) {
                    Rect AutoCrop = await Task.Run(() => EncoderBusiness.GetAutoCropRect(settings.FilePath, settings.SourceHeight ?? 0, settings.SourceWidth ?? 0, null));
                    if (settings.CropLeft == 0)
                        settings.CropLeft = AutoCrop.Left;
                    if (settings.CropTop == 0)
                        settings.CropTop = AutoCrop.Top;
                    if (settings.CropRight == 0)
                        settings.CropRight = AutoCrop.Right;
                    if (settings.CropBottom == 0)
                        settings.CropBottom = AutoCrop.Bottom;
                }
            }
        }
        private void EncoderThread(object obj) {
            MediaEncoderSettings settings = obj as MediaEncoderSettings;
            settings.CompletionStatus = CompletionStatus.Success;
            DateTime StartTime = DateTime.Now;

            FFmpegConfig.UserInterfaceManager.Start(settings.JobIndex, "Processing Video");

            MediaEncoderSegments SegBusiness = PrepareExistingJob(settings);
            // If merging is completed, SegBusiness==null. If work is completed but not merged, SegBusiness.SegLeft = empty list.
            if (SegBusiness != null && SegBusiness.SegLeft.Count() > 0) {
                if (settings.Deshaker && (settings.DeshakerSettings.PrescanAction != PrescanType.Full || !settings.DeshakerSettings.PrescanCompleted)) {
                    settings.DeshakerSettings.PrescanAction = PrescanType.Full;
                    settings.CompletionStatus = GenerateDeshakerLog(settings, settings.InputFile);
                }

                if (settings.CompletionStatus == CompletionStatus.Success) {
                    // Encode audio stream
                    Task EncAudio = null;
                    if (settings.HasAudioOptions)
                        EncAudio = Task.Run(() => EncoderBusiness.EncodeAudio(settings));

                    // Encode video stream in segments
                    List<Task<CompletionStatus>> EncTasks = new List<Task<CompletionStatus>>();
                    if (settings.VideoAction != VideoAction.Copy) {
                        bool Cancel = false;
                        foreach (SegmentInfo seg in SegBusiness.SegLeft) {
                            MediaEncoderSettings EncSettings = settings.Clone();
                            EncSettings.ResumePos = seg.Start;
                            File.Delete(EncSettings.OutputFile);
                            EncTasks.Add(Task.Run(() => EncoderBusiness.EncodeVideo(EncSettings, seg.Length, SegBusiness.TotalFrames)));

                            // If there are more segments than max parallel instances, wait until some threads finish
                            if (EncTasks.Count >= settings.ParallelProcessing) {
                                Task.WaitAny(EncTasks.ToArray());
                                foreach (var item in EncTasks.ToArray()) {
                                    if (item.IsCompleted) {
                                        if (item.Result == CompletionStatus.Success)
                                            EncTasks.Remove(item);
                                        else {
                                            settings.CompletionStatus = item.Result;
                                            Cancel = true;
                                        }
                                    }
                                }
                            }
                            if (Cancel)
                                break;
                        }
                    }

                    EncAudio?.Wait();
                    Task.WaitAll(EncTasks.ToArray());
                }
            } else if (settings.CompletionStatus == CompletionStatus.None)
                settings.CompletionStatus = CompletionStatus.Success;

            if (FFmpegConfig.UserInterfaceManager.AppExited)
                return;

            // Check if encode is completed.
            EncodingCompletedEventArgs CompletedArgs = null;
            if (settings.CompletionStatus == CompletionStatus.Success) {
                CompletedArgs = FinalizeEncoding(settings, StartTime);
                if (settings.CompletionStatus == CompletionStatus.Success && CompletedArgs != null)
                    EncodingCompleted?.Invoke(this, CompletedArgs);

            }
            if (settings.CompletionStatus != CompletionStatus.Success) {
                CompletedArgs = GetEncodingResults(settings, null, StartTime);
                if (IsEncoding)
                    EncodingFailed?.Invoke(this, CompletedArgs);
            }

            if (IsEncoding || settings.CompletionStatus == CompletionStatus.Success)
                Application.Current.Dispatcher.Invoke(() => ProcessingQueue.Remove(settings));

            FFmpegConfig.UserInterfaceManager.Stop(settings.JobIndex);

            // Start next job.
            if (IsEncoding && ProcessingQueue.Count > 0)
                EncoderThread(ProcessingQueue.First());

            JobThread = null;
        }
Example #4
0
        /// <summary>
        /// Analyzes output files to determine what work is done and what needs to be done.
        /// </summary>
        /// <param name="settings">The media encoder settings of the job to scan.</param>
        /// <returns>Whether job needs to execute.</returns>
        public void Analyze(MediaEncoderSettings settings)
        {
            int Threads = settings.ParallelProcessing;

            if (Threads == 0)
            {
                Threads = 1;
            }
            this.settings = settings;
            SegDone       = new List <SegmentInfo>();
            SegLeft       = new List <SegmentInfo>();

            // Get script total frame count, and run in background until all other files are scanned.
            EncoderBusiness.EditStartPosition(settings.ScriptFile, 0, 0);
            ProcessStartOptions Options   = new ProcessStartOptions(settings.JobIndex, "Analyzing Segments...", false).TrackProcess(settings);
            Task <long>         TaskCount = Task.Run(() => AvisynthTools.GetFrameCount(settings.ScriptFile, Options));

            // Get list of output files in folder. The number after "_Output_" is the frame position of that segment.
            string FileName    = string.Format("Job{0}_Output_", settings.JobIndex);
            string FileNameExt = string.Format(".{0}", settings.Container);

            string[] SegFiles = Directory.GetFiles(PathManager.TempFilesPath, FileName + "*" + FileNameExt);

            // Scan each segment file and discard files smaller than 10kb or of less than 10 frames.
            // Create a list of tasks to run them all in parallel.
            List <Task <KeyValuePair <string, long> > > TaskList = new List <Task <KeyValuePair <string, long> > >();

            foreach (string seg in SegFiles)
            {
                if (settings.CompletionStatus == CompletionStatus.Cancelled)
                {
                    break;
                }
                // Discard empty files.
                if (new FileInfo(seg).Length > 0)
                {
                    // string SegmentLocal = seg;
                    TaskList.Add(Task.Run(() => {
                        return(new KeyValuePair <string, long>(seg, MediaInfo.GetFrameCount(seg, null)));
                    }));
                }
                else
                {
                    File.Delete(seg);
                }
            }

            // Run all segment length queries simultaneously and analyze results to fill SegDone.
            Task.WaitAll(TaskList.ToArray());
            string SegFile;
            long   SegStart;
            bool   SegAdded;
            int    Pos;
            string SegText;

            if (settings.CompletionStatus != CompletionStatus.Cancelled)
            {
                foreach (var item in TaskList)
                {
                    SegFile  = item.Result.Key;
                    SegStart = 0;
                    SegAdded = false;
                    if (item.Result.Value >= 1)   // Segment must contain at least one valid frame.
                    {
                        Pos = SegFile.IndexOf(FileName);
                        if (Pos > -1)
                        {
                            Pos    += FileName.Length;
                            SegText = SegFile.Substring(Pos, SegFile.Length - Pos - FileNameExt.Length);
                            if (long.TryParse(SegText, out SegStart))
                            {
                                SegDone.Add(new SegmentInfo(SegStart, SegStart + item.Result.Value));
                                SegAdded = true;
                            }
                        }
                    }
                    if (!SegAdded)
                    {
                        File.Delete(SegFile);
                    }
                }
                // Order can be random, must sort.
                SegDone = SegDone.OrderBy(s => s.Start).ToList();
            }

            // Get script total frames and calculate the work left.
            TaskCount.Wait();
            if (settings.CompletionStatus == CompletionStatus.Cancelled)
            {
                SegDone.Clear();
            }
            else
            {
                // Create list of segments left.
                TotalFrames = TaskCount.Result;
                long SegPos = 0;
                foreach (SegmentInfo segd in SegDone)
                {
                    if (segd.Start > SegPos)
                    {
                        SegLeft.Add(new SegmentInfo(SegPos, segd.Start - 1));
                    }
                    SegPos = segd.End + 1;
                }
                if (SegPos < TotalFrames)
                {
                    SegLeft.Add(new SegmentInfo(SegPos, TotalFrames - 1));
                }

                if (settings.ParallelProcessing > 1)
                {
                    // Divide in segments
                    int         Instances    = settings.ParallelProcessing;
                    int         SmallSegSize = SegSize / Instances;
                    int         StartSegSize = ((Instances - 1) * Instances / 2) * SmallSegSize;
                    SegmentInfo Seg;
                    int         SegCount = 0;
                    long        IdealSeg, AvailSeg;

                    // Work on copy because original list will be modified.
                    List <SegmentInfo> SegLeftCopy = SegLeft.ToList();
                    SegLeft.Clear();

                    for (int i = 0; i < SegLeftCopy.Count(); i++)
                    {
                        Seg      = SegLeftCopy[i];
                        AvailSeg = Seg.Length;
                        while (AvailSeg > 0)
                        {
                            // Start with smaller segments.
                            IdealSeg = SegCount < Instances ? ++SegCount * SmallSegSize : SegSize;
                            // Split segment only if it is larger than threshold
                            if (AvailSeg > IdealSeg + SmallSegSize)
                            {
                                SegLeft.Add(new SegmentInfo(Seg.Start, Seg.Start + IdealSeg - 1));
                                Seg.Start += IdealSeg;
                                AvailSeg  -= IdealSeg;
                            }
                            else
                            {
                                SegLeft.Add(new SegmentInfo(Seg.Start, Seg.End));
                                AvailSeg = 0;
                            }
                        }
                    }

                    // If we're still missing segments (for short clips), split using other method.
                    // Create segments to reach the desired amount of threads.
                    int SegMissing = SegLeft.Count > 0 ? Threads - SegLeft.Count() : 0;
                    for (int i = 0; i < SegMissing; i++)
                    {
                        // Find largest segment.
                        int  SegMaxIndex = 0;
                        long SegMax      = SegLeft[0].Length;
                        for (int j = 1; j < SegLeft.Count(); j++)
                        {
                            if (SegLeft[j].Length > SegMax)
                            {
                                SegMaxIndex = j;
                                SegMax      = SegLeft[j].Length;
                            }
                        }
                        // Split largest segment in half.
                        Seg = SegLeft[SegMaxIndex];
                        if (Seg.Length > 80)   // Only split if segment has at least 80 frames (creating segments of 40).
                        {
                            long SegSep = Seg.Start + (Seg.Length - 1) / 2;
                            SegLeft[SegMaxIndex] = new SegmentInfo(Seg.Start, SegSep);
                            SegLeft.Insert(SegMaxIndex + 1, new SegmentInfo(SegSep + 1, Seg.End));
                        }
                    }
                }
            }
        }