diff --git a/Tesses.YouTubeDownloader.Server/Class1.cs b/Tesses.YouTubeDownloader.Server/Class1.cs index adbf4f5..661c5b1 100644 --- a/Tesses.YouTubeDownloader.Server/Class1.cs +++ b/Tesses.YouTubeDownloader.Server/Class1.cs @@ -10,9 +10,78 @@ using System.IO; using System.Text; using YoutubeExplode.Playlists; using YoutubeExplode.Channels; +using Tesses.Extensions; +using YoutubeExplode.Videos.Streams; + +namespace Tesses.Extensions +{ + public static class Extensions + { + public static string Substring(this string value,string str) + { + return value.Substring(str.Length); + } + } +} namespace Tesses.YouTubeDownloader.Server { + +internal static class B64 +{ + public static string Base64UrlEncodes(string arg) + { + return Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(arg)); + } + + public static string Base64Encode(byte[] arg) + { + return Convert.ToBase64String(arg); + } + public static byte[] Base64Decode(string arg) + { + return Convert.FromBase64String(arg); + } + + public static string Base64Encodes(string arg) + { + return Base64Encode(System.Text.Encoding.UTF8.GetBytes(arg)); + } + + public static string Base64UrlEncode(byte[] arg) + { + string s = Convert.ToBase64String(arg); // Regular base64 encoder + s = s.Split('=')[0]; // Remove any trailing '='s + s = s.Replace('+', '-'); // 62nd char of encoding + s = s.Replace('/', '_'); // 63rd char of encoding + return s; + } + public static string Base64Decodes(string arg) + { + return System.Text.Encoding.UTF8.GetString(Base64Decode(arg)); + } + public static string Base64UrlDecodes(string arg) + { + return System.Text.Encoding.UTF8.GetString(Base64UrlDecode(arg)); + } + public static byte[] Base64UrlDecode(string arg) + { + string s = arg; + s = s.Replace('-', '+'); // 62nd char of encoding + s = s.Replace('_', '/'); // 63rd char of encoding + switch (s.Length % 4) // Pad with trailing '='s + { + case 0: break; // No pad chars in this case + case 2: s += "=="; break; // Two pad chars + case 3: s += "="; break; // One pad char + default: throw new System.Exception( + "Illegal base64url string!"); + } + return Convert.FromBase64String(s); // Standard base64 decoder + } + +} + internal class ApiV1Server : Tesses.WebServer.Server { IDownloader downloader1; @@ -328,7 +397,71 @@ namespace Tesses.YouTubeDownloader.Server var data2=JsonConvert.DeserializeObject(data); await ctx.SendJsonAsync(data2.ToLegacy()); } - else*/ if(path.StartsWith("/File/")) + else*/ + if(path.StartsWith("/File/FileInfo/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/File/FileInfo/"))); + string url = B64.Base64UrlDecodes(file); + if(baseCtl.DownloadExists(url)) + { + var obj=await baseCtl.GetDownloadInfoAsync(url); + await ctx.SendJsonAsync(obj); + }else{ + ctx.StatusCode = 404; + ctx.NetworkStream.Close(); + } + } + else if(path.StartsWith("/File/Info/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/File/Info/"))); + VideoId? id =VideoId.TryParse(file); + if(id.HasValue && baseCtl.VideoInfoExists(id.Value)) + { + var obj=await baseCtl.GetVideoInfoAsync(id.Value); + await ctx.SendJsonAsync(obj); + }else{ + ctx.StatusCode = 404; + ctx.NetworkStream.Close(); + } + }else if(path.StartsWith("/File/Playlist/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/File/Playlist/"))); + PlaylistId? id =PlaylistId.TryParse(file); + if(id.HasValue && baseCtl.PlaylistInfoExists(id.Value)) + { + var obj=await baseCtl.GetPlaylistInfoAsync(id.Value); + await ctx.SendJsonAsync(obj); + }else{ + ctx.StatusCode = 404; + ctx.NetworkStream.Close(); + } + }else if(path.StartsWith("/File/Channel/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/File/Channel/"))); + ChannelId? id =ChannelId.TryParse(file); + if(id.HasValue && baseCtl.ChannelInfoExists(id.Value)) + { + + var obj=await baseCtl.GetChannelInfoAsync(id.Value); + await ctx.SendJsonAsync(obj); + }else{ + ctx.StatusCode = 404; + ctx.NetworkStream.Close(); + } + }else if(path.StartsWith("/File/StreamInfo/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/File/Info/"))); + VideoId? id =VideoId.TryParse(file); + if(id.HasValue && baseCtl.BestStreamInfoExists(id.Value)) + { + var obj=await baseCtl.GetBestStreamInfoAsync(id.Value); + await ctx.SendJsonAsync(obj); + }else{ + ctx.StatusCode = 404; + ctx.NetworkStream.Close(); + } + } + else if(path.StartsWith("/File/")) { string file=WebUtility.UrlDecode(path.Substring(6)); if(!await baseCtl.FileExistsAsync(file)) @@ -353,15 +486,107 @@ namespace Tesses.YouTubeDownloader.Server await ctx.SendStreamAsync(s); } }*/ + else if(path.StartsWith("/GetFiles/FileInfo") || path.StartsWith("/GetFiles/FileInfo/")) + { + List urls=new List(); + await foreach(var url in baseCtl.GetDownloadUrlsAsync()) + { + urls.Add($"{B64.Base64UrlEncodes(url)}.json"); + } + await ctx.SendJsonAsync(urls); + } + else if(path.StartsWith("/GetFiles/Info") || path.StartsWith("/GetFiles/Info/") || path.StartsWith("/GetFiles/StreamInfo") || path.StartsWith("/GetFiles/StreamInfo/")) + { + bool containsStrmInfo=path.Contains("StreamInfo"); + List items=new List(); + await foreach(var vid in baseCtl.GetVideoIdsAsync()) + { + var vid2=VideoId.TryParse(vid); + + if(!containsStrmInfo || (vid2.HasValue && baseCtl.BestStreamInfoExists(vid2.Value))){ + items.Add($"{vid}.json"); + } + } + await ctx.SendJsonAsync(items); + }else if(path.StartsWith("/GetFiles/Playlist") || path.StartsWith("/GetFiles/Playlist/")) + { + + List items=new List(); + await foreach(var vid in baseCtl.GetPlaylistIdsAsync()) + { + items.Add($"{vid}.json"); + } + await ctx.SendJsonAsync(items); + }else if(path.StartsWith("/GetFiles/Channel") || path.StartsWith("/GetFiles/Channel/")) + { + List items=new List(); + await foreach(var vid in baseCtl.GetChannelIdsAsync()) + { + items.Add($"{vid}.json"); + } + await ctx.SendJsonAsync(items); + } else if(path.StartsWith("/GetFiles/")) { await ctx.SendJsonAsync(baseCtl.EnumerateFiles( WebUtility.UrlDecode(path.Substring(10))).ToList()); - }else if(path.StartsWith("/GetDirectories/")) + } + else if(path.StartsWith("/GetDirectories/")) { await ctx.SendJsonAsync(baseCtl.EnumerateDirectories( WebUtility.UrlDecode(path.Substring(16))).ToList()); - }else if(path.StartsWith("/FileExists-v2/")) + } + else if(path.StartsWith("/FileExists/StreamInfo/")) { - await ctx.SendTextAsync(baseCtl.FileExists(WebUtility.UrlDecode(path.Substring(15))) ? "true" : "false","text/plain"); + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/FileExists/StreamInfo/"))); + VideoId? id =VideoId.TryParse(file); + if(id.HasValue && baseCtl.BestStreamInfoExists(id.Value)) + { + await ctx.SendTextAsync( "true","text/plain"); + }else{ + await ctx.SendTextAsync( "false","text/plain"); + } + } + else if(path.StartsWith("/FileExists/FileInfo/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/FileExists/StreamInfo/"))); + string url = B64.Base64Decodes(file); + + if(baseCtl.DownloadExists(url)) + { + await ctx.SendTextAsync( "true","text/plain"); + }else{ + await ctx.SendTextAsync( "false","text/plain"); + } + } + else if(path.StartsWith("/FileExists/Info/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/FileExists/Info/"))); + VideoId? id =VideoId.TryParse(file); + if(id.HasValue && baseCtl.VideoInfoExists(id.Value)) + { + await ctx.SendTextAsync( "true","text/plain"); + }else{ + await ctx.SendTextAsync( "false","text/plain"); + } + } else if(path.StartsWith("/FileExists/Playlist/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/FileExists/Playlist/"))); + PlaylistId? id =PlaylistId.TryParse(file); + if(id.HasValue && baseCtl.PlaylistInfoExists(id.Value)) + { + await ctx.SendTextAsync( "true","text/plain"); + }else{ + await ctx.SendTextAsync( "false","text/plain"); + } + }else if(path.StartsWith("/FileExists/Channel/")) + { + string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/FileExists/Channel/"))); + ChannelId? id =ChannelId.TryParse(file); + if(id.HasValue && baseCtl.ChannelInfoExists(id.Value)) + { + await ctx.SendTextAsync( "true","text/plain"); + }else{ + await ctx.SendTextAsync( "false","text/plain"); + } } else if(path.StartsWith("/FileExists/")) { @@ -369,13 +594,27 @@ namespace Tesses.YouTubeDownloader.Server }else if(path.StartsWith("/DirectoryExists/")) { await ctx.SendTextAsync(baseCtl.DirectoryExists(WebUtility.UrlDecode(path.Substring(17))) ? "true" : "false","text/plain"); - }else if(path.StartsWith("/Video/")) + }else if(path.StartsWith("/Download/")) + { + string url = path.Substring("/Download/"); + if(baseCtl.DownloadExists(url) && baseCtl.DownloadFileExists(url)) + { + var v = await baseCtl.GetDownloadInfoAsync(url); + string header=GetVideoContentDisposition(v.Title).ToString(); + ctx.ResponseHeaders.Add("Content-Disposition",header); + using(var strm = await baseCtl.OpenReadAsync(baseCtl.GetDownloadFile(url))) + { + await ctx.SendStreamAsync(strm,HeyRed.Mime.MimeTypesMap.GetMimeType(v.Title)); + } + } + } + else if(path.StartsWith("/Video/")) { string id=path.Substring(7); VideoId? id1=VideoId.TryParse(id); if(id1.HasValue){ - if(baseCtl.FileExists($"Info/{id1.Value.Value}.json")) + if(baseCtl.VideoInfoExists(id1.Value)) { //Console.WriteLine("Id exists"); SavedVideo v = await baseCtl.GetVideoInfoAsync(id1.Value); @@ -397,7 +636,8 @@ namespace Tesses.YouTubeDownloader.Server } } - }else if(path.StartsWith("/VideoRes/")) + } + else if(path.StartsWith("/VideoRes/")) { string id_res=path.Substring(10); string[] id_res_split = id_res.Split(new char[] {'/'},2,StringSplitOptions.RemoveEmptyEntries); @@ -415,7 +655,7 @@ namespace Tesses.YouTubeDownloader.Server VideoId? id1=VideoId.TryParse(id_res_split[1]); if(id1.HasValue){ - if(baseCtl.FileExists($"Info/{id1.Value.Value}.json")) + if(baseCtl.VideoInfoExists(id1.Value)) { //Console.WriteLine("Id exists"); SavedVideo v = await baseCtl.GetVideoInfoAsync(id1.Value); @@ -439,6 +679,162 @@ namespace Tesses.YouTubeDownloader.Server } } } + else if(path.StartsWith("/Watch/")) + { + + string id=path.Substring(7); + VideoId? id1=VideoId.TryParse(id); + if(id1.HasValue){ + int i=0; + alt: + if(i>= 10) + { + ctx.StatusCode=500; + + return; + } + if(baseCtl.VideoInfoExists(id1.Value)) + { + + //Console.WriteLine("Id exists"); + SavedVideo v = await baseCtl.GetVideoInfoAsync(id1.Value); + var res= await BestStreamInfo.GetBestStreams(baseCtl,id1.Value); + string path0= await BestStreams.GetPathResolution(baseCtl,v,Resolution.PreMuxed); + + if(!string.IsNullOrWhiteSpace(path0) && baseCtl.VideoInfoExists(id1.Value)) + { + + //Console.WriteLine("F is not null"); + string filename = $"{v.Title}-{Path.GetFileName(path0)}"; + string header=GetVideoContentDisposition(filename).ToString(); + ctx.ResponseHeaders.Add("Content-Disposition",header); + using(var strm = await baseCtl.OpenReadAsync(path0)) + { + await ctx.SendStreamAsync(strm,HeyRed.Mime.MimeTypesMap.GetMimeType(filename)); + } + }else{ + //stream to browser + string url=res.MuxedStreamInfo.Url; + var b = baseCtl as TYTDStorage; + if(b != null) + { + + string filename = $"{v.Title}-{Path.GetFileName(path0)}"; + string header=GetVideoContentDisposition(filename).ToString(); + ctx.ResponseHeaders.Add("Content-Disposition",header); + using( var strm=await b.YoutubeClient.Videos.Streams.GetAsync(res.MuxedStreamInfo)) + { + await ctx.SendStreamAsync(strm,HeyRed.Mime.MimeTypesMap.GetMimeType(filename)); + } + }else{ + ctx.StatusCode=500; + return; + } + } + }else{ + var b = baseCtl as TYTDStorage; + if(b != null) + { + var videoInfo=await b.YoutubeClient.Videos.GetAsync(id1.Value); + await b.WriteVideoInfoAsync(new SavedVideo(videoInfo)); + }else{ + ctx.StatusCode=500; + return; + } + i++; + goto alt; + } + + } + + } + else if(path.StartsWith("/WatchRes/")) + { + string id_res=path.Substring(10); + string[] id_res_split = id_res.Split(new char[] {'/'},2,StringSplitOptions.RemoveEmptyEntries); + if(id_res_split.Length ==2) + { + int num; + if(int.TryParse(id_res_split[0],out num)) + { + if(num < 0) num=1; + if(num > 3) num=1; + VideoId? id1=VideoId.TryParse(id_res_split[1]); + + if(id1.HasValue){ + int i=0; + alt: + if(i>= 10) + { + ctx.StatusCode=500; + + return; + } + if(baseCtl.VideoInfoExists(id1.Value)) + { + + //Console.WriteLine("Id exists"); + SavedVideo v = await baseCtl.GetVideoInfoAsync(id1.Value); + var res= await BestStreamInfo.GetBestStreams(baseCtl,id1.Value); + string path0= await BestStreams.GetPathResolution(baseCtl,v,(Resolution)num); + + if(!string.IsNullOrWhiteSpace(path0) && baseCtl.VideoInfoExists(id1.Value)) + { + + //Console.WriteLine("F is not null"); + string filename = $"{v.Title}-{Path.GetFileName(path0)}"; + string header=GetVideoContentDisposition(filename).ToString(); + ctx.ResponseHeaders.Add("Content-Disposition",header); + using(var strm = await baseCtl.OpenReadAsync(path0)) + { + await ctx.SendStreamAsync(strm,HeyRed.Mime.MimeTypesMap.GetMimeType(filename)); + } + }else{ + //stream to browser + + var b = baseCtl as TYTDStorage; + if(b != null) + { + + string filename = $"{v.Title}-{Path.GetFileName(path0)}"; + string header=GetVideoContentDisposition(filename).ToString(); + ctx.ResponseHeaders.Add("Content-Disposition",header); + IStreamInfo info=res.MuxedStreamInfo; + if(num == 2) + { + info = res.AudioOnlyStreamInfo; + }else if(num == 3){ + info = res.VideoOnlyStreamInfo; + } + + using( var strm=await b.YoutubeClient.Videos.Streams.GetAsync(info)) + { + await ctx.SendStreamAsync(strm,HeyRed.Mime.MimeTypesMap.GetMimeType(filename)); + } + }else{ + ctx.StatusCode=500; + return; + } + } + }else{ + var b = baseCtl as TYTDStorage; + if(b != null) + { + var videoInfo=await b.YoutubeClient.Videos.GetAsync(id1.Value); + await b.WriteVideoInfoAsync(new SavedVideo(videoInfo)); + }else{ + ctx.StatusCode=500; + return; + } + i++; + goto alt; + } + + } + + } + } + } else{ await NotFoundServer.ServerNull.GetAsync(ctx); } @@ -450,7 +846,7 @@ namespace Tesses.YouTubeDownloader.Server public ApiV2Server(IDownloader downloader) { this.Downloader=downloader; - + AddBoth("/Search",Search); AddBoth("/AddItem",AddItem); AddBoth("/AddChannel",AddChannel); AddBoth("/AddUser",AddUser); @@ -472,6 +868,14 @@ namespace Tesses.YouTubeDownloader.Server Add("/ReplaceList",ReplaceList,"POST"); AddBoth("/DeleteList",DeleteList); AddBoth("/SetResolutionInList",SetResolutionInList); + + Add("/export/everything.json",Everything_Export,"GET"); + Add("/export/videos.json",VideosExport,"GET"); + Add("/export/playlists.json",PlaylistsExport,"GET"); + Add("/export/channels.json",ChannelsExport,"GET"); + Add("/export/filedownloads.json",FilesExport,"GET"); + Add("/export/subscriptions.json",SubscriptionsExport,"GET"); + Add("/export/personal_lists.json",PersonalListsExport,"GET"); /* public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<(VideoId Id, Resolution Resolution)> items) @@ -495,6 +899,31 @@ namespace Tesses.YouTubeDownloader.Server }*/ } + private async Task Search(ServerContext ctx) + { + var dl = Downloader as IStorage; + string q; + if(ctx.QueryParams.TryGetFirst("q",out q)) + { + bool getInfoBool=false; + string getInfo; + if(ctx.QueryParams.TryGetFirst("getinfo",out getInfo)) + { + if(!bool.TryParse(getInfo,out getInfoBool)) getInfoBool=false; + } + List results=new List(); + await foreach(var vid in dl.SearchYouTubeAsync(q,getInfoBool)) + { + results.Add(vid); + } + if(getInfoBool) + { + dl.WaitTillMediaContentQueueEmpty(); + } + await ctx.SendJsonAsync(results); + } + } + private void AddBoth(string url,HttpActionAsync action) { Add(url,action); @@ -541,7 +970,118 @@ namespace Tesses.YouTubeDownloader.Server $"You Will Be Redirected in 5 Sec

You Will Be Redirected in 5 Sec

\n" ); } - + public async Task Everything_Export(ServerContext ctx) + { + var storage = Downloader as TYTDStorage; + if(storage != null) + { + if(storage.GetLoggerProperties().AllowExport) + { + TYTDExporter exporter=new TYTDExporter(storage); + var res=await exporter.ExportEverythingAsync(); + await ctx.SendJsonAsync(res); + }else{ + ctx.StatusCode=403; + await ctx.SendTextAsync("Can't Export, Access Denied

Can't Export, Access Denied

Call the TYTD adminstrator if you are not the administrator to edit the following


In file: config/tytdprop.json, unless overriden in code
Change "AllowExport":false with "AllowExport":true

"); + } + } + } + public async Task VideosExport(ServerContext ctx) + { + var storage = Downloader as TYTDStorage; + if(storage != null) + { + if(storage.GetLoggerProperties().AllowExport) + { + TYTDExporter exporter=new TYTDExporter(storage); + var res=await exporter.ExportVideosAsync(); + await ctx.SendJsonAsync(res); + }else{ + ctx.StatusCode=403; + await ctx.SendTextAsync("Can't Export, Access Denied

Can't Export, Access Denied

Call the TYTD adminstrator if you are not the administrator to edit the following


In file: config/tytdprop.json, unless overriden in code
Change "AllowExport":false with "AllowExport":true

"); + } + } + } + public async Task PlaylistsExport(ServerContext ctx) + { + var storage = Downloader as TYTDStorage; + if(storage != null) + { + if(storage.GetLoggerProperties().AllowExport) + { + TYTDExporter exporter=new TYTDExporter(storage); + var res=await exporter.ExportPlaylistsAsync(); + await ctx.SendJsonAsync(res); + }else{ + ctx.StatusCode=403; + await ctx.SendTextAsync("Can't Export, Access Denied

Can't Export, Access Denied

Call the TYTD adminstrator if you are not the administrator to edit the following


In file: config/tytdprop.json, unless overriden in code
Change "AllowExport":false with "AllowExport":true

"); + } + } + } + public async Task ChannelsExport(ServerContext ctx) + { + var storage = Downloader as TYTDStorage; + if(storage != null) + { + if(storage.GetLoggerProperties().AllowExport) + { + TYTDExporter exporter=new TYTDExporter(storage); + var res=await exporter.ExportChannelsAsync(); + await ctx.SendJsonAsync(res); + }else{ + ctx.StatusCode=403; + await ctx.SendTextAsync("Can't Export, Access Denied

Can't Export, Access Denied

Call the TYTD adminstrator if you are not the administrator to edit the following


In file: config/tytdprop.json, unless overriden in code
Change "AllowExport":false with "AllowExport":true

"); + } + } + } + public async Task FilesExport(ServerContext ctx) + { + var storage = Downloader as TYTDStorage; + if(storage != null) + { + if(storage.GetLoggerProperties().AllowExport) + { + TYTDExporter exporter=new TYTDExporter(storage); + var res=await exporter.ExportDownloadsAsync(); + await ctx.SendJsonAsync(res); + }else{ + ctx.StatusCode=403; + await ctx.SendTextAsync("Can't Export, Access Denied

Can't Export, Access Denied

Call the TYTD adminstrator if you are not the administrator to edit the following


In file: config/tytdprop.json, unless overriden in code
Change "AllowExport":false with "AllowExport":true

"); + } + } + } + public async Task SubscriptionsExport(ServerContext ctx) + { + var storage = Downloader as TYTDStorage; + if(storage != null) + { + if(storage.GetLoggerProperties().AllowExport) + { + TYTDExporter exporter=new TYTDExporter(storage); + var res=await exporter.ExportSubscriptionsAsync(); + await ctx.SendJsonAsync(res); + }else{ + ctx.StatusCode=403; + await ctx.SendTextAsync("Can't Export, Access Denied

Can't Export, Access Denied

Call the TYTD adminstrator if you are not the administrator to edit the following


In file: config/tytdprop.json, unless overriden in code
Change "AllowExport":false with "AllowExport":true

"); + } + } + } + public async Task PersonalListsExport(ServerContext ctx) + { + var storage = Downloader as TYTDStorage; + if(storage != null) + { + if(storage.GetLoggerProperties().AllowExport) + { + TYTDExporter exporter=new TYTDExporter(storage); + var res=await exporter.ExportPersonalPlaylistsAsync(); + await ctx.SendJsonAsync(res); + }else{ + ctx.StatusCode=403; + await ctx.SendTextAsync("Can't Export, Access Denied

Can't Export, Access Denied

Call the TYTD adminstrator if you are not the administrator to edit the following


In file: config/tytdprop.json, unless overriden in code
Change "AllowExport":false with "AllowExport":true

"); + } + } + } public async Task AddToList(ServerContext ctx) { diff --git a/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj b/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj index 8ee06f7..b555a1e 100644 --- a/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj +++ b/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj @@ -11,7 +11,7 @@ netstandard2.0 true - + 8.0 Tesses.YouTubeDownloader.Server Mike Nolan Tesses diff --git a/Tesses.YouTubeDownloader/DownloadLoop.cs b/Tesses.YouTubeDownloader/DownloadLoop.cs index c788861..c8661cf 100644 --- a/Tesses.YouTubeDownloader/DownloadLoop.cs +++ b/Tesses.YouTubeDownloader/DownloadLoop.cs @@ -17,7 +17,7 @@ namespace Tesses.YouTubeDownloader { public abstract partial class TYTDStorage { - + private async Task DownloadLoop(CancellationToken token = default(CancellationToken)) { while (!token.IsCancellationRequested) @@ -549,7 +549,7 @@ namespace Tesses.YouTubeDownloader bool ret=false; var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false); - if(!can_download) return false; + if(streams != null) { if(streams.VideoFrozen) @@ -562,6 +562,7 @@ namespace Tesses.YouTubeDownloader if(await Continue(complete)) { + if(!can_download) return false; streams = await BestStreamInfo.GetBestStreams(this,video.Id,token); if(streams != null) { @@ -649,19 +650,20 @@ namespace Tesses.YouTubeDownloader bool ret=false; var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false); - if(!can_download) return false; + if(streams != null) { if(streams.VideoFrozen) { throw new Exception($"[TYTD Specific Error] Video is frozen, we wont do anything with the video.\nplease set \"VideoFrozen\": false in the file \"Info/{video.Id}.json\" to fix this problem"); } + string complete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}"; string incomplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}"; await MoveLegacyStreams(video,streams); if(await Continue(complete)) { - + if(!can_download) return false; streams = await BestStreamInfo.GetBestStreams(this,video.Id,token); if(streams != null) { diff --git a/Tesses.YouTubeDownloader/Export.cs b/Tesses.YouTubeDownloader/Export.cs new file mode 100644 index 0000000..bb6a27c --- /dev/null +++ b/Tesses.YouTubeDownloader/Export.cs @@ -0,0 +1,128 @@ +using Newtonsoft.Json; +using YoutubeExplode.Videos.Streams; +using System.Linq; +using System; +using System.Threading.Tasks; +using YoutubeExplode.Videos; +using System.Threading; +using YoutubeExplode.Exceptions; +using System.Collections.Generic; + +namespace Tesses.YouTubeDownloader +{ + public class TYTDExporter + { + ITYTDBase _base; + string _tytd_tag; + public TYTDExporter(ITYTDBase baseCtl) + { + _tytd_tag=TYTDStorage.TYTDTag; + _base=baseCtl; + } + + public async Task ExportEverythingAsync() + { + EverythingExport everythingExport=new EverythingExport(); + everythingExport.Videos=await ExportVideosAsync(); + everythingExport.Playlists = await ExportPlaylistsAsync(); + everythingExport.Channels = await ExportChannelsAsync(); + everythingExport.DownloadedFiles=await ExportDownloadsAsync(); + everythingExport.Subscriptions= await ExportSubscriptionsAsync(); + everythingExport.PersonalPlaylists=await ExportPersonalPlaylistsAsync(); + everythingExport.TYTDTag = _tytd_tag; + return everythingExport; + } + public async Task> ExportVideosAsync() + { + List videos=new List(); + await foreach(var item in _base.GetVideosAsync()) + { + videos.Add(item); + } + return videos; + } + public async Task> ExportPlaylistsAsync() + { + List videos=new List(); + await foreach(var item in _base.GetPlaylistsAsync()) + { + videos.Add(item); + } + return videos; + } + public async Task> ExportChannelsAsync() + { + List videos=new List(); + await foreach(var item in _base.GetChannelsAsync()) + { + videos.Add(item); + } + return videos; + } + public async Task> ExportDownloadsAsync() + { + List videos=new List(); + await foreach(var item in _base.GetDownloadsAsync()) + { + videos.Add(item); + } + return videos; + } + public async Task> ExportSubscriptionsAsync() + { + List subs=new List(); + var dler = _base as IDownloader; + if(dler != null) + { + await foreach(var item in dler.GetSubscriptionsAsync()) + { + subs.Add(item); + } + } + return subs; + } + public async Task> ExportPersonalPlaylistsAsync() + { + List playlists=new List(); + await foreach(var item in _base.GetPersonalPlaylistsAsync()) + { + PersonalPlaylist personalPlaylist=new PersonalPlaylist(); + personalPlaylist.Name=item; + personalPlaylist.Items=new List(); + await foreach(var item2 in _base.GetPersonalPlaylistContentsAsync(item)) + { + personalPlaylist.Items.Add(item2); + } + playlists.Add(personalPlaylist); + } + + return playlists; + } + + + } + + public class EverythingExport + { + public string TYTDTag {get;set;} + public List Videos {get;set;} + + public List Playlists {get;set;} + + public List Channels {get;set;} + + public List DownloadedFiles {get;set;} + + public List Subscriptions {get;set;} + + public List PersonalPlaylists {get;set;} + + } + + public class PersonalPlaylist + { + public string Name {get;set;} + + public List Items {get;set;} + } +} \ No newline at end of file diff --git a/Tesses.YouTubeDownloader/IStorage.cs b/Tesses.YouTubeDownloader/IStorage.cs index e56ac37..2c4e68e 100644 --- a/Tesses.YouTubeDownloader/IStorage.cs +++ b/Tesses.YouTubeDownloader/IStorage.cs @@ -15,6 +15,8 @@ namespace Tesses.YouTubeDownloader { public interface IStorage : IWritable, IDownloader, ITYTDBase { + void WaitTillMediaContentQueueEmpty(); + Task WriteBestStreamInfoAsync(VideoId id,BestStreamInfo.BestStreamsSerialized serialized); Task MuxVideosAsync(SavedVideo video,string videoSrc,string audioSrc,string videoDest,IProgress progress=null,CancellationToken token=default(CancellationToken)); Task Continue(string path); diff --git a/Tesses.YouTubeDownloader/ITYTDBase.cs b/Tesses.YouTubeDownloader/ITYTDBase.cs index 3c9ae3a..073d495 100644 --- a/Tesses.YouTubeDownloader/ITYTDBase.cs +++ b/Tesses.YouTubeDownloader/ITYTDBase.cs @@ -15,6 +15,11 @@ namespace Tesses.YouTubeDownloader { public interface ITYTDBase : IPersonalPlaylistGet { + + IAsyncEnumerable GetDownloadUrlsAsync(); + IAsyncEnumerable GetDownloadsAsync(); + Task GetDownloadInfoAsync(string url); + bool DownloadExists(string path); Task GetBestStreamInfoAsync(VideoId id); bool BestStreamInfoExists(VideoId id); IAsyncEnumerable GetPersonalPlaylistsAsync(); diff --git a/Tesses.YouTubeDownloader/Logging.cs b/Tesses.YouTubeDownloader/Logging.cs index 659903b..3c458cf 100644 --- a/Tesses.YouTubeDownloader/Logging.cs +++ b/Tesses.YouTubeDownloader/Logging.cs @@ -35,17 +35,27 @@ namespace Tesses.YouTubeDownloader } public partial class TYTDStorage { + + protected virtual LoggerProperties ReadLoggerProperties() + { + string data=ReadAllTextAsync("config/tytdprop.json").GetAwaiter().GetResult(); + return JsonConvert.DeserializeObject(data); + } + protected virtual bool LoggerPropertiesExists + { + get{ + return FileExists("config/tytdprop.json"); + } + } + public event EventHandler Error; internal LoggerProperties Properties {get;set;} public LoggerProperties GetProperties() { - CreateDirectoryIfNotExist("config"); - CreateDirectoryIfNotExist("config/logs"); - - if(FileExists("config/tytdprop.json")) + + if(LoggerPropertiesExists) { - string data=ReadAllTextAsync("config/tytdprop.json").GetAwaiter().GetResult(); - return JsonConvert.DeserializeObject(data); + return ReadLoggerProperties(); }else{ LoggerProperties prop=new LoggerProperties(); prop.AddDateInLog=true; @@ -55,6 +65,8 @@ namespace Tesses.YouTubeDownloader prop.UseLogs=true; prop.SubscriptionInterval=TimeSpan.FromHours(1); prop.AlwaysDownloadChannel = false; + prop.AllowExport=false; + return prop; } } @@ -64,6 +76,7 @@ namespace Tesses.YouTubeDownloader { Properties=GetProperties(); } + return Properties; } internal static LockObj o=new LockObj(); @@ -83,6 +96,7 @@ namespace Tesses.YouTubeDownloader } public class LoggerProperties { + public bool AllowExport {get;set;} public bool AlwaysDownloadChannel {get;set;} public TimeSpan SubscriptionInterval {get;set;} @@ -96,6 +110,8 @@ namespace Tesses.YouTubeDownloader public bool AddDateInLog {get;set;} + + } public class Logger { diff --git a/Tesses.YouTubeDownloader/SavedVideo.cs b/Tesses.YouTubeDownloader/SavedVideo.cs index 4aebc6d..eb3c582 100644 --- a/Tesses.YouTubeDownloader/SavedVideo.cs +++ b/Tesses.YouTubeDownloader/SavedVideo.cs @@ -14,6 +14,7 @@ namespace Tesses.YouTubeDownloader { public class VideoDownloadProgress { + public SavedVideo Saved { get; set; } public Resolution Resolution {get;set;} @@ -24,7 +25,7 @@ namespace Tesses.YouTubeDownloader } public class SavedVideoLegacy { - + public string Id {get;set;} public string Title {get;set;} @@ -69,6 +70,7 @@ namespace Tesses.YouTubeDownloader { public SavedVideo() { + TYTDTag=""; Id = ""; Title = ""; AuthorChannelId = ""; @@ -88,6 +90,7 @@ namespace Tesses.YouTubeDownloader public SavedVideo(Video video) { + TYTDTag=TYTDStorage.TYTDTag; Id=video.Id; Title = video.Title; AuthorChannelId = video.Author.ChannelId; @@ -104,6 +107,8 @@ namespace Tesses.YouTubeDownloader DownloadFrom="YouTube"; VideoFrozen=false; } + + public string TYTDTag {get;set;} public bool LegacyVideo {get;set;} public bool VideoFrozen {get;set;} @@ -260,6 +265,7 @@ namespace Tesses.YouTubeDownloader { public SavedPlaylist() { + TYTDTag=""; Title = ""; AuthorChannelId=""; AuthorTitle=""; @@ -269,6 +275,7 @@ namespace Tesses.YouTubeDownloader } public SavedPlaylist(Playlist playlist,List videos) { + TYTDTag=TYTDStorage.TYTDTag; Title = playlist.Title; AuthorChannelId = playlist.Author.ChannelId; AuthorTitle=playlist.Author.ChannelTitle; @@ -315,6 +322,7 @@ namespace Tesses.YouTubeDownloader public string Id { get; set; } public string Description { get; set; } public string Title { get; set; } + public string TYTDTag {get;set;} } public class SavedChannel { @@ -322,12 +330,13 @@ namespace Tesses.YouTubeDownloader { Id=c.Id; Title=c.Title; - + TYTDTag=TYTDStorage.TYTDTag; } public SavedChannel() { Id=""; Title=""; + TYTDTag=""; } public async IAsyncEnumerable GetVideosAsync(TYTDBase baseCls) { @@ -354,6 +363,6 @@ namespace Tesses.YouTubeDownloader } public string Id { get; set; } public string Title { get; set; } - + public string TYTDTag {get;set;} } } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader/TYTD.cs b/Tesses.YouTubeDownloader/TYTD.cs index 9fb04ef..1990da3 100644 --- a/Tesses.YouTubeDownloader/TYTD.cs +++ b/Tesses.YouTubeDownloader/TYTD.cs @@ -41,7 +41,14 @@ namespace Tesses.YouTubeDownloader await s.WriteAsync(data,0,data.Length,token); } } - + public static string TYTDTag {get {return _tytd_tag;}} + private static string _tytd_tag=_getTYTDTag(); + private static string _getTYTDTag() + { + string tag=Environment.GetEnvironmentVariable("TYTD_TAG"); + if(string.IsNullOrWhiteSpace(tag)) return "UnknownPC"; + return tag; + } bool can_download=true; public bool CanDownload {get {return can_download;} set {can_download=value;}} public IExtensionContext ExtensionContext {get;set;} diff --git a/Tesses.YouTubeDownloader/TYTDBase.cs b/Tesses.YouTubeDownloader/TYTDBase.cs index 1487322..46b0045 100644 --- a/Tesses.YouTubeDownloader/TYTDBase.cs +++ b/Tesses.YouTubeDownloader/TYTDBase.cs @@ -11,6 +11,8 @@ using System.IO; using YoutubeExplode.Playlists; using YoutubeExplode.Channels; +using YoutubeExplode.Search; + namespace Tesses.YouTubeDownloader { @@ -206,6 +208,8 @@ namespace Tesses.YouTubeDownloader return JsonConvert.DeserializeObject(await ReadAllTextAsync(enc)); } + + public virtual bool DownloadExists(string url) { string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json"; @@ -321,9 +325,95 @@ namespace Tesses.YouTubeDownloader return (VideoId.Parse(item.Id),item.Resolution); } } + public enum MediaType + { + Video=0, + Playlist=1, + Channel=2, + + } + public class SearchResult + { + + public SearchResult() + { + Title=""; + Id=""; + Type=MediaType.Video; + } + public SearchResult(ISearchResult result) + { + var video = result as VideoSearchResult; + var playlist = result as PlaylistSearchResult; + var channel = result as ChannelSearchResult; + if(video != null) + { + Id=video.Id; + Title = video.Title; + Type=MediaType.Video; + } + if(playlist != null) + { + Id=playlist.Id; + Title=playlist.Title; + Type=MediaType.Playlist; + } + if(channel != null) + { + Id=channel.Id; + Title = channel.Title; + Type=MediaType.Channel; + } + } + + public string Title {get;set;} + public string Id {get;set;} + + public MediaType Type {get;set;} + + public void AddToQueue(IStorage storage) + { + switch(Type) + { + case MediaType.Video: + storage.AddVideoAsync(Id,Resolution.NoDownload); + break; + case MediaType.Playlist: + storage.AddPlaylistAsync(Id,Resolution.NoDownload); + break; + case MediaType.Channel: + storage.AddChannelAsync(Id,Resolution.NoDownload); + break; + } + } + } public static class TYTDManager { + public static async IAsyncEnumerable SearchYouTubeAsync(this IStorage storage, string query,bool getMediaInfo=true) + { + await foreach(var vid in storage.YoutubeClient.Search.GetResultsAsync(query)) + { + var res=new SearchResult(vid); + if(getMediaInfo) + { + res.AddToQueue(storage); + } + yield return res; + } + } + public static bool DownloadFileExists(this ITYTDBase baseCtl,string url) + { + return baseCtl.FileExists(GetDownloadFile(url)); + } + public static string GetDownloadFile(this ITYTDBase baseCtl,string url) + { + return GetDownloadFile(url); + } + public static string GetDownloadFile(string url) + { + return $"Download/{B64.Base64UrlEncodes(url)}.bin"; + } /// /// Add Video, Playlist, Channel Or Username /// diff --git a/Tesses.YouTubeDownloader/TYTDIDownloaderStorageProxy.cs b/Tesses.YouTubeDownloader/TYTDIDownloaderStorageProxy.cs index e8b91af..d5c21ff 100644 --- a/Tesses.YouTubeDownloader/TYTDIDownloaderStorageProxy.cs +++ b/Tesses.YouTubeDownloader/TYTDIDownloaderStorageProxy.cs @@ -292,7 +292,10 @@ namespace Tesses.YouTubeDownloader e.CreateDirectoryIfNotExist(path); }); } - + public void WaitTillMediaContentQueueEmpty() + { + StorageAsStorage((e)=>e.WaitTillMediaContentQueueEmpty()); + } public Logger GetLogger() { Logger logger=null; @@ -676,21 +679,41 @@ namespace Tesses.YouTubeDownloader public async Task GetBestStreamInfoAsync(VideoId id) { - BestStreamInfo.BestStreamsSerialized s=null; - await StorageAsStorageAsync(async(e)=>{ - s=await e.GetBestStreamInfoAsync(id); - }); - - return s; + return await Storage.GetBestStreamInfoAsync(id); } public bool BestStreamInfoExists(VideoId id) { - bool res=false; - StorageAsStorage((e)=>{ - res=e.BestStreamInfoExists(id); - }); - return res; + + return Storage.BestStreamInfoExists(id); + } + public bool DownloadExists(string p) + { + + return Storage.DownloadExists(p); + } + + public async IAsyncEnumerable GetDownloadUrlsAsync() + { + await foreach(var url in Storage.GetDownloadUrlsAsync()) + { + yield return url; + } + } + + + + public async IAsyncEnumerable GetDownloadsAsync() + { + await foreach(var item in Storage.GetDownloadsAsync()) + { + yield return item; + } + } + + public async Task GetDownloadInfoAsync(string url) + { + return await Storage.GetDownloadInfoAsync(url); } } diff --git a/Tesses.YouTubeDownloader/VideoQueue.cs b/Tesses.YouTubeDownloader/VideoQueue.cs index a3a77e0..3f72faf 100644 --- a/Tesses.YouTubeDownloader/VideoQueue.cs +++ b/Tesses.YouTubeDownloader/VideoQueue.cs @@ -35,6 +35,19 @@ namespace Tesses.YouTubeDownloader List Subscriptions {get;set;} List<(SavedVideo Video, Resolution Resolution)> QueueList = new List<(SavedVideo Video, Resolution Resolution)>(); List Temporary =new List(); + + public void WaitTillMediaContentQueueEmpty() + { + while(true) + { + lock(Temporary) + { + if(Temporary.Count <= 0) return; + } + Thread.Sleep(100); + } + } + private async Task QueueLoop(CancellationToken token) {