Add SFTP And Rearrange

This commit is contained in:
Mike Nolan 2022-06-24 18:02:51 -05:00
parent 4f31f73b6d
commit f79c6122fb
29 changed files with 1036 additions and 218 deletions

View File

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

View File

@ -83,10 +83,7 @@ namespace Tesses.YouTubeDownloader.DiscUtils
{
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)
{

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
</ItemGroup>
<ItemGroup>
@ -16,9 +16,9 @@
<PackageId>Tesses.YouTubeDownloader.DiscUtils</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.0.0.0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Version>1.0.0.1</Version>
<AssemblyVersion>1.0.0.1</AssemblyVersion>
<FileVersion>1.0.0.1</FileVersion>
<Description>Adds DiscUtils filesystem support</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Tesses.YouTubeDownloader.ExtensionLoader\Tesses.YouTubeDownloader.ExtensionLoader.csproj" />
<ItemGroup>
<ProjectReference Include="..\..\Tesses.YouTubeDownloader.ExtensionLoader\Tesses.YouTubeDownloader.ExtensionLoader.csproj" />
</ItemGroup>
<PropertyGroup>

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
</ItemGroup>
<ItemGroup>
@ -16,10 +16,10 @@
<PackageId>Tesses.YouTubeDownloader.Zio</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.0.0.0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<Version>1.0.0.1</Version>
<AssemblyVersion>1.0.0.1</AssemblyVersion>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<FileVersion>1.0.0.0</FileVersion>
<FileVersion>1.0.0.1</FileVersion>
<Description>Adds Zio filesystem support</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageTags>YoutubeExplode, YouTube, YouTubeDownloader</PackageTags>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
<ItemGroup>
<ProjectReference Include="..\..\Tesses.YouTubeDownloader\Tesses.YouTubeDownloader.csproj" />
</ItemGroup>
<PropertyGroup>

View File

@ -75,6 +75,7 @@ namespace Tesses.YouTubeDownloader.Server
var cd = new System.Net.Mime.ContentDisposition();
string filename = GetVideoName(name);
cd.FileName = filename;
cd.DispositionType = System.Net.Mime.DispositionTypeNames.Inline;
return cd;
}
@ -211,7 +212,7 @@ namespace Tesses.YouTubeDownloader.Server
await NotFoundServer.ServerNull.GetAsync(ctx);
return;
}
using(var s = await baseCtl.OpenReadAsyncWithLength(file))
using(var s = await baseCtl.OpenReadAsync(file))
{
await ctx.SendStreamAsync(s);
}
@ -326,18 +327,190 @@ namespace Tesses.YouTubeDownloader.Server
{
this.Downloader=downloader;
Add("/AddItem",AddItem);
Add("/AddChannel",AddChannel);
Add("/AddUser",AddUser);
Add("/AddPlaylist",AddPlaylist);
Add("/AddVideo",AddVideo);
Add("/Progress",ProgressFunc);
Add("/QueueList",QueueList);
Add("/subscribe",Subscribe);
Add("/resubscribe",Resubscribe);
Add("/unsubscribe",Unsubscribe);
Add("/subscriptions",Subscriptions);
AddBoth("/AddItem",AddItem);
AddBoth("/AddChannel",AddChannel);
AddBoth("/AddUser",AddUser);
AddBoth("/AddPlaylist",AddPlaylist);
AddBoth("/AddVideo",AddVideo);
AddBoth("/Progress",ProgressFunc);
AddBoth("/QueueList",QueueList);
AddBoth("/subscribe",Subscribe);
AddBoth("/resubscribe",Resubscribe);
AddBoth("/unsubscribe",Unsubscribe);
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)
{
@ -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)
{
@ -373,7 +548,7 @@ namespace Tesses.YouTubeDownloader.Server
}
}
ChannelId? cid=ChannelId.TryParse(WebUtility.UrlDecode(id));
ChannelId? cid=ChannelId.TryParse(id);
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)
{
@ -441,14 +616,14 @@ namespace Tesses.YouTubeDownloader.Server
}
}
ChannelId? cid=ChannelId.TryParse(WebUtility.UrlDecode(id));
ChannelId? cid=ChannelId.TryParse(id);
if(cid.HasValue)
{
await storage.SubscribeAsync(cid.Value,getinfo,conf);
}else{
UserName? uname=UserName.TryParse(WebUtility.UrlDecode(id));
UserName? uname=UserName.TryParse(id);
await storage.SubscribeAsync(uname.Value,conf);
}
@ -473,6 +648,7 @@ namespace Tesses.YouTubeDownloader.Server
if(ctx.QueryParams.TryGetFirst("v",out id))
{
Resolution resolution=Resolution.PreMuxed;
string res;
if(ctx.QueryParams.TryGetFirst("res",out res))

View File

@ -5,7 +5,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Tesses.WebServer" Version="1.0.3.3" />
<PackageReference Include="Tesses.WebServer" Version="1.0.3.4" />
</ItemGroup>
<PropertyGroup>
@ -15,9 +15,9 @@
<PackageId>Tesses.YouTubeDownloader.Server</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.3</Version>
<AssemblyVersion>1.1.3</AssemblyVersion>
<FileVersion>1.1.3</FileVersion>
<Description>Adds WebServer to TYTD</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>

View File

@ -11,7 +11,7 @@ namespace Tesses.YouTubeDownloader
{
public class BestStreams
{
public bool VideoFrozen {get;set;}
public BestStreamInfo MuxedStreamInfo {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)
{
if(video.LegacyVideo)
{
if(resolution == Resolution.Mux)
@ -27,10 +28,22 @@ namespace Tesses.YouTubeDownloader
return $"{TYTDManager.ResolutionToDirectory(resolution)}/{video.Id}.mp4";
}else{
var f= await BestStreamInfo.GetBestStreams(storage,video.Id);
if(f ==null)
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 ext=exts[(int)resolution];
@ -87,6 +100,15 @@ namespace Tesses.YouTubeDownloader
}
DateTime expires=DateTime.Now.AddHours(6);
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 audioOnly=res.GetAudioOnlyStreams().GetWithHighestBitrate();

View File

@ -145,7 +145,7 @@ namespace Tesses.YouTubeDownloader
}
}catch(Exception ex)
{
await GetLogger().WriteAsync(ex);
await GetLogger().WriteAsync(ex,video.Id);
}
}
@ -154,7 +154,7 @@ namespace Tesses.YouTubeDownloader
if (await FileExistsAsync(path))
{
return (await GetLengthAsync(path) == 0);
return ((await OpenReadAsync(path)).Length == 0);
}
return true;
}
@ -205,8 +205,9 @@ namespace Tesses.YouTubeDownloader
}
}catch(Exception ex)
{
Console.WriteLine(ex.Message);
_=ex;
Console.WriteLine("FFMPEG ERROR, sorry cant read logging config");
return false;
}
return true;
@ -332,10 +333,11 @@ namespace Tesses.YouTubeDownloader
DeleteIfExists(video_bin);
DeleteIfExists(audio_bin);
DeleteIfExists(output_mkv);
long len=await GetLengthAsync(videoSrc);
using(var vstrm_src=await OpenReadAsync(videoSrc))
{
long len = vstrm_src.Length;
using(var vstrm_dest = File.Create(video_bin))
{
Console.WriteLine("Opening vstream");
@ -359,7 +361,7 @@ namespace Tesses.YouTubeDownloader
using(var astrm_dest = File.Create(audio_bin))
{
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)=>{
if(progress !=null)
{
@ -455,6 +457,10 @@ namespace Tesses.YouTubeDownloader
if(!can_download) return false;
if(streams != null)
{
if(streams.VideoFrozen)
{
throw new Exception($"[TYTD Specific Error] Video is frozen, we wont do anything with the video.\nplease set \"VideoFrozen\": false in the file \"Info/{video.Id}.json\" to fix this problem");
}
await MoveLegacyStreams(video,streams);
string complete = $"VideoOnly/{video.Id}.{streams.VideoOnlyStreamInfo.Container}";
string incomplete = $"VideoOnly/{video.Id}incomplete.{streams.VideoOnlyStreamInfo.Container}";
@ -472,11 +478,11 @@ namespace Tesses.YouTubeDownloader
}
if(report)
await ReportStartVideo(video, Resolution.VideoOnly,streams.VideoOnlyStreamInfo.Size.Bytes);
long len=await GetLengthAsync(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)
{
@ -511,6 +517,7 @@ namespace Tesses.YouTubeDownloader
}
public async Task MoveLegacyStreams(SavedVideo video,BestStreams streams)
{
if(video.VideoFrozen) return;
if(video.LegacyVideo)
{
string legacyVideoOnlyComplete = $"VideoOnly/{video.Id}.mp4";
@ -550,6 +557,10 @@ namespace Tesses.YouTubeDownloader
if(!can_download) return false;
if(streams != null)
{
if(streams.VideoFrozen)
{
throw new Exception($"[TYTD Specific Error] Video is frozen, we wont do anything with the video.\nplease set \"VideoFrozen\": false in the file \"Info/{video.Id}.json\" to fix this problem");
}
string complete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}";
string incomplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}";
await MoveLegacyStreams(video,streams);
@ -568,11 +579,11 @@ namespace Tesses.YouTubeDownloader
}
if(report)
await ReportStartVideo(video, Resolution.AudioOnly,streams.AudioOnlyStreamInfo.Size.Bytes);
long len=await GetLengthAsync(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)
{
@ -597,6 +608,10 @@ namespace Tesses.YouTubeDownloader
if(!can_download) return;
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);
string complete = $"PreMuxed/{video.Id}.{streams.MuxedStreamInfo.Container}";
string incomplete = $"PreMuxed/{video.Id}incomplete.{streams.MuxedStreamInfo.Container}";
@ -616,11 +631,11 @@ namespace Tesses.YouTubeDownloader
}
if(report)
await ReportStartVideo(video,Resolution.PreMuxed,streams.MuxedStreamInfo.Size.Bytes);
long len=await GetLengthAsync(incomplete);
bool ret;
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
if(ret)

View File

@ -10,7 +10,7 @@ using YoutubeExplode.Playlists;
using YoutubeExplode.Channels;
namespace Tesses.YouTubeDownloader
{
public interface IDownloader
public interface IDownloader : IPersonalPlaylistSet
{
Task AddVideoAsync(VideoId 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(UserName name,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload);
Task ResubscribeAsync(ChannelId id,ChannelBellInfo info=ChannelBellInfo.NotifyAndDownload);
void DeletePersonalPlaylist(string name);
}
}

View File

@ -55,5 +55,6 @@ namespace Tesses.YouTubeDownloader
Task MoveLegacyStreams(SavedVideo video,BestStreams streams);
void StartLoop(CancellationToken token=default(CancellationToken));
event EventHandler<TYTDErrorEventArgs> Error;
}
}

View File

@ -18,7 +18,7 @@ namespace Tesses.YouTubeDownloader
IAsyncEnumerable<string> GetPersonalPlaylistsAsync();
Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path);
Task<long> GetLengthAsync(string path);
bool FileExists(string path);
IAsyncEnumerable<string> GetVideoIdsAsync();
Task<SavedVideo> GetVideoInfoAsync(VideoId id);
@ -46,7 +46,7 @@ namespace Tesses.YouTubeDownloader
IEnumerable<string> EnumerateFiles(string path);
IEnumerable<string> EnumerateDirectories(string path);
Task<Stream> OpenReadAsyncWithLength(string path);
Task<Stream> OpenReadAsync(string path);
Task<bool> FileExistsAsync(string path);
@ -56,5 +56,7 @@ namespace Tesses.YouTubeDownloader
IAsyncEnumerable<string> EnumerateFilesAsync(string path);
IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path);
}
}

View File

@ -1,7 +1,3 @@
using System;
using YoutubeExplode;
using YoutubeExplode.Videos;
@ -21,9 +17,25 @@ namespace Tesses.YouTubeDownloader
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 event EventHandler<TYTDErrorEventArgs> Error;
internal LoggerProperties Properties {get;set;}
public LoggerProperties GetProperties()
{
@ -154,10 +166,17 @@ namespace Tesses.YouTubeDownloader
//mtx.ReleaseMutex();
}
}
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)
{

View File

@ -40,9 +40,15 @@ namespace Tesses.YouTubeDownloader
string path=$"Channel/{Id.Value}.json";
if(await storage.Continue(path))
{
try{
channel=await DownloadThumbnails(storage,await storage.YoutubeClient.Channels.GetAsync(Id.Value));
//channel=new SavedChannel(i);
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(channel));
}catch(Exception ex)
{
await storage.GetLogger().WriteAsync(ex);
return null;
}
return channel;
}else{
var j=JsonConvert.DeserializeObject<SavedChannel>(await storage.ReadAllTextAsync(path));
@ -114,6 +120,8 @@ namespace Tesses.YouTubeDownloader
string path=$"Playlist/{Id}.json";
List<IVideo> videos=new List<IVideo>();
try{
await foreach(var vid in storage.YoutubeClient.Playlists.GetVideosAsync(Id))
{
videos.Add(vid);
@ -127,6 +135,10 @@ namespace Tesses.YouTubeDownloader
}
await storage.WriteAllTextAsync(path,JsonConvert.SerializeObject(p));
}catch(Exception ex)
{
await storage.GetLogger().WriteAsync(ex);
}
if(Resolution == Resolution.NoDownload) return;
foreach(var item in videos)
{
@ -161,7 +173,8 @@ namespace Tesses.YouTubeDownloader
await video.DownloadThumbnails(storage);
}catch(Exception ex)
{
_=ex;
await storage.GetLogger().WriteAsync(ex,Id);
return;
}

View File

@ -24,6 +24,8 @@ namespace Tesses.YouTubeDownloader
}
public class SavedVideoLegacy
{
public string Id {get;set;}
public string Title {get;set;}
public string AuthorChannelId {get;set;}
@ -80,7 +82,10 @@ namespace Tesses.YouTubeDownloader
UploadDate=new DateTime(1992,8,20);
AddDate=DateTime.Now;
LegacyVideo=false;
DownloadFrom="YouTube";
VideoFrozen=false;
}
public SavedVideo(Video video)
{
Id=video.Id;
@ -96,11 +101,16 @@ namespace Tesses.YouTubeDownloader
UploadDate = video.UploadDate.DateTime;
AddDate=DateTime.Now;
LegacyVideo=false;
DownloadFrom="YouTube";
VideoFrozen=false;
}
public bool LegacyVideo {get;set;}
public bool VideoFrozen {get;set;}
public string DownloadFrom {get;set;}
public SavedVideoLegacy ToLegacy()
{
SavedVideoLegacy legacy=new SavedVideoLegacy();
legacy.Thumbnails=new List<(int, int, string)>();
legacy.Thumbnails.Add((120,90,$"https://s.ytimg.com/vi/{Id}/default.jpg"));
@ -117,6 +127,8 @@ namespace Tesses.YouTubeDownloader
legacy.Title=Title;
legacy.UploadDate=UploadDate.ToString();
legacy.Views=Views;
return legacy;
}
public Video ToVideo()

View File

@ -164,6 +164,10 @@ namespace Tesses.YouTubeDownloader
});
thread1.Start();
}
internal void ThrowError(TYTDErrorEventArgs e)
{
Error?.Invoke(this,e);
}
public async Task WriteAllTextAsync(string path,string data)
{
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));
}
}
}

View File

@ -13,74 +13,18 @@ using YoutubeExplode.Playlists;
using YoutubeExplode.Channels;
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 async IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string playlist)
public bool PersonalPlaylistExists(string name)
{
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)
{
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)
{
return FileExistsAsync(path).GetAwaiter().GetResult();
@ -182,17 +118,18 @@ namespace Tesses.YouTubeDownloader
}
public async Task<byte[]> ReadAllBytesAsync(string path,CancellationToken token=default(CancellationToken))
{
byte[] data=new byte[await GetLengthAsync(path)];
using(var strm = await OpenReadAsync(path))
{using(var strm = await OpenReadAsync(path))
{
byte[] data=new byte[strm.Length];
await strm.ReadAsync(data,0,data.Length,token);
if(token.IsCancellationRequested)
{
return new byte[0];
}
return data;
}
return data;
}
public async IAsyncEnumerable<string> GetPlaylistIdsAsync()
{
@ -287,10 +224,7 @@ namespace Tesses.YouTubeDownloader
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<bool> FileExistsAsync(string path);
@ -302,7 +236,57 @@ namespace Tesses.YouTubeDownloader
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>
/// 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)
{
if(prog !=null)
@ -429,11 +381,12 @@ namespace Tesses.YouTubeDownloader
if(string.IsNullOrWhiteSpace(path)) return 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))
{
double len=srcFile.Length;
ret= await CopyStream(srcFile,destFile,new Progress<long>((e)=>{
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))
{
double len=await _src.GetLengthAsync(src);
using(var srcFile = await _src.OpenReadAsync(src))
{
{double len=srcFile.Length;
bool deleteFile=false;
using(var destFile = await _dest.CreateAsync(dest))
{
@ -738,10 +691,11 @@ namespace Tesses.YouTubeDownloader
{
return;
}
double len=await src.GetLengthAsync(path);
dest.CreateDirectory(resDir);
using(var srcFile = await src.OpenReadAsync(path))
{
double len=srcFile.Length;
bool deleteFile=false;
using(var destFile = await dest.CreateAsync(path))
{
@ -776,11 +730,25 @@ namespace Tesses.YouTubeDownloader
}
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);
}
}

View File

@ -12,10 +12,186 @@ using YoutubeExplode.Channels;
using YoutubeExplode.Playlists;
using System.Net.Http;
using System.Net;
using Espresso3389.HttpStream;
using System.Diagnostics.CodeAnalysis;
using YoutubeExplode.Utils.Extensions;
using System.Net.Http.Headers;
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
{
string url;
@ -244,22 +420,84 @@ namespace Tesses.YouTubeDownloader
return GetQueueListAsync().GetAwaiter().GetResult();
}
public async override Task<Stream> OpenReadAsync(string path)
{
try{
HttpStream v=new HttpStream(new Uri($"{url}api/Storage/File/{path}"),new MemoryStream(),true,32 * 1024,null,client);
return await Task.FromResult(v);
var strmLen= await client.TryGetContentLengthAsync($"{url}api/Storage/File/{path}",true);
SegmentedHttpStream v=new SegmentedHttpStream(client,$"{url}api/Storage/File/{path}",strmLen.GetValueOrDefault(),null);
return v;
}catch(Exception 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;
}
}
}
}

View File

@ -18,6 +18,7 @@ namespace Tesses.YouTubeDownloader
public IDownloader Downloader {get;set;}
private ITYTDBase _base=null;
public ITYTDBase Storage {get {return _base;} set{_base=value;
var v = value as IStorage;
if(v != null)
@ -32,6 +33,7 @@ namespace Tesses.YouTubeDownloader
_storage.VideoFinished -= _EVT_VFIN;
_storage.VideoProgress -= _EVT_VPROG;
_storage.VideoStarted -= _EVT_VSTAR;
_storage.Error -= _EVT_ERR;
}
_storage=storage;
if(storage != null)
@ -41,6 +43,7 @@ namespace Tesses.YouTubeDownloader
_storage.VideoFinished += _EVT_VFIN;
_storage.VideoProgress += _EVT_VPROG;
_storage.VideoStarted += _EVT_VSTAR;
_storage.Error += _EVT_ERR;
}
}
@ -64,6 +67,10 @@ namespace Tesses.YouTubeDownloader
{
Bell?.Invoke(this,evt);
}
private void _EVT_ERR(object sender,TYTDErrorEventArgs evt)
{
Error?.Invoke(this,evt);
}
IStorage _storage=null;
public LegacyConverter Legacy {
@ -125,7 +132,7 @@ namespace Tesses.YouTubeDownloader
public event EventHandler<VideoProgressEventArgs> VideoProgress;
public event EventHandler<VideoFinishedEventArgs> VideoFinished;
public event EventHandler<TYTDErrorEventArgs> Error;
public async Task StorageAsStorageAsync(Func<IStorage,Task> callback)
{
@ -201,21 +208,25 @@ namespace Tesses.YouTubeDownloader
public async Task<Stream> OpenReadAsync(string path)
{
if(Storage ==null) return Stream.Null;
return await Storage.OpenReadAsync(path);
}
public async Task<bool> FileExistsAsync(string path)
{
if(Storage ==null) return false;
return await Storage.FileExistsAsync(path);
}
public async Task<bool> DirectoryExistsAsync(string path)
{
if(Storage ==null) return false;
return await Storage.DirectoryExistsAsync(path);
}
public async IAsyncEnumerable<string> EnumerateFilesAsync(string path)
{
if(Storage == null) yield break;
await foreach(var item in Storage.EnumerateFilesAsync(path))
{
yield return item;
@ -224,6 +235,7 @@ namespace Tesses.YouTubeDownloader
public async IAsyncEnumerable<string> EnumerateDirectoriesAsync(string path)
{
if(Storage == null) yield break;
await foreach(var item in Storage.EnumerateDirectoriesAsync(path))
{
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)=>{
items=e.GetPersonalPlaylistContentsAsync(name);
@ -331,31 +343,37 @@ namespace Tesses.YouTubeDownloader
public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed)
{
if(Downloader != null)
await Downloader.AddVideoAsync(id,resolution);
}
public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed)
{
if(Downloader != null)
await Downloader.AddPlaylistAsync(id,resolution);
}
public async Task AddChannelAsync(ChannelId id, Resolution resolution = Resolution.PreMuxed)
{
if(Downloader != null)
await Downloader.AddChannelAsync(id,resolution);
}
public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed)
{
if(Downloader != null)
await Downloader.AddUserAsync(userName,resolution);
}
public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList()
{
if(Downloader == null) return new List<(SavedVideo Video,Resolution Resolution)>();
return Downloader.GetQueueList();
}
public SavedVideoProgress GetProgress()
{
if(Downloader == null)return new SavedVideoProgress();
return Downloader.GetProgress();
}
@ -414,23 +432,22 @@ namespace Tesses.YouTubeDownloader
public async Task<(string Path, bool Delete)> GetRealUrlOrPathAsync(string path)
{
if(Storage == null) return ("",false);
return await Storage.GetRealUrlOrPathAsync(path);
}
public async Task<long> GetLengthAsync(string path)
{
return await Storage.GetLengthAsync(path);
}
public bool FileExists(string path)
{
if(Storage == null) return false;
return Storage.FileExists(path);
}
public async IAsyncEnumerable<string> GetVideoIdsAsync()
{
if(Storage == null) yield break;
await foreach(var id in Storage.GetVideoIdsAsync())
{
yield return await Task.FromResult(id);
@ -439,11 +456,13 @@ namespace Tesses.YouTubeDownloader
public async Task<SavedVideo> GetVideoInfoAsync(VideoId id)
{
if(Storage == null) return new SavedVideo();
return await Storage.GetVideoInfoAsync(id);
}
public async IAsyncEnumerable<SavedVideo> GetVideosAsync()
{
if(Storage ==null) yield break;
await foreach(var vid in Storage.GetVideosAsync())
{
yield return await Task.FromResult(vid);
@ -452,6 +471,7 @@ namespace Tesses.YouTubeDownloader
public async IAsyncEnumerable<SavedVideoLegacy> GetLegacyVideosAsync()
{
if(Storage ==null) yield break;
await foreach(var item in Storage.GetLegacyVideosAsync())
{
yield return await Task.FromResult(item);
@ -460,11 +480,13 @@ namespace Tesses.YouTubeDownloader
public async Task<SavedVideoLegacy> GetLegacyVideoInfoAsync(VideoId id)
{
if(Storage == null) return new SavedVideoLegacy();
return await Storage.GetLegacyVideoInfoAsync(id);
}
public async IAsyncEnumerable<SavedPlaylist> GetPlaylistsAsync()
{
if(Storage ==null) yield break;
await foreach(var item in Storage.GetPlaylistsAsync())
{
yield return await Task.FromResult(item);
@ -473,11 +495,13 @@ namespace Tesses.YouTubeDownloader
public async Task<byte[]> ReadAllBytesAsync(string path, CancellationToken token = default)
{
if(Storage ==null) return new byte[0];
return await Storage.ReadAllBytesAsync(path,token);
}
public async IAsyncEnumerable<string> GetPlaylistIdsAsync()
{
if(Storage ==null) yield break;
await foreach(var item in Storage.GetPlaylistIdsAsync())
{
yield return await Task.FromResult(item);
@ -486,6 +510,7 @@ namespace Tesses.YouTubeDownloader
public async IAsyncEnumerable<string> GetChannelIdsAsync()
{
if(Storage ==null) yield break;
await foreach(var item in Storage.GetChannelIdsAsync())
{
yield return await Task.FromResult(item);
@ -494,6 +519,7 @@ namespace Tesses.YouTubeDownloader
public async IAsyncEnumerable<VideoId> GetYouTubeExplodeVideoIdsAsync()
{
if(Storage ==null) yield break;
await foreach(var item in Storage.GetYouTubeExplodeVideoIdsAsync())
{
yield return await Task.FromResult(item);
@ -502,11 +528,13 @@ namespace Tesses.YouTubeDownloader
public async Task<SavedChannel> GetChannelInfoAsync(ChannelId id)
{
if(Storage ==null) return new SavedChannel();
return await Storage.GetChannelInfoAsync(id);
}
public async IAsyncEnumerable<SavedChannel> GetChannelsAsync()
{
if(Storage ==null) yield break;
await foreach(var item in Storage.GetChannelsAsync())
{
yield return await Task.FromResult(item);
@ -515,49 +543,60 @@ namespace Tesses.YouTubeDownloader
public bool PlaylistInfoExists(PlaylistId id)
{
if(Storage ==null) return false;
return Storage.PlaylistInfoExists(id);
}
public bool VideoInfoExists(VideoId id)
{
if(Storage ==null) return false;
return Storage.VideoInfoExists(id);
}
public bool ChannelInfoExists(ChannelId id)
{
if(Storage ==null) return false;
return Storage.ChannelInfoExists(id);
}
public async Task<SavedPlaylist> GetPlaylistInfoAsync(PlaylistId id)
{
if(Storage ==null) return new SavedPlaylist();
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)
{
if(Storage ==null) return false;
return Storage.DirectoryExists(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)
{
return Storage.EnumerateDirectories(path);
}
public async Task<Stream> OpenReadAsyncWithLength(string path)
{
return await Storage.OpenReadAsyncWithLength(path);
if(Storage ==null) yield break;
foreach(var item in Storage.EnumerateDirectories(path))
{
yield return item;
}
}
public IReadOnlyList<Subscription> GetLoadedSubscriptions()
{
IReadOnlyList<Subscription> subs=new List<Subscription>();
@ -580,6 +619,47 @@ namespace Tesses.YouTubeDownloader
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

View File

@ -7,9 +7,9 @@
<PackageId>Tesses.YouTubeDownloader</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.1.4</Version>
<AssemblyVersion>1.1.4</AssemblyVersion>
<FileVersion>1.1.4</FileVersion>
<Version>1.1.5</Version>
<AssemblyVersion>1.1.5</AssemblyVersion>
<FileVersion>1.1.5</FileVersion>
<Description>A YouTube Downloader</Description>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
@ -18,8 +18,6 @@
</PropertyGroup>
<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="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="YouTubeExplode" Version="6.1.2" />