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); }
public override async Task <(ResultType result, List <DownloadInfo> downloadInfos)> GetFileUrlsOfVod(CancellationToken cancellationToken) { var video_json = await TwitchYTDL.GetVideoJson(long.Parse(VideoInfo.VideoId)); VideoInfo = new TwitchVideoInfo(TwitchYTDL.VideoFromJson(video_json)); string folderpath; List <DownloadInfo> downloadInfos; while (true) { try { bool interactive = false; if (interactive) { Status = ""; string tmp1 = Path.Combine(GetTempFolder(), GetTempFilenameWithoutExtension() + "_baseurl.txt"); string tmp2 = Path.Combine(GetTempFolder(), GetTempFilenameWithoutExtension() + "_tsnames.txt"); string linesbaseurl = TryGetUserCopyBaseurlM3U(tmp1); string linestsnames = TryGetUserCopyTsnamesM3U(tmp2); if (linesbaseurl == null) { File.WriteAllText(tmp1, "get baseurl for m3u8 from https://www.twitch.tv/videos/" + VideoInfo.VideoId); } if (linestsnames == null) { File.WriteAllText(tmp2, "get actual m3u file from https://www.twitch.tv/videos/" + VideoInfo.VideoId); } if (linesbaseurl == null || linestsnames == null) { await Task.Delay(200); Process.Start(tmp1); await Task.Delay(200); Process.Start(tmp2); await Task.Delay(200); return(ResultType.UserInputRequired, null); } folderpath = TsVideoJob.GetFolder(GetM3U8PathFromM3U(linesbaseurl, VideoQuality)); downloadInfos = TsVideoJob.GetFilenamesFromM3U8(folderpath, linestsnames); } else { string m3u8path = ExtractM3u8FromJson(video_json); folderpath = TsVideoJob.GetFolder(m3u8path); var client = new System.Net.Http.HttpClient(); var result = await client.GetAsync(m3u8path); string m3u8 = await result.Content.ReadAsStringAsync(); downloadInfos = TsVideoJob.GetFilenamesFromM3U8(folderpath, m3u8); } } catch (TwitchHttpException e) { if (e.StatusCode == System.Net.HttpStatusCode.NotFound && VideoInfo.VideoRecordingState == RecordingState.Live) { // this can happen on streams that have just started, in this just wait a bit and retry try { await Task.Delay(20000, cancellationToken); } catch (TaskCanceledException) { return(ResultType.Cancelled, null); } video_json = await TwitchYTDL.GetVideoJson(long.Parse(VideoInfo.VideoId)); VideoInfo = new TwitchVideoInfo(TwitchYTDL.VideoFromJson(video_json)); continue; } else { throw; } } break; } return(ResultType.Success, downloadInfos); }