/// <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; }
/// <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)); } } } } }