From 321165acff110aed9b81cb51af39c31a0ea3757f Mon Sep 17 00:00:00 2001 From: Cristian Stoica Date: Thu, 7 Mar 2024 21:59:59 +0200 Subject: [PATCH] Updated YoutubeExplode library to the latest version (6.3.13); Refactored code to use IVideo interface instead of PlaylistVideo when possible; Fixed an issue when downloading single video files (when playlistId is null YoutubeExplode throws 'Invalid YouTube playlist ID or URL'). --- .../DownloadPage.xaml.cs | 52 ++++++++++++------- YoutubePlaylistDownloader/GlobalConsts.cs | 4 +- YoutubePlaylistDownloader/MainPage.xaml.cs | 12 ++--- .../Objects/DownloadSettings.cs | 2 +- .../Objects/FullPlaylist.cs | 4 +- .../Utilities/YoutubeHelpers.cs | 10 ++-- .../YoutubePlaylistDownloader.csproj | 2 +- 7 files changed, 50 insertions(+), 36 deletions(-) diff --git a/YoutubePlaylistDownloader/DownloadPage.xaml.cs b/YoutubePlaylistDownloader/DownloadPage.xaml.cs index 598b176..c6ea6b6 100644 --- a/YoutubePlaylistDownloader/DownloadPage.xaml.cs +++ b/YoutubePlaylistDownloader/DownloadPage.xaml.cs @@ -18,8 +18,8 @@ public partial class DownloadPage : UserControl, IDisposable, IDownload private readonly CancellationTokenSource cts; private readonly VideoQuality Quality; private string Bitrate; - private List> NotDownloaded; - private IEnumerable Videos; + private List> NotDownloaded; + private IEnumerable Videos; private readonly bool AudioOnly; private readonly bool PreferHighestFPS; private bool DownloadCaptions; @@ -28,7 +28,7 @@ public partial class DownloadPage : UserControl, IDisposable, IDownload const int megaBytes = 1 << 20; private readonly bool silent; private FixedQueue downloadSpeeds; - private readonly Dictionary indexes = []; + private readonly Dictionary indexes = []; private readonly List convertionTasks = []; public bool StillDownloading; @@ -106,7 +106,7 @@ public string CurrentStatus } - public DownloadPage(FullPlaylist playlist, DownloadSettings settings, string savePath = "", IEnumerable videos = null, + public DownloadPage(FullPlaylist playlist, DownloadSettings settings, string savePath = "", IEnumerable videos = null, bool silent = false, CancellationTokenSource cancellationToken = null) { InitializeComponent(); @@ -185,7 +185,7 @@ public DownloadPage(FullPlaylist playlist, DownloadSettings settings, string sav CurrentDownloadSpeed = $"{FindResource("DownloadSpeed")}: 0 MiB/s"; if (settings.Convert || settings.AudioOnly) - StartDownloadingWithConverting(playlist.BasePlaylist.Id, cts.Token).ConfigureAwait(false); + StartDownloadingWithConverting(playlist.BasePlaylist?.Id, cts.Token).ConfigureAwait(false); else StartDownloading(cts.Token).ConfigureAwait(false); @@ -198,11 +198,11 @@ public static async Task SequenceDownload(IEnumerable links, DownloadSet Playlist basePlaylist; FullPlaylist fullPlaylist; Channel channel; - IEnumerable videos = new List(); + IEnumerable videos = new List(); var notDownloaded = new List<(string, string)>(); foreach (var link in links) { - async Task Download(FullPlaylist playlistD, IEnumerable videosD) + async Task Download(FullPlaylist playlistD, IEnumerable videosD) { await Application.Current.Dispatcher.InvokeAsync(() => { @@ -213,9 +213,9 @@ await Application.Current.Dispatcher.InvokeAsync(() => { if (YoutubeHelpers.TryParsePlaylistId(link, out var playlistId)) { - basePlaylist = await client.Playlists.GetAsync(playlistId).ConfigureAwait(false); + basePlaylist = await client.Playlists.GetAsync(playlistId.Value).ConfigureAwait(false); fullPlaylist = new FullPlaylist(basePlaylist, await client.Playlists.GetVideosAsync(basePlaylist.Id).CollectAsync().ConfigureAwait(false)); - await Download(fullPlaylist, new List()); + await Download(fullPlaylist, new List()); } else if (YoutubeHelpers.TryParseChannelId(link, out var channelId)) { @@ -233,8 +233,14 @@ await Application.Current.Dispatcher.InvokeAsync(() => } else if (YoutubeHelpers.TryParseVideoId(link, out var videoId)) { - var video = await client.Videos.GetAsync(videoId); - await Download(null, new[] { new PlaylistVideo(playlistId, video.Id, video.Title, video.Author, video.Duration, video.Thumbnails) }); + IVideo video = await client.Videos.GetAsync(videoId); + + if (playlistId.HasValue) + { + video = new PlaylistVideo(playlistId.Value, video.Id, video.Title, video.Author, video.Duration, video.Thumbnails); + } + + await Download(null, new[] { video }); } else { @@ -261,7 +267,7 @@ await GlobalConsts.ShowSelectableDialog($"{Application.Current.FindResource("Cou } } - public async Task StartDownloadingWithConverting(PlaylistId playlistId, CancellationToken token) + public async Task StartDownloadingWithConverting(PlaylistId? playlistId, CancellationToken token) { try { @@ -279,7 +285,7 @@ public async Task StartDownloadingWithConverting(PlaylistId playlistId, Cancella { var video = Videos.ElementAtOrDefault(i); - if (video == default(PlaylistVideo)) + if (video == default(IVideo)) goto exit; indexes.Add(video, i + 1); @@ -414,7 +420,11 @@ await Dispatcher.InvokeAsync(() => FileType = new string(copyFileLoc.Skip(copyFileLoc.LastIndexOf('.') + 1).ToArray()); if (afterTagName != outputFileLoc) { - video = new PlaylistVideo(playlistId, video.Id, afterTagName, video.Author, video.Duration, video.Thumbnails); + if (playlistId.HasValue) + { + video = new PlaylistVideo(playlistId.Value, video.Id, afterTagName, video.Author, video.Duration, video.Thumbnails); + } + cleanFileName = GlobalConsts.CleanFileName(downloadSettings.GetFilenameByPattern(video, i, title, Playlist)); copyFileLoc = $"{SavePath}\\{cleanFileName}.{FileType}"; } @@ -475,7 +485,11 @@ await Dispatcher.InvokeAsync(() => var afterTagName = await GlobalConsts.TagFile(video, i + 1, copyFileLoc, Playlist); if (afterTagName != outputFileLoc) { - video = new PlaylistVideo(playlistId, video.Id, afterTagName, video.Author, video.Duration, video.Thumbnails); + if (playlistId.HasValue) + { + video = new PlaylistVideo(playlistId.Value, video.Id, afterTagName, video.Author, video.Duration, video.Thumbnails); + } + cleanFileName = GlobalConsts.CleanFileName(downloadSettings.GetFilenameByPattern(video, i, title, Playlist)); copyFileLoc = $"{SavePath}\\{cleanFileName}.{FileType}"; } @@ -498,7 +512,7 @@ await Dispatcher.InvokeAsync(() => catch (Exception ex) { await GlobalConsts.Log(ex.ToString(), "DownloadPage DownlaodWithConvert"); - NotDownloaded.Add(new Tuple(video, ex.Message)); + NotDownloaded.Add(new Tuple(video, ex.Message)); } } @@ -589,7 +603,7 @@ public async Task StartDownloading(CancellationToken token) { var video = Videos.ElementAtOrDefault(i); - if (video == default(PlaylistVideo)) + if (video == default(IVideo)) goto exit; try @@ -812,7 +826,7 @@ await Dispatcher.InvokeAsync(() => catch (Exception ex) { await GlobalConsts.Log(ex.ToString(), "DownloadPage DownlaodWithConvert"); - NotDownloaded.Add(new Tuple(video, ex.Message)); + NotDownloaded.Add(new Tuple(video, ex.Message)); } } @@ -884,7 +898,7 @@ private void Background_Exit(object sender, RoutedEventArgs e) GlobalConsts.LoadPage(GlobalConsts.MainPage.Load()); } - private void Update(int percent, PlaylistVideo video) + private void Update(int percent, IVideo video) { CurrentDownloadProgressBar.Value = percent; HeadlineTextBlock.Text = (string)FindResource("CurrentlyDownlading") + video.Title; diff --git a/YoutubePlaylistDownloader/GlobalConsts.cs b/YoutubePlaylistDownloader/GlobalConsts.cs index 6bfec51..95dabaf 100644 --- a/YoutubePlaylistDownloader/GlobalConsts.cs +++ b/YoutubePlaylistDownloader/GlobalConsts.cs @@ -383,7 +383,7 @@ static async Task TagMusicFile(Video fullVideo, string file, int vIndex) return $"{string.Join(", ", artists)} - {title}"; } - public static async Task TagFileBasedOnTitle(PlaylistVideo video, int vIndex, string file, FullPlaylist playlist = null) + public static async Task TagFileBasedOnTitle(IVideo video, int vIndex, string file, FullPlaylist playlist = null) { var genre = video.Title.Split('[', ']').ElementAtOrDefault(1); if (genre == null) @@ -450,7 +450,7 @@ public static async Task TagFileBasedOnTitle(PlaylistVideo video, int vI return file; } - public static async Task TagFile(PlaylistVideo video, int vIndex, string file, FullPlaylist playlist = null) + public static async Task TagFile(IVideo video, int vIndex, string file, FullPlaylist playlist = null) { ArgumentNullException.ThrowIfNull(video); diff --git a/YoutubePlaylistDownloader/MainPage.xaml.cs b/YoutubePlaylistDownloader/MainPage.xaml.cs index 4de1bc1..4044664 100644 --- a/YoutubePlaylistDownloader/MainPage.xaml.cs +++ b/YoutubePlaylistDownloader/MainPage.xaml.cs @@ -4,7 +4,7 @@ public partial class MainPage : UserControl { private readonly YoutubeClient client; private FullPlaylist list = null; - private IEnumerable VideoList; + private IEnumerable VideoList; private Channel channel = null; private readonly Dictionary Resolutions = new() { @@ -32,7 +32,7 @@ public MainPage() GlobalConsts.ShowSettingsButton(); GlobalConsts.ShowAboutButton(); GlobalConsts.ShowHelpButton(); - VideoList = new List(); + VideoList = new List(); client = GlobalConsts.YoutubeClient; GlobalConsts.MainPage = this; @@ -55,7 +55,7 @@ private async void TextBox_TextChanged(object sender, TextChangedEventArgs e) { _ = Task.Run(async () => { - var basePlaylist = await client.Playlists.GetAsync(playlistId).ConfigureAwait(false); + var basePlaylist = await client.Playlists.GetAsync(playlistId.Value).ConfigureAwait(false); list = new FullPlaylist(basePlaylist, await client.Playlists.GetVideosAsync(basePlaylist.Id).CollectAsync().ConfigureAwait(false)); VideoList = new List(); await UpdatePlaylistInfo(Visibility.Visible, list.BasePlaylist.Title, list.BasePlaylist.Author?.ChannelTitle ?? "", "", list.Videos.Count().ToString(), $"https://img.youtube.com/vi/{list?.Videos?.FirstOrDefault()?.Id}/maxresdefault.jpg", true, true); @@ -96,7 +96,7 @@ private async void TextBox_TextChanged(object sender, TextChangedEventArgs e) _ = Task.Run(async () => { var video = await client.Videos.GetAsync(videoId); - VideoList = new List { new(playlistId, video.Id, video.Title, video.Author, video.Duration, video.Thumbnails) }; + VideoList = new List