824 lines
34 KiB
C#
824 lines
34 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)
|
|
{
|
|
try{
|
|
bool hasAny;
|
|
var (Video, Resolution) = Dequeue(out hasAny);
|
|
if (hasAny)
|
|
{
|
|
await DownloadVideoAsync(Video, Resolution, token,new Progress<double>(
|
|
async (e)=>{
|
|
await ReportProgress(e);
|
|
}
|
|
),true);
|
|
}
|
|
|
|
}catch(Exception ex)
|
|
{
|
|
await GetLogger().WriteAsync(ex);
|
|
}
|
|
}
|
|
}
|
|
private CancellationTokenSource currentVideoCancelSource=null;
|
|
public readonly SavedVideoProgress Progress = new SavedVideoProgress();
|
|
private async Task ReportProgress(double progress)
|
|
{
|
|
|
|
Progress.Progress = (int)(progress * 100);
|
|
Progress.ProgressRaw = progress;
|
|
|
|
if (ExtensionContext != null)
|
|
{
|
|
foreach (var ext in ExtensionContext.Extensions)
|
|
{
|
|
await ext.VideoProgress(Progress.Video, progress);
|
|
}
|
|
}
|
|
|
|
VideoProgressEventArgs args=new VideoProgressEventArgs();
|
|
args.VideoInfo=Progress.Video;
|
|
args.Progress=progress;
|
|
|
|
|
|
VideoProgress?.Invoke(this,args);
|
|
|
|
}
|
|
private async Task ReportStartVideo(SavedVideo video, Resolution resolution, long length)
|
|
{
|
|
GetLogger().WriteAsync(video).Wait();
|
|
Progress.Video = video;
|
|
Progress.Progress = 0;
|
|
Progress.ProgressRaw = 0;
|
|
Progress.Length = length;
|
|
|
|
if (ExtensionContext != null)
|
|
{
|
|
foreach (var item in ExtensionContext.Extensions)
|
|
{
|
|
await item.VideoStarted(video, resolution, length);
|
|
}
|
|
}
|
|
VideoStartedEventArgs args=new VideoStartedEventArgs();
|
|
args.VideoInfo=video;
|
|
args.Resolution=resolution;
|
|
args.EstimatedLength=length;
|
|
|
|
VideoStarted?.Invoke(this,args);
|
|
}
|
|
private async Task ReportEndVideo(SavedVideo video, Resolution resolution)
|
|
{
|
|
Progress.Progress = 100;
|
|
Progress.ProgressRaw = 1;
|
|
|
|
|
|
if (ExtensionContext != null)
|
|
{
|
|
foreach (var item in ExtensionContext.Extensions)
|
|
{
|
|
await item.VideoFinished(video, resolution);
|
|
}
|
|
}
|
|
VideoFinishedEventArgs args=new VideoFinishedEventArgs();
|
|
args.VideoInfo=video;
|
|
args.Resolution = resolution;
|
|
VideoFinished?.Invoke(this,args);
|
|
}
|
|
|
|
public async Task DownloadNoQueue(SavedVideo info,Resolution resolution=Resolution.Mux,CancellationToken token=default(CancellationToken),IProgress<double> progress=null)
|
|
{
|
|
|
|
await DownloadVideoAsync(info,resolution,token,progress,false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async Task<SavedVideo> GetSavedVideoAsync(VideoId id)
|
|
{
|
|
VideoMediaContext context=new VideoMediaContext(id,Resolution.PreMuxed);
|
|
List<(SavedVideo Video,Resolution)> s=new List<(SavedVideo Video, Resolution)>();
|
|
await context.FillQueue(this,s);
|
|
if(s.Count> 0)
|
|
{
|
|
return s.First().Video;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
private async Task DownloadVideoAsync(SavedVideo video, Resolution resolution, CancellationToken token=default(CancellationToken),IProgress<double> progress=null,bool report=true)
|
|
{
|
|
|
|
begin_download:
|
|
|
|
try{
|
|
if(video.DownloadFrom == "YouTube")
|
|
{
|
|
switch (resolution)
|
|
{
|
|
case Resolution.Mux:
|
|
await DownloadVideoMuxedAsync(video,token,progress,report);
|
|
break;
|
|
case Resolution.PreMuxed:
|
|
await DownloadPreMuxedVideoAsync(video, token,progress,report);
|
|
break;
|
|
case Resolution.AudioOnly:
|
|
await DownloadAudioOnlyAsync(video,token,progress,report);
|
|
break;
|
|
case Resolution.VideoOnly:
|
|
await DownloadVideoOnlyAsync(video,token,progress,report);
|
|
break;
|
|
}
|
|
|
|
}else if(video.DownloadFrom.StartsWith("NormalDownload,Length="))
|
|
{
|
|
await DownloadFileAsync(video,token,progress,report);
|
|
}
|
|
}catch(Exception ex)
|
|
{
|
|
VideoId? id=VideoId.TryParse(video.Id);
|
|
if(id.HasValue){
|
|
await GetLogger().WriteAsync(ex,id.Value);
|
|
}else{
|
|
await GetLogger().WriteAsync(ex);
|
|
}
|
|
}
|
|
cancelDownload=false;
|
|
if(restartDownload)
|
|
{
|
|
restartDownload=false;
|
|
goto begin_download;
|
|
|
|
}
|
|
}
|
|
public bool cancelDownload=false;
|
|
public bool restartDownload=false;
|
|
|
|
public void CancelDownload(bool restart=false)
|
|
{
|
|
cancelDownload=true;
|
|
restartDownload=restart;
|
|
if(currentVideoCancelSource != null) currentVideoCancelSource.Cancel();
|
|
}
|
|
|
|
private async Task DownloadFileAsync(SavedVideo video, CancellationToken token, IProgress<double> progress, bool report)
|
|
{
|
|
string incomplete_file_path = $"Download/{B64.Base64UrlEncodes(video.Id)}-incomplete.part";
|
|
string file_path = $"Download/{B64.Base64UrlEncodes(video.Id)}.bin";
|
|
string url = video.Id;
|
|
bool canSeek=false;
|
|
long length=0;
|
|
|
|
foreach(var kvp in video.DownloadFrom.Split(',').Select<string,KeyValuePair<string,string>>((e)=>{
|
|
if(!e.Contains('=')) return new KeyValuePair<string, string>("","");
|
|
string[] keyVP = e.Split(new char[]{'='},2);
|
|
|
|
|
|
return new KeyValuePair<string, string>(keyVP[0],keyVP[1]);}))
|
|
{
|
|
switch(kvp.Key)
|
|
{
|
|
case "CanSeek":
|
|
bool.TryParse(kvp.Value,out canSeek);
|
|
break;
|
|
case "Length":
|
|
long len;
|
|
if(long.TryParse(kvp.Value,out len))
|
|
{
|
|
length=len;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
await ReportStartVideo(video,Resolution.PreMuxed,length);
|
|
Func<long,Task<Stream>> openDownload = async(e)=>{
|
|
HttpRequestMessage msg=new HttpRequestMessage(HttpMethod.Get,url);
|
|
if(e > 0)
|
|
{
|
|
msg.Headers.Range.Ranges.Add(new System.Net.Http.Headers.RangeItemHeaderValue(e,null));
|
|
}
|
|
|
|
var res=await HttpClient.SendAsync(msg);
|
|
return await res.Content.ReadAsStreamAsync();
|
|
};
|
|
|
|
if(await Continue(file_path))
|
|
{
|
|
bool deleteAndRestart=false;
|
|
|
|
using(var file = await OpenOrCreateAsync(incomplete_file_path))
|
|
{
|
|
if(file.Length > 0 && !canSeek)
|
|
{
|
|
deleteAndRestart = true;
|
|
}
|
|
if(!deleteAndRestart)
|
|
{
|
|
Stream strm=await openDownload(file.Length);
|
|
bool res=await CopyStreamAsync(strm,file,0,length,4096,progress,token);
|
|
if(res)
|
|
{
|
|
RenameFile(incomplete_file_path,file_path);
|
|
if(report)
|
|
await ReportEndVideo(video, Resolution.PreMuxed);
|
|
}
|
|
}
|
|
}
|
|
if(deleteAndRestart){
|
|
DeleteFile(incomplete_file_path);
|
|
using(var file = await OpenOrCreateAsync(incomplete_file_path))
|
|
{
|
|
Stream strm=await openDownload(0);
|
|
bool res=await CopyStreamAsync(strm,file,0,length,4096,progress,token);
|
|
if(res)
|
|
{
|
|
RenameFile(incomplete_file_path,file_path);
|
|
if(report)
|
|
await ReportEndVideo(video, Resolution.PreMuxed);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public async Task<bool> Continue(string path)
|
|
{
|
|
|
|
if (await FileExistsAsync(path))
|
|
{
|
|
return ((await OpenReadAsync(path)).Length == 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)
|
|
{
|
|
_=ex;
|
|
Console.WriteLine("FFMPEG ERROR, sorry cant read logging config");
|
|
|
|
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 || restartDownload || cancelDownload)
|
|
{
|
|
return false;
|
|
}
|
|
curPos+=read;
|
|
await dest.WriteAsync(buffer,0,read);
|
|
if(progress != null && len != 0)
|
|
{
|
|
progress.Report(curPos / len);
|
|
}
|
|
}while(read>0 && !token.IsCancellationRequested && !restartDownload && !cancelDownload);
|
|
if(token.IsCancellationRequested || restartDownload || cancelDownload)
|
|
{
|
|
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);
|
|
|
|
|
|
using(var vstrm_src=await OpenReadAsync(videoSrc))
|
|
{
|
|
long len = vstrm_src.Length;
|
|
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,astrm_src.Length,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,IProgress<double> progress,bool report=true)
|
|
{
|
|
|
|
bool isValid=true;
|
|
isValid=await DownloadVideoOnlyAsync(video,token,progress,report);
|
|
if(token.IsCancellationRequested || !isValid || cancelDownload || restartDownload)
|
|
{
|
|
return;
|
|
}
|
|
isValid = await DownloadAudioOnlyAsync(video,token,progress,report);
|
|
if(token.IsCancellationRequested || !isValid || cancelDownload || restartDownload)
|
|
{
|
|
return;
|
|
}
|
|
var streams=await BestStreamInfo.GetBestStreams(this,video.Id,token,false);
|
|
if(token.IsCancellationRequested || cancelDownload || restartDownload)
|
|
{
|
|
return;
|
|
}
|
|
if(report)
|
|
await 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,progress,token))
|
|
{
|
|
RenameFile(incomplete,complete);
|
|
}
|
|
}
|
|
if(report)
|
|
await 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,IProgress<double> progress,bool report=true)
|
|
{
|
|
currentVideoCancelSource = new CancellationTokenSource();
|
|
token.Register(()=>{
|
|
currentVideoCancelSource.Cancel();
|
|
});
|
|
|
|
|
|
bool ret=false;
|
|
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, currentVideoCancelSource.Token, 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}";
|
|
|
|
if(await Continue(complete))
|
|
{
|
|
if(!can_download) return false;
|
|
streams = await BestStreamInfo.GetBestStreams(this,video.Id,currentVideoCancelSource.Token);
|
|
if(streams != null)
|
|
{
|
|
currentVideoCancelSource.CancelAfter(streams.TillExpire - new TimeSpan(0,5,0));
|
|
|
|
using(var strm = await YoutubeClient.Videos.Streams.GetAsync(streams.VideoOnlyStreamInfo,currentVideoCancelSource.Token))
|
|
{
|
|
if(token.IsCancellationRequested || cancelDownload || restartDownload)
|
|
{
|
|
return false;
|
|
}
|
|
if(report)
|
|
await ReportStartVideo(video, Resolution.VideoOnly,streams.VideoOnlyStreamInfo.Size.Bytes);
|
|
|
|
|
|
using(var dest = await OpenOrCreateAsync(incomplete))
|
|
{
|
|
ret=await CopyStreamAsync(strm,dest,dest.Length,streams.VideoOnlyStreamInfo.Size.Bytes,4096,progress,currentVideoCancelSource.Token);
|
|
currentVideoCancelSource.Dispose();
|
|
currentVideoCancelSource=null;
|
|
}
|
|
if(ret)
|
|
{
|
|
RenameFile(incomplete,complete);
|
|
if(report)
|
|
await 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.VideoFrozen) return;
|
|
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));
|
|
|
|
|
|
}
|
|
}
|
|
private async Task<bool> DownloadAudioOnlyAsync(SavedVideo video,CancellationToken token,IProgress<double> progress,bool report=true)
|
|
{
|
|
currentVideoCancelSource = new CancellationTokenSource();
|
|
token.Register(()=>{
|
|
currentVideoCancelSource.Cancel();
|
|
});
|
|
|
|
bool ret=false;
|
|
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, token, 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);
|
|
if(await Continue(complete))
|
|
{
|
|
if(!can_download) return false;
|
|
streams = await BestStreamInfo.GetBestStreams(this,video.Id,currentVideoCancelSource.Token);
|
|
|
|
if(streams != null)
|
|
{
|
|
currentVideoCancelSource.CancelAfter(streams.TillExpire - new TimeSpan(0,5,0));
|
|
|
|
using(var strm = await YoutubeClient.Videos.Streams.GetAsync(streams.AudioOnlyStreamInfo,currentVideoCancelSource.Token))
|
|
{
|
|
if(token.IsCancellationRequested || cancelDownload || restartDownload)
|
|
{
|
|
return false;
|
|
}
|
|
if(report)
|
|
await ReportStartVideo(video, Resolution.AudioOnly,streams.AudioOnlyStreamInfo.Size.Bytes);
|
|
|
|
|
|
using(var dest = await OpenOrCreateAsync(incomplete))
|
|
{
|
|
ret=await CopyStreamAsync(strm,dest,dest.Length,streams.AudioOnlyStreamInfo.Size.Bytes,4096,progress,currentVideoCancelSource.Token);
|
|
currentVideoCancelSource.Dispose();
|
|
currentVideoCancelSource=null;
|
|
}
|
|
if(ret)
|
|
{
|
|
RenameFile(incomplete,complete);
|
|
if(report)
|
|
await ReportEndVideo(video, Resolution.AudioOnly);
|
|
}
|
|
}
|
|
}
|
|
}else{
|
|
|
|
ret=true;
|
|
}
|
|
}
|
|
|
|
//We know its resolution
|
|
return ret;
|
|
}
|
|
private async Task DownloadPreMuxedVideoAsync(SavedVideo video, CancellationToken token,IProgress<double> progress,bool report=true)
|
|
{
|
|
currentVideoCancelSource = new CancellationTokenSource();
|
|
token.Register(()=>{
|
|
currentVideoCancelSource.Cancel();
|
|
});
|
|
|
|
|
|
var streams = await BestStreamInfo.GetBestStreams(this, video.Id, currentVideoCancelSource.Token, false);
|
|
|
|
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}";
|
|
|
|
if(await Continue(complete))
|
|
{
|
|
|
|
streams = await BestStreamInfo.GetBestStreams(this,video.Id,currentVideoCancelSource.Token);
|
|
|
|
if(streams != null)
|
|
{
|
|
currentVideoCancelSource.CancelAfter(streams.TillExpire - new TimeSpan(0,5,0));
|
|
|
|
using(var strm = await YoutubeClient.Videos.Streams.GetAsync(streams.MuxedStreamInfo,currentVideoCancelSource.Token))
|
|
{
|
|
if(token.IsCancellationRequested || cancelDownload || restartDownload)
|
|
{
|
|
return;
|
|
}
|
|
if(report)
|
|
await ReportStartVideo(video,Resolution.PreMuxed,streams.MuxedStreamInfo.Size.Bytes);
|
|
|
|
bool ret;
|
|
using(var dest = await OpenOrCreateAsync(incomplete))
|
|
{
|
|
ret=await CopyStreamAsync(strm,dest,dest.Length,streams.MuxedStreamInfo.Size.Bytes,4096,progress,currentVideoCancelSource.Token);
|
|
currentVideoCancelSource.Dispose();
|
|
currentVideoCancelSource=null;
|
|
}
|
|
//We know its resolution
|
|
if(ret)
|
|
{
|
|
RenameFile(incomplete,complete);
|
|
if(report)
|
|
await 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);
|
|
}
|
|
}
|
|
} |