diff --git a/.gitignore b/.gitignore index a4fd64f..2f84bbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +tmp/ # ---> VisualStudio ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/Program.cs b/Program.cs index 85d876f..4045275 100644 --- a/Program.cs +++ b/Program.cs @@ -20,18 +20,20 @@ namespace ttrss_co_client foreach (var uf in unreadFeeds) { Console.WriteLine($"unread feed: {uf.title}"); - var headlines = await ttrssClient.GetHeadlines(uf.id, view_mode: ttrss.ApiClient.VIEWMODE.Unread); + var headlines = await ttrssClient.GetHeadlines(uf.id, view_mode: ttrss.ApiClient.VIEWMODE.Unread, include_attachments: true); foreach (var hl in headlines) { var labelsWRTFeed = (await ttrssClient.GetLabels(hl.id)); - var actionsForFeed = conf.feedActions.Where(fa => + var actionsForFeed = conf.feedActions.Where(fa => labelsWRTFeed.Where(l => l.@checked).Select(l => l.caption).Contains(fa.triggerlabelCaption))?.ToList(); - if(actionsForFeed != null && actionsForFeed.Any()) + if (actionsForFeed != null && actionsForFeed.Any()) { - foreach(var action in actionsForFeed) + foreach (var action in actionsForFeed) { Console.WriteLine($" {hl.title} -> action: {action.command}"); var noteString = hl.note; + ttrss.datastructures.Label nameLabel; + string podcastName; if (!string.IsNullOrWhiteSpace(noteString)) { noteString += $"{hl.note}\n"; @@ -55,28 +57,53 @@ namespace ttrss_co_client Console.WriteLine($" {hl.title} -> dl failed"); } break; - case "podcastify": - var nameLabel = labelsWRTFeed.FirstOrDefault(l => l.caption?.StartsWith(conf.PodcastTitlePrefix) == true); - var podcastName = nameLabel?.caption.Substring(conf.PodcastTitlePrefix.Length) + case "podcastifyYT": + nameLabel = labelsWRTFeed.FirstOrDefault(l => l.caption?.StartsWith(conf.PodcastTitlePrefix) == true && l.@checked); + podcastName = nameLabel?.caption.Substring(conf.PodcastTitlePrefix.Length) ?? hl.feed_title; - var podcastifyResult = await podcastify(hl.link.ToString(), podcastName); - miscTasks.Add(ttrssClient.UpdateArticleNote($"{noteString}[{DateTime.Now.ToLongTimeString()}] - {podcastifyResult.Item2}", hl.id)); - if (podcastifyResult.Item1 == true) + var YTpodcastifyResult = await podcastifyYT(hl.link.ToString(), podcastName); + miscTasks.Add(ttrssClient.UpdateArticleNote($"{noteString}[{DateTime.Now.ToLongTimeString()}] - {YTpodcastifyResult.Item2}", hl.id)); + if (YTpodcastifyResult.Item1 == true) { - Console.WriteLine($" {hl.title} -> podcastify success, removing labels"); + Console.WriteLine($" {hl.title} -> podcastify (YT) success, removing labels"); miscTasks.Add( ttrssClient.SetArticleLabel( labelsWRTFeed.First(l => l.caption == action.triggerlabelCaption).id, false, hl.id)); - if(nameLabel != null) + if (nameLabel != null) { miscTasks.Add(ttrssClient.SetArticleLabel(nameLabel.id, false, hl.id)); } } else { - Console.WriteLine($" {hl.title} -> podcastify failed"); + Console.WriteLine($" {hl.title} -> podcastify (YT) failed"); + } + break; + case "podcastifyAttachment": + nameLabel = labelsWRTFeed.FirstOrDefault(l => l.caption?.StartsWith(conf.PodcastTitlePrefix) == true && l.@checked); + podcastName = nameLabel?.caption.Substring(conf.PodcastTitlePrefix.Length) + ?? hl.feed_title; + var attachmentLink = hl.attachments.Select(a => a.content_url)?.FirstOrDefault(); + var ATTpodcastifyResult = await podcastifyAttachment(attachmentLink.ToString(), podcastName, hl.title); + miscTasks.Add(ttrssClient.UpdateArticleNote($"{noteString}[{DateTime.Now.ToLongTimeString()}] - {ATTpodcastifyResult.Item2}", hl.id)); + if (ATTpodcastifyResult.Item1 == true) + { + Console.WriteLine($" {hl.title} -> podcastify (att) success, removing labels"); + miscTasks.Add( + ttrssClient.SetArticleLabel( + labelsWRTFeed.First(l => l.caption == action.triggerlabelCaption).id, + false, + hl.id)); + if (nameLabel != null) + { + miscTasks.Add(ttrssClient.SetArticleLabel(nameLabel.id, false, hl.id)); + } + } + else + { + Console.WriteLine($" {hl.title} -> podcastify (att) failed"); } break; default: @@ -96,12 +123,12 @@ namespace ttrss_co_client Console.WriteLine($"done, moving files from temporary location"); var resulted = Directory.GetFiles("tmp", "*.*", SearchOption.AllDirectories); - if(resulted.Count() > 0) + if (resulted.Count() > 0) { - foreach(var f in resulted) + foreach (var f in resulted) { var moveTarget = Path.Combine(conf.OnDoneCopy, f.Substring("tmp/".Length)); - if(!Path.Exists(Path.GetDirectoryName(moveTarget))) + if (!Path.Exists(Path.GetDirectoryName(moveTarget))) { Directory.CreateDirectory(Path.GetDirectoryName(moveTarget)); } @@ -141,64 +168,123 @@ namespace ttrss_co_client private static async Task> standardDL(string articleLink) { Console.WriteLine($" standard downloading {articleLink}"); - var ytdl = new YoutubeDLSharp.YoutubeDL(); - ytdl.YoutubeDLPath = "yt-dlp"; - ytdl.FFmpegPath = "ffmpeg"; - ytdl.OutputFolder = "./tmp/recent episodes"; - ytdl.OutputFileTemplate = "%(upload_date)s - %(title)s - [%(id)s].%(ext)s"; - var sw = new Stopwatch(); - sw.Start(); - var res = await ytdl.RunVideoDownload(articleLink); - sw.Stop(); - var outputStr = $"{(res.Success ? "Success" : "fail")} in {sw.Elapsed}"; - if(res.ErrorOutput != null && res.ErrorOutput.Length > 0) + try { - outputStr += "\n" + string.Join('\n', res.ErrorOutput); - } - Console.WriteLine($" download {(res.Success ? "success" : "failed")}: {articleLink} -> {res.Data}"); - if(!res.Data.EndsWith(".mp4")) - { - Console.WriteLine("must convert video"); - sw.Reset(); - var outputFilename = res.Data.Substring(0, res.Data.LastIndexOf('.')) + ".mp4"; + var ytdl = new YoutubeDLSharp.YoutubeDL(); + ytdl.YoutubeDLPath = "yt-dlp"; + ytdl.FFmpegPath = "ffmpeg"; + ytdl.OutputFolder = "./tmp/recent episodes"; + ytdl.OutputFileTemplate = "%(upload_date)s - %(title)s - [%(id)s].%(ext)s"; + var sw = new Stopwatch(); sw.Start(); - var conversionProc =Process.Start("ffmpeg", $"-y -i \"{res.Data}\" \"{outputFilename}\""); - conversionProc.WaitForExit(); + var res = await ytdl.RunVideoDownload(articleLink); sw.Stop(); - Console.WriteLine($" converted {res.Data} -> {outputFilename}"); - File.Delete(res.Data); - outputStr += $"\nconverted in {sw.Elapsed}"; + var outputStr = $"{(res.Success ? "Success" : "fail")} in {sw.Elapsed}"; + if (res.ErrorOutput != null && res.ErrorOutput.Length > 0) + { + outputStr += "\n" + string.Join('\n', res.ErrorOutput); + } + Console.WriteLine($" download {(res.Success ? "success" : "failed")}: {articleLink} -> {res.Data}"); + if (!res.Data.EndsWith(".mp4")) + { + Console.WriteLine(" must convert video"); + sw.Reset(); + var outputFilename = res.Data.Substring(0, res.Data.LastIndexOf('.')) + ".mp4"; + 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}"; + } + return new Tuple(res.Success, outputStr); + + } + catch (Exception e) + { + Console.Error.WriteLine($"{e.ToString()}: {e.Message}.\n{e.StackTrace}"); + return new Tuple(false, $"{e.ToString()}: {e.Message}.\n{e.StackTrace}"); } - return new Tuple(res.Success, outputStr); } - private static async Task> podcastify(string articleLink, string podcastName) + private static async Task> podcastifyYT(string articleLink, string podcastName) { - var ytdl = new YoutubeDLSharp.YoutubeDL(); - ytdl.YoutubeDLPath = "yt-dlp"; - ytdl.FFmpegPath = "ffmpeg"; - ytdl.OutputFolder = $"./tmp/podcasts/{podcastName}"; - ytdl.OutputFileTemplate = "%(upload_date)s - %(title)s - [%(id)s].%(ext)s"; - var sw = new Stopwatch(); - sw.Start(); - var res = await ytdl.RunAudioDownload(articleLink); - sw.Stop(); - var outputStr = $"{(res.Success ? "Success" : "fail")} in {sw.Elapsed}"; - if(res.ErrorOutput != null && res.ErrorOutput.Length > 0) + Console.WriteLine($" youtube-podcastifying {articleLink} ({podcastName})"); + try { - outputStr += "\n" + string.Join('\n', res.ErrorOutput); - } - if(!res.Data.EndsWith(".mp3")) - { - sw.Reset(); - var outputFilename = res.Data.Substring(0, res.Data.LastIndexOf('.')) + ".mp3"; + var ytdl = new YoutubeDLSharp.YoutubeDL(); + ytdl.YoutubeDLPath = "yt-dlp"; + ytdl.FFmpegPath = "ffmpeg"; + ytdl.OutputFolder = $"./tmp/podcasts/{podcastName}"; + ytdl.OutputFileTemplate = "%(upload_date)s - %(title)s - [%(id)s].%(ext)s"; + var sw = new Stopwatch(); sw.Start(); - var conversionProc =Process.Start("ffmpeg", $"-y -i \"{res.Data}\" \"{outputFilename}\""); - conversionProc.WaitForExit(); + var res = await ytdl.RunAudioDownload(articleLink); sw.Stop(); - File.Delete(res.Data); - outputStr += $"\nconverted in {sw.Elapsed}"; + var outputStr = $"{(res.Success ? "Success" : "fail")} in {sw.Elapsed}"; + if (res.ErrorOutput != null && res.ErrorOutput.Length > 0) + { + outputStr += "\n" + string.Join('\n', res.ErrorOutput); + } + Console.WriteLine($" {(res.Success ? "Success" : "fail")} in {sw.Elapsed} - {res.Data}"); + if (res.Success && !res.Data.EndsWith(".mp3")) + { + Console.WriteLine(" must convert audio"); + sw.Reset(); + var outputFilename = res.Data.Substring(0, res.Data.LastIndexOf('.')) + ".mp3"; + sw.Start(); + var conversionProc = Process.Start("ffmpeg", $"-y -i \"{res.Data}\" \"{outputFilename}\""); + conversionProc.WaitForExit(); + sw.Stop(); + File.Delete(res.Data); + outputStr += $"\nconverted in {sw.Elapsed}"; + } + return new Tuple(res.Success, outputStr); + } + catch (Exception e) + { + Console.Error.WriteLine($"{e.ToString()}: {e.Message}.\n{e.StackTrace}"); + return new Tuple(false, $"{e.ToString()}: {e.Message}.\n{e.StackTrace}"); + } + } + private static async Task> podcastifyAttachment(string attachmentLink, string podcastName, string episodeTitle) + { + Console.WriteLine($" attachment-podcastifying {attachmentLink} ({podcastName})"); + try + { + var extensionUpstream = attachmentLink.Substring(attachmentLink.LastIndexOf('.')); + var containingDirectory = $"./tmp/podcasts/{podcastName}/"; + var outputFilename = $"{containingDirectory}{episodeTitle}{extensionUpstream}"; + if(!Directory.Exists(containingDirectory)) + { + Directory.CreateDirectory(containingDirectory); + } + var downloader = new HttpClient(); + var sw = new Stopwatch(); + sw.Start(); + File.WriteAllBytes(outputFilename, await (await downloader.GetAsync(attachmentLink)).Content.ReadAsByteArrayAsync()); + sw.Stop(); + var outputStr = $"{(File.Exists(outputFilename) ? "Success" : "fail")} in {sw.Elapsed}"; + Console.WriteLine($" {outputStr} - {outputFilename}"); + if (File.Exists(outputFilename) && extensionUpstream != ".mp3") + { + Console.WriteLine(" must convert audio"); + sw.Reset(); + var targetFilename = outputFilename.Substring(0, outputFilename.LastIndexOf('.')) + ".mp3"; + sw.Start(); + var conversionProc = Process.Start("ffmpeg", $"-y -i \"{outputFilename}\" \"{targetFilename}\""); + conversionProc.WaitForExit(); + sw.Stop(); + File.Delete(outputFilename); + outputStr += $"\nconverted in {sw.Elapsed}"; + } + return new Tuple(true, outputStr); + } + catch (Exception e) + { + Console.Error.WriteLine($"{e.ToString()}: {e.Message}.\n{e.StackTrace}"); + return new Tuple(false, $"{e.ToString()}: {e.Message}.\n{e.StackTrace}"); } - return new Tuple(res.Success, outputStr); } } } \ No newline at end of file diff --git a/sample-appsettings.json b/sample-appsettings.json index 06448f2..2a46a81 100644 --- a/sample-appsettings.json +++ b/sample-appsettings.json @@ -11,8 +11,12 @@ "command":"dl" }, { - "triggerlabelCaption":"podcastify plz", - "command":"podcastify" + "triggerlabelCaption":"podcastify-yt plz", + "command":"podcastifyYT" + }, + { + "triggerlabelCaption":"podcastify-attachment plz", + "command":"podcastifyAttachment" } ] } \ No newline at end of file diff --git a/ttrss/datastructures/Headline.cs b/ttrss/datastructures/Headline.cs index cc5099a..d7c7aca 100644 --- a/ttrss/datastructures/Headline.cs +++ b/ttrss/datastructures/Headline.cs @@ -18,6 +18,7 @@ namespace ttrss_co_client.ttrss.datastructures public IEnumerable tags { get; set; } public string content { get; set; } ///See + public IEnumerable attachments { get; set; } public IEnumerable> labels { get; set; } public string feed_title { get; set; } public int comments_count { get; set; }