Added File Download, abstracted JSON

This commit is contained in:
Mike Nolan 2022-07-06 17:59:50 -05:00
parent f79c6122fb
commit 08675d678b
27 changed files with 874 additions and 82 deletions

2
.gitignore vendored
View File

@ -143,4 +143,6 @@ Tesses.YouTubeDownloader.Net6/config/
Tesses.YouTubeDownloader.Net6/Playlist/
Tesses.YouTubeDownloader.Net6/Channel/
Tesses.YouTubeDownloader.Net6/Subscriptions/
Tesses.YouTubeDownloader.Net6/Download/
Tesses.YouTubeDownloader.Net6/FileInfo/
push

View File

@ -11,9 +11,9 @@
<PackageId>Tesses.YouTubeDownloader.ExtensionLoader</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.1.1</Version>
<AssemblyVersion>1.1.1</AssemblyVersion>
<FileVersion>1.1.1</FileVersion>
<Version>1.1.2</Version>
<AssemblyVersion>1.1.2</AssemblyVersion>
<FileVersion>1.1.2</FileVersion>
<Description>Load Extensions into TYTD (Not Tested)</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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>

View File

@ -0,0 +1,4 @@
{
"Url": "http://10.137.42.142:3252/",
"LocalFiles": "/media/mike/PhotoDrive/wii-vids/working"
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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>

View File

@ -25,6 +25,79 @@ namespace Tesses.YouTubeDownloader.Server
public override async Task GetAsync(ServerContext ctx)
{
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/"))
{
string id_res=path.Substring(12);
@ -43,7 +116,7 @@ namespace Tesses.YouTubeDownloader.Server
// 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("/AddItem/"))
{
@ -58,8 +131,59 @@ namespace Tesses.YouTubeDownloader.Server
// 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("/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
@ -332,6 +456,7 @@ namespace Tesses.YouTubeDownloader.Server
AddBoth("/AddUser",AddUser);
AddBoth("/AddPlaylist",AddPlaylist);
AddBoth("/AddVideo",AddVideo);
AddBoth("/AddFile",AddFile);
AddBoth("/Progress",ProgressFunc);
AddBoth("/QueueList",QueueList);
AddBoth("/subscribe",Subscribe);
@ -641,6 +766,28 @@ namespace Tesses.YouTubeDownloader.Server
public async Task ProgressFunc(ServerContext ctx)
{
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)
{

View File

@ -5,7 +5,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Tesses.WebServer" Version="1.0.3.4" />
<PackageReference Include="Tesses.WebServer" Version="1.0.3.5" />
</ItemGroup>
<PropertyGroup>
@ -15,9 +15,9 @@
<PackageId>Tesses.YouTubeDownloader.Server</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.1.3</Version>
<AssemblyVersion>1.1.3</AssemblyVersion>
<FileVersion>1.1.3</FileVersion>
<Version>1.1.4</Version>
<AssemblyVersion>1.1.4</AssemblyVersion>
<FileVersion>1.1.4</FileVersion>
<Description>Adds WebServer to TYTD</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>

View File

@ -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
}
}
}

View File

@ -32,7 +32,7 @@ namespace Tesses.YouTubeDownloader
var f= await BestStreamInfo.GetBestStreams(storage,video.Id);
if(f ==null)
return "";
return resolution == Resolution.NoDownload ? "" : resolution == Resolution.Mux ? $"Mux/{video.Id}.mkv" : $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mp4";
if(f.VideoFrozen)
{
@ -61,13 +61,12 @@ namespace Tesses.YouTubeDownloader
public static async Task<BestStreams> GetBestStreams(ITYTDBase storage,VideoId id)
{
//Console.WriteLine("IN FUNC");
if(storage.DirectoryExists("StreamInfo"))
{
//Console.WriteLine("DIR");
if(storage.FileExists($"StreamInfo/{id.Value}.json"))
if(storage.BestStreamInfoExists(id))
{
//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();
streams.VideoOnlyStreamInfo = new BestStreamInfo(serialization.VideoOnly);
@ -76,16 +75,16 @@ namespace Tesses.YouTubeDownloader
return streams;
}
}
return null;
}
public static async Task<BestStreams> GetBestStreams(IStorage storage,VideoId id,CancellationToken token=default(CancellationToken),bool expire_check=true)
{
if(storage.DirectoryExists("StreamInfo"))
{
if(storage.FileExists($"StreamInfo/{id.Value}.json"))
if(storage.BestStreamInfoExists(id))
{
BestStreamsSerialized serialization=JsonConvert.DeserializeObject<BestStreamsSerialized>(await storage.ReadAllTextAsync($"StreamInfo/{id.Value}.json"));
BestStreamsSerialized serialization=await storage.GetBestStreamInfoAsync(id);
if(DateTime.Now < serialization.Expires || !expire_check)
{
BestStreams streams=new BestStreams();
@ -95,9 +94,7 @@ namespace Tesses.YouTubeDownloader
return streams;
}
}
}else{
storage.CreateDirectory("StreamInfo");
}
DateTime expires=DateTime.Now.AddHours(6);
try{
if(storage.VideoInfoExists(id))
@ -126,8 +123,7 @@ namespace Tesses.YouTubeDownloader
streams1.MuxedStreamInfo =new BestStreamInfo();
streams1.MuxedStreamInfo.SetInfo(muxed);
serialized.Muxed = streams1.MuxedStreamInfo.Serialization;
await storage.WriteAllTextAsync($"StreamInfo/{id.Value}.json",JsonConvert.SerializeObject(serialized));
await storage.WriteBestStreamInfoAsync(id,serialized);
return streams1;
}catch(YoutubeExplodeException ex)
{
@ -135,7 +131,7 @@ namespace Tesses.YouTubeDownloader
return null;
}
}
private class BestStreamsSerialized
public class BestStreamsSerialized
{
public DateTime Expires {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 int FrameRate {get;set;}

View File

@ -133,7 +133,7 @@ namespace Tesses.YouTubeDownloader
int count = videos.Count;
foreach(var v in videos)
{
await storage1.WriteAllTextAsync($"Info/{v.Id}.json",JsonConvert.SerializeObject(v));
await storage1.WriteVideoInfoAsync(v);
}

View File

@ -112,6 +112,8 @@ namespace Tesses.YouTubeDownloader
}
public async Task<SavedVideo> GetSavedVideoAsync(VideoId id)
{
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)
{
try{
if(video.DownloadFrom == "YouTube")
{
switch (resolution)
{
case Resolution.Mux:
@ -143,12 +147,103 @@ namespace Tesses.YouTubeDownloader
await DownloadVideoOnlyAsync(video,token,progress,report);
break;
}
}else if(video.DownloadFrom.StartsWith("NormalDownload,Length="))
{
await DownloadFileAsync(video,token,progress,report);
}
}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)
{
@ -282,7 +377,7 @@ namespace Tesses.YouTubeDownloader
}
curPos+=read;
await dest.WriteAsync(buffer,0,read);
if(progress != null)
if(progress != null && len != 0)
{
progress.Report(curPos / len);
}

View File

@ -17,7 +17,7 @@ namespace Tesses.YouTubeDownloader
Task AddChannelAsync(ChannelId id,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();
SavedVideoProgress GetProgress();
IAsyncEnumerable<Subscription> GetSubscriptionsAsync();
@ -26,5 +26,6 @@ namespace Tesses.YouTubeDownloader
Task SubscribeAsync(UserName name,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload);
Task ResubscribeAsync(ChannelId id,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload);
void DeletePersonalPlaylist(string name);
}
}

View File

@ -15,7 +15,7 @@ namespace Tesses.YouTubeDownloader
{
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> Continue(string path);
Task WriteVideoInfoAsync(SavedVideo channel);

View File

@ -15,7 +15,8 @@ namespace Tesses.YouTubeDownloader
{
public interface ITYTDBase : IPersonalPlaylistGet
{
Task<BestStreamInfo.BestStreamsSerialized> GetBestStreamInfoAsync(VideoId id);
bool BestStreamInfoExists(VideoId id);
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path);

View File

@ -37,13 +37,13 @@ namespace Tesses.YouTubeDownloader
SavedChannel channel;
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";
if(await storage.Continue(path))
//string path=$"Channel/{Id.Value}.json";
if(!storage.ChannelInfoExists(Id.Value))
{
try{
channel=await DownloadThumbnails(storage,await storage.YoutubeClient.Channels.GetAsync(Id.Value));
//channel=new SavedChannel(i);
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(channel));
await storage.WriteChannelInfoAsync(channel);
}catch(Exception ex)
{
await storage.GetLogger().WriteAsync(ex);
@ -51,16 +51,16 @@ namespace Tesses.YouTubeDownloader
}
return channel;
}else{
var j=JsonConvert.DeserializeObject<SavedChannel>(await storage.ReadAllTextAsync(path));
var j=await storage.GetChannelInfoAsync(Id.Value);
return j;
}
}else{
var c=await storage.YoutubeClient.Channels.GetByUserAsync(name1);
channel=await DownloadThumbnails(storage,c);
string path=$"Channel/{c.Id.Value}.json";
if(await storage.Continue(path))
//string path=$"Channel/{c.Id.Value}.json";
if(!storage.ChannelInfoExists(c.Id.Value))
{
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(channel));
await storage.WriteChannelInfoAsync(channel);
}
return channel;
@ -118,7 +118,7 @@ namespace Tesses.YouTubeDownloader
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>();
try{
@ -134,7 +134,7 @@ namespace Tesses.YouTubeDownloader
await cmc.GetChannel(storage);
}
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(p));
await storage.WritePlaylistInfoAsync(p);
}catch(Exception 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
{
VideoId Id;
@ -161,15 +231,15 @@ namespace Tesses.YouTubeDownloader
}
public async Task FillQueue(TYTDStorage storage,List<(SavedVideo,Resolution)> queue)
{
string path=$"Info/{Id}.json";
SavedVideo video;
if(await storage.Continue(path))
if(!storage.VideoInfoExists(Id))
{
try{
video = new SavedVideo(await storage.YoutubeClient.Videos.GetAsync(Id));
storage.SendBeforeSaveInfo(video);
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(video));
await storage.WriteVideoInfoAsync(video);
await video.DownloadThumbnails(storage);
}catch(Exception ex)
{
@ -179,7 +249,7 @@ namespace Tesses.YouTubeDownloader
}
}else{
video = JsonConvert.DeserializeObject<SavedVideo>(await storage.ReadAllTextAsync(path));
video = await storage.GetVideoInfoAsync(Id);
}
if(storage.GetLoggerProperties().AlwaysDownloadChannel)
{

View File

@ -50,17 +50,23 @@ namespace Tesses.YouTubeDownloader
public abstract void MoveDirectory(string src,string dest);
public abstract void DeleteFile(string file);
public abstract void DeleteDirectory(string dir,bool recursive=false);
public async Task WriteVideoInfoAsync(SavedVideo info)
public virtual async Task WriteBestStreamInfoAsync(VideoId id,BestStreamInfo.BestStreamsSerialized serialized)
{
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))
{
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";
if(!FileExists(file))
@ -68,7 +74,7 @@ namespace Tesses.YouTubeDownloader
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";
if(!FileExists(file))
@ -111,6 +117,14 @@ namespace Tesses.YouTubeDownloader
}
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)
{
if(!DirectoryExists(dir))
@ -151,6 +165,9 @@ namespace Tesses.YouTubeDownloader
CreateDirectoryIfNotExist("Thumbnails");
CreateDirectoryIfNotExist("config");
CreateDirectoryIfNotExist("config/logs");
CreateDirectoryIfNotExist("FileInfo");
CreateDirectoryIfNotExist("Download");
CreateDirectoryIfNotExist("StreamInfo");
}
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>();
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()));
}
public void DeletePersonalPlaylist(string name)
public virtual void DeletePersonalPlaylist(string name)
{
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>();
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>();
await foreach(var item in GetPersonalPlaylistContentsAsync(name))

View File

@ -17,11 +17,11 @@ namespace Tesses.YouTubeDownloader
public abstract class TYTDBase : ITYTDBase
{
public bool PersonalPlaylistExists(string name)
public virtual bool PersonalPlaylistExists(string name)
{
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;
var ls=JsonConvert.DeserializeObject<List<ListContentItem>>(await ReadAllTextAsync($"PersonalPlaylist/{playlist}.json"));
@ -30,7 +30,7 @@ namespace Tesses.YouTubeDownloader
yield return await Task.FromResult(item);
}
}
public async IAsyncEnumerable<string> GetPersonalPlaylistsAsync()
public virtual async IAsyncEnumerable<string> GetPersonalPlaylistsAsync()
{
await foreach(var item in EnumerateFilesAsync("PersonalPlaylist"))
{
@ -62,7 +62,7 @@ namespace Tesses.YouTubeDownloader
return FileExistsAsync(path).GetAwaiter().GetResult();
}
public async IAsyncEnumerable<string> GetVideoIdsAsync()
public virtual async IAsyncEnumerable<string> GetVideoIdsAsync()
{
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"));
}
public async IAsyncEnumerable<SavedVideo> GetVideosAsync()
public virtual async IAsyncEnumerable<SavedVideo> GetVideosAsync()
{
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())
{
@ -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"));
}
public async IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync()
public virtual async IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync()
{
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"))
{
@ -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"))
{
@ -151,7 +151,7 @@ namespace Tesses.YouTubeDownloader
}
}
}
public async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
public virtual async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
{
await foreach(var item in GetVideoIdsAsync())
{
@ -159,11 +159,11 @@ namespace Tesses.YouTubeDownloader
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"));
}
public async IAsyncEnumerable<SavedChannel> GetChannelsAsync()
public virtual async IAsyncEnumerable<SavedChannel> GetChannelsAsync()
{
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");
}
public bool VideoInfoExists(VideoId id)
public virtual bool VideoInfoExists(VideoId id)
{
return FileExists($"Info/{id}.json");
}
public bool ChannelInfoExists(ChannelId id)
public virtual bool ChannelInfoExists(ChannelId id)
{
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"));
}
public async Task<string> ReadAllTextAsync(string file)
public virtual async Task<string> ReadAllTextAsync(string 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();
}
public IEnumerable<string> EnumerateFiles(string path)
public virtual IEnumerable<string> EnumerateFiles(string path)
{
var e = EnumerateFilesAsync(path).GetAsyncEnumerator();
while(e.MoveNextAsync().GetAwaiter().GetResult())
@ -216,7 +252,7 @@ namespace Tesses.YouTubeDownloader
yield return e.Current;
}
}
public IEnumerable<string> EnumerateDirectories(string path)
public virtual IEnumerable<string> EnumerateDirectories(string path)
{
var e = EnumerateDirectoriesAsync(path).GetAsyncEnumerator();
while(e.MoveNextAsync().GetAwaiter().GetResult())
@ -748,7 +784,7 @@ namespace Tesses.YouTubeDownloader
public interface IWritable : IPersonalPlaylistGet, IPersonalPlaylistSet
{
public Task WriteAllTextAsync(string path,string data);
}
}
}

View File

@ -256,7 +256,15 @@ internal class SegmentedHttpStream : Stream
_=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)
{
try{

View File

@ -346,6 +346,11 @@ namespace Tesses.YouTubeDownloader
if(Downloader != null)
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)
{
@ -660,6 +665,33 @@ namespace Tesses.YouTubeDownloader
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

View File

@ -7,9 +7,9 @@
<PackageId>Tesses.YouTubeDownloader</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.1.5</Version>
<AssemblyVersion>1.1.5</AssemblyVersion>
<FileVersion>1.1.5</FileVersion>
<Version>1.1.6</Version>
<AssemblyVersion>1.1.6</AssemblyVersion>
<FileVersion>1.1.6</FileVersion>
<Description>A YouTube Downloader</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>