430 lines
16 KiB
C#
430 lines
16 KiB
C#
|
/*
|
|||
|
A Program that splits videos between two servers (determines which server using keywords) to https://gitlab.tesses.net/tesses50/tytd
|
|||
|
Copyright (C) 2023 Tesses
|
|||
|
|
|||
|
This program is free software: you can redistribute it and/or modify
|
|||
|
it under the terms of the GNU General Public License as published by
|
|||
|
the Free Software Foundation, either version 3 of the License, or
|
|||
|
(at your option) any later version.
|
|||
|
|
|||
|
This program is distributed in the hope that it will be useful,
|
|||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
GNU General Public License for more details.
|
|||
|
|
|||
|
You should have received a copy of the GNU General Public License
|
|||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
*/
|
|||
|
|
|||
|
namespace TYTDMultiDownloader;
|
|||
|
using Tesses.YouTubeDownloader;
|
|||
|
using Tesses.WebServer;
|
|||
|
using System.Threading.Tasks;
|
|||
|
using YoutubeExplode.Videos;
|
|||
|
using YoutubeExplode.Channels;
|
|||
|
using YoutubeExplode.Playlists;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Text;
|
|||
|
using Newtonsoft.Json;
|
|||
|
|
|||
|
class Program
|
|||
|
{
|
|||
|
|
|||
|
public class Downloader : IDownloader
|
|||
|
{
|
|||
|
public List<SavedVideo> Videos {get;set;}=new List<SavedVideo>();
|
|||
|
TYTDPathDirectory dir;
|
|||
|
TYTDClient nwii;
|
|||
|
TYTDClient nswitch;
|
|||
|
Func<SavedVideo,bool> isWii;
|
|||
|
public Downloader(TYTDPathDirectory dir,TYTDClient nwii,TYTDClient nswitch,Func<SavedVideo,bool> isWii)
|
|||
|
{
|
|||
|
this.dir = dir;
|
|||
|
this.nwii = nwii;
|
|||
|
this.nswitch = nswitch;
|
|||
|
this.isWii = isWii;
|
|||
|
}
|
|||
|
public event EventHandler<VideoStartedEventArgs>? VideoStarted;
|
|||
|
public event EventHandler<VideoProgressEventArgs>? VideoProgress;
|
|||
|
public event EventHandler<VideoFinishedEventArgs>? VideoFinished;
|
|||
|
public event EventHandler<TYTDErrorEventArgs>? Error;
|
|||
|
public event EventHandler<BellEventArgs>? Bell;
|
|||
|
|
|||
|
public async Task AddChannelAsync(ChannelId id, Resolution resolution = Resolution.PreMuxed)
|
|||
|
{
|
|||
|
if(!dir.ChannelInfoExists(id))
|
|||
|
{
|
|||
|
var res=await dir.YoutubeClient.Channels.GetAsync(id);
|
|||
|
await dir.WriteChannelInfoAsync(new SavedChannel(res));
|
|||
|
}
|
|||
|
await foreach(var video in dir.YoutubeClient.Channels.GetUploadsAsync(id))
|
|||
|
{
|
|||
|
await AddVideoAsync(video.Id,resolution);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public async Task AddFileAsync(string url, bool download = true)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
|
|||
|
public async Task AddHandleAsync(ChannelHandle handle, Resolution resolution = Resolution.PreMuxed)
|
|||
|
{
|
|||
|
var res=await dir.YoutubeClient.Channels.GetByHandleAsync(handle);
|
|||
|
await SaveChannelDataAsync(res);
|
|||
|
await AddChannelAsync(res.Id,resolution);
|
|||
|
}
|
|||
|
|
|||
|
public async Task AddPlaylistAsync(PlaylistId id, Resolution resolution = Resolution.PreMuxed)
|
|||
|
{
|
|||
|
|
|||
|
var res=await dir.YoutubeClient.Playlists.GetAsync(id);
|
|||
|
List<IVideo> videos=new List<IVideo>();
|
|||
|
await foreach(var item in dir.YoutubeClient.Playlists.GetVideosAsync(id))
|
|||
|
{
|
|||
|
videos.Add(item);
|
|||
|
|
|||
|
await AddVideoAsync(item.Id,resolution);
|
|||
|
}
|
|||
|
var pl=new SavedPlaylist(res,videos);
|
|||
|
await dir.WritePlaylistInfoAsync(pl);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public async Task AddSlugAsync(ChannelSlug handle, Resolution resolution = Resolution.PreMuxed)
|
|||
|
{
|
|||
|
var res=await dir.YoutubeClient.Channels.GetBySlugAsync(handle);
|
|||
|
await SaveChannelDataAsync(res);
|
|||
|
await AddChannelAsync(res.Id,resolution);
|
|||
|
}
|
|||
|
|
|||
|
private async Task SaveChannelDataAsync(Channel res)
|
|||
|
{
|
|||
|
if(!dir.ChannelInfoExists(res.Id))
|
|||
|
{
|
|||
|
await dir.WriteChannelInfoAsync(new SavedChannel(res));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public async Task AddToPersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
|
|||
|
public async Task AddUserAsync(UserName userName, Resolution resolution = Resolution.PreMuxed)
|
|||
|
{
|
|||
|
var res=await dir.YoutubeClient.Channels.GetByUserAsync(userName);
|
|||
|
await SaveChannelDataAsync(res);
|
|||
|
await AddChannelAsync(res.Id,resolution);
|
|||
|
}
|
|||
|
|
|||
|
public async Task AddVideoAsync(VideoId id, Resolution resolution = Resolution.PreMuxed)
|
|||
|
{
|
|||
|
SavedVideo video;
|
|||
|
if(!dir.VideoInfoExists(id))
|
|||
|
{
|
|||
|
video=new SavedVideo(await dir.YoutubeClient.Videos.GetAsync(id));
|
|||
|
await dir.WriteVideoInfoAsync(video);
|
|||
|
}else{
|
|||
|
video = await dir.GetVideoInfoAsync(id);
|
|||
|
}
|
|||
|
Videos.Add(video);
|
|||
|
if(resolution != Resolution.NoDownload)
|
|||
|
{
|
|||
|
await (isWii(video) ? nwii : nswitch).AddVideoAsync(id,resolution);
|
|||
|
if(isWii(video))
|
|||
|
{
|
|||
|
Console.WriteLine($"Wii Download: {video.Title} with Id: {video.Id}");
|
|||
|
}else{
|
|||
|
Console.WriteLine($"Switch Download: {video.Title} with Id: {video.Id}");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void CancelDownload(bool restart = false)
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public void DeletePersonalPlaylist(string name)
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public ExtraData GetExtraData()
|
|||
|
{
|
|||
|
return new ExtraData();
|
|||
|
}
|
|||
|
|
|||
|
public async IAsyncEnumerable<ListContentItem> GetPersonalPlaylistContentsAsync(string name)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
yield break;
|
|||
|
}
|
|||
|
|
|||
|
public SavedVideoProgress GetProgress()
|
|||
|
{
|
|||
|
return new SavedVideoProgress();
|
|||
|
}
|
|||
|
|
|||
|
public IReadOnlyList<(SavedVideo Video, Resolution Resolution)> GetQueueList()
|
|||
|
{
|
|||
|
return new List<(SavedVideo Video,Resolution Resolution)>();
|
|||
|
}
|
|||
|
|
|||
|
public async IAsyncEnumerable<Subscription> GetSubscriptionsAsync()
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
yield break;
|
|||
|
}
|
|||
|
|
|||
|
public bool PersonalPlaylistExists(string name)
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
public async Task RemoveItemFromPersonalPlaylistAsync(string name, VideoId id)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
|
|||
|
public async Task ReplacePersonalPlaylistAsync(string name, IEnumerable<ListContentItem> items)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
|
|||
|
public async Task ResubscribeAsync(ChannelId id, ChannelBellInfo info = ChannelBellInfo.NotifyAndDownload)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
|
|||
|
public async Task SetResolutionForItemInPersonalPlaylistAsync(string name, VideoId id, Resolution resolution)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
|
|||
|
public async Task SubscribeAsync(ChannelId id, ChannelBellInfo bellInfo = ChannelBellInfo.NotifyAndDownload)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
|
|||
|
public async Task SubscribeAsync(UserName name, ChannelBellInfo info = ChannelBellInfo.NotifyAndDownload)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
|
|||
|
public async Task UnsubscribeAsync(ChannelId id)
|
|||
|
{
|
|||
|
await Task.FromResult(0);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static Config _get_cfg()
|
|||
|
{
|
|||
|
Directory.CreateDirectory("working/WebSite");
|
|||
|
if(File.Exists("working/cfg.json"))
|
|||
|
{
|
|||
|
var cfg=JsonConvert.DeserializeObject<Config>(File.ReadAllText("working/cfg.json"));
|
|||
|
if(cfg != null) return cfg;
|
|||
|
}
|
|||
|
return new Config();
|
|||
|
}
|
|||
|
public static Config cfg = _get_cfg();
|
|||
|
public static TYTDPathDirectory dir= new TYTDPathDirectory("working");
|
|||
|
public static TYTDClient nintendo_wii = new TYTDClient(cfg.Match); //change my url to your url for if video added contains keyword/keywords from working/wii.txt
|
|||
|
public static TYTDClient nintendo_switch = new TYTDClient(cfg.NoMatch); //change my url to your url for if video added does not contain keyword/keywords from working/wii.txt
|
|||
|
|
|||
|
public static List<string> wiiLines = new List<string>();
|
|||
|
public static bool WiiSwitchHandler(SavedVideo video)
|
|||
|
{
|
|||
|
StringBuilder b = new StringBuilder();
|
|||
|
foreach(var item in video.Keywords)
|
|||
|
{
|
|||
|
b.Append(item.Replace(" ","").ToLower());
|
|||
|
}
|
|||
|
b.Append(video.Title.Replace(" ","").ToLower());
|
|||
|
b.Append(video.Description.Replace(" ","").Replace("\n","").Replace("\r","").Replace("\t","").ToLower());
|
|||
|
b.Append(video.AuthorTitle.Replace(" ","").ToLower());
|
|||
|
|
|||
|
string b2 = b.ToString();
|
|||
|
foreach(var item in wiiLines)
|
|||
|
{
|
|||
|
if(b2.Contains(item)) return true;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
static void Main(string[] args)
|
|||
|
{
|
|||
|
|
|||
|
|
|||
|
foreach(var item in cfg.Matches)
|
|||
|
{
|
|||
|
wiiLines.Add(item.Replace(" ","").ToLower());
|
|||
|
}
|
|||
|
Func<Downloader> dl = ()=>{
|
|||
|
return new Downloader(dir,nintendo_wii,nintendo_switch,WiiSwitchHandler);
|
|||
|
};
|
|||
|
dir.CreateDirectories();
|
|||
|
dir.CanDownload = false; //we do not want this computer to download any videos or thumbnails
|
|||
|
StaticServer staticServer = new StaticServer("working/WebSite",true);
|
|||
|
staticServer.AllowUpload=false;
|
|||
|
MountableServer mountableServer = new MountableServer(staticServer);
|
|||
|
mountableServer.Mount("/api/",new AddServer(dl));
|
|||
|
|
|||
|
mountableServer.StartServer(cfg.Port);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
public class AddServer : Server
|
|||
|
{
|
|||
|
public override async Task PostAsync(ServerContext ctx)
|
|||
|
{
|
|||
|
ctx.ParseBody();
|
|||
|
var dl=dl0();
|
|||
|
|
|||
|
|
|||
|
Resolution _dl2=Resolution.NoDownload;
|
|||
|
string _dl;
|
|||
|
|
|||
|
if(ctx.QueryParams.TryGetFirst("dl",out _dl))
|
|||
|
{
|
|||
|
if(_dl == "on") _dl2=Resolution.PreMuxed;
|
|||
|
}
|
|||
|
string videoLs;
|
|||
|
if(ctx.QueryParams.TryGetFirst("urls",out videoLs))
|
|||
|
{
|
|||
|
string[] _videos=videoLs.Replace("\r",",").Replace("\n",",").Replace(" ",",").Split(",",StringSplitOptions.RemoveEmptyEntries);
|
|||
|
foreach(var item in _videos)
|
|||
|
{
|
|||
|
await dl.AddItemAsync(item,_dl2);
|
|||
|
}
|
|||
|
StringBuilder b = new StringBuilder("<!doctype html><html><head><title>Videos</title></head><body><h1>Videos</h1>");
|
|||
|
foreach(var items in dl.Videos)
|
|||
|
{
|
|||
|
var strm=await BestStreamInfo.GetBestStreams(dir,items.Id);
|
|||
|
string title = $"{items.Title}-{items.Id}.{strm.MuxedStreamInfo.Container.Name}";
|
|||
|
string item = $"<a href=\"./Stream?v={items.Id}\">{System.Web.HttpUtility.HtmlEncode(title)}</a><br>";
|
|||
|
b.Append(item);
|
|||
|
}
|
|||
|
b.Append("</body></html>");
|
|||
|
|
|||
|
await ctx.SendTextAsync(b.ToString());
|
|||
|
return;
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
await ctx.SendTextAsync("<!doctype html><html><head><title>Error</title></head><body><h1>Error</h1></body></html>");
|
|||
|
}
|
|||
|
Func<Downloader> dl0;
|
|||
|
|
|||
|
public AddServer(Func<Downloader> dl)
|
|||
|
{
|
|||
|
this.dl0 = dl;
|
|||
|
// this._lock = _lock;
|
|||
|
}
|
|||
|
public bool IsPath(ServerContext ctx,string path,out string myArgument)
|
|||
|
{
|
|||
|
if(ctx.UrlPath.StartsWith(path))
|
|||
|
{
|
|||
|
myArgument = ctx.UrlPath.Substring(path.Length);
|
|||
|
return true;
|
|||
|
}
|
|||
|
myArgument = "";
|
|||
|
return false;
|
|||
|
}
|
|||
|
public override async Task GetAsync(ServerContext ctx)
|
|||
|
{
|
|||
|
|
|||
|
var dl=dl0();
|
|||
|
|
|||
|
try{
|
|||
|
string data;
|
|||
|
if(IsPath(ctx,"/AddItem/",out data))
|
|||
|
{
|
|||
|
await dl.AddItemAsync(data);
|
|||
|
}
|
|||
|
if(IsPath(ctx,"/AddVideo/",out data))
|
|||
|
{
|
|||
|
VideoId? videoId = VideoId.TryParse(data);
|
|||
|
if(videoId.HasValue)
|
|||
|
{
|
|||
|
await dl.AddVideoAsync(videoId.Value);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
if(IsPath(ctx,"/AddItemRes/",out data))
|
|||
|
{
|
|||
|
string[] data2=data.Split('/',2,StringSplitOptions.RemoveEmptyEntries);
|
|||
|
if(data2.Length == 2)
|
|||
|
{
|
|||
|
await dl.AddItemAsync(data2[1]);
|
|||
|
}
|
|||
|
}
|
|||
|
if(IsPath(ctx,"/AddVideoRes/",out data))
|
|||
|
{
|
|||
|
string[] data2=data.Split('/',2,StringSplitOptions.RemoveEmptyEntries);
|
|||
|
|
|||
|
if(data2.Length == 2)
|
|||
|
{
|
|||
|
VideoId? videoId = VideoId.TryParse(data2[1]);
|
|||
|
if(videoId.HasValue)
|
|||
|
{
|
|||
|
await dl.AddVideoAsync(videoId.Value);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(ctx.UrlPath == "/Stream")
|
|||
|
{
|
|||
|
//?dl=true
|
|||
|
string v;
|
|||
|
if(ctx.QueryParams.TryGetFirst("v",out v))
|
|||
|
{
|
|||
|
VideoId? id = VideoId.TryParse(v);
|
|||
|
if(id.HasValue)
|
|||
|
{
|
|||
|
SavedVideo video;
|
|||
|
if(!dir.VideoInfoExists(id.Value))
|
|||
|
{
|
|||
|
video = new SavedVideo(await dir.YoutubeClient.Videos.GetAsync(id.Value));
|
|||
|
await dir.WriteVideoInfoAsync(video);
|
|||
|
}else{
|
|||
|
video = await dir.GetVideoInfoAsync(id.Value);
|
|||
|
}
|
|||
|
var strm=await BestStreamInfo.GetBestStreams(dir,id.Value);
|
|||
|
|
|||
|
string title = $"{video.Title}-{video.Id}.{strm.MuxedStreamInfo.Container.Name}";
|
|||
|
System.Net.Mime.ContentDisposition cd = new System.Net.Mime.ContentDisposition();
|
|||
|
cd.FileName = title;
|
|||
|
|
|||
|
ctx.ResponseHeaders.Add("Content-Disposition",cd.ToString());
|
|||
|
|
|||
|
await ctx.SendStreamAsync(await dir.YoutubeClient.Videos.Streams.GetAsync(strm.MuxedStreamInfo),HeyRed.Mime.MimeTypesMap.GetMimeType(title));
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
await ctx.SendTextAsync("OK");
|
|||
|
}catch(Exception ex)
|
|||
|
{
|
|||
|
|
|||
|
await ctx.SendExceptionAsync(ex);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
internal class Config
|
|||
|
{
|
|||
|
public string Match {get;set;}="http://192.168.0.142:3252/";
|
|||
|
public string NoMatch {get;set;}="http://192.168.0.112:3252/";
|
|||
|
public int Port {get;set;}=3252;
|
|||
|
public List<string> Matches {get;set;}=new List<string>();
|
|||
|
}
|