/// <summary> /// Downloads videos from a YouTube playlist and creates metadata for use in media players. /// </summary> /// <param name="playlistId">The ID of the YouTube playlist.</param> /// <param name="downloadFolder">The folder to download videos to.</param> public static void Main(string playlistId, string downloadFolder) { if (playlistId == null) { throw new ArgumentNullException(nameof(playlistId)); } if (downloadFolder == null) { throw new ArgumentNullException(nameof(downloadFolder)); } Directory.CreateDirectory(downloadFolder); var httpClient = new HttpClient(); var seenItems = new SeenItems(downloadFolder); var localMetadata = new LocalMetadata(httpClient); EnsureFfmpegDependency(new Ffmpeg(httpClient)); var playlist = GetPlaylist(playlistId); EnsureMetadata(playlist, downloadFolder, localMetadata); var allVideos = playlist.Videos; var newVideos = FilterAndSortVideos(allVideos, seenItems.GetIds()); ProcessVideos(newVideos, seenItems, downloadFolder, localMetadata); }
public LocalMetadataTest() { FakeHttpMessageHandler = new FakeHttpMessageHandler(); LocalMetadata = new LocalMetadata(new HttpClient(FakeHttpMessageHandler)); Folder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(Folder); }
public void DownloadPlaylist(string id, bool doNotReorder, string downloadFolder, int season = 1) { if (id == null) { throw new ArgumentNullException(nameof(id)); } if (downloadFolder == null) { throw new ArgumentNullException(nameof(downloadFolder)); } Directory.CreateDirectory(downloadFolder); var seenItems = new SeenItems(downloadFolder); var localMetadata = new LocalMetadata(HttpClient); var playlist = GetPlaylist(id); EnsureMetadata(playlist, downloadFolder, localMetadata); var allVideos = playlist.Videos; var sortedVideos = doNotReorder ? allVideos : allVideos.OrderBy(item => item.UploadDate).ToList(); var newVideos = sortedVideos.Where(video => !seenItems.GetIds().Contains(video.Id.Value)); ProcessVideos(newVideos, seenItems, downloadFolder, season, localMetadata); }
public void Save_Episode_Required() { var metadata = new Episode("제목"); LocalMetadata.Save(metadata, Folder, metadata.Title); GetMetadataFileContents(metadata.Title).ShouldBe( @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes""?> <episodedetails> <title>제목</title> <plot /> <aired /> </episodedetails>" ); }
public void Save_Movie_Required() { var metadata = new Movie("제목"); LocalMetadata.Save(metadata, Folder, metadata.Title); GetMetadataFileContents(metadata.Title).ShouldBe( @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes""?> <movie> <title>제목</title> <plot /> <releasedate /> </movie>" ); }
public void Save_TVShow_Required() { var metadata = new TVShow("제목"); LocalMetadata.Save(metadata, Folder); GetMetadataFileContents("tvshow").ShouldBe( @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes""?> <tvshow> <title>제목</title> <plot /> <premiered /> </tvshow>" ); }
public void Save_TVShow_PosterUrl() { var fakePosterUrl = "https://fake.tv.show/poster.png"; var fakePosterContents = "fake tv show poster"; FakeHttpMessageHandler.Mock(fakePosterUrl, fakePosterContents); string posterMetadataPath = Path.Combine(Folder, "folder.png"); var metadata = new TVShow(title: "title", posterPathOrUrl: fakePosterUrl); LocalMetadata.Save(metadata, Folder); File.Exists(posterMetadataPath).ShouldBeTrue(); File.ReadAllText(posterMetadataPath).ShouldBe(fakePosterContents); }
public void Save_TVShow_PosterPath() { var fakePosterPath = Path.GetTempFileName(); var fakePosterContents = "fake tv show poster"; File.WriteAllText(fakePosterPath, fakePosterContents); string posterMetadataPath = Path.Combine(Folder, $"folder{Path.GetExtension(fakePosterPath)}"); var metadata = new TVShow(title: "title", posterPathOrUrl: fakePosterPath); LocalMetadata.Save(metadata, Folder); File.Exists(posterMetadataPath).ShouldBeTrue(); File.ReadAllText(posterMetadataPath).ShouldBe(fakePosterContents); }
public void Save_TVShow_Optional() { var metadata = new TVShow(title: "제목", plot: "a\nb", premiered: new DateTime(2019, 02, 03), posterPathOrUrl: ""); LocalMetadata.Save(metadata, Folder); GetMetadataFileContents("tvshow").ShouldBe( @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes""?> <tvshow> <title>제목</title> <plot>a b</plot> <premiered>2019-02-03</premiered> </tvshow>" ); }
public void Save_Episode_Optional() { var metadata = new Episode(title: "제목", plot: "a\nb", aired: new DateTime(2019, 02, 03)); LocalMetadata.Save(metadata, Folder, metadata.Title); GetMetadataFileContents(metadata.Title).ShouldBe( @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes""?> <episodedetails> <title>제목</title> <plot>a b</plot> <aired>2019-02-03</aired> </episodedetails>" ); }
public void Save_Movie_PosterUrl() { var fakePosterUrl = "https://fake.com/poster.png"; var fakePosterContents = "fake poster"; FakeHttpMessageHandler.Mock(fakePosterUrl, fakePosterContents); var metadata = new Movie(title: "title", posterPathOrUrl: fakePosterUrl); string posterMetadataPath = Path.Combine(Folder, $"{metadata.Title}.png"); LocalMetadata.Save(metadata, Folder, metadata.Title); File.Exists(posterMetadataPath).ShouldBeTrue(); File.ReadAllText(posterMetadataPath).ShouldBe(fakePosterContents); }
public void Save_Movie_Optional() { var metadata = new Movie(title: "제목", plot: "a\nb", releaseDate: new DateTime(2019, 02, 03), posterPathOrUrl: ""); LocalMetadata.Save(metadata, Folder, metadata.Title); GetMetadataFileContents(metadata.Title).ShouldBe( @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes""?> <movie> <title>제목</title> <plot>a b</plot> <releasedate>2019-02-03</releasedate> </movie>" ); }
private static void EnsureMetadata(Playlist playlist, string downloadFolder, LocalMetadata localMetadata) { if (!File.Exists(Path.Combine(downloadFolder, "tvshow.nfo"))) { Console.WriteLine("Creating metadata"); Console.Write("\tInput poster path or URL: "); var posterPathOrUrl = Console.ReadLine(); localMetadata.Save( new TVShow( title: playlist.Title, plot: playlist.Description, premiered: playlist.Videos.OrderBy(video => video.UploadDate).First().UploadDate, posterPathOrUrl: posterPathOrUrl), downloadFolder); } }
public void DownloadVideo(string id, string downloadFolder) { if (id == null) { throw new ArgumentNullException(nameof(id)); } if (downloadFolder == null) { throw new ArgumentNullException(nameof(downloadFolder)); } Directory.CreateDirectory(downloadFolder); var localMetadata = new LocalMetadata(HttpClient); var video = GetVideo(id); var videoFileNameBase = video.Title.Aggregate("", (agg, cur) => Path.GetInvalidFileNameChars().Contains(cur) ? agg : agg + cur); SaveMetadata(video, downloadFolder, videoFileNameBase, localMetadata); SaveVideo(video, downloadFolder, videoFileNameBase); }
private void SaveMetadata(YTVideo video, string downloadFolder, string videoFileNameBase, LocalMetadata localMetadata) { Console.WriteLine("Creating metadata"); Console.Write("\tInput poster path or URL: "); var posterPathOrUrl = Console.ReadLine(); localMetadata.Save( new Movie( title: video.Title, plot: video.Description, releaseDate: video.UploadDate, posterPathOrUrl: posterPathOrUrl), downloadFolder, videoFileNameBase); }
private void ProcessVideos(IEnumerable <YTVideo> videos, SeenItems seenItems, string downloadFolder, int season, LocalMetadata localMetadata) { var client = new YoutubeClient(); var seasonFolder = Path.Combine(downloadFolder, $"Season {season}"); Directory.CreateDirectory(seasonFolder); var episodeNumber = GetLastEpisodeNumber(seasonFolder); videos.ToList().ForEach(video => { episodeNumber++; Console.WriteLine($" 0.00% Downloading {video.Id} {episodeNumber} {video.Title}"); Console.SetCursorPosition(0, Console.CursorTop - 1); var videoFileNameBase = $"S{season.ToString().PadLeft(2, '0')}E{episodeNumber.ToString("N0").PadLeft(2, '0')} " + video.Title.Aggregate("", (agg, cur) => Path.GetInvalidFileNameChars().Contains(cur) ? agg : agg + cur); var progress = new ConcurrentProgress <double>(d => { Console.SetCursorPosition(0, Console.CursorTop); Console.Write((d * 100).ToString("N2").PadLeft(6)); }); Downloaders.DownloadVideo(client, video, seasonFolder, videoFileNameBase, progress); Downloaders.DownloadAllCaptions(client, video, seasonFolder, videoFileNameBase, progress); CreateMetadata(video, seasonFolder, videoFileNameBase, localMetadata); seenItems.SaveId(video.Id); Console.WriteLine(); }); }
private static void ProcessVideos(IEnumerable <Video> videos, SeenItems seenItems, string downloadFolder, LocalMetadata localMetadata) { var client = new YoutubeClient(); var seasonFolder = Path.Combine(downloadFolder, "Season 1"); Directory.CreateDirectory(seasonFolder); var episodeNumber = GetLastEpisodeNumber(seasonFolder); videos.ToList().ForEach(video => { Console.WriteLine($" 0.00% Downloading {video.Title}"); Console.SetCursorPosition(0, Console.CursorTop - 1); episodeNumber++; var videoFileNameBase = $"S01E{episodeNumber.ToString("N0").PadLeft(2, '0')} " + video.Title.Replace('|', '-'); var progress = new ConcurrentProgress <double>(d => { Console.SetCursorPosition(0, Console.CursorTop); Console.Write((d * 100).ToString("N2").PadLeft(6)); }); DownloadVideo(client, video, seasonFolder, videoFileNameBase, progress); DownloadAllCaptions(client, video, seasonFolder, videoFileNameBase, progress); CreateMetadata(video, seasonFolder, videoFileNameBase, localMetadata); seenItems.SaveId(video.Id); Console.WriteLine(); }); }
private static void CreateMetadata(Video video, string downloadFolder, string videoFileNameBase, LocalMetadata localMetadata) { Console.WriteLine(); Console.Write(" \tmetadata"); localMetadata.Save(new Episode(title: video.Title, plot: video.Description, aired: video.UploadDate), downloadFolder, videoFileNameBase); }