Add SFTP And Rearrange
This commit is contained in:
parent
4f31f73b6d
commit
f79c6122fb
|
@ -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.0</Version>
|
<Version>1.1.1</Version>
|
||||||
<AssemblyVersion>1.1.0</AssemblyVersion>
|
<AssemblyVersion>1.1.1</AssemblyVersion>
|
||||||
<FileVersion>1.1.0</FileVersion>
|
<FileVersion>1.1.1</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>
|
||||||
|
|
|
@ -83,10 +83,7 @@ namespace Tesses.YouTubeDownloader.DiscUtils
|
||||||
{
|
{
|
||||||
fileSystem.MoveFile(ConvertToDiscUtils(src),ConvertToDiscUtils(dest));
|
fileSystem.MoveFile(ConvertToDiscUtils(src),ConvertToDiscUtils(dest));
|
||||||
}
|
}
|
||||||
public override async Task<long> GetLengthAsync(string path)
|
|
||||||
{
|
|
||||||
return await Task.FromResult(fileSystem.GetFileLength(ConvertToDiscUtils(path)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ConvertToDiscUtils(string path)
|
private string ConvertToDiscUtils(string path)
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -16,9 +16,9 @@
|
||||||
<PackageId>Tesses.YouTubeDownloader.DiscUtils</PackageId>
|
<PackageId>Tesses.YouTubeDownloader.DiscUtils</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
<Version>1.0.0.0</Version>
|
<Version>1.0.0.1</Version>
|
||||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
<AssemblyVersion>1.0.0.1</AssemblyVersion>
|
||||||
<FileVersion>1.0.0.0</FileVersion>
|
<FileVersion>1.0.0.1</FileVersion>
|
||||||
<Description>Adds DiscUtils filesystem support</Description>
|
<Description>Adds DiscUtils filesystem support</Description>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Tesses.YouTubeDownloader.ExtensionLoader\Tesses.YouTubeDownloader.ExtensionLoader.csproj" />
|
<ProjectReference Include="..\..\Tesses.YouTubeDownloader.ExtensionLoader\Tesses.YouTubeDownloader.ExtensionLoader.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
|
@ -0,0 +1,186 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Renci.SshNet;
|
||||||
|
using Tesses.YouTubeDownloader;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Tesses.YouTubeDownloader.SFTP
|
||||||
|
{
|
||||||
|
public class SSHFS : TYTDStorage
|
||||||
|
{
|
||||||
|
SftpClient ftp;
|
||||||
|
string path;
|
||||||
|
public SSHFS(string url,string userName,string passWord)
|
||||||
|
{
|
||||||
|
Uri uri=new Uri(url);
|
||||||
|
int port;
|
||||||
|
if(uri.Port == -1)
|
||||||
|
{
|
||||||
|
port=22;
|
||||||
|
}else{
|
||||||
|
port=uri.Port;
|
||||||
|
}
|
||||||
|
path=uri.PathAndQuery;
|
||||||
|
ftp=new SftpClient(uri.Host,port,userName,passWord);
|
||||||
|
|
||||||
|
ftp.Connect();
|
||||||
|
ftp.ChangeDirectory(path);
|
||||||
|
}
|
||||||
|
private string GetArg(string[] a,int aI)
|
||||||
|
{
|
||||||
|
if(aI < a.Length)
|
||||||
|
{
|
||||||
|
return a[aI];
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
public SSHFS(Uri uri)
|
||||||
|
{
|
||||||
|
int port;
|
||||||
|
if(uri.Port == -1)
|
||||||
|
{
|
||||||
|
port=22;
|
||||||
|
}else{
|
||||||
|
port=uri.Port;
|
||||||
|
}
|
||||||
|
path=uri.PathAndQuery;
|
||||||
|
var userPass=uri.UserInfo.Split(new char[]{':'},2,StringSplitOptions.None);
|
||||||
|
ftp=new SftpClient(uri.Host,port,GetArg(userPass,0),GetArg(userPass,1));
|
||||||
|
ftp.Connect();
|
||||||
|
ftp.ChangeDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSHFS(string url,string userName,params PrivateKeyFile[] keys)
|
||||||
|
{
|
||||||
|
Uri uri=new Uri(url);
|
||||||
|
int port;
|
||||||
|
if(uri.Port == -1)
|
||||||
|
{
|
||||||
|
port=22;
|
||||||
|
}else{
|
||||||
|
port=uri.Port;
|
||||||
|
}
|
||||||
|
path=uri.PathAndQuery;
|
||||||
|
ftp=new SftpClient(uri.Host,port,userName,keys);
|
||||||
|
|
||||||
|
ftp.Connect();
|
||||||
|
ftp.ChangeDirectory(path);
|
||||||
|
}
|
||||||
|
public SSHFS(SftpClient client)
|
||||||
|
{
|
||||||
|
ftp=client;
|
||||||
|
path=client.WorkingDirectory;
|
||||||
|
}
|
||||||
|
public override async Task<Stream> CreateAsync(string path)
|
||||||
|
{
|
||||||
|
return await Task.FromResult(ftp.Open($"{this.path.TrimEnd('/')}/{path.TrimStart('/')}",FileMode.Create,FileAccess.Write));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CreateDirectory(string path)
|
||||||
|
{
|
||||||
|
ftp.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DeleteDirectory(string dir, bool recursive = false)
|
||||||
|
{
|
||||||
|
var entries = ftp.ListDirectory(dir).ToArray();
|
||||||
|
|
||||||
|
if(!recursive && entries.Length > 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(recursive)
|
||||||
|
{
|
||||||
|
foreach(var entry in entries)
|
||||||
|
{
|
||||||
|
if(entry.IsDirectory)
|
||||||
|
{
|
||||||
|
DeleteDirectory($"{dir.TrimEnd('/')}/{entry.Name}",true);
|
||||||
|
}else{
|
||||||
|
entry.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ftp.DeleteDirectory(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DeleteFile(string file)
|
||||||
|
{
|
||||||
|
ftp.DeleteFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<bool> DirectoryExistsAsync(string path)
|
||||||
|
{
|
||||||
|
bool value=false;
|
||||||
|
if(ftp.Exists(path))
|
||||||
|
{
|
||||||
|
|
||||||
|
if(ftp.Get(path).IsDirectory)
|
||||||
|
{
|
||||||
|
value=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await Task.FromResult(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path)
|
||||||
|
{
|
||||||
|
foreach(var item in ftp.ListDirectory(path))
|
||||||
|
{
|
||||||
|
if(item.IsDirectory)
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async IAsyncEnumerable<string> EnumerateFilesAsync(string path)
|
||||||
|
{
|
||||||
|
foreach(var item in ftp.ListDirectory(path))
|
||||||
|
{
|
||||||
|
if(item.IsRegularFile)
|
||||||
|
{
|
||||||
|
yield return await Task.FromResult(item.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<bool> FileExistsAsync(string path)
|
||||||
|
{
|
||||||
|
bool value=false;
|
||||||
|
if(ftp.Exists(path))
|
||||||
|
{
|
||||||
|
if(ftp.Get(path).IsRegularFile)
|
||||||
|
{
|
||||||
|
value=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await Task.FromResult(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void MoveDirectory(string src, string dest)
|
||||||
|
{
|
||||||
|
ftp.RenameFile(src,dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<Stream> OpenOrCreateAsync(string path)
|
||||||
|
{
|
||||||
|
return await Task.FromResult(ftp.Open($"{this.path.TrimEnd('/')}/{path.TrimStart('/')}",FileMode.OpenOrCreate,FileAccess.Write));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<Stream> OpenReadAsync(string path)
|
||||||
|
{
|
||||||
|
|
||||||
|
return await Task.FromResult(ftp.Open($"{this.path.TrimEnd('/')}/{path.TrimStart('/')}",FileMode.Open,FileAccess.Read));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RenameFile(string src, string dest)
|
||||||
|
{
|
||||||
|
ftp.RenameFile(src,dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>8.0</LangVersion>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
<PackageId>Tesses.YouTubeDownloader.SFTP</PackageId>
|
||||||
|
<Author>Mike Nolan</Author>
|
||||||
|
<Company>Tesses</Company>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.0</FileVersion>
|
||||||
|
<Description>SSH.NET SFTP for Tesses.YouTubeDownloader</Description>
|
||||||
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
<PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags>
|
||||||
|
<RepositoryUrl>https://gitlab.tesses.cf/tesses50/tytd</RepositoryUrl>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
|
||||||
|
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
|
||||||
|
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -16,10 +16,10 @@
|
||||||
<PackageId>Tesses.YouTubeDownloader.Zio</PackageId>
|
<PackageId>Tesses.YouTubeDownloader.Zio</PackageId>
|
||||||
<Author>Mike Nolan</Author>
|
<Author>Mike Nolan</Author>
|
||||||
<Company>Tesses</Company>
|
<Company>Tesses</Company>
|
||||||
<Version>1.0.0.0</Version>
|
<Version>1.0.0.1</Version>
|
||||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
<AssemblyVersion>1.0.0.1</AssemblyVersion>
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<FileVersion>1.0.0.0</FileVersion>
|
<FileVersion>1.0.0.1</FileVersion>
|
||||||
<Description>Adds Zio filesystem support</Description>
|
<Description>Adds Zio filesystem support</Description>
|
||||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||||
<PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags>
|
<PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags>
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
|
@ -75,6 +75,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
var cd = new System.Net.Mime.ContentDisposition();
|
var cd = new System.Net.Mime.ContentDisposition();
|
||||||
string filename = GetVideoName(name);
|
string filename = GetVideoName(name);
|
||||||
cd.FileName = filename;
|
cd.FileName = filename;
|
||||||
|
cd.DispositionType = System.Net.Mime.DispositionTypeNames.Inline;
|
||||||
|
|
||||||
return cd;
|
return cd;
|
||||||
}
|
}
|
||||||
|
@ -211,7 +212,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
await NotFoundServer.ServerNull.GetAsync(ctx);
|
await NotFoundServer.ServerNull.GetAsync(ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
using(var s = await baseCtl.OpenReadAsyncWithLength(file))
|
using(var s = await baseCtl.OpenReadAsync(file))
|
||||||
{
|
{
|
||||||
await ctx.SendStreamAsync(s);
|
await ctx.SendStreamAsync(s);
|
||||||
}
|
}
|
||||||
|
@ -326,18 +327,190 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
{
|
{
|
||||||
this.Downloader=downloader;
|
this.Downloader=downloader;
|
||||||
|
|
||||||
Add("/AddItem",AddItem);
|
AddBoth("/AddItem",AddItem);
|
||||||
Add("/AddChannel",AddChannel);
|
AddBoth("/AddChannel",AddChannel);
|
||||||
Add("/AddUser",AddUser);
|
AddBoth("/AddUser",AddUser);
|
||||||
Add("/AddPlaylist",AddPlaylist);
|
AddBoth("/AddPlaylist",AddPlaylist);
|
||||||
Add("/AddVideo",AddVideo);
|
AddBoth("/AddVideo",AddVideo);
|
||||||
Add("/Progress",ProgressFunc);
|
AddBoth("/Progress",ProgressFunc);
|
||||||
Add("/QueueList",QueueList);
|
AddBoth("/QueueList",QueueList);
|
||||||
Add("/subscribe",Subscribe);
|
AddBoth("/subscribe",Subscribe);
|
||||||
Add("/resubscribe",Resubscribe);
|
AddBoth("/resubscribe",Resubscribe);
|
||||||
Add("/unsubscribe",Unsubscribe);
|
AddBoth("/unsubscribe",Unsubscribe);
|
||||||
Add("/subscriptions",Subscriptions);
|
AddBoth("/subscriptions",Subscriptions);
|
||||||
|
AddBoth("/Subscribe",Subscribe);
|
||||||
|
AddBoth("/Resubscribe",Resubscribe);
|
||||||
|
AddBoth("/Unsubscribe",Unsubscribe);
|
||||||
|
AddBoth("/Subscriptions",Subscriptions);
|
||||||
|
AddBoth("/AddToList",AddToList);
|
||||||
|
AddBoth("/DeleteFromList",DeleteFromList);
|
||||||
|
Add("/ReplaceList",ReplaceList,"POST");
|
||||||
|
AddBoth("/DeleteList",DeleteList);
|
||||||
|
AddBoth("/SetResolutionInList",SetResolutionInList);
|
||||||
|
|
||||||
|
/*
|
||||||
|
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<(VideoId Id, Resolution Resolution)> items)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<(VideoId Id, Resolution Resolution)> items)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddBoth(string url,HttpActionAsync action)
|
||||||
|
{
|
||||||
|
Add(url,action);
|
||||||
|
Add(url,async(evt)=>{
|
||||||
|
evt.ParseBody();
|
||||||
|
await action(evt);
|
||||||
|
},"POST");
|
||||||
|
}
|
||||||
|
public async Task DeleteList(ServerContext ctx)
|
||||||
|
{
|
||||||
|
//this is for personal playlists
|
||||||
|
string name;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("name",out name)){
|
||||||
|
Downloader.DeletePersonalPlaylist(name);
|
||||||
|
|
||||||
|
//Downloader.AddToPersonalPlaylistAsync(name);
|
||||||
|
|
||||||
|
}
|
||||||
|
await ctx.SendTextAsync(
|
||||||
|
$"<html><head><title>You Will Be Redirected in 5 Sec</title><meta http-equiv=\"Refresh\" content=\"5; url='../../'\" /></head><body><h1>You Will Be Redirected in 5 Sec</h1></body></html>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public async Task ReplaceList(ServerContext ctx)
|
||||||
|
{
|
||||||
|
|
||||||
|
//this is for personal playlists
|
||||||
|
string name;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("name",out name)){
|
||||||
|
string jsonData;
|
||||||
|
List<ListContentItem> itemList;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("data",out jsonData))
|
||||||
|
{
|
||||||
|
itemList = JsonConvert.DeserializeObject<List<ListContentItem>>(jsonData);
|
||||||
|
|
||||||
|
await Downloader.ReplacePersonalPlaylistAsync(name,itemList);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Downloader.AddToPersonalPlaylistAsync(name);
|
||||||
|
|
||||||
|
}
|
||||||
|
await ctx.SendTextAsync(
|
||||||
|
$"<html><head><title>You Will Be Redirected in 5 Sec</title><meta http-equiv=\"Refresh\" content=\"5; url='../../'\" /></head><body><h1>You Will Be Redirected in 5 Sec</h1></body></html>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddToList(ServerContext ctx)
|
||||||
|
{
|
||||||
|
|
||||||
|
//this is for personal playlists
|
||||||
|
string name;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("name",out name)){
|
||||||
|
string jsonData;
|
||||||
|
List<ListContentItem> itemList;
|
||||||
|
if(ctx.Method == "POST" && ctx.QueryParams.TryGetFirst("data",out jsonData))
|
||||||
|
{
|
||||||
|
itemList = JsonConvert.DeserializeObject<List<ListContentItem>>(jsonData);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
itemList=new List<ListContentItem>();
|
||||||
|
string id;
|
||||||
|
|
||||||
|
if(ctx.QueryParams.TryGetFirst("v",out id))
|
||||||
|
{
|
||||||
|
|
||||||
|
Resolution resolution=Resolution.PreMuxed;
|
||||||
|
string res;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("res",out res))
|
||||||
|
{
|
||||||
|
if(!Enum.TryParse<Resolution>(res,out resolution))
|
||||||
|
{
|
||||||
|
resolution=Resolution.PreMuxed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VideoId? id1=VideoId.TryParse(id);
|
||||||
|
if(id1.HasValue)
|
||||||
|
{
|
||||||
|
itemList.Add(new ListContentItem(id1,resolution));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
await Downloader.AddToPersonalPlaylistAsync(name,itemList);
|
||||||
|
|
||||||
|
//Downloader.AddToPersonalPlaylistAsync(name);
|
||||||
|
|
||||||
|
}
|
||||||
|
await ctx.SendTextAsync(
|
||||||
|
$"<html><head><title>You Will Be Redirected in 5 Sec</title><meta http-equiv=\"Refresh\" content=\"5; url='../../'\" /></head><body><h1>You Will Be Redirected in 5 Sec</h1></body></html>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public async Task DeleteFromList(ServerContext ctx)
|
||||||
|
{
|
||||||
|
//this is for personal playlists
|
||||||
|
string name;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("name",out name)){
|
||||||
|
string id;
|
||||||
|
|
||||||
|
if(ctx.QueryParams.TryGetFirst("v",out id))
|
||||||
|
{
|
||||||
|
VideoId? id1=VideoId.TryParse(id);
|
||||||
|
if(id1.HasValue)
|
||||||
|
{
|
||||||
|
await Downloader.RemoveItemFromPersonalPlaylistAsync(name,id1.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await ctx.SendTextAsync(
|
||||||
|
$"<html><head><title>You Will Be Redirected in 5 Sec</title><meta http-equiv=\"Refresh\" content=\"5; url='../../'\" /></head><body><h1>You Will Be Redirected in 5 Sec</h1></body></html>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public async Task SetResolutionInList(ServerContext ctx)
|
||||||
|
{
|
||||||
|
//this is for personal playlists
|
||||||
|
string name;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("name",out name)){
|
||||||
|
string id;
|
||||||
|
|
||||||
|
if(ctx.QueryParams.TryGetFirst("v",out id))
|
||||||
|
{
|
||||||
|
Resolution resolution=Resolution.PreMuxed;
|
||||||
|
string res;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("res",out res))
|
||||||
|
{
|
||||||
|
if(!Enum.TryParse<Resolution>(res,out resolution))
|
||||||
|
{
|
||||||
|
resolution=Resolution.PreMuxed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VideoId? id1=VideoId.TryParse(id);
|
||||||
|
if(id1.HasValue)
|
||||||
|
{
|
||||||
|
await Downloader.SetResolutionForItemInPersonalPlaylistAsync(name,id1.Value,resolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await ctx.SendTextAsync(
|
||||||
|
$"<html><head><title>You Will Be Redirected in 5 Sec</title><meta http-equiv=\"Refresh\" content=\"5; url='../../'\" /></head><body><h1>You Will Be Redirected in 5 Sec</h1></body></html>\n"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
public async Task Subscriptions(ServerContext ctx)
|
public async Task Subscriptions(ServerContext ctx)
|
||||||
{
|
{
|
||||||
|
@ -351,7 +524,9 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
await ctx.SendTextAsync(
|
||||||
|
$"<html><head><title>You Will Be Redirected in 5 Sec</title><meta http-equiv=\"Refresh\" content=\"5; url='../../'\" /></head><body><h1>You Will Be Redirected in 5 Sec</h1></body></html>\n"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
public async Task Resubscribe(ServerContext ctx)
|
public async Task Resubscribe(ServerContext ctx)
|
||||||
{
|
{
|
||||||
|
@ -373,7 +548,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelId? cid=ChannelId.TryParse(WebUtility.UrlDecode(id));
|
ChannelId? cid=ChannelId.TryParse(id);
|
||||||
|
|
||||||
if(cid.HasValue)
|
if(cid.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -399,7 +574,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ChannelId? cid=ChannelId.TryParse(WebUtility.UrlDecode(id));
|
ChannelId? cid=ChannelId.TryParse(id);
|
||||||
|
|
||||||
if(cid.HasValue)
|
if(cid.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -441,14 +616,14 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelId? cid=ChannelId.TryParse(WebUtility.UrlDecode(id));
|
ChannelId? cid=ChannelId.TryParse(id);
|
||||||
|
|
||||||
if(cid.HasValue)
|
if(cid.HasValue)
|
||||||
{
|
{
|
||||||
|
|
||||||
await storage.SubscribeAsync(cid.Value,getinfo,conf);
|
await storage.SubscribeAsync(cid.Value,getinfo,conf);
|
||||||
}else{
|
}else{
|
||||||
UserName? uname=UserName.TryParse(WebUtility.UrlDecode(id));
|
UserName? uname=UserName.TryParse(id);
|
||||||
await storage.SubscribeAsync(uname.Value,conf);
|
await storage.SubscribeAsync(uname.Value,conf);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -473,6 +648,7 @@ namespace Tesses.YouTubeDownloader.Server
|
||||||
|
|
||||||
if(ctx.QueryParams.TryGetFirst("v",out id))
|
if(ctx.QueryParams.TryGetFirst("v",out id))
|
||||||
{
|
{
|
||||||
|
|
||||||
Resolution resolution=Resolution.PreMuxed;
|
Resolution resolution=Resolution.PreMuxed;
|
||||||
string res;
|
string res;
|
||||||
if(ctx.QueryParams.TryGetFirst("res",out res))
|
if(ctx.QueryParams.TryGetFirst("res",out res))
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Tesses.WebServer" Version="1.0.3.3" />
|
<PackageReference Include="Tesses.WebServer" Version="1.0.3.4" />
|
||||||
</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.1</Version>
|
<Version>1.1.3</Version>
|
||||||
<AssemblyVersion>1.1.1</AssemblyVersion>
|
<AssemblyVersion>1.1.3</AssemblyVersion>
|
||||||
<FileVersion>1.1.1</FileVersion>
|
<FileVersion>1.1.3</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>
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public class BestStreams
|
public class BestStreams
|
||||||
{
|
{
|
||||||
|
public bool VideoFrozen {get;set;}
|
||||||
public BestStreamInfo MuxedStreamInfo {get;set;}
|
public BestStreamInfo MuxedStreamInfo {get;set;}
|
||||||
|
|
||||||
public BestStreamInfo VideoOnlyStreamInfo {get;set;}
|
public BestStreamInfo VideoOnlyStreamInfo {get;set;}
|
||||||
|
@ -20,6 +20,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public static async Task<string> GetPathResolution(ITYTDBase storage,SavedVideo video,Resolution resolution=Resolution.PreMuxed)
|
public static async Task<string> GetPathResolution(ITYTDBase storage,SavedVideo video,Resolution resolution=Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
|
|
||||||
if(video.LegacyVideo)
|
if(video.LegacyVideo)
|
||||||
{
|
{
|
||||||
if(resolution == Resolution.Mux)
|
if(resolution == Resolution.Mux)
|
||||||
|
@ -27,10 +28,22 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
return $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mp4";
|
return $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mp4";
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
var f= await BestStreamInfo.GetBestStreams(storage,video.Id);
|
var f= await BestStreamInfo.GetBestStreams(storage,video.Id);
|
||||||
|
|
||||||
if(f ==null)
|
if(f ==null)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
|
if(f.VideoFrozen)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(resolution == Resolution.Mux)
|
||||||
|
return $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mkv";
|
||||||
|
|
||||||
|
return $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mp4";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
string[] exts= new string[] {"mkv",f.MuxedStreamInfo.Container.Name,f.AudioOnlyStreamInfo.Container.Name,f.VideoOnlyStreamInfo.Container.Name};
|
string[] exts= new string[] {"mkv",f.MuxedStreamInfo.Container.Name,f.AudioOnlyStreamInfo.Container.Name,f.VideoOnlyStreamInfo.Container.Name};
|
||||||
string ext=exts[(int)resolution];
|
string ext=exts[(int)resolution];
|
||||||
|
|
||||||
|
@ -87,6 +100,15 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
DateTime expires=DateTime.Now.AddHours(6);
|
DateTime expires=DateTime.Now.AddHours(6);
|
||||||
try{
|
try{
|
||||||
|
if(storage.VideoInfoExists(id))
|
||||||
|
{
|
||||||
|
var video = await storage.GetVideoInfoAsync(id);
|
||||||
|
if(video.VideoFrozen)
|
||||||
|
{
|
||||||
|
|
||||||
|
return new BestStreams() {VideoFrozen=true,MuxedStreamInfo=null,VideoOnlyStreamInfo=null,AudioOnlyStreamInfo=null};
|
||||||
|
}
|
||||||
|
}
|
||||||
var res=await storage.YoutubeClient.Videos.Streams.GetManifestAsync(id,token);
|
var res=await storage.YoutubeClient.Videos.Streams.GetManifestAsync(id,token);
|
||||||
|
|
||||||
var audioOnly=res.GetAudioOnlyStreams().GetWithHighestBitrate();
|
var audioOnly=res.GetAudioOnlyStreams().GetWithHighestBitrate();
|
||||||
|
|
|
@ -145,7 +145,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}catch(Exception ex)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
await GetLogger().WriteAsync(ex);
|
await GetLogger().WriteAsync(ex,video.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
if (await FileExistsAsync(path))
|
if (await FileExistsAsync(path))
|
||||||
{
|
{
|
||||||
return (await GetLengthAsync(path) == 0);
|
return ((await OpenReadAsync(path)).Length == 0);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -205,8 +205,9 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}catch(Exception ex)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(ex.Message);
|
|
||||||
_=ex;
|
_=ex;
|
||||||
|
Console.WriteLine("FFMPEG ERROR, sorry cant read logging config");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -332,10 +333,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
DeleteIfExists(video_bin);
|
DeleteIfExists(video_bin);
|
||||||
DeleteIfExists(audio_bin);
|
DeleteIfExists(audio_bin);
|
||||||
DeleteIfExists(output_mkv);
|
DeleteIfExists(output_mkv);
|
||||||
long len=await GetLengthAsync(videoSrc);
|
|
||||||
|
|
||||||
using(var vstrm_src=await OpenReadAsync(videoSrc))
|
using(var vstrm_src=await OpenReadAsync(videoSrc))
|
||||||
{
|
{
|
||||||
|
long len = vstrm_src.Length;
|
||||||
using(var vstrm_dest = File.Create(video_bin))
|
using(var vstrm_dest = File.Create(video_bin))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Opening vstream");
|
Console.WriteLine("Opening vstream");
|
||||||
|
@ -359,7 +361,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
using(var astrm_dest = File.Create(audio_bin))
|
using(var astrm_dest = File.Create(audio_bin))
|
||||||
{
|
{
|
||||||
Console.WriteLine("opening astream");
|
Console.WriteLine("opening astream");
|
||||||
if(!await CopyStreamAsync(astrm_src,astrm_dest,0,len,4096,
|
if(!await CopyStreamAsync(astrm_src,astrm_dest,0,astrm_src.Length,4096,
|
||||||
new Progress<double>((e)=>{
|
new Progress<double>((e)=>{
|
||||||
if(progress !=null)
|
if(progress !=null)
|
||||||
{
|
{
|
||||||
|
@ -455,6 +457,10 @@ namespace Tesses.YouTubeDownloader
|
||||||
if(!can_download) return false;
|
if(!can_download) return false;
|
||||||
if(streams != null)
|
if(streams != null)
|
||||||
{
|
{
|
||||||
|
if(streams.VideoFrozen)
|
||||||
|
{
|
||||||
|
throw new Exception($"[TYTD Specific Error] Video is frozen, we wont do anything with the video.\nplease set \"VideoFrozen\": false in the file \"Info/{video.Id}.json\" to fix this problem");
|
||||||
|
}
|
||||||
await MoveLegacyStreams(video,streams);
|
await MoveLegacyStreams(video,streams);
|
||||||
string complete = $"VideoOnly/{video.Id}.{streams.VideoOnlyStreamInfo.Container}";
|
string complete = $"VideoOnly/{video.Id}.{streams.VideoOnlyStreamInfo.Container}";
|
||||||
string incomplete = $"VideoOnly/{video.Id}incomplete.{streams.VideoOnlyStreamInfo.Container}";
|
string incomplete = $"VideoOnly/{video.Id}incomplete.{streams.VideoOnlyStreamInfo.Container}";
|
||||||
|
@ -472,11 +478,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
if(report)
|
if(report)
|
||||||
await ReportStartVideo(video, Resolution.VideoOnly,streams.VideoOnlyStreamInfo.Size.Bytes);
|
await ReportStartVideo(video, Resolution.VideoOnly,streams.VideoOnlyStreamInfo.Size.Bytes);
|
||||||
long len=await GetLengthAsync(incomplete);
|
|
||||||
|
|
||||||
using(var dest = await OpenOrCreateAsync(incomplete))
|
using(var dest = await OpenOrCreateAsync(incomplete))
|
||||||
{
|
{
|
||||||
ret=await CopyStreamAsync(strm,dest,len,streams.VideoOnlyStreamInfo.Size.Bytes,4096,progress,token);
|
ret=await CopyStreamAsync(strm,dest,dest.Length,streams.VideoOnlyStreamInfo.Size.Bytes,4096,progress,token);
|
||||||
}
|
}
|
||||||
if(ret)
|
if(ret)
|
||||||
{
|
{
|
||||||
|
@ -511,6 +517,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public async Task MoveLegacyStreams(SavedVideo video,BestStreams streams)
|
public async Task MoveLegacyStreams(SavedVideo video,BestStreams streams)
|
||||||
{
|
{
|
||||||
|
if(video.VideoFrozen) return;
|
||||||
if(video.LegacyVideo)
|
if(video.LegacyVideo)
|
||||||
{
|
{
|
||||||
string legacyVideoOnlyComplete = $"VideoOnly/{video.Id}.mp4";
|
string legacyVideoOnlyComplete = $"VideoOnly/{video.Id}.mp4";
|
||||||
|
@ -550,6 +557,10 @@ namespace Tesses.YouTubeDownloader
|
||||||
if(!can_download) return false;
|
if(!can_download) return false;
|
||||||
if(streams != null)
|
if(streams != null)
|
||||||
{
|
{
|
||||||
|
if(streams.VideoFrozen)
|
||||||
|
{
|
||||||
|
throw new Exception($"[TYTD Specific Error] Video is frozen, we wont do anything with the video.\nplease set \"VideoFrozen\": false in the file \"Info/{video.Id}.json\" to fix this problem");
|
||||||
|
}
|
||||||
string complete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}";
|
string complete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}";
|
||||||
string incomplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}";
|
string incomplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}";
|
||||||
await MoveLegacyStreams(video,streams);
|
await MoveLegacyStreams(video,streams);
|
||||||
|
@ -568,11 +579,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
if(report)
|
if(report)
|
||||||
await ReportStartVideo(video, Resolution.AudioOnly,streams.AudioOnlyStreamInfo.Size.Bytes);
|
await ReportStartVideo(video, Resolution.AudioOnly,streams.AudioOnlyStreamInfo.Size.Bytes);
|
||||||
long len=await GetLengthAsync(incomplete);
|
|
||||||
|
|
||||||
using(var dest = await OpenOrCreateAsync(incomplete))
|
using(var dest = await OpenOrCreateAsync(incomplete))
|
||||||
{
|
{
|
||||||
ret=await CopyStreamAsync(strm,dest,len,streams.AudioOnlyStreamInfo.Size.Bytes,4096,progress,token);
|
ret=await CopyStreamAsync(strm,dest,dest.Length,streams.AudioOnlyStreamInfo.Size.Bytes,4096,progress,token);
|
||||||
}
|
}
|
||||||
if(ret)
|
if(ret)
|
||||||
{
|
{
|
||||||
|
@ -597,6 +608,10 @@ namespace Tesses.YouTubeDownloader
|
||||||
if(!can_download) return;
|
if(!can_download) return;
|
||||||
if(streams != null)
|
if(streams != null)
|
||||||
{
|
{
|
||||||
|
if(streams.VideoFrozen)
|
||||||
|
{
|
||||||
|
throw new Exception($"[TYTD Specific Error] Video is frozen, we wont do anything with the video.\nplease set \"VideoFrozen\": false in the file \"Info/{video.Id}.json\" to fix this problem");
|
||||||
|
}
|
||||||
await MoveLegacyStreams(video,streams);
|
await MoveLegacyStreams(video,streams);
|
||||||
string complete = $"PreMuxed/{video.Id}.{streams.MuxedStreamInfo.Container}";
|
string complete = $"PreMuxed/{video.Id}.{streams.MuxedStreamInfo.Container}";
|
||||||
string incomplete = $"PreMuxed/{video.Id}incomplete.{streams.MuxedStreamInfo.Container}";
|
string incomplete = $"PreMuxed/{video.Id}incomplete.{streams.MuxedStreamInfo.Container}";
|
||||||
|
@ -616,11 +631,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
if(report)
|
if(report)
|
||||||
await ReportStartVideo(video,Resolution.PreMuxed,streams.MuxedStreamInfo.Size.Bytes);
|
await ReportStartVideo(video,Resolution.PreMuxed,streams.MuxedStreamInfo.Size.Bytes);
|
||||||
long len=await GetLengthAsync(incomplete);
|
|
||||||
bool ret;
|
bool ret;
|
||||||
using(var dest = await OpenOrCreateAsync(incomplete))
|
using(var dest = await OpenOrCreateAsync(incomplete))
|
||||||
{
|
{
|
||||||
ret=await CopyStreamAsync(strm,dest,len,streams.MuxedStreamInfo.Size.Bytes,4096,progress,token);
|
ret=await CopyStreamAsync(strm,dest,dest.Length,streams.MuxedStreamInfo.Size.Bytes,4096,progress,token);
|
||||||
}
|
}
|
||||||
//We know its resolution
|
//We know its resolution
|
||||||
if(ret)
|
if(ret)
|
||||||
|
|
|
@ -10,7 +10,7 @@ using YoutubeExplode.Playlists;
|
||||||
using YoutubeExplode.Channels;
|
using YoutubeExplode.Channels;
|
||||||
namespace Tesses.YouTubeDownloader
|
namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
public interface IDownloader
|
public interface IDownloader : IPersonalPlaylistSet
|
||||||
{
|
{
|
||||||
Task AddVideoAsync(VideoId id,Resolution resolution=Resolution.PreMuxed);
|
Task AddVideoAsync(VideoId id,Resolution resolution=Resolution.PreMuxed);
|
||||||
Task AddPlaylistAsync(PlaylistId id,Resolution resolution=Resolution.PreMuxed);
|
Task AddPlaylistAsync(PlaylistId id,Resolution resolution=Resolution.PreMuxed);
|
||||||
|
@ -25,5 +25,6 @@ namespace Tesses.YouTubeDownloader
|
||||||
Task SubscribeAsync(ChannelId id,bool downloadChannelInfo=false,ChannelBellInfo bellInfo = ChannelBellInfo.NotifyAndDownload);
|
Task SubscribeAsync(ChannelId id,bool downloadChannelInfo=false,ChannelBellInfo bellInfo = ChannelBellInfo.NotifyAndDownload);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -55,5 +55,6 @@ namespace Tesses.YouTubeDownloader
|
||||||
Task MoveLegacyStreams(SavedVideo video,BestStreams streams);
|
Task MoveLegacyStreams(SavedVideo video,BestStreams streams);
|
||||||
|
|
||||||
void StartLoop(CancellationToken token=default(CancellationToken));
|
void StartLoop(CancellationToken token=default(CancellationToken));
|
||||||
|
event EventHandler<TYTDErrorEventArgs> Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
|
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
|
||||||
Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path);
|
Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path);
|
||||||
Task<long> GetLengthAsync(string path);
|
|
||||||
bool FileExists(string path);
|
bool FileExists(string path);
|
||||||
IAsyncEnumerable<string> GetVideoIdsAsync();
|
IAsyncEnumerable<string> GetVideoIdsAsync();
|
||||||
Task<SavedVideo> GetVideoInfoAsync(VideoId id);
|
Task<SavedVideo> GetVideoInfoAsync(VideoId id);
|
||||||
|
@ -46,7 +46,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
IEnumerable<string> EnumerateFiles(string path);
|
IEnumerable<string> EnumerateFiles(string path);
|
||||||
IEnumerable<string> EnumerateDirectories(string path);
|
IEnumerable<string> EnumerateDirectories(string path);
|
||||||
Task<Stream> OpenReadAsyncWithLength(string path);
|
|
||||||
Task<Stream> OpenReadAsync(string path);
|
Task<Stream> OpenReadAsync(string path);
|
||||||
|
|
||||||
Task<bool> FileExistsAsync(string path);
|
Task<bool> FileExistsAsync(string path);
|
||||||
|
@ -56,5 +56,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
IAsyncEnumerable<string> EnumerateFilesAsync(string path);
|
IAsyncEnumerable<string> EnumerateFilesAsync(string path);
|
||||||
|
|
||||||
IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path);
|
IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using YoutubeExplode;
|
using YoutubeExplode;
|
||||||
using YoutubeExplode.Videos;
|
using YoutubeExplode.Videos;
|
||||||
|
@ -21,9 +17,25 @@ namespace Tesses.YouTubeDownloader
|
||||||
internal class LockObj
|
internal class LockObj
|
||||||
{
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public class TYTDErrorEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public TYTDErrorEventArgs(VideoId? id,Exception exception)
|
||||||
|
{
|
||||||
|
Id=id;
|
||||||
|
Exception=exception;
|
||||||
|
PrintError =true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoId? Id {get;set;}
|
||||||
|
|
||||||
|
public Exception Exception {get;set;}
|
||||||
|
|
||||||
|
public bool PrintError {get;set;}
|
||||||
}
|
}
|
||||||
public partial class TYTDStorage
|
public partial class TYTDStorage
|
||||||
{
|
{
|
||||||
|
public event EventHandler<TYTDErrorEventArgs> Error;
|
||||||
internal LoggerProperties Properties {get;set;}
|
internal LoggerProperties Properties {get;set;}
|
||||||
public LoggerProperties GetProperties()
|
public LoggerProperties GetProperties()
|
||||||
{
|
{
|
||||||
|
@ -154,10 +166,17 @@ namespace Tesses.YouTubeDownloader
|
||||||
//mtx.ReleaseMutex();
|
//mtx.ReleaseMutex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task WriteAsync(Exception ex)
|
public async Task WriteAsync(Exception ex)
|
||||||
{
|
{
|
||||||
await WriteAsync($"Exception Catched:\n{ex.ToString()}",_storage.GetLoggerProperties().PrintErrors,true);
|
await WriteAsync(ex,null);
|
||||||
|
}
|
||||||
|
public async Task WriteAsync(Exception ex,VideoId? id)
|
||||||
|
{
|
||||||
|
TYTDErrorEventArgs args=new TYTDErrorEventArgs(id,ex);
|
||||||
|
_storage.ThrowError(args);
|
||||||
|
|
||||||
|
await WriteAsync($"Exception Catched:\n{ex.ToString()}",_storage.GetLoggerProperties().PrintErrors && args.PrintError,true);
|
||||||
|
|
||||||
}
|
}
|
||||||
public async Task WriteAsync(SavedVideo video)
|
public async Task WriteAsync(SavedVideo video)
|
||||||
{
|
{
|
||||||
|
|
|
@ -40,9 +40,15 @@ namespace Tesses.YouTubeDownloader
|
||||||
string path=$"Channel/{Id.Value}.json";
|
string path=$"Channel/{Id.Value}.json";
|
||||||
if(await storage.Continue(path))
|
if(await storage.Continue(path))
|
||||||
{
|
{
|
||||||
|
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.WriteAllTextAsync(path,JsonConvert.SerializeObject(channel));
|
||||||
|
}catch(Exception ex)
|
||||||
|
{
|
||||||
|
await storage.GetLogger().WriteAsync(ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return channel;
|
return channel;
|
||||||
}else{
|
}else{
|
||||||
var j=JsonConvert.DeserializeObject<SavedChannel>(await storage.ReadAllTextAsync(path));
|
var j=JsonConvert.DeserializeObject<SavedChannel>(await storage.ReadAllTextAsync(path));
|
||||||
|
@ -114,6 +120,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
string path=$"Playlist/{Id}.json";
|
string path=$"Playlist/{Id}.json";
|
||||||
List<IVideo> videos=new List<IVideo>();
|
List<IVideo> videos=new List<IVideo>();
|
||||||
|
try{
|
||||||
|
|
||||||
await foreach(var vid in storage.YoutubeClient.Playlists.GetVideosAsync(Id))
|
await foreach(var vid in storage.YoutubeClient.Playlists.GetVideosAsync(Id))
|
||||||
{
|
{
|
||||||
videos.Add(vid);
|
videos.Add(vid);
|
||||||
|
@ -127,6 +135,10 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
}
|
}
|
||||||
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(p));
|
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(p));
|
||||||
|
}catch(Exception ex)
|
||||||
|
{
|
||||||
|
await storage.GetLogger().WriteAsync(ex);
|
||||||
|
}
|
||||||
if(Resolution == Resolution.NoDownload) return;
|
if(Resolution == Resolution.NoDownload) return;
|
||||||
foreach(var item in videos)
|
foreach(var item in videos)
|
||||||
{
|
{
|
||||||
|
@ -161,7 +173,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
await video.DownloadThumbnails(storage);
|
await video.DownloadThumbnails(storage);
|
||||||
}catch(Exception ex)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
_=ex;
|
|
||||||
|
await storage.GetLogger().WriteAsync(ex,Id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public class SavedVideoLegacy
|
public class SavedVideoLegacy
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public string Id {get;set;}
|
public string Id {get;set;}
|
||||||
public string Title {get;set;}
|
public string Title {get;set;}
|
||||||
public string AuthorChannelId {get;set;}
|
public string AuthorChannelId {get;set;}
|
||||||
|
@ -80,7 +82,10 @@ namespace Tesses.YouTubeDownloader
|
||||||
UploadDate=new DateTime(1992,8,20);
|
UploadDate=new DateTime(1992,8,20);
|
||||||
AddDate=DateTime.Now;
|
AddDate=DateTime.Now;
|
||||||
LegacyVideo=false;
|
LegacyVideo=false;
|
||||||
|
DownloadFrom="YouTube";
|
||||||
|
VideoFrozen=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SavedVideo(Video video)
|
public SavedVideo(Video video)
|
||||||
{
|
{
|
||||||
Id=video.Id;
|
Id=video.Id;
|
||||||
|
@ -96,11 +101,16 @@ namespace Tesses.YouTubeDownloader
|
||||||
UploadDate = video.UploadDate.DateTime;
|
UploadDate = video.UploadDate.DateTime;
|
||||||
AddDate=DateTime.Now;
|
AddDate=DateTime.Now;
|
||||||
LegacyVideo=false;
|
LegacyVideo=false;
|
||||||
|
DownloadFrom="YouTube";
|
||||||
|
VideoFrozen=false;
|
||||||
}
|
}
|
||||||
public bool LegacyVideo {get;set;}
|
public bool LegacyVideo {get;set;}
|
||||||
|
public bool VideoFrozen {get;set;}
|
||||||
|
|
||||||
|
public string DownloadFrom {get;set;}
|
||||||
public SavedVideoLegacy ToLegacy()
|
public SavedVideoLegacy ToLegacy()
|
||||||
{
|
{
|
||||||
|
|
||||||
SavedVideoLegacy legacy=new SavedVideoLegacy();
|
SavedVideoLegacy legacy=new SavedVideoLegacy();
|
||||||
legacy.Thumbnails=new List<(int, int, string)>();
|
legacy.Thumbnails=new List<(int, int, string)>();
|
||||||
legacy.Thumbnails.Add((120,90,$"https://s.ytimg.com/vi/{Id}/default.jpg"));
|
legacy.Thumbnails.Add((120,90,$"https://s.ytimg.com/vi/{Id}/default.jpg"));
|
||||||
|
@ -117,6 +127,8 @@ namespace Tesses.YouTubeDownloader
|
||||||
legacy.Title=Title;
|
legacy.Title=Title;
|
||||||
legacy.UploadDate=UploadDate.ToString();
|
legacy.UploadDate=UploadDate.ToString();
|
||||||
legacy.Views=Views;
|
legacy.Views=Views;
|
||||||
|
|
||||||
|
|
||||||
return legacy;
|
return legacy;
|
||||||
}
|
}
|
||||||
public Video ToVideo()
|
public Video ToVideo()
|
||||||
|
|
|
@ -164,6 +164,10 @@ namespace Tesses.YouTubeDownloader
|
||||||
});
|
});
|
||||||
thread1.Start();
|
thread1.Start();
|
||||||
}
|
}
|
||||||
|
internal void ThrowError(TYTDErrorEventArgs e)
|
||||||
|
{
|
||||||
|
Error?.Invoke(this,e);
|
||||||
|
}
|
||||||
public async Task WriteAllTextAsync(string path,string data)
|
public async Task WriteAllTextAsync(string path,string data)
|
||||||
{
|
{
|
||||||
using(var dstStrm= await CreateAsync(path))
|
using(var dstStrm= await CreateAsync(path))
|
||||||
|
@ -174,5 +178,60 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
||||||
|
{
|
||||||
|
List<ListContentItem> items0=new List<ListContentItem>();
|
||||||
|
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
||||||
|
{
|
||||||
|
items0.Add(item);
|
||||||
|
}
|
||||||
|
items0.AddRange(items);
|
||||||
|
await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
||||||
|
{
|
||||||
|
|
||||||
|
await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items.ToList()));
|
||||||
|
|
||||||
|
}
|
||||||
|
public void DeletePersonalPlaylist(string name)
|
||||||
|
{
|
||||||
|
DeleteFile($"PersonalPlaylist/{name}.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
|
||||||
|
{
|
||||||
|
List<ListContentItem> items0=new List<ListContentItem>();
|
||||||
|
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
||||||
|
{
|
||||||
|
if(item.Id != id)
|
||||||
|
{
|
||||||
|
items0.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution)
|
||||||
|
{
|
||||||
|
List<ListContentItem> items0=new List<ListContentItem>();
|
||||||
|
await foreach(var item in GetPersonalPlaylistContentsAsync(name))
|
||||||
|
{
|
||||||
|
if(item.Id != id)
|
||||||
|
{
|
||||||
|
items0.Add(item);
|
||||||
|
}else{
|
||||||
|
items0.Add(new ListContentItem(item.Id,resolution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0));
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,74 +13,18 @@ using YoutubeExplode.Playlists;
|
||||||
using YoutubeExplode.Channels;
|
using YoutubeExplode.Channels;
|
||||||
namespace Tesses.YouTubeDownloader
|
namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
internal class TYTDBaseFileReader : Stream
|
|
||||||
{
|
|
||||||
//TYTDBase baseCtl;
|
|
||||||
Stream baseStrm;
|
|
||||||
long len;
|
|
||||||
private TYTDBaseFileReader(long leng)
|
|
||||||
{
|
|
||||||
len=leng;
|
|
||||||
}
|
|
||||||
public static async Task<Stream> GetStream(TYTDBase baseCtl,string path)
|
|
||||||
{
|
|
||||||
var basect=new TYTDBaseFileReader(await baseCtl.GetLengthAsync(path));
|
|
||||||
|
|
||||||
basect.baseStrm = await baseCtl.OpenReadAsync(path);
|
|
||||||
return basect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanRead => baseStrm.CanRead;
|
|
||||||
|
|
||||||
public override bool CanSeek => baseStrm.CanSeek;
|
|
||||||
|
|
||||||
public override bool CanWrite => false;
|
|
||||||
|
|
||||||
public override long Length => len;
|
|
||||||
|
|
||||||
public override long Position { get => baseStrm.Position; set => baseStrm.Position=value; }
|
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
baseStrm.Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
return baseStrm.Read(buffer,offset,count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
|
||||||
{
|
|
||||||
return baseStrm.Seek(offset,origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetLength(long value)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await baseStrm.ReadAsync(buffer,offset,count,cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
baseStrm.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public abstract class TYTDBase : ITYTDBase
|
public abstract class TYTDBase : ITYTDBase
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public bool PersonalPlaylistExists(string name)
|
||||||
public async IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string playlist)
|
|
||||||
{
|
{
|
||||||
var ls=JsonConvert.DeserializeObject<List<(string Id,Resolution Resolution)>>(await ReadAllTextAsync($"PersonalPlaylist/{playlist}.json"));
|
return FileExists($"PersonalPlaylist/{name}.json");
|
||||||
|
}
|
||||||
|
public async IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string playlist)
|
||||||
|
{
|
||||||
|
if(!PersonalPlaylistExists(playlist)) yield break;
|
||||||
|
var ls=JsonConvert.DeserializeObject<List<ListContentItem>>(await ReadAllTextAsync($"PersonalPlaylist/{playlist}.json"));
|
||||||
foreach(var item in ls)
|
foreach(var item in ls)
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(item);
|
yield return await Task.FromResult(item);
|
||||||
|
@ -113,14 +57,6 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public virtual async Task<long> GetLengthAsync(string path)
|
|
||||||
{
|
|
||||||
if(!await FileExistsAsync(path)) return 0;
|
|
||||||
using(var f = await OpenReadAsync(path))
|
|
||||||
{
|
|
||||||
return f.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public bool FileExists(string path)
|
public bool FileExists(string path)
|
||||||
{
|
{
|
||||||
return FileExistsAsync(path).GetAwaiter().GetResult();
|
return FileExistsAsync(path).GetAwaiter().GetResult();
|
||||||
|
@ -182,17 +118,18 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> ReadAllBytesAsync(string path,CancellationToken token=default(CancellationToken))
|
public async Task<byte[]> ReadAllBytesAsync(string path,CancellationToken token=default(CancellationToken))
|
||||||
{
|
{using(var strm = await OpenReadAsync(path))
|
||||||
byte[] data=new byte[await GetLengthAsync(path)];
|
|
||||||
using(var strm = await OpenReadAsync(path))
|
|
||||||
{
|
{
|
||||||
|
byte[] data=new byte[strm.Length];
|
||||||
|
|
||||||
await strm.ReadAsync(data,0,data.Length,token);
|
await strm.ReadAsync(data,0,data.Length,token);
|
||||||
if(token.IsCancellationRequested)
|
if(token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
public async IAsyncEnumerable<string> GetPlaylistIdsAsync()
|
public async IAsyncEnumerable<string> GetPlaylistIdsAsync()
|
||||||
{
|
{
|
||||||
|
@ -287,10 +224,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
yield return e.Current;
|
yield return e.Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<Stream> OpenReadAsyncWithLength(string path)
|
|
||||||
{
|
|
||||||
return await TYTDBaseFileReader.GetStream(this,path);
|
|
||||||
}
|
|
||||||
public abstract Task<Stream> OpenReadAsync(string path);
|
public abstract Task<Stream> OpenReadAsync(string path);
|
||||||
|
|
||||||
public abstract Task<bool> FileExistsAsync(string path);
|
public abstract Task<bool> FileExistsAsync(string path);
|
||||||
|
@ -302,7 +236,57 @@ namespace Tesses.YouTubeDownloader
|
||||||
public abstract IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path);
|
public abstract IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path);
|
||||||
|
|
||||||
}
|
}
|
||||||
public static class TYTDManager
|
|
||||||
|
public class ListContentItem
|
||||||
|
{
|
||||||
|
public ListContentItem()
|
||||||
|
{
|
||||||
|
Id="";
|
||||||
|
Resolution=Resolution.PreMuxed;
|
||||||
|
}
|
||||||
|
public ListContentItem(VideoId id)
|
||||||
|
{
|
||||||
|
Id=id.Value;
|
||||||
|
Resolution=Resolution.PreMuxed;
|
||||||
|
}
|
||||||
|
public ListContentItem(string id)
|
||||||
|
{
|
||||||
|
Id=id;
|
||||||
|
Resolution =Resolution.PreMuxed;
|
||||||
|
}
|
||||||
|
public ListContentItem(string id,Resolution resolution)
|
||||||
|
{
|
||||||
|
Id=id;
|
||||||
|
Resolution=resolution;
|
||||||
|
}
|
||||||
|
public ListContentItem(VideoId id,Resolution resolution)
|
||||||
|
{
|
||||||
|
Id=id.Value;
|
||||||
|
Resolution=resolution;
|
||||||
|
}
|
||||||
|
public string Id {get;set;}
|
||||||
|
public Resolution Resolution {get;set;}
|
||||||
|
|
||||||
|
public static implicit operator ListContentItem(VideoId id)
|
||||||
|
{
|
||||||
|
return new ListContentItem(id.Value, Resolution.PreMuxed);
|
||||||
|
}
|
||||||
|
public static implicit operator VideoId?(ListContentItem item)
|
||||||
|
{
|
||||||
|
return VideoId.TryParse(item.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator ListContentItem((VideoId Id,Resolution Resolution) item)
|
||||||
|
{
|
||||||
|
return new ListContentItem (item.Id,item.Resolution);
|
||||||
|
}
|
||||||
|
public static implicit operator (VideoId Id,Resolution Resolution)(ListContentItem item)
|
||||||
|
{
|
||||||
|
return (VideoId.Parse(item.Id),item.Resolution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TYTDManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add Video, Playlist, Channel Or Username
|
/// Add Video, Playlist, Channel Or Username
|
||||||
|
@ -355,41 +339,9 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// Replace Personal Playlist
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">Name of playlist</param>
|
|
||||||
/// <param name="items">Videos to set in playlist</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static async Task ReplacePersonalPlaylistAsync(this IWritable writable,string name,IEnumerable<(VideoId Id,Resolution Resolution)> items)
|
|
||||||
{
|
|
||||||
List<(string Id,Resolution Resolution)> items0=new List<(string Id, Resolution Resolution)>();
|
|
||||||
|
|
||||||
items0.AddRange(items.Select<(VideoId Id,Resolution Resolution),(string Id,Resolution Resolution)>((e)=>{
|
|
||||||
return (e.Id.Value,e.Resolution);
|
|
||||||
}) );
|
|
||||||
await writable.WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0));
|
|
||||||
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Append to PersonalPlaylist
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">Name of playlist</param>
|
|
||||||
/// <param name="items">Videos to add in playlist</param>
|
|
||||||
public static async Task AddToPersonalPlaylistAsync(this IWritable writable, string name, IEnumerable<(VideoId Id, Resolution Resolution)> items)
|
|
||||||
{
|
|
||||||
|
|
||||||
List<(string Id,Resolution Resolution)> items0=new List<(string Id, Resolution Resolution)>();
|
|
||||||
await foreach(var item in writable.GetPersonalPlaylistContentsAsync(name))
|
|
||||||
{
|
|
||||||
items0.Add(item);
|
|
||||||
}
|
|
||||||
items0.AddRange(items.Select<(VideoId Id,Resolution Resolution),(string Id,Resolution Resolution)>((e)=>{
|
|
||||||
return (e.Id.Value,e.Resolution);
|
|
||||||
}) );
|
|
||||||
await writable.WriteAllTextAsync($"PersonalPlaylist/{name}.json",JsonConvert.SerializeObject(items0));
|
|
||||||
|
|
||||||
}
|
|
||||||
internal static void Print(this IProgress<string> prog,string text)
|
internal static void Print(this IProgress<string> prog,string text)
|
||||||
{
|
{
|
||||||
if(prog !=null)
|
if(prog !=null)
|
||||||
|
@ -429,11 +381,12 @@ namespace Tesses.YouTubeDownloader
|
||||||
if(string.IsNullOrWhiteSpace(path)) return false;
|
if(string.IsNullOrWhiteSpace(path)) return false;
|
||||||
|
|
||||||
bool ret=false;
|
bool ret=false;
|
||||||
double len=await src.GetLengthAsync(path);
|
if(await src.FileExistsAsync(path))
|
||||||
if(await src.FileExistsAsync(path))
|
|
||||||
{
|
{
|
||||||
using(var srcFile = await src.OpenReadAsync(path))
|
using(var srcFile = await src.OpenReadAsync(path))
|
||||||
{
|
{
|
||||||
|
double len=srcFile.Length;
|
||||||
|
|
||||||
|
|
||||||
ret= await CopyStream(srcFile,destFile,new Progress<long>((e)=>{
|
ret= await CopyStream(srcFile,destFile,new Progress<long>((e)=>{
|
||||||
if(progress !=null)
|
if(progress !=null)
|
||||||
|
@ -546,9 +499,9 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public static async Task CopyFileFrom(this IStorage _dest,ITYTDBase _src,string src,string dest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
public static async Task CopyFileFrom(this IStorage _dest,ITYTDBase _src,string src,string dest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
|
||||||
{
|
{
|
||||||
double len=await _src.GetLengthAsync(src);
|
|
||||||
using(var srcFile = await _src.OpenReadAsync(src))
|
using(var srcFile = await _src.OpenReadAsync(src))
|
||||||
{
|
{double len=srcFile.Length;
|
||||||
bool deleteFile=false;
|
bool deleteFile=false;
|
||||||
using(var destFile = await _dest.CreateAsync(dest))
|
using(var destFile = await _dest.CreateAsync(dest))
|
||||||
{
|
{
|
||||||
|
@ -738,10 +691,11 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double len=await src.GetLengthAsync(path);
|
|
||||||
dest.CreateDirectory(resDir);
|
dest.CreateDirectory(resDir);
|
||||||
using(var srcFile = await src.OpenReadAsync(path))
|
using(var srcFile = await src.OpenReadAsync(path))
|
||||||
{
|
{
|
||||||
|
double len=srcFile.Length;
|
||||||
bool deleteFile=false;
|
bool deleteFile=false;
|
||||||
using(var destFile = await dest.CreateAsync(path))
|
using(var destFile = await dest.CreateAsync(path))
|
||||||
{
|
{
|
||||||
|
@ -776,11 +730,25 @@ namespace Tesses.YouTubeDownloader
|
||||||
}
|
}
|
||||||
public interface IPersonalPlaylistGet
|
public interface IPersonalPlaylistGet
|
||||||
{
|
{
|
||||||
public IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name);
|
IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string name);
|
||||||
|
bool PersonalPlaylistExists(string name);
|
||||||
|
}
|
||||||
|
public interface IPersonalPlaylistSet : IPersonalPlaylistGet
|
||||||
|
{
|
||||||
|
Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items);
|
||||||
|
|
||||||
|
Task ReplacePersonalPlaylistAsync(string name,IEnumerable<ListContentItem> items);
|
||||||
|
|
||||||
|
Task RemoveItemFromPersonalPlaylistAsync(string name,VideoId id);
|
||||||
|
|
||||||
|
Task SetResolutionForItemInPersonalPlaylistAsync(string name,VideoId id,Resolution resolution);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
public interface IWritable : IPersonalPlaylistGet
|
public interface IWritable : IPersonalPlaylistGet, IPersonalPlaylistSet
|
||||||
{
|
{
|
||||||
public Task WriteAllTextAsync(string path,string data);
|
public Task WriteAllTextAsync(string path,string data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -12,10 +12,186 @@ using YoutubeExplode.Channels;
|
||||||
using YoutubeExplode.Playlists;
|
using YoutubeExplode.Playlists;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Espresso3389.HttpStream;
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using YoutubeExplode.Utils.Extensions;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Tesses.YouTubeDownloader
|
namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
|
|
||||||
|
//From YouTubeExplode
|
||||||
|
internal static class Helpers
|
||||||
|
{
|
||||||
|
public static async ValueTask<HttpResponseMessage> HeadAsync(
|
||||||
|
this HttpClient http,
|
||||||
|
string requestUri,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Head, requestUri);
|
||||||
|
return await http.SendAsync(
|
||||||
|
request,
|
||||||
|
HttpCompletionOption.ResponseHeadersRead,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public static async ValueTask<Stream> GetStreamAsync(
|
||||||
|
this HttpClient http,
|
||||||
|
string requestUri,
|
||||||
|
long? from = null,
|
||||||
|
long? to = null,
|
||||||
|
bool ensureSuccess = true,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||||
|
request.Headers.Range = new RangeHeaderValue(from, to);
|
||||||
|
|
||||||
|
var response = await http.SendAsync(
|
||||||
|
request,
|
||||||
|
HttpCompletionOption.ResponseHeadersRead,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ensureSuccess)
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
return await response.Content.ReadAsStreamAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async ValueTask<long?> TryGetContentLengthAsync(
|
||||||
|
this HttpClient http,
|
||||||
|
string requestUri,
|
||||||
|
bool ensureSuccess = true,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var response = await http.HeadAsync(requestUri, cancellationToken);
|
||||||
|
|
||||||
|
if (ensureSuccess)
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
return response.Content.Headers.ContentLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Special abstraction that works around YouTube's stream throttling
|
||||||
|
// and provides seeking support.
|
||||||
|
// From YouTubeExplode
|
||||||
|
internal class SegmentedHttpStream : Stream
|
||||||
|
{
|
||||||
|
private readonly HttpClient _http;
|
||||||
|
private readonly string _url;
|
||||||
|
private readonly long? _segmentSize;
|
||||||
|
|
||||||
|
private Stream _segmentStream;
|
||||||
|
private long _actualPosition;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override bool CanRead => true;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override bool CanSeek => true;
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
public override long Length { get; }
|
||||||
|
|
||||||
|
public override long Position { get; set; }
|
||||||
|
|
||||||
|
public SegmentedHttpStream(HttpClient http, string url, long length, long? segmentSize)
|
||||||
|
{
|
||||||
|
_url = url;
|
||||||
|
_http = http;
|
||||||
|
Length = length;
|
||||||
|
_segmentSize = segmentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetSegmentStream()
|
||||||
|
{
|
||||||
|
_segmentStream?.Dispose();
|
||||||
|
_segmentStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<Stream> ResolveSegmentStreamAsync(
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (_segmentStream != null)
|
||||||
|
return _segmentStream;
|
||||||
|
|
||||||
|
var from = Position;
|
||||||
|
|
||||||
|
var to = _segmentSize != null
|
||||||
|
? Position + _segmentSize - 1
|
||||||
|
: null;
|
||||||
|
|
||||||
|
var stream = await _http.GetStreamAsync(_url, from, to, true, cancellationToken);
|
||||||
|
|
||||||
|
return _segmentStream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask PreloadAsync(CancellationToken cancellationToken = default) =>
|
||||||
|
await ResolveSegmentStreamAsync(cancellationToken);
|
||||||
|
|
||||||
|
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Check if consumer changed position between reads
|
||||||
|
if (_actualPosition != Position)
|
||||||
|
ResetSegmentStream();
|
||||||
|
|
||||||
|
// Check if finished reading (exit condition)
|
||||||
|
if (Position >= Length)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var stream = await ResolveSegmentStreamAsync(cancellationToken);
|
||||||
|
var bytesRead = await stream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||||
|
_actualPosition = Position += bytesRead;
|
||||||
|
|
||||||
|
if (bytesRead != 0)
|
||||||
|
return bytesRead;
|
||||||
|
|
||||||
|
// Reached the end of the segment, try to load the next one
|
||||||
|
ResetSegmentStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override int Read(byte[] buffer, int offset, int count) =>
|
||||||
|
ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override long Seek(long offset, SeekOrigin origin) => Position = origin switch
|
||||||
|
{
|
||||||
|
SeekOrigin.Begin => offset,
|
||||||
|
SeekOrigin.Current => Position + offset,
|
||||||
|
SeekOrigin.End => Length + offset,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(origin))
|
||||||
|
};
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override void Flush() =>
|
||||||
|
throw new NotSupportedException();
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override void SetLength(long value) =>
|
||||||
|
throw new NotSupportedException();
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
public override void Write(byte[] buffer, int offset, int count) =>
|
||||||
|
throw new NotSupportedException();
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
ResetSegmentStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public class TYTDClient : TYTDBase,IDownloader
|
public class TYTDClient : TYTDBase,IDownloader
|
||||||
{
|
{
|
||||||
string url;
|
string url;
|
||||||
|
@ -244,22 +420,84 @@ namespace Tesses.YouTubeDownloader
|
||||||
return GetQueueListAsync().GetAwaiter().GetResult();
|
return GetQueueListAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async override Task<Stream> OpenReadAsync(string path)
|
public async override Task<Stream> OpenReadAsync(string path)
|
||||||
{
|
{
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
var strmLen= await client.TryGetContentLengthAsync($"{url}api/Storage/File/{path}",true);
|
||||||
HttpStream v=new HttpStream(new Uri($"{url}api/Storage/File/{path}"),new MemoryStream(),true,32 * 1024,null,client);
|
SegmentedHttpStream v=new SegmentedHttpStream(client,$"{url}api/Storage/File/{path}",strmLen.GetValueOrDefault(),null);
|
||||||
|
return v;
|
||||||
return await Task.FromResult(v);
|
|
||||||
}catch(Exception ex)
|
}catch(Exception ex)
|
||||||
{
|
{
|
||||||
_=ex;
|
_=ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(Stream.Null);
|
return Stream.Null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
||||||
|
{
|
||||||
|
Dictionary<string,string> values=new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "name", name},
|
||||||
|
{ "data", JsonConvert.SerializeObject(items.ToArray())}
|
||||||
|
};
|
||||||
|
var content = new FormUrlEncodedContent(values);
|
||||||
|
var response = await client.PostAsync($"{url}api/v2/AddToList",content);
|
||||||
|
var resposeStr = await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
||||||
|
{
|
||||||
|
//ReplaceList
|
||||||
|
Dictionary<string,string> values=new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "name", name},
|
||||||
|
{ "data", JsonConvert.SerializeObject(items.ToArray())}
|
||||||
|
};
|
||||||
|
var content = new FormUrlEncodedContent(values);
|
||||||
|
var response = await client.PostAsync($"{url}api/v2/ReplaceList",content);
|
||||||
|
var resposeStr = await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
|
||||||
|
|
||||||
|
await client.GetStringAsync($"{url}api/v2/DeleteFromList?name={WebUtility.UrlEncode(name)}&v={id.Value}");
|
||||||
|
|
||||||
|
}catch(Exception ex)
|
||||||
|
{
|
||||||
|
_=ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
|
||||||
|
|
||||||
|
await client.GetStringAsync($"{url}api/v2/SetResolutionInList?name={WebUtility.UrlEncode(name)}&v={id.Value}&res={resolution.ToString()}");
|
||||||
|
|
||||||
|
}catch(Exception ex)
|
||||||
|
{
|
||||||
|
_=ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void DeletePersonalPlaylist(string name)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
|
||||||
|
|
||||||
|
client.GetStringAsync($"{url}api/v2/DeleteList?name={WebUtility.UrlEncode(name)}").GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
}catch(Exception ex)
|
||||||
|
{
|
||||||
|
_=ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,6 +18,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
public IDownloader Downloader {get;set;}
|
public IDownloader Downloader {get;set;}
|
||||||
|
|
||||||
private ITYTDBase _base=null;
|
private ITYTDBase _base=null;
|
||||||
|
|
||||||
public ITYTDBase Storage {get {return _base;} set{_base=value;
|
public ITYTDBase Storage {get {return _base;} set{_base=value;
|
||||||
var v = value as IStorage;
|
var v = value as IStorage;
|
||||||
if(v != null)
|
if(v != null)
|
||||||
|
@ -32,6 +33,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
_storage.VideoFinished -= _EVT_VFIN;
|
_storage.VideoFinished -= _EVT_VFIN;
|
||||||
_storage.VideoProgress -= _EVT_VPROG;
|
_storage.VideoProgress -= _EVT_VPROG;
|
||||||
_storage.VideoStarted -= _EVT_VSTAR;
|
_storage.VideoStarted -= _EVT_VSTAR;
|
||||||
|
_storage.Error -= _EVT_ERR;
|
||||||
}
|
}
|
||||||
_storage=storage;
|
_storage=storage;
|
||||||
if(storage != null)
|
if(storage != null)
|
||||||
|
@ -41,6 +43,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
_storage.VideoFinished += _EVT_VFIN;
|
_storage.VideoFinished += _EVT_VFIN;
|
||||||
_storage.VideoProgress += _EVT_VPROG;
|
_storage.VideoProgress += _EVT_VPROG;
|
||||||
_storage.VideoStarted += _EVT_VSTAR;
|
_storage.VideoStarted += _EVT_VSTAR;
|
||||||
|
_storage.Error += _EVT_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -64,6 +67,10 @@ namespace Tesses.YouTubeDownloader
|
||||||
{
|
{
|
||||||
Bell?.Invoke(this,evt);
|
Bell?.Invoke(this,evt);
|
||||||
}
|
}
|
||||||
|
private void _EVT_ERR(object sender,TYTDErrorEventArgs evt)
|
||||||
|
{
|
||||||
|
Error?.Invoke(this,evt);
|
||||||
|
}
|
||||||
IStorage _storage=null;
|
IStorage _storage=null;
|
||||||
|
|
||||||
public LegacyConverter Legacy {
|
public LegacyConverter Legacy {
|
||||||
|
@ -125,7 +132,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
public event EventHandler<VideoProgressEventArgs> VideoProgress;
|
public event EventHandler<VideoProgressEventArgs> VideoProgress;
|
||||||
public event EventHandler<VideoFinishedEventArgs> VideoFinished;
|
public event EventHandler<VideoFinishedEventArgs> VideoFinished;
|
||||||
|
|
||||||
|
public event EventHandler<TYTDErrorEventArgs> Error;
|
||||||
|
|
||||||
public async Task StorageAsStorageAsync(Func<IStorage,Task> callback)
|
public async Task StorageAsStorageAsync(Func<IStorage,Task> callback)
|
||||||
{
|
{
|
||||||
|
@ -201,21 +208,25 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async Task<Stream> OpenReadAsync(string path)
|
public async Task<Stream> OpenReadAsync(string path)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return Stream.Null;
|
||||||
return await Storage.OpenReadAsync(path);
|
return await Storage.OpenReadAsync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> FileExistsAsync(string path)
|
public async Task<bool> FileExistsAsync(string path)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return false;
|
||||||
return await Storage.FileExistsAsync(path);
|
return await Storage.FileExistsAsync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> DirectoryExistsAsync(string path)
|
public async Task<bool> DirectoryExistsAsync(string path)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return false;
|
||||||
return await Storage.DirectoryExistsAsync(path);
|
return await Storage.DirectoryExistsAsync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> EnumerateFilesAsync(string path)
|
public async IAsyncEnumerable<string> EnumerateFilesAsync(string path)
|
||||||
{
|
{
|
||||||
|
if(Storage == null) yield break;
|
||||||
await foreach(var item in Storage.EnumerateFilesAsync(path))
|
await foreach(var item in Storage.EnumerateFilesAsync(path))
|
||||||
{
|
{
|
||||||
yield return item;
|
yield return item;
|
||||||
|
@ -224,6 +235,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path)
|
public async IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path)
|
||||||
{
|
{
|
||||||
|
if(Storage == null) yield break;
|
||||||
await foreach(var item in Storage.EnumerateDirectoriesAsync(path))
|
await foreach(var item in Storage.EnumerateDirectoriesAsync(path))
|
||||||
{
|
{
|
||||||
yield return item;
|
yield return item;
|
||||||
|
@ -315,9 +327,9 @@ namespace Tesses.YouTubeDownloader
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<(VideoId Id, Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name)
|
public async IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string name)
|
||||||
{
|
{
|
||||||
IAsyncEnumerable<(VideoId Id,Resolution Resolution)> items=null;
|
IAsyncEnumerable<ListContentItem> items=null;
|
||||||
StorageAsStorage((e)=>{
|
StorageAsStorage((e)=>{
|
||||||
items=e.GetPersonalPlaylistContentsAsync(name);
|
items=e.GetPersonalPlaylistContentsAsync(name);
|
||||||
|
|
||||||
|
@ -331,31 +343,37 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed)
|
public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
|
if(Downloader != null)
|
||||||
await Downloader.AddVideoAsync(id,resolution);
|
await Downloader.AddVideoAsync(id,resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed)
|
public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
|
if(Downloader != null)
|
||||||
await Downloader.AddPlaylistAsync(id,resolution);
|
await Downloader.AddPlaylistAsync(id,resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddChannelAsync(ChannelId id, Resolution resolution = Resolution.PreMuxed)
|
public async Task AddChannelAsync(ChannelId id, Resolution resolution = Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
|
if(Downloader != null)
|
||||||
await Downloader.AddChannelAsync(id,resolution);
|
await Downloader.AddChannelAsync(id,resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed)
|
public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed)
|
||||||
{
|
{
|
||||||
|
if(Downloader != null)
|
||||||
await Downloader.AddUserAsync(userName,resolution);
|
await Downloader.AddUserAsync(userName,resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList()
|
public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList()
|
||||||
{
|
{
|
||||||
|
if(Downloader == null) return new List<(SavedVideo Video,Resolution Resolution)>();
|
||||||
return Downloader.GetQueueList();
|
return Downloader.GetQueueList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SavedVideoProgress GetProgress()
|
public SavedVideoProgress GetProgress()
|
||||||
{
|
{
|
||||||
|
if(Downloader == null)return new SavedVideoProgress();
|
||||||
return Downloader.GetProgress();
|
return Downloader.GetProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,23 +432,22 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async Task<(string Path, bool Delete)> GetRealUrlOrPathAsync(string path)
|
public async Task<(string Path, bool Delete)> GetRealUrlOrPathAsync(string path)
|
||||||
{
|
{
|
||||||
|
if(Storage == null) return ("",false);
|
||||||
return await Storage.GetRealUrlOrPathAsync(path);
|
return await Storage.GetRealUrlOrPathAsync(path);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<long> GetLengthAsync(string path)
|
|
||||||
{
|
|
||||||
return await Storage.GetLengthAsync(path);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FileExists(string path)
|
public bool FileExists(string path)
|
||||||
{
|
{
|
||||||
|
if(Storage == null) return false;
|
||||||
return Storage.FileExists(path);
|
return Storage.FileExists(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> GetVideoIdsAsync()
|
public async IAsyncEnumerable<string> GetVideoIdsAsync()
|
||||||
{
|
{
|
||||||
|
if(Storage == null) yield break;
|
||||||
await foreach(var id in Storage.GetVideoIdsAsync())
|
await foreach(var id in Storage.GetVideoIdsAsync())
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(id);
|
yield return await Task.FromResult(id);
|
||||||
|
@ -439,11 +456,13 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async Task<SavedVideo> GetVideoInfoAsync(VideoId id)
|
public async Task<SavedVideo> GetVideoInfoAsync(VideoId id)
|
||||||
{
|
{
|
||||||
|
if(Storage == null) return new SavedVideo();
|
||||||
return await Storage.GetVideoInfoAsync(id);
|
return await Storage.GetVideoInfoAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<SavedVideo> GetVideosAsync()
|
public async IAsyncEnumerable<SavedVideo> GetVideosAsync()
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) yield break;
|
||||||
await foreach(var vid in Storage.GetVideosAsync())
|
await foreach(var vid in Storage.GetVideosAsync())
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(vid);
|
yield return await Task.FromResult(vid);
|
||||||
|
@ -452,6 +471,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async IAsyncEnumerable<SavedVideoLegacy> GetLegacyVideosAsync()
|
public async IAsyncEnumerable<SavedVideoLegacy> GetLegacyVideosAsync()
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) yield break;
|
||||||
await foreach(var item in Storage.GetLegacyVideosAsync())
|
await foreach(var item in Storage.GetLegacyVideosAsync())
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(item);
|
yield return await Task.FromResult(item);
|
||||||
|
@ -460,11 +480,13 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async Task<SavedVideoLegacy> GetLegacyVideoInfoAsync(VideoId id)
|
public async Task<SavedVideoLegacy> GetLegacyVideoInfoAsync(VideoId id)
|
||||||
{
|
{
|
||||||
|
if(Storage == null) return new SavedVideoLegacy();
|
||||||
return await Storage.GetLegacyVideoInfoAsync(id);
|
return await Storage.GetLegacyVideoInfoAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync()
|
public async IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync()
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) yield break;
|
||||||
await foreach(var item in Storage.GetPlaylistsAsync())
|
await foreach(var item in Storage.GetPlaylistsAsync())
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(item);
|
yield return await Task.FromResult(item);
|
||||||
|
@ -473,11 +495,13 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async Task<byte[]> ReadAllBytesAsync(string path, CancellationToken token = default)
|
public async Task<byte[]> ReadAllBytesAsync(string path, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return new byte[0];
|
||||||
return await Storage.ReadAllBytesAsync(path,token);
|
return await Storage.ReadAllBytesAsync(path,token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> GetPlaylistIdsAsync()
|
public async IAsyncEnumerable<string> GetPlaylistIdsAsync()
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) yield break;
|
||||||
await foreach(var item in Storage.GetPlaylistIdsAsync())
|
await foreach(var item in Storage.GetPlaylistIdsAsync())
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(item);
|
yield return await Task.FromResult(item);
|
||||||
|
@ -486,6 +510,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async IAsyncEnumerable<string> GetChannelIdsAsync()
|
public async IAsyncEnumerable<string> GetChannelIdsAsync()
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) yield break;
|
||||||
await foreach(var item in Storage.GetChannelIdsAsync())
|
await foreach(var item in Storage.GetChannelIdsAsync())
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(item);
|
yield return await Task.FromResult(item);
|
||||||
|
@ -494,6 +519,7 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
|
public async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) yield break;
|
||||||
await foreach(var item in Storage.GetYouTubeExplodeVideoIdsAsync())
|
await foreach(var item in Storage.GetYouTubeExplodeVideoIdsAsync())
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(item);
|
yield return await Task.FromResult(item);
|
||||||
|
@ -502,11 +528,13 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public async Task<SavedChannel> GetChannelInfoAsync(ChannelId id)
|
public async Task<SavedChannel> GetChannelInfoAsync(ChannelId id)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return new SavedChannel();
|
||||||
return await Storage.GetChannelInfoAsync(id);
|
return await Storage.GetChannelInfoAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<SavedChannel> GetChannelsAsync()
|
public async IAsyncEnumerable<SavedChannel> GetChannelsAsync()
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) yield break;
|
||||||
await foreach(var item in Storage.GetChannelsAsync())
|
await foreach(var item in Storage.GetChannelsAsync())
|
||||||
{
|
{
|
||||||
yield return await Task.FromResult(item);
|
yield return await Task.FromResult(item);
|
||||||
|
@ -515,49 +543,60 @@ namespace Tesses.YouTubeDownloader
|
||||||
|
|
||||||
public bool PlaylistInfoExists(PlaylistId id)
|
public bool PlaylistInfoExists(PlaylistId id)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return false;
|
||||||
return Storage.PlaylistInfoExists(id);
|
return Storage.PlaylistInfoExists(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool VideoInfoExists(VideoId id)
|
public bool VideoInfoExists(VideoId id)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return false;
|
||||||
return Storage.VideoInfoExists(id);
|
return Storage.VideoInfoExists(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ChannelInfoExists(ChannelId id)
|
public bool ChannelInfoExists(ChannelId id)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return false;
|
||||||
return Storage.ChannelInfoExists(id);
|
return Storage.ChannelInfoExists(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id)
|
public async Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return new SavedPlaylist();
|
||||||
return await Storage.GetPlaylistInfoAsync(id);
|
return await Storage.GetPlaylistInfoAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> ReadAllTextAsync(string file)
|
public async Task<string> ReadAllTextAsync(string file)
|
||||||
{
|
{
|
||||||
return Storage.ReadAllTextAsync(file);
|
if(Storage ==null) return "";
|
||||||
|
return await Storage.ReadAllTextAsync(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DirectoryExists(string path)
|
public bool DirectoryExists(string path)
|
||||||
{
|
{
|
||||||
|
if(Storage ==null) return false;
|
||||||
return Storage.DirectoryExists(path);
|
return Storage.DirectoryExists(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> EnumerateFiles(string path)
|
public IEnumerable<string> EnumerateFiles(string path)
|
||||||
{
|
{
|
||||||
return Storage.EnumerateFiles(path);
|
if(Storage ==null) yield break;
|
||||||
|
foreach(var item in Storage.EnumerateFiles(path))
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> EnumerateDirectories(string path)
|
public IEnumerable<string> EnumerateDirectories(string path)
|
||||||
{
|
{
|
||||||
return Storage.EnumerateDirectories(path);
|
if(Storage ==null) yield break;
|
||||||
}
|
foreach(var item in Storage.EnumerateDirectories(path))
|
||||||
|
{
|
||||||
public async Task<Stream> OpenReadAsyncWithLength(string path)
|
yield return item;
|
||||||
{
|
}
|
||||||
return await Storage.OpenReadAsyncWithLength(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public IReadOnlyList<Subscription> GetLoadedSubscriptions()
|
public IReadOnlyList<Subscription> GetLoadedSubscriptions()
|
||||||
{
|
{
|
||||||
IReadOnlyList<Subscription> subs=new List<Subscription>();
|
IReadOnlyList<Subscription> subs=new List<Subscription>();
|
||||||
|
@ -580,6 +619,47 @@ namespace Tesses.YouTubeDownloader
|
||||||
e.StartLoop(token);
|
e.StartLoop(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.AddToPersonalPlaylistAsync(name,items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.ReplacePersonalPlaylistAsync(name,items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.RemoveItemFromPersonalPlaylistAsync(name,id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution)
|
||||||
|
{
|
||||||
|
await StorageAsStorageAsync(async(e)=>{
|
||||||
|
await e.SetResolutionForItemInPersonalPlaylistAsync(name,id,resolution);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PersonalPlaylistExists(string name)
|
||||||
|
{
|
||||||
|
if(Storage ==null) return false;
|
||||||
|
return Storage.PersonalPlaylistExists(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeletePersonalPlaylist(string name)
|
||||||
|
{
|
||||||
|
StorageAsStorage((e)=>{
|
||||||
|
e.DeletePersonalPlaylist(name);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.4</Version>
|
<Version>1.1.5</Version>
|
||||||
<AssemblyVersion>1.1.4</AssemblyVersion>
|
<AssemblyVersion>1.1.5</AssemblyVersion>
|
||||||
<FileVersion>1.1.4</FileVersion>
|
<FileVersion>1.1.5</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>
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Espresso3389.HttpStream" Version="2.0.52.3" />
|
|
||||||
<PackageReference Include="HttpStream" Version="2.0.50" />
|
|
||||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="YouTubeExplode" Version="6.1.2" />
|
<PackageReference Include="YouTubeExplode" Version="6.1.2" />
|
||||||
|
|
Loading…
Reference in New Issue