Add Search, Export

This commit is contained in:
Mike Nolan 2022-07-13 08:59:23 -05:00
parent 93c0f3d5ab
commit baaff558d7
12 changed files with 872 additions and 37 deletions

View File

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

View File

@ -11,7 +11,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>true</IsPackable>
<LangVersion>8.0</LangVersion>
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>

View File

@ -17,7 +17,7 @@ namespace Tesses.YouTubeDownloader
{
public abstract partial class TYTDStorage
{
private async Task DownloadLoop(CancellationToken token = default(CancellationToken))
{
while (!token.IsCancellationRequested)
@ -549,7 +549,7 @@ namespace Tesses.YouTubeDownloader
bool ret=false;
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
if(!can_download) return false;
if(streams != null)
{
if(streams.VideoFrozen)
@ -562,6 +562,7 @@ namespace Tesses.YouTubeDownloader
if(await Continue(complete))
{
if(!can_download) return false;
streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
if(streams != null)
{
@ -649,19 +650,20 @@ namespace Tesses.YouTubeDownloader
bool ret=false;
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
if(!can_download) return false;
if(streams != null)
{
if(streams.VideoFrozen)
{
throw new Exception($"[TYTD Specific Error] Video is frozen, we wont do anything with the video.\nplease set \"VideoFrozen\": false in the file \"Info/{video.Id}.json\" to fix this problem");
}
string complete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}";
string incomplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}";
await MoveLegacyStreams(video,streams);
if(await Continue(complete))
{
if(!can_download) return false;
streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
if(streams != null)
{

View File

@ -0,0 +1,128 @@
using Newtonsoft.Json;
using YoutubeExplode.Videos.Streams;
using System.Linq;
using System;
using System.Threading.Tasks;
using YoutubeExplode.Videos;
using System.Threading;
using YoutubeExplode.Exceptions;
using System.Collections.Generic;
namespace Tesses.YouTubeDownloader
{
public class TYTDExporter
{
ITYTDBase _base;
string _tytd_tag;
public TYTDExporter(ITYTDBase baseCtl)
{
_tytd_tag=TYTDStorage.TYTDTag;
_base=baseCtl;
}
public async Task<EverythingExport> ExportEverythingAsync()
{
EverythingExport everythingExport=new EverythingExport();
everythingExport.Videos=await ExportVideosAsync();
everythingExport.Playlists = await ExportPlaylistsAsync();
everythingExport.Channels = await ExportChannelsAsync();
everythingExport.DownloadedFiles=await ExportDownloadsAsync();
everythingExport.Subscriptions= await ExportSubscriptionsAsync();
everythingExport.PersonalPlaylists=await ExportPersonalPlaylistsAsync();
everythingExport.TYTDTag = _tytd_tag;
return everythingExport;
}
public async Task<List<SavedVideo>> ExportVideosAsync()
{
List<SavedVideo> videos=new List<SavedVideo>();
await foreach(var item in _base.GetVideosAsync())
{
videos.Add(item);
}
return videos;
}
public async Task<List<SavedPlaylist>> ExportPlaylistsAsync()
{
List<SavedPlaylist> videos=new List<SavedPlaylist>();
await foreach(var item in _base.GetPlaylistsAsync())
{
videos.Add(item);
}
return videos;
}
public async Task<List<SavedChannel>> ExportChannelsAsync()
{
List<SavedChannel> videos=new List<SavedChannel>();
await foreach(var item in _base.GetChannelsAsync())
{
videos.Add(item);
}
return videos;
}
public async Task<List<SavedVideo>> ExportDownloadsAsync()
{
List<SavedVideo> videos=new List<SavedVideo>();
await foreach(var item in _base.GetDownloadsAsync())
{
videos.Add(item);
}
return videos;
}
public async Task<List<Subscription>> ExportSubscriptionsAsync()
{
List<Subscription> subs=new List<Subscription>();
var dler = _base as IDownloader;
if(dler != null)
{
await foreach(var item in dler.GetSubscriptionsAsync())
{
subs.Add(item);
}
}
return subs;
}
public async Task<List<PersonalPlaylist>> ExportPersonalPlaylistsAsync()
{
List<PersonalPlaylist> playlists=new List<PersonalPlaylist>();
await foreach(var item in _base.GetPersonalPlaylistsAsync())
{
PersonalPlaylist personalPlaylist=new PersonalPlaylist();
personalPlaylist.Name=item;
personalPlaylist.Items=new List<ListContentItem>();
await foreach(var item2 in _base.GetPersonalPlaylistContentsAsync(item))
{
personalPlaylist.Items.Add(item2);
}
playlists.Add(personalPlaylist);
}
return playlists;
}
}
public class EverythingExport
{
public string TYTDTag {get;set;}
public List<SavedVideo> Videos {get;set;}
public List<SavedPlaylist> Playlists {get;set;}
public List<SavedChannel> Channels {get;set;}
public List<SavedVideo> DownloadedFiles {get;set;}
public List<Subscription> Subscriptions {get;set;}
public List<PersonalPlaylist> PersonalPlaylists {get;set;}
}
public class PersonalPlaylist
{
public string Name {get;set;}
public List<ListContentItem> Items {get;set;}
}
}

View File

@ -15,6 +15,8 @@ namespace Tesses.YouTubeDownloader
{
public interface IStorage : IWritable, IDownloader, ITYTDBase
{
void WaitTillMediaContentQueueEmpty();
Task WriteBestStreamInfoAsync(VideoId id,BestStreamInfo.BestStreamsSerialized serialized);
Task<bool> MuxVideosAsync(SavedVideo video,string videoSrc,string audioSrc,string videoDest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken));
Task<bool> Continue(string path);

View File

@ -15,6 +15,11 @@ namespace Tesses.YouTubeDownloader
{
public interface ITYTDBase : IPersonalPlaylistGet
{
IAsyncEnumerable<string> GetDownloadUrlsAsync();
IAsyncEnumerable<SavedVideo> GetDownloadsAsync();
Task<SavedVideo> GetDownloadInfoAsync(string url);
bool DownloadExists(string path);
Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id);
bool BestStreamInfoExists(VideoId id);
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();

View File

@ -35,17 +35,27 @@ namespace Tesses.YouTubeDownloader
}
public partial class TYTDStorage
{
protected virtual LoggerProperties ReadLoggerProperties()
{
string data=ReadAllTextAsync("config/tytdprop.json").GetAwaiter().GetResult();
return JsonConvert.DeserializeObject<LoggerProperties>(data);
}
protected virtual bool LoggerPropertiesExists
{
get{
return FileExists("config/tytdprop.json");
}
}
public event EventHandler<TYTDErrorEventArgs> Error;
internal LoggerProperties Properties {get;set;}
public LoggerProperties GetProperties()
{
CreateDirectoryIfNotExist("config");
CreateDirectoryIfNotExist("config/logs");
if(FileExists("config/tytdprop.json"))
if(LoggerPropertiesExists)
{
string data=ReadAllTextAsync("config/tytdprop.json").GetAwaiter().GetResult();
return JsonConvert.DeserializeObject<LoggerProperties>(data);
return ReadLoggerProperties();
}else{
LoggerProperties prop=new LoggerProperties();
prop.AddDateInLog=true;
@ -55,6 +65,8 @@ namespace Tesses.YouTubeDownloader
prop.UseLogs=true;
prop.SubscriptionInterval=TimeSpan.FromHours(1);
prop.AlwaysDownloadChannel = false;
prop.AllowExport=false;
return prop;
}
}
@ -64,6 +76,7 @@ namespace Tesses.YouTubeDownloader
{
Properties=GetProperties();
}
return Properties;
}
internal static LockObj o=new LockObj();
@ -83,6 +96,7 @@ namespace Tesses.YouTubeDownloader
}
public class LoggerProperties
{
public bool AllowExport {get;set;}
public bool AlwaysDownloadChannel {get;set;}
public TimeSpan SubscriptionInterval {get;set;}
@ -96,6 +110,8 @@ namespace Tesses.YouTubeDownloader
public bool AddDateInLog {get;set;}
}
public class Logger
{

View File

@ -14,6 +14,7 @@ namespace Tesses.YouTubeDownloader
{
public class VideoDownloadProgress
{
public SavedVideo Saved { get; set; }
public Resolution Resolution {get;set;}
@ -24,7 +25,7 @@ namespace Tesses.YouTubeDownloader
}
public class SavedVideoLegacy
{
public string Id {get;set;}
public string Title {get;set;}
@ -69,6 +70,7 @@ namespace Tesses.YouTubeDownloader
{
public SavedVideo()
{
TYTDTag="";
Id = "";
Title = "";
AuthorChannelId = "";
@ -88,6 +90,7 @@ namespace Tesses.YouTubeDownloader
public SavedVideo(Video video)
{
TYTDTag=TYTDStorage.TYTDTag;
Id=video.Id;
Title = video.Title;
AuthorChannelId = video.Author.ChannelId;
@ -104,6 +107,8 @@ namespace Tesses.YouTubeDownloader
DownloadFrom="YouTube";
VideoFrozen=false;
}
public string TYTDTag {get;set;}
public bool LegacyVideo {get;set;}
public bool VideoFrozen {get;set;}
@ -260,6 +265,7 @@ namespace Tesses.YouTubeDownloader
{
public SavedPlaylist()
{
TYTDTag="";
Title = "";
AuthorChannelId="";
AuthorTitle="";
@ -269,6 +275,7 @@ namespace Tesses.YouTubeDownloader
}
public SavedPlaylist(Playlist playlist,List<IVideo> videos)
{
TYTDTag=TYTDStorage.TYTDTag;
Title = playlist.Title;
AuthorChannelId = playlist.Author.ChannelId;
AuthorTitle=playlist.Author.ChannelTitle;
@ -315,6 +322,7 @@ namespace Tesses.YouTubeDownloader
public string Id { get; set; }
public string Description { get; set; }
public string Title { get; set; }
public string TYTDTag {get;set;}
}
public class SavedChannel
{
@ -322,12 +330,13 @@ namespace Tesses.YouTubeDownloader
{
Id=c.Id;
Title=c.Title;
TYTDTag=TYTDStorage.TYTDTag;
}
public SavedChannel()
{
Id="";
Title="";
TYTDTag="";
}
public async IAsyncEnumerable<SavedVideo> GetVideosAsync(TYTDBase baseCls)
{
@ -354,6 +363,6 @@ namespace Tesses.YouTubeDownloader
}
public string Id { get; set; }
public string Title { get; set; }
public string TYTDTag {get;set;}
}
}

View File

@ -41,7 +41,14 @@ namespace Tesses.YouTubeDownloader
await s.WriteAsync(data,0,data.Length,token);
}
}
public static string TYTDTag {get {return _tytd_tag;}}
private static string _tytd_tag=_getTYTDTag();
private static string _getTYTDTag()
{
string tag=Environment.GetEnvironmentVariable("TYTD_TAG");
if(string.IsNullOrWhiteSpace(tag)) return "UnknownPC";
return tag;
}
bool can_download=true;
public bool CanDownload {get {return can_download;} set {can_download=value;}}
public IExtensionContext ExtensionContext {get;set;}

View File

@ -11,6 +11,8 @@ using System.IO;
using YoutubeExplode.Playlists;
using YoutubeExplode.Channels;
using YoutubeExplode.Search;
namespace Tesses.YouTubeDownloader
{
@ -206,6 +208,8 @@ namespace Tesses.YouTubeDownloader
return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync(enc));
}
public virtual bool DownloadExists(string url)
{
string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json";
@ -321,9 +325,95 @@ namespace Tesses.YouTubeDownloader
return (VideoId.Parse(item.Id),item.Resolution);
}
}
public enum MediaType
{
Video=0,
Playlist=1,
Channel=2,
}
public class SearchResult
{
public SearchResult()
{
Title="";
Id="";
Type=MediaType.Video;
}
public SearchResult(ISearchResult result)
{
var video = result as VideoSearchResult;
var playlist = result as PlaylistSearchResult;
var channel = result as ChannelSearchResult;
if(video != null)
{
Id=video.Id;
Title = video.Title;
Type=MediaType.Video;
}
if(playlist != null)
{
Id=playlist.Id;
Title=playlist.Title;
Type=MediaType.Playlist;
}
if(channel != null)
{
Id=channel.Id;
Title = channel.Title;
Type=MediaType.Channel;
}
}
public string Title {get;set;}
public string Id {get;set;}
public MediaType Type {get;set;}
public void AddToQueue(IStorage storage)
{
switch(Type)
{
case MediaType.Video:
storage.AddVideoAsync(Id,Resolution.NoDownload);
break;
case MediaType.Playlist:
storage.AddPlaylistAsync(Id,Resolution.NoDownload);
break;
case MediaType.Channel:
storage.AddChannelAsync(Id,Resolution.NoDownload);
break;
}
}
}
public static class TYTDManager
{
public static async IAsyncEnumerable<SearchResult> SearchYouTubeAsync(this IStorage storage, string query,bool getMediaInfo=true)
{
await foreach(var vid in storage.YoutubeClient.Search.GetResultsAsync(query))
{
var res=new SearchResult(vid);
if(getMediaInfo)
{
res.AddToQueue(storage);
}
yield return res;
}
}
public static bool DownloadFileExists(this ITYTDBase baseCtl,string url)
{
return baseCtl.FileExists(GetDownloadFile(url));
}
public static string GetDownloadFile(this ITYTDBase baseCtl,string url)
{
return GetDownloadFile(url);
}
public static string GetDownloadFile(string url)
{
return $"Download/{B64.Base64UrlEncodes(url)}.bin";
}
/// <summary>
/// Add Video, Playlist, Channel Or Username
/// </summary>

View File

@ -292,7 +292,10 @@ namespace Tesses.YouTubeDownloader
e.CreateDirectoryIfNotExist(path);
});
}
public void WaitTillMediaContentQueueEmpty()
{
StorageAsStorage((e)=>e.WaitTillMediaContentQueueEmpty());
}
public Logger GetLogger()
{
Logger logger=null;
@ -676,21 +679,41 @@ namespace Tesses.YouTubeDownloader
public async Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id)
{
BestStreamInfo.BestStreamsSerialized s=null;
await StorageAsStorageAsync(async(e)=>{
s=await e.GetBestStreamInfoAsync(id);
});
return s;
return await Storage.GetBestStreamInfoAsync(id);
}
public bool BestStreamInfoExists(VideoId id)
{
bool res=false;
StorageAsStorage((e)=>{
res=e.BestStreamInfoExists(id);
});
return res;
return Storage.BestStreamInfoExists(id);
}
public bool DownloadExists(string p)
{
return Storage.DownloadExists(p);
}
public async IAsyncEnumerable<string> GetDownloadUrlsAsync()
{
await foreach(var url in Storage.GetDownloadUrlsAsync())
{
yield return url;
}
}
public async IAsyncEnumerable<SavedVideo> GetDownloadsAsync()
{
await foreach(var item in Storage.GetDownloadsAsync())
{
yield return item;
}
}
public async Task<SavedVideo> GetDownloadInfoAsync(string url)
{
return await Storage.GetDownloadInfoAsync(url);
}
}

View File

@ -35,6 +35,19 @@ namespace Tesses.YouTubeDownloader
List<Subscription> Subscriptions {get;set;}
List<(SavedVideo Video, Resolution Resolution)> QueueList = new List<(SavedVideo Video, Resolution Resolution)>();
List<IMediaContext> Temporary =new List<IMediaContext>();
public void WaitTillMediaContentQueueEmpty()
{
while(true)
{
lock(Temporary)
{
if(Temporary.Count <= 0) return;
}
Thread.Sleep(100);
}
}
private async Task QueueLoop(CancellationToken token)
{