public override async Task <ResultType> Run(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } JobStatus = VideoJobStatus.Running; Status = "Retrieving video info..."; (ResultType getFileUrlsResult, List <DownloadInfo> downloadInfos) = await GetFileUrlsOfVod(cancellationToken); if (getFileUrlsResult == ResultType.UserInputRequired) { Status = "Need manual fetch of file URLs."; return(ResultType.UserInputRequired); } if (getFileUrlsResult != ResultType.Success) { Status = "Failed retrieving file URLs."; return(ResultType.Failure); } string combinedTempname = Path.Combine(GetTempFolder(), GetTempFilenameWithoutExtension() + "_combined.ts"); string combinedFilename = Path.Combine(GetTempFolder(), GetFinalFilenameWithoutExtension() + ".ts"); string remuxedTempname = Path.Combine(GetTempFolder(), GetTempFilenameWithoutExtension() + "_combined.mp4"); string remuxedFilename = Path.Combine(GetTempFolder(), GetFinalFilenameWithoutExtension() + ".mp4"); string targetFilename = Path.Combine(GetTargetFolder(), GetFinalFilenameWithoutExtension() + ".mp4"); string baseurlfilepath = Path.Combine(GetTempFolder(), GetTempFilenameWithoutExtension() + "_baseurl.txt"); string tsnamesfilepath = Path.Combine(GetTempFolder(), GetTempFilenameWithoutExtension() + "_tsnames.txt"); if (!await Util.FileExists(targetFilename)) { if (!await Util.FileExists(combinedFilename)) { if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } Status = "Downloading files..."; string[] files; while (true) { if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch(); timer.Start(); var downloadResult = await Download(this, cancellationToken, GetTempFolderForParts(), downloadInfos); if (downloadResult.result != ResultType.Success) { return(downloadResult.result); } files = downloadResult.files; if (this.AssumeFinished || this.VideoInfo.VideoRecordingState != RecordingState.Live) { break; } else { // we're downloading a stream that is still streaming timer.Stop(); // if too little time has passed wait a bit to allow the stream to provide new data if (timer.Elapsed.TotalMinutes < 2.5) { TimeSpan ts = TimeSpan.FromMinutes(2.5) - timer.Elapsed; Status = "Waiting " + ts.TotalSeconds + " seconds for stream to update..."; _UserInputRequest = new UserInputRequestStreamLive(this); try { await Task.Delay(ts, cancellationToken); } catch (TaskCanceledException) { return(ResultType.Cancelled); } } if (File.Exists(tsnamesfilepath)) { File.Delete(tsnamesfilepath); } (getFileUrlsResult, downloadInfos) = await GetFileUrlsOfVod(cancellationToken); if (getFileUrlsResult != ResultType.Success) { Status = "Failed retrieving file URLs."; return(ResultType.Failure); } } } _UserInputRequest = null; Status = "Waiting for free disk IO slot to combine..."; try { await Util.ExpensiveDiskIOSemaphore.WaitAsync(cancellationToken); } catch (OperationCanceledException) { return(ResultType.Cancelled); } try { long expectedTargetFilesize = 0; foreach (var file in files) { expectedTargetFilesize += new FileInfo(file).Length; } Status = "Combining downloaded video parts..."; if (await Util.FileExists(combinedTempname)) { await Util.DeleteFile(combinedTempname); } await StallWrite(combinedFilename, expectedTargetFilesize, cancellationToken); if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } ResultType combineResult = await TsVideoJob.Combine(cancellationToken, combinedTempname, files); if (combineResult != ResultType.Success) { return(combineResult); } // sanity check Status = "Sanity check on combined video..."; TimeSpan actualVideoLength = (await FFMpegUtil.Probe(combinedTempname)).Duration; TimeSpan expectedVideoLength = VideoInfo.VideoLength; if (!IgnoreTimeDifferenceCombined && actualVideoLength.Subtract(expectedVideoLength).Duration() > TimeSpan.FromSeconds(5)) { // if difference is bigger than 5 seconds something is off, report Status = "Large time difference between expected (" + expectedVideoLength.ToString() + ") and combined (" + actualVideoLength.ToString() + "), stopping."; _UserInputRequest = new UserInputRequestTimeMismatchCombined(this); _IsWaitingForUserInput = true; return(ResultType.UserInputRequired); } Util.MoveFileOverwrite(combinedTempname, combinedFilename); await Util.DeleteFiles(files); System.IO.Directory.Delete(GetTempFolderForParts()); } finally { Util.ExpensiveDiskIOSemaphore.Release(); } } Status = "Waiting for free disk IO slot to remux..."; try { await Util.ExpensiveDiskIOSemaphore.WaitAsync(cancellationToken); } catch (OperationCanceledException) { return(ResultType.Cancelled); } try { Status = "Remuxing to MP4..."; if (await Util.FileExists(remuxedTempname)) { await Util.DeleteFile(remuxedTempname); } await StallWrite(remuxedFilename, new FileInfo( combinedFilename ).Length, cancellationToken); if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } await Task.Run(() => TsVideoJob.Remux(remuxedFilename, combinedFilename, remuxedTempname)); // sanity check Status = "Sanity check on remuxed video..."; TimeSpan actualVideoLength = (await FFMpegUtil.Probe(remuxedFilename)).Duration; TimeSpan expectedVideoLength = VideoInfo.VideoLength; if (!IgnoreTimeDifferenceRemuxed && actualVideoLength.Subtract(expectedVideoLength).Duration() > TimeSpan.FromSeconds(5)) { // if difference is bigger than 5 seconds something is off, report Status = "Large time difference between expected (" + expectedVideoLength.ToString() + ") and remuxed (" + actualVideoLength.ToString() + "), stopping."; _UserInputRequest = new UserInputRequestTimeMismatchRemuxed(this); _IsWaitingForUserInput = true; return(ResultType.UserInputRequired); } Util.MoveFileOverwrite(remuxedFilename, targetFilename); } finally { Util.ExpensiveDiskIOSemaphore.Release(); } } Status = "Done!"; JobStatus = VideoJobStatus.Finished; if (File.Exists(tsnamesfilepath)) { File.Delete(tsnamesfilepath); } if (File.Exists(baseurlfilepath)) { File.Delete(baseurlfilepath); } return(ResultType.Success); }
public override async Task <ResultType> Run(CancellationToken cancellationToken) { JobStatus = VideoJobStatus.Running; Status = "Checking files..."; if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } string file = VideoInfo.VideoId; string path = Path.GetDirectoryName(file); string name = Path.GetFileNameWithoutExtension(file); List <string> ffmpegOptions; string postfixOld; string postfixNew; string outputformat; if (VideoInfo is FFMpegReencodeJobVideoInfo) { FFMpegReencodeJobVideoInfo ffvi = VideoInfo as FFMpegReencodeJobVideoInfo; ffmpegOptions = ffvi.FFMpegOptions; postfixOld = ffvi.PostfixOld; postfixNew = ffvi.PostfixNew; outputformat = ffvi.OutputFormat; } else { ffmpegOptions = new List <string>() { "-c:v", "libx264", "-preset", "slower", "-crf", "23", "-g", "2000", "-c:a", "copy", "-max_muxing_queue_size", "100000", }; postfixOld = "_chunked"; postfixNew = "_x264crf23"; outputformat = "mp4"; } string ext = "." + outputformat; string chunked = postfixOld; string postfix = postfixNew; string newfile = Path.Combine(path, postfix, name.Substring(0, name.Length - chunked.Length) + postfix + ext); string newfileinlocal = Path.Combine(path, name.Substring(0, name.Length - chunked.Length) + postfix + ext); string tempfile = Path.Combine(path, name.Substring(0, name.Length - chunked.Length) + postfix + "_TEMP" + ext); string chunkeddir = Path.Combine(path, chunked); string postfixdir = Path.Combine(path, postfix); string oldfileinchunked = Path.Combine(chunkeddir, Path.GetFileName(file)); FFProbeResult probe = null; string encodeinput = null; if (await Util.FileExists(file)) { probe = await FFMpegUtil.Probe(file); encodeinput = file; } else if (await Util.FileExists(oldfileinchunked)) { probe = await FFMpegUtil.Probe(oldfileinchunked); encodeinput = oldfileinchunked; } if (probe != null) { VideoInfo = new FFMpegReencodeJobVideoInfo(file, probe, ffmpegOptions, postfixOld, postfixNew, outputformat); } // if the input file doesn't exist we might still be in a state where we can set this to finished if the output file already exists, so continue anyway bool newfileexists = await Util.FileExists(newfile); bool newfilelocalexists = await Util.FileExists(newfileinlocal); if (!newfileexists && !newfilelocalexists) { if (encodeinput == null) { // neither input nor output exist, bail Status = "Missing!"; return(ResultType.Failure); } if (await Util.FileExists(tempfile)) { await Util.DeleteFile(tempfile); } if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } Status = "Encoding " + newfile + "..."; await StallWrite(newfile, new FileInfo( encodeinput ).Length, cancellationToken); if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } Directory.CreateDirectory(postfixdir); FFMpegReencodeJobVideoInfo ffmpegVideoInfo = VideoInfo as FFMpegReencodeJobVideoInfo; await Reencode(newfile, encodeinput, tempfile, ffmpegVideoInfo.FFMpegOptions); } if (!newfileexists && newfilelocalexists) { Directory.CreateDirectory(postfixdir); File.Move(newfileinlocal, newfile); } if (await Util.FileExists(file) && !await Util.FileExists(oldfileinchunked)) { Directory.CreateDirectory(chunkeddir); File.Move(file, oldfileinchunked); } Status = "Done!"; JobStatus = VideoJobStatus.Finished; return(ResultType.Success); }
public override async Task <ResultType> Run(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } JobStatus = VideoJobStatus.Running; if ((VideoInfo as YoutubeVideoInfo) == null) { Status = "Retrieving video info..."; bool wantCookies = Notes != null && Notes.Contains("cookies"); var result = await Youtube.RetrieveVideo(VideoInfo.VideoId, VideoInfo.Username, wantCookies); switch (result.result) { case Youtube.RetrieveVideoResult.Success: VideoInfo = result.info; break; case Youtube.RetrieveVideoResult.ParseFailure: // this seems to happen randomly from time to time, just retry later return(ResultType.TemporarilyUnavailable); default: return(ResultType.Failure); } } string filenameWithoutExtension = "youtube_" + VideoInfo.Username + "_" + VideoInfo.VideoTimestamp.ToString("yyyy-MM-dd") + "_" + VideoInfo.VideoId; string filename = filenameWithoutExtension + ".mkv"; string tempFolder = Path.Combine(Util.TempFolderPath, filenameWithoutExtension); string tempFilepath = Path.Combine(tempFolder, filename); { if (!await Util.FileExists(tempFilepath)) { if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } Directory.CreateDirectory(tempFolder); Status = "Running youtube-dl..."; await StallWrite(tempFilepath, 0, cancellationToken); // don't know expected filesize, so hope we have a sensible value in minimum free space if (cancellationToken.IsCancellationRequested) { return(ResultType.Cancelled); } List <string> args = new List <string>() { "-f", "bestvideo[height<=?1080]+bestaudio/best", "-o", tempFilepath, "--merge-output-format", "mkv", "--no-color", "--abort-on-error", "--abort-on-unavailable-fragment", "--no-sponsorblock", }; string limit = Util.YoutubeSpeedLimit; if (limit != "") { args.Add("--rate-limit"); args.Add(limit); } bool wantCookies = Notes != null && Notes.Contains("cookies"); if (wantCookies) { args.Add("--cookies"); args.Add(@"d:\cookies.txt"); } bool nokill = Notes != null && Notes.Contains("nokill"); args.Add("https://www.youtube.com/watch?v=" + VideoInfo.VideoId); var data = await ExternalProgramExecution.RunProgram( @"yt-dlp", args.ToArray(), youtubeSpeedWorkaround : !nokill, stdoutCallbacks : new System.Diagnostics.DataReceivedEventHandler[1] { (sender, received) => { if (!String.IsNullOrEmpty(received.Data)) { Status = received.Data; } } } ); } string finalFilename = GenerateOutputFilename(); string finalFilepath = Path.Combine(Util.TargetFolderPath, finalFilename); if (File.Exists(finalFilepath)) { throw new Exception("File exists: " + finalFilepath); } Status = "Waiting for free disk IO slot to move..."; try { await Util.ExpensiveDiskIOSemaphore.WaitAsync(cancellationToken); } catch (OperationCanceledException) { return(ResultType.Cancelled); } try { // sanity check Status = "Sanity check on downloaded video..."; TimeSpan actualVideoLength = (await FFMpegUtil.Probe(tempFilepath)).Duration; TimeSpan expectedVideoLength = VideoInfo.VideoLength; if (actualVideoLength.Subtract(expectedVideoLength).Duration() > TimeSpan.FromSeconds(5)) { // if difference is bigger than 5 seconds something is off, report Status = "Large time difference between expected (" + expectedVideoLength.ToString() + ") and actual (" + actualVideoLength.ToString() + "), stopping."; return(ResultType.Failure); } Status = "Moving..."; await Task.Run(() => Util.MoveFileOverwrite(tempFilepath, finalFilepath)); await Task.Run(() => Directory.Delete(tempFolder)); } finally { Util.ExpensiveDiskIOSemaphore.Release(); } } Status = "Done!"; JobStatus = VideoJobStatus.Finished; return(ResultType.Success); }