Add Email Extra and add support for YouTube Handles and Slugs

This commit is contained in:
Mike Nolan 2023-01-10 11:03:44 -06:00
parent c471162cf0
commit 6a403b7d15
17 changed files with 839 additions and 55 deletions

View File

@ -0,0 +1,298 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using YoutubeExplode.Channels;
using YoutubeExplode.Playlists;
using YoutubeExplode.Videos;
using MailKit.Net.Smtp;
using MimeKit;
using MailKit;
using MailKit.Net.Imap;
using System.IO;
using System.Threading;
namespace Tesses.YouTubeDownloader.MailKit
{
public sealed class SMTPDownloader : IDownloader
{
string fromEmail;
string toEmail;
Func<Task<SmtpClient>> new_client;
public SMTPDownloader(Func<Task<SmtpClient>> client,string fromEmail,string toEmail)
{
this.new_client = client;
this.fromEmail=fromEmail;
this.toEmail=toEmail;
}
public SMTPDownloader(string smtpServer,string fromEmail,string toEmail,string username,string password,int port=587,global::MailKit.Security.SecureSocketOptions options=global::MailKit.Security.SecureSocketOptions.StartTls)
{
this.new_client = async ()=>{
var clt= new SmtpClient();
await clt.ConnectAsync(smtpServer,port,options);
await clt.AuthenticateAsync(username,password);
return clt;
};
this.fromEmail=fromEmail;
this.toEmail = toEmail;
}
public event EventHandler<VideoStartedEventArgs> VideoStarted;
public event EventHandler<VideoProgressEventArgs> VideoProgress;
public event EventHandler<VideoFinishedEventArgs> VideoFinished;
public event EventHandler<TYTDErrorEventArgs> Error;
public event EventHandler<BellEventArgs> Bell;
private async Task AddItemToAsync(string url)
{
using(var clt = await new_client())
{
var messageToSend = new MimeMessage();
messageToSend.From.Add(MailboxAddress.Parse(fromEmail));
messageToSend.To.Add(MailboxAddress.Parse(toEmail));
messageToSend.Body = new TextPart(MimeKit.Text.TextFormat.Plain){Text=url};
messageToSend.Subject="Sent By Tesses.YouTubeDownloader.MailKit";
await clt.SendAsync(messageToSend);
await clt.DisconnectAsync(true);
}
}
public async Task AddChannelAsync(ChannelId id, Resolution resolution = Resolution.PreMuxed)
{
await AddItemToAsync($"https://www.youtube.com/channel/{id.Value}");
}
public async Task AddFileAsync(string url, bool download = true)
{
await Task.CompletedTask;
}
public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed)
{
await AddItemToAsync($"https://www.youtube.com/playlist?list={id.Value}");
}
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
{
await Task.CompletedTask;
}
public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed)
{
await AddItemToAsync($"https://www.youtube.com/user/{userName.Value}");
}
public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed)
{
await AddItemToAsync($"https://www.youtube.com/watch?v={id.Value}");
}
public async Task AddSlugAsync(ChannelSlug slug,Resolution resolution=Resolution.PreMuxed)
{
await AddItemToAsync($"https://www.youtube.com/c/{slug.Value}");
}
public async Task AddHandleAsync(ChannelHandle handle,Resolution resolution =Resolution.PreMuxed)
{
await AddItemToAsync($"https://www.youtube.com/@{handle.Value}");
}
public void CancelDownload(bool restart = false)
{
}
public void DeletePersonalPlaylist(string name)
{
}
public ExtraData GetExtraData()
{
return new ExtraData();
}
public async IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string name)
{
await Task.CompletedTask;
yield break;
}
public SavedVideoProgress GetProgress()
{
return new SavedVideoProgress();
}
public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList()
{
return new List<(SavedVideo Video,Resolution Resolution)>();
}
public async IAsyncEnumerable<Subscription> GetSubscriptionsAsync()
{
await Task.CompletedTask;
yield break;
}
public bool PersonalPlaylistExists(string name)
{
return false;
}
public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
{
await Task.CompletedTask;
}
public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
{
await Task.CompletedTask;
}
public async Task ResubscribeAsync(ChannelId id, ChannelBellInfo info = ChannelBellInfo.NotifyAndDownload)
{
await Task.CompletedTask;
}
public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution)
{
await Task.CompletedTask;
}
public async Task SubscribeAsync(ChannelId id, ChannelBellInfo bellInfo = ChannelBellInfo.NotifyAndDownload)
{
await Task.CompletedTask;
}
public async Task SubscribeAsync(UserName name, ChannelBellInfo info = ChannelBellInfo.NotifyAndDownload)
{
await Task.CompletedTask;
}
public async Task UnsubscribeAsync(ChannelId id)
{
await Task.CompletedTask;
}
}
public sealed class IMAPDownloader
{
IDownloader downloader;
string imapServer;
int imapPort;
bool ssl;
string username;
string password;
string[] folders;
public IMAPDownloader(IDownloader downloader,string imapServer,int imapPort,bool ssl,string username,string password,params string[] folders)
{
this.downloader=downloader;
this.imapServer =imapServer;
this.imapPort = imapPort;
this.ssl = ssl;
this.username = username;
this.password = password;
this.folders = folders;
}
public void Scan()
{
using (var client = new ImapClient())
{
client.Connect(imapServer, imapPort,ssl);
client.Authenticate(username,password);
foreach (var folder_name in folders)
{
var folder = client.GetFolder(folder_name);
folder.Open(FolderAccess.ReadOnly);
foreach(var item in folder)
{
using (var ms = new MemoryStream())
{
item.Body.WriteTo(ms);
ms.Position = 0;
using (var sr = new StreamReader(ms))
{
string line;
while((line=sr.ReadLine()) != null)
{
try
{
Task.Run(async () => await downloader.AddItemAsync(line)).Wait();
}
catch(Exception ex)
{
_ = ex;
}
}
}
}
}
}
client.Disconnect(true);
}
}
public async Task ScanAsync(CancellationToken token=default)
{
using (var client = new ImapClient())
{
await client.ConnectAsync(imapServer, imapPort,ssl,token);
await client.AuthenticateAsync(username,password,token);
foreach (var folder_name in folders)
{
if(token.IsCancellationRequested) break;
var folder = await client.GetFolderAsync(folder_name,token);
await folder.OpenAsync(FolderAccess.ReadOnly,token);
foreach(var item in folder)
{
if(token.IsCancellationRequested) break;
using (var ms = new MemoryStream())
{
item.Body.WriteTo(ms);
ms.Position = 0;
using (var sr = new StreamReader(ms))
{
string line;
while((line=await sr.ReadLineAsync()) != null)
{
if(token.IsCancellationRequested) break;
try
{
await downloader.AddItemAsync(line);
}
catch(Exception ex)
{
_ = ex;
}
}
}
}
}
}
await client.DisconnectAsync(true,token);
}
}
public void ScanLoop(TimeSpan interval,CancellationToken token=default)
{
Thread t = new Thread(async()=>{
try{
while(!token.IsCancellationRequested)
{
await ScanAsync(token);
await Task.Delay(interval.Milliseconds,token);
}
}catch(Exception ex)
{
_=ex;
}
});
t.Start();
}
}
}

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MailKit" Version="3.4.3" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<IsPackable>true</IsPackable>
<PackageId>Tesses.YouTubeDownloader.MailKit</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Description>A YouTube Downloader</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags>
<RepositoryUrl>https://gitlab.tesses.cf/tesses50/tytd</RepositoryUrl>
</PropertyGroup>
</Project>

View File

@ -6,11 +6,14 @@ public class TYTDConfiguration
{ {
Url = "http://127.0.0.1:3252/"; Url = "http://127.0.0.1:3252/";
LocalFiles=Environment.CurrentDirectory; LocalFiles=Environment.CurrentDirectory;
AddComplete = true;
} }
public string Url {get;set;} public string Url {get;set;}
public string LocalFiles {get;set;} public string LocalFiles {get;set;}
public bool AddComplete {get;set;}
public static TYTDConfiguration Load() public static TYTDConfiguration Load()
{ {
if(!File.Exists("proxy.json")) return new TYTDConfiguration(); if(!File.Exists("proxy.json")) return new TYTDConfiguration();

View File

@ -12,6 +12,7 @@ TYTDClient client=new TYTDClient(c,config.Url);
TYTDDownloaderStorageProxy proxy=new TYTDDownloaderStorageProxy(); TYTDDownloaderStorageProxy proxy=new TYTDDownloaderStorageProxy();
proxy.Storage = currentDirectory; proxy.Storage = currentDirectory;
proxy.Downloader=client; proxy.Downloader=client;
proxy.AddIfCompletedInStorage = config.AddComplete;
TYTDServer server=new TYTDServer(proxy); TYTDServer server=new TYTDServer(proxy);
server.RootServer.Server=new StaticServer("WebSite"); server.RootServer.Server=new StaticServer("WebSite");

View File

@ -1,4 +1,6 @@
{ {
"Url": "http://10.137.42.142:3252/", "Url": "http://192.168.0.142:3252/",
"LocalFiles": "/media/mike/PhotoDrive/wii-vids/working" "LocalFiles": "/media/mike/BackupPlus4/Videos/TYTD_working",
"AddComplete": false,
"AddPlaylistsToStorage": true
} }

View File

@ -405,7 +405,20 @@ internal static class B64
await ctx.SendJsonAsync(data2.ToLegacy()); await ctx.SendJsonAsync(data2.ToLegacy());
} }
else*/ else*/
if(path.StartsWith("/File/FileInfo/")) if(path.StartsWith("/File/DownloadsInfo/"))
{
string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/File/DownloadsInfo/")));
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/FileInfo/"))
{ {
string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/File/FileInfo/"))); string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/File/FileInfo/")));
string url = B64.Base64UrlDecodes(file); string url = B64.Base64UrlDecodes(file);
@ -493,6 +506,15 @@ internal static class B64
await ctx.SendStreamAsync(s); await ctx.SendStreamAsync(s);
} }
}*/ }*/
if(path.StartsWith("/GetFiles/DownloadsInfo") || path.StartsWith("/GetFiles/DownloadsInfo/"))
{
List<string> urls=new List<string>();
await foreach(var url in baseCtl.GetDownloadUrlsAsync())
{
urls.Add($"{TYTDBase.HashDownloadUrl(url)}.json");
}
await ctx.SendJsonAsync(urls);
}
else if(path.StartsWith("/GetFiles/FileInfo") || path.StartsWith("/GetFiles/FileInfo/")) else if(path.StartsWith("/GetFiles/FileInfo") || path.StartsWith("/GetFiles/FileInfo/"))
{ {
List<string> urls=new List<string>(); List<string> urls=new List<string>();
@ -552,9 +574,23 @@ internal static class B64
await ctx.SendTextAsync( "false","text/plain"); await ctx.SendTextAsync( "false","text/plain");
} }
} }
else if(path.StartsWith("/FileExists/DownloadsInfo/"))
{
string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/FileExists/DownloadsInfo/")));
await foreach(var item in baseCtl.GetDownloadUrlsAsync())
{
if(TYTDBase.HashDownloadUrl(item) == file)
{
await ctx.SendTextAsync( "true","text/plain");
return;
}
}
await ctx.SendTextAsync( "false","text/plain");
}
else if(path.StartsWith("/FileExists/FileInfo/")) else if(path.StartsWith("/FileExists/FileInfo/"))
{ {
string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/FileExists/StreamInfo/"))); string file = Path.GetFileNameWithoutExtension(WebUtility.UrlDecode(path.Substring("/FileExists/FileInfo/")));
string url = B64.Base64Decodes(file); string url = B64.Base64Decodes(file);
if(baseCtl.DownloadExists(url)) if(baseCtl.DownloadExists(url))
@ -861,6 +897,9 @@ internal static class B64
AddBoth("/AddPlaylist",AddPlaylist,new SwagmeDocumentation("/AddPlaylist?id=PLgXAgLm6Kre7M3c8G2OlQTG-PETLHs4Vd&res=PreMuxed","Add youtube playlist","id: Playlist Id Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items"); AddBoth("/AddPlaylist",AddPlaylist,new SwagmeDocumentation("/AddPlaylist?id=PLgXAgLm6Kre7M3c8G2OlQTG-PETLHs4Vd&res=PreMuxed","Add youtube playlist","id: Playlist Id Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items");
AddBoth("/AddChannel",AddChannel,new SwagmeDocumentation("/AddChannel?id=UC4QobU6STFB0P71PMvOGN5A&res=PreMuxed","Add youtube channel","id: YouTube Channel Id Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items"); AddBoth("/AddChannel",AddChannel,new SwagmeDocumentation("/AddChannel?id=UC4QobU6STFB0P71PMvOGN5A&res=PreMuxed","Add youtube channel","id: YouTube Channel Id Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items");
AddBoth("/AddUser",AddUser,new SwagmeDocumentation("/AddUser?id=jawed&res=PreMuxed","Add youtube user","id: YouTube Channel Name Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items"); AddBoth("/AddUser",AddUser,new SwagmeDocumentation("/AddUser?id=jawed&res=PreMuxed","Add youtube user","id: YouTube Channel Name Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items");
AddBoth("/AddSlug",AddSlug,new SwagmeDocumentation("/AddSlug?id=https%3A%2F%2Fwww.youtube.com%2Fc%2FLinusTechTips&res=PreMuxed","Add youtube channel by slug","id: YouTube Channel with slug Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items");
AddBoth("/AddHandle",AddHandle,new SwagmeDocumentation("/AddHandle?id=@tesses50&res=PreMuxed","Add youtube channel by handle","id: YouTube Channel with handle Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items");
AddBoth("/AddItem",AddItem,new SwagmeDocumentation("/AddItem?v=jNQXAC9IVRw&res=PreMuxed","Add any type of item","v: Media Id Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items"); AddBoth("/AddItem",AddItem,new SwagmeDocumentation("/AddItem?v=jNQXAC9IVRw&res=PreMuxed","Add any type of item","v: Media Id Or encodeUriComponent Url<br>res: NoDownload=Do not download clip but just info, Mux=Requires conversion, PreMuxed=Does not require conversion, AudioOnly=Best audio only, VideoOnly=Best video only"),"Adding items");
AddBoth("/AddFile",AddFile,new SwagmeDocumentation("/AddFile?url=https%3A%2F%2Ftesses.cf%2Fimages%2Frvl.jpg&download=true","Add normal file download","url: Url to file<br>download: whether to download file"),"Adding items"); AddBoth("/AddFile",AddFile,new SwagmeDocumentation("/AddFile?url=https%3A%2F%2Ftesses.cf%2Fimages%2Frvl.jpg&download=true","Add normal file download","url: Url to file<br>download: whether to download file"),"Adding items");
/*Getting status*/ /*Getting status*/
@ -901,7 +940,7 @@ internal static class B64
AddBoth("/extra_data.json",ExtraData,new SwagmeDocumentation("Get extra info about downloader","Get extra data such as TYTD Tag, item count in queue and tytd version")); AddBoth("/extra_data.json",ExtraData,new SwagmeDocumentation("Get extra info about downloader","Get extra data such as TYTD Tag, item count in queue and tytd version"));
AddBoth("/CancelDownload",Cancel,new SwagmeDocumentation("/CancelDownload?restart=true","Cancel or Restart download","restart:<br>&emsp;true: Restart download<br>&emsp;false: Cancel Download")); AddBoth("/CancelDownload",Cancel,new SwagmeDocumentation("/CancelDownload?restart=true","Cancel or Restart download","restart:<br>&emsp;true: Restart download<br>&emsp;false: Cancel Download"));
AddBoth("/Thumbnail",Thumbnail,new SwagmeDocumentation("/Thumbnail?v=PzUKeGZiEl0&res=maxresdefault","Get Thumbnail for video","v: Video Id<br>res: YouTube Thumbnail Resolution <a href=\"https://www.binarymoon.co.uk/2014/03/using-youtube-thumbnails/\">List of resolutions</a>"),"Other");
/* /*
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<(VideoId Id, Resolution Resolution)> items) public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<(VideoId Id, Resolution Resolution)> items)
{ {
@ -922,6 +961,20 @@ internal static class B64
{ {
throw new NotImplementedException(); throw new NotImplementedException();
}*/ }*/
}
public async Task Thumbnail(ServerContext ctx)
{
string v="";
string res="";
VideoId? id=null;
ITYTDBase baseCtl = Downloader as ITYTDBase;
if(ctx.QueryParams.TryGetFirst("v",out v) && ctx.QueryParams.TryGetFirst("res",out res) && (id=VideoId.TryParse(v)).HasValue && baseCtl != null)
{
await ctx.SendBytesAsync(await baseCtl.ReadThumbnailAsync(id.Value,res),"image/jpg");
}else{
await ctx.SendTextAsync("Expected v=YouTubeVideoId\r\nres=a youtube thumbnail resolution\r\nAnd Downloader must implement ITYTDBase","text/plain");
}
} }
public async Task ExtraData(ServerContext ctx) public async Task ExtraData(ServerContext ctx)
{ {
@ -1496,7 +1549,51 @@ internal static class B64
} }
await ctx.RedirectBackAsync(); await ctx.RedirectBackAsync();
} }
public async Task AddHandle(ServerContext ctx)
{
string id;
if(ctx.QueryParams.TryGetFirst("id",out id))
{
Resolution resolution=Resolution.PreMuxed;
string res;
if(ctx.QueryParams.TryGetFirst("res",out res))
{
if(!Enum.TryParse<Resolution>(res,out resolution))
{
resolution=Resolution.PreMuxed;
}
}
ChannelHandle? id1=ChannelHandle.TryParse(id);
if(id1.HasValue)
{
await Downloader.AddHandleAsync(id1.Value,resolution);
}
}
await ctx.RedirectBackAsync();
}
public async Task AddSlug(ServerContext ctx)
{
string id;
if(ctx.QueryParams.TryGetFirst("id",out id))
{
Resolution resolution=Resolution.PreMuxed;
string res;
if(ctx.QueryParams.TryGetFirst("res",out res))
{
if(!Enum.TryParse<Resolution>(res,out resolution))
{
resolution=Resolution.PreMuxed;
}
}
ChannelSlug? id1=ChannelSlug.TryParse(id);
if(id1.HasValue)
{
await Downloader.AddSlugAsync(id1.Value,resolution);
}
}
await ctx.RedirectBackAsync();
}
public async Task AddChannel(ServerContext ctx) public async Task AddChannel(ServerContext ctx)
{ {
string id; string id;

View File

@ -16,9 +16,9 @@
<PackageId>Tesses.YouTubeDownloader.Server</PackageId> <PackageId>Tesses.YouTubeDownloader.Server</PackageId>
<Author>Mike Nolan</Author> <Author>Mike Nolan</Author>
<Company>Tesses</Company> <Company>Tesses</Company>
<Version>1.1.8</Version> <Version>2.0.1</Version>
<AssemblyVersion>1.1.8</AssemblyVersion> <AssemblyVersion>2.0.1</AssemblyVersion>
<FileVersion>1.1.8</FileVersion> <FileVersion>2.0.1</FileVersion>
<Description>Adds WebServer to TYTD</Description> <Description>Adds WebServer to TYTD</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>

View File

@ -184,8 +184,8 @@ namespace Tesses.YouTubeDownloader
private async Task DownloadFileAsync(SavedVideo video, CancellationToken token, IProgress<double> progress, bool report) private async Task DownloadFileAsync(SavedVideo video, CancellationToken token, IProgress<double> progress, bool report)
{ {
string incomplete_file_path = $"Download/{B64.Base64UrlEncodes(video.Id)}-incomplete.part"; string incomplete_file_path = $"Downloads/{HashDownloadUrl(video.Id)}-incomplete.part";
string file_path = $"Download/{B64.Base64UrlEncodes(video.Id)}.bin"; string file_path = $"Downloads/{HashDownloadUrl(video.Id)}.bin";
string url = video.Id; string url = video.Id;
bool canSeek=false; bool canSeek=false;
long length=0; long length=0;

View File

@ -23,10 +23,12 @@ namespace Tesses.YouTubeDownloader
event EventHandler<BellEventArgs> Bell; event EventHandler<BellEventArgs> Bell;
void CancelDownload(bool restart=false); void CancelDownload(bool restart=false);
Task AddVideoAsync(VideoId id,Resolution resolution=Resolution.PreMuxed); Task AddVideoAsync(VideoId id,Resolution resolution=Resolution.PreMuxed);
Task AddPlaylistAsync(PlaylistId id,Resolution resolution=Resolution.PreMuxed); Task AddPlaylistAsync(PlaylistId id,Resolution resolution=Resolution.PreMuxed);
Task AddChannelAsync(ChannelId id,Resolution resolution=Resolution.PreMuxed); Task AddChannelAsync(ChannelId id,Resolution resolution=Resolution.PreMuxed);
Task AddHandleAsync(ChannelHandle handle,Resolution resolution=Resolution.PreMuxed);
Task AddSlugAsync(ChannelSlug handle,Resolution resolution=Resolution.PreMuxed);
Task AddUserAsync(UserName userName,Resolution resolution=Resolution.PreMuxed); Task AddUserAsync(UserName userName,Resolution resolution=Resolution.PreMuxed);
Task AddFileAsync(string url,bool download=true); Task AddFileAsync(string url,bool download=true);
IReadOnlyList<(SavedVideo Video,Resolution Resolution)> GetQueueList(); IReadOnlyList<(SavedVideo Video,Resolution Resolution)> GetQueueList();

View File

@ -15,6 +15,7 @@ namespace Tesses.YouTubeDownloader
{ {
public interface IStorage : IWritable, IDownloader, ITYTDBase public interface IStorage : IWritable, IDownloader, ITYTDBase
{ {
Task WriteThumbnailAsync(VideoId videoId,string res,byte[] data,CancellationToken token=default);
void WaitTillMediaContentQueueEmpty(); void WaitTillMediaContentQueueEmpty();
Task WriteBestStreamInfoAsync(VideoId id,BestStreamInfo.BestStreamsSerialized serialized); Task WriteBestStreamInfoAsync(VideoId id,BestStreamInfo.BestStreamsSerialized serialized);

View File

@ -16,6 +16,9 @@ namespace Tesses.YouTubeDownloader
public interface ITYTDBase : IPersonalPlaylistGet public interface ITYTDBase : IPersonalPlaylistGet
{ {
Task<bool> ThumbnailExistsAsync(VideoId videoId,string res);
bool ThumbnailExists(VideoId videoId,string res);
Task<byte[]> ReadThumbnailAsync(VideoId videoId,string res,CancellationToken token=default);
IAsyncEnumerable<string> GetDownloadUrlsAsync(); IAsyncEnumerable<string> GetDownloadUrlsAsync();
IAsyncEnumerable<SavedVideo> GetDownloadsAsync(); IAsyncEnumerable<SavedVideo> GetDownloadsAsync();
Task<SavedVideo> GetDownloadInfoAsync(string url); Task<SavedVideo> GetDownloadInfoAsync(string url);

View File

@ -18,6 +18,16 @@ namespace Tesses.YouTubeDownloader
internal class ChannelMediaContext : IMediaContext internal class ChannelMediaContext : IMediaContext
{ {
public ChannelMediaContext(ChannelSlug slug,Resolution resolution)
{
this.slug=slug;
this.Resolution = resolution;
}
public ChannelMediaContext(ChannelHandle handle,Resolution resolution)
{
this.handle=handle;
this.Resolution = resolution;
}
public ChannelMediaContext(ChannelId id,Resolution resolution) public ChannelMediaContext(ChannelId id,Resolution resolution)
{ {
Id=id; Id=id;
@ -29,9 +39,12 @@ namespace Tesses.YouTubeDownloader
Resolution=resolution; Resolution=resolution;
} }
Resolution Resolution; Resolution Resolution;
UserName name1; UserName? name1;
ChannelId? Id; //made me nullable ChannelId? Id; //made me nullable
ChannelHandle handle;
ChannelSlug? slug;
public async Task<SavedChannel> GetChannel(TYTDStorage storage) public async Task<SavedChannel> GetChannel(TYTDStorage storage)
{ {
SavedChannel channel; SavedChannel channel;
@ -54,8 +67,8 @@ namespace Tesses.YouTubeDownloader
var j=await storage.GetChannelInfoAsync(Id.Value); var j=await storage.GetChannelInfoAsync(Id.Value);
return j; return j;
} }
}else{ }else if(name1.HasValue){
var c=await storage.YoutubeClient.Channels.GetByUserAsync(name1); var c=await storage.YoutubeClient.Channels.GetByUserAsync(name1.Value);
channel=await DownloadThumbnails(storage,c); channel=await DownloadThumbnails(storage,c);
//string path=$"Channel/{c.Id.Value}.json"; //string path=$"Channel/{c.Id.Value}.json";
if(!storage.ChannelInfoExists(c.Id.Value)) if(!storage.ChannelInfoExists(c.Id.Value))
@ -64,6 +77,24 @@ namespace Tesses.YouTubeDownloader
} }
return channel; return channel;
}else if(slug.HasValue)
{
var c = await storage.YoutubeClient.Channels.GetBySlugAsync(slug.Value);
channel = await DownloadThumbnails(storage,c);
if(!storage.ChannelInfoExists(c.Id.Value))
{
await storage.WriteChannelInfoAsync(channel);
}
return channel;
}
else{
var c = await storage.YoutubeClient.Channels.GetByHandleAsync(handle);
channel = await DownloadThumbnails(storage,c);
if(!storage.ChannelInfoExists(c.Id.Value))
{
await storage.WriteChannelInfoAsync(channel);
}
return channel;
} }
} }
private async Task<SavedChannel> DownloadThumbnails(TYTDStorage storage,YoutubeExplode.Channels.Channel channel) private async Task<SavedChannel> DownloadThumbnails(TYTDStorage storage,YoutubeExplode.Channels.Channel channel)

View File

@ -17,6 +17,23 @@ namespace Tesses.YouTubeDownloader
public abstract partial class TYTDStorage : TYTDBase, IStorage public abstract partial class TYTDStorage : TYTDBase, IStorage
{ {
public new virtual async Task<byte[]> ReadThumbnailAsync(VideoId videoId,string res,CancellationToken token=default)
{
CreateDirectoryIfNotExist($"Thumbnails/{videoId.Value}");
if(await ThumbnailExistsAsync(videoId,res))
{
return await ReadAllBytesAsync($"Thumbnails/{videoId.Value}/{res}.jpg",token);
}else{
var result= await HttpClient.GetByteArrayAsync($"https://s.ytimg.com/vi/{videoId.Value}/{res}.jpg");
await WriteThumbnailAsync(videoId,res,result,token);
return result;
}
}
public virtual async Task WriteThumbnailAsync(VideoId videoId,string res,byte[] data,CancellationToken token=default)
{
CreateDirectoryIfNotExist($"Thumbnails/{videoId.Value}");
await WriteAllBytesAsync($"Thumbnails/{videoId.Value}/{res}.jpg",data,token);
}
public override ExtraData GetExtraData() public override ExtraData GetExtraData()
{ {
ExtraData data=new ExtraData(); ExtraData data=new ExtraData();
@ -110,7 +127,7 @@ namespace Tesses.YouTubeDownloader
public virtual async Task WriteVideoInfoAsync(SavedVideo info) public virtual async Task WriteVideoInfoAsync(SavedVideo info)
{ {
string file = info.DownloadFrom.StartsWith("NormalDownload,Length=") ? $"FileInfo/{B64.Base64UrlEncodes(info.Id)}.json" : $"Info/{info.Id}.json"; string file = info.DownloadFrom.StartsWith("NormalDownload,Length=") ? $"DownloadsInfo/{HashDownloadUrl(info.Id)}.json" : $"Info/{info.Id}.json";
if(!FileExists(file)) if(!FileExists(file))
{ {
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info)); await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
@ -150,6 +167,22 @@ namespace Tesses.YouTubeDownloader
} }
await Task.FromResult(0); await Task.FromResult(0);
} }
public async Task AddSlugAsync(ChannelSlug slug,Resolution resolution=Resolution.PreMuxed)
{
lock(Temporary)
{
Temporary.Add(new ChannelMediaContext(slug,resolution));
}
await Task.FromResult(0);
}
public async Task AddHandleAsync(ChannelHandle handle,Resolution resolution=Resolution.PreMuxed)
{
lock(Temporary)
{
Temporary.Add(new ChannelMediaContext(handle,resolution));
}
await Task.FromResult(0);
}
public async Task AddUserAsync(UserName name,Resolution resolution=Resolution.PreMuxed) public async Task AddUserAsync(UserName name,Resolution resolution=Resolution.PreMuxed)
{ {
lock(Temporary) lock(Temporary)
@ -215,14 +248,16 @@ namespace Tesses.YouTubeDownloader
CreateDirectoryIfNotExist("Thumbnails"); CreateDirectoryIfNotExist("Thumbnails");
CreateDirectoryIfNotExist("config"); CreateDirectoryIfNotExist("config");
CreateDirectoryIfNotExist("config/logs"); CreateDirectoryIfNotExist("config/logs");
CreateDirectoryIfNotExist("FileInfo"); CreateDirectoryIfNotExist("DownloadsInfo");
CreateDirectoryIfNotExist("Download"); CreateDirectoryIfNotExist("Downloads");
CreateDirectoryIfNotExist("StreamInfo"); CreateDirectoryIfNotExist("StreamInfo");
CreateDirectoryIfNotExist("PersonalPlaylist"); CreateDirectoryIfNotExist("PersonalPlaylist");
} }
public void StartLoop(CancellationToken token = default(CancellationToken)) public void StartLoop(CancellationToken token = default(CancellationToken))
{ {
CreateDirectories(); CreateDirectories();
if(DirectoryExists("Download") && DirectoryExists("FileInfo"))
Task.Run(MigrateDownloads).Wait();
Thread thread0=new Thread(()=>{ Thread thread0=new Thread(()=>{
DownloadLoop(token).Wait(); DownloadLoop(token).Wait();
}); });
@ -232,6 +267,113 @@ namespace Tesses.YouTubeDownloader
}); });
thread1.Start(); thread1.Start();
} }
private async IAsyncEnumerable<SavedVideo> GetDownloadsLegacyAsync()
{
await foreach(var item in EnumerateFilesAsync("FileInfo"))
{
if(Path.GetExtension(item).Equals(".json",StringComparison.Ordinal))
{
var res= JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync(item));
DeleteFile(item);
yield return res;
}
}
}
private async Task MigrateDownloads()
{
await GetLogger().WriteAsync("Migrating File Downloads (Please Don't close TYTD)",true);
await foreach(var dl in GetDownloadsLegacyAsync())
{
await MoveLegacyDownload(dl);
}
int files=0;
await foreach(var f in EnumerateDirectoriesAsync("FileInfo"))
{
files++;
break;
}
if(files==0)
{
await foreach(var f in EnumerateFilesAsync("FileInfo"))
{
files++;
break;
}
}
if(files>0)
{
MoveDirectory("FileInfo","DownloadsInfoLegacy");
await GetLogger().WriteAsync("WARNING: there were still files/folders in FileInfo so they are stored in DownloadsInfoLegacy",true);
}else{
DeleteDirectory("FileInfo");
}
files=0;
await foreach(var f in EnumerateDirectoriesAsync("Download"))
{
files++;
break;
}
if(files==0)
{
await foreach(var f in EnumerateFilesAsync("Download"))
{
files++;
break;
}
}
if(files>0)
{
MoveDirectory("Download","DownloadsLegacy");
await GetLogger().WriteAsync("WARNING: there were still files/folders in Download so they are stored in DownloadsLegacy",true);
}else{
DeleteDirectory("Download");
}
await GetLogger().WriteAsync("Migrating Downloads Complete",true);
}
private async Task MoveLegacyDownload(SavedVideo dl)
{
await WriteVideoInfoAsync(dl);
string old_incomplete_file_path = $"Download/{B64.Base64UrlEncodes(dl.Id)}-incomplete.part";
string old_file_path = $"Download/{B64.Base64UrlEncodes(dl.Id)}.bin";
string incomplete_file_path = $"Downloads/{HashDownloadUrl(dl.Id)}-incomplete.part";
string file_path = $"Downloads/{HashDownloadUrl(dl.Id)}.bin";
bool complete = FileExists(old_file_path);
bool missing = !complete && !FileExists(old_incomplete_file_path);
string alreadyStr ="";
if(!missing)
{
if(complete)
{
//migrate complete
if(!FileExists(file_path))
{
RenameFile(old_file_path,file_path);
}else{
alreadyStr="Already ";
}
}
else
{
//migrate incomplete
if(!FileExists(incomplete_file_path))
{
RenameFile(old_incomplete_file_path,incomplete_file_path);
}else{
alreadyStr="Already ";
}
}
}
await GetLogger().WriteAsync($"{alreadyStr}Migrated {(missing? "missing" : (complete ? "complete" : "incomplete"))} download: {dl.Title} with Url: {dl.Id}",true);
}
internal void ThrowError(TYTDErrorEventArgs e) internal void ThrowError(TYTDErrorEventArgs e)
{ {
Error?.Invoke(this,e); Error?.Invoke(this,e);

View File

@ -12,13 +12,30 @@ using System.IO;
using YoutubeExplode.Playlists; using YoutubeExplode.Playlists;
using YoutubeExplode.Channels; using YoutubeExplode.Channels;
using YoutubeExplode.Search; using YoutubeExplode.Search;
using System.Security.Cryptography;
using System.Text;
namespace Tesses.YouTubeDownloader namespace Tesses.YouTubeDownloader
{ {
public abstract class TYTDBase : ITYTDBase public abstract class TYTDBase : ITYTDBase
{ {
public virtual async Task<bool> ThumbnailExistsAsync(VideoId videoId,string res)
{
return await FileExistsAsync($"Thumbnails/{videoId.Value}/{res}.jpg");
}
public virtual bool ThumbnailExists(VideoId videoId,string res)
{
return FileExists($"Thumbnails/{videoId.Value}/{res}.jpg");
}
public virtual async Task<byte[]> ReadThumbnailAsync(VideoId videoId,string res,CancellationToken token=default)
{
if(await ThumbnailExistsAsync(videoId,res))
{
return await ReadAllBytesAsync($"Thumbnails/{videoId.Value}/{res}.jpg",token);
}
return new byte[0];
}
public virtual bool PersonalPlaylistExists(string name) public virtual bool PersonalPlaylistExists(string name)
{ {
return FileExists($"PersonalPlaylist/{name}.json"); return FileExists($"PersonalPlaylist/{name}.json");
@ -179,21 +196,23 @@ namespace Tesses.YouTubeDownloader
public virtual async IAsyncEnumerable<SavedVideo> GetDownloadsAsync() public virtual async IAsyncEnumerable<SavedVideo> GetDownloadsAsync()
{ {
await foreach(var item in GetDownloadUrlsAsync()) await foreach(var item in EnumerateFilesAsync("DownloadsInfo"))
{ {
yield return await GetDownloadInfoAsync(item); if(Path.GetExtension(item).Equals(".json",StringComparison.Ordinal))
{
yield return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync(item));
}
} }
} }
public virtual async IAsyncEnumerable<string> GetDownloadUrlsAsync() public virtual async IAsyncEnumerable<string> GetDownloadUrlsAsync()
{ {
await foreach(var item in EnumerateFilesAsync("FileInfo")) await foreach(var item in GetDownloadsAsync())
{ {
if(Path.GetExtension(item).Equals(".json",StringComparison.Ordinal)) yield return item.Id;
{
yield return B64.Base64UrlDecodes(Path.GetFileNameWithoutExtension(item));
}
} }
} }
public virtual async Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id) public virtual async Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id)
{ {
return JsonConvert.DeserializeObject<BestStreamInfo.BestStreamsSerialized>(await ReadAllTextAsync($"StreamInfo/{id.Value}.json")); return JsonConvert.DeserializeObject<BestStreamInfo.BestStreamsSerialized>(await ReadAllTextAsync($"StreamInfo/{id.Value}.json"));
@ -204,15 +223,23 @@ namespace Tesses.YouTubeDownloader
} }
public virtual async Task<SavedVideo> GetDownloadInfoAsync(string url) public virtual async Task<SavedVideo> GetDownloadInfoAsync(string url)
{ {
string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json"; //string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json";
string enc=$"DownloadsInfo/{HashDownloadUrl(url)}.json";
return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync(enc)); return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync(enc));
} }
public static string HashDownloadUrl(string url)
{
using(var sha1=SHA1.Create()){
return B64.Base64UrlEncode(sha1.ComputeHash(Encoding.UTF8.GetBytes(url)));
}
}
public virtual bool DownloadExists(string url) public virtual bool DownloadExists(string url)
{ {
string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json"; string enc=$"DownloadsInfo/{HashDownloadUrl(url)}.json";
return FileExists(enc); return FileExists(enc);
} }
@ -282,6 +309,8 @@ namespace Tesses.YouTubeDownloader
return data; return data;
} }
} }
public class ListContentItem public class ListContentItem
@ -397,6 +426,20 @@ namespace Tesses.YouTubeDownloader
} }
public static class TYTDManager public static class TYTDManager
{ {
public static async Task<bool> VideoExistsAsync(this ITYTDBase baseCtl,VideoId id,Resolution resolution=Resolution.PreMuxed)
{
return (await GetVideoPathAsync(baseCtl,id,resolution)).Exists;
}
public static async Task<(bool Exists,string Path)> GetVideoPathAsync(this ITYTDBase baseCtl,VideoId id,Resolution resolution=Resolution.PreMuxed)
{
if(!baseCtl.VideoInfoExists(id) || !baseCtl.BestStreamInfoExists(id)) return (false,"");
var video=await baseCtl.GetVideoInfoAsync(id);
var name = await BestStreams.GetPathResolution(baseCtl,video,resolution);
if(string.IsNullOrWhiteSpace(name)) return (false,"");
return (await baseCtl.FileExistsAsync(name),name);
}
public static async IAsyncEnumerable<SearchResult> SearchYouTubeAsync(this IStorage storage, string query,bool getMediaInfo=true) public static async IAsyncEnumerable<SearchResult> SearchYouTubeAsync(this IStorage storage, string query,bool getMediaInfo=true)
{ {
await foreach(var vid in storage.YoutubeClient.Search.GetResultsAsync(query)) await foreach(var vid in storage.YoutubeClient.Search.GetResultsAsync(query))
@ -419,7 +462,8 @@ namespace Tesses.YouTubeDownloader
} }
public static string GetDownloadFile(string url) public static string GetDownloadFile(string url)
{ {
return $"Download/{B64.Base64UrlEncodes(url)}.bin"; string file_path = $"Downloads/{TYTDBase.HashDownloadUrl(url)}.bin";
return file_path;
} }
/// <summary> /// <summary>
/// Add Video, Playlist, Channel Or Username /// Add Video, Playlist, Channel Or Username
@ -441,8 +485,11 @@ namespace Tesses.YouTubeDownloader
VideoId? vid = VideoId.TryParse(url); VideoId? vid = VideoId.TryParse(url);
PlaylistId? pid = PlaylistId.TryParse(url); PlaylistId? pid = PlaylistId.TryParse(url);
ChannelId? cid = ChannelId.TryParse(url); ChannelId? cid = ChannelId.TryParse(url);
ChannelSlug? slug = ChannelSlug.TryParse(url);
ChannelHandle? handle = ChannelHandle.TryParse(url);
UserName? user = UserName.TryParse(url); UserName? user = UserName.TryParse(url);
if (url.Length == 11) if (url.Length == 11)
{ {
if (vid.HasValue) if (vid.HasValue)
@ -465,6 +512,13 @@ namespace Tesses.YouTubeDownloader
{ {
await downloader.AddChannelAsync(cid.Value, resolution); await downloader.AddChannelAsync(cid.Value, resolution);
} }
else if(handle.HasValue)
{
await downloader.AddHandleAsync(handle.Value, resolution);
}else if(slug.HasValue)
{
await downloader.AddSlugAsync(slug.Value,resolution);
}
else if (user.HasValue) else if (user.HasValue)
{ {
await downloader.AddUserAsync(user.Value, resolution); await downloader.AddUserAsync(user.Value, resolution);

View File

@ -195,6 +195,21 @@ internal class SegmentedHttpStream : Stream
} }
public class TYTDClient : TYTDBase,IDownloader public class TYTDClient : TYTDBase,IDownloader
{ {
ExtraData data=null;
public override async Task<byte[]> ReadThumbnailAsync(VideoId videoId, string res, CancellationToken token = default)
{
if(CanReadThumbnailFromUrl())
{
return await client.GetByteArrayAsync($"{url}api/v2/Thumbnail?v={videoId.Value}&res={res}");
}
return await base.ReadThumbnailAsync(videoId,res,token);
}
private bool CanReadThumbnailFromUrl()
{
var data=GetExtraDataOnce();
Version v=new Version(data.TYTDServerVersion);
return v.Major >= 2;
}
string url; string url;
public TYTDClient(string url) public TYTDClient(string url)
{ {
@ -241,7 +256,7 @@ internal class SegmentedHttpStream : Stream
public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed) public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed)
{ {
try{ try{
await client.GetStringAsync($"{url}api/v2/AddUser?v={userName.Value}&res={resolution.ToString()}"); await client.GetStringAsync($"{url}api/v2/AddUser?id={userName.Value}&res={resolution.ToString()}");
}catch(Exception ex) }catch(Exception ex)
{ {
_error.Invoke(this,new TYTDErrorEventArgs("jNQXAC9IVRw",ex)); _error.Invoke(this,new TYTDErrorEventArgs("jNQXAC9IVRw",ex));
@ -303,7 +318,7 @@ internal class SegmentedHttpStream : Stream
{ {
string v="[]"; string v="[]";
try{ try{
v=await client.GetStringAsync("{url}api/v2/subscriptions"); v=await client.GetStringAsync($"{url}api/v2/subscriptions");
}catch(Exception ex) }catch(Exception ex)
{ {
_error.Invoke(this,new TYTDErrorEventArgs("jNQXAC9IVRw",ex)); _error.Invoke(this,new TYTDErrorEventArgs("jNQXAC9IVRw",ex));
@ -694,14 +709,43 @@ internal class SegmentedHttpStream : Stream
Task.Run(_startEventStreamAsync).Wait(0); Task.Run(_startEventStreamAsync).Wait(0);
} }
public ExtraData GetExtraDataOnce()
{
if(data ==null){
data= Task.Run<ExtraData>(async()=>{
string text= await client.GetStringAsync($"{url}api/v2/extra_data.json");
return JsonConvert.DeserializeObject<ExtraData>(text);
}).GetAwaiter().GetResult();
}
return data;
}
public override ExtraData GetExtraData() public override ExtraData GetExtraData()
{ {
return Task.Run<ExtraData>(async()=>{ return Task.Run<ExtraData>(async()=>{
string text= await client.GetStringAsync($"{url}api/v2/extra_data.json"); string text= await client.GetStringAsync($"{url}api/v2/extra_data.json");
return JsonConvert.DeserializeObject<ExtraData>(text); return JsonConvert.DeserializeObject<ExtraData>(text);
}).GetAwaiter().GetResult(); }).GetAwaiter().GetResult();
}
} }
public async Task AddHandleAsync(ChannelHandle handle, Resolution resolution = Resolution.PreMuxed)
{
try{
await client.GetStringAsync($"{url}api/v2/AddHandle?id={handle.Value}&res={resolution.ToString()}");
}catch(Exception ex)
{
_error.Invoke(this,new TYTDErrorEventArgs("jNQXAC9IVRw",ex));
}
}
public async Task AddSlugAsync(ChannelSlug handle, Resolution resolution = Resolution.PreMuxed)
{
try{
await client.GetStringAsync($"{url}api/v2/AddSlug?id={handle.Value}&res={resolution.ToString()}");
}catch(Exception ex)
{
_error.Invoke(this,new TYTDErrorEventArgs("jNQXAC9IVRw",ex));
}
}
}
} }

View File

@ -15,6 +15,8 @@ namespace Tesses.YouTubeDownloader
{ {
public class TYTDDownloaderStorageProxy : IStorage public class TYTDDownloaderStorageProxy : IStorage
{ {
public bool AddIfCompletedInStorage {get;set;}=true;
public bool ThumbnailsFromDownloader {get;set;}=true;
public IDownloader Downloader {get{return _downloader;} set public IDownloader Downloader {get{return _downloader;} set
{ {
SetDownloader(value); SetDownloader(value);
@ -482,12 +484,22 @@ namespace Tesses.YouTubeDownloader
yield return await Task.FromResult(item); yield return await Task.FromResult(item);
} }
} }
private async Task<bool> SkipVideoAsync(VideoId id,Resolution resolution)
{
if(AddIfCompletedInStorage) return false;
if(await this.VideoExistsAsync(id,resolution))
{
return true;
}
return false;
}
public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed) public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed)
{ {
if(await SkipVideoAsync(id,resolution)) return;
if(Downloader != null) if(Downloader != null)
await Downloader.AddVideoAsync(id,resolution); await Downloader.AddVideoAsync(id,resolution);
} }
public async Task AddFileAsync(string url,bool download=true) public async Task AddFileAsync(string url,bool download=true)
{ {
if(Downloader != null) if(Downloader != null)
@ -511,7 +523,16 @@ namespace Tesses.YouTubeDownloader
if(Downloader != null) if(Downloader != null)
await Downloader.AddUserAsync(userName,resolution); await Downloader.AddUserAsync(userName,resolution);
} }
public async Task AddHandleAsync(ChannelHandle handle, Resolution resolution = Resolution.PreMuxed)
{
if(Downloader != null)
await Downloader.AddHandleAsync(handle,resolution);
}
public async Task AddSlugAsync(ChannelSlug slug, Resolution resolution = Resolution.PreMuxed)
{
if(Downloader != null)
await Downloader.AddSlugAsync(slug,resolution);
}
public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList() public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList()
{ {
if(Downloader == null) return new List<(SavedVideo Video,Resolution Resolution)>(); if(Downloader == null) return new List<(SavedVideo Video,Resolution Resolution)>();
@ -868,8 +889,65 @@ namespace Tesses.YouTubeDownloader
public ExtraData GetExtraData() public ExtraData GetExtraData()
{ {
throw new NotImplementedException(); return Downloader.GetExtraData();
} }
public bool ThumbnailExists(VideoId videoId, string res)
{
bool exists=false;
if(ThumbnailsFromDownloader)
{
DownloaderAsITYTDBase((s)=>{
exists = s.ThumbnailExists(videoId,res);
});
}else{
StorageAsStorage((s)=>{
exists = s.ThumbnailExists(videoId,res);
});
}
return exists;
}
public async Task<byte[]> ReadThumbnailAsync(VideoId videoId, string res,CancellationToken token=default)
{
byte[] data=new byte[0];
if(ThumbnailsFromDownloader)
{
await DownloaderAsITYTDBaseAsync(async (s)=>{
data = await s.ReadThumbnailAsync(videoId,res,token);
});
}else{
await StorageAsStorageAsync(async(s)=>{
data = await s.ReadThumbnailAsync(videoId,res,token);
});
}
return data;
}
public async Task WriteThumbnailAsync(VideoId videoId, string res, byte[] data, CancellationToken token = default)
{
await StorageAsStorageAsync(async(s)=>{
await s.WriteThumbnailAsync(videoId,res,data,token);
});
}
public async Task<bool> ThumbnailExistsAsync(VideoId videoId, string res)
{
bool exists=false;
if(ThumbnailsFromDownloader)
{
await DownloaderAsITYTDBaseAsync(async(s)=>{
exists = await s.ThumbnailExistsAsync(videoId,res);
});
}else{
await StorageAsStorageAsync(async(s)=>{
exists = await s.ThumbnailExistsAsync(videoId,res);
});
}
return exists;
}
} }
public class DownloaderMigration public class DownloaderMigration

View File

@ -7,9 +7,9 @@
<PackageId>Tesses.YouTubeDownloader</PackageId> <PackageId>Tesses.YouTubeDownloader</PackageId>
<Author>Mike Nolan</Author> <Author>Mike Nolan</Author>
<Company>Tesses</Company> <Company>Tesses</Company>
<Version>1.2.6</Version> <Version>2.0.1</Version>
<AssemblyVersion>1.2.6</AssemblyVersion> <AssemblyVersion>2.0.1</AssemblyVersion>
<FileVersion>1.2.6</FileVersion> <FileVersion>2.0.1</FileVersion>
<Description>A YouTube Downloader</Description> <Description>A YouTube Downloader</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
@ -20,7 +20,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="YouTubeExplode" Version="6.1.2" /> <PackageReference Include="YouTubeExplode" Version="6.2.5" />
</ItemGroup> </ItemGroup>
</Project> </Project>