Ejemplo n.º 1
0
        private ContextMenuStrip CreateItemMenu(object model, BrightIdeasSoftware.OLVColumn column)
        {
            if (model != null && model as IVideoJob != null)
            {
                IVideoJob        job  = model as IVideoJob;
                ContextMenuStrip menu = new ContextMenuStrip();

                {
                    IUserInputRequest uir = job.UserInputRequest;
                    if (uir != null)
                    {
                        ToolStripMenuItem item = new ToolStripMenuItem(uir.GetQuestion());
                        foreach (string option in uir.GetOptions())
                        {
                            item.DropDownItems.Add(option).Click += (sender, e) => {
                                if (uir != null)
                                {
                                    uir.SelectOption(option);
                                }
                            };
                        }
                        menu.Items.Add(item);
                        menu.Items.Add(new ToolStripSeparator());
                    }
                }

                bool queueOptionsAvailable = false;
                if ((job.JobStatus == VideoJobStatus.NotStarted || job.JobStatus == VideoJobStatus.Dead) && !VideoTaskGroups[job.VideoInfo.Service].IsInQueue(job))
                {
                    queueOptionsAvailable = true;
                    ToolStripItem item = menu.Items.Add("Enqueue");
                    item.Click += (sender, e) => {
                        VideoTaskGroups[job.VideoInfo.Service].Add(new WaitingVideoJob(job));
                    };
                }

                if ((job.JobStatus == VideoJobStatus.NotStarted || job.JobStatus == VideoJobStatus.Dead) && VideoTaskGroups[job.VideoInfo.Service].IsInQueue(job))
                {
                    queueOptionsAvailable = true;
                    ToolStripItem item = menu.Items.Add("Dequeue");
                    item.Click += (sender, e) => {
                        VideoTaskGroups[job.VideoInfo.Service].Dequeue(job);
                    };
                }

                if (job.JobStatus == VideoJobStatus.NotStarted || job.JobStatus == VideoJobStatus.Dead)
                {
                    queueOptionsAvailable = true;
                    ToolStripItem item = menu.Items.Add("Download now");
                    item.Click += (sender, e) => {
                        if (job.JobStatus == VideoJobStatus.NotStarted || job.JobStatus == VideoJobStatus.Dead)
                        {
                            VideoTaskGroups[job.VideoInfo.Service].Add(new WaitingVideoJob(job, true));
                        }
                    };
                }

                if (queueOptionsAvailable)
                {
                    menu.Items.Add(new ToolStripSeparator());
                }


                {
                    ToolStripItem item = menu.Items.Add("Copy Video ID");
                    item.Click += (sender, e) => {
                        string id = job?.VideoInfo?.VideoId;
                        if (id != null)
                        {
                            Clipboard.SetText(id);
                        }
                    };
                }
                {
                    ToolStripItem item = menu.Items.Add("Copy Output Filename");
                    item.Click += (sender, e) => {
                        string fn = job?.GenerateOutputFilename();
                        if (fn != null)
                        {
                            Clipboard.SetText(fn);
                        }
                    };
                }

                menu.Items.Add(new ToolStripSeparator());

                if (job.JobStatus == VideoJobStatus.Running)
                {
                    ToolStripItem item = menu.Items.Add("Stop");
                    item.Click += (sender, e) => {
                        if (job.JobStatus == VideoJobStatus.Running)
                        {
                            VideoTaskGroups[job.VideoInfo.Service].CancelJob(job);
                        }
                    };
                }

                if (job.JobStatus == VideoJobStatus.NotStarted)
                {
                    ToolStripItem item = menu.Items.Add("Kill");
                    item.Click += (sender, e) => {
                        if (job.JobStatus == VideoJobStatus.NotStarted)
                        {
                            job.JobStatus = VideoJobStatus.Dead;
                            job.Status    = "[Manually killed] " + job.Status;
                        }
                    };
                }

                if (job.JobStatus != VideoJobStatus.Running)
                {
                    ToolStripItem item = menu.Items.Add("Remove");
                    item.Click += (sender, e) => {
                        if (job.JobStatus != VideoJobStatus.Running)
                        {
                            objectListViewDownloads.RemoveObject(job);
                            JobSet.Remove(job);
                        }
                    };
                }

                return(menu.Items.Count > 0 ? menu : null);
            }
            return(null);
        }
        public override async Task <ResultType> Run(CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(ResultType.Cancelled);
            }

            JobStatus = VideoJobStatus.Running;
            Status    = "Retrieving video info...";
            var video_json = await TwitchYTDL.GetVideoJson(long.Parse(VideoInfo.VideoId));

            VideoInfo = new TwitchVideoInfo(TwitchYTDL.VideoFromJson(video_json), StreamService.TwitchChatReplay);
            if (!AssumeFinished && VideoInfo.VideoRecordingState == RecordingState.Live)
            {
                _UserInputRequest = new UserInputRequestStreamLive(this);
                return(ResultType.TemporarilyUnavailable);
            }

            string tempname       = Path.Combine(Util.TempFolderPath, GetTempFilenameWithoutExtension() + ".json.tmp");
            string finalintmpname = Path.Combine(Util.TempFolderPath, GetTempFilenameWithoutExtension() + ".json");
            string filename       = Path.Combine(Util.TargetFolderPath, GetTargetFilenameWithoutExtension() + ".json");
            Random rng            = new Random(int.Parse(VideoInfo.VideoId));

            if (!await Util.FileExists(filename))
            {
                if (!await Util.FileExists(finalintmpname))
                {
                    Status = "Downloading chat (Initial)...";
                    StringBuilder concatJson            = new StringBuilder();
                    string        url                   = GetStartUrl(VideoInfo);
                    int           attemptsLeft          = 5;
                    TimeSpan?     lastTimeSpan          = new TimeSpan(0);
                    int           nextDelayMilliseconds = 0;
                    while (true)
                    {
                        using (var client = new KeepAliveWebClient())
                            using (var cancellationCallback = cancellationToken.Register(client.CancelAsync)) {
                                try {
                                    try {
                                        if (nextDelayMilliseconds != 0)
                                        {
                                            await Task.Delay(nextDelayMilliseconds > 0?nextDelayMilliseconds : rng.Next(90000, 270000), cancellationToken);
                                        }
                                    } catch (TaskCanceledException) {
                                        return(ResultType.Cancelled);
                                    }

                                    string commentJson = await TwitchAPI.GetLegacy(url, Util.TwitchClientId);

                                    JObject responseObject = JObject.Parse(commentJson);
                                    if (responseObject["comments"] == null)
                                    {
                                        throw new Exception("Nonsense JSON returned, no comments.");
                                    }

                                    string offset = "Unknown";
                                    try {
                                        JToken   c   = ((JArray)responseObject["comments"]).Last;
                                        double   val = (double)c["content_offset_seconds"];
                                        TimeSpan ts  = TimeSpan.FromSeconds(val);
                                        if (lastTimeSpan != null)
                                        {
                                            TimeSpan diff  = ts - lastTimeSpan.Value;
                                            double   delay = diff.TotalMilliseconds;
                                            if (delay < 0.0)
                                            {
                                                nextDelayMilliseconds = -1;
                                            }
                                            else if (delay < 90000.0)
                                            {
                                                nextDelayMilliseconds = (int)delay;
                                            }
                                            else if (delay < 270000.0)
                                            {
                                                nextDelayMilliseconds = rng.Next(90000, (int)delay);
                                            }
                                            else
                                            {
                                                nextDelayMilliseconds = -1;
                                            }
                                        }
                                        else
                                        {
                                            nextDelayMilliseconds = -1;
                                        }
                                        lastTimeSpan = ts;
                                        offset       = ts.ToString();
                                    } catch (Exception) {
                                        lastTimeSpan          = null;
                                        nextDelayMilliseconds = -1;
                                    }

                                    concatJson.Append(commentJson);
                                    if (responseObject["_next"] != null)
                                    {
                                        string next = (string)responseObject["_next"];
                                        attemptsLeft = 5;
                                        Status       = "Downloading chat (Last offset: " + offset + "; next file: " + next + ")...";
                                        url          = GetNextUrl(VideoInfo, next);
                                    }
                                    else
                                    {
                                        // presumably done?
                                        break;
                                    }
                                } catch (System.Net.WebException ex) {
                                    Console.WriteLine(ex.ToString());
                                    --attemptsLeft;
                                    Status = "Downloading chat (Error; " + attemptsLeft + " attempts left)...";
                                    if (attemptsLeft <= 0)
                                    {
                                        throw;
                                    }
                                    continue;
                                }
                            }
                    }

                    await StallWrite(tempname, concatJson.Length * 4, cancellationToken);                       // size not accurate because encoding but whatever

                    if (cancellationToken.IsCancellationRequested)
                    {
                        return(ResultType.Cancelled);
                    }
                    File.WriteAllText(tempname, concatJson.ToString());
                    File.Move(tempname, finalintmpname);
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return(ResultType.Cancelled);
                }
                Status = "Moving to final location...";
                await StallWrite(filename, new FileInfo( finalintmpname ).Length, cancellationToken);

                if (cancellationToken.IsCancellationRequested)
                {
                    return(ResultType.Cancelled);
                }
                Util.MoveFileOverwrite(finalintmpname, filename);
            }

            Status    = "Done!";
            JobStatus = VideoJobStatus.Finished;
            return(ResultType.Success);
        }
Ejemplo n.º 3
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);
        }