diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..cf19ee1 --- /dev/null +++ b/Program.cs @@ -0,0 +1,320 @@ +using CommandLine; +using System; +using System.IO; +using System.IO.Pipes; +using System.Reflection; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading; +using TessesYoutubeDownloader.Server.Models; + +namespace youtube_downloader +{ + + class Program + { + static void SendStringArray(System.IO.Stream s, string[] array) + { + s.Write(BitConverter.GetBytes(array.Length), 0, 4); + foreach (var item in array) + { + byte[] lenOfitem = BitConverter.GetBytes(item.Length); + byte[] argtext = System.Text.Encoding.UTF8.GetBytes(item); + s.Write(lenOfitem, 0, lenOfitem.Length); + s.Write(argtext, 0, argtext.Length); + } + } + static string[] GetStringArray(System.IO.Stream s) + { + byte[] items = new byte[4]; + s.Read(items, 0, items.Length); + int items2 = BitConverter.ToInt32(items, 0); + + string[] arraydata = new string[items2]; + for (int i = 0; i < items2; i++) + { + s.Read(items, 0, 4); + int item3 = BitConverter.ToInt32(items, 0); + byte[] v = new byte[item3]; + s.Read(v, 0, item3); + arraydata[i] = System.Text.Encoding.UTF8.GetString(v); + } + return arraydata; + } + static string _AppName = + Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().GetName().Name); + static void Main(string[] args) + { + // we need to get our app name so that + // we can create unique names for our mutex and our pipe + + var notAlreadyRunning = true; + // wrap the meat of the application code with a named mutex so it runs only once + using (var mutex = new Mutex(true, _AppName + "Singleton", out notAlreadyRunning)) + { + if (notAlreadyRunning) + { + // do additional work here, startup stuff + // Console.WriteLine("Running. Press any key to exit..."); + // ... + // now process our initial main command line + _ProcessCommandLine(args); + // start the IPC sink. + var srv = new NamedPipeServerStream(_AppName + "IPC", + PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + // it's easier to use the AsyncCallback than it is to use Tasks here: + // this can't block, so some form of async is a must + + srv.BeginWaitForConnection(new AsyncCallback(_ConnectionHandler), srv); + + // block here until exit + Console.ReadKey(); + // if this was a windows forms app you would put your + // "Applicantion.Run(new MyForm());" here + // finally, run any teardown code and exit + + srv.Close(); + } + else // another instance is running + { + // connect to the main app + var cli = new NamedPipeClientStream(".", _AppName + "IPC", PipeDirection.InOut); + cli.Connect(); + SendStringArray(cli, args); + byte[] leng = new byte[4]; + cli.Read(leng, 0, 4); + int sz = BitConverter.ToInt32(leng); + byte[] sdata = new byte[sz]; + cli.Read(sdata, 0, sz); + string strdata = System.Text.Encoding.UTF8.GetString(sdata); + Console.Write(strdata); + // serialize and send the command line + + cli.Close(); + // and exit + } + } + } + static void _ConnectionHandler(IAsyncResult result) + { + var srv = result.AsyncState as NamedPipeServerStream; + srv.EndWaitForConnection(result); + // we're connected, now deserialize the incoming command line + + var inargs = GetStringArray(srv); + r = new RESPONSE(srv); + // process incoming command line + _ProcessCommandLine(inargs); + r.SendResponse(); + srv = new NamedPipeServerStream(_AppName + "IPC", PipeDirection.InOut, + 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + + srv.BeginWaitForConnection(new AsyncCallback(_ConnectionHandler), srv); + } + + [Verb("exit", HelpText = "Download Video")] + + public class ExitApp + { + + } + [Verb("video", true, HelpText = "Download Video")] + + public class DownloadVideo + { + [Value(0, Required = true, HelpText = "The id or url of video")] + public string Id { get; set; } + + [Option('f', "format", Default = Resolution.NoConvert, HelpText = "possible values are (NoConvert,Convert,Audio)")] + public Resolution Format { get; set; } + + } + [Verb("playlist", HelpText = "Download entire Playlist")] + public class DownloadPlaylist + { + [Value(0, Required = true, HelpText = "The id or url of playlist")] + public string Id { get; set; } + + [Option('f', "format", Default = Resolution.NoConvert, HelpText = "possible values are (NoConvert,Convert,Audio)")] + public Resolution Format { get; set; } + } + [Verb("channel", HelpText = "Download entire Channel (using channel id)")] + public class DownloadChannel + { + [Value(0, Required = true, HelpText = "The id or url of channel")] + public string Id { get; set; } + + [Option('f', "format", Default = Resolution.NoConvert, HelpText = "possible values are (NoConvert,Convert,Audio)")] + public Resolution Format { get; set; } + } + [Verb("user", HelpText = "Download entire Channel (using username)")] + + public class DownloadUser + { + [Value(0, Required = true, HelpText = "The name or url of the user")] + public string Id { get; set; } + + [Option('f', "format", Default = Resolution.NoConvert, HelpText = "possible values are (NoConvert,Convert,Audio)")] + public Resolution Format { get; set; } + + + } + [Verb("move", HelpText = "Move item in queue")] + public class MoveQueue + { + [Option('d', "moveto", HelpText = "can be (up,down,top,bottom,remove)")] + public string MoveTo { get; set; } + + [Option('s', "movefrom", HelpText = "Can be number (index) or \"last\"")] + + public string MoveFrom { get; set; } + } + [Verb("info", HelpText = "Get info")] + public class Info + { + [Option('m', "machine", Default = false, HelpText = "In json")] + public bool ForMachine { get; set; } + + [Option('p', "page", Required = true, HelpText = "can be (progress,queue,location)")] + public string Page { get; set; } + + } + static RESPONSE r=null; + static void _ProcessCommandLine(string[] args) + { + CommandLine.Parser.Default.ParseArguments(args) + .MapResult( + (DownloadVideo opts) => RunDownloadVideo(opts), + (DownloadPlaylist opts) => RunDownloadPlaylist(opts), + (DownloadChannel opts) => RunDownloadChannel(opts), + (DownloadUser opts) => RunDownloadUser(opts), + (MoveQueue opts) => RunMoveQueue(opts), + (Info opts) => RunInfo(opts), + (ExitApp opts) => RunExitApp(opts), + + errs => 1); + } + static void WriteVideoInfo(StringBuilder b,SavedVideo v,bool description) + { + //v.AuthorChannelId + //v.AuthorTitle + //v.Dislikes + //v.Duration + //v.Id + //v.Keywords + //v.Likes + //v.Title + //v.UploadDate + //v.Views + //v.Description + + b.AppendLine($"Title: {v.Title}"); + b.AppendLine($"AuthorName: {v.AuthorTitle}"); + b.AppendLine($"AuthorID: {v.AuthorChannelId}"); + b.AppendLine($"Duration: {TimeSpan.FromSeconds(v.Duration).ToString()}"); + b.AppendLine($"Id: {v.Id}"); + b.AppendLine($"UploadDate: {v.UploadDate}"); + b.AppendLine($"Views: {v.Views}, Likes: {v.Likes}, Dislikes: {v.Dislikes}"); + b.AppendLine("Keywords:"); + foreach(var kw in v.Keywords) + { + b.AppendLine($"\t{kw}"); + } + + if (description) + { + b.AppendLine("Description:"); + b.AppendLine(v.Description); + } + } + private static int RunInfo(Info opts) + { if (r != null) + { + switch (opts.Page) + { + case "progress": + if (opts.ForMachine) + { + string jsonData = Newtonsoft.Json.JsonConvert.SerializeObject(Server.Functions.Downloader.GetProgress()); + r.SendResponse(jsonData); + } + else + { + var s=Server.Functions.Downloader.GetProgress(); + StringBuilder sb = new StringBuilder(); + sb.AppendLine("=======Progress======="); + sb.AppendLine($"Progress: {s.Progress}%"); + sb.AppendLine($"Size: {Math.Round((double)s.Length / (double)(1000 * 1000), 2)} MB"); + sb.AppendLine(); + sb.AppendLine("=======Video Info======="); + WriteVideoInfo(sb, s.Saved, false); + r.SendResponse(sb.ToString()); + + } + break; + case "queue": + if (opts.ForMachine) + { + string jsonData = Server.Functions.Downloader.GetQueue(); + r.SendResponse(jsonData); + } + else + { + var s = Server.Functions.Downloader.GetQueueItems(); + StringBuilder sb = new StringBuilder(); + foreach(var item in s) + { + WriteVideoInfo(sb, item.Video, false); + sb.AppendLine(); + } + r.SendResponse(sb.ToString()); + + + + } + break; + case "location": + r.SendResponse(Server.Functions.Downloader.DL.StorageLocation); + break; + } + } + return 0; + } + + private static int RunMoveQueue(MoveQueue opts) + { + Server.Functions.Downloader.ModQueue(opts.MoveTo, opts.MoveFrom); + return 0; + } + + private static int RunDownloadUser(DownloadUser opts) + { + Server.Functions.Downloader.DownloadUser(opts.Id, opts.Format); + return 0; + } + + private static int RunExitApp(ExitApp opts) + { + Environment.Exit(0); + return 0; + } + + private static int RunDownloadVideo(DownloadVideo opts) + { + Server.Functions.Downloader.DownloadVideo(opts.Id, opts.Format); + return 0; + } + + private static int RunDownloadPlaylist(DownloadPlaylist opts) + { + Server.Functions.Downloader.DownloadPlaylist(opts.Id, opts.Format); + return 0; + } + + private static int RunDownloadChannel(DownloadChannel opts) + { + Server.Functions.Downloader.DownloadChannel(opts.Id, opts.Format); + return 0; + } + } +} diff --git a/RESPONSE.cs b/RESPONSE.cs new file mode 100644 index 0000000..171eba9 --- /dev/null +++ b/RESPONSE.cs @@ -0,0 +1,27 @@ +using System; + +namespace youtube_downloader +{ + internal class RESPONSE + { + public RESPONSE(System.IO.Stream strm) + { + this.strm = strm; + } + System.IO.Stream strm; + public void SendResponse(string response) + { + if(strm != null) + { + strm.Write(BitConverter.GetBytes(response.Length), 0, 4); + strm.Write(System.Text.Encoding.UTF8.GetBytes(response), 0, response.Length); + strm.Dispose(); + strm = null; + } + } + public void SendResponse() + { + SendResponse(""); + } + } +} \ No newline at end of file diff --git a/Server/Functions/Downloader.cs b/Server/Functions/Downloader.cs new file mode 100644 index 0000000..7f28c45 --- /dev/null +++ b/Server/Functions/Downloader.cs @@ -0,0 +1,412 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YoutubeExplode.Videos; +using YoutubeExplode; +using System.Net.Http; +using System.Net; +using System.IO; +using TessesYoutubeDownloader.Server.Models; +using Newtonsoft.Json; + +using YoutubeExplode.Videos.Streams; +using YoutubeExplode.Channels; +using YoutubeExplode.Playlists; + +namespace youtube_downloader.Server.Functions +{ + public class Downloader + { + public static YoutubeClient CreateYoutubeClient() + { + + ServicePointManager +.ServerCertificateValidationCallback += +(sender, cert, chain, sslPolicyErrors) => true; + HttpClientHandler handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; + Http = new HttpClient(handler); + + return new YoutubeExplode.YoutubeClient(Http); + } + + internal static VideoDownloadProgress GetProgress() + { + return P; + } + + static HttpClient Http; + YoutubeClient ytc = CreateYoutubeClient(); + static VideoDownloadProgress P=new VideoDownloadProgress(); + Progress DownloadP =new Progress( (e)=> { P.Progress = (int)(e * 100.0); }); + + List Queue = new List(); + + internal static string GetQueue() + { + string q; + lock (DL.Queue) + { + q = JsonConvert.SerializeObject(DL.Queue.Select(o => o.Video)) ; + } + return q; + } + + internal static void ModQueue(string mvto, string index) + { + try + { + //?mv=up|down|top|bottom|remove,int&i=0,last + lock (DL.Queue) + { + int index2 = 0; + if (index == "last") + { + index2 = DL.Queue.Count - 1; + } + else + { + if (!int.TryParse(index, out index2)) + { + index2 = 0; + } + } + if (index2 >= DL.Queue.Count) + { + index2 = DL.Queue.Count - 1; + } + if (mvto == "top") + { + var v = DL.Queue[index2]; + DL.Queue.Remove(v); + DL.Queue.Insert(0,v); + } + else if (mvto == "bottom") + { + var v = DL.Queue[index2]; + DL.Queue.Remove(v); + DL.Queue.Add(v); + } + else if (mvto == "remove") + { + var v = DL.Queue[index2]; + DL.Queue.Remove(v); + + } + else if (mvto == "up") + { + if (index2 > 0) + { + var v = DL.Queue[index2]; + DL.Queue.Remove(v); + + DL.Queue.Insert(index2 - 1, v); + } + + } + else if (mvto == "down") + { + if (index2 < DL.Queue.Count - 1) + { + var v = DL.Queue[index2]; + DL.Queue.Remove(v); + + DL.Queue.Insert(index2 + 1, v); + } + } + else + { + int n1; + + if (int.TryParse(mvto, out n1)) + { + var v = DL.Queue[index2]; + DL.Queue.Remove(v); + if (n1 > index2) + { + DL.Queue.Insert(n1 - 1, v); + } + else + { + DL.Queue.Insert(n1, v); + } + } + } + + } + }catch(Exception ex) + { + _ = ex; + } + } + public bool Continue(string v) + { + if (File.Exists(v)) + { + using(var f = File.OpenRead(v)) + { + return f.Length == 0; + } + } + return true; + } + private string gStorageLocation() + { + if (System.IO.File.Exists("loc.txt")) + { + string loc=System.IO.File.ReadAllText("loc.txt"); + try + { + System.IO.Directory.CreateDirectory(loc); + if (System.IO.Directory.Exists(loc)) + { + return loc; + } + }catch(Exception ex) + { + + } + } + return Environment.CurrentDirectory; + } + public string StorageLocation { get { return gStorageLocation(); } } + private async Task _DownloadVideo(VideoId videoId,Resolution res) + { string infpath= GetPath(true, "Info", videoId + ".json"); + SavedVideoObject sv; + bool exist = File.Exists(videoId); + if (exist) + { + sv = new SavedVideoObject(); + sv.Resolution = res; + sv.Video = Newtonsoft.Json.JsonConvert.DeserializeObject(File.ReadAllText(infpath)); + } + else + { + var vinfo = await ytc.Videos.GetAsync(videoId); + + + sv = SavedVideo.CreateFrom(res, vinfo, _DownloadThumbnail); + + } + + if (!exist) + { + File.WriteAllText(infpath, JsonConvert.SerializeObject(sv.Video)); + } + lock (Queue) + { + Queue.Insert(0,sv); //new elements get added to begining + } + } + public async Task DownloadThread() + { + do + { + bool canDownload = false; + SavedVideoObject v; + lock (Queue) + { + canDownload= Queue.Count > 0; + if (canDownload) + { + v = Queue[0]; + Queue.RemoveAt(0); + P.Saved = v.Video; + Console.WriteLine($"Download: {v.Video.Title}"); + } + else + { + v = null; + } + } + + if (canDownload) + { + switch (v.Resolution) + { + case Resolution.Convert: + + string mypath = GetPath(true, "Converted", v.Video.Id + "-vidonly.bkp"); + string mypathaudio = GetPath(true, "Audio", v.Video.Id + "incomplete.mp4"); + string mypathCompleteAudio = GetPath(true, "Audio", v.Video.Id + ".mp4"); + string mypathComplete = GetPath(true, "Converted", v.Video.Id + ".mp4"); + string mypathIncompleteConverting = GetPath(true, "Converted", "conv.mkv"); + + if (Continue(mypathComplete)) + { + var s3 = await ytc.Videos.Streams.GetManifestAsync(v.Video.Id); + var best2 = s3.GetAudioOnlyStreams().GetWithHighestBitrate(); + + + + var best = s3.GetVideoOnlyStreams().GetWithHighestVideoQuality(); + P.Length = best.Size.Bytes + best2.Size.Bytes; + ProgressTwo p = new ProgressTwo(best.Size.Bytes, best2.Size.Bytes, DownloadP); + await ytc.Videos.Streams.DownloadAsync(best,mypath, p.Video); + IProgress pv = p.Video; + pv.Report(1); + if (Continue(mypathCompleteAudio)) + { + await ytc.Videos.Streams.DownloadAsync(best2, mypathaudio,p.Audio); + File.Move(mypathaudio, mypathCompleteAudio); + } + IProgress pa = p.Video; + pa.Report(1); + ffmpeg.mux(mypath,mypathCompleteAudio,mypathIncompleteConverting); + + + File.Move(mypathIncompleteConverting, mypathComplete); + } + break; + case Resolution.NoConvert: + string mypath2 = GetPath(true, "NotConverted", v.Video.Id + "incomplete.mp4"); + string mypath2Complete = GetPath(true, "NotConverted", v.Video.Id + ".mp4"); + + if (Continue(mypath2Complete)) + { + var s = await ytc.Videos.Streams.GetManifestAsync(v.Video.Id); + var best = s.GetMuxedStreams().GetWithHighestVideoQuality(); + P.Length = best.Size.Bytes; + + await ytc.Videos.Streams.DownloadAsync(best, mypath2, DownloadP); + File.Move(mypath2, mypath2Complete); + } + + + break; + case Resolution.Audio: + string mypath3 = GetPath(true, "Audio", v.Video.Id + "incomplete.mp4"); + string mypath3Complete = GetPath(true, "Audio", v.Video.Id + ".mp4"); + if (Continue(mypath3Complete)) + { + var s2 = await ytc.Videos.Streams.GetManifestAsync(v.Video.Id); + var best2 = s2.GetAudioOnlyStreams().GetWithHighestBitrate(); + P.Length = best2.Size.Bytes; + await ytc.Videos.Streams.DownloadAsync(best2, mypath3, DownloadP); + File.Move(mypath3, mypath3Complete); + } + break; + } + } + System.Threading.Thread.Sleep(1); + } + while (true); + } + private void _DownloadThumbnail(int w,int h,string id,string tnail) + { + try + { + string p = GetPath(true,"Thumbnails", w.ToString() + 'x' + h.ToString(), id + ".jpg"); + if (!File.Exists(p)) + { + ffmpeg.download_thumbnail(tnail, p); + } + } + catch(Exception ex) + { + _ = ex; + } + } + + internal static List GetQueueItems() + { + return DL.Queue; + } + + private async Task _DownloadChannel(ChannelId id,Resolution res) + { + var c=await ytc.Channels.GetAsync(id); + SavedChannel c2 = SavedChannel.FromChannel(c, _DownloadThumbnail); + string infpath = GetPath(true, "Channel", id + ".json"); + File.WriteAllText(infpath, JsonConvert.SerializeObject(c2)); + var cvids = ytc.Channels.GetUploadsAsync(id).GetAsyncEnumerator(); + while (await cvids.MoveNextAsync()) + { + await _DownloadVideo(cvids.Current.Id, res); + } + + } + private async Task _DownloadPlaylist(PlaylistId id,Resolution res) + { + SavedPlaylist pl =await SavedPlaylist.FromPlaylistId(res, ytc, id, DownloadVideo, _DownloadThumbnail); + string infpath = GetPath(true, "Playlist", id + ".json"); + File.WriteAllText(infpath, JsonConvert.SerializeObject(pl)); + } + private async Task _DownloadUser(UserName name, Resolution res) + { + var c = await ytc.Channels.GetByUserAsync(name); + SavedChannel c2 = SavedChannel.FromChannel(c, _DownloadThumbnail); + string infpath = GetPath(true, "Channels", c.Id + ".json"); + File.WriteAllText(infpath, JsonConvert.SerializeObject(c2)); + var cvids = ytc.Channels.GetUploadsAsync(c.Id).GetAsyncEnumerator(); + while (await cvids.MoveNextAsync()) + { + await _DownloadVideo(cvids.Current.Id, res); + } + + } + public static void DownloadVideo(VideoId v,Resolution res) + { + DL._DownloadVideo(v, res).GetAwaiter().GetResult(); + } + public static void DownloadVideo(VideoId v) + { + DownloadVideo(v, Resolution.NoConvert); + } + public static void DownloadPlaylist(PlaylistId id,Resolution res) + { + DL._DownloadPlaylist(id, res).GetAwaiter().GetResult(); + } + public static void DownloadPlaylist(PlaylistId id) + { + DownloadPlaylist(id, Resolution.NoConvert); + } + public static void DownloadChannel(ChannelId id,Resolution res) + { + DL._DownloadChannel(id, res).GetAwaiter().GetResult(); + } + public static void DownloadChannel(ChannelId id) + { + DownloadChannel(id, Resolution.NoConvert); + } + public static void DownloadUser(UserName name, Resolution res) + { + DL._DownloadUser(name, res).GetAwaiter().GetResult(); + } + public static void DownloadUser(UserName name) + { + DownloadUser(name, Resolution.NoConvert); + } + public string GetPath(bool createParent,params string[] _path) + { + + if (createParent) + { + string dir = GetPath(_path); + try + { + Directory.CreateDirectory(Path.GetDirectoryName(dir)); + }catch(Exception ex) + { + _ = ex; + } + return dir; + } + else + { + return GetPath(_path); + } + } + private string GetPath(params string[] _path) + { + string[] array2 = new string[_path.Length + 1]; + array2[0] = StorageLocation; + Array.Copy(_path, 0, array2, 1,_path.Length); + return System.IO.Path.Combine(array2); + } + public static Downloader DL = new Downloader(); + + } +} diff --git a/Server/Functions/ffmpeg.cs b/Server/Functions/ffmpeg.cs new file mode 100644 index 0000000..3b03dc4 --- /dev/null +++ b/Server/Functions/ffmpeg.cs @@ -0,0 +1,91 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace youtube_downloader.Server.Functions +{ + internal class ffmpeg + { + internal static string FFMPEG = ffmpeg.get_ffmpeg(); + + private static string get_ffmpeg() + { + if (File.Exists("ffmpeg.txt")) + { + return File.ReadAllText("ffmpeg.txt"); + } + return "ffmpeg"; + } + + internal static void mux(string mypath, string mypathCompleteAudio, string mypathIncompleteConverting) + { + using (var p = new Process()) + { + p.StartInfo.FileName = FFMPEG; + p.StartInfo.Arguments = $"-i \"{mypath}\' -i \"{mypathCompleteAudio}\" -c copy -map 0:v -map 1:a \"{mypathIncompleteConverting}\'"; + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + if (p.Start()) + { + p.WaitForExit(); + } + } + } + + internal static void download_thumbnail(string tnail, string p2) + { + using (var p = new Process()) + { + p.StartInfo.FileName = FFMPEG; + p.StartInfo.Arguments = $"-i \"{tnail}\' \"{p2}\'"; + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + if (p.Start()) + { + p.WaitForExit(); + } + } + } + } + + public class ProgressTwo + { + double vanda; + double v; + double a; + + double vval=0; + double aval=0; + IProgress report; + public ProgressTwo(double vsize,double asize,IProgress p) + { + vanda = vsize + asize; + v = vsize / vanda; //v = 0.1 if vsize == 20 and vanda=200 + a = asize / vanda; + Video = new Progress(ProgressVideo); + Audio = new Progress(ProgressAudio); + report = p; + } + + private void ProgressAudio(double d) + { + aval = d * a; + Process(); + } + + private void ProgressVideo(double d) + { + vval = d * v; + Process(); + } + private void Process() + { + report.Report(aval + vval); + } + public Progress Video { get; set; } + public Progress Audio { get; set; } + } + +} \ No newline at end of file diff --git a/Server/Models/SavedChannel.cs b/Server/Models/SavedChannel.cs new file mode 100644 index 0000000..1ea2caa --- /dev/null +++ b/Server/Models/SavedChannel.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TessesYoutubeDownloader.Server.Models +{ + class SavedChannel + { + public static SavedChannel FromChannel(YoutubeExplode.Channels.Channel c, Action downloadThumbnail) + { + SavedChannel sc = new SavedChannel(); + sc.Id= c.Id; + sc.Title=c.Title; + sc.Url = c.Url; + foreach(var thumb in c.Thumbnails) + { + downloadThumbnail(thumb.Resolution.Width, thumb.Resolution.Height,c.Id, thumb.Url); + } + return sc; + } + public string Id { get; set; } + public string Title { get; set; } + + public string Url { get; set; } + } +} diff --git a/Server/Models/SavedPlaylist.cs b/Server/Models/SavedPlaylist.cs new file mode 100644 index 0000000..a999d8e --- /dev/null +++ b/Server/Models/SavedPlaylist.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TessesYoutubeDownloader.Server.Models +{ + class SavedPlaylist + { + public static async Task FromPlaylistId(Resolution res,YoutubeExplode.YoutubeClient ytc,YoutubeExplode.Playlists.PlaylistId id,Action addToQueue, Action downloadThumbnail) + { + SavedPlaylist pl2 = new SavedPlaylist(); + pl2.Videos = new List(); + + var pl=await ytc.Playlists.GetAsync(id); + var a = pl.Author; + pl2.AuthorChannelId = a.ChannelId; + pl2.AuthorTitle = a.Title; + pl2.Id= pl.Id; + pl2.Description = pl.Description; + pl2.Title = pl.Title; + + foreach (var thumb in pl.Thumbnails) + { + downloadThumbnail(thumb.Resolution.Width, thumb.Resolution.Height,id, thumb.Url); + } + var plv = ytc.Playlists.GetVideosAsync(id).GetAsyncEnumerator(); + while(await plv.MoveNextAsync()) + { + addToQueue(plv.Current.Id,res); + pl2.Videos.Add(plv.Current.Id); + } + return pl2; + } + public List Videos { get; set; } + public string AuthorTitle { get; set; } + public string AuthorChannelId { get; set; } + public string Id { get; set; } + public string Description { get; set; } + public string Title { get; set; } + } +} diff --git a/Server/Models/SavedVideo.cs b/Server/Models/SavedVideo.cs new file mode 100644 index 0000000..c56fdc4 --- /dev/null +++ b/Server/Models/SavedVideo.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TessesYoutubeDownloader.Server.Models +{ + + public enum Resolution + { + Convert=0, + NoConvert=1, + Audio=2 + } + public class SavedVideoObject + { + public SavedVideo Video { get; set; } + public Resolution Resolution { get; set; } + } + public class SavedVideo + { + + public static SavedVideoObject CreateFrom(Resolution res,YoutubeExplode.Videos.Video v,Action downloadThumbnail) + { + SavedVideo sv = new SavedVideo(); + sv.Title = v.Title; + sv.UploadDate = v.UploadDate.ToString(); + sv.Id = v.Id; + sv.AuthorChannelId = v.Author.ChannelId; + sv.AuthorTitle = v.Author.Title; + sv.Description = v.Description; + sv.Dislikes = v.Engagement.DislikeCount; + sv.Duration = v.Duration.GetValueOrDefault().TotalSeconds; + sv.Keywords = v.Keywords.ToArray(); + sv.Likes = v.Engagement.LikeCount; + sv.Views = v.Engagement.ViewCount; + foreach(var thumb in v.Thumbnails) + { + + downloadThumbnail(thumb.Resolution.Width, thumb.Resolution.Height,v.Id, thumb.Url); + } + SavedVideoObject obj = new SavedVideoObject(); + obj.Video = sv; + obj.Resolution = res; + return obj; + + } + public string Title { get; set; } + public string UploadDate { get; set; } + public string[] Keywords { get; set; } + public string Id { get; set; } + public string AuthorTitle { get; set; } + public string AuthorChannelId { get; set; } + + public string Description { get; set; } + + public double Duration { get; set; } + + public long Views { get; set; } + public long Likes { get; set; } + public long Dislikes { get; set; } + } +} diff --git a/Server/Models/VideoDownloadProgress.cs b/Server/Models/VideoDownloadProgress.cs new file mode 100644 index 0000000..0b2b883 --- /dev/null +++ b/Server/Models/VideoDownloadProgress.cs @@ -0,0 +1,13 @@ +using System; + +namespace TessesYoutubeDownloader.Server.Models +{ + internal class VideoDownloadProgress + { + public Models.SavedVideo Saved { get; set; } + public long Length { get; set; } + + public int Progress { get; set; } + public double ProgressRaw { get; set; } + } +} \ No newline at end of file diff --git a/youtube-downloader.csproj b/youtube-downloader.csproj new file mode 100644 index 0000000..3cf0ae0 --- /dev/null +++ b/youtube-downloader.csproj @@ -0,0 +1,15 @@ + + + + Exe + net5.0 + youtube_downloader + + + + + + + + + diff --git a/youtube-downloader.sln b/youtube-downloader.sln new file mode 100644 index 0000000..0ed42f5 --- /dev/null +++ b/youtube-downloader.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31410.357 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "youtube-downloader", "youtube-downloader.csproj", "{CAC0629B-5729-49F2-9316-34CF990BB725}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CAC0629B-5729-49F2-9316-34CF990BB725}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAC0629B-5729-49F2-9316-34CF990BB725}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAC0629B-5729-49F2-9316-34CF990BB725}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAC0629B-5729-49F2-9316-34CF990BB725}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {51396FBD-23AB-477B-ADE1-3922A998DA18} + EndGlobalSection +EndGlobal