using System; using YoutubeExplode; using YoutubeExplode.Videos; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Net.Http; using Newtonsoft.Json; using System.IO; 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 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 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 { public async IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string playlist) { var ls=JsonConvert.DeserializeObject>(await ReadAllTextAsync($"PersonalPlaylist/{playlist}.json")); foreach(var item in ls) { yield return await Task.FromResult(item); } } public async IAsyncEnumerable GetPersonalPlaylistsAsync() { await foreach(var item in EnumerateFilesAsync("PersonalPlaylist")) { if(Path.GetExtension(item) == ".json") { yield return await Task.FromResult(Path.GetFileNameWithoutExtension(item)); } } } public virtual async Task<(String Path,bool Delete)> GetRealUrlOrPathAsync(string path) { string tmpFile = Path.GetTempFileName(); File.Delete(tmpFile); tmpFile=tmpFile + Path.GetExtension(path); using(var f = File.Create(tmpFile)) { using(var src=await OpenReadAsync(path)) { await TYTDManager.CopyStream(src,f); } } return (tmpFile,true); } public virtual async Task 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(); } public async IAsyncEnumerable GetVideoIdsAsync() { await foreach(var item in EnumerateFilesAsync("Info")) { if(Path.GetExtension(item).Equals(".json",StringComparison.Ordinal)) { yield return Path.GetFileNameWithoutExtension(item); } } } public async Task GetVideoInfoAsync(VideoId id) { return JsonConvert.DeserializeObject(await ReadAllTextAsync($"Info/{id}.json")); } public async IAsyncEnumerable GetVideosAsync() { await foreach(var item in GetVideoIdsAsync()) { var item0=await GetVideoInfoAsync(item); if(item0 != null) { yield return item0; } } } public async IAsyncEnumerable GetLegacyVideosAsync() { await foreach(var item in GetVideoIdsAsync()) { var item0 =await GetLegacyVideoInfoAsync(item); if(item0 != null) { yield return item0; } } } public async Task GetLegacyVideoInfoAsync(VideoId id) { return JsonConvert.DeserializeObject(await ReadAllTextAsync($"Info/{id}.json")); } public async IAsyncEnumerable GetPlaylistsAsync() { await foreach(var item in GetPlaylistIdsAsync()) { var item0=await GetPlaylistInfoAsync(item); if(item0 != null) { yield return item0; } } } public async Task ReadAllBytesAsync(string path,CancellationToken token=default(CancellationToken)) { byte[] data=new byte[await GetLengthAsync(path)]; using(var strm = await OpenReadAsync(path)) { await strm.ReadAsync(data,0,data.Length,token); if(token.IsCancellationRequested) { return new byte[0]; } } return data; } public async IAsyncEnumerable GetPlaylistIdsAsync() { await foreach(var item in EnumerateFilesAsync("Playlist")) { if(Path.GetExtension(item).Equals(".json",StringComparison.Ordinal)) { yield return Path.GetFileNameWithoutExtension(item); } } } public async IAsyncEnumerable GetChannelIdsAsync() { await foreach(var item in EnumerateFilesAsync("Channel")) { if(Path.GetExtension(item).Equals(".json",StringComparison.Ordinal)) { yield return Path.GetFileNameWithoutExtension(item); } } } public async Task GetChannelInfoAsync(ChannelId id) { return JsonConvert.DeserializeObject(await ReadAllTextAsync($"Channel/{id}.json")); } public async IAsyncEnumerable GetChannelsAsync() { await foreach(var item in GetChannelIdsAsync()) { var item0=await GetChannelInfoAsync(item); if(item0 != null) { yield return item0; } } } public async Task GetPlaylistInfoAsync(PlaylistId id) { return JsonConvert.DeserializeObject(await ReadAllTextAsync($"Playlist/{id}.json")); } public async Task ReadAllTextAsync(string file) { using(var s = await OpenReadAsync(file)) { using(var sr = new StreamReader(s)) { return await sr.ReadToEndAsync(); } } } public bool DirectoryExists(string path) { return DirectoryExistsAsync(path).GetAwaiter().GetResult(); } public IEnumerable EnumerateFiles(string path) { var e = EnumerateFilesAsync(path).GetAsyncEnumerator(); while(e.MoveNextAsync().GetAwaiter().GetResult()) { yield return e.Current; } } public IEnumerable EnumerateDirectories(string path) { var e = EnumerateDirectoriesAsync(path).GetAsyncEnumerator(); while(e.MoveNextAsync().GetAwaiter().GetResult()) { yield return e.Current; } } public async Task OpenReadAsyncWithLength(string path) { return await TYTDBaseFileReader.GetStream(this,path); } public abstract Task OpenReadAsync(string path); public abstract Task FileExistsAsync(string path); public abstract Task DirectoryExistsAsync(string path); public abstract IAsyncEnumerable EnumerateFilesAsync(string path); public abstract IAsyncEnumerable EnumerateDirectoriesAsync(string path); } public static class TYTDManager { /// /// Add Video, Playlist, Channel Or Username /// /// Video, Playlist, Channel Or UserName Url Or Id /// Video Resolution public static async Task AddItemAsync(this IDownloader downloader,string url,Resolution resolution=Resolution.PreMuxed) { VideoId? vid = VideoId.TryParse(url); PlaylistId? pid = PlaylistId.TryParse(url); ChannelId? cid = ChannelId.TryParse(url); UserName? user = UserName.TryParse(url); if (url.Length == 11) { if (vid.HasValue) { await downloader.AddVideoAsync(vid.Value, resolution); //shall we download video } } else { if (pid.HasValue) { await downloader.AddPlaylistAsync(pid.Value, resolution); } else if (vid.HasValue) { await downloader.AddVideoAsync(vid.Value, resolution); } else if (cid.HasValue) { await downloader.AddChannelAsync(cid.Value, resolution); } else if (user.HasValue) { await downloader.AddUserAsync(user.Value, resolution); } } } /// /// Replace Personal Playlist /// /// Name of playlist /// Videos to set in playlist /// 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)); } /// /// Append to PersonalPlaylist /// /// Name of playlist /// Videos to add in playlist 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 prog,string text) { if(prog !=null) { prog.Report(text); } } private static string[] resStr = new string[] {"Muxed","PreMuxed","AudioOnly","VideoOnly"}; /// /// Convert DirectoryName to Resolution /// /// /// Video Resolution public static Resolution DirectoryToResolution(string folder) { int e= Array.IndexOf(resStr,folder); if(e == -1) {return Resolution.NoDownload;} return (Resolution)e; } public static string ResolutionToDirectory(Resolution res) { return resStr[(int)res]; } public static async Task CopyVideoToFileAsync(VideoId id,TYTDBase src,Stream destFile,Resolution res=Resolution.PreMuxed,IProgress progress=null,CancellationToken token=default(CancellationToken)) { string resDir = ResolutionToDirectory(res); string path=$"{resDir}/{id.Value}.mp4"; bool ret=false; double len=await src.GetLengthAsync(path); if(await src.FileExistsAsync(path)) { using(var srcFile = await src.OpenReadAsync(path)) { ret= await CopyStream(srcFile,destFile,new Progress((e)=>{ if(progress !=null) { progress.Report(e/len); } }),token); } } return ret; } public static async Task CopyVideoToFileAsync(VideoId id,TYTDBase src,string dest,Resolution res=Resolution.PreMuxed,IProgress progress=null,CancellationToken token=default(CancellationToken)) { bool delete=false; using(var f = File.Create(dest)) { delete=!await CopyVideoToFileAsync(id,src,f,res,progress,token); } if(delete) { File.Delete(dest); } } public static async Task CopyStream(Stream src,Stream dest,IProgress progress=null,CancellationToken token=default(CancellationToken)) { try{ byte[] array=new byte[1024]; int read=0; long totrd=0; do{ read=await src.ReadAsync(array,0,array.Length,token); if(token.IsCancellationRequested) { return false; } totrd += read; await dest.WriteAsync(array,0,read,token); if(token.IsCancellationRequested) { return false; } if(progress!=null) { progress.Report(totrd); } }while(read> 0); }catch(Exception ex) { _=ex; return false; } return true; } public static async Task CopyVideoAsync(VideoId id,TYTDBase src,TYTDStorage dest,Resolution res=Resolution.PreMuxed,IProgress progress=null,CancellationToken token=default(CancellationToken)) { string resDir = ResolutionToDirectory(res); string path=$"{resDir}/{id.Value}.mp4"; string infoFile = $"Info/{id.Value}.json"; if(await src.FileExistsAsync(infoFile)) { if(!await dest.FileExistsAsync(infoFile)) { string f=await src.ReadAllTextAsync(infoFile); await dest.WriteAllTextAsync(infoFile,f); } } if(token.IsCancellationRequested) { return; } if(await src.FileExistsAsync(path)) { if(!await dest.FileExistsAsync(path)) { return; } double len=await src.GetLengthAsync(path); dest.CreateDirectory(resDir); using(var srcFile = await src.OpenReadAsync(path)) { bool deleteFile=false; using(var destFile = await dest.CreateAsync(path)) { deleteFile=!await CopyStream(srcFile,destFile,new Progress((e)=>{ if(progress !=null) { progress.Report(e/len); } }),token); } //dest.DeleteFile(path); } } } } public class Chapter { public Chapter(TimeSpan ts, string name) { Offset = ts; ChapterName = name; } public TimeSpan Length {get;set;} public TimeSpan Offset { get; set; } public string ChapterName { get; set; } public override string ToString() { return $"[{Offset}-{Offset.Add(Length)}] {ChapterName}"; } } public interface IWritable { public IAsyncEnumerable<(VideoId Id,Resolution Resolution)> GetPersonalPlaylistContentsAsync(string name); public Task WriteAllTextAsync(string path,string data); } }