Add Search, Export
This commit is contained in:
parent
93c0f3d5ab
commit
baaff558d7
|
@ -10,9 +10,78 @@ using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using YoutubeExplode.Playlists;
|
using YoutubeExplode.Playlists;
|
||||||
using YoutubeExplode.Channels;
|
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
|
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
|
internal class ApiV1Server : Tesses.WebServer.Server
|
||||||
{
|
{
|
||||||
IDownloader downloader1;
|
IDownloader downloader1;
|
||||||
|
@ -328,7 +397,71 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
var data2=JsonConvert.DeserializeObject<SavedVideo>(data);
|
var data2=JsonConvert.DeserializeObject<SavedVideo>(data);
|
||||||
await ctx.SendJsonAsync(data2.ToLegacy());
|
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));
|
string file=WebUtility.UrlDecode(path.Substring(6));
|
||||||
if(!await baseCtl.FileExistsAsync(file))
|
if(!await baseCtl.FileExistsAsync(file))
|
||||||
|
@ -353,15 +486,107 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
await ctx.SendStreamAsync(s);
|
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/"))
|
else if(path.StartsWith("/GetFiles/"))
|
||||||
{
|
{
|
||||||
await ctx.SendJsonAsync(baseCtl.EnumerateFiles( WebUtility.UrlDecode(path.Substring(10))).ToList());
|
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());
|
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/"))
|
else if(path.StartsWith("/FileExists/"))
|
||||||
{
|
{
|
||||||
|
@ -369,13 +594,27 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
}else if(path.StartsWith("/DirectoryExists/"))
|
}else if(path.StartsWith("/DirectoryExists/"))
|
||||||
{
|
{
|
||||||
await ctx.SendTextAsync(baseCtl.DirectoryExists(WebUtility.UrlDecode(path.Substring(17))) ? "true" : "false","text/plain");
|
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);
|
string id=path.Substring(7);
|
||||||
VideoId? id1=VideoId.TryParse(id);
|
VideoId? id1=VideoId.TryParse(id);
|
||||||
if(id1.HasValue){
|
if(id1.HasValue){
|
||||||
if(baseCtl.FileExists($"Info/{id1.Value.Value}.json"))
|
if(baseCtl.VideoInfoExists(id1.Value))
|
||||||
{
|
{
|
||||||
//Console.WriteLine("Id exists");
|
//Console.WriteLine("Id exists");
|
||||||
SavedVideo v = await baseCtl.GetVideoInfoAsync(id1.Value);
|
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=path.Substring(10);
|
||||||
string[] id_res_split = id_res.Split(new char[] {'/'},2,StringSplitOptions.RemoveEmptyEntries);
|
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]);
|
VideoId? id1=VideoId.TryParse(id_res_split[1]);
|
||||||
if(id1.HasValue){
|
if(id1.HasValue){
|
||||||
if(baseCtl.FileExists($"Info/{id1.Value.Value}.json"))
|
if(baseCtl.VideoInfoExists(id1.Value))
|
||||||
{
|
{
|
||||||
//Console.WriteLine("Id exists");
|
//Console.WriteLine("Id exists");
|
||||||
SavedVideo v = await baseCtl.GetVideoInfoAsync(id1.Value);
|
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{
|
else{
|
||||||
await NotFoundServer.ServerNull.GetAsync(ctx);
|
await NotFoundServer.ServerNull.GetAsync(ctx);
|
||||||
}
|
}
|
||||||
|
@ -450,7 +846,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
public ApiV2Server(IDownloader downloader)
|
public ApiV2Server(IDownloader downloader)
|
||||||
{
|
{
|
||||||
this.Downloader=downloader;
|
this.Downloader=downloader;
|
||||||
|
AddBoth("/Search",Search);
|
||||||
AddBoth("/AddItem",AddItem);
|
AddBoth("/AddItem",AddItem);
|
||||||
AddBoth("/AddChannel",AddChannel);
|
AddBoth("/AddChannel",AddChannel);
|
||||||
AddBoth("/AddUser",AddUser);
|
AddBoth("/AddUser",AddUser);
|
||||||
|
@ -473,6 +869,14 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
AddBoth("/DeleteList",DeleteList);
|
AddBoth("/DeleteList",DeleteList);
|
||||||
AddBoth("/SetResolutionInList",SetResolutionInList);
|
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)
|
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)
|
private void AddBoth(string url,HttpActionAsync action)
|
||||||
{
|
{
|
||||||
Add(url,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"
|
$"<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\">"AllowExport"</font>:<font color=\"#569cd6\">false</font> with <font color=\"#ce9178\">"AllowExport"</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\">"AllowExport"</font>:<font color=\"#569cd6\">false</font> with <font color=\"#ce9178\">"AllowExport"</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\">"AllowExport"</font>:<font color=\"#569cd6\">false</font> with <font color=\"#ce9178\">"AllowExport"</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\">"AllowExport"</font>:<font color=\"#569cd6\">false</font> with <font color=\"#ce9178\">"AllowExport"</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\">"AllowExport"</font>:<font color=\"#569cd6\">false</font> with <font color=\"#ce9178\">"AllowExport"</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\">"AllowExport"</font>:<font color=\"#569cd6\">false</font> with <font color=\"#ce9178\">"AllowExport"</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\">"AllowExport"</font>:<font color=\"#569cd6\">false</font> with <font color=\"#ce9178\">"AllowExport"</font>:<font color=\"#569cd6\">true</font></p></body></html>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public async Task AddToList(ServerContext ctx)
|
public async Task AddToList(ServerContext ctx)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
|
<LangVersion>8.0</LangVersion>
|
||||||
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
|
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
|
|
|
@ -549,7 +549,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
bool ret=false;
|
bool ret=false;
|
||||||
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
|
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
|
||||||
if(!can_download) return false;
|
|
||||||
if(streams != null)
|
if(streams != null)
|
||||||
{
|
{
|
||||||
if(streams.VideoFrozen)
|
if(streams.VideoFrozen)
|
||||||
|
@ -562,6 +562,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
if(await Continue(complete))
|
if(await Continue(complete))
|
||||||
{
|
{
|
||||||
|
if(!can_download) return false;
|
||||||
streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
|
streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
|
||||||
if(streams != null)
|
if(streams != null)
|
||||||
{
|
{
|
||||||
|
@ -649,19 +650,20 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
bool ret=false;
|
bool ret=false;
|
||||||
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
|
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
|
||||||
if(!can_download) return false;
|
|
||||||
if(streams != null)
|
if(streams != null)
|
||||||
{
|
{
|
||||||
if(streams.VideoFrozen)
|
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");
|
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 complete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}";
|
||||||
string incomplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}";
|
string incomplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}";
|
||||||
await MoveLegacyStreams(video,streams);
|
await MoveLegacyStreams(video,streams);
|
||||||
if(await Continue(complete))
|
if(await Continue(complete))
|
||||||
{
|
{
|
||||||
|
if(!can_download) return false;
|
||||||
streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
|
streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
|
||||||
if(streams != null)
|
if(streams != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public interface IStorage : IWritable, IDownloader, ITYTDBase
|
public interface IStorage : IWritable, IDownloader, ITYTDBase
|
||||||
{
|
{
|
||||||
|
void WaitTillMediaContentQueueEmpty();
|
||||||
|
|
||||||
Task WriteBestStreamInfoAsync(VideoId id,BestStreamInfo.BestStreamsSerialized serialized);
|
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> MuxVideosAsync(SavedVideo video,string videoSrc,string audioSrc,string videoDest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken));
|
||||||
Task<bool> Continue(string path);
|
Task<bool> Continue(string path);
|
||||||
|
|
|
@ -15,6 +15,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public interface ITYTDBase : IPersonalPlaylistGet
|
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);
|
Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id);
|
||||||
bool BestStreamInfoExists(VideoId id);
|
bool BestStreamInfoExists(VideoId id);
|
||||||
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
|
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
|
||||||
|
|
|
@ -35,17 +35,27 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public partial class TYTDStorage
|
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;
|
public event EventHandler<TYTDErrorEventArgs> Error;
|
||||||
internal LoggerProperties Properties {get;set;}
|
internal LoggerProperties Properties {get;set;}
|
||||||
public LoggerProperties GetProperties()
|
public LoggerProperties GetProperties()
|
||||||
{
|
{
|
||||||
CreateDirectoryIfNotExist("config");
|
|
||||||
CreateDirectoryIfNotExist("config/logs");
|
|
||||||
|
|
||||||
if(FileExists("config/tytdprop.json"))
|
if(LoggerPropertiesExists)
|
||||||
{
|
{
|
||||||
string data=ReadAllTextAsync("config/tytdprop.json").GetAwaiter().GetResult();
|
return ReadLoggerProperties();
|
||||||
return JsonConvert.DeserializeObject<LoggerProperties>(data);
|
|
||||||
}else{
|
}else{
|
||||||
LoggerProperties prop=new LoggerProperties();
|
LoggerProperties prop=new LoggerProperties();
|
||||||
prop.AddDateInLog=true;
|
prop.AddDateInLog=true;
|
||||||
|
@ -55,6 +65,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
prop.UseLogs=true;
|
prop.UseLogs=true;
|
||||||
prop.SubscriptionInterval=TimeSpan.FromHours(1);
|
prop.SubscriptionInterval=TimeSpan.FromHours(1);
|
||||||
prop.AlwaysDownloadChannel = false;
|
prop.AlwaysDownloadChannel = false;
|
||||||
|
prop.AllowExport=false;
|
||||||
|
|
||||||
return prop;
|
return prop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +76,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
Properties=GetProperties();
|
Properties=GetProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Properties;
|
return Properties;
|
||||||
}
|
}
|
||||||
internal static LockObj o=new LockObj();
|
internal static LockObj o=new LockObj();
|
||||||
|
@ -83,6 +96,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public class LoggerProperties
|
public class LoggerProperties
|
||||||
{
|
{
|
||||||
|
public bool AllowExport {get;set;}
|
||||||
public bool AlwaysDownloadChannel {get;set;}
|
public bool AlwaysDownloadChannel {get;set;}
|
||||||
public TimeSpan SubscriptionInterval {get;set;}
|
public TimeSpan SubscriptionInterval {get;set;}
|
||||||
|
|
||||||
|
@ -96,6 +110,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
|
|
||||||
public bool AddDateInLog {get;set;}
|
public bool AddDateInLog {get;set;}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
public class Logger
|
public class Logger
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public class VideoDownloadProgress
|
public class VideoDownloadProgress
|
||||||
{
|
{
|
||||||
|
|
||||||
public SavedVideo Saved { get; set; }
|
public SavedVideo Saved { get; set; }
|
||||||
|
|
||||||
public Resolution Resolution {get;set;}
|
public Resolution Resolution {get;set;}
|
||||||
|
@ -69,6 +70,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public SavedVideo()
|
public SavedVideo()
|
||||||
{
|
{
|
||||||
|
TYTDTag="";
|
||||||
Id = "";
|
Id = "";
|
||||||
Title = "";
|
Title = "";
|
||||||
AuthorChannelId = "";
|
AuthorChannelId = "";
|
||||||
|
@ -88,6 +90,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public SavedVideo(Video video)
|
public SavedVideo(Video video)
|
||||||
{
|
{
|
||||||
|
TYTDTag=TYTDStorage.TYTDTag;
|
||||||
Id=video.Id;
|
Id=video.Id;
|
||||||
Title = video.Title;
|
Title = video.Title;
|
||||||
AuthorChannelId = video.Author.ChannelId;
|
AuthorChannelId = video.Author.ChannelId;
|
||||||
|
@ -104,6 +107,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
DownloadFrom="YouTube";
|
DownloadFrom="YouTube";
|
||||||
VideoFrozen=false;
|
VideoFrozen=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string TYTDTag {get;set;}
|
||||||
public bool LegacyVideo {get;set;}
|
public bool LegacyVideo {get;set;}
|
||||||
public bool VideoFrozen {get;set;}
|
public bool VideoFrozen {get;set;}
|
||||||
|
|
||||||
|
@ -260,6 +265,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public SavedPlaylist()
|
public SavedPlaylist()
|
||||||
{
|
{
|
||||||
|
TYTDTag="";
|
||||||
Title = "";
|
Title = "";
|
||||||
AuthorChannelId="";
|
AuthorChannelId="";
|
||||||
AuthorTitle="";
|
AuthorTitle="";
|
||||||
|
@ -269,6 +275,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public SavedPlaylist(Playlist playlist,List<IVideo> videos)
|
public SavedPlaylist(Playlist playlist,List<IVideo> videos)
|
||||||
{
|
{
|
||||||
|
TYTDTag=TYTDStorage.TYTDTag;
|
||||||
Title = playlist.Title;
|
Title = playlist.Title;
|
||||||
AuthorChannelId = playlist.Author.ChannelId;
|
AuthorChannelId = playlist.Author.ChannelId;
|
||||||
AuthorTitle=playlist.Author.ChannelTitle;
|
AuthorTitle=playlist.Author.ChannelTitle;
|
||||||
|
@ -315,6 +322,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
public string TYTDTag {get;set;}
|
||||||
}
|
}
|
||||||
public class SavedChannel
|
public class SavedChannel
|
||||||
{
|
{
|
||||||
|
@ -322,12 +330,13 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
Id=c.Id;
|
Id=c.Id;
|
||||||
Title=c.Title;
|
Title=c.Title;
|
||||||
|
TYTDTag=TYTDStorage.TYTDTag;
|
||||||
}
|
}
|
||||||
public SavedChannel()
|
public SavedChannel()
|
||||||
{
|
{
|
||||||
Id="";
|
Id="";
|
||||||
Title="";
|
Title="";
|
||||||
|
TYTDTag="";
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<SavedVideo> GetVideosAsync(TYTDBase baseCls)
|
public async IAsyncEnumerable<SavedVideo> GetVideosAsync(TYTDBase baseCls)
|
||||||
{
|
{
|
||||||
|
@ -354,6 +363,6 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
public string TYTDTag {get;set;}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -41,7 +41,14 @@ namespace Tesses.YouTubeDownloader
|
||||||
await s.WriteAsync(data,0,data.Length,token);
|
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;
|
bool can_download=true;
|
||||||
public bool CanDownload {get {return can_download;} set {can_download=value;}}
|
public bool CanDownload {get {return can_download;} set {can_download=value;}}
|
||||||
public IExtensionContext ExtensionContext {get;set;}
|
public IExtensionContext ExtensionContext {get;set;}
|
||||||
|
|
|
@ -11,6 +11,8 @@ using System.IO;
|
||||||
|
|
||||||
using YoutubeExplode.Playlists;
|
using YoutubeExplode.Playlists;
|
||||||
using YoutubeExplode.Channels;
|
using YoutubeExplode.Channels;
|
||||||
|
using YoutubeExplode.Search;
|
||||||
|
|
||||||
namespace Tesses.YouTubeDownloader
|
namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -206,6 +208,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync(enc));
|
return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync(enc));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public virtual bool DownloadExists(string url)
|
public virtual bool DownloadExists(string url)
|
||||||
{
|
{
|
||||||
string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json";
|
string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json";
|
||||||
|
@ -321,9 +325,95 @@ namespace Tesses.YouTubeDownloader
|
||||||
return (VideoId.Parse(item.Id),item.Resolution);
|
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 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>
|
/// <summary>
|
||||||
/// Add Video, Playlist, Channel Or Username
|
/// Add Video, Playlist, Channel Or Username
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -292,7 +292,10 @@ namespace Tesses.YouTubeDownloader
|
||||||
e.CreateDirectoryIfNotExist(path);
|
e.CreateDirectoryIfNotExist(path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public void WaitTillMediaContentQueueEmpty()
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>e.WaitTillMediaContentQueueEmpty());
|
||||||
|
}
|
||||||
public Logger GetLogger()
|
public Logger GetLogger()
|
||||||
{
|
{
|
||||||
Logger logger=null;
|
Logger logger=null;
|
||||||
|
@ -676,21 +679,41 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id)
|
public async Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id)
|
||||||
{
|
{
|
||||||
BestStreamInfo.BestStreamsSerialized s=null;
|
return await Storage.GetBestStreamInfoAsync(id);
|
||||||
await StorageAsStorageAsync(async(e)=>{
|
|
||||||
s=await e.GetBestStreamInfoAsync(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BestStreamInfoExists(VideoId id)
|
public bool BestStreamInfoExists(VideoId id)
|
||||||
{
|
{
|
||||||
bool res=false;
|
|
||||||
StorageAsStorage((e)=>{
|
return Storage.BestStreamInfoExists(id);
|
||||||
res=e.BestStreamInfoExists(id);
|
}
|
||||||
});
|
public bool DownloadExists(string p)
|
||||||
return res;
|
{
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,19 @@ namespace Tesses.YouTubeDownloader
|
||||||
List<Subscription> Subscriptions {get;set;}
|
List<Subscription> Subscriptions {get;set;}
|
||||||
List<(SavedVideo Video, Resolution Resolution)> QueueList = new List<(SavedVideo Video, Resolution Resolution)>();
|
List<(SavedVideo Video, Resolution Resolution)> QueueList = new List<(SavedVideo Video, Resolution Resolution)>();
|
||||||
List<IMediaContext> Temporary =new List<IMediaContext>();
|
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)
|
private async Task QueueLoop(CancellationToken token)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue