From f79c6122fb765fa2ffbf5f726350747487b83567 Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Fri, 24 Jun 2022 18:02:51 -0500 Subject: [PATCH] Add SFTP And Rearrange --- ...s.YouTubeDownloader.ExtensionLoader.csproj | 6 +- .../Class1.cs | 5 +- .../Tesses.YouTubeDownloader.DiscUtils.csproj | 8 +- .../Class1.cs | 0 ....YouTubeDownloader.ExampleExtension.csproj | 4 +- .../JavaScript/readme.txt | 0 .../JavaScript/tytd-jquery.js | 0 .../JavaScript/tytd-jquery.min.js | 0 .../Tesses.YouTubeDownloader.SFTP/Class1.cs | 186 +++++++++++++ .../Tesses.YouTubeDownloader.SFTP.csproj | 31 +++ .../Tesses.YouTubeDownloader.Zio}/Class1.cs | 0 .../Tesses.YouTubeDownloader.Zio.csproj | 8 +- .../Program.cs | 0 ...s.YouTubeDownloder.WorkingConverter.csproj | 4 +- Tesses.YouTubeDownloader.Server/Class1.cs | 210 +++++++++++++-- .../Tesses.YouTubeDownloader.Server.csproj | 8 +- Tesses.YouTubeDownloader/BestStreams.cs | 26 +- Tesses.YouTubeDownloader/DownloadLoop.cs | 37 ++- Tesses.YouTubeDownloader/IDownloader.cs | 3 +- Tesses.YouTubeDownloader/IStorage.cs | 1 + Tesses.YouTubeDownloader/ITYTDBase.cs | 6 +- Tesses.YouTubeDownloader/Logging.cs | 31 ++- Tesses.YouTubeDownloader/PreMediaContext.cs | 15 +- Tesses.YouTubeDownloader/SavedVideo.cs | 14 +- Tesses.YouTubeDownloader/TYTD.cs | 59 ++++ Tesses.YouTubeDownloader/TYTDBase.cs | 216 +++++++-------- Tesses.YouTubeDownloader/TYTDClient.cs | 254 +++++++++++++++++- .../TYTDIDownloaderStorageProxy.cs | 114 ++++++-- .../Tesses.YouTubeDownloader.csproj | 8 +- 29 files changed, 1036 insertions(+), 218 deletions(-) rename {Tesses.YouTubeDownloader.DiscUtils => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.DiscUtils}/Class1.cs (92%) rename {Tesses.YouTubeDownloader.DiscUtils => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.DiscUtils}/Tesses.YouTubeDownloader.DiscUtils.csproj (77%) rename {Tesses.YouTubeDownloader.ExampleExtension => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.ExampleExtension}/Class1.cs (100%) rename {Tesses.YouTubeDownloader.ExampleExtension => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.ExampleExtension}/Tesses.YouTubeDownloader.ExampleExtension.csproj (54%) rename {Tesses.YouTubeDownloader.OtherClients => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients}/JavaScript/readme.txt (100%) rename {Tesses.YouTubeDownloader.OtherClients => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients}/JavaScript/tytd-jquery.js (100%) rename {Tesses.YouTubeDownloader.OtherClients => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients}/JavaScript/tytd-jquery.min.js (100%) create mode 100644 Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.SFTP/Class1.cs create mode 100644 Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.SFTP/Tesses.YouTubeDownloader.SFTP.csproj rename {Tesses.YouTubeDownloader.Zio => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.Zio}/Class1.cs (100%) rename {Tesses.YouTubeDownloader.Zio => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.Zio}/Tesses.YouTubeDownloader.Zio.csproj (76%) rename {Tesses.YouTubeDownloder.WorkingConverter => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloder.WorkingConverter}/Program.cs (100%) rename {Tesses.YouTubeDownloder.WorkingConverter => Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloder.WorkingConverter}/Tesses.YouTubeDownloder.WorkingConverter.csproj (70%) diff --git a/Tesses.YouTubeDownloader.ExtensionLoader/Tesses.YouTubeDownloader.ExtensionLoader.csproj b/Tesses.YouTubeDownloader.ExtensionLoader/Tesses.YouTubeDownloader.ExtensionLoader.csproj index 1898d2d..5babc7d 100644 --- a/Tesses.YouTubeDownloader.ExtensionLoader/Tesses.YouTubeDownloader.ExtensionLoader.csproj +++ b/Tesses.YouTubeDownloader.ExtensionLoader/Tesses.YouTubeDownloader.ExtensionLoader.csproj @@ -11,9 +11,9 @@ Tesses.YouTubeDownloader.ExtensionLoader Mike Nolan Tesses - 1.1.0 - 1.1.0 - 1.1.0 + 1.1.1 + 1.1.1 + 1.1.1 Load Extensions into TYTD (Not Tested) LGPL-3.0-only true diff --git a/Tesses.YouTubeDownloader.DiscUtils/Class1.cs b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.DiscUtils/Class1.cs similarity index 92% rename from Tesses.YouTubeDownloader.DiscUtils/Class1.cs rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.DiscUtils/Class1.cs index e4c5213..e459085 100644 --- a/Tesses.YouTubeDownloader.DiscUtils/Class1.cs +++ b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.DiscUtils/Class1.cs @@ -83,10 +83,7 @@ namespace Tesses.YouTubeDownloader.DiscUtils { fileSystem.MoveFile(ConvertToDiscUtils(src),ConvertToDiscUtils(dest)); } - public override async Task GetLengthAsync(string path) - { - return await Task.FromResult(fileSystem.GetFileLength(ConvertToDiscUtils(path))); - } + private string ConvertToDiscUtils(string path) { diff --git a/Tesses.YouTubeDownloader.DiscUtils/Tesses.YouTubeDownloader.DiscUtils.csproj b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.DiscUtils/Tesses.YouTubeDownloader.DiscUtils.csproj similarity index 77% rename from Tesses.YouTubeDownloader.DiscUtils/Tesses.YouTubeDownloader.DiscUtils.csproj rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.DiscUtils/Tesses.YouTubeDownloader.DiscUtils.csproj index 2005cac..072cd3c 100644 --- a/Tesses.YouTubeDownloader.DiscUtils/Tesses.YouTubeDownloader.DiscUtils.csproj +++ b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.DiscUtils/Tesses.YouTubeDownloader.DiscUtils.csproj @@ -1,7 +1,7 @@ - + @@ -16,9 +16,9 @@ Tesses.YouTubeDownloader.DiscUtils Mike Nolan Tesses - 1.0.0.0 - 1.0.0.0 - 1.0.0.0 + 1.0.0.1 + 1.0.0.1 + 1.0.0.1 Adds DiscUtils filesystem support LGPL-3.0-only true diff --git a/Tesses.YouTubeDownloader.ExampleExtension/Class1.cs b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.ExampleExtension/Class1.cs similarity index 100% rename from Tesses.YouTubeDownloader.ExampleExtension/Class1.cs rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.ExampleExtension/Class1.cs diff --git a/Tesses.YouTubeDownloader.ExampleExtension/Tesses.YouTubeDownloader.ExampleExtension.csproj b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.ExampleExtension/Tesses.YouTubeDownloader.ExampleExtension.csproj similarity index 54% rename from Tesses.YouTubeDownloader.ExampleExtension/Tesses.YouTubeDownloader.ExampleExtension.csproj rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.ExampleExtension/Tesses.YouTubeDownloader.ExampleExtension.csproj index 663e45b..377992b 100644 --- a/Tesses.YouTubeDownloader.ExampleExtension/Tesses.YouTubeDownloader.ExampleExtension.csproj +++ b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.ExampleExtension/Tesses.YouTubeDownloader.ExampleExtension.csproj @@ -1,7 +1,7 @@ - - + + diff --git a/Tesses.YouTubeDownloader.OtherClients/JavaScript/readme.txt b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients/JavaScript/readme.txt similarity index 100% rename from Tesses.YouTubeDownloader.OtherClients/JavaScript/readme.txt rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients/JavaScript/readme.txt diff --git a/Tesses.YouTubeDownloader.OtherClients/JavaScript/tytd-jquery.js b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients/JavaScript/tytd-jquery.js similarity index 100% rename from Tesses.YouTubeDownloader.OtherClients/JavaScript/tytd-jquery.js rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients/JavaScript/tytd-jquery.js diff --git a/Tesses.YouTubeDownloader.OtherClients/JavaScript/tytd-jquery.min.js b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients/JavaScript/tytd-jquery.min.js similarity index 100% rename from Tesses.YouTubeDownloader.OtherClients/JavaScript/tytd-jquery.min.js rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.OtherClients/JavaScript/tytd-jquery.min.js diff --git a/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.SFTP/Class1.cs b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.SFTP/Class1.cs new file mode 100644 index 0000000..54c2492 --- /dev/null +++ b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.SFTP/Class1.cs @@ -0,0 +1,186 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Renci.SshNet; +using Tesses.YouTubeDownloader; +using System.Collections.Generic; + +namespace Tesses.YouTubeDownloader.SFTP +{ + public class SSHFS : TYTDStorage +{ + SftpClient ftp; + string path; + public SSHFS(string url,string userName,string passWord) + { + Uri uri=new Uri(url); + int port; + if(uri.Port == -1) + { + port=22; + }else{ + port=uri.Port; + } + path=uri.PathAndQuery; + ftp=new SftpClient(uri.Host,port,userName,passWord); + + ftp.Connect(); + ftp.ChangeDirectory(path); + } + private string GetArg(string[] a,int aI) + { + if(aI < a.Length) + { + return a[aI]; + } + return ""; + } + public SSHFS(Uri uri) + { + int port; + if(uri.Port == -1) + { + port=22; + }else{ + port=uri.Port; + } + path=uri.PathAndQuery; + var userPass=uri.UserInfo.Split(new char[]{':'},2,StringSplitOptions.None); + ftp=new SftpClient(uri.Host,port,GetArg(userPass,0),GetArg(userPass,1)); + ftp.Connect(); + ftp.ChangeDirectory(path); + } + + public SSHFS(string url,string userName,params PrivateKeyFile[] keys) + { + Uri uri=new Uri(url); + int port; + if(uri.Port == -1) + { + port=22; + }else{ + port=uri.Port; + } + path=uri.PathAndQuery; + ftp=new SftpClient(uri.Host,port,userName,keys); + + ftp.Connect(); + ftp.ChangeDirectory(path); + } + public SSHFS(SftpClient client) + { + ftp=client; + path=client.WorkingDirectory; + } + public override async Task CreateAsync(string path) + { + return await Task.FromResult(ftp.Open($"{this.path.TrimEnd('/')}/{path.TrimStart('/')}",FileMode.Create,FileAccess.Write)); + } + + public override void CreateDirectory(string path) + { + ftp.CreateDirectory(path); + } + + public override void DeleteDirectory(string dir, bool recursive = false) + { + var entries = ftp.ListDirectory(dir).ToArray(); + + if(!recursive && entries.Length > 0) + { + return; + } + if(recursive) + { + foreach(var entry in entries) + { + if(entry.IsDirectory) + { + DeleteDirectory($"{dir.TrimEnd('/')}/{entry.Name}",true); + }else{ + entry.Delete(); + } + } + } + ftp.DeleteDirectory(dir); + } + + public override void DeleteFile(string file) + { + ftp.DeleteFile(path); + } + + public override async Task DirectoryExistsAsync(string path) + { + bool value=false; + if(ftp.Exists(path)) + { + + if(ftp.Get(path).IsDirectory) + { + value=true; + } + } + return await Task.FromResult(value); + } + + public override async IAsyncEnumerable EnumerateDirectoriesAsync(string path) + { + foreach(var item in ftp.ListDirectory(path)) + { + if(item.IsDirectory) + { + yield return await Task.FromResult(item.Name); + } + } + } + + public override async IAsyncEnumerable EnumerateFilesAsync(string path) + { + foreach(var item in ftp.ListDirectory(path)) + { + if(item.IsRegularFile) + { + yield return await Task.FromResult(item.Name); + } + + } + } + + public override async Task FileExistsAsync(string path) + { + bool value=false; + if(ftp.Exists(path)) + { + if(ftp.Get(path).IsRegularFile) + { + value=true; + } + } + return await Task.FromResult(value); + } + + public override void MoveDirectory(string src, string dest) + { + ftp.RenameFile(src,dest); + } + + public override async Task OpenOrCreateAsync(string path) + { + return await Task.FromResult(ftp.Open($"{this.path.TrimEnd('/')}/{path.TrimStart('/')}",FileMode.OpenOrCreate,FileAccess.Write)); + } + + public override async Task OpenReadAsync(string path) + { + + return await Task.FromResult(ftp.Open($"{this.path.TrimEnd('/')}/{path.TrimStart('/')}",FileMode.Open,FileAccess.Read)); + } + + public override void RenameFile(string src, string dest) + { + ftp.RenameFile(src,dest); + } +} + +} diff --git a/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.SFTP/Tesses.YouTubeDownloader.SFTP.csproj b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.SFTP/Tesses.YouTubeDownloader.SFTP.csproj new file mode 100644 index 0000000..4c86335 --- /dev/null +++ b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.SFTP/Tesses.YouTubeDownloader.SFTP.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.0 + 8.0 + true + Tesses.YouTubeDownloader.SFTP + Mike Nolan + Tesses + 1.0.0 + 1.0.0 + 1.0.0 + SSH.NET SFTP for Tesses.YouTubeDownloader + LGPL-3.0-only + true + YoutubeExplode, YouTube, YouTubeDownloader + https://gitlab.tesses.cf/tesses50/tytd + + + + + + + + + + + + + + diff --git a/Tesses.YouTubeDownloader.Zio/Class1.cs b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.Zio/Class1.cs similarity index 100% rename from Tesses.YouTubeDownloader.Zio/Class1.cs rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.Zio/Class1.cs diff --git a/Tesses.YouTubeDownloader.Zio/Tesses.YouTubeDownloader.Zio.csproj b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.Zio/Tesses.YouTubeDownloader.Zio.csproj similarity index 76% rename from Tesses.YouTubeDownloader.Zio/Tesses.YouTubeDownloader.Zio.csproj rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.Zio/Tesses.YouTubeDownloader.Zio.csproj index 972c897..409ecd3 100644 --- a/Tesses.YouTubeDownloader.Zio/Tesses.YouTubeDownloader.Zio.csproj +++ b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloader.Zio/Tesses.YouTubeDownloader.Zio.csproj @@ -1,7 +1,7 @@ - + @@ -16,10 +16,10 @@ Tesses.YouTubeDownloader.Zio Mike Nolan Tesses - 1.0.0.0 - 1.0.0.0 + 1.0.0.1 + 1.0.0.1 true - 1.0.0.0 + 1.0.0.1 Adds Zio filesystem support LGPL-3.0-only YoutubeExplode, YouTube, YouTubeDownloader diff --git a/Tesses.YouTubeDownloder.WorkingConverter/Program.cs b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloder.WorkingConverter/Program.cs similarity index 100% rename from Tesses.YouTubeDownloder.WorkingConverter/Program.cs rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloder.WorkingConverter/Program.cs diff --git a/Tesses.YouTubeDownloder.WorkingConverter/Tesses.YouTubeDownloder.WorkingConverter.csproj b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloder.WorkingConverter/Tesses.YouTubeDownloder.WorkingConverter.csproj similarity index 70% rename from Tesses.YouTubeDownloder.WorkingConverter/Tesses.YouTubeDownloder.WorkingConverter.csproj rename to Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloder.WorkingConverter/Tesses.YouTubeDownloder.WorkingConverter.csproj index 3415ca0..fca1829 100644 --- a/Tesses.YouTubeDownloder.WorkingConverter/Tesses.YouTubeDownloder.WorkingConverter.csproj +++ b/Tesses.YouTubeDownloader.Extras/Tesses.YouTubeDownloder.WorkingConverter/Tesses.YouTubeDownloder.WorkingConverter.csproj @@ -1,7 +1,7 @@ - - + + diff --git a/Tesses.YouTubeDownloader.Server/Class1.cs b/Tesses.YouTubeDownloader.Server/Class1.cs index 8bc9200..11a2c68 100644 --- a/Tesses.YouTubeDownloader.Server/Class1.cs +++ b/Tesses.YouTubeDownloader.Server/Class1.cs @@ -75,6 +75,7 @@ namespace Tesses.YouTubeDownloader.Server var cd = new System.Net.Mime.ContentDisposition(); string filename = GetVideoName(name); cd.FileName = filename; + cd.DispositionType = System.Net.Mime.DispositionTypeNames.Inline; return cd; } @@ -211,7 +212,7 @@ namespace Tesses.YouTubeDownloader.Server await NotFoundServer.ServerNull.GetAsync(ctx); return; } - using(var s = await baseCtl.OpenReadAsyncWithLength(file)) + using(var s = await baseCtl.OpenReadAsync(file)) { await ctx.SendStreamAsync(s); } @@ -326,18 +327,190 @@ namespace Tesses.YouTubeDownloader.Server { this.Downloader=downloader; - Add("/AddItem",AddItem); - Add("/AddChannel",AddChannel); - Add("/AddUser",AddUser); - Add("/AddPlaylist",AddPlaylist); - Add("/AddVideo",AddVideo); - Add("/Progress",ProgressFunc); - Add("/QueueList",QueueList); - Add("/subscribe",Subscribe); - Add("/resubscribe",Resubscribe); - Add("/unsubscribe",Unsubscribe); - Add("/subscriptions",Subscriptions); + AddBoth("/AddItem",AddItem); + AddBoth("/AddChannel",AddChannel); + AddBoth("/AddUser",AddUser); + AddBoth("/AddPlaylist",AddPlaylist); + AddBoth("/AddVideo",AddVideo); + AddBoth("/Progress",ProgressFunc); + AddBoth("/QueueList",QueueList); + AddBoth("/subscribe",Subscribe); + AddBoth("/resubscribe",Resubscribe); + AddBoth("/unsubscribe",Unsubscribe); + AddBoth("/subscriptions",Subscriptions); + AddBoth("/Subscribe",Subscribe); + AddBoth("/Resubscribe",Resubscribe); + AddBoth("/Unsubscribe",Unsubscribe); + AddBoth("/Subscriptions",Subscriptions); + AddBoth("/AddToList",AddToList); + AddBoth("/DeleteFromList",DeleteFromList); + Add("/ReplaceList",ReplaceList,"POST"); + AddBoth("/DeleteList",DeleteList); + AddBoth("/SetResolutionInList",SetResolutionInList); + /* + public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<(VideoId Id, Resolution Resolution)> items) + { + throw new NotImplementedException(); + } + + public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<(VideoId Id, Resolution Resolution)> items) + { + throw new NotImplementedException(); + } + + public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id) + { + throw new NotImplementedException(); + } + + public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution) + { + throw new NotImplementedException(); + }*/ + } + + private void AddBoth(string url,HttpActionAsync action) + { + Add(url,action); + Add(url,async(evt)=>{ + evt.ParseBody(); + await action(evt); + },"POST"); + } + public async Task DeleteList(ServerContext ctx) + { + //this is for personal playlists + string name; + if(ctx.QueryParams.TryGetFirst("name",out name)){ + Downloader.DeletePersonalPlaylist(name); + + //Downloader.AddToPersonalPlaylistAsync(name); + + } + await ctx.SendTextAsync( + $"You Will Be Redirected in 5 Sec

You Will Be Redirected in 5 Sec

\n" + ); + } + public async Task ReplaceList(ServerContext ctx) + { + + //this is for personal playlists + string name; + if(ctx.QueryParams.TryGetFirst("name",out name)){ + string jsonData; + List itemList; + if(ctx.QueryParams.TryGetFirst("data",out jsonData)) + { + itemList = JsonConvert.DeserializeObject>(jsonData); + + await Downloader.ReplacePersonalPlaylistAsync(name,itemList); + + + } + + //Downloader.AddToPersonalPlaylistAsync(name); + + } + await ctx.SendTextAsync( + $"You Will Be Redirected in 5 Sec

You Will Be Redirected in 5 Sec

\n" + ); + } + + public async Task AddToList(ServerContext ctx) + { + + //this is for personal playlists + string name; + if(ctx.QueryParams.TryGetFirst("name",out name)){ + string jsonData; + List itemList; + if(ctx.Method == "POST" && ctx.QueryParams.TryGetFirst("data",out jsonData)) + { + itemList = JsonConvert.DeserializeObject>(jsonData); + + }else{ + itemList=new List(); + string id; + + if(ctx.QueryParams.TryGetFirst("v",out id)) + { + + Resolution resolution=Resolution.PreMuxed; + string res; + if(ctx.QueryParams.TryGetFirst("res",out res)) + { + if(!Enum.TryParse(res,out resolution)) + { + resolution=Resolution.PreMuxed; + } + } + VideoId? id1=VideoId.TryParse(id); + if(id1.HasValue) + { + itemList.Add(new ListContentItem(id1,resolution)); + + } + } + + } + await Downloader.AddToPersonalPlaylistAsync(name,itemList); + + //Downloader.AddToPersonalPlaylistAsync(name); + + } + await ctx.SendTextAsync( + $"You Will Be Redirected in 5 Sec

You Will Be Redirected in 5 Sec

\n" + ); + } + public async Task DeleteFromList(ServerContext ctx) + { + //this is for personal playlists + string name; + if(ctx.QueryParams.TryGetFirst("name",out name)){ + string id; + + if(ctx.QueryParams.TryGetFirst("v",out id)) + { + VideoId? id1=VideoId.TryParse(id); + if(id1.HasValue) + { + await Downloader.RemoveItemFromPersonalPlaylistAsync(name,id1.Value); + } + } + } + await ctx.SendTextAsync( + $"You Will Be Redirected in 5 Sec

You Will Be Redirected in 5 Sec

\n" + ); + } + public async Task SetResolutionInList(ServerContext ctx) + { + //this is for personal playlists + string name; + if(ctx.QueryParams.TryGetFirst("name",out name)){ + string id; + + if(ctx.QueryParams.TryGetFirst("v",out id)) + { + Resolution resolution=Resolution.PreMuxed; + string res; + if(ctx.QueryParams.TryGetFirst("res",out res)) + { + if(!Enum.TryParse(res,out resolution)) + { + resolution=Resolution.PreMuxed; + } + } + VideoId? id1=VideoId.TryParse(id); + if(id1.HasValue) + { + await Downloader.SetResolutionForItemInPersonalPlaylistAsync(name,id1.Value,resolution); + } + } + } + await ctx.SendTextAsync( + $"You Will Be Redirected in 5 Sec

You Will Be Redirected in 5 Sec

\n" + ); } public async Task Subscriptions(ServerContext ctx) { @@ -351,7 +524,9 @@ namespace Tesses.YouTubeDownloader.Server } - + await ctx.SendTextAsync( + $"You Will Be Redirected in 5 Sec

You Will Be Redirected in 5 Sec

\n" + ); } public async Task Resubscribe(ServerContext ctx) { @@ -373,7 +548,7 @@ namespace Tesses.YouTubeDownloader.Server } } - ChannelId? cid=ChannelId.TryParse(WebUtility.UrlDecode(id)); + ChannelId? cid=ChannelId.TryParse(id); if(cid.HasValue) { @@ -399,7 +574,7 @@ namespace Tesses.YouTubeDownloader.Server - ChannelId? cid=ChannelId.TryParse(WebUtility.UrlDecode(id)); + ChannelId? cid=ChannelId.TryParse(id); if(cid.HasValue) { @@ -441,14 +616,14 @@ namespace Tesses.YouTubeDownloader.Server } } - ChannelId? cid=ChannelId.TryParse(WebUtility.UrlDecode(id)); + ChannelId? cid=ChannelId.TryParse(id); if(cid.HasValue) { await storage.SubscribeAsync(cid.Value,getinfo,conf); }else{ - UserName? uname=UserName.TryParse(WebUtility.UrlDecode(id)); + UserName? uname=UserName.TryParse(id); await storage.SubscribeAsync(uname.Value,conf); } @@ -473,6 +648,7 @@ namespace Tesses.YouTubeDownloader.Server if(ctx.QueryParams.TryGetFirst("v",out id)) { + Resolution resolution=Resolution.PreMuxed; string res; if(ctx.QueryParams.TryGetFirst("res",out res)) diff --git a/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj b/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj index eafb34c..f2cbd3d 100644 --- a/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj +++ b/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj @@ -5,7 +5,7 @@
- + @@ -15,9 +15,9 @@ Tesses.YouTubeDownloader.Server Mike Nolan Tesses - 1.1.1 - 1.1.1 - 1.1.1 + 1.1.3 + 1.1.3 + 1.1.3 Adds WebServer to TYTD LGPL-3.0-only true diff --git a/Tesses.YouTubeDownloader/BestStreams.cs b/Tesses.YouTubeDownloader/BestStreams.cs index b08a1d4..e40a42b 100644 --- a/Tesses.YouTubeDownloader/BestStreams.cs +++ b/Tesses.YouTubeDownloader/BestStreams.cs @@ -11,7 +11,7 @@ namespace Tesses.YouTubeDownloader { public class BestStreams { - + public bool VideoFrozen {get;set;} public BestStreamInfo MuxedStreamInfo {get;set;} public BestStreamInfo VideoOnlyStreamInfo {get;set;} @@ -20,6 +20,7 @@ namespace Tesses.YouTubeDownloader public static async Task GetPathResolution(ITYTDBase storage,SavedVideo video,Resolution resolution=Resolution.PreMuxed) { + if(video.LegacyVideo) { if(resolution == Resolution.Mux) @@ -27,10 +28,22 @@ namespace Tesses.YouTubeDownloader return $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mp4"; }else{ + var f= await BestStreamInfo.GetBestStreams(storage,video.Id); + if(f ==null) return ""; - + + if(f.VideoFrozen) + { + + if(resolution == Resolution.Mux) + return $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mkv"; + + return $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mp4"; + + } + string[] exts= new string[] {"mkv",f.MuxedStreamInfo.Container.Name,f.AudioOnlyStreamInfo.Container.Name,f.VideoOnlyStreamInfo.Container.Name}; string ext=exts[(int)resolution]; @@ -87,6 +100,15 @@ namespace Tesses.YouTubeDownloader } DateTime expires=DateTime.Now.AddHours(6); try{ + if(storage.VideoInfoExists(id)) + { + var video = await storage.GetVideoInfoAsync(id); + if(video.VideoFrozen) + { + + return new BestStreams() {VideoFrozen=true,MuxedStreamInfo=null,VideoOnlyStreamInfo=null,AudioOnlyStreamInfo=null}; + } + } var res=await storage.YoutubeClient.Videos.Streams.GetManifestAsync(id,token); var audioOnly=res.GetAudioOnlyStreams().GetWithHighestBitrate(); diff --git a/Tesses.YouTubeDownloader/DownloadLoop.cs b/Tesses.YouTubeDownloader/DownloadLoop.cs index 30a9528..adc776d 100644 --- a/Tesses.YouTubeDownloader/DownloadLoop.cs +++ b/Tesses.YouTubeDownloader/DownloadLoop.cs @@ -145,7 +145,7 @@ namespace Tesses.YouTubeDownloader } }catch(Exception ex) { - await GetLogger().WriteAsync(ex); + await GetLogger().WriteAsync(ex,video.Id); } } @@ -154,7 +154,7 @@ namespace Tesses.YouTubeDownloader if (await FileExistsAsync(path)) { - return (await GetLengthAsync(path) == 0); + return ((await OpenReadAsync(path)).Length == 0); } return true; } @@ -205,8 +205,9 @@ namespace Tesses.YouTubeDownloader } }catch(Exception ex) { - Console.WriteLine(ex.Message); _=ex; + Console.WriteLine("FFMPEG ERROR, sorry cant read logging config"); + return false; } return true; @@ -332,10 +333,11 @@ namespace Tesses.YouTubeDownloader DeleteIfExists(video_bin); DeleteIfExists(audio_bin); DeleteIfExists(output_mkv); - long len=await GetLengthAsync(videoSrc); + using(var vstrm_src=await OpenReadAsync(videoSrc)) { + long len = vstrm_src.Length; using(var vstrm_dest = File.Create(video_bin)) { Console.WriteLine("Opening vstream"); @@ -359,7 +361,7 @@ namespace Tesses.YouTubeDownloader using(var astrm_dest = File.Create(audio_bin)) { Console.WriteLine("opening astream"); - if(!await CopyStreamAsync(astrm_src,astrm_dest,0,len,4096, + if(!await CopyStreamAsync(astrm_src,astrm_dest,0,astrm_src.Length,4096, new Progress((e)=>{ if(progress !=null) { @@ -455,6 +457,10 @@ namespace Tesses.YouTubeDownloader 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"); + } await MoveLegacyStreams(video,streams); string complete = $"VideoOnly/{video.Id}.{streams.VideoOnlyStreamInfo.Container}"; string incomplete = $"VideoOnly/{video.Id}incomplete.{streams.VideoOnlyStreamInfo.Container}"; @@ -472,11 +478,11 @@ namespace Tesses.YouTubeDownloader } if(report) await ReportStartVideo(video, Resolution.VideoOnly,streams.VideoOnlyStreamInfo.Size.Bytes); - long len=await GetLengthAsync(incomplete); + using(var dest = await OpenOrCreateAsync(incomplete)) { - ret=await CopyStreamAsync(strm,dest,len,streams.VideoOnlyStreamInfo.Size.Bytes,4096,progress,token); + ret=await CopyStreamAsync(strm,dest,dest.Length,streams.VideoOnlyStreamInfo.Size.Bytes,4096,progress,token); } if(ret) { @@ -511,6 +517,7 @@ namespace Tesses.YouTubeDownloader } public async Task MoveLegacyStreams(SavedVideo video,BestStreams streams) { + if(video.VideoFrozen) return; if(video.LegacyVideo) { string legacyVideoOnlyComplete = $"VideoOnly/{video.Id}.mp4"; @@ -550,6 +557,10 @@ namespace Tesses.YouTubeDownloader 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); @@ -568,11 +579,11 @@ namespace Tesses.YouTubeDownloader } if(report) await ReportStartVideo(video, Resolution.AudioOnly,streams.AudioOnlyStreamInfo.Size.Bytes); - long len=await GetLengthAsync(incomplete); + using(var dest = await OpenOrCreateAsync(incomplete)) { - ret=await CopyStreamAsync(strm,dest,len,streams.AudioOnlyStreamInfo.Size.Bytes,4096,progress,token); + ret=await CopyStreamAsync(strm,dest,dest.Length,streams.AudioOnlyStreamInfo.Size.Bytes,4096,progress,token); } if(ret) { @@ -597,6 +608,10 @@ namespace Tesses.YouTubeDownloader if(!can_download) return; 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"); + } await MoveLegacyStreams(video,streams); string complete = $"PreMuxed/{video.Id}.{streams.MuxedStreamInfo.Container}"; string incomplete = $"PreMuxed/{video.Id}incomplete.{streams.MuxedStreamInfo.Container}"; @@ -616,11 +631,11 @@ namespace Tesses.YouTubeDownloader } if(report) await ReportStartVideo(video,Resolution.PreMuxed,streams.MuxedStreamInfo.Size.Bytes); - long len=await GetLengthAsync(incomplete); + bool ret; using(var dest = await OpenOrCreateAsync(incomplete)) { - ret=await CopyStreamAsync(strm,dest,len,streams.MuxedStreamInfo.Size.Bytes,4096,progress,token); + ret=await CopyStreamAsync(strm,dest,dest.Length,streams.MuxedStreamInfo.Size.Bytes,4096,progress,token); } //We know its resolution if(ret) diff --git a/Tesses.YouTubeDownloader/IDownloader.cs b/Tesses.YouTubeDownloader/IDownloader.cs index a28e0ae..b095357 100644 --- a/Tesses.YouTubeDownloader/IDownloader.cs +++ b/Tesses.YouTubeDownloader/IDownloader.cs @@ -10,7 +10,7 @@ using YoutubeExplode.Playlists; using YoutubeExplode.Channels; namespace Tesses.YouTubeDownloader { - public interface IDownloader + public interface IDownloader : IPersonalPlaylistSet { Task AddVideoAsync(VideoId id,Resolution resolution=Resolution.PreMuxed); Task AddPlaylistAsync(PlaylistId id,Resolution resolution=Resolution.PreMuxed); @@ -25,5 +25,6 @@ namespace Tesses.YouTubeDownloader Task SubscribeAsync(ChannelId id,bool downloadChannelInfo=false,ChannelBellInfo bellInfo = ChannelBellInfo.NotifyAndDownload); Task SubscribeAsync(UserName name,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload); Task ResubscribeAsync(ChannelId id,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload); + void DeletePersonalPlaylist(string name); } } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader/IStorage.cs b/Tesses.YouTubeDownloader/IStorage.cs index 827fc63..79c688d 100644 --- a/Tesses.YouTubeDownloader/IStorage.cs +++ b/Tesses.YouTubeDownloader/IStorage.cs @@ -55,5 +55,6 @@ namespace Tesses.YouTubeDownloader Task MoveLegacyStreams(SavedVideo video,BestStreams streams); void StartLoop(CancellationToken token=default(CancellationToken)); + event EventHandler Error; } } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader/ITYTDBase.cs b/Tesses.YouTubeDownloader/ITYTDBase.cs index 82a05e6..890e622 100644 --- a/Tesses.YouTubeDownloader/ITYTDBase.cs +++ b/Tesses.YouTubeDownloader/ITYTDBase.cs @@ -18,7 +18,7 @@ namespace Tesses.YouTubeDownloader IAsyncEnumerable GetPersonalPlaylistsAsync(); Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path); - Task GetLengthAsync(string path); + bool FileExists(string path); IAsyncEnumerable GetVideoIdsAsync(); Task GetVideoInfoAsync(VideoId id); @@ -46,7 +46,7 @@ namespace Tesses.YouTubeDownloader IEnumerable EnumerateFiles(string path); IEnumerable EnumerateDirectories(string path); - Task OpenReadAsyncWithLength(string path); + Task OpenReadAsync(string path); Task FileExistsAsync(string path); @@ -56,5 +56,7 @@ namespace Tesses.YouTubeDownloader IAsyncEnumerable EnumerateFilesAsync(string path); IAsyncEnumerable EnumerateDirectoriesAsync(string path); + + } } diff --git a/Tesses.YouTubeDownloader/Logging.cs b/Tesses.YouTubeDownloader/Logging.cs index 21dae9a..659903b 100644 --- a/Tesses.YouTubeDownloader/Logging.cs +++ b/Tesses.YouTubeDownloader/Logging.cs @@ -1,7 +1,3 @@ - - - - using System; using YoutubeExplode; using YoutubeExplode.Videos; @@ -21,9 +17,25 @@ namespace Tesses.YouTubeDownloader internal class LockObj { + } + public class TYTDErrorEventArgs : EventArgs + { + public TYTDErrorEventArgs(VideoId? id,Exception exception) + { + Id=id; + Exception=exception; + PrintError =true; + } + + public VideoId? Id {get;set;} + + public Exception Exception {get;set;} + + public bool PrintError {get;set;} } public partial class TYTDStorage { + public event EventHandler Error; internal LoggerProperties Properties {get;set;} public LoggerProperties GetProperties() { @@ -154,10 +166,17 @@ namespace Tesses.YouTubeDownloader //mtx.ReleaseMutex(); } } - public async Task WriteAsync(Exception ex) { - await WriteAsync($"Exception Catched:\n{ex.ToString()}",_storage.GetLoggerProperties().PrintErrors,true); + await WriteAsync(ex,null); + } + public async Task WriteAsync(Exception ex,VideoId? id) + { + TYTDErrorEventArgs args=new TYTDErrorEventArgs(id,ex); + _storage.ThrowError(args); + + await WriteAsync($"Exception Catched:\n{ex.ToString()}",_storage.GetLoggerProperties().PrintErrors && args.PrintError,true); + } public async Task WriteAsync(SavedVideo video) { diff --git a/Tesses.YouTubeDownloader/PreMediaContext.cs b/Tesses.YouTubeDownloader/PreMediaContext.cs index 58d18ae..d9db14b 100644 --- a/Tesses.YouTubeDownloader/PreMediaContext.cs +++ b/Tesses.YouTubeDownloader/PreMediaContext.cs @@ -40,9 +40,15 @@ namespace Tesses.YouTubeDownloader string path=$"Channel/{Id.Value}.json"; if(await storage.Continue(path)) { + try{ channel=await DownloadThumbnails(storage,await storage.YoutubeClient.Channels.GetAsync(Id.Value)); //channel=new SavedChannel(i); await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(channel)); + }catch(Exception ex) + { + await storage.GetLogger().WriteAsync(ex); + return null; + } return channel; }else{ var j=JsonConvert.DeserializeObject(await storage.ReadAllTextAsync(path)); @@ -114,6 +120,8 @@ namespace Tesses.YouTubeDownloader string path=$"Playlist/{Id}.json"; List videos=new List(); + try{ + await foreach(var vid in storage.YoutubeClient.Playlists.GetVideosAsync(Id)) { videos.Add(vid); @@ -127,6 +135,10 @@ namespace Tesses.YouTubeDownloader } await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(p)); + }catch(Exception ex) + { + await storage.GetLogger().WriteAsync(ex); + } if(Resolution == Resolution.NoDownload) return; foreach(var item in videos) { @@ -161,7 +173,8 @@ namespace Tesses.YouTubeDownloader await video.DownloadThumbnails(storage); }catch(Exception ex) { - _=ex; + + await storage.GetLogger().WriteAsync(ex,Id); return; } diff --git a/Tesses.YouTubeDownloader/SavedVideo.cs b/Tesses.YouTubeDownloader/SavedVideo.cs index 2f6345b..4aebc6d 100644 --- a/Tesses.YouTubeDownloader/SavedVideo.cs +++ b/Tesses.YouTubeDownloader/SavedVideo.cs @@ -24,6 +24,8 @@ namespace Tesses.YouTubeDownloader } public class SavedVideoLegacy { + + public string Id {get;set;} public string Title {get;set;} public string AuthorChannelId {get;set;} @@ -80,7 +82,10 @@ namespace Tesses.YouTubeDownloader UploadDate=new DateTime(1992,8,20); AddDate=DateTime.Now; LegacyVideo=false; + DownloadFrom="YouTube"; + VideoFrozen=false; } + public SavedVideo(Video video) { Id=video.Id; @@ -96,11 +101,16 @@ namespace Tesses.YouTubeDownloader UploadDate = video.UploadDate.DateTime; AddDate=DateTime.Now; LegacyVideo=false; + DownloadFrom="YouTube"; + VideoFrozen=false; } public bool LegacyVideo {get;set;} - + public bool VideoFrozen {get;set;} + + public string DownloadFrom {get;set;} public SavedVideoLegacy ToLegacy() { + SavedVideoLegacy legacy=new SavedVideoLegacy(); legacy.Thumbnails=new List<(int, int, string)>(); legacy.Thumbnails.Add((120,90,$"https://s.ytimg.com/vi/{Id}/default.jpg")); @@ -117,6 +127,8 @@ namespace Tesses.YouTubeDownloader legacy.Title=Title; legacy.UploadDate=UploadDate.ToString(); legacy.Views=Views; + + return legacy; } public Video ToVideo() diff --git a/Tesses.YouTubeDownloader/TYTD.cs b/Tesses.YouTubeDownloader/TYTD.cs index fda6f7b..336ba48 100644 --- a/Tesses.YouTubeDownloader/TYTD.cs +++ b/Tesses.YouTubeDownloader/TYTD.cs @@ -164,6 +164,10 @@ namespace Tesses.YouTubeDownloader }); thread1.Start(); } + internal void ThrowError(TYTDErrorEventArgs e) + { + Error?.Invoke(this,e); + } public async Task WriteAllTextAsync(string path,string data) { using(var dstStrm= await CreateAsync(path)) @@ -174,5 +178,60 @@ namespace Tesses.YouTubeDownloader } } } + + public async Task AddToPersonalPlaylistAsync(string name, IEnumerable items) + { + List items0=new List(); + await foreach(var item in GetPersonalPlaylistContentsAsync(name)) + { + items0.Add(item); + } + items0.AddRange(items); + await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0)); + + } + + public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable items) + { + + await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items.ToList())); + + } + public void DeletePersonalPlaylist(string name) + { + DeleteFile($"PersonalPlaylist/{name}.json"); + } + + public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id) + { + List items0=new List(); + await foreach(var item in GetPersonalPlaylistContentsAsync(name)) + { + if(item.Id != id) + { + items0.Add(item); + } + } + + await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0)); + + } + + public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution) + { + List items0=new List(); + await foreach(var item in GetPersonalPlaylistContentsAsync(name)) + { + if(item.Id != id) + { + items0.Add(item); + }else{ + items0.Add(new ListContentItem(item.Id,resolution)); + } + } + + await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0)); + + } } } diff --git a/Tesses.YouTubeDownloader/TYTDBase.cs b/Tesses.YouTubeDownloader/TYTDBase.cs index a73a49c..a011c75 100644 --- a/Tesses.YouTubeDownloader/TYTDBase.cs +++ b/Tesses.YouTubeDownloader/TYTDBase.cs @@ -13,74 +13,18 @@ using YoutubeExplode.Playlists; using YoutubeExplode.Channels; namespace Tesses.YouTubeDownloader { - internal class TYTDBaseFileReader : Stream - { - //TYTDBase baseCtl; - Stream baseStrm; - long len; - private TYTDBaseFileReader(long leng) - { - len=leng; - } - public static async Task GetStream(TYTDBase baseCtl,string path) - { - var basect=new TYTDBaseFileReader(await baseCtl.GetLengthAsync(path)); - - basect.baseStrm = await baseCtl.OpenReadAsync(path); - return basect; - } - - public override bool CanRead => baseStrm.CanRead; - - public override bool CanSeek => baseStrm.CanSeek; - - public override bool CanWrite => false; - - public override long Length => len; - - public override long Position { get => baseStrm.Position; set => baseStrm.Position=value; } - - public override void Flush() - { - baseStrm.Flush(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return baseStrm.Read(buffer,offset,count); - } - - public override long Seek(long offset, SeekOrigin origin) - { - return baseStrm.Seek(offset,origin); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return await baseStrm.ReadAsync(buffer,offset,count,cancellationToken); - } - - public override void Close() - { - baseStrm.Close(); - } - } + public abstract class TYTDBase : ITYTDBase { - - - public async IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string playlist) + + public bool PersonalPlaylistExists(string name) { - var ls=JsonConvert.DeserializeObject>(await ReadAllTextAsync($"PersonalPlaylist/{playlist}.json")); + return FileExists($"PersonalPlaylist/{name}.json"); + } + public async IAsyncEnumerable GetPersonalPlaylistContentsAsync(string playlist) + { + if(!PersonalPlaylistExists(playlist)) yield break; + var ls=JsonConvert.DeserializeObject>(await ReadAllTextAsync($"PersonalPlaylist/{playlist}.json")); foreach(var item in ls) { yield return await Task.FromResult(item); @@ -113,14 +57,6 @@ namespace Tesses.YouTubeDownloader } - public virtual async Task GetLengthAsync(string path) - { - if(!await FileExistsAsync(path)) return 0; - using(var f = await OpenReadAsync(path)) - { - return f.Length; - } - } public bool FileExists(string path) { return FileExistsAsync(path).GetAwaiter().GetResult(); @@ -182,17 +118,18 @@ namespace Tesses.YouTubeDownloader } public async Task ReadAllBytesAsync(string path,CancellationToken token=default(CancellationToken)) - { - byte[] data=new byte[await GetLengthAsync(path)]; - using(var strm = await OpenReadAsync(path)) + {using(var strm = await OpenReadAsync(path)) { + byte[] data=new byte[strm.Length]; + await strm.ReadAsync(data,0,data.Length,token); if(token.IsCancellationRequested) { return new byte[0]; } + return data; } - return data; + } public async IAsyncEnumerable GetPlaylistIdsAsync() { @@ -287,10 +224,7 @@ namespace Tesses.YouTubeDownloader yield return e.Current; } } - public async Task OpenReadAsyncWithLength(string path) - { - return await TYTDBaseFileReader.GetStream(this,path); - } + public abstract Task OpenReadAsync(string path); public abstract Task FileExistsAsync(string path); @@ -302,7 +236,57 @@ namespace Tesses.YouTubeDownloader public abstract IAsyncEnumerable EnumerateDirectoriesAsync(string path); } - public static class TYTDManager + + public class ListContentItem + { + public ListContentItem() + { + Id=""; + Resolution=Resolution.PreMuxed; + } + public ListContentItem(VideoId id) + { + Id=id.Value; + Resolution=Resolution.PreMuxed; + } + public ListContentItem(string id) + { + Id=id; + Resolution =Resolution.PreMuxed; + } + public ListContentItem(string id,Resolution resolution) + { + Id=id; + Resolution=resolution; + } + public ListContentItem(VideoId id,Resolution resolution) + { + Id=id.Value; + Resolution=resolution; + } + public string Id {get;set;} + public Resolution Resolution {get;set;} + + public static implicit operator ListContentItem(VideoId id) + { + return new ListContentItem(id.Value, Resolution.PreMuxed); + } + public static implicit operator VideoId?(ListContentItem item) + { + return VideoId.TryParse(item.Id); + } + + public static implicit operator ListContentItem((VideoId Id,Resolution Resolution) item) + { + return new ListContentItem (item.Id,item.Resolution); + } + public static implicit operator (VideoId Id,Resolution Resolution)(ListContentItem item) + { + return (VideoId.Parse(item.Id),item.Resolution); + } + } + + public static class TYTDManager { /// /// Add Video, Playlist, Channel Or Username @@ -355,41 +339,9 @@ namespace Tesses.YouTubeDownloader } } - /// - /// Replace Personal Playlist - /// - /// Name of playlist - /// Videos to set in playlist - /// - public static async Task ReplacePersonalPlaylistAsync(this IWritable writable,string name,IEnumerable<(VideoId Id,Resolution Resolution)> items) - { - List<(string Id,Resolution Resolution)> items0=new List<(string Id, Resolution Resolution)>(); - - items0.AddRange(items.Select<(VideoId Id,Resolution Resolution),(string Id,Resolution Resolution)>((e)=>{ - return (e.Id.Value,e.Resolution); - }) ); - await writable.WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0)); - - } - /// - /// Append to PersonalPlaylist - /// - /// Name of playlist - /// Videos to add in playlist - public static async Task AddToPersonalPlaylistAsync(this IWritable writable, string name, IEnumerable<(VideoId Id, Resolution Resolution)> items) - { - - List<(string Id,Resolution Resolution)> items0=new List<(string Id, Resolution Resolution)>(); - await foreach(var item in writable.GetPersonalPlaylistContentsAsync(name)) - { - items0.Add(item); - } - items0.AddRange(items.Select<(VideoId Id,Resolution Resolution),(string Id,Resolution Resolution)>((e)=>{ - return (e.Id.Value,e.Resolution); - }) ); - await writable.WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0)); - - } + + + internal static void Print(this IProgress prog,string text) { if(prog !=null) @@ -429,11 +381,12 @@ namespace Tesses.YouTubeDownloader if(string.IsNullOrWhiteSpace(path)) return false; bool ret=false; - double len=await src.GetLengthAsync(path); - if(await src.FileExistsAsync(path)) + if(await src.FileExistsAsync(path)) { using(var srcFile = await src.OpenReadAsync(path)) { + double len=srcFile.Length; + ret= await CopyStream(srcFile,destFile,new Progress((e)=>{ if(progress !=null) @@ -546,9 +499,9 @@ namespace Tesses.YouTubeDownloader } public static async Task CopyFileFrom(this IStorage _dest,ITYTDBase _src,string src,string dest,IProgress progress=null,CancellationToken token=default(CancellationToken)) { - double len=await _src.GetLengthAsync(src); + using(var srcFile = await _src.OpenReadAsync(src)) - { + {double len=srcFile.Length; bool deleteFile=false; using(var destFile = await _dest.CreateAsync(dest)) { @@ -738,10 +691,11 @@ namespace Tesses.YouTubeDownloader { return; } - double len=await src.GetLengthAsync(path); + dest.CreateDirectory(resDir); using(var srcFile = await src.OpenReadAsync(path)) { + double len=srcFile.Length; bool deleteFile=false; using(var destFile = await dest.CreateAsync(path)) { @@ -776,11 +730,25 @@ namespace Tesses.YouTubeDownloader } public interface IPersonalPlaylistGet { - public IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name); + IAsyncEnumerable GetPersonalPlaylistContentsAsync(string name); + bool PersonalPlaylistExists(string name); + } + public interface IPersonalPlaylistSet : IPersonalPlaylistGet + { + Task AddToPersonalPlaylistAsync(string name, IEnumerable items); + + Task ReplacePersonalPlaylistAsync(string name,IEnumerable items); + + Task RemoveItemFromPersonalPlaylistAsync(string name,VideoId id); + + Task SetResolutionForItemInPersonalPlaylistAsync(string name,VideoId id,Resolution resolution); + } - public interface IWritable : IPersonalPlaylistGet + public interface IWritable : IPersonalPlaylistGet, IPersonalPlaylistSet { public Task WriteAllTextAsync(string path,string data); } + + } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader/TYTDClient.cs b/Tesses.YouTubeDownloader/TYTDClient.cs index 467f6e1..2dcf0ca 100644 --- a/Tesses.YouTubeDownloader/TYTDClient.cs +++ b/Tesses.YouTubeDownloader/TYTDClient.cs @@ -12,10 +12,186 @@ using YoutubeExplode.Channels; using YoutubeExplode.Playlists; using System.Net.Http; using System.Net; -using Espresso3389.HttpStream; + +using System.Diagnostics.CodeAnalysis; +using YoutubeExplode.Utils.Extensions; +using System.Net.Http.Headers; namespace Tesses.YouTubeDownloader { + +//From YouTubeExplode +internal static class Helpers +{ + public static async ValueTask HeadAsync( + this HttpClient http, + string requestUri, + CancellationToken cancellationToken = default) + { + using var request = new HttpRequestMessage(HttpMethod.Head, requestUri); + return await http.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken + ); + } + public static async ValueTask GetStreamAsync( + this HttpClient http, + string requestUri, + long? from = null, + long? to = null, + bool ensureSuccess = true, + CancellationToken cancellationToken = default) + { + using var request = new HttpRequestMessage(HttpMethod.Get, requestUri); + request.Headers.Range = new RangeHeaderValue(from, to); + + var response = await http.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead, + cancellationToken + ); + + if (ensureSuccess) + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsStreamAsync(); + } + + public static async ValueTask TryGetContentLengthAsync( + this HttpClient http, + string requestUri, + bool ensureSuccess = true, + CancellationToken cancellationToken = default) + { + using var response = await http.HeadAsync(requestUri, cancellationToken); + + if (ensureSuccess) + response.EnsureSuccessStatusCode(); + + return response.Content.Headers.ContentLength; + } +} + + +// Special abstraction that works around YouTube's stream throttling +// and provides seeking support. +// From YouTubeExplode +internal class SegmentedHttpStream : Stream +{ + private readonly HttpClient _http; + private readonly string _url; + private readonly long? _segmentSize; + + private Stream _segmentStream; + private long _actualPosition; + + [ExcludeFromCodeCoverage] + public override bool CanRead => true; + + [ExcludeFromCodeCoverage] + public override bool CanSeek => true; + + [ExcludeFromCodeCoverage] + public override bool CanWrite => false; + + public override long Length { get; } + + public override long Position { get; set; } + + public SegmentedHttpStream(HttpClient http, string url, long length, long? segmentSize) + { + _url = url; + _http = http; + Length = length; + _segmentSize = segmentSize; + } + + private void ResetSegmentStream() + { + _segmentStream?.Dispose(); + _segmentStream = null; + } + + private async ValueTask ResolveSegmentStreamAsync( + CancellationToken cancellationToken = default) + { + if (_segmentStream != null) + return _segmentStream; + + var from = Position; + + var to = _segmentSize != null + ? Position + _segmentSize - 1 + : null; + + var stream = await _http.GetStreamAsync(_url, from, to, true, cancellationToken); + + return _segmentStream = stream; + } + + public async ValueTask PreloadAsync(CancellationToken cancellationToken = default) => + await ResolveSegmentStreamAsync(cancellationToken); + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + while (true) + { + // Check if consumer changed position between reads + if (_actualPosition != Position) + ResetSegmentStream(); + + // Check if finished reading (exit condition) + if (Position >= Length) + return 0; + + var stream = await ResolveSegmentStreamAsync(cancellationToken); + var bytesRead = await stream.ReadAsync(buffer, offset, count, cancellationToken); + _actualPosition = Position += bytesRead; + + if (bytesRead != 0) + return bytesRead; + + // Reached the end of the segment, try to load the next one + ResetSegmentStream(); + } + } + + [ExcludeFromCodeCoverage] + public override int Read(byte[] buffer, int offset, int count) => + ReadAsync(buffer, offset, count).GetAwaiter().GetResult(); + + [ExcludeFromCodeCoverage] + public override long Seek(long offset, SeekOrigin origin) => Position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => Position + offset, + SeekOrigin.End => Length + offset, + _ => throw new ArgumentOutOfRangeException(nameof(origin)) + }; + + [ExcludeFromCodeCoverage] + public override void Flush() => + throw new NotSupportedException(); + + [ExcludeFromCodeCoverage] + public override void SetLength(long value) => + throw new NotSupportedException(); + + [ExcludeFromCodeCoverage] + public override void Write(byte[] buffer, int offset, int count) => + throw new NotSupportedException(); + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + ResetSegmentStream(); + } + } +} public class TYTDClient : TYTDBase,IDownloader { string url; @@ -244,22 +420,84 @@ namespace Tesses.YouTubeDownloader return GetQueueListAsync().GetAwaiter().GetResult(); } + + public async override Task OpenReadAsync(string path) { - + try{ - - HttpStream v=new HttpStream(new Uri($"{url}api/Storage/File/{path}"),new MemoryStream(),true,32 * 1024,null,client); - - return await Task.FromResult(v); + var strmLen= await client.TryGetContentLengthAsync($"{url}api/Storage/File/{path}",true); + SegmentedHttpStream v=new SegmentedHttpStream(client,$"{url}api/Storage/File/{path}",strmLen.GetValueOrDefault(),null); + return v; }catch(Exception ex) { _=ex; } - return await Task.FromResult(Stream.Null); + return Stream.Null; } - + public async Task AddToPersonalPlaylistAsync(string name, IEnumerable items) + { + Dictionary values=new Dictionary + { + { "name", name}, + { "data", JsonConvert.SerializeObject(items.ToArray())} + }; + var content = new FormUrlEncodedContent(values); + var response = await client.PostAsync($"{url}api/v2/AddToList",content); + var resposeStr = await response.Content.ReadAsStringAsync(); + } + + public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable items) + { + //ReplaceList + Dictionary values=new Dictionary + { + { "name", name}, + { "data", JsonConvert.SerializeObject(items.ToArray())} + }; + var content = new FormUrlEncodedContent(values); + var response = await client.PostAsync($"{url}api/v2/ReplaceList",content); + var resposeStr = await response.Content.ReadAsStringAsync(); + } + + public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id) + { + try{ + + + await client.GetStringAsync($"{url}api/v2/DeleteFromList?name={WebUtility.UrlEncode(name)}&v={id.Value}"); + + }catch(Exception ex) + { + _=ex; + } + } + + public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution) + { + try{ + + + await client.GetStringAsync($"{url}api/v2/SetResolutionInList?name={WebUtility.UrlEncode(name)}&v={id.Value}&res={resolution.ToString()}"); + + }catch(Exception ex) + { + _=ex; + } + } + public void DeletePersonalPlaylist(string name) + { + try{ + + + client.GetStringAsync($"{url}api/v2/DeleteList?name={WebUtility.UrlEncode(name)}").GetAwaiter().GetResult(); + + }catch(Exception ex) + { + _=ex; + } + } } } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader/TYTDIDownloaderStorageProxy.cs b/Tesses.YouTubeDownloader/TYTDIDownloaderStorageProxy.cs index 983035d..f8a29d8 100644 --- a/Tesses.YouTubeDownloader/TYTDIDownloaderStorageProxy.cs +++ b/Tesses.YouTubeDownloader/TYTDIDownloaderStorageProxy.cs @@ -18,6 +18,7 @@ namespace Tesses.YouTubeDownloader public IDownloader Downloader {get;set;} private ITYTDBase _base=null; + public ITYTDBase Storage {get {return _base;} set{_base=value; var v = value as IStorage; if(v != null) @@ -32,6 +33,7 @@ namespace Tesses.YouTubeDownloader _storage.VideoFinished -= _EVT_VFIN; _storage.VideoProgress -= _EVT_VPROG; _storage.VideoStarted -= _EVT_VSTAR; + _storage.Error -= _EVT_ERR; } _storage=storage; if(storage != null) @@ -41,6 +43,7 @@ namespace Tesses.YouTubeDownloader _storage.VideoFinished += _EVT_VFIN; _storage.VideoProgress += _EVT_VPROG; _storage.VideoStarted += _EVT_VSTAR; + _storage.Error += _EVT_ERR; } } @@ -64,6 +67,10 @@ namespace Tesses.YouTubeDownloader { Bell?.Invoke(this,evt); } + private void _EVT_ERR(object sender,TYTDErrorEventArgs evt) + { + Error?.Invoke(this,evt); + } IStorage _storage=null; public LegacyConverter Legacy { @@ -125,7 +132,7 @@ namespace Tesses.YouTubeDownloader public event EventHandler VideoProgress; public event EventHandler VideoFinished; - + public event EventHandler Error; public async Task StorageAsStorageAsync(Func callback) { @@ -201,21 +208,25 @@ namespace Tesses.YouTubeDownloader public async Task OpenReadAsync(string path) { + if(Storage ==null) return Stream.Null; return await Storage.OpenReadAsync(path); } public async Task FileExistsAsync(string path) { + if(Storage ==null) return false; return await Storage.FileExistsAsync(path); } public async Task DirectoryExistsAsync(string path) { + if(Storage ==null) return false; return await Storage.DirectoryExistsAsync(path); } public async IAsyncEnumerable EnumerateFilesAsync(string path) { + if(Storage == null) yield break; await foreach(var item in Storage.EnumerateFilesAsync(path)) { yield return item; @@ -224,6 +235,7 @@ namespace Tesses.YouTubeDownloader public async IAsyncEnumerable EnumerateDirectoriesAsync(string path) { + if(Storage == null) yield break; await foreach(var item in Storage.EnumerateDirectoriesAsync(path)) { yield return item; @@ -315,9 +327,9 @@ namespace Tesses.YouTubeDownloader }); } - public async IAsyncEnumerable<(VideoId Id, Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name) + public async IAsyncEnumerable GetPersonalPlaylistContentsAsync(string name) { - IAsyncEnumerable<(VideoId Id,Resolution Resolution)> items=null; + IAsyncEnumerable items=null; StorageAsStorage((e)=>{ items=e.GetPersonalPlaylistContentsAsync(name); @@ -331,31 +343,37 @@ namespace Tesses.YouTubeDownloader public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed) { + if(Downloader != null) await Downloader.AddVideoAsync(id,resolution); } public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed) { + if(Downloader != null) await Downloader.AddPlaylistAsync(id,resolution); } public async Task AddChannelAsync(ChannelId id, Resolution resolution = Resolution.PreMuxed) { + if(Downloader != null) await Downloader.AddChannelAsync(id,resolution); } public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed) { + if(Downloader != null) await Downloader.AddUserAsync(userName,resolution); } public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList() { + if(Downloader == null) return new List<(SavedVideo Video,Resolution Resolution)>(); return Downloader.GetQueueList(); } public SavedVideoProgress GetProgress() { + if(Downloader == null)return new SavedVideoProgress(); return Downloader.GetProgress(); } @@ -414,23 +432,22 @@ namespace Tesses.YouTubeDownloader public async Task<(string Path, bool Delete)> GetRealUrlOrPathAsync(string path) { + if(Storage == null) return ("",false); return await Storage.GetRealUrlOrPathAsync(path); } - public async Task GetLengthAsync(string path) - { - return await Storage.GetLengthAsync(path); - - } + public bool FileExists(string path) { + if(Storage == null) return false; return Storage.FileExists(path); } public async IAsyncEnumerable GetVideoIdsAsync() { + if(Storage == null) yield break; await foreach(var id in Storage.GetVideoIdsAsync()) { yield return await Task.FromResult(id); @@ -439,11 +456,13 @@ namespace Tesses.YouTubeDownloader public async Task GetVideoInfoAsync(VideoId id) { + if(Storage == null) return new SavedVideo(); return await Storage.GetVideoInfoAsync(id); } public async IAsyncEnumerable GetVideosAsync() { + if(Storage ==null) yield break; await foreach(var vid in Storage.GetVideosAsync()) { yield return await Task.FromResult(vid); @@ -452,6 +471,7 @@ namespace Tesses.YouTubeDownloader public async IAsyncEnumerable GetLegacyVideosAsync() { + if(Storage ==null) yield break; await foreach(var item in Storage.GetLegacyVideosAsync()) { yield return await Task.FromResult(item); @@ -460,11 +480,13 @@ namespace Tesses.YouTubeDownloader public async Task GetLegacyVideoInfoAsync(VideoId id) { + if(Storage == null) return new SavedVideoLegacy(); return await Storage.GetLegacyVideoInfoAsync(id); } public async IAsyncEnumerable GetPlaylistsAsync() { + if(Storage ==null) yield break; await foreach(var item in Storage.GetPlaylistsAsync()) { yield return await Task.FromResult(item); @@ -473,11 +495,13 @@ namespace Tesses.YouTubeDownloader public async Task ReadAllBytesAsync(string path, CancellationToken token = default) { + if(Storage ==null) return new byte[0]; return await Storage.ReadAllBytesAsync(path,token); } public async IAsyncEnumerable GetPlaylistIdsAsync() { + if(Storage ==null) yield break; await foreach(var item in Storage.GetPlaylistIdsAsync()) { yield return await Task.FromResult(item); @@ -486,6 +510,7 @@ namespace Tesses.YouTubeDownloader public async IAsyncEnumerable GetChannelIdsAsync() { + if(Storage ==null) yield break; await foreach(var item in Storage.GetChannelIdsAsync()) { yield return await Task.FromResult(item); @@ -494,6 +519,7 @@ namespace Tesses.YouTubeDownloader public async IAsyncEnumerable GetYouTubeExplodeVideoIdsAsync() { + if(Storage ==null) yield break; await foreach(var item in Storage.GetYouTubeExplodeVideoIdsAsync()) { yield return await Task.FromResult(item); @@ -502,11 +528,13 @@ namespace Tesses.YouTubeDownloader public async Task GetChannelInfoAsync(ChannelId id) { + if(Storage ==null) return new SavedChannel(); return await Storage.GetChannelInfoAsync(id); } public async IAsyncEnumerable GetChannelsAsync() { + if(Storage ==null) yield break; await foreach(var item in Storage.GetChannelsAsync()) { yield return await Task.FromResult(item); @@ -515,49 +543,60 @@ namespace Tesses.YouTubeDownloader public bool PlaylistInfoExists(PlaylistId id) { + if(Storage ==null) return false; return Storage.PlaylistInfoExists(id); } public bool VideoInfoExists(VideoId id) { + if(Storage ==null) return false; return Storage.VideoInfoExists(id); } public bool ChannelInfoExists(ChannelId id) { + if(Storage ==null) return false; return Storage.ChannelInfoExists(id); } public async Task GetPlaylistInfoAsync(PlaylistId id) { + if(Storage ==null) return new SavedPlaylist(); return await Storage.GetPlaylistInfoAsync(id); } - public Task ReadAllTextAsync(string file) + public async Task ReadAllTextAsync(string file) { - return Storage.ReadAllTextAsync(file); + if(Storage ==null) return ""; + return await Storage.ReadAllTextAsync(file); } public bool DirectoryExists(string path) { + if(Storage ==null) return false; return Storage.DirectoryExists(path); } public IEnumerable EnumerateFiles(string path) { - return Storage.EnumerateFiles(path); + if(Storage ==null) yield break; + foreach(var item in Storage.EnumerateFiles(path)) + { + yield return item; + } + } public IEnumerable EnumerateDirectories(string path) { - return Storage.EnumerateDirectories(path); - } - - public async Task OpenReadAsyncWithLength(string path) - { - return await Storage.OpenReadAsyncWithLength(path); + if(Storage ==null) yield break; + foreach(var item in Storage.EnumerateDirectories(path)) + { + yield return item; + } } + public IReadOnlyList GetLoadedSubscriptions() { IReadOnlyList subs=new List(); @@ -580,6 +619,47 @@ namespace Tesses.YouTubeDownloader e.StartLoop(token); }); } + + public async Task AddToPersonalPlaylistAsync(string name, IEnumerable items) + { + await StorageAsStorageAsync(async(e)=>{ + await e.AddToPersonalPlaylistAsync(name,items); + }); + } + + public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable items) + { + await StorageAsStorageAsync(async(e)=>{ + await e.ReplacePersonalPlaylistAsync(name,items); + }); + } + + public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id) + { + await StorageAsStorageAsync(async(e)=>{ + await e.RemoveItemFromPersonalPlaylistAsync(name,id); + }); + } + + public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution) + { + await StorageAsStorageAsync(async(e)=>{ + await e.SetResolutionForItemInPersonalPlaylistAsync(name,id,resolution); + }); + } + + public bool PersonalPlaylistExists(string name) + { + if(Storage ==null) return false; + return Storage.PersonalPlaylistExists(name); + } + + public void DeletePersonalPlaylist(string name) + { + StorageAsStorage((e)=>{ + e.DeletePersonalPlaylist(name); + }); + } } public class DownloaderMigration diff --git a/Tesses.YouTubeDownloader/Tesses.YouTubeDownloader.csproj b/Tesses.YouTubeDownloader/Tesses.YouTubeDownloader.csproj index 0cb7b2d..c8cc549 100644 --- a/Tesses.YouTubeDownloader/Tesses.YouTubeDownloader.csproj +++ b/Tesses.YouTubeDownloader/Tesses.YouTubeDownloader.csproj @@ -7,9 +7,9 @@ Tesses.YouTubeDownloader Mike Nolan Tesses - 1.1.4 - 1.1.4 - 1.1.4 + 1.1.5 + 1.1.5 + 1.1.5 A YouTube Downloader LGPL-3.0-only true @@ -18,8 +18,6 @@ - -