diff --git a/Program.cs b/Program.cs index f8d93da..6a83f51 100644 --- a/Program.cs +++ b/Program.cs @@ -287,50 +287,5 @@ namespace ttrss_co_client return new Tuple(false, $"{e.ToString()}: {e.Message}.\n{e.StackTrace}"); } } - // private static async Task> SponsorCheck(ttrss.datastructures.Headline hl) - // { - // if (!hl.link.Host.EndsWith("youtube.com")) - // { - // return new Tuple(true, "sponsorblock, sadly, only exists for youtube"); - // } - // var match = Regex.Match(hl.link.Query, "v=([^&]+)(&|$)"); - // var videoId = match.Groups?[1].Value; - // var c = new HttpClient(); - - // var sponsorblockcheck = await c.GetAsync($"https://sponsor.ajay.app/api/skipSegments?videoID={videoId}&category=sponsor&category=selfpromo&category=interaciton&category=intro&category=outro&category=preview"); - // if (sponsorblockcheck.StatusCode == System.Net.HttpStatusCode.NotFound) - // { - // Console.WriteLine($"sponsorblock reports that {videoId} has no entries (yet)"); - // var updateTimestamp = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - // updateTimestamp = updateTimestamp.AddSeconds(hl.updated).ToLocalTime(); - // if (DateTime.Now - updateTimestamp > TimeSpan.FromMinutes(45)) - // { - // return new Tuple(true, $"updated {updateTimestamp} (more than 45 minutes ago), going to give up waiting for sponsorblock"); - // } - // else - // { - // return new Tuple(false, "none found, waiting"); - // } - // } - // else - // { - // try - // { - // var segments = JsonConvert.DeserializeObject>(await sponsorblockcheck.Content.ReadAsStringAsync()); - // if (segments.Count() > 1) - // { - // return new Tuple(true, $"{segments.Count()} segments"); - // } - // else - // { - // return new Tuple(false, $"no segments"); - // } - // } - // catch (Exception e) - // { - // return new Tuple(false, $"{e.ToString()} - {e.Message}"); - // } - // } - // } } } \ No newline at end of file diff --git a/tasks/Publish.cs b/tasks/Publish.cs index 109265e..27db26e 100644 --- a/tasks/Publish.cs +++ b/tasks/Publish.cs @@ -8,6 +8,7 @@ namespace ttrss_co_client.tasks ///Move to output public class Publish : Phase2Task { + public override string TaskName => "filemovePublish"; public override async Task>ActOn(WorkOrder workOrder) { var wo = workOrder as PublishWorkOrder; @@ -22,4 +23,4 @@ namespace ttrss_co_client.tasks public string Path => data["path"]; public string PublishTarget => data["publish-target"]; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/tasks/Sponsorblock.cs b/tasks/Sponsorblock.cs index 758e8fe..e6cc387 100644 --- a/tasks/Sponsorblock.cs +++ b/tasks/Sponsorblock.cs @@ -1,15 +1,115 @@ -// using System.Diagnostics; -// using ttrss_co_client.ttrss; -// using ttrss_co_client.ttrss.datastructures; +using Newtonsoft.Json; +using System.Linq; +using System.Diagnostics; +using System.Text.RegularExpressions; +using ttrss_co_client.ttrss; +using ttrss_co_client.ttrss.datastructures; -// namespace ttrss_co_client.tasks -// { -// ///Move to output -// public class Sponsorblock : Phase2Task -// { -// public override async Task ActOn(WorkOrder wo) -// { +namespace ttrss_co_client.tasks +{ + ///Move to output + public class Sponsorblock : Phase2Task + { + public override string TaskName => "sponsorblock"; + public override async Task>ActOn(WorkOrder workOrder) + { + var sw = new Stopwatch(); + sw.Start(); + var article = (await TtrssClient.GetArticles(workOrder.articleId)).FirstOrDefault(); + if(article == null) + { + Console.Error.WriteLine($"couldn't find article {workOrder.articleId} for workOrder {workOrder.guid}?!"); + return new Tuple(TaskStatus.Done, null); + } + if (!article.link.Host.EndsWith("youtube.com")) + { + Console.WriteLine("no sponsorblock segments on sites other than YT"); + return new Tuple(TaskStatus.ContinueNow, workOrder); + } + var match = Regex.Match(article.link.Query, "v=([^&]+)(&|$)"); + var videoId = match.Groups?[1].Value; + var c = new HttpClient(); -// } -// } -// } \ No newline at end of file + var sponsorblockcheck = await c.GetAsync($"https://sponsor.ajay.app/api/skipSegments?videoID={videoId}&category=sponsor&category=selfpromo&category=interaciton&category=intro&category=outro&category=preview"); + IEnumerable segments = null; + try + { + segments = JsonConvert.DeserializeObject>(await sponsorblockcheck.Content.ReadAsStringAsync()); + } + catch (Exception e) + { + segments = null; + } + if (sponsorblockcheck.StatusCode == System.Net.HttpStatusCode.NotFound || segments == null || segments.Count() == 0) + { + Console.WriteLine($"sponsorblock reports that {videoId} has no entries (yet)"); + var updateTimestamp = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + updateTimestamp = updateTimestamp.AddSeconds(article.updated).ToLocalTime(); + if (DateTime.Now - updateTimestamp > TimeSpan.FromMinutes(45)) + { + await TtrssClient.UpdateArticleNote($"{article.note}\n[{DateTime.Now.ToLongTimeString()}] updated {updateTimestamp} (more than 45 minutes ago), going to give up waiting for sponsorblock", article.id); + return new Tuple(TaskStatus.ContinueNow, workOrder); + } + else + { + await TtrssClient.UpdateArticleNote($"{article.note}\n[{DateTime.Now.ToLongTimeString()}] waiting for sponsorblock segments", article.id); + workOrder.Phase2TaskList[workOrder.Phase2TaskList.Keys.Min() - 1] = this.TaskName; + return new Tuple(TaskStatus.TryLater, workOrder); + } + } + else + { + var contentSegments = new List>(); + var previousEdge = 0.0; + var extension = workOrder.data["path"].Substring(workOrder.data["path"].LastIndexOf('.')); + var intermediatePathBase = workOrder.data["path"].Substring(0, workOrder.data["path"].LastIndexOf('.')); + var conversionProcesses = new List>>(); + + foreach(var junkSegment in segments.OrderBy(s => s.segment[0])) + { + contentSegments.Add(new Tuple(previousEdge, junkSegment.segment[0])); + previousEdge = junkSegment.segment[1]; + if(previousEdge < 0.5) + { + contentSegments.Clear(); + } + } + contentSegments.Add(new Tuple(previousEdge, segments.First().videoDuration)); + + var intermediateCount = 0; + foreach(var seg in contentSegments) + { + var intermediateTargetPath = $"{intermediatePathBase}-intermediate-{intermediateCount.ToString("ddd")}{extension}"; + conversionProcesses.Add(new Tuple>( + Process.Start("ffmpeg", $"-y -i \"{workOrder.data["path"]}\" \"{intermediateTargetPath}\""), + new Tuple(intermediateCount, intermediateTargetPath) + )); + intermediateCount++; + } + Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}] intermediate content segments being exported"); + var intermediates = new Dictionary(); + foreach(var proc in conversionProcesses) + { + proc.Item1.WaitForExit(); + intermediates[proc.Item2.Item1] = proc.Item2.Item2; + } + Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}] intermediate content segments should be exported, stitching together"); + + Process.Start("ffmpeg", $"-y -i \"concat:{string.Join('|', intermediates.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value).ToArray())}\" -c copy \"{workOrder.data["path"]}\"") + .WaitForExit(); + + Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}] intermediate content segments stitched. Deleting originals."); + foreach(var intermediate in intermediates.Values) + { + File.Delete(intermediate); + } + + sw.Stop(); + + await TtrssClient.UpdateArticleNote($"{article.note}\n[{DateTime.Now.ToLongTimeString()}] removed {segments.Count()} junk segments", article.id); + + return new Tuple(TaskStatus.ContinueNow, workOrder); + } + } + } +} \ No newline at end of file diff --git a/tasks/StandardDL.cs b/tasks/StandardDL.cs index c41bd86..64c2ec3 100644 --- a/tasks/StandardDL.cs +++ b/tasks/StandardDL.cs @@ -51,29 +51,20 @@ namespace ttrss_co_client.tasks }; toReturn.data["path"] = outputFilename; + if(headline.link.Host.EndsWith("youtube.com")) + { + toReturn.Phase2TaskList[0] = "sponsorblock"; + } if(!outputFilename.EndsWith(".mp4")) { Console.WriteLine($"{headline.title} needs conversion task."); - toReturn.Phase2TaskList[0] = "convert"; + toReturn.Phase2TaskList[1] = "convert"; toReturn.data["conversion-target"] = outputFilename.Substring(0, res.Data.LastIndexOf('.')) + ".mp4"; } - if(headline.link.Host.EndsWith("youtube.com")) - { - toReturn.Phase2TaskList[1] = "sponsorblock"; - } toReturn.Phase2TaskList[2] = "filemovePublish"; - toReturn.data["publish-target"] = $"{Conf.OnDoneCopy}/recent episodes/{toReturn.data["conversion-target"]}"; + toReturn.data["publish-target"] = $"{Conf.OnDoneCopy}/recent episodes/{Path.GetFileName(toReturn.data["conversion-target"])}"; return toReturn; - // sw.Reset(); - // outputFilename = - // sw.Start(); - // var conversionProc = Process.Start("ffmpeg", $"-y -i \"{res.Data}\" \"{outputFilename}\""); - // conversionProc.WaitForExit(); - // sw.Stop(); - // Console.WriteLine($" converted {res.Data} -> {outputFilename}"); - // File.Delete(res.Data); - //outputStr += $"\nconverted in {sw.Elapsed}"; } } catch (Exception e)