コード例 #1
0
        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);
        }
コード例 #2
0
        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);
        }
コード例 #3
0
        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);
        }