Added File Download, abstracted JSON
This commit is contained in:
parent
f79c6122fb
commit
08675d678b
|
@ -143,4 +143,6 @@ Tesses.YouTubeDownloader.Net6/config/
|
||||||
Tesses.YouTubeDownloader.Net6/Playlist/
|
Tesses.YouTubeDownloader.Net6/Playlist/
|
||||||
Tesses.YouTubeDownloader.Net6/Channel/
|
Tesses.YouTubeDownloader.Net6/Channel/
|
||||||
Tesses.YouTubeDownloader.Net6/Subscriptions/
|
Tesses.YouTubeDownloader.Net6/Subscriptions/
|
||||||
|
Tesses.YouTubeDownloader.Net6/Download/
|
||||||
|
Tesses.YouTubeDownloader.Net6/FileInfo/
|
||||||
push
|
push
|
||||||
|
|
|
@ -11,9 +11,9 @@
|
||||||
<PackageId>Tesses.YouTubeDownloader.ExtensionLoader</PackageId>
|
<PackageId>Tesses.YouTubeDownloader.ExtensionLoader</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
<Version>1.1.1</Version>
|
<Version>1.1.2</Version>
|
||||||
<AssemblyVersion>1.1.1</AssemblyVersion>
|
<AssemblyVersion>1.1.2</AssemblyVersion>
|
||||||
<FileVersion>1.1.1</FileVersion>
|
<FileVersion>1.1.2</FileVersion>
|
||||||
<Description>Load Extensions into TYTD (Not Tested)</Description>
|
<Description>Load Extensions into TYTD (Not Tested)</Description>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
public class TYTDConfiguration
|
||||||
|
{
|
||||||
|
public TYTDConfiguration()
|
||||||
|
{
|
||||||
|
Url = "http://127.0.0.1:3252/";
|
||||||
|
LocalFiles=Environment.CurrentDirectory;
|
||||||
|
}
|
||||||
|
public string Url {get;set;}
|
||||||
|
|
||||||
|
public string LocalFiles {get;set;}
|
||||||
|
|
||||||
|
public static TYTDConfiguration Load()
|
||||||
|
{
|
||||||
|
if(!File.Exists("proxy.json")) return new TYTDConfiguration();
|
||||||
|
var res= JsonConvert.DeserializeObject<TYTDConfiguration>(File.ReadAllText("proxy.json"));
|
||||||
|
if(res != null)
|
||||||
|
{
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return new TYTDConfiguration();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
using Tesses.YouTubeDownloader;
|
||||||
|
using Tesses.YouTubeDownloader.Server;
|
||||||
|
using Tesses.WebServer;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
var config=TYTDConfiguration.Load();
|
||||||
|
Environment.CurrentDirectory=config.LocalFiles;
|
||||||
|
var c=new HttpClient();
|
||||||
|
TYTDCurrentDirectory currentDirectory=new TYTDCurrentDirectory(c);
|
||||||
|
TYTDClient client=new TYTDClient(c,config.Url);
|
||||||
|
|
||||||
|
TYTDDownloaderStorageProxy proxy=new TYTDDownloaderStorageProxy();
|
||||||
|
proxy.Storage = currentDirectory;
|
||||||
|
proxy.Downloader=client;
|
||||||
|
|
||||||
|
TYTDServer server=new TYTDServer(proxy);
|
||||||
|
server.RootServer.Server=new StaticServer("WebSite");
|
||||||
|
currentDirectory.CanDownload=false;
|
||||||
|
HttpServerListener listener=new HttpServerListener(new System.Net.IPEndPoint(System.Net.IPAddress.Any,3252),server.InnerServer);
|
||||||
|
currentDirectory.StartLoop();
|
||||||
|
TYTDStorage.FFmpeg ="/usr/bin/ffmpeg";
|
||||||
|
Console.WriteLine("Almost Ready to Listen");
|
||||||
|
await listener.ListenAsync(CancellationToken.None);
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Tesses.YouTubeDownloader.Server\Tesses.YouTubeDownloader.Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"Url": "http://10.137.42.142:3252/",
|
||||||
|
"LocalFiles": "/media/mike/PhotoDrive/wii-vids/working"
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Tesses.YouTubeDownloader.Tools.Common
|
||||||
|
{
|
||||||
|
public static class StringUtils
|
||||||
|
{
|
||||||
|
public static string GetSafeFileName(this string filename)
|
||||||
|
{
|
||||||
|
StringBuilder b=new StringBuilder(filename);
|
||||||
|
foreach(var badChr in "\\\"\'/?*<>|:")
|
||||||
|
{
|
||||||
|
b.Replace(badChr.ToString(),"");
|
||||||
|
}
|
||||||
|
if(b.Length == 0) return "file";
|
||||||
|
return b.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System;
|
||||||
|
namespace Tesses.YouTubeDownloader.Tools.Common
|
||||||
|
{
|
||||||
|
public static class SymlinkGenerator
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
||||||
|
static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
|
||||||
|
private static void createHardLink(string destPath,string srcPath)
|
||||||
|
{if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
CreateHardLink(destPath,srcPath,IntPtr.Zero);
|
||||||
|
}else{
|
||||||
|
using(var p=new Process())
|
||||||
|
{
|
||||||
|
|
||||||
|
p.StartInfo.FileName = "ln";
|
||||||
|
p.StartInfo.ArgumentList.Add(srcPath);
|
||||||
|
p.StartInfo.ArgumentList.Add(destPath);
|
||||||
|
|
||||||
|
if(p.Start()){ p.WaitForExit();}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task GenerateHardLinks(TYTDStorage storage,string dest="GoodFileNames",Resolution res=Resolution.PreMuxed,bool verbose=false)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dest);
|
||||||
|
await foreach(var item in storage.GetVideosAsync())
|
||||||
|
{
|
||||||
|
if(await item.VideoExistsAsync(storage,res))
|
||||||
|
{
|
||||||
|
var (path,delete)= await storage.GetRealUrlOrPathAsync(await BestStreams.GetPathResolution(storage,item,res));
|
||||||
|
string? ext=Path.GetExtension(path);
|
||||||
|
|
||||||
|
string defaultExt = res == Resolution.Mux ? ".mkv" : ".mp4";
|
||||||
|
if(string.IsNullOrWhiteSpace(ext))
|
||||||
|
{
|
||||||
|
ext=defaultExt;
|
||||||
|
}
|
||||||
|
string destPathMkv=Path.Combine(dest,$"{item.Title.GetSafeFileName()}-{item.Id}{defaultExt}");
|
||||||
|
string destPath=Path.Combine(dest,$"{item.Title.GetSafeFileName()}-{item.Id}{ext}");
|
||||||
|
|
||||||
|
if(File.Exists(destPathMkv) && destPathMkv != destPath)
|
||||||
|
{
|
||||||
|
File.Delete(destPathMkv);
|
||||||
|
createHardLink(destPath,path);
|
||||||
|
if(verbose)
|
||||||
|
Console.WriteLine($"Changed: {item.Title} {defaultExt} -> {ext}");
|
||||||
|
}
|
||||||
|
if(!File.Exists(destPath))
|
||||||
|
{
|
||||||
|
createHardLink(destPath,path);
|
||||||
|
if(verbose)
|
||||||
|
Console.WriteLine(item.Title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public static async Task GenerateSymlinks(TYTDBase storage,string dest="GoodFileNames",Resolution res=Resolution.PreMuxed,bool verbose=false)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dest);
|
||||||
|
await foreach(var item in storage.GetVideosAsync())
|
||||||
|
{
|
||||||
|
if(await item.VideoExistsAsync(storage,res))
|
||||||
|
{
|
||||||
|
var (path,delete)= await storage.GetRealUrlOrPathAsync(await BestStreams.GetPathResolution(storage,item,res));
|
||||||
|
string? ext=Path.GetExtension(path);
|
||||||
|
|
||||||
|
string defaultExt = res == Resolution.Mux ? ".mkv" : ".mp4";
|
||||||
|
if(string.IsNullOrWhiteSpace(ext))
|
||||||
|
{
|
||||||
|
ext=defaultExt;
|
||||||
|
}
|
||||||
|
string destPathMkv=Path.Combine(dest,$"{item.Title.GetSafeFileName()}-{item.Id}{defaultExt}");
|
||||||
|
string destPath=Path.Combine(dest,$"{item.Title.GetSafeFileName()}-{item.Id}{ext}");
|
||||||
|
|
||||||
|
if(File.Exists(destPathMkv) && destPathMkv != destPath)
|
||||||
|
{
|
||||||
|
File.Delete(destPathMkv);
|
||||||
|
File.CreateSymbolicLink(destPath,path);
|
||||||
|
if(verbose)
|
||||||
|
Console.WriteLine($"Changed: {item.Title} {defaultExt} -> {ext}");
|
||||||
|
}
|
||||||
|
if(!File.Exists(destPath))
|
||||||
|
{
|
||||||
|
File.CreateSymbolicLink(destPath,path);
|
||||||
|
if(verbose)
|
||||||
|
Console.WriteLine(item.Title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System;
|
||||||
|
using Tesses.YouTubeDownloader.SFTP;
|
||||||
|
namespace Tesses.YouTubeDownloader.Tools.Common
|
||||||
|
{
|
||||||
|
public static class TYTDOpener
|
||||||
|
{
|
||||||
|
public static TYTDBase? GetTYTDBase(string p)
|
||||||
|
{
|
||||||
|
Uri? uri;
|
||||||
|
|
||||||
|
if(Uri.TryCreate(p,UriKind.Absolute,out uri))
|
||||||
|
{
|
||||||
|
if(uri.IsFile)
|
||||||
|
{
|
||||||
|
return new TYTDPathDirectory(uri.LocalPath);
|
||||||
|
}
|
||||||
|
if(uri.Scheme == "sftp")
|
||||||
|
{
|
||||||
|
return new SSHFS(uri);
|
||||||
|
}
|
||||||
|
if(uri.Scheme == "http" || uri.Scheme == "https")
|
||||||
|
{
|
||||||
|
return new TYTDClient(uri);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if(!string.IsNullOrWhiteSpace(p)) return new TYTDPathDirectory(p);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Tesses.YouTubeDownloader.SFTP\Tesses.YouTubeDownloader.SFTP.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,83 @@
|
||||||
|
using Tesses.YouTubeDownloader.Tools.Common;
|
||||||
|
using Tesses.YouTubeDownloader;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
Resolution res=Resolution.PreMuxed;
|
||||||
|
|
||||||
|
bool verbose=false;
|
||||||
|
bool isSymlink = false;
|
||||||
|
List<string> _args=new List<string>();
|
||||||
|
foreach(var arg in args)
|
||||||
|
{
|
||||||
|
bool any=false;
|
||||||
|
if(arg == "-h" || arg == "--help")
|
||||||
|
{
|
||||||
|
_args.Clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if( (arg.Length >= 2 && arg[1] != '-' && arg[0] == '-' && arg.Contains("s") )|| arg == "--symbolic")
|
||||||
|
{
|
||||||
|
any=true;
|
||||||
|
isSymlink=true;
|
||||||
|
}
|
||||||
|
if((arg.Length >= 2 && arg[1] != '-' && arg[0] == '-' && arg.Contains("m") ) || arg == "--mux" )
|
||||||
|
{
|
||||||
|
any=true;
|
||||||
|
res = Resolution.Mux;
|
||||||
|
}
|
||||||
|
if((arg.Length >= 2 && arg[1] != '-' && arg[0] == '-' && arg.Contains("a") ) || arg == "--audio-only")
|
||||||
|
{
|
||||||
|
any=true;
|
||||||
|
res = Resolution.AudioOnly;
|
||||||
|
|
||||||
|
}
|
||||||
|
if ((arg.Length >= 2 && arg[1] != '-' && arg[0] == '-' && arg.Contains("V") ) || arg=="--video-only")
|
||||||
|
{
|
||||||
|
any=true;
|
||||||
|
res = Resolution.VideoOnly;
|
||||||
|
|
||||||
|
}
|
||||||
|
if ((arg.Length >= 2 && arg[1] != '-' && arg[0] == '-' && arg.Contains("v") ) || arg=="--verbose")
|
||||||
|
{
|
||||||
|
any=true;
|
||||||
|
verbose=true;
|
||||||
|
|
||||||
|
}
|
||||||
|
if(!any)
|
||||||
|
_args.Add(arg);
|
||||||
|
|
||||||
|
}
|
||||||
|
string[] argv = _args.ToArray();
|
||||||
|
if(argv.Length < 2)
|
||||||
|
{
|
||||||
|
string app = Path.GetFileNameWithoutExtension(Environment.GetCommandLineArgs()[0]);
|
||||||
|
|
||||||
|
Console.WriteLine($"usage: {app} [-smaVv] <Working> <Destination> [<Resolution>]");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Options:");
|
||||||
|
Console.WriteLine(" -s, --symbolic make symbolic links instead of hard links");
|
||||||
|
Console.WriteLine(" -m, --mux set resolution to Mux");
|
||||||
|
Console.WriteLine(" -a, --audio-only set resolution to AudioOnly");
|
||||||
|
Console.WriteLine(" -V, --video-only set resolution to VideoOnly");
|
||||||
|
Console.WriteLine(" -h, --help show this help");
|
||||||
|
Console.WriteLine(" -v, --verbose print video names");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Positional Arguments:");
|
||||||
|
Console.WriteLine(" Working the folder containing the Info Directory for TYTD. (required)");
|
||||||
|
Console.WriteLine(" Destination the folder to create links within. (required)");
|
||||||
|
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
|
||||||
|
Environment.CurrentDirectory=argv[0];
|
||||||
|
TYTDCurrentDirectory currentDirectory=new TYTDCurrentDirectory();
|
||||||
|
currentDirectory.CanDownload=false;
|
||||||
|
|
||||||
|
if(isSymlink){
|
||||||
|
await SymlinkGenerator.GenerateSymlinks(currentDirectory,argv[1],res,verbose);
|
||||||
|
}else{
|
||||||
|
await SymlinkGenerator.GenerateHardLinks(currentDirectory,argv[1],res,verbose);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Tesses.YouTubeDownloader.Tools.Common\Tesses.YouTubeDownloader.Tools.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -25,6 +25,79 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
public override async Task GetAsync(ServerContext ctx)
|
public override async Task GetAsync(ServerContext ctx)
|
||||||
{
|
{
|
||||||
string path=ctx.UrlAndQuery;
|
string path=ctx.UrlAndQuery;
|
||||||
|
|
||||||
|
if(path.StartsWith("/AddPlaylistRes/"))
|
||||||
|
{ string id_res=path.Substring(16);
|
||||||
|
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;
|
||||||
|
|
||||||
|
await downloader1.AddPlaylistAsync(id_res_split[1],(Resolution)num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(path.StartsWith("/AddChannelRes/"))
|
||||||
|
{ string id_res=path.Substring(15);
|
||||||
|
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;
|
||||||
|
|
||||||
|
await downloader1.AddChannelAsync(id_res_split[1],(Resolution)num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(path.StartsWith("/AddChannel/"))
|
||||||
|
{
|
||||||
|
await downloader1.AddChannelAsync(path.Substring(12),Resolution.PreMuxed);
|
||||||
|
|
||||||
|
}
|
||||||
|
if(path.StartsWith("/AddPlaylist/"))
|
||||||
|
{
|
||||||
|
await downloader1.AddPlaylistAsync(path.Substring(13),Resolution.PreMuxed);
|
||||||
|
|
||||||
|
}
|
||||||
|
if(path.StartsWith("/AddVideoRes/"))
|
||||||
|
{ string id_res=path.Substring(13);
|
||||||
|
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;
|
||||||
|
|
||||||
|
await downloader1.AddVideoAsync(id_res_split[1],(Resolution)num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(path.StartsWith("/AddVideo/"))
|
||||||
|
{
|
||||||
|
//string id_res=path.Substring(12);
|
||||||
|
//string[] id_res_split = id_res.Split(new char[] {'/'},2,StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
//if(id_res_split.Length ==2)
|
||||||
|
//{
|
||||||
|
|
||||||
|
await downloader1.AddVideoAsync(path.Substring(10),Resolution.PreMuxed);
|
||||||
|
|
||||||
|
// }
|
||||||
|
// await ctx.SendTextAsync(
|
||||||
|
// $"<html><head><titleYou 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"
|
||||||
|
//);
|
||||||
|
await ctx.SendRedirectAsync("/");
|
||||||
|
}
|
||||||
if(path.StartsWith("/AddItemRes/"))
|
if(path.StartsWith("/AddItemRes/"))
|
||||||
{
|
{
|
||||||
string id_res=path.Substring(12);
|
string id_res=path.Substring(12);
|
||||||
|
@ -43,7 +116,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
// await ctx.SendTextAsync(
|
// await ctx.SendTextAsync(
|
||||||
// $"<html><head><titleYou 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><titleYou 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"
|
||||||
//);
|
//);
|
||||||
await ctx.SendRedirectAsync("/");
|
|
||||||
}
|
}
|
||||||
if(path.StartsWith("/AddItem/"))
|
if(path.StartsWith("/AddItem/"))
|
||||||
{
|
{
|
||||||
|
@ -58,8 +131,59 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
// await ctx.SendTextAsync(
|
// await ctx.SendTextAsync(
|
||||||
// $"<html><head><titleYou 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><titleYou 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"
|
||||||
//);
|
//);
|
||||||
await ctx.SendRedirectAsync("/");
|
|
||||||
}
|
}
|
||||||
|
if(path.StartsWith("/AddUserRes/"))
|
||||||
|
{
|
||||||
|
string id_res=path.Substring(12);
|
||||||
|
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;
|
||||||
|
|
||||||
|
await downloader1.AddUserAsync(id_res_split[1],(Resolution)num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// await ctx.SendTextAsync(
|
||||||
|
// $"<html><head><titleYou 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"
|
||||||
|
//);
|
||||||
|
|
||||||
|
}
|
||||||
|
if(path.StartsWith("/AddUser/"))
|
||||||
|
{
|
||||||
|
//string id_res=path.Substring(12);
|
||||||
|
//string[] id_res_split = id_res.Split(new char[] {'/'},2,StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
//if(id_res_split.Length ==2)
|
||||||
|
//{
|
||||||
|
|
||||||
|
await downloader1.AddUserAsync(path.Substring(9),Resolution.PreMuxed);
|
||||||
|
|
||||||
|
// }
|
||||||
|
// await ctx.SendTextAsync(
|
||||||
|
// $"<html><head><titleYou 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"
|
||||||
|
//);
|
||||||
|
|
||||||
|
}
|
||||||
|
if(path.StartsWith("/AddFile/"))
|
||||||
|
{
|
||||||
|
//string id_res=path.Substring(12);
|
||||||
|
//string[] id_res_split = id_res.Split(new char[] {'/'},2,StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
//if(id_res_split.Length ==2)
|
||||||
|
//{
|
||||||
|
|
||||||
|
await downloader1.AddFileAsync(path.Substring(9));
|
||||||
|
|
||||||
|
// }
|
||||||
|
// await ctx.SendTextAsync(
|
||||||
|
// $"<html><head><titleYou 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"
|
||||||
|
//);
|
||||||
|
|
||||||
|
}
|
||||||
|
await ctx.SendRedirectAsync("/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal class ApiStorage : Tesses.WebServer.Server
|
internal class ApiStorage : Tesses.WebServer.Server
|
||||||
|
@ -332,6 +456,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
AddBoth("/AddUser",AddUser);
|
AddBoth("/AddUser",AddUser);
|
||||||
AddBoth("/AddPlaylist",AddPlaylist);
|
AddBoth("/AddPlaylist",AddPlaylist);
|
||||||
AddBoth("/AddVideo",AddVideo);
|
AddBoth("/AddVideo",AddVideo);
|
||||||
|
AddBoth("/AddFile",AddFile);
|
||||||
AddBoth("/Progress",ProgressFunc);
|
AddBoth("/Progress",ProgressFunc);
|
||||||
AddBoth("/QueueList",QueueList);
|
AddBoth("/QueueList",QueueList);
|
||||||
AddBoth("/subscribe",Subscribe);
|
AddBoth("/subscribe",Subscribe);
|
||||||
|
@ -641,6 +766,28 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
public async Task ProgressFunc(ServerContext ctx)
|
public async Task ProgressFunc(ServerContext ctx)
|
||||||
{
|
{
|
||||||
await ctx.SendJsonAsync(Downloader.GetProgress());
|
await ctx.SendJsonAsync(Downloader.GetProgress());
|
||||||
|
}
|
||||||
|
public async Task AddFile(ServerContext ctx)
|
||||||
|
{
|
||||||
|
string url;
|
||||||
|
string downloadStr;
|
||||||
|
bool download=true;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("url",out url))
|
||||||
|
{
|
||||||
|
if(ctx.QueryParams.TryGetFirst("download",out downloadStr))
|
||||||
|
{
|
||||||
|
bool dl;
|
||||||
|
if(bool.TryParse(downloadStr,out dl))
|
||||||
|
{
|
||||||
|
download=dl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Downloader.AddFileAsync(url,download);
|
||||||
|
await ctx.SendTextAsync(
|
||||||
|
$"<html><head><titleYou 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 AddVideo(ServerContext ctx)
|
public async Task AddVideo(ServerContext ctx)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Tesses.WebServer" Version="1.0.3.4" />
|
<PackageReference Include="Tesses.WebServer" Version="1.0.3.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -15,9 +15,9 @@
|
||||||
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
|
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
<Version>1.1.3</Version>
|
<Version>1.1.4</Version>
|
||||||
<AssemblyVersion>1.1.3</AssemblyVersion>
|
<AssemblyVersion>1.1.4</AssemblyVersion>
|
||||||
<FileVersion>1.1.3</FileVersion>
|
<FileVersion>1.1.4</FileVersion>
|
||||||
<Description>Adds WebServer to TYTD</Description>
|
<Description>Adds WebServer to TYTD</Description>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
namespace Tesses.YouTubeDownloader
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
var f= await BestStreamInfo.GetBestStreams(storage,video.Id);
|
var f= await BestStreamInfo.GetBestStreams(storage,video.Id);
|
||||||
|
|
||||||
if(f ==null)
|
if(f ==null)
|
||||||
return "";
|
return resolution == Resolution.NoDownload ? "" : resolution == Resolution.Mux ? $"Mux/{video.Id}.mkv" : $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mp4";
|
||||||
|
|
||||||
if(f.VideoFrozen)
|
if(f.VideoFrozen)
|
||||||
{
|
{
|
||||||
|
@ -61,13 +61,12 @@ namespace Tesses.YouTubeDownloader
|
||||||
public static async Task<BestStreams> GetBestStreams(ITYTDBase storage,VideoId id)
|
public static async Task<BestStreams> GetBestStreams(ITYTDBase storage,VideoId id)
|
||||||
{
|
{
|
||||||
//Console.WriteLine("IN FUNC");
|
//Console.WriteLine("IN FUNC");
|
||||||
if(storage.DirectoryExists("StreamInfo"))
|
|
||||||
{
|
|
||||||
//Console.WriteLine("DIR");
|
//Console.WriteLine("DIR");
|
||||||
if(storage.FileExists($"StreamInfo/{id.Value}.json"))
|
if(storage.BestStreamInfoExists(id))
|
||||||
{
|
{
|
||||||
//Console.WriteLine("STREAMS");
|
//Console.WriteLine("STREAMS");
|
||||||
BestStreamsSerialized serialization=JsonConvert.DeserializeObject<BestStreamsSerialized>(await storage.ReadAllTextAsync($"StreamInfo/{id.Value}.json"));
|
BestStreamsSerialized serialization=await storage.GetBestStreamInfoAsync(id);
|
||||||
|
|
||||||
BestStreams streams=new BestStreams();
|
BestStreams streams=new BestStreams();
|
||||||
streams.VideoOnlyStreamInfo = new BestStreamInfo(serialization.VideoOnly);
|
streams.VideoOnlyStreamInfo = new BestStreamInfo(serialization.VideoOnly);
|
||||||
|
@ -76,16 +75,16 @@ namespace Tesses.YouTubeDownloader
|
||||||
return streams;
|
return streams;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public static async Task<BestStreams> GetBestStreams(IStorage storage,VideoId id,CancellationToken token=default(CancellationToken),bool expire_check=true)
|
public static async Task<BestStreams> GetBestStreams(IStorage storage,VideoId id,CancellationToken token=default(CancellationToken),bool expire_check=true)
|
||||||
{
|
{
|
||||||
if(storage.DirectoryExists("StreamInfo"))
|
|
||||||
|
|
||||||
|
if(storage.BestStreamInfoExists(id))
|
||||||
{
|
{
|
||||||
if(storage.FileExists($"StreamInfo/{id.Value}.json"))
|
BestStreamsSerialized serialization=await storage.GetBestStreamInfoAsync(id);
|
||||||
{
|
|
||||||
BestStreamsSerialized serialization=JsonConvert.DeserializeObject<BestStreamsSerialized>(await storage.ReadAllTextAsync($"StreamInfo/{id.Value}.json"));
|
|
||||||
if(DateTime.Now < serialization.Expires || !expire_check)
|
if(DateTime.Now < serialization.Expires || !expire_check)
|
||||||
{
|
{
|
||||||
BestStreams streams=new BestStreams();
|
BestStreams streams=new BestStreams();
|
||||||
|
@ -95,9 +94,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
return streams;
|
return streams;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
|
||||||
storage.CreateDirectory("StreamInfo");
|
|
||||||
}
|
|
||||||
DateTime expires=DateTime.Now.AddHours(6);
|
DateTime expires=DateTime.Now.AddHours(6);
|
||||||
try{
|
try{
|
||||||
if(storage.VideoInfoExists(id))
|
if(storage.VideoInfoExists(id))
|
||||||
|
@ -126,8 +123,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
streams1.MuxedStreamInfo =new BestStreamInfo();
|
streams1.MuxedStreamInfo =new BestStreamInfo();
|
||||||
streams1.MuxedStreamInfo.SetInfo(muxed);
|
streams1.MuxedStreamInfo.SetInfo(muxed);
|
||||||
serialized.Muxed = streams1.MuxedStreamInfo.Serialization;
|
serialized.Muxed = streams1.MuxedStreamInfo.Serialization;
|
||||||
|
await storage.WriteBestStreamInfoAsync(id,serialized);
|
||||||
await storage.WriteAllTextAsync($"StreamInfo/{id.Value}.json",JsonConvert.SerializeObject(serialized));
|
|
||||||
return streams1;
|
return streams1;
|
||||||
}catch(YoutubeExplodeException ex)
|
}catch(YoutubeExplodeException ex)
|
||||||
{
|
{
|
||||||
|
@ -135,7 +131,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private class BestStreamsSerialized
|
public class BestStreamsSerialized
|
||||||
{
|
{
|
||||||
public DateTime Expires {get;set;}
|
public DateTime Expires {get;set;}
|
||||||
public BestStreamInfoSerialization VideoOnly {get;set;}
|
public BestStreamInfoSerialization VideoOnly {get;set;}
|
||||||
|
@ -202,7 +198,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class BestStreamInfoSerialization
|
public class BestStreamInfoSerialization
|
||||||
{
|
{
|
||||||
public string AudioCodec {get;set;}
|
public string AudioCodec {get;set;}
|
||||||
public int FrameRate {get;set;}
|
public int FrameRate {get;set;}
|
||||||
|
|
|
@ -133,7 +133,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
int count = videos.Count;
|
int count = videos.Count;
|
||||||
foreach(var v in videos)
|
foreach(var v in videos)
|
||||||
{
|
{
|
||||||
await storage1.WriteAllTextAsync($"Info/{v.Id}.json",JsonConvert.SerializeObject(v));
|
await storage1.WriteVideoInfoAsync(v);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<SavedVideo> GetSavedVideoAsync(VideoId id)
|
public async Task<SavedVideo> GetSavedVideoAsync(VideoId id)
|
||||||
{
|
{
|
||||||
VideoMediaContext context=new VideoMediaContext(id,Resolution.PreMuxed);
|
VideoMediaContext context=new VideoMediaContext(id,Resolution.PreMuxed);
|
||||||
|
@ -128,6 +130,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
private async Task DownloadVideoAsync(SavedVideo video, Resolution resolution, CancellationToken token=default(CancellationToken),IProgress<double> progress=null,bool report=true)
|
private async Task DownloadVideoAsync(SavedVideo video, Resolution resolution, CancellationToken token=default(CancellationToken),IProgress<double> progress=null,bool report=true)
|
||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
|
if(video.DownloadFrom == "YouTube")
|
||||||
|
{
|
||||||
switch (resolution)
|
switch (resolution)
|
||||||
{
|
{
|
||||||
case Resolution.Mux:
|
case Resolution.Mux:
|
||||||
|
@ -143,12 +147,103 @@ namespace Tesses.YouTubeDownloader
|
||||||
await DownloadVideoOnlyAsync(video,token,progress,report);
|
await DownloadVideoOnlyAsync(video,token,progress,report);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}else if(video.DownloadFrom.StartsWith("NormalDownload,Length="))
|
||||||
|
{
|
||||||
|
await DownloadFileAsync(video,token,progress,report);
|
||||||
|
}
|
||||||
}catch(Exception ex)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
await GetLogger().WriteAsync(ex,video.Id);
|
VideoId? id=VideoId.TryParse(video.Id);
|
||||||
|
if(id.HasValue){
|
||||||
|
await GetLogger().WriteAsync(ex,id.Value);
|
||||||
|
}else{
|
||||||
|
await GetLogger().WriteAsync(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 file_path = $"Download/{B64.Base64UrlEncodes(video.Id)}.bin";
|
||||||
|
string url = video.Id;
|
||||||
|
bool canSeek=false;
|
||||||
|
long length=0;
|
||||||
|
|
||||||
|
foreach(var kvp in video.DownloadFrom.Split(',').Select<string,KeyValuePair<string,string>>((e)=>{
|
||||||
|
if(!e.Contains('=')) return new KeyValuePair<string, string>("","");
|
||||||
|
string[] keyVP = e.Split(new char[]{'='},2);
|
||||||
|
|
||||||
|
|
||||||
|
return new KeyValuePair<string, string>(keyVP[0],keyVP[1]);}))
|
||||||
|
{
|
||||||
|
switch(kvp.Key)
|
||||||
|
{
|
||||||
|
case "CanSeek":
|
||||||
|
bool.TryParse(kvp.Value,out canSeek);
|
||||||
|
break;
|
||||||
|
case "Length":
|
||||||
|
long len;
|
||||||
|
if(long.TryParse(kvp.Value,out len))
|
||||||
|
{
|
||||||
|
length=len;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await ReportStartVideo(video,Resolution.PreMuxed,length);
|
||||||
|
Func<long,Task<Stream>> openDownload = async(e)=>{
|
||||||
|
HttpRequestMessage msg=new HttpRequestMessage(HttpMethod.Get,url);
|
||||||
|
if(e > 0)
|
||||||
|
{
|
||||||
|
msg.Headers.Range.Ranges.Add(new System.Net.Http.Headers.RangeItemHeaderValue(e,null));
|
||||||
|
}
|
||||||
|
|
||||||
|
var res=await HttpClient.SendAsync(msg);
|
||||||
|
return await res.Content.ReadAsStreamAsync();
|
||||||
|
};
|
||||||
|
|
||||||
|
if(await Continue(file_path))
|
||||||
|
{
|
||||||
|
bool deleteAndRestart=false;
|
||||||
|
|
||||||
|
using(var file = await OpenOrCreateAsync(incomplete_file_path))
|
||||||
|
{
|
||||||
|
if(file.Length > 0 && !canSeek)
|
||||||
|
{
|
||||||
|
deleteAndRestart = true;
|
||||||
|
}
|
||||||
|
if(!deleteAndRestart)
|
||||||
|
{
|
||||||
|
Stream strm=await openDownload(file.Length);
|
||||||
|
bool res=await CopyStreamAsync(strm,file,0,length,4096,progress,token);
|
||||||
|
if(res)
|
||||||
|
{
|
||||||
|
RenameFile(incomplete_file_path,file_path);
|
||||||
|
if(report)
|
||||||
|
await ReportEndVideo(video, Resolution.PreMuxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(deleteAndRestart){
|
||||||
|
DeleteFile(incomplete_file_path);
|
||||||
|
using(var file = await OpenOrCreateAsync(incomplete_file_path))
|
||||||
|
{
|
||||||
|
Stream strm=await openDownload(0);
|
||||||
|
bool res=await CopyStreamAsync(strm,file,0,length,4096,progress,token);
|
||||||
|
if(res)
|
||||||
|
{
|
||||||
|
RenameFile(incomplete_file_path,file_path);
|
||||||
|
if(report)
|
||||||
|
await ReportEndVideo(video, Resolution.PreMuxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> Continue(string path)
|
public async Task<bool> Continue(string path)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -282,7 +377,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
curPos+=read;
|
curPos+=read;
|
||||||
await dest.WriteAsync(buffer,0,read);
|
await dest.WriteAsync(buffer,0,read);
|
||||||
if(progress != null)
|
if(progress != null && len != 0)
|
||||||
{
|
{
|
||||||
progress.Report(curPos / len);
|
progress.Report(curPos / len);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
Task AddChannelAsync(ChannelId id,Resolution resolution=Resolution.PreMuxed);
|
Task AddChannelAsync(ChannelId id,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);
|
||||||
IReadOnlyList<(SavedVideo Video,Resolution Resolution)> GetQueueList();
|
IReadOnlyList<(SavedVideo Video,Resolution Resolution)> GetQueueList();
|
||||||
SavedVideoProgress GetProgress();
|
SavedVideoProgress GetProgress();
|
||||||
IAsyncEnumerable<Subscription> GetSubscriptionsAsync();
|
IAsyncEnumerable<Subscription> GetSubscriptionsAsync();
|
||||||
|
@ -26,5 +26,6 @@ namespace Tesses.YouTubeDownloader
|
||||||
Task SubscribeAsync(UserName name,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload);
|
Task SubscribeAsync(UserName name,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload);
|
||||||
Task ResubscribeAsync(ChannelId id,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload);
|
Task ResubscribeAsync(ChannelId id,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload);
|
||||||
void DeletePersonalPlaylist(string name);
|
void DeletePersonalPlaylist(string name);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public interface IStorage : IWritable, IDownloader, ITYTDBase
|
public interface IStorage : IWritable, IDownloader, ITYTDBase
|
||||||
{
|
{
|
||||||
|
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);
|
||||||
Task WriteVideoInfoAsync(SavedVideo channel);
|
Task WriteVideoInfoAsync(SavedVideo channel);
|
||||||
|
|
|
@ -15,7 +15,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public interface ITYTDBase : IPersonalPlaylistGet
|
public interface ITYTDBase : IPersonalPlaylistGet
|
||||||
{
|
{
|
||||||
|
Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id);
|
||||||
|
bool BestStreamInfoExists(VideoId id);
|
||||||
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
|
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
|
||||||
Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path);
|
Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path);
|
||||||
|
|
||||||
|
|
|
@ -37,13 +37,13 @@ namespace Tesses.YouTubeDownloader
|
||||||
SavedChannel channel;
|
SavedChannel channel;
|
||||||
if(Id.HasValue) //dont check for if(Id != null) hince I was looking for several minutes for the bug
|
if(Id.HasValue) //dont check for if(Id != null) hince I was looking for several minutes for the bug
|
||||||
{
|
{
|
||||||
string path=$"Channel/{Id.Value}.json";
|
//string path=$"Channel/{Id.Value}.json";
|
||||||
if(await storage.Continue(path))
|
if(!storage.ChannelInfoExists(Id.Value))
|
||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
channel=await DownloadThumbnails(storage,await storage.YoutubeClient.Channels.GetAsync(Id.Value));
|
channel=await DownloadThumbnails(storage,await storage.YoutubeClient.Channels.GetAsync(Id.Value));
|
||||||
//channel=new SavedChannel(i);
|
//channel=new SavedChannel(i);
|
||||||
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(channel));
|
await storage.WriteChannelInfoAsync(channel);
|
||||||
}catch(Exception ex)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
await storage.GetLogger().WriteAsync(ex);
|
await storage.GetLogger().WriteAsync(ex);
|
||||||
|
@ -51,16 +51,16 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
return channel;
|
return channel;
|
||||||
}else{
|
}else{
|
||||||
var j=JsonConvert.DeserializeObject<SavedChannel>(await storage.ReadAllTextAsync(path));
|
var j=await storage.GetChannelInfoAsync(Id.Value);
|
||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
var c=await storage.YoutubeClient.Channels.GetByUserAsync(name1);
|
var c=await storage.YoutubeClient.Channels.GetByUserAsync(name1);
|
||||||
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(await storage.Continue(path))
|
if(!storage.ChannelInfoExists(c.Id.Value))
|
||||||
{
|
{
|
||||||
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(channel));
|
await storage.WriteChannelInfoAsync(channel);
|
||||||
|
|
||||||
}
|
}
|
||||||
return channel;
|
return channel;
|
||||||
|
@ -118,7 +118,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
public async Task FillQueue(TYTDStorage storage, List<(SavedVideo video, Resolution resolution)> Queue)
|
public async Task FillQueue(TYTDStorage storage, List<(SavedVideo video, Resolution resolution)> Queue)
|
||||||
{
|
{
|
||||||
|
|
||||||
string path=$"Playlist/{Id}.json";
|
// string path=$"Playlist/{Id}.json";
|
||||||
List<IVideo> videos=new List<IVideo>();
|
List<IVideo> videos=new List<IVideo>();
|
||||||
try{
|
try{
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
await cmc.GetChannel(storage);
|
await cmc.GetChannel(storage);
|
||||||
|
|
||||||
}
|
}
|
||||||
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(p));
|
await storage.WritePlaylistInfoAsync(p);
|
||||||
}catch(Exception ex)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
await storage.GetLogger().WriteAsync(ex);
|
await storage.GetLogger().WriteAsync(ex);
|
||||||
|
@ -148,6 +148,76 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
internal class NormalDownloadMediaContext : IMediaContext
|
||||||
|
{
|
||||||
|
public NormalDownloadMediaContext(string url,bool download=true)
|
||||||
|
{
|
||||||
|
this.url=url;
|
||||||
|
this.download=download;
|
||||||
|
}
|
||||||
|
bool download;
|
||||||
|
string url;
|
||||||
|
public async Task FillQueue(TYTDStorage storage, List<(SavedVideo video, Resolution resolution)> Queue)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
SavedVideo video=new SavedVideo();
|
||||||
|
if(storage.DownloadExists(url)){
|
||||||
|
video = await storage.GetDownloadInfoAsync(url);
|
||||||
|
}else{
|
||||||
|
video.Id = url;
|
||||||
|
|
||||||
|
await GetFileNameAsync(storage,video);
|
||||||
|
}
|
||||||
|
lock(Queue){
|
||||||
|
Queue.Add((video,Resolution.PreMuxed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task GetFileNameAsync(TYTDStorage storage,SavedVideo video)
|
||||||
|
{
|
||||||
|
string[] uri0=url.Split(new char[]{'?'},2,StringSplitOptions.None);
|
||||||
|
string filename=Path.GetFileName(uri0[0]);
|
||||||
|
System.Net.Http.HttpRequestMessage message=new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Head,url);
|
||||||
|
message.Headers.Add("Range","bytes=0-");
|
||||||
|
|
||||||
|
var head=await storage.HttpClient.SendAsync(message);
|
||||||
|
if(head.Content.Headers.ContentDisposition != null && !string.IsNullOrWhiteSpace(head.Content.Headers.ContentDisposition.FileName))
|
||||||
|
{
|
||||||
|
filename = head.Content.Headers.ContentDisposition.FileName;
|
||||||
|
}
|
||||||
|
long length = 0;
|
||||||
|
if(head.Content.Headers.ContentLength.HasValue)
|
||||||
|
{
|
||||||
|
length = head.Content.Headers.ContentLength.Value;
|
||||||
|
}
|
||||||
|
video.Title = filename;
|
||||||
|
var res=head.StatusCode == System.Net.HttpStatusCode.PartialContent ? "true" : "false";
|
||||||
|
video.DownloadFrom=$"NormalDownload,Length={length},CanSeek={res}";
|
||||||
|
video.AuthorTitle = "NotYouTube";
|
||||||
|
video.AuthorChannelId = "TYTD_FILEDOWNLOAD";
|
||||||
|
List<string> hdrs=new List<string>();
|
||||||
|
foreach(var hdr in head.Content.Headers)
|
||||||
|
{
|
||||||
|
foreach(var item in hdr.Value){
|
||||||
|
hdrs.Add($"{hdr.Key}: {item}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string headers=string.Join("\n",hdrs);
|
||||||
|
video.Description=$"File Download on \"{DateTime.Now.ToShortDateString()}\" at \"{DateTime.Now.ToShortTimeString()}\"\nHeaders:\n{headers}";
|
||||||
|
video.Likes=42;
|
||||||
|
video.Dislikes=42;
|
||||||
|
video.Views=42;
|
||||||
|
video.Duration = new TimeSpan(0,0,0);
|
||||||
|
video.Keywords = new string[] {"FILE"};
|
||||||
|
if(head.Headers.Date.HasValue)
|
||||||
|
{
|
||||||
|
video.UploadDate = head.Headers.Date.Value.DateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
await storage.WriteVideoInfoAsync(video);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
internal class VideoMediaContext : IMediaContext
|
internal class VideoMediaContext : IMediaContext
|
||||||
{
|
{
|
||||||
VideoId Id;
|
VideoId Id;
|
||||||
|
@ -161,15 +231,15 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public async Task FillQueue(TYTDStorage storage,List<(SavedVideo,Resolution)> queue)
|
public async Task FillQueue(TYTDStorage storage,List<(SavedVideo,Resolution)> queue)
|
||||||
{
|
{
|
||||||
string path=$"Info/{Id}.json";
|
|
||||||
SavedVideo video;
|
SavedVideo video;
|
||||||
if(await storage.Continue(path))
|
if(!storage.VideoInfoExists(Id))
|
||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
video = new SavedVideo(await storage.YoutubeClient.Videos.GetAsync(Id));
|
video = new SavedVideo(await storage.YoutubeClient.Videos.GetAsync(Id));
|
||||||
|
|
||||||
storage.SendBeforeSaveInfo(video);
|
storage.SendBeforeSaveInfo(video);
|
||||||
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(video));
|
await storage.WriteVideoInfoAsync(video);
|
||||||
await video.DownloadThumbnails(storage);
|
await video.DownloadThumbnails(storage);
|
||||||
}catch(Exception ex)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -179,7 +249,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
video = JsonConvert.DeserializeObject<SavedVideo>(await storage.ReadAllTextAsync(path));
|
video = await storage.GetVideoInfoAsync(Id);
|
||||||
}
|
}
|
||||||
if(storage.GetLoggerProperties().AlwaysDownloadChannel)
|
if(storage.GetLoggerProperties().AlwaysDownloadChannel)
|
||||||
{
|
{
|
||||||
|
|
|
@ -51,16 +51,22 @@ namespace Tesses.YouTubeDownloader
|
||||||
public abstract void DeleteFile(string file);
|
public abstract void DeleteFile(string file);
|
||||||
public abstract void DeleteDirectory(string dir,bool recursive=false);
|
public abstract void DeleteDirectory(string dir,bool recursive=false);
|
||||||
|
|
||||||
|
public virtual async Task WriteBestStreamInfoAsync(VideoId id,BestStreamInfo.BestStreamsSerialized serialized)
|
||||||
public async Task WriteVideoInfoAsync(SavedVideo info)
|
|
||||||
{
|
{
|
||||||
string file = $"Info/{info.Id}.json";
|
await WriteAllTextAsync($"StreamInfo/{id.Value}.json",JsonConvert.SerializeObject(serialized));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task WriteVideoInfoAsync(SavedVideo info)
|
||||||
|
{
|
||||||
|
|
||||||
|
string file = info.DownloadFrom.StartsWith("NormalDownload,Length=") ? $"FileInfo/{B64.Base64UrlEncodes(info.Id)}.json" : $"Info/{info.Id}.json";
|
||||||
if(!FileExists(file))
|
if(!FileExists(file))
|
||||||
{
|
{
|
||||||
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
|
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task WritePlaylistInfoAsync(SavedPlaylist info)
|
public virtual async Task WritePlaylistInfoAsync(SavedPlaylist info)
|
||||||
{
|
{
|
||||||
string file = $"Playlist/{info.Id}.json";
|
string file = $"Playlist/{info.Id}.json";
|
||||||
if(!FileExists(file))
|
if(!FileExists(file))
|
||||||
|
@ -68,7 +74,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
|
await WriteAllTextAsync(file,JsonConvert.SerializeObject(info));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public 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(!FileExists(file))
|
||||||
|
@ -111,6 +117,14 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
await Task.FromResult(0);
|
await Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
public async Task AddFileAsync(string url,bool download=true)
|
||||||
|
{
|
||||||
|
lock(Temporary)
|
||||||
|
{
|
||||||
|
Temporary.Add(new NormalDownloadMediaContext(url,download));
|
||||||
|
}
|
||||||
|
await Task.FromResult(0);
|
||||||
|
}
|
||||||
public void CreateDirectoryIfNotExist(string dir)
|
public void CreateDirectoryIfNotExist(string dir)
|
||||||
{
|
{
|
||||||
if(!DirectoryExists(dir))
|
if(!DirectoryExists(dir))
|
||||||
|
@ -151,6 +165,9 @@ namespace Tesses.YouTubeDownloader
|
||||||
CreateDirectoryIfNotExist("Thumbnails");
|
CreateDirectoryIfNotExist("Thumbnails");
|
||||||
CreateDirectoryIfNotExist("config");
|
CreateDirectoryIfNotExist("config");
|
||||||
CreateDirectoryIfNotExist("config/logs");
|
CreateDirectoryIfNotExist("config/logs");
|
||||||
|
CreateDirectoryIfNotExist("FileInfo");
|
||||||
|
CreateDirectoryIfNotExist("Download");
|
||||||
|
CreateDirectoryIfNotExist("StreamInfo");
|
||||||
}
|
}
|
||||||
public void StartLoop(CancellationToken token = default(CancellationToken))
|
public void StartLoop(CancellationToken token = default(CancellationToken))
|
||||||
{
|
{
|
||||||
|
@ -179,7 +196,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
public virtual async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
||||||
{
|
{
|
||||||
List<ListContentItem> items0=new List<ListContentItem>();
|
List<ListContentItem> items0=new List<ListContentItem>();
|
||||||
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
||||||
|
@ -191,18 +208,18 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
public virtual async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
||||||
{
|
{
|
||||||
|
|
||||||
await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items.ToList()));
|
await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items.ToList()));
|
||||||
|
|
||||||
}
|
}
|
||||||
public void DeletePersonalPlaylist(string name)
|
public virtual void DeletePersonalPlaylist(string name)
|
||||||
{
|
{
|
||||||
DeleteFile($"PersonalPlaylist/{name}.json");
|
DeleteFile($"PersonalPlaylist/{name}.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
|
public virtual async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
|
||||||
{
|
{
|
||||||
List<ListContentItem> items0=new List<ListContentItem>();
|
List<ListContentItem> items0=new List<ListContentItem>();
|
||||||
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
||||||
|
@ -217,7 +234,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution)
|
public virtual async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution)
|
||||||
{
|
{
|
||||||
List<ListContentItem> items0=new List<ListContentItem>();
|
List<ListContentItem> items0=new List<ListContentItem>();
|
||||||
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
||||||
|
|
|
@ -17,11 +17,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
public abstract class TYTDBase : ITYTDBase
|
public abstract class TYTDBase : ITYTDBase
|
||||||
{
|
{
|
||||||
|
|
||||||
public bool PersonalPlaylistExists(string name)
|
public virtual bool PersonalPlaylistExists(string name)
|
||||||
{
|
{
|
||||||
return FileExists($"PersonalPlaylist/{name}.json");
|
return FileExists($"PersonalPlaylist/{name}.json");
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string playlist)
|
public virtual async IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string playlist)
|
||||||
{
|
{
|
||||||
if(!PersonalPlaylistExists(playlist)) yield break;
|
if(!PersonalPlaylistExists(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"));
|
||||||
|
@ -30,7 +30,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
yield return await Task.FromResult(item);
|
yield return await Task.FromResult(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<string> GetPersonalPlaylistsAsync()
|
public virtual async IAsyncEnumerable<string> GetPersonalPlaylistsAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in EnumerateFilesAsync("PersonalPlaylist"))
|
await foreach(var item in EnumerateFilesAsync("PersonalPlaylist"))
|
||||||
{
|
{
|
||||||
|
@ -62,7 +62,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
return FileExistsAsync(path).GetAwaiter().GetResult();
|
return FileExistsAsync(path).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> GetVideoIdsAsync()
|
public virtual async IAsyncEnumerable<string> GetVideoIdsAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in EnumerateFilesAsync("Info"))
|
await foreach(var item in EnumerateFilesAsync("Info"))
|
||||||
{
|
{
|
||||||
|
@ -73,13 +73,13 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SavedVideo> GetVideoInfoAsync(VideoId id)
|
public virtual async Task<SavedVideo> GetVideoInfoAsync(VideoId id)
|
||||||
{
|
{
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync($"Info/{id}.json"));
|
return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync($"Info/{id}.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<SavedVideo> GetVideosAsync()
|
public virtual async IAsyncEnumerable<SavedVideo> GetVideosAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in GetVideoIdsAsync())
|
await foreach(var item in GetVideoIdsAsync())
|
||||||
{
|
{
|
||||||
|
@ -90,7 +90,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<SavedVideoLegacy> GetLegacyVideosAsync()
|
public virtual async IAsyncEnumerable<SavedVideoLegacy> GetLegacyVideosAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in GetVideoIdsAsync())
|
await foreach(var item in GetVideoIdsAsync())
|
||||||
{
|
{
|
||||||
|
@ -101,11 +101,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<SavedVideoLegacy> GetLegacyVideoInfoAsync(VideoId id)
|
public virtual async Task<SavedVideoLegacy> GetLegacyVideoInfoAsync(VideoId id)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<SavedVideoLegacy>(await ReadAllTextAsync($"Info/{id}.json"));
|
return JsonConvert.DeserializeObject<SavedVideoLegacy>(await ReadAllTextAsync($"Info/{id}.json"));
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync()
|
public virtual async IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in GetPlaylistIdsAsync())
|
await foreach(var item in GetPlaylistIdsAsync())
|
||||||
{
|
{
|
||||||
|
@ -131,7 +131,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<string> GetPlaylistIdsAsync()
|
public virtual async IAsyncEnumerable<string> GetPlaylistIdsAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in EnumerateFilesAsync("Playlist"))
|
await foreach(var item in EnumerateFilesAsync("Playlist"))
|
||||||
{
|
{
|
||||||
|
@ -141,7 +141,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<string> GetChannelIdsAsync()
|
public virtual async IAsyncEnumerable<string> GetChannelIdsAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in EnumerateFilesAsync("Channel"))
|
await foreach(var item in EnumerateFilesAsync("Channel"))
|
||||||
{
|
{
|
||||||
|
@ -151,7 +151,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
|
public virtual async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in GetVideoIdsAsync())
|
await foreach(var item in GetVideoIdsAsync())
|
||||||
{
|
{
|
||||||
|
@ -159,11 +159,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
if(id.HasValue) yield return id.Value;
|
if(id.HasValue) yield return id.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<SavedChannel> GetChannelInfoAsync(ChannelId id)
|
public virtual async Task<SavedChannel> GetChannelInfoAsync(ChannelId id)
|
||||||
{
|
{
|
||||||
return JsonConvert.DeserializeObject<SavedChannel>(await ReadAllTextAsync($"Channel/{id}.json"));
|
return JsonConvert.DeserializeObject<SavedChannel>(await ReadAllTextAsync($"Channel/{id}.json"));
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<SavedChannel> GetChannelsAsync()
|
public virtual async IAsyncEnumerable<SavedChannel> GetChannelsAsync()
|
||||||
{
|
{
|
||||||
await foreach(var item in GetChannelIdsAsync())
|
await foreach(var item in GetChannelIdsAsync())
|
||||||
{
|
{
|
||||||
|
@ -175,24 +175,60 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool PlaylistInfoExists(PlaylistId id)
|
public virtual async IAsyncEnumerable<SavedVideo> GetDownloadsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in GetDownloadUrlsAsync())
|
||||||
|
{
|
||||||
|
yield return await GetDownloadInfoAsync(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public virtual async IAsyncEnumerable<string> GetDownloadUrlsAsync()
|
||||||
|
{
|
||||||
|
await foreach(var item in EnumerateFilesAsync("FileInfo"))
|
||||||
|
{
|
||||||
|
if(Path.GetExtension(item).Equals(".json",StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
yield return B64.Base64UrlDecodes(Path.GetFileNameWithoutExtension(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public virtual async Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<BestStreamInfo.BestStreamsSerialized>(await ReadAllTextAsync($"StreamInfo/{id.Value}.json"));
|
||||||
|
}
|
||||||
|
public virtual bool BestStreamInfoExists(VideoId id)
|
||||||
|
{
|
||||||
|
return FileExists($"StreamInfo/{id.Value}.json");
|
||||||
|
}
|
||||||
|
public virtual async Task<SavedVideo> GetDownloadInfoAsync(string url)
|
||||||
|
{
|
||||||
|
string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json";
|
||||||
|
return JsonConvert.DeserializeObject<SavedVideo>(await ReadAllTextAsync(enc));
|
||||||
|
|
||||||
|
}
|
||||||
|
public virtual bool DownloadExists(string url)
|
||||||
|
{
|
||||||
|
string enc=$"FileInfo/{B64.Base64UrlEncodes(url)}.json";
|
||||||
|
return FileExists(enc);
|
||||||
|
}
|
||||||
|
public virtual bool PlaylistInfoExists(PlaylistId id)
|
||||||
{
|
{
|
||||||
return FileExists($"Playlist/{id}.json");
|
return FileExists($"Playlist/{id}.json");
|
||||||
}
|
}
|
||||||
public bool VideoInfoExists(VideoId id)
|
public virtual bool VideoInfoExists(VideoId id)
|
||||||
{
|
{
|
||||||
return FileExists($"Info/{id}.json");
|
return FileExists($"Info/{id}.json");
|
||||||
}
|
}
|
||||||
public bool ChannelInfoExists(ChannelId id)
|
public virtual bool ChannelInfoExists(ChannelId id)
|
||||||
{
|
{
|
||||||
return FileExists($"Channel/{id}.json");
|
return FileExists($"Channel/{id}.json");
|
||||||
}
|
}
|
||||||
public 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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ReadAllTextAsync(string file)
|
public virtual async Task<string> ReadAllTextAsync(string file)
|
||||||
{
|
{
|
||||||
using(var s = await OpenReadAsync(file))
|
using(var s = await OpenReadAsync(file))
|
||||||
{
|
{
|
||||||
|
@ -203,12 +239,12 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DirectoryExists(string path)
|
public virtual bool DirectoryExists(string path)
|
||||||
{
|
{
|
||||||
return DirectoryExistsAsync(path).GetAwaiter().GetResult();
|
return DirectoryExistsAsync(path).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> EnumerateFiles(string path)
|
public virtual IEnumerable<string> EnumerateFiles(string path)
|
||||||
{
|
{
|
||||||
var e = EnumerateFilesAsync(path).GetAsyncEnumerator();
|
var e = EnumerateFilesAsync(path).GetAsyncEnumerator();
|
||||||
while(e.MoveNextAsync().GetAwaiter().GetResult())
|
while(e.MoveNextAsync().GetAwaiter().GetResult())
|
||||||
|
@ -216,7 +252,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
yield return e.Current;
|
yield return e.Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public IEnumerable<string> EnumerateDirectories(string path)
|
public virtual IEnumerable<string> EnumerateDirectories(string path)
|
||||||
{
|
{
|
||||||
var e = EnumerateDirectoriesAsync(path).GetAsyncEnumerator();
|
var e = EnumerateDirectoriesAsync(path).GetAsyncEnumerator();
|
||||||
while(e.MoveNextAsync().GetAwaiter().GetResult())
|
while(e.MoveNextAsync().GetAwaiter().GetResult())
|
||||||
|
|
|
@ -256,7 +256,15 @@ internal class SegmentedHttpStream : Stream
|
||||||
_=ex;
|
_=ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async Task AddFileAsync(string url,bool download=true)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
await client.GetStringAsync($"{url}api/v2/AddFile?url={WebUtility.UrlEncode(url)}&download={download}");
|
||||||
|
}catch(Exception ex)
|
||||||
|
{
|
||||||
|
_=ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
public override async Task<bool> DirectoryExistsAsync(string path)
|
public override async Task<bool> DirectoryExistsAsync(string path)
|
||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
|
|
|
@ -346,6 +346,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
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)
|
||||||
|
{
|
||||||
|
if(Downloader != null)
|
||||||
|
await Downloader.AddFileAsync(url,download);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed)
|
public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
|
@ -660,6 +665,33 @@ namespace Tesses.YouTubeDownloader
|
||||||
e.DeletePersonalPlaylist(name);
|
e.DeletePersonalPlaylist(name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task WriteBestStreamInfoAsync(VideoId id, BestStreamInfo.BestStreamsSerialized serialized)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.WriteBestStreamInfoAsync(id,serialized);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id)
|
||||||
|
{
|
||||||
|
BestStreamInfo.BestStreamsSerialized s=null;
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
s=await e.GetBestStreamInfoAsync(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BestStreamInfoExists(VideoId id)
|
||||||
|
{
|
||||||
|
bool res=false;
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
res=e.BestStreamInfoExists(id);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DownloaderMigration
|
public class DownloaderMigration
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
<PackageId>Tesses.YouTubeDownloader</PackageId>
|
<PackageId>Tesses.YouTubeDownloader</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
<Version>1.1.5</Version>
|
<Version>1.1.6</Version>
|
||||||
<AssemblyVersion>1.1.5</AssemblyVersion>
|
<AssemblyVersion>1.1.6</AssemblyVersion>
|
||||||
<FileVersion>1.1.5</FileVersion>
|
<FileVersion>1.1.6</FileVersion>
|
||||||
<Description>A YouTube Downloader</Description>
|
<Description>A YouTube Downloader</Description>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
|
Loading…
Reference in New Issue