595 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			595 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C#
		
	
	
	
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 YoutubeExplode.Videos.Streams;
 | 
						|
using System.IO;
 | 
						|
using System.Diagnostics;
 | 
						|
using System.Text;
 | 
						|
using System.Globalization;
 | 
						|
using Newtonsoft.Json;
 | 
						|
 | 
						|
namespace Tesses.YouTubeDownloader
 | 
						|
{
 | 
						|
    public abstract partial class TYTDStorage
 | 
						|
    {
 | 
						|
        
 | 
						|
        private async Task DownloadLoop(CancellationToken token = default(CancellationToken))
 | 
						|
        {
 | 
						|
            while (!token.IsCancellationRequested)
 | 
						|
            {
 | 
						|
                bool hasAny;
 | 
						|
                var (Video, Resolution) = Dequeue(out hasAny);
 | 
						|
                if (hasAny)
 | 
						|
                {
 | 
						|
                    await DownloadVideoAsync(Video, Resolution, token);
 | 
						|
                }
 | 
						|
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        public readonly SavedVideoProgress Progress = new SavedVideoProgress();
 | 
						|
        private void ReportProgress(double progress)
 | 
						|
        {
 | 
						|
            Progress.Progress = (int)(progress * 100);
 | 
						|
            Progress.ProgressRaw = progress;
 | 
						|
 | 
						|
            if (ExtensionContext != null)
 | 
						|
            {
 | 
						|
                foreach (var ext in ExtensionContext.Extensions)
 | 
						|
                {
 | 
						|
                    ext.VideoProgress(Progress.Video, progress);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        private void ReportStartVideo(SavedVideo video, Resolution resolution, long length)
 | 
						|
        {
 | 
						|
            Progress.Video = video;
 | 
						|
            Progress.Progress = 0;
 | 
						|
            Progress.ProgressRaw = 0;
 | 
						|
            Progress.Length = length;
 | 
						|
 | 
						|
            if (ExtensionContext != null)
 | 
						|
            {
 | 
						|
                foreach (var item in ExtensionContext.Extensions)
 | 
						|
                {
 | 
						|
                    item.VideoStarted(video, resolution, length);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        private void ReportEndVideo(SavedVideo video, Resolution resolution)
 | 
						|
        {
 | 
						|
            Progress.Progress = 100;
 | 
						|
            Progress.ProgressRaw = 1;
 | 
						|
 | 
						|
 | 
						|
            if (ExtensionContext != null)
 | 
						|
            {
 | 
						|
                foreach (var item in ExtensionContext.Extensions)
 | 
						|
                {
 | 
						|
                    item.VideoFinished(video, resolution);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        private async Task DownloadVideoAsync(SavedVideo video, Resolution resolution, CancellationToken token)
 | 
						|
        {
 | 
						|
            switch (resolution)
 | 
						|
            {
 | 
						|
                case Resolution.Mux:
 | 
						|
                    await DownloadVideoMuxedAsync(video,token);
 | 
						|
                    break;
 | 
						|
                case Resolution.PreMuxed:
 | 
						|
                    await DownloadPreMuxedVideoAsync(video, token);
 | 
						|
                    break;
 | 
						|
                case Resolution.AudioOnly:
 | 
						|
                    await DownloadAudioOnlyAsync(video,token);
 | 
						|
                    break;
 | 
						|
                case Resolution.VideoOnly:
 | 
						|
                    await DownloadVideoOnlyAsync(video,token);
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
 | 
						|
        }
 | 
						|
        public async Task<bool> Continue(string path)
 | 
						|
        {
 | 
						|
   
 | 
						|
            if (await FileExistsAsync(path))
 | 
						|
            {
 | 
						|
                return (await GetLengthAsync(path) == 0);
 | 
						|
            }
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
         private static async Task<bool> run_process(string filename,CancellationToken token,params string[] args)
 | 
						|
        {
 | 
						|
            return await run_process(filename,null,token,args);
 | 
						|
        }
 | 
						|
        private static async Task<bool> run_process(string filename,IProgress<string> new_line,CancellationToken token, params string[] args)
 | 
						|
        {
 | 
						|
                using(Process process=new Process()){
 | 
						|
                process.StartInfo.FileName = filename;
 | 
						|
                StringBuilder builder=new StringBuilder();
 | 
						|
                foreach(var arg in args)
 | 
						|
                {
 | 
						|
                    builder.Append($"\"{arg}\" ");
 | 
						|
                }
 | 
						|
                process.StartInfo.Arguments=builder.ToString();
 | 
						|
                if(new_line !=null)
 | 
						|
                {
 | 
						|
                    process.StartInfo.UseShellExecute=false;
 | 
						|
                    process.StartInfo.RedirectStandardError=true;
 | 
						|
                    process.StartInfo.RedirectStandardOutput=true;
 | 
						|
                
 | 
						|
                }
 | 
						|
                try{
 | 
						|
                if(process.Start())
 | 
						|
                {
 | 
						|
                    if(new_line != null)
 | 
						|
                    {
 | 
						|
                        while(!process.HasExited)
 | 
						|
                        {
 | 
						|
                            if(token.IsCancellationRequested)
 | 
						|
                            {
 | 
						|
                                process.Kill();
 | 
						|
                            }
 | 
						|
                            new_line.Report(await process.StandardError.ReadLineAsync());
 | 
						|
                        }
 | 
						|
                    }else{
 | 
						|
                        while(!process.HasExited)
 | 
						|
                        {
 | 
						|
                            if(token.IsCancellationRequested)
 | 
						|
                            {
 | 
						|
                                process.Kill();
 | 
						|
                            }
 | 
						|
                            await Task.Delay(100);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                }catch(Exception ex)
 | 
						|
                {
 | 
						|
                    Console.WriteLine(ex.Message);
 | 
						|
                    _=ex;
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                return true;
 | 
						|
                }
 | 
						|
        }
 | 
						|
        
 | 
						|
        protected async Task<bool> Convert(SavedVideo video,string videoFile,string audioFile,string outFile,CancellationToken token,IProgress<string> new_line,string ffmpeg="ffmpeg")
 | 
						|
        {
 | 
						|
            
 | 
						|
            Func<string,string> escape_ffmetadata_str = (e)=>{
 | 
						|
                StringBuilder builder=new StringBuilder(e);
 | 
						|
                builder.Replace("\r\n","\n");
 | 
						|
                foreach(var item in new char[] {'\\','#',';','=','\n'})
 | 
						|
                {
 | 
						|
                    builder.Replace(item.ToString(), $"\\{item}");
 | 
						|
                }
 | 
						|
                return builder.ToString();
 | 
						|
            };
 | 
						|
            Action<List<string>,Chapter> add_chapter = (list,chapter)=>{
 | 
						|
                list.Add("[CHAPTER]");
 | 
						|
                list.Add("TIMEBASE=1/1");
 | 
						|
                list.Add($"START={(int)chapter.Offset.TotalSeconds}");
 | 
						|
                list.Add($"END={(int)(chapter.Offset.Add(chapter.Length).TotalSeconds)}");
 | 
						|
                list.Add($"TITLE={escape_ffmetadata_str(chapter.ChapterName)}");
 | 
						|
                list.Add("");
 | 
						|
            };
 | 
						|
            string txtFile=Path.Combine("TYTD_TEMP","video_info.txt");
 | 
						|
           
 | 
						|
            DeleteIfExists (txtFile);
 | 
						|
            if(! await run_process(ffmpeg,token,"-y","-i",videoFile,"-f","ffmetadata",txtFile))
 | 
						|
            {
 | 
						|
                        return false;
 | 
						|
            }
 | 
						|
                List<string> entries=File.ReadAllLines(txtFile).ToList();
 | 
						|
                if(token.IsCancellationRequested)
 | 
						|
                    {
 | 
						|
                        return false;
 | 
						|
                    }
 | 
						|
                entries.Add($"title={escape_ffmetadata_str(video.Title)}");
 | 
						|
                    entries.Add($"artist={escape_ffmetadata_str(video.AuthorTitle)}");
 | 
						|
                    entries.Add($"description={escape_ffmetadata_str(video.Description)}");
 | 
						|
                    entries.Add($"comments=videoId\\={video.Id},likes\\={video.Likes},views\\={video.Views},authorChannelId\\={video.AuthorChannelId}");
 | 
						|
                    entries.Add("");
 | 
						|
                    foreach(var chapter in video.GetChapters())
 | 
						|
                    {
 | 
						|
                    //    Console.WriteLine(chapter.ChapterName);
 | 
						|
                        add_chapter(entries,chapter);
 | 
						|
                    }
 | 
						|
                    File.WriteAllLines(txtFile,entries);
 | 
						|
                 return await run_process(ffmpeg,new_line,token,"-y","-i",videoFile,"-i",txtFile,"-i",audioFile,"-map_metadata","1","-c","copy","-map","0:v","-map","2:a",outFile);
 | 
						|
                 
 | 
						|
 | 
						|
                  
 | 
						|
 | 
						|
 | 
						|
        }
 | 
						|
        private async Task<bool> CopyStreamAsync(Stream src,Stream dest,long pos=0,long len=0,int bufferSize=4096,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
 | 
						|
        {
 | 
						|
            if(pos > 0)
 | 
						|
            {
 | 
						|
                src.Position=pos;
 | 
						|
                dest.Position=pos;
 | 
						|
            }
 | 
						|
            double curPos = pos;
 | 
						|
            int read;
 | 
						|
            byte[] buffer=new byte[bufferSize];
 | 
						|
            do{
 | 
						|
                read=await src.ReadAsync(buffer,0,buffer.Length,token);
 | 
						|
                if(token.IsCancellationRequested)
 | 
						|
                {
 | 
						|
                    return false;
 | 
						|
                }
 | 
						|
                curPos+=read;
 | 
						|
                await dest.WriteAsync(buffer,0,read);
 | 
						|
                if(progress != null)
 | 
						|
                {
 | 
						|
                    progress.Report(curPos / len);
 | 
						|
                }
 | 
						|
            }while(read>0 && !token.IsCancellationRequested);
 | 
						|
            if(token.IsCancellationRequested)
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        public abstract Task<Stream> OpenOrCreateAsync(string path);
 | 
						|
        public abstract void RenameFile(string src,string dest);
 | 
						|
        public static string FFmpeg {get;set;}
 | 
						|
 | 
						|
        public virtual async Task<bool> MuxVideosAsync(SavedVideo video,string videoSrc,string audioSrc,string videoDest,IProgress<double> progress=null,CancellationToken token=default(CancellationToken))
 | 
						|
        {
 | 
						|
            if(string.IsNullOrWhiteSpace(FFmpeg))
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
             Func<string,TimeSpan> get_percent=(e)=>{
 | 
						|
                if(string.IsNullOrWhiteSpace(e))
 | 
						|
                    return TimeSpan.Zero;
 | 
						|
 | 
						|
                int index=e.IndexOf("time=");
 | 
						|
                if(index < 0)
 | 
						|
                    return TimeSpan.Zero;
 | 
						|
                
 | 
						|
                int spaceIndex=e.IndexOf(' ',index+5);
 | 
						|
                string j=e.Substring(index+5,spaceIndex-(index+5));
 | 
						|
                
 | 
						|
                double val;
 | 
						|
                if(double.TryParse(j,out val))
 | 
						|
                    return TimeSpan.FromSeconds(val);
 | 
						|
 | 
						|
                return TimeSpan.ParseExact(j, "c", CultureInfo.InvariantCulture);
 | 
						|
                
 | 
						|
                //return TimeSpan.Zero;
 | 
						|
            };
 | 
						|
           
 | 
						|
            bool ret=false;
 | 
						|
             string video_bin=Path.Combine("TYTD_TEMP","video.bin");
 | 
						|
             string audio_bin=Path.Combine("TYTD_TEMP","audio.bin");
 | 
						|
              string output_mkv=Path.Combine("TYTD_TEMP","output.mkv");
 | 
						|
           
 | 
						|
                Directory.CreateDirectory("TYTD_TEMP");
 | 
						|
                //hince we are in a unknown environment we need to copy video
 | 
						|
                DeleteIfExists(video_bin);
 | 
						|
                DeleteIfExists(audio_bin);
 | 
						|
                DeleteIfExists(output_mkv);
 | 
						|
                long len=await GetLengthAsync(videoSrc);
 | 
						|
 | 
						|
                using(var vstrm_src=await OpenReadAsync(videoSrc))
 | 
						|
                {
 | 
						|
                    using(var vstrm_dest = File.Create(video_bin))
 | 
						|
                    {
 | 
						|
                        Console.WriteLine("Opening vstream");
 | 
						|
                        if(!await CopyStreamAsync(vstrm_src,vstrm_dest,0,len,4096,
 | 
						|
                            new Progress<double>((e)=>{
 | 
						|
                                 if(progress !=null)
 | 
						|
                                {
 | 
						|
                                    progress.Report(e/4);
 | 
						|
                                }
 | 
						|
                            })
 | 
						|
                            ,token
 | 
						|
                        ))
 | 
						|
                        {
 | 
						|
                            
 | 
						|
                            goto end;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                 using(var astrm_src=await OpenReadAsync(audioSrc))
 | 
						|
                {
 | 
						|
                    using(var astrm_dest = File.Create(audio_bin))
 | 
						|
                    {
 | 
						|
                        Console.WriteLine("opening astream");
 | 
						|
                        if(!await CopyStreamAsync(astrm_src,astrm_dest,0,len,4096,
 | 
						|
                            new Progress<double>((e)=>{
 | 
						|
                                if(progress !=null)
 | 
						|
                                {
 | 
						|
                                progress.Report(e/4+0.25);
 | 
						|
                                }
 | 
						|
                            })
 | 
						|
                            ,token
 | 
						|
                        ))
 | 
						|
                        {
 | 
						|
                            goto end;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                 string videoFile=Path.Combine("TYTD_TEMP","video.bin");
 | 
						|
            string audioFile=Path.Combine("TYTD_TEMP","audio.bin");
 | 
						|
            string outFile = Path.Combine("TYTD_TEMP","output.mkv");
 | 
						|
                ret=await Convert(video,videoFile,audioFile,outFile,token,new Progress<string>((e)=>{
 | 
						|
                    // time=\"\"
 | 
						|
                    if(progress!=null)
 | 
						|
                    {
 | 
						|
                        var progr=get_percent(e).TotalSeconds / video.Duration.TotalSeconds;
 | 
						|
                        progress.Report(progr / 4 + 0.50);
 | 
						|
                    }
 | 
						|
                }),FFmpeg);
 | 
						|
                if(ret)
 | 
						|
                {
 | 
						|
                    using(var mstrm_src=File.OpenRead(output_mkv))
 | 
						|
                    {
 | 
						|
                        using(var mstrm_dest=await CreateAsync(videoDest))
 | 
						|
                        {
 | 
						|
                            ret=await CopyStreamAsync(mstrm_src,mstrm_dest,0,mstrm_src.Length,4096,new Progress<double>((e)=>{
 | 
						|
                                if(progress!=null)
 | 
						|
                                {
 | 
						|
                                    progress.Report(e / 4 + 0.75);
 | 
						|
                                }
 | 
						|
                            }),token);
 | 
						|
                            
 | 
						|
                        }
 | 
						|
                    }             
 | 
						|
                }
 | 
						|
                end:
 | 
						|
                Directory.Delete("TYTD_TEMP",true);
 | 
						|
                return ret;
 | 
						|
        }
 | 
						|
        private async Task DownloadVideoMuxedAsync(SavedVideo video,CancellationToken token)
 | 
						|
        {
 | 
						|
            bool isValid=true;
 | 
						|
            isValid=await DownloadVideoOnlyAsync(video,token);
 | 
						|
            if(token.IsCancellationRequested || !isValid)
 | 
						|
            {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            isValid = await DownloadAudioOnlyAsync(video,token);
 | 
						|
            if(token.IsCancellationRequested || !isValid)
 | 
						|
            {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
             var streams=await BestStreamInfo.GetBestStreams(this,video.Id,token,false);
 | 
						|
             if(token.IsCancellationRequested)
 | 
						|
             {
 | 
						|
                 return;
 | 
						|
             }
 | 
						|
            ReportStartVideo(video,Resolution.Mux,0);
 | 
						|
             string complete = $"Muxed/{video.Id}.mkv";
 | 
						|
             string incomplete = $"Muxed/{video.Id}incomplete.mkv";
 | 
						|
              string complete_vidonly = $"VideoOnly/{video.Id}.{streams.VideoOnlyStreamInfo.Container}";
 | 
						|
               string complete_audonly = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}";
 | 
						|
                                                      
 | 
						|
            if(await Continue(complete))
 | 
						|
            {
 | 
						|
              
 | 
						|
                    if(await MuxVideosAsync(video,complete_vidonly,complete_audonly,incomplete,new Progress<double>(ReportProgress),token))
 | 
						|
                    {
 | 
						|
                        RenameFile(incomplete,complete);
 | 
						|
                    }
 | 
						|
            }
 | 
						|
            
 | 
						|
            ReportEndVideo(video,Resolution.Mux);
 | 
						|
        }
 | 
						|
        private void DeleteIfExists(string path)
 | 
						|
        {
 | 
						|
            if(File.Exists(path))
 | 
						|
            {
 | 
						|
                File.Delete(path);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        public async Task<bool> DownloadVideoOnlyAsync(SavedVideo video,CancellationToken token)
 | 
						|
        {
 | 
						|
            bool ret=false;
 | 
						|
            var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
 | 
						|
            if(streams != null)
 | 
						|
            {
 | 
						|
                await MoveLegacyStreams(video,streams);
 | 
						|
                string complete = $"VideoOnly/{video.Id}.{streams.VideoOnlyStreamInfo.Container}";
 | 
						|
                string incomplete = $"VideoOnly/{video.Id}incomplete.{streams.VideoOnlyStreamInfo.Container}";
 | 
						|
 | 
						|
                if(await Continue(complete))
 | 
						|
                {
 | 
						|
                    streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
 | 
						|
                    if(streams != null)
 | 
						|
                    {
 | 
						|
                        using(var strm = await YoutubeClient.Videos.Streams.GetAsync(streams.VideoOnlyStreamInfo,token))
 | 
						|
                        {
 | 
						|
                            if(token.IsCancellationRequested)
 | 
						|
                            {
 | 
						|
                                return false;
 | 
						|
                            }
 | 
						|
                            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,new Progress<double>(ReportProgress),token);
 | 
						|
                            }
 | 
						|
                            if(ret)
 | 
						|
                            {
 | 
						|
                                RenameFile(incomplete,complete);
 | 
						|
                                ReportEndVideo(video, Resolution.VideoOnly);
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }else{
 | 
						|
                        
 | 
						|
                    ret=true;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
             //We know its resolution 
 | 
						|
            return ret;
 | 
						|
        }
 | 
						|
        private async Task MoveLegacyStream(string src,string dest)
 | 
						|
        {
 | 
						|
            if(src.Equals(dest))
 | 
						|
                return;
 | 
						|
            
 | 
						|
            if(!await Continue(dest))
 | 
						|
                return;
 | 
						|
            
 | 
						|
            if(!await FileExistsAsync(src))
 | 
						|
                return;
 | 
						|
 | 
						|
            RenameFile(src,dest);
 | 
						|
 | 
						|
        }
 | 
						|
        public async Task MoveLegacyStreams(SavedVideo video,BestStreams streams)
 | 
						|
        {
 | 
						|
            if(video.LegacyVideo)
 | 
						|
            {
 | 
						|
                string legacyVideoOnlyComplete = $"VideoOnly/{video.Id}.mp4";
 | 
						|
                string legacyAudioOnlyComplete = $"AudioOnly/{video.Id}.mp4";
 | 
						|
                 string legacyPreMuxedComplete = $"PreMuxed/{video.Id}.mp4";
 | 
						|
 | 
						|
                 string modernVideoOnlyComplete = $"VideoOnly/{video.Id}.{streams.VideoOnlyStreamInfo.Container}";
 | 
						|
                 string modernAudioOnlyComplete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}";
 | 
						|
                 string modernPreMuxedComplete = $"PreMuxed/{video.Id}.{streams.MuxedStreamInfo}";
 | 
						|
 | 
						|
                string legacyVideoOnlyInComplete = $"VideoOnly/{video.Id}incomplete.mp4";
 | 
						|
                string legacyAudioOnlyInComplete = $"AudioOnly/{video.Id}incomplete.mp4";
 | 
						|
                 string legacyPreMuxedInComplete = $"PreMuxed/{video.Id}incomplete.mp4";
 | 
						|
 | 
						|
                 string modernVideoOnlyInComplete = $"VideoOnly/{video.Id}incomplete.{streams.VideoOnlyStreamInfo.Container}";
 | 
						|
                 string modernAudioOnlyInComplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}";
 | 
						|
                 string modernPreMuxedInComplete = $"PreMuxed/{video.Id}incomplete.{streams.MuxedStreamInfo}";
 | 
						|
 | 
						|
                await MoveLegacyStream(legacyVideoOnlyComplete,modernVideoOnlyComplete);
 | 
						|
                await MoveLegacyStream(legacyAudioOnlyComplete,modernAudioOnlyComplete);
 | 
						|
                await MoveLegacyStream(legacyPreMuxedComplete,modernPreMuxedComplete);
 | 
						|
 | 
						|
                 await MoveLegacyStream(legacyVideoOnlyInComplete,modernVideoOnlyInComplete);
 | 
						|
                await MoveLegacyStream(legacyAudioOnlyInComplete,modernAudioOnlyInComplete);
 | 
						|
                await MoveLegacyStream(legacyPreMuxedInComplete,modernPreMuxedInComplete);
 | 
						|
                   video.LegacyVideo=false;
 | 
						|
                            await WriteAllTextAsync($"Info/{video.Id}.json",JsonConvert.SerializeObject(video));
 | 
						|
                      
 | 
						|
                
 | 
						|
            }
 | 
						|
        }
 | 
						|
        public async Task<bool> DownloadAudioOnlyAsync(SavedVideo video,CancellationToken token)
 | 
						|
        {
 | 
						|
            bool ret=false;
 | 
						|
            var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
 | 
						|
            if(streams != null)
 | 
						|
            {
 | 
						|
                string complete = $"AudioOnly/{video.Id}.{streams.AudioOnlyStreamInfo.Container}";
 | 
						|
                string incomplete = $"AudioOnly/{video.Id}incomplete.{streams.AudioOnlyStreamInfo.Container}";
 | 
						|
                await MoveLegacyStreams(video,streams);
 | 
						|
                if(await Continue(complete))
 | 
						|
                {
 | 
						|
                  
 | 
						|
                    streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
 | 
						|
                    if(streams != null)
 | 
						|
                    {
 | 
						|
                        
 | 
						|
                        using(var strm = await YoutubeClient.Videos.Streams.GetAsync(streams.AudioOnlyStreamInfo,token))
 | 
						|
                        {
 | 
						|
                            if(token.IsCancellationRequested)
 | 
						|
                            {
 | 
						|
                                return false;
 | 
						|
                            }
 | 
						|
                            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,new Progress<double>(ReportProgress),token);
 | 
						|
                            }
 | 
						|
                            if(ret)
 | 
						|
                            {
 | 
						|
                                RenameFile(incomplete,complete);
 | 
						|
                                ReportEndVideo(video, Resolution.AudioOnly);
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }else{
 | 
						|
                      
 | 
						|
                    ret=true;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
             //We know its resolution 
 | 
						|
            return ret;
 | 
						|
        }
 | 
						|
        private async Task DownloadPreMuxedVideoAsync(SavedVideo video, CancellationToken token)
 | 
						|
        {
 | 
						|
            var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, false);
 | 
						|
            if(streams != null)
 | 
						|
            {
 | 
						|
                await MoveLegacyStreams(video,streams);
 | 
						|
                string complete = $"PreMuxed/{video.Id}.{streams.MuxedStreamInfo.Container}";
 | 
						|
                string incomplete = $"PreMuxed/{video.Id}incomplete.{streams.MuxedStreamInfo.Container}";
 | 
						|
 | 
						|
                if(await Continue(complete))
 | 
						|
                {
 | 
						|
                   
 | 
						|
                    streams = await BestStreamInfo.GetBestStreams(this,video.Id,token);
 | 
						|
                    if(streams != null)
 | 
						|
                    {
 | 
						|
                        
 | 
						|
                        using(var strm = await YoutubeClient.Videos.Streams.GetAsync(streams.MuxedStreamInfo,token))
 | 
						|
                        {
 | 
						|
                             if(token.IsCancellationRequested)
 | 
						|
                            {
 | 
						|
                                return;
 | 
						|
                            }
 | 
						|
                            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,new Progress<double>(ReportProgress),token);
 | 
						|
                            }
 | 
						|
                            //We know its resolution 
 | 
						|
                            if(ret)
 | 
						|
                            {
 | 
						|
                                RenameFile(incomplete,complete);
 | 
						|
                                ReportEndVideo(video, Resolution.PreMuxed); 
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
            }
 | 
						|
 | 
						|
            
 | 
						|
        }
 | 
						|
        private (SavedVideo video, Resolution resolution) Dequeue(out bool hasAny)
 | 
						|
        {
 | 
						|
            SavedVideo video;
 | 
						|
            Resolution resolution;
 | 
						|
            hasAny = false;
 | 
						|
            lock (QueueList)
 | 
						|
            {
 | 
						|
                if (QueueList.Count > 0)
 | 
						|
                {
 | 
						|
                    (video, resolution) = QueueList[QueueList.Count - 1];
 | 
						|
                    hasAny = true;
 | 
						|
                    QueueList.RemoveAt(QueueList.Count - 1);
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    video = null;
 | 
						|
                    resolution = Resolution.PreMuxed;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return (video, resolution);
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |