From dadd15f7bc5c35ea6b280a39c24cc26ab169554b Mon Sep 17 00:00:00 2001 From: Michael Nolan Date: Sat, 9 Apr 2022 19:18:45 -0500 Subject: [PATCH] Version 1.0.1 --- Tesses.YouTubeDownloader.Server/Class1.cs | 247 +++++++++++++++++- .../Tesses.YouTubeDownloader.Server.csproj | 6 +- Tesses.YouTubeDownloader/BestStreams.cs | 1 + Tesses.YouTubeDownloader/DownloadLoop.cs | 70 +++-- Tesses.YouTubeDownloader/TYTD.cs | 14 +- .../Tesses.YouTubeDownloader.csproj | 6 +- 6 files changed, 307 insertions(+), 37 deletions(-) diff --git a/Tesses.YouTubeDownloader.Server/Class1.cs b/Tesses.YouTubeDownloader.Server/Class1.cs index 7dcba64..a9f6448 100644 --- a/Tesses.YouTubeDownloader.Server/Class1.cs +++ b/Tesses.YouTubeDownloader.Server/Class1.cs @@ -8,6 +8,8 @@ using YoutubeExplode.Videos; using System.Linq; using System.IO; using System.Text; +using YoutubeExplode.Playlists; +using YoutubeExplode.Channels; namespace Tesses.YouTubeDownloader.Server { @@ -94,22 +96,149 @@ namespace Tesses.YouTubeDownloader.Server ); return asAscii; } + public override async Task GetAsync(ServerContext ctx) { string path=ctx.UrlAndQuery; - if(path.StartsWith("/File/")) + /*if(path.StartsWith("/File/NotConverted/")) { - using(var s = await baseCtl.OpenReadAsyncWithLength( WebUtility.UrlDecode(path.Substring(6)))) + // redirect to new + // /File/NotConverted/xxxxxxxxxxx.mp4 + string idmp4=WebUtility.UrlDecode(path.Substring(19)); + if(idmp4.Length == 15) + { + string id=Path.GetFileNameWithoutExtension(idmp4); + string path2 = $"Info/{id}.json"; + + if(!await baseCtl.FileExistsAsync(path2)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } var data= await baseCtl.ReadAllTextAsync(path2); + var data2=JsonConvert.DeserializeObject(data); + var loc= await BestStreams.GetPathResolution(baseCtl,data2,Resolution.PreMuxed); + + if(!await baseCtl.FileExistsAsync(loc)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } + using(var s = await baseCtl.OpenReadAsyncWithLength(loc)) + { + await ctx.SendStreamAsync(s,HeyRed.Mime.MimeTypesMap.GetMimeType(loc)); + } + + } + } + else if(path.StartsWith("/File/Converted/")) + { + // redirect to new + // /File/NotConverted/xxxxxxxxxxx.mp4 + string idmp4=WebUtility.UrlDecode(path.Substring(16)); + if(idmp4.Length == 15) + { + string id=Path.GetFileNameWithoutExtension(idmp4); + string path2 = $"Info/{id}.json"; + + if(!await baseCtl.FileExistsAsync(path2)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } var data= await baseCtl.ReadAllTextAsync(path2); + var data2=JsonConvert.DeserializeObject(data); + var loc= await BestStreams.GetPathResolution(baseCtl,data2,Resolution.Mux); + + if(!await baseCtl.FileExistsAsync(loc)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } + using(var s = await baseCtl.OpenReadAsyncWithLength(loc)) + { + await ctx.SendStreamAsync(s,HeyRed.Mime.MimeTypesMap.GetMimeType(loc)); + } + + } + } + else if(path.StartsWith("/File/Audio/")) + { + // redirect to new + // /File/NotConverted/xxxxxxxxxxx.mp4 + string idmp4=WebUtility.UrlDecode(path.Substring(12)); + if(idmp4.Length == 15) + { + string id=Path.GetFileNameWithoutExtension(idmp4); + string path2 = $"Info/{id}.json"; + + if(!await baseCtl.FileExistsAsync(path2)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } var data= await baseCtl.ReadAllTextAsync(path2); + var data2=JsonConvert.DeserializeObject(data); + var loc= await BestStreams.GetPathResolution(baseCtl,data2,Resolution.AudioOnly); + + if(!await baseCtl.FileExistsAsync(loc)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } + using(var s = await baseCtl.OpenReadAsyncWithLength(loc)) + { + await ctx.SendStreamAsync(s,HeyRed.Mime.MimeTypesMap.GetMimeType(loc)); + } + + } + } + else if(path.StartsWith("/File/Info/")) + { + string idjson=WebUtility.UrlDecode(path.Substring(11)); + string path2 = $"Info/{idjson}"; + if(!await baseCtl.FileExistsAsync(path2)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } + var data= await baseCtl.ReadAllTextAsync(path2); + var data2=JsonConvert.DeserializeObject(data); + await ctx.SendJsonAsync(data2.ToLegacy()); + } + else*/ if(path.StartsWith("/File/")) + { + string file=WebUtility.UrlDecode(path.Substring(6)); + if(!await baseCtl.FileExistsAsync(file)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } + using(var s = await baseCtl.OpenReadAsyncWithLength(file)) { await ctx.SendStreamAsync(s); } - }else if(path.StartsWith("/GetFiles/")) + }/*else if(path.StartsWith("/File-v2/")) + { + string file=WebUtility.UrlDecode(path.Substring(9)); + if(!await baseCtl.FileExistsAsync(file)) + { + await NotFoundServer.ServerNull.GetAsync(ctx); + return; + } + using(var s = await baseCtl.OpenReadAsyncWithLength(file)) + { + await ctx.SendStreamAsync(s); + } + }*/ + else if(path.StartsWith("/GetFiles/")) { await ctx.SendJsonAsync(baseCtl.EnumerateFiles( WebUtility.UrlDecode(path.Substring(10))).ToList()); }else if(path.StartsWith("/GetDirectories/")) { await ctx.SendJsonAsync(baseCtl.EnumerateDirectories( WebUtility.UrlDecode(path.Substring(16))).ToList()); - }else if(path.StartsWith("/FileExists/")) + }else if(path.StartsWith("/FileExists-v2/")) + { + await ctx.SendTextAsync(baseCtl.FileExists(WebUtility.UrlDecode(path.Substring(15))) ? "true" : "false","text/plain"); + } + else if(path.StartsWith("/FileExists/")) { await ctx.SendTextAsync(baseCtl.FileExists(WebUtility.UrlDecode(path.Substring(12))) ? "true" : "false","text/plain"); }else if(path.StartsWith("/DirectoryExists/")) @@ -132,7 +261,7 @@ namespace Tesses.YouTubeDownloader.Server { //Console.WriteLine("F is not null"); - string filename = Path.GetFileName(path0); + 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)) @@ -172,7 +301,7 @@ namespace Tesses.YouTubeDownloader.Server { //Console.WriteLine("F is not null"); - string filename = Path.GetFileName(path0); + 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)) @@ -197,6 +326,10 @@ 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); } @@ -208,7 +341,7 @@ namespace Tesses.YouTubeDownloader.Server { await ctx.SendJsonAsync(Downloader.GetProgress()); } - public async Task AddItem(ServerContext ctx) + public async Task AddVideo(ServerContext ctx) { string id; if(ctx.QueryParams.TryGetFirst("v",out id)) @@ -225,14 +358,110 @@ namespace Tesses.YouTubeDownloader.Server VideoId? id1=VideoId.TryParse(id); if(id1.HasValue) { - await Downloader.AddItemAsync(id1,resolution); + await Downloader.AddVideoAsync(id1.Value,resolution); } } await ctx.SendTextAsync( $"

You Will Be Redirected in 5 Sec

\n" ); } - + public async Task AddItem(ServerContext ctx) + { + 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; + } + } + + await Downloader.AddItemAsync(id,resolution); + + } + await ctx.SendTextAsync( + $"

You Will Be Redirected in 5 Sec

\n" + ); + } + public async Task AddUser(ServerContext ctx) + { + 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; + } + } + UserName? id1=UserName.TryParse(id); + if(id1.HasValue) + { + await Downloader.AddUserAsync(id1.Value,resolution); + } + } + await ctx.SendTextAsync( + $"

You Will Be Redirected in 5 Sec

\n" + ); + } + + public async Task AddChannel(ServerContext ctx) + { + 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; + } + } + ChannelId? id1=ChannelId.TryParse(id); + if(id1.HasValue) + { + await Downloader.AddChannelAsync(id1.Value,resolution); + } + } + await ctx.SendTextAsync( + $"

You Will Be Redirected in 5 Sec

\n" + ); + } + + public async Task AddPlaylist(ServerContext ctx) + { + 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; + } + } + PlaylistId? id1=PlaylistId.TryParse(id); + if(id1.HasValue) + { + await Downloader.AddPlaylistAsync(id1.Value,resolution); + } + } + await ctx.SendTextAsync( + $"

You Will Be Redirected in 5 Sec

\n" + ); + } + } public class TYTDServer { diff --git a/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj b/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj index 6ae38c0..5e85ca5 100644 --- a/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj +++ b/Tesses.YouTubeDownloader.Server/Tesses.YouTubeDownloader.Server.csproj @@ -15,9 +15,9 @@ Tesses.YouTubeDownloader.Server Mike Nolan Tesses - 1.0.0.0 - 1.0.0.0 - 1.0.0.0 + 1.0.1.0 + 1.0.1.0 + 1.0.1.0 Adds WebServer to TYTD LGPL-3.0-only true diff --git a/Tesses.YouTubeDownloader/BestStreams.cs b/Tesses.YouTubeDownloader/BestStreams.cs index 8aa48f3..b281cba 100644 --- a/Tesses.YouTubeDownloader/BestStreams.cs +++ b/Tesses.YouTubeDownloader/BestStreams.cs @@ -241,6 +241,7 @@ namespace Tesses.YouTubeDownloader audioInfo.AudioCodec=asi.AudioCodec; AudioInfo = audioInfo; } + _si=info; //vsi.VideoCodec } } diff --git a/Tesses.YouTubeDownloader/DownloadLoop.cs b/Tesses.YouTubeDownloader/DownloadLoop.cs index 7d26f2a..729b905 100644 --- a/Tesses.YouTubeDownloader/DownloadLoop.cs +++ b/Tesses.YouTubeDownloader/DownloadLoop.cs @@ -26,7 +26,7 @@ namespace Tesses.YouTubeDownloader var (Video, Resolution) = Dequeue(out hasAny); if (hasAny) { - await DownloadVideoAsync(Video, Resolution, token); + await DownloadVideoAsync(Video, Resolution, token,new Progress(ReportProgress),true); } } @@ -75,21 +75,41 @@ namespace Tesses.YouTubeDownloader } } } - private async Task DownloadVideoAsync(SavedVideo video, Resolution resolution, CancellationToken token) + public async Task DownloadNoQueue(SavedVideo info,Resolution resolution=Resolution.Mux,CancellationToken token=default(CancellationToken),IProgress progress=null) + { + + await DownloadVideoAsync(info,resolution,token,progress,false); + + } + + public async Task GetSavedVideoAsync(VideoId id) + { + VideoMediaContext context=new VideoMediaContext(id,Resolution.PreMuxed); + List<(SavedVideo Video,Resolution)> s=new List<(SavedVideo Video, Resolution)>(); + await context.FillQueue(this,s); + if(s.Count> 0) + { + return s.First().Video; + } + return null; + } + + + private async Task DownloadVideoAsync(SavedVideo video, Resolution resolution, CancellationToken token=default(CancellationToken),IProgress progress=null,bool report=true) { switch (resolution) { case Resolution.Mux: - await DownloadVideoMuxedAsync(video,token); + await DownloadVideoMuxedAsync(video,token,progress,report); break; case Resolution.PreMuxed: - await DownloadPreMuxedVideoAsync(video, token); + await DownloadPreMuxedVideoAsync(video, token,progress,report); break; case Resolution.AudioOnly: - await DownloadAudioOnlyAsync(video,token); + await DownloadAudioOnlyAsync(video,token,progress,report); break; case Resolution.VideoOnly: - await DownloadVideoOnlyAsync(video,token); + await DownloadVideoOnlyAsync(video,token,progress,report); break; } @@ -349,15 +369,15 @@ namespace Tesses.YouTubeDownloader Directory.Delete("TYTD_TEMP",true); return ret; } - private async Task DownloadVideoMuxedAsync(SavedVideo video,CancellationToken token) + private async Task DownloadVideoMuxedAsync(SavedVideo video,CancellationToken token,IProgress progress,bool report=true) { bool isValid=true; - isValid=await DownloadVideoOnlyAsync(video,token); + isValid=await DownloadVideoOnlyAsync(video,token,progress,report); if(token.IsCancellationRequested || !isValid) { return; } - isValid = await DownloadAudioOnlyAsync(video,token); + isValid = await DownloadAudioOnlyAsync(video,token,progress,report); if(token.IsCancellationRequested || !isValid) { return; @@ -367,6 +387,7 @@ namespace Tesses.YouTubeDownloader { return; } + if(report) ReportStartVideo(video,Resolution.Mux,0); string complete = $"Muxed/{video.Id}.mkv"; string incomplete = $"Muxed/{video.Id}incomplete.mkv"; @@ -376,12 +397,12 @@ namespace Tesses.YouTubeDownloader if(await Continue(complete)) { - if(await MuxVideosAsync(video,complete_vidonly,complete_audonly,incomplete,new Progress(ReportProgress),token)) + if(await MuxVideosAsync(video,complete_vidonly,complete_audonly,incomplete,progress,token)) { RenameFile(incomplete,complete); } } - + if(report) ReportEndVideo(video,Resolution.Mux); } private void DeleteIfExists(string path) @@ -391,10 +412,12 @@ namespace Tesses.YouTubeDownloader File.Delete(path); } } - public async Task DownloadVideoOnlyAsync(SavedVideo video,CancellationToken token) + public async Task DownloadVideoOnlyAsync(SavedVideo video,CancellationToken token,IProgress progress,bool report=true) { + bool ret=false; - var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false); + var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false); + if(!can_download) return false; if(streams != null) { await MoveLegacyStreams(video,streams); @@ -412,16 +435,18 @@ namespace Tesses.YouTubeDownloader { return false; } + if(report) 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,new Progress(ReportProgress),token); + ret=await CopyStreamAsync(strm,dest,len,streams.VideoOnlyStreamInfo.Size.Bytes,4096,progress,token); } if(ret) { RenameFile(incomplete,complete); + if(report) ReportEndVideo(video, Resolution.VideoOnly); } } @@ -482,10 +507,12 @@ namespace Tesses.YouTubeDownloader } } - public async Task DownloadAudioOnlyAsync(SavedVideo video,CancellationToken token) + private async Task DownloadAudioOnlyAsync(SavedVideo video,CancellationToken token,IProgress progress,bool report=true) { + bool ret=false; var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false); + if(!can_download) return false; if(streams != null) { string complete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}"; @@ -493,7 +520,7 @@ namespace Tesses.YouTubeDownloader await MoveLegacyStreams(video,streams); if(await Continue(complete)) { - + streams = await BestStreamInfo.GetBestStreams(this,video.Id,token); if(streams != null) { @@ -504,16 +531,18 @@ namespace Tesses.YouTubeDownloader { return false; } + if(report) 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,new Progress(ReportProgress),token); + ret=await CopyStreamAsync(strm,dest,len,streams.AudioOnlyStreamInfo.Size.Bytes,4096,progress,token); } if(ret) { RenameFile(incomplete,complete); + if(report) ReportEndVideo(video, Resolution.AudioOnly); } } @@ -527,9 +556,10 @@ namespace Tesses.YouTubeDownloader //We know its resolution return ret; } - private async Task DownloadPreMuxedVideoAsync(SavedVideo video, CancellationToken token) + private async Task DownloadPreMuxedVideoAsync(SavedVideo video, CancellationToken token,IProgress progress,bool report=true) { var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false); + if(!can_download) return; if(streams != null) { await MoveLegacyStreams(video,streams); @@ -549,17 +579,19 @@ namespace Tesses.YouTubeDownloader { return; } + if(report) 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,new Progress(ReportProgress),token); + ret=await CopyStreamAsync(strm,dest,len,streams.MuxedStreamInfo.Size.Bytes,4096,progress,token); } //We know its resolution if(ret) { RenameFile(incomplete,complete); + if(report) ReportEndVideo(video, Resolution.PreMuxed); } } diff --git a/Tesses.YouTubeDownloader/TYTD.cs b/Tesses.YouTubeDownloader/TYTD.cs index 608b561..84b0289 100644 --- a/Tesses.YouTubeDownloader/TYTD.cs +++ b/Tesses.YouTubeDownloader/TYTD.cs @@ -14,6 +14,7 @@ namespace Tesses.YouTubeDownloader { public abstract partial class TYTDStorage : TYTDBase, IWritable, IDownloader { + private static readonly HttpClient _default = new HttpClient(); public abstract Task CreateAsync(string path); public abstract void CreateDirectory(string path); @@ -28,7 +29,7 @@ namespace Tesses.YouTubeDownloader } public TYTDStorage() { - HttpClient=new HttpClient(); + HttpClient=_default; YoutubeClient=new YoutubeClient(HttpClient); ExtensionContext=null; } @@ -40,6 +41,8 @@ namespace Tesses.YouTubeDownloader } } + bool can_download=true; + public bool CanDownload {get {return can_download;} set {can_download=value;}} public abstract void MoveDirectory(string src,string dest); public abstract void DeleteFile(string file); @@ -92,6 +95,7 @@ namespace Tesses.YouTubeDownloader } public async Task DownloadThumbnails(VideoId id) { + if(!can_download) return; string Id=id.Value; string[] res=new string[] {"default.jpg","sddefault.jpg","mqdefault.jpg","hqdefault.jpg","maxresdefault.jpg"}; CreateDirectoryIfNotExist($"Thumbnails/{Id}"); @@ -109,14 +113,18 @@ namespace Tesses.YouTubeDownloader } } } - public void StartLoop(CancellationToken token = default(CancellationToken)) + public void CreateDirectories() { - CreateDirectoryIfNotExist("VideoOnly"); + CreateDirectoryIfNotExist("VideoOnly"); CreateDirectoryIfNotExist("AudioOnly"); CreateDirectoryIfNotExist("Muxed"); CreateDirectoryIfNotExist("PreMuxed"); CreateDirectoryIfNotExist("Info"); CreateDirectoryIfNotExist("Thumbnails"); + } + public void StartLoop(CancellationToken token = default(CancellationToken)) + { + CreateDirectories(); Thread thread0=new Thread(()=>{ DownloadLoop(token).Wait(); }); diff --git a/Tesses.YouTubeDownloader/Tesses.YouTubeDownloader.csproj b/Tesses.YouTubeDownloader/Tesses.YouTubeDownloader.csproj index ab007ec..ca5a7c2 100644 --- a/Tesses.YouTubeDownloader/Tesses.YouTubeDownloader.csproj +++ b/Tesses.YouTubeDownloader/Tesses.YouTubeDownloader.csproj @@ -7,9 +7,9 @@ Tesses.YouTubeDownloader Mike Nolan Tesses - 1.0.0.0 - 1.0.0.0 - 1.0.0.0 + 1.0.1.0 + 1.0.1.0 + 1.0.1.0 A YouTube Downloader LGPL-3.0-only true