Compare commits

...

10 Commits

Author SHA1 Message Date
Mike Nolan 2ef439d162 Fix ReplaceList 2023-02-23 21:23:50 -06:00
Mike Nolan e498ba2fdc Fix some stuff 2023-02-23 20:59:20 -06:00
Mike Nolan 03408687a8 Fix some stuff 2023-02-23 20:59:09 -06:00
Mike Nolan 53b6f8b27f Remove crappy blazor class and add throttle to SSE 2023-02-17 12:34:18 -06:00
Mike Nolan 15cf9a6606 Fix TYTDClient for blazor 2023-02-16 11:44:34 -06:00
Mike Nolan e1046c33b5 Fix TYTDClient for blazor 2023-02-16 11:43:41 -06:00
Mike Nolan 26c9960003 Add GetRealUrlOrPathAsync for TYTDClient 2023-02-16 10:51:46 -06:00
Mike Nolan 4a03e3398e Now GPL 2023-02-16 08:48:50 -06:00
Mike Nolan 6a403b7d15 Add Email Extra and add support for YouTube Handles and Slugs 2023-01-10 11:03:44 -06:00
Mike Nolan c471162cf0 Personal playlists use get methods 2022-11-01 03:55:28 -05:00
25 changed files with 1413 additions and 730 deletions

View File

@ -1,22 +0,0 @@
Copyright (c) 2008-2011, Kenneth Bell
Copyright (c) 2014, Quamotion
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
DiscUtils: https://github.com/DiscUtils/DiscUtils

View File

@ -1,26 +0,0 @@
The MIT License (MIT)
Copyright (c) Darko Jurić
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Licensing for JSON.NET dependency also applies: https://www.newtonsoft.com/json
Licensing for SimpleHttp also applies: https://github.com/dajuric/simple-http
I Used same license hince I used some of dajurics code in Tesses.WebServer

View File

@ -1,25 +0,0 @@
Copyright (c) 2017-2022, Alexandre Mutel
All rights reserved.
Redistribution and use in source and binary forms, with or without modification
, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Zio: https://github.com/xoofx/zio

File diff suppressed because it is too large Load Diff

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

@ -17,9 +17,11 @@ namespace Tesses.YouTubeDownloader.Net6
server.RootServer.Server=new StaticServer("WebSite"); server.RootServer.Server=new StaticServer("WebSite");
currentDirectory.CanDownload=true; currentDirectory.CanDownload=true;
HttpServerListener listener=new HttpServerListener(new System.Net.IPEndPoint(System.Net.IPAddress.Any,3252),server.InnerServer); HttpServerListener listener=new HttpServerListener(new System.Net.IPEndPoint(System.Net.IPAddress.Any,3252),server.InnerServer);
currentDirectory.StartLoop(); currentDirectory.StartLoop();
TYTDStorage.FFmpeg ="/usr/bin/ffmpeg"; TYTDStorage.FFmpeg ="/usr/bin/ffmpeg";
Console.WriteLine("Almost Ready to Listen"); Console.WriteLine("Almost Ready to Listen");
await listener.ListenAsync(CancellationToken.None); await listener.ListenAsync(CancellationToken.None);
} }

View File

@ -16,6 +16,7 @@ using Tesses.WebServer.Swagme;
namespace Tesses.Extensions namespace Tesses.Extensions
{ {
public static class Extensions public static class Extensions
{ {
public static string Substring(this string value,string str) public static string Substring(this string value,string str)
@ -31,8 +32,37 @@ namespace Tesses.Extensions
namespace Tesses.YouTubeDownloader.Server namespace Tesses.YouTubeDownloader.Server
{ {
using Tesses.YouTubeDownloader; using Tesses.YouTubeDownloader;
internal class EventSender
{
DateTime lastScan = DateTime.Now;
TimeSpan ts;
public EventSender(TimeSpan interval)
{
this.ts = interval;
}
public bool CanScan
{
get{
return DateTime.Now - lastScan >= ts;
}
}
public void SendEvent(Action action)
{
if(CanScan)
{
lastScan = DateTime.Now;
action();
}
}
public void Sent()
{
lastScan = DateTime.Now;
}
}
internal static class B64 internal static class B64
{ {
@ -405,7 +435,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 +536,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 +604,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 +927,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 +970,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 +991,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)
{ {
@ -935,6 +1018,13 @@ internal static class B64
} }
public async Task Event(ServerContext ctx) public async Task Event(ServerContext ctx)
{ {
double interval=0;
string intervalStr;
if(ctx.QueryParams.TryGetFirst("interval",out intervalStr))
{
if(!double.TryParse(intervalStr,out interval)) interval=0;
}
IStorage storage=Downloader as IStorage; IStorage storage=Downloader as IStorage;
if(storage != null){ if(storage != null){
@ -957,7 +1047,10 @@ internal static class B64
p.Video=e.VideoInfo; p.Video=e.VideoInfo;
evts.SendEvent(p); evts.SendEvent(p);
}; };
EventSender s = new EventSender(TimeSpan.FromSeconds(interval));
storage.VideoProgress += (sender,e)=>{ storage.VideoProgress += (sender,e)=>{
bool wasFirst= first;
ProgressItem p=new ProgressItem(); ProgressItem p=new ProgressItem();
p.StartEvent=false; p.StartEvent=false;
p.StopEvent=false; p.StopEvent=false;
@ -969,7 +1062,13 @@ internal static class B64
p.Video=e.VideoInfo; p.Video=e.VideoInfo;
first=false; first=false;
if(wasFirst || s.CanScan)
{
s.Sent();
evts.SendEvent(p); evts.SendEvent(p);
}
}; };
storage.VideoFinished +=(sender,e)=>{ storage.VideoFinished +=(sender,e)=>{
ProgressItem p=new ProgressItem(); ProgressItem p=new ProgressItem();
@ -1075,7 +1174,7 @@ internal static class B64
} }
public async Task ReplaceList(ServerContext ctx) public async Task ReplaceList(ServerContext ctx)
{ {
ctx.ParseBody();
//this is for personal playlists //this is for personal playlists
string name; string name;
if(ctx.QueryParams.TryGetFirst("name",out name)){ if(ctx.QueryParams.TryGetFirst("name",out name)){
@ -1496,7 +1595,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

@ -6,7 +6,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Tesses.WebServer" Version="1.0.3.8" /> <PackageReference Include="Tesses.WebServer" Version="1.0.3.8" />
<PackageReference Include="Tesses.WebServer.Swagme" Version="1.0.1" /> <PackageReference Include="Tesses.WebServer.Swagme" Version="1.0.2" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
@ -16,11 +16,11 @@
<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.2</Version>
<AssemblyVersion>1.1.8</AssemblyVersion> <AssemblyVersion>2.0.1.2</AssemblyVersion>
<FileVersion>1.1.8</FileVersion> <FileVersion>2.0.1.2</FileVersion>
<Description>Adds WebServer to TYTD</Description> <Description>Adds WebServer to TYTD</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags> <PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags>
<RepositoryUrl>https://gitlab.tesses.cf/tesses50/tytd</RepositoryUrl> <RepositoryUrl>https://gitlab.tesses.cf/tesses50/tytd</RepositoryUrl>

View File

@ -64,7 +64,7 @@ namespace Tesses.YouTubeDownloader
//Console.WriteLine("IN FUNC"); //Console.WriteLine("IN FUNC");
//Console.WriteLine("DIR"); //Console.WriteLine("DIR");
if(storage.BestStreamInfoExists(id)) if(await storage.BestStreamInfoExistsAsync(id))
{ {
//Console.WriteLine("STREAMS"); //Console.WriteLine("STREAMS");
BestStreamsSerialized serialization=await storage.GetBestStreamInfoAsync(id); BestStreamsSerialized serialization=await storage.GetBestStreamInfoAsync(id);
@ -83,7 +83,7 @@ namespace Tesses.YouTubeDownloader
{ {
if(storage.BestStreamInfoExists(id)) if(await storage.BestStreamInfoExistsAsync(id))
{ {
BestStreamsSerialized serialization=await storage.GetBestStreamInfoAsync(id); BestStreamsSerialized serialization=await storage.GetBestStreamInfoAsync(id);
if(DateTime.Now < serialization.Expires || !expire_check) if(DateTime.Now < serialization.Expires || !expire_check)
@ -99,7 +99,7 @@ namespace Tesses.YouTubeDownloader
DateTime expires=DateTime.Now.AddHours(6); DateTime expires=DateTime.Now.AddHours(6);
try{ try{
if(storage.VideoInfoExists(id)) if(await storage.VideoInfoExistsAsync(id))
{ {
var video = await storage.GetVideoInfoAsync(id); var video = await storage.GetVideoInfoAsync(id);
if(video.VideoFrozen) if(video.VideoFrozen)

View File

@ -131,10 +131,11 @@ namespace Tesses.YouTubeDownloader
{ {
begin_download: begin_download:
//await Task.Delay(this.GetLoggerProperties().DownloadMediaDelay);
try{ try{
if(video.DownloadFrom == "YouTube") if(video.DownloadFrom == "YouTube")
{ {
switch (resolution) switch (resolution)
{ {
case Resolution.Mux: case Resolution.Mux:
@ -184,8 +185,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

@ -15,7 +15,13 @@ namespace Tesses.YouTubeDownloader
{ {
public interface ITYTDBase : IPersonalPlaylistGet public interface ITYTDBase : IPersonalPlaylistGet
{ {
Task<bool> BestStreamInfoExistsAsync(VideoId id);
Task<bool> VideoInfoExistsAsync(VideoId id);
Task<bool> PlaylistInfoExistsAsync(PlaylistId id);
Task<bool> ChannelInfoExistsAsync(ChannelId id);
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

@ -111,11 +111,16 @@ namespace Tesses.YouTubeDownloader
public bool AddDateInLog {get;set;} public bool AddDateInLog {get;set;}
public bool LogEntriesRightWhenAdded {get;set;}=true;
//public TimeSpan DownloadMediaDelay {get;set;}=new TimeSpan(0,0,5);
//public TimeSpan GetInfoDelay {get;set;}=new TimeSpan(0,0,5);
} }
public class Logger public class Logger
{ {
private string _video_log_file;
private string _filename; private string _filename;
private TYTDStorage _storage; private TYTDStorage _storage;
@ -126,8 +131,9 @@ namespace Tesses.YouTubeDownloader
_storage=storage; _storage=storage;
string dateTime = DateTime.Now.ToString("yyyyMMdd_hhmmss"); string dateTime = DateTime.Now.ToString("yyyyMMdd_HHmmss");
_filename = $"config/logs/{dateTime}.log"; _filename = $"config/logs/{dateTime}.log";
_video_log_file = $"config/addlog/{dateTime}.log";
} }
private void WriteStdErr(string message) private void WriteStdErr(string message)
{ {
@ -162,6 +168,30 @@ namespace Tesses.YouTubeDownloader
Write(message,writeToConsole,isError,log); Write(message,writeToConsole,isError,log);
}); });
} }
public void WriteVideoLog(string url)
{
if(!_storage.GetLoggerProperties().LogEntriesRightWhenAdded) return;
lock(TYTDStorage.o){
try{
using(var strm = _storage.OpenOrCreateAsync(_video_log_file).GetAwaiter().GetResult())
{
if(!strm.CanSeek) return;
strm.Seek(0,SeekOrigin.End);
using(var sw = new StreamWriter(strm))
{
DateTime dat=DateTime.Now;
sw.WriteLine($"[{dat.ToShortDateString()} at {dat.ToLongTimeString()}]: {url}");
}
} }
catch(Exception ex)
{
_=ex;
}
//mtx.ReleaseMutex();
}
}
public void Write(string message,bool writeToConsole=false, bool isError=false,bool log=true) public void Write(string message,bool writeToConsole=false, bool isError=false,bool log=true)
{ {

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

@ -310,7 +310,8 @@ namespace Tesses.YouTubeDownloader
{ {
foreach(var item in Videos) foreach(var item in Videos)
{ {
if(await baseCls.FileExistsAsync($"Info/{item}.json")) VideoId? id = VideoId.TryParse(item);
if(id.HasValue && await baseCls.VideoExistsAsync(id.Value))
{ {
yield return await baseCls.GetVideoInfoAsync(item); yield return await baseCls.GetVideoInfoAsync(item);
} }

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();
@ -72,9 +89,10 @@ namespace Tesses.YouTubeDownloader
} }
public async Task WriteAllBytesAsync(string path,byte[] data,CancellationToken token=default(CancellationToken)) public async Task WriteAllBytesAsync(string path,byte[] data,CancellationToken token=default(CancellationToken))
{ {
MemoryStream ms = new MemoryStream(data);
using(var s=await CreateAsync(path)) using(var s=await CreateAsync(path))
{ {
await s.WriteAsync(data,0,data.Length,token); await ms.CopyToAsync(s,4096,token);
} }
} }
public EventHandler<ConsoleWriteEventArgs> ConsoleWrite; public EventHandler<ConsoleWriteEventArgs> ConsoleWrite;
@ -110,8 +128,8 @@ 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(!await FileExistsAsync(file))
{ {
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info)); await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
} }
@ -127,7 +145,7 @@ namespace Tesses.YouTubeDownloader
public virtual async Task WriteChannelInfoAsync(SavedChannel info) public virtual async Task WriteChannelInfoAsync(SavedChannel info)
{ {
string file = $"Channel/{info.Id}.json"; string file = $"Channel/{info.Id}.json";
if(!FileExists(file)) if(!await FileExistsAsync(file))
{ {
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info)); await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
} }
@ -136,6 +154,7 @@ namespace Tesses.YouTubeDownloader
{ {
lock(Temporary) lock(Temporary)
{ {
GetLogger().WriteVideoLog($"https://www.youtube.com/playlist?list={id.Value}");
Temporary.Add( new PlaylistMediaContext(id,resolution)); Temporary.Add( new PlaylistMediaContext(id,resolution));
} }
@ -146,14 +165,35 @@ namespace Tesses.YouTubeDownloader
{ {
lock(Temporary) lock(Temporary)
{ {
GetLogger().WriteVideoLog($"https://www.youtube.com/channel/{id.Value}");
Temporary.Add(new ChannelMediaContext(id,resolution)); Temporary.Add(new ChannelMediaContext(id,resolution));
} }
await Task.FromResult(0); await Task.FromResult(0);
} }
public async Task AddSlugAsync(ChannelSlug slug,Resolution resolution=Resolution.PreMuxed)
{
lock(Temporary)
{
GetLogger().WriteVideoLog($"https://www.youtube.com/c/{slug.Value}");
Temporary.Add(new ChannelMediaContext(slug,resolution));
}
await Task.FromResult(0);
}
public async Task AddHandleAsync(ChannelHandle handle,Resolution resolution=Resolution.PreMuxed)
{
lock(Temporary)
{
GetLogger().WriteVideoLog($"https://www.youtube.com/@{handle.Value}");
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)
{ {
GetLogger().WriteVideoLog($"https://www.youtube.com/user/{name.Value}");
Temporary.Add(new ChannelMediaContext(name,resolution)); Temporary.Add(new ChannelMediaContext(name,resolution));
} }
await Task.FromResult(0); await Task.FromResult(0);
@ -163,6 +203,7 @@ namespace Tesses.YouTubeDownloader
{ {
lock(Temporary) lock(Temporary)
{ {
GetLogger().WriteVideoLog($"https://www.youtube.com/watch?v={videoId.Value}");
Temporary.Add(new VideoMediaContext(videoId,res)); Temporary.Add(new VideoMediaContext(videoId,res));
} }
await Task.FromResult(0); await Task.FromResult(0);
@ -171,6 +212,7 @@ namespace Tesses.YouTubeDownloader
{ {
lock(Temporary) lock(Temporary)
{ {
GetLogger().WriteVideoLog(url);
Temporary.Add(new NormalDownloadMediaContext(url,download)); Temporary.Add(new NormalDownloadMediaContext(url,download));
} }
await Task.FromResult(0); await Task.FromResult(0);
@ -215,14 +257,19 @@ namespace Tesses.YouTubeDownloader
CreateDirectoryIfNotExist("Thumbnails"); CreateDirectoryIfNotExist("Thumbnails");
CreateDirectoryIfNotExist("config"); CreateDirectoryIfNotExist("config");
CreateDirectoryIfNotExist("config/logs"); CreateDirectoryIfNotExist("config/logs");
CreateDirectoryIfNotExist("FileInfo"); CreateDirectoryIfNotExist("config/addlog");
CreateDirectoryIfNotExist("Download"); CreateDirectoryIfNotExist("DownloadsInfo");
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 +279,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,20 +12,41 @@ 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");
} }
public virtual async Task<bool> PersonalPlaylistExistsAsync(string name)
{
return await FileExistsAsync($"PersonalPlaylist/{name}.json");
}
public virtual async IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string playlist) public virtual async IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string playlist)
{ {
if(!PersonalPlaylistExists(playlist)) yield break; if(!await PersonalPlaylistExistsAsync(playlist)) yield break;
var ls=JsonConvert.DeserializeObject<List<ListContentItem>>(await ReadAllTextAsync($"PersonalPlaylist/{playlist}.json")); var ls=JsonConvert.DeserializeObject<List<ListContentItem>>(await ReadAllTextAsync($"PersonalPlaylist/{playlist}.json"));
foreach(var item in ls) foreach(var item in ls)
{ {
@ -120,17 +141,14 @@ namespace Tesses.YouTubeDownloader
} }
public async Task<byte[]> ReadAllBytesAsync(string path,CancellationToken token=default(CancellationToken)) public async Task<byte[]> ReadAllBytesAsync(string path,CancellationToken token=default(CancellationToken))
{using(var strm = await OpenReadAsync(path))
{ {
byte[] data=new byte[strm.Length]; MemoryStream memoryStream=new MemoryStream();
using(var strm = await OpenReadAsync(path))
{
await strm.CopyToAsync(memoryStream,4096,token);
await strm.ReadAsync(data,0,data.Length,token);
if(token.IsCancellationRequested)
{
return new byte[0];
}
return data;
} }
return memoryStream.ToArray();
} }
public virtual async IAsyncEnumerable<string> GetPlaylistIdsAsync() public virtual async IAsyncEnumerable<string> GetPlaylistIdsAsync()
@ -179,21 +197,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"));
@ -201,18 +221,35 @@ namespace Tesses.YouTubeDownloader
public virtual bool BestStreamInfoExists(VideoId id) public virtual bool BestStreamInfoExists(VideoId id)
{ {
return FileExists($"StreamInfo/{id.Value}.json"); return FileExists($"StreamInfo/{id.Value}.json");
}
public virtual async Task<bool> BestStreamInfoExistsAsync(VideoId id)
{
return await FileExistsAsync($"StreamInfo/{id.Value}.json");
} }
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 async Task<bool> DownloadExistsAsync(string url)
{
string enc=$"DownloadsInfo/{HashDownloadUrl(url)}.json";
return await FileExistsAsync(enc);
}
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);
} }
@ -220,14 +257,26 @@ namespace Tesses.YouTubeDownloader
{ {
return FileExists($"Playlist/{id}.json"); return FileExists($"Playlist/{id}.json");
} }
public virtual async Task<bool> PlaylistInfoExistsAsync(PlaylistId id)
{
return await FileExistsAsync($"Playlist/{id}.json");
}
public virtual bool VideoInfoExists(VideoId id) public virtual bool VideoInfoExists(VideoId id)
{ {
return FileExists($"Info/{id}.json"); return FileExists($"Info/{id}.json");
} }
public virtual async Task<bool> VideoInfoExistsAsync(VideoId id)
{
return await FileExistsAsync($"Info/{id}.json");
}
public virtual bool ChannelInfoExists(ChannelId id) public virtual bool ChannelInfoExists(ChannelId id)
{ {
return FileExists($"Channel/{id}.json"); return FileExists($"Channel/{id}.json");
} }
public virtual async Task<bool> ChannelInfoExistsAsync(ChannelId id)
{
return await FileExistsAsync($"Channel/{id}.json");
}
public virtual async Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id) public virtual async Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id)
{ {
return JsonConvert.DeserializeObject<SavedPlaylist>(await ReadAllTextAsync($"Playlist/{id}.json")); return JsonConvert.DeserializeObject<SavedPlaylist>(await ReadAllTextAsync($"Playlist/{id}.json"));
@ -282,6 +331,8 @@ namespace Tesses.YouTubeDownloader
return data; return data;
} }
} }
public class ListContentItem public class ListContentItem
@ -397,6 +448,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 +484,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 +507,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 +534,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);
@ -866,6 +942,7 @@ namespace Tesses.YouTubeDownloader
ExtraData GetExtraData(); ExtraData GetExtraData();
IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string name); IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string name);
bool PersonalPlaylistExists(string name); bool PersonalPlaylistExists(string name);
Task<bool> PersonalPlaylistExistsAsync(string name);
} }
public interface IPersonalPlaylistSet : IPersonalPlaylistGet public interface IPersonalPlaylistSet : IPersonalPlaylistGet
{ {

View File

@ -16,6 +16,7 @@ using System.Net;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using YoutubeExplode.Utils.Extensions; using YoutubeExplode.Utils.Extensions;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Web;
namespace Tesses.YouTubeDownloader namespace Tesses.YouTubeDownloader
{ {
@ -194,7 +195,23 @@ 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 string Url {get{return url;}}
public TYTDClient(string url) public TYTDClient(string url)
{ {
client=new HttpClient(); client=new HttpClient();
@ -240,7 +257,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));
@ -298,11 +315,16 @@ internal class SegmentedHttpStream : Stream
} }
} }
} }
public override async Task<(string Path,bool Delete)> GetRealUrlOrPathAsync(string path)
{
return await Task.FromResult(($"{url}api/Storage/File/{path.TrimStart('/')}",false));
}
public async IAsyncEnumerable<Subscription> GetSubscriptionsAsync() public async IAsyncEnumerable<Subscription> GetSubscriptionsAsync()
{ {
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));
@ -392,7 +414,7 @@ internal class SegmentedHttpStream : Stream
} }
return false; return false;
} }
private async Task<IReadOnlyList<(SavedVideo Video, Resolution Resolution)>> GetQueueListAsync() public async Task<IReadOnlyList<(SavedVideo Video, Resolution Resolution)>> GetQueueListAsync()
{ {
try{ try{
@ -405,7 +427,7 @@ internal class SegmentedHttpStream : Stream
return new List<(SavedVideo video,Resolution resolution)>(); return new List<(SavedVideo video,Resolution resolution)>();
} }
private async Task<SavedVideoProgress> GetProgressAsync() public async Task<SavedVideoProgress> GetProgressAsync()
{ {
try{ try{
@ -452,27 +474,18 @@ internal class SegmentedHttpStream : Stream
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items) public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
{ {
Dictionary<string,string> values=new Dictionary<string, string> foreach(var item in items)
{ {
{ "name", name}, var response = await client.GetStringAsync($"{url}api/v2/AddToList?name={WebUtility.UrlEncode(name)}&v={item.Id}&res={item.Resolution.ToString()}");
{ "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<ListContentItem> items) public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
{ {
//ReplaceList var content = new MultipartFormDataContent();
Dictionary<string,string> values=new Dictionary<string, string> content.Add(new StringContent(name), "name");
{ content.Add(new StringContent(JsonConvert.SerializeObject(items.ToList())), "data");
{ "name", name}, await client.PostAsync($"{url}api/v2/ReplaceList",content);
{ "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) public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
@ -505,7 +518,7 @@ internal class SegmentedHttpStream : Stream
try{ try{
client.GetStringAsync($"{url}api/v2/DeleteList?name={WebUtility.UrlEncode(name)}").GetAwaiter().GetResult(); Task.Run(()=>client.GetStringAsync($"{url}api/v2/DeleteList?name={WebUtility.UrlEncode(name)}")).GetAwaiter().GetResult();
}catch(Exception ex) }catch(Exception ex)
{ {
@ -547,6 +560,7 @@ internal class SegmentedHttpStream : Stream
if(src != null) { if(src != null) {
src.Cancel(); src.Cancel();
src.Dispose(); src.Dispose();
} }
if(hadBeenListeningToEvents) if(hadBeenListeningToEvents)
@ -656,7 +670,7 @@ internal class SegmentedHttpStream : Stream
} }
if(item.BellEvent) if(item.BellEvent)
{ {
bell=true;
} }
progress.Length = item.Length; progress.Length = item.Length;
progress.ProgressRaw=item.Percent; progress.ProgressRaw=item.Percent;
@ -703,15 +717,59 @@ 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 async Task<ExtraData> GetExtraDataOnceAsync()
{
if(data ==null)
{
string text= await client.GetStringAsync($"{url}api/v2/extra_data.json");
data = JsonConvert.DeserializeObject<ExtraData>(text);
}
return data;
}
public async Task<ExtraData> GetExtraDataAsync()
{
string text= await client.GetStringAsync($"{url}api/v2/extra_data.json");
return JsonConvert.DeserializeObject<ExtraData>(text);
}
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,7 +889,91 @@ 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 async Task<bool> VideoInfoExistsAsync(VideoId id)
{
if(Storage ==null) return await Task.FromResult(false);
return await Storage.VideoInfoExistsAsync(id);
}
public async Task<bool> PlaylistInfoExistsAsync(PlaylistId id)
{
if(Storage ==null) return await Task.FromResult(false);
return await Storage.PlaylistInfoExistsAsync(id);
}
public async Task<bool> ChannelInfoExistsAsync(ChannelId id)
{
if(Storage ==null) return await Task.FromResult(false);
return await Storage.ChannelInfoExistsAsync(id);
}
public async Task<bool> PersonalPlaylistExistsAsync(string name)
{
if(Storage ==null) return await Task.FromResult(false);
return await Storage.PersonalPlaylistExistsAsync(name);
}
public async Task<bool> BestStreamInfoExistsAsync(VideoId id)
{
if(Storage ==null) return await Task.FromResult(false);
return await Storage.BestStreamInfoExistsAsync(id);
} }
} }

View File

@ -7,11 +7,11 @@
<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.5</Version> <Version>2.0.2.4</Version>
<AssemblyVersion>1.2.5</AssemblyVersion> <AssemblyVersion>2.0.2.4</AssemblyVersion>
<FileVersion>1.2.5</FileVersion> <FileVersion>2.0.2.4</FileVersion>
<Description>A YouTube Downloader</Description> <Description>A YouTube Downloader</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags> <PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags>
<RepositoryUrl>https://gitlab.tesses.cf/tesses50/tytd</RepositoryUrl> <RepositoryUrl>https://gitlab.tesses.cf/tesses50/tytd</RepositoryUrl>
@ -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>