Added documentation to Api just type /api/endpoints.html
This commit is contained in:
parent
e6c095b125
commit
3122652145
|
@ -7,7 +7,7 @@
|
||||||
<File FileName="TYTD.Api/Server/Models/SavedVideo.cs" Line="8" Column="1" />
|
<File FileName="TYTD.Api/Server/Models/SavedVideo.cs" Line="8" Column="1" />
|
||||||
<File FileName="TYTD.Api/Server/Models/InfomationQueueItem.cs" Line="280" Column="42" />
|
<File FileName="TYTD.Api/Server/Models/InfomationQueueItem.cs" Line="280" Column="42" />
|
||||||
<File FileName="TYTD.Api/Server/Functions/ffmpeg.cs" Line="5" Column="15" />
|
<File FileName="TYTD.Api/Server/Functions/ffmpeg.cs" Line="5" Column="15" />
|
||||||
<File FileName="Program.cs" Line="122" Column="88" />
|
<File FileName="Program.cs" Line="1060" Column="23" />
|
||||||
<File FileName="TYTD.Api/MyClass.cs" Line="80" Column="14" />
|
<File FileName="TYTD.Api/MyClass.cs" Line="80" Column="14" />
|
||||||
<File FileName="TYTD.Api/Server/Models/SavedChannel.cs" Line="6" Column="10" />
|
<File FileName="TYTD.Api/Server/Models/SavedChannel.cs" Line="6" Column="10" />
|
||||||
<File FileName="TYTD.Api/Server/Models/SavedPlaylist.cs" Line="7" Column="1" />
|
<File FileName="TYTD.Api/Server/Models/SavedPlaylist.cs" Line="7" Column="1" />
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<File FileName="youtube-downloader.csproj" Line="1" Column="1" />
|
<File FileName="youtube-downloader.csproj" Line="1" Column="1" />
|
||||||
<File FileName="../../../usr/lib/mono/msbuild/15.0/bin/Microsoft.Common.CurrentVersion.targets" Line="2101" Column="5" />
|
<File FileName="../../../usr/lib/mono/msbuild/15.0/bin/Microsoft.Common.CurrentVersion.targets" Line="2101" Column="5" />
|
||||||
<File FileName="TYTD.Api/Server/Models/IEnumerator.cs" Line="1" Column="1" />
|
<File FileName="TYTD.Api/Server/Models/IEnumerator.cs" Line="1" Column="1" />
|
||||||
<File FileName="packages.config" Line="1" Column="1" />
|
<File FileName="TYTD.Api/SimpleHttpExtensions.cs" Line="432" Column="1" />
|
||||||
</Files>
|
</Files>
|
||||||
<Pads>
|
<Pads>
|
||||||
<Pad Id="ProjectPad">
|
<Pad Id="ProjectPad">
|
||||||
|
|
236
Program.cs
236
Program.cs
|
@ -24,6 +24,7 @@ using PlaylistsNET.Utils;
|
||||||
using YoutubeExplode.Playlists;
|
using YoutubeExplode.Playlists;
|
||||||
using Dasync.Collections;
|
using Dasync.Collections;
|
||||||
using YoutubeExplode.Search;
|
using YoutubeExplode.Search;
|
||||||
|
using Hyperlinq;
|
||||||
|
|
||||||
namespace TYTD
|
namespace TYTD
|
||||||
{
|
{
|
||||||
|
@ -41,6 +42,7 @@ namespace TYTD
|
||||||
static string webSitePath;
|
static string webSitePath;
|
||||||
static void Main(string[] arg)
|
static void Main(string[] arg)
|
||||||
{
|
{
|
||||||
|
Directory.CreateDirectory(Path.Combine("config", "apidll"));
|
||||||
Downloader.GetManifest = GetManifest;
|
Downloader.GetManifest = GetManifest;
|
||||||
Thread t = new Thread(new ThreadStart(() => {
|
Thread t = new Thread(new ThreadStart(() => {
|
||||||
Downloader.DL.DownloadThread().GetAwaiter().GetResult();
|
Downloader.DL.DownloadThread().GetAwaiter().GetResult();
|
||||||
|
@ -61,79 +63,105 @@ namespace TYTD
|
||||||
Route.Before += Route_Before;
|
Route.Before += Route_Before;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Generic */
|
/* Generic */
|
||||||
Route.Add("/api/AddItems", (HttpAction)AddItems, "POST");
|
Downloader.RouteAdd("/api/AddItems","Generic","Post JSON file with\n" + WebUtility.HtmlEncode("<input type=\"file\" name=\"somename\">") + "\nJson structure is like: <a href=\"example_tripple_structure.json\">this</a>", (HttpAction)AddItems, "POST");
|
||||||
Route.Add("/api/AddItem/{Id}", (HttpAction)AddItem);
|
Downloader.RouteAdd("/api/AddItem/{Id}", "Generic", "Add Item to downloader\nDownloader will auto detect media type\nIt will be SD (Premuxed Video)\nParams:\n{Id}: The Id or URL for the media",(HttpAction)AddItem);
|
||||||
Route.Add("/api/AddItemRes/{R}/{Id}", (HttpAction)AddItemRes);
|
Downloader.RouteAdd("/api/AddItemRes/{R}/{Id}","Generic", "Add Item to downloader\nDownloader will auto detect media type\nParams:\n{R}:0=HD (Muxed using ffmpeg), 1=SD (Premuxed Video), 2=Audio only\n{Id}: The Id or URL for the media", (HttpAction)AddItemRes);
|
||||||
Route.Add("/api/AddFile/{Url}", (HttpAction)AddFile);
|
Downloader.RouteAdd("/api/AddFile/{Url}", "Generic", "Add Normal HTTP(S) download\nParams:\n{Url}: url to file for download", (HttpAction)AddFile);
|
||||||
Route.Add("/api/AddCaptions/{Id}", (HttpAction)AddCaptions);
|
Downloader.RouteAdd("/api/AddCaptions/{Id}", "Generic", "Download all subtitles for video\nParams:\n{Id}: video id to get subtitles from", (HttpAction)AddCaptions);
|
||||||
|
|
||||||
|
|
||||||
/* Videos */
|
/* Videos */
|
||||||
Route.Add("/api/AddVideoInfo/{Id}", AddVideoInfo);
|
Downloader.RouteAdd("/api/AddVideoInfo/{Id}", "Videos", "Get Video Info only (Don't Download the video)\nParams:\n{Id}: Video Id to download Info and Thumbnails for",AddVideoInfo);
|
||||||
Route.Add("/api/AddVideo/{Id}", (HttpAction)AddVideo);
|
Downloader.RouteAdd("/api/AddVideo/{Id}","Videos", "Add Video to downloader\nIt will be SD (Premuxed Video)\nParams:\n{Id}: The Id or URL for the video", (HttpAction)AddVideo);
|
||||||
Route.Add("/api/AddVideoRes/{R}/{Id}", (HttpAction)AddVideoRes);
|
Downloader.RouteAdd("/api/AddVideoRes/{R}/{Id}","Videos", "Add Video to downloader\nParams:\n{R}:0=HD (Muxed using ffmpeg), 1=SD (Premuxed Video), 2=Audio only\n{Id}: The Id or URL for the video", (HttpAction)AddVideoRes);
|
||||||
Route.Add("/api/Redownload", (HttpAction)Redownload);
|
Downloader.RouteAdd("/api/Redownload","Videos","Resume all videos downloading (Ignores Complete) (SD, Premuxed Video)", (HttpAction)Redownload);
|
||||||
Route.Add("/api/Redownload/{R}", (HttpAction)RedownloadRes);
|
Downloader.RouteAdd("/api/RedownloadRes/{R}","Videos", "Resume all videos downloading (Ignores Complete)\nParams:\n{R}: 0=HD (Muxed using ffmpeg), 1=SD (Premuxed Video), 2=Audio only", (HttpAction)RedownloadRes);
|
||||||
Route.Add("/api/Watch/{VideoId}", (HttpAction)Watch);
|
Downloader.RouteAdd("/api/Watch/{VideoId}","Videos", "Brings Up Watch Page, loads watch_page.thtml\nParams:\n{VideoId}: the video id, replaces value in thtml file", (HttpAction)Watch);
|
||||||
Route.Add("/api/VideoInfo/{Id}",(HttpAction)VideoInfo);
|
Downloader.RouteAdd("/api/VideoInfo/{Id}","Videos","Brings up html Video Info\nParams:\n{Id}: Video Id or Url to get info from",(HttpAction)VideoInfo);
|
||||||
/* Playlist */
|
/* Playlist */
|
||||||
Route.Add("/api/AddPlaylistOnly/{Id}", AddPlaylistOnly);
|
Downloader.RouteAdd("/api/AddPlaylistOnly/{Id}","Playlist","Add playlist, dont download videos\nParams:\n{Id}: Playlist Id or Url to download", AddPlaylistOnly);
|
||||||
Route.Add("/api/AddPlaylist/{Id}", (HttpAction)AddPlaylist);
|
Downloader.RouteAdd("/api/RedownloadPlaylist/{Id}", "Playlist", "Redownload Playlist Entries (Wont Update Playlist, use /api/AddPlaylist/ for that)\nThis will download playlist if not already done, or if playlist is empty\n(SD, Premuxed Video)\nParams:\n{Id}: Existing Playlist Id",(HttpAction)RedownloadPlaylist);
|
||||||
Route.Add("/api/AddPlaylistRes/{R}/{Id}", (HttpAction)AddPlaylistRes);
|
Downloader.RouteAdd("/api/RedownloadPlaylistRes/{R}/{Id}", "Playlist", "Redownload Playlist Entries (Wont Update Playlist, use /api/AddPlaylist/ for that)\nThis will download playlist if not already done, or if playlist is empty\nParams:\n{Id}: Existing Playlist Id\n{R}: 0=HD (Muxed using ffmpeg), 1=SD (Premuxed Video), 2=Audio only", (HttpAction)RedownloadPlaylistRes);
|
||||||
Route.Add("/api/PersonalPlaylist/{PlaylistName}",(HttpAction)PersonalPlaylist);
|
Downloader.RouteAdd("/api/AddPlaylist/{Id}","Playlist", "Add Playlist to downloader\nIt will be SD (Premuxed Video)\nParams:\n{Id}: The Id or URL for the Playlist", (HttpAction)AddPlaylist);
|
||||||
Route.Add("/api/CreatePlaylist/{Ids}/playlist.{extension}", (HttpAction)CreatePlaylist);
|
Downloader.RouteAdd("/api/AddPlaylistRes/{R}/{Id}","Playlist", "Add Playlist to downloader\nParams:\n{R}:0=HD (Muxed using ffmpeg), 1=SD (Premuxed Video), 2=Audio only\n{Id}: The Id or URL for the playlist", (HttpAction)AddPlaylistRes);
|
||||||
Route.Add("/api/CreatePlaylistRes/{Ids}/playlist.{extension}", (HttpAction)CreatePlaylistRes);
|
Downloader.RouteAdd("/api/PersonalPlaylist/{PlaylistName}","PersonalPlaylist","Create personal playlist with name",(HttpAction)PersonalPlaylist);
|
||||||
Route.Add("/api/PlaylistInfo/{Id}", (HttpAction)PlaylistInfo);
|
Downloader.RouteAdd("/api/CreatePlaylist/{Ids}/playlist.{extension}","PersonalPlaylist","Create Playlist with Ids\nParams:\n{Ids}: Comma seperated VideoIds\n{extension}: m3u, m3u8, pls, wpl, zpl", (HttpAction)CreatePlaylist);
|
||||||
Route.Add("/api/ListPlaylists/", (HttpAction)ListPlaylists);
|
Downloader.RouteAdd("/api/CreatePlaylistRes/{Ids}/playlist.{extension}","PersonalPlaylist","Create Playlist with Ids, Res\nParams:\n {Ids}: Comma seperated VideoIds,Res,VideoId,Res where Res is 0=HD, 1=SD, 2=Audio only\n{extension}: m3u, m3u8, pls, wpl, zpl", (HttpAction)CreatePlaylistRes);
|
||||||
|
Downloader.RouteAdd("/api/PlaylistInfo/{Id}","Playlist", "Brings up html Playlist Info\nParams:\n{Id}: Playlist Id or Url to get info from", (HttpAction)PlaylistInfo);
|
||||||
|
Downloader.RouteAdd("/api/ListPlaylists/","Playlist","List all playlists", (HttpAction)ListPlaylists);
|
||||||
/* Search */
|
/* Search */
|
||||||
Route.Add("/api/SearchOnly/{text}", (HttpAction)SearchOnly);
|
Downloader.RouteAdd("/api/SearchOnly/{text}","Search","Search youtube without downloading thumbnails, VideoInfos", (HttpAction)SearchOnly);
|
||||||
Route.Add("/api/Search/{text}", (HttpAction)Search);
|
Downloader.RouteAdd("/api/Search/{text}","Search","Search youtube", (HttpAction)Search);
|
||||||
Route.Add("/api/SearchPage/", SearchPage);
|
Downloader.RouteAdd("/api/SearchPage/","Search","Search youtube html", SearchPage);
|
||||||
Route.Add("/api/SearchPage/{query}",SearchPage);
|
Downloader.RouteAdd("/api/SearchPage/{query}","Search","Search youtube html",SearchPage);
|
||||||
Route.Add("/api/SearchPage/",SearchPage,"POST");
|
Downloader.RouteAdd("/api/SearchPage/","Search","Search youtube html",SearchPage,"POST");
|
||||||
Route.Add("/api/SearchVideos/", (HttpAction)SearchVideos,"POST");
|
Downloader.RouteAdd("/api/SearchVideos/","Video", "Search existing videos", (HttpAction)SearchVideos,"POST");
|
||||||
Route.Add("/api/SearchVideos/{query}", (HttpAction)SearchVideos, "GET");
|
Downloader.RouteAdd("/api/SearchVideos/{query}","Videos", "Search existing videos", (HttpAction)SearchVideos, "GET");
|
||||||
Route.Add("/api/SearchVideos/", (HttpAction)SearchVideos, "GET");
|
Downloader.RouteAdd("/api/SearchVideos/", "Videos", "Search existing videos", (HttpAction)SearchVideos, "GET");
|
||||||
|
|
||||||
/* Channel */
|
/* Channel */
|
||||||
Route.Add("/api/AddChannelOnly/{Id}", AddChannelOnly);
|
Downloader.RouteAdd("/api/AddChannelOnly/{Id}","Channel", "Add Channel, dont download videos\nParams:\n{Id}: Channel Id or Url to download", AddChannelOnly);
|
||||||
Route.Add("/api/AddChannel/{Id}", (HttpAction)AddChannel);
|
Downloader.RouteAdd("/api/AddChannel/{Id}","Channel", "Add Channel to downloader\nIt will be SD (Premuxed Video)\nParams:\n{Id}: The Id or URL for the Channel", (HttpAction)AddChannel);
|
||||||
Route.Add("/api/AddChannelRes/{R}/{Id}", (HttpAction)AddChannelRes);
|
Downloader.RouteAdd("/api/AddChannelRes/{R}/{Id}","Channel", "Add Channel to downloader\nParams:\n{R}:0=HD (Muxed using ffmpeg), 1=SD (Premuxed Video), 2=Audio only\n{Id}: The Id or URL for the Channel", (HttpAction)AddChannelRes);
|
||||||
|
|
||||||
/* User */
|
/* User */
|
||||||
Route.Add("/api/AddUserOnly/{Id}", AddUserOnly);
|
Downloader.RouteAdd("/api/AddUserOnly/{Id}","User", "Add Channel By Username, dont download videos\nParams:\n{Id}: Username or UserUrl to download", AddUserOnly);
|
||||||
Route.Add("/api/AddUser/{Id}", (HttpAction)AddUser);
|
Downloader.RouteAdd("/api/AddUser/{Id}","User", "Add Channel By Username to downloader\nIt will be SD (Premuxed Video)\nParams:\n{Id}: The Name or UserURL for the Channel", (HttpAction)AddUser);
|
||||||
Route.Add("/api/AddUserRes/{R}/{Id}", (HttpAction)AddUserRes);
|
Downloader.RouteAdd("/api/AddUserRes/{R}/{Id}","User", "Add Channel By Username to downloader\nParams:\n{R}:0=HD (Muxed using ffmpeg), 1=SD (Premuxed Video), 2=Audio only\n{Id}: The Id or UserURL for the Channel", (HttpAction)AddUserRes);
|
||||||
|
|
||||||
/* Queue and Progress */
|
/* Queue and Progress */
|
||||||
Route.Add("/api/QueueList", (HttpAction)QueueList);
|
Downloader.RouteAdd("/api/QueueList","Queue","QueueList as json", (HttpAction)QueueList);
|
||||||
Route.Add("/api/QueueListPage/", (HttpAction)QueueListHtml);
|
Downloader.RouteAdd("/api/QueueListPage/","Queue","QueueList as html", (HttpAction)QueueListHtml);
|
||||||
Route.Add("/api/QueueMove/{From}/{To}", (HttpAction)QueueMove);
|
Downloader.RouteAdd("/api/QueueMove/{From}/{To}","Queue","Move item in queue\nParams:\n{From}: where to move item from (Either number or \"last\")\n{To}: where to move item to (Either number, \"up\", \"down\", \"top\", \"bottom\" or \"remove\"", (HttpAction)QueueMove);
|
||||||
Route.Add("/api/QueueMove2/{To}/{Id}", (HttpAction)QueueMove2);
|
Downloader.RouteAdd("/api/QueueMoveId/{To}/{Id}","Queue", "Move item in queue (Id is from)\nParams:\n{Id}: Video Id (some queue position)\n{To}: where to move item to (Either number, \"up\", \"down\", \"top\", \"bottom\" or \"remove\"", (HttpAction)QueueMove2);
|
||||||
Route.Add("/api/Progress", (HttpAction)VideoProgress);
|
Downloader.RouteAdd("/api/Progress","Current","Get progress as json", (HttpAction)VideoProgress);
|
||||||
Route.Add("/api/Progress.html", (HttpAction)VideoProgressHtml);
|
Downloader.RouteAdd("/api/Progress.html","Current","Get progress as html", (HttpAction)VideoProgressHtml);
|
||||||
Route.Add("/api/Redo", (HttpAction)Redo);
|
Downloader.RouteAdd("/api/Redo","Current","Cancel current video, redo video (however It may act like /api/Cancel, that is a bug)", (HttpAction)Redo);
|
||||||
Route.Add("/api/Cancel", (HttpAction)Cancel);
|
|
||||||
|
Downloader.RouteAdd("/api/Cancel","Current","Cancel current video, go to next", (HttpAction)Cancel);
|
||||||
|
|
||||||
|
|
||||||
/* Storage */
|
/* Storage */
|
||||||
Route.Add("/api/Storage/GetDirectories/{Path}", (HttpAction)StorageGetDirectories);
|
Downloader.RouteAdd("/api/Storage/GetDirectories/{Path}", "Storage","Get list of directories in Path\nAlready used Path.GetFileName(), its json array", (HttpAction)StorageGetDirectories);
|
||||||
Route.Add("/api/Storage/GetFiles/{Path}", (HttpAction)StorageGetFiles);
|
Downloader.RouteAdd("/api/Storage/GetFiles/{Path}","Storage","Get list of files in Path\nAlready used Path.GetFileName(), its json array", (HttpAction)StorageGetFiles);
|
||||||
Route.Add("/api/Storage/DirectoryExists/{Path}", (HttpAction)StorageDirectoryExists);
|
Downloader.RouteAdd("/api/Storage/DirectoryExists/{Path}", "Storage", "returns \"true\" if directory exists or \"false\" if not",(HttpAction)StorageDirectoryExists);
|
||||||
Route.Add("/api/Storage/FileExists/{Path}", (HttpAction)StorageFileExists);
|
Downloader.RouteAdd("/api/Storage/FileExists/{Path}","Storage","returns \"true\" if file exists or \"false\" if not", (HttpAction)StorageFileExists);
|
||||||
Route.Add("/api/Storage/File/{Path}", (HttpAction)StorageFile);
|
Downloader.RouteAdd("/api/Storage/File/{Path}","Storage","Get file based on working directory", (HttpAction)StorageFile);
|
||||||
Route.Add("/api/Storage/Video/{Id}",(HttpAction)Video);
|
Downloader.RouteAdd("/api/Storage/Video/{Id}","Storage","",(HttpAction)Video);
|
||||||
Route.Add("/api/Storage/VideoRes/{Res}/{Id}",(HttpAction)VideoRes);
|
Downloader.RouteAdd("/api/Storage/VideoRes/{Res}/{Id}","Storage","Download Video to Computer from Downloader\nParams:\n{Res}: 0=HD (Muxed using ffmpeg), 1=SD (Premuxed Video), 2=Audio only\n{Id}: Video Id to Download",(HttpAction)VideoRes);
|
||||||
Route.Add("/api/upload/", (HttpAction)UploadFiles, "POST");
|
Downloader.RouteAdd("/api/upload/","Storage","Upload file via POST", (HttpAction)UploadFiles, "POST");
|
||||||
|
|
||||||
Route.Add("/api/endpoint", (HttpAction)Endpoint,"POST");
|
|
||||||
|
|
||||||
|
Downloader.RouteAdd("/api/endpoint","Generic","POST endpoint for many functions\n<a href=\"https://tesses.cf/markdown.php#apps/tytd/api_endpoint.md\">Documentation</a>", (HttpAction)Endpoint,"POST");
|
||||||
|
Downloader.RouteAdd("/api/endpoints.html","Other","This Page", (HttpAction)Endpoints);
|
||||||
|
Route.Add("/api/example_tripple_structure.json", (req, resp, args) =>
|
||||||
|
{
|
||||||
|
List<IDResolutionTypeTriplet> v = new List<IDResolutionTypeTriplet>();
|
||||||
|
v.Add(new IDResolutionTypeTriplet() { Id = "xxxxxxxxxxx", Resolution = Resolution.NoConvert, Type = InfoType.Video });
|
||||||
|
v.Add(new IDResolutionTypeTriplet() { Id = "PLxxxxxxxxxxxxxxxxx", Resolution = Resolution.NoConvert, Type = InfoType.Playlist });
|
||||||
|
v.Add(new IDResolutionTypeTriplet() { Id = "UCxxxxxxxxxxxxxxxxxxxxxx", Resolution = Resolution.NoConvert, Type = InfoType.Channel });
|
||||||
|
v.Add(new IDResolutionTypeTriplet() { Id = "SomeUserName", Resolution = Resolution.NoConvert, Type = InfoType.User });
|
||||||
|
v.Add(new IDResolutionTypeTriplet() { Id = "xxxxxxxxxxx", Resolution = Resolution.NoConvert, Type = InfoType.ClosedCaptions });
|
||||||
|
v.Add(new IDResolutionTypeTriplet() { Id = "https://example.com/path/to/file.txt", Resolution = Resolution.NoConvert, Type = InfoType.FileDownload });
|
||||||
|
|
||||||
|
resp.AsJson(v);
|
||||||
|
});
|
||||||
ApiLoader.Init();
|
ApiLoader.Init();
|
||||||
|
Downloader.RouteAdd("/api{p}","Other","Just a redirect to /api/endpoints.html", (request, response, action) =>
|
||||||
|
{
|
||||||
|
request.RedirectIt(response, "/api/endpoints.html");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Other */
|
/* Other */
|
||||||
Route.Add("/", (HttpAction)Index);
|
Downloader.RouteAdd("/","Other","Home page", (HttpAction)Index);
|
||||||
Route.Add("/extensions.html", (HttpAction)Extensions);
|
Downloader.RouteAdd("/extensions.html","Other","Extensions URL", (HttpAction)Extensions);
|
||||||
Route.Add("/{Path}", (HttpAction)RootPath);
|
Downloader.RouteAdd("/{Path}","Other","Website Files", (HttpAction)RootPath);
|
||||||
Route.Add("/{Path}",(HttpAction)UploadFilePut,"PUT");
|
Downloader.RouteAdd("/{Path}","Other","Upload file over put",(HttpAction)UploadFilePut,"PUT");
|
||||||
Console.CancelKeyPress += (sender, e) => { ApiLoader.Dispose();var date = DateTime.Now.ToString("yyyyMMdd_HHmmss");Directory.CreateDirectory(Path.Combine("config","queues-close")); File.WriteAllText(Path.Combine("config", "queues-close", $"{date}.json"), Downloader.GetQueue()); Console.WriteLine("TYTD has Closed"); Environment.Exit(0); };
|
Console.CancelKeyPress += (sender, e) => { ApiLoader.Dispose();var date = DateTime.Now.ToString("yyyyMMdd_HHmmss");Directory.CreateDirectory(Path.Combine("config","queues-close")); File.WriteAllText(Path.Combine("config", "queues-close", $"{date}.json"), Downloader.GetQueue()); Console.WriteLine("TYTD has Closed"); Environment.Exit(0); };
|
||||||
|
|
||||||
|
|
||||||
|
@ -301,6 +329,60 @@ namespace TYTD
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#region Playlist
|
#region Playlist
|
||||||
|
public static void RedownloadPlaylistRes(HttpListenerRequest req,HttpListenerResponse resp,Dictionary<string,string> args)
|
||||||
|
{
|
||||||
|
Resolution res=(Resolution)int.Parse(args["R"]);
|
||||||
|
var id = args["Id"];
|
||||||
|
string jsonFile = Path.Combine("Playlist", $"{id}.json");
|
||||||
|
|
||||||
|
if (File.Exists(jsonFile))
|
||||||
|
{
|
||||||
|
SavedPlaylist playlist = JsonConvert.DeserializeObject<SavedPlaylist>(File.ReadAllText(jsonFile));
|
||||||
|
if (playlist.Videos != null && playlist.Videos.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var v in playlist.Videos)
|
||||||
|
{
|
||||||
|
Downloader.DownloadVideo(v,res);
|
||||||
|
}
|
||||||
|
req.RedirectIt(resp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddPlaylistRes(req, resp, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddPlaylistRes(req, resp, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void RedownloadPlaylist(HttpListenerRequest req,HttpListenerResponse resp,Dictionary<string,string> args)
|
||||||
|
{
|
||||||
|
var id=args["Id"];
|
||||||
|
string jsonFile = Path.Combine("Playlist", $"{id}.json");
|
||||||
|
if(File.Exists(jsonFile))
|
||||||
|
{
|
||||||
|
SavedPlaylist playlist = JsonConvert.DeserializeObject<SavedPlaylist>(File.ReadAllText(jsonFile));
|
||||||
|
if(playlist.Videos != null && playlist.Videos.Count > 0)
|
||||||
|
{
|
||||||
|
foreach(var v in playlist.Videos)
|
||||||
|
{
|
||||||
|
Downloader.DownloadVideo(v);
|
||||||
|
}
|
||||||
|
req.RedirectIt(resp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddPlaylist(req, resp, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddPlaylist(req, resp, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
public static async Task AddPlaylistOnly(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
|
public static async Task AddPlaylistOnly(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
|
||||||
{
|
{
|
||||||
await Downloader.DownloadPlaylistOnly(System.Web.HttpUtility.UrlDecode(args["Id"]), Resolution.NoConvert);
|
await Downloader.DownloadPlaylistOnly(System.Web.HttpUtility.UrlDecode(args["Id"]), Resolution.NoConvert);
|
||||||
|
@ -1043,6 +1125,41 @@ namespace TYTD
|
||||||
response.WithCode(HttpStatusCode.Created);
|
response.WithCode(HttpStatusCode.Created);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private static void Endpoints(HttpListenerRequest request, HttpListenerResponse response, Dictionary<string, string> arguments)
|
||||||
|
{
|
||||||
|
Func<string, string> get_color = (meth) =>
|
||||||
|
{
|
||||||
|
switch(meth)
|
||||||
|
{
|
||||||
|
case "GET":
|
||||||
|
return "red";
|
||||||
|
case "POST":
|
||||||
|
return "green";
|
||||||
|
case "PUT":
|
||||||
|
return "blue";
|
||||||
|
case "PATCH":
|
||||||
|
return "purple";
|
||||||
|
}
|
||||||
|
return "orange";
|
||||||
|
};
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.Append("<!DOCTYPE html><html><head><title>TYTD Endpoint Documentation</title></head><body><h1>TYTD Endpoint Documentation<h1>");
|
||||||
|
foreach(var g in Downloader.Endpoints)
|
||||||
|
{
|
||||||
|
builder.Append($"<h2>{g.Key}</h2><hr>");
|
||||||
|
foreach(var j in g.Value)
|
||||||
|
{
|
||||||
|
builder.Append($"<h3><font color=\"{get_color(j.Method)}\">{j.Method}</font> <a href=\"{GetServerRoot(request).TrimEnd('/') + j.Path}\">{j.Path}</a>");
|
||||||
|
builder.Append($"<p><i>{j.Description}</i></p><br>");
|
||||||
|
}
|
||||||
|
builder.Append("<br>");
|
||||||
|
|
||||||
|
}
|
||||||
|
builder.Append("</body></html>");
|
||||||
|
|
||||||
|
response.AsText(builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void Endpoint(HttpListenerRequest request, HttpListenerResponse response, Dictionary<string, string> arguments)
|
private static void Endpoint(HttpListenerRequest request, HttpListenerResponse response, Dictionary<string, string> arguments)
|
||||||
{
|
{
|
||||||
|
@ -1364,17 +1481,18 @@ namespace TYTD
|
||||||
response.WithCORS();
|
response.WithCORS();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
public static void RedirectIt(this HttpListenerRequest req,HttpListenerResponse resp)
|
public static void RedirectIt(this HttpListenerRequest req,HttpListenerResponse resp,string path="/")
|
||||||
{
|
{
|
||||||
if (req.Headers.AllKeys.Contains("ServerRoot"))
|
if (req.Headers.AllKeys.Contains("ServerRoot"))
|
||||||
{
|
{
|
||||||
resp.AsRedirect(req.Headers["ServerRoot"]);
|
resp.AsRedirect(req.Headers["ServerRoot"].TrimEnd('/') + path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
resp.AsRedirect("/");
|
resp.AsRedirect(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetServerRoot(HttpListenerRequest req)
|
public static string GetServerRoot(HttpListenerRequest req)
|
||||||
{
|
{
|
||||||
if(req.Headers.AllKeys.Contains("ServerRoot"))
|
if(req.Headers.AllKeys.Contains("ServerRoot"))
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
namespace TYTD.Api
|
||||||
|
{
|
||||||
|
public class Log
|
||||||
|
{
|
||||||
|
public Log()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,11 +16,52 @@ using YoutubeExplode.Playlists;
|
||||||
|
|
||||||
using Dasync.Collections;
|
using Dasync.Collections;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using SimpleHttp;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace TYTD.Server.Functions
|
namespace TYTD.Server.Functions
|
||||||
{
|
{
|
||||||
public class Downloader
|
public class Downloader
|
||||||
{
|
{
|
||||||
|
private static string HandleDescription(string desc)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
foreach(var ln in desc.Split('\n'))
|
||||||
|
{
|
||||||
|
|
||||||
|
builder.Append($" {ln}<br>");
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
public static Dictionary<string, List<(string Path, string Description, string Method)>> Endpoints = new Dictionary<string, List<(string Path, string Description, string Method)>>();
|
||||||
|
/// <summary>
|
||||||
|
/// Adds route while adding it to list for documentation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path.</param>
|
||||||
|
/// <param name="group">Group.</param>
|
||||||
|
/// <param name="description">Description. (you have to use WebUtility.HtmlEncode())</param>
|
||||||
|
/// <param name="act">action to call</param>
|
||||||
|
/// <param name="method">HTTP Method</param>
|
||||||
|
public static void RouteAdd(string path, string group, string description, HttpActionAsync act, string method = "GET")
|
||||||
|
{
|
||||||
|
|
||||||
|
Endpoints.AddToList(group, (WebUtility.HtmlEncode(path), HandleDescription(description), method));
|
||||||
|
//_endpoint.((WebUtility.HtmlEncode(path),description));
|
||||||
|
Route.Add(path, act, method);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Adds route while adding it to list for documentation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path.</param>
|
||||||
|
/// <param name="group">Group.</param>
|
||||||
|
/// <param name="description">Description. (you have to use WebUtility.HtmlEncode())</param>
|
||||||
|
/// <param name="act">action to call</param>
|
||||||
|
/// <param name="method">HTTP Method</param>
|
||||||
|
public static void RouteAdd(string path, string group, string description, HttpAction act, string method = "GET")
|
||||||
|
{
|
||||||
|
Endpoints.AddToList(group, (WebUtility.HtmlEncode(path), HandleDescription(description), method));
|
||||||
|
Route.Add(path, act, method);
|
||||||
|
}
|
||||||
public static List<SavedVideo> SearchFor(string str)
|
public static List<SavedVideo> SearchFor(string str)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,431 @@
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Hyperlinq;
|
||||||
|
//Uses nuget packages HyperLinq, SimpleHttp
|
||||||
|
namespace SimpleHttp
|
||||||
|
{
|
||||||
|
|
||||||
|
public delegate void HttpActionQuery(HttpListenerRequest req, HttpListenerResponse resp, Dictionary<string, List<string>> args);
|
||||||
|
public delegate Task HttpActionQueryAsync(HttpListenerRequest req, HttpListenerResponse resp, Dictionary<string, List<string>> args);
|
||||||
|
|
||||||
|
public static class RequestExtensionsQuery
|
||||||
|
{
|
||||||
|
|
||||||
|
public static void AsJson(this HttpListenerResponse resp, object respValue, string mime = "application/json")
|
||||||
|
{
|
||||||
|
resp.AsText(JsonConvert.SerializeObject(respValue), mime);
|
||||||
|
}
|
||||||
|
public static T ParseRawBodyJson<T>(this HttpListenerRequest req)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<T>(req.BodyAsString());
|
||||||
|
}
|
||||||
|
public static object ParseRawBodyJson(this HttpListenerRequest req)
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject(req.BodyAsString());
|
||||||
|
}
|
||||||
|
public static void AddToList<T, T2>(this Dictionary<T, List<T2>> dict, T key, T2 value)
|
||||||
|
{
|
||||||
|
if (dict.ContainsKey(key))
|
||||||
|
{
|
||||||
|
dict[key].Add(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<T2> items = new List<T2>();
|
||||||
|
items.Add(value);
|
||||||
|
dict.Add(key, items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static Dictionary<string, List<string>> GetQueryParams(this string parm)
|
||||||
|
{
|
||||||
|
Dictionary<string, List<string>> ls = new Dictionary<string, List<string>>();
|
||||||
|
|
||||||
|
string[] args = parm.Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
//name=value
|
||||||
|
string[] nvp = arg.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
if (nvp.Length == 2)
|
||||||
|
{
|
||||||
|
ls.AddToList(nvp[0], nvp[1]);
|
||||||
|
}
|
||||||
|
else if (nvp.Length == 1)
|
||||||
|
{
|
||||||
|
ls.AddToList(nvp[0], "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return ls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body of the request including form and multi-part form data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">HTTP request.</param>
|
||||||
|
/// <param name="args">Key-value pairs populated by the form data by this function.</param>
|
||||||
|
/// <returns>Name-file pair collection.</returns>
|
||||||
|
public static Dictionary<string, HttpFileQuery> ParseBody(this HttpListenerRequest request, Dictionary<string, List<string>> args)
|
||||||
|
{
|
||||||
|
return request.ParseBody(args, (n, fn, ct) => new MemoryStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses body of the request including form and multi-part form data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">HTTP request.</param>
|
||||||
|
/// <param name="args">Key-value pairs populated by the form data by this function.</param>
|
||||||
|
/// <param name="onFile">
|
||||||
|
/// Function called if a file is about to be parsed. The stream is attached to a corresponding <see cref="HttpFile"/>.
|
||||||
|
/// <para>By default, <see cref="MemoryStream"/> is used, but for large files, it is recommended to open <see cref="FileStream"/> directly.</para>
|
||||||
|
/// </param>
|
||||||
|
/// <returns>Name-file pair collection.</returns>
|
||||||
|
public static Dictionary<string, HttpFileQuery> ParseBody(this HttpListenerRequest request, Dictionary<string, List<string>> args, OnFile onFile)
|
||||||
|
{
|
||||||
|
if (request == null)
|
||||||
|
throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
|
if (args == null)
|
||||||
|
throw new ArgumentNullException(nameof(args));
|
||||||
|
|
||||||
|
if (onFile == null)
|
||||||
|
throw new ArgumentNullException(nameof(onFile));
|
||||||
|
|
||||||
|
|
||||||
|
var files = new Dictionary<string, HttpFileQuery>();
|
||||||
|
|
||||||
|
if (request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
ParseForm(request, args);
|
||||||
|
}
|
||||||
|
else if (request.ContentType.StartsWith("multipart/form-data", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
files = ParseMultipartForm(request, args, onFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new NotSupportedException("The body content-type is not supported.");
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
static bool ParseForm(HttpListenerRequest request, Dictionary<string, List<string>> args)
|
||||||
|
{
|
||||||
|
if (request.ContentType != "application/x-www-form-urlencoded")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var str = request.BodyAsString();
|
||||||
|
if (str == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var pair in str.Split('&'))
|
||||||
|
{
|
||||||
|
var nameValue = pair.Split('=');
|
||||||
|
if (nameValue.Length != (1 + 1))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
args.AddToList(nameValue[0], WebUtility.UrlDecode(nameValue[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string BodyAsString(this HttpListenerRequest request)
|
||||||
|
{
|
||||||
|
if (!request.HasEntityBody)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string str = null;
|
||||||
|
using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
|
||||||
|
{
|
||||||
|
str = reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
static Dictionary<string, HttpFileQuery> ParseMultipartForm(HttpListenerRequest request, Dictionary<string, List<string>> args, OnFile onFile)
|
||||||
|
{
|
||||||
|
if (request.ContentType.StartsWith("multipart/form-data") == false)
|
||||||
|
throw new InvalidDataException("Not 'multipart/form-data'.");
|
||||||
|
|
||||||
|
var boundary = Regex.Match(request.ContentType, "boundary=(.+)").Groups[1].Value;
|
||||||
|
boundary = "--" + boundary;
|
||||||
|
|
||||||
|
|
||||||
|
var files = new Dictionary<string, HttpFileQuery>();
|
||||||
|
var inputStream = new BufferedStream(request.InputStream);
|
||||||
|
|
||||||
|
parseUntillBoundaryEnd(inputStream, new MemoryStream(), boundary);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var (n, v, fn, ct) = parseSection(inputStream, "\r\n" + boundary, onFile);
|
||||||
|
if (String.IsNullOrEmpty(n)) break;
|
||||||
|
|
||||||
|
v.Position = 0;
|
||||||
|
if (!String.IsNullOrEmpty(fn))
|
||||||
|
files.Add(n, new HttpFileQuery(fn, v, ct));
|
||||||
|
else
|
||||||
|
args.AddToList(n, readAsString(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (string Name, Stream Value, string FileName, string ContentType) parseSection(Stream source, string boundary, OnFile onFile)
|
||||||
|
{
|
||||||
|
var (n, fn, ct) = readContentDisposition(source);
|
||||||
|
source.ReadByte(); source.ReadByte(); //\r\n (empty row)
|
||||||
|
|
||||||
|
var dst = String.IsNullOrEmpty(fn) ? new MemoryStream() : onFile(n, fn, ct);
|
||||||
|
if (dst == null)
|
||||||
|
throw new ArgumentException(nameof(onFile), "The on-file callback must return a stream.");
|
||||||
|
|
||||||
|
parseUntillBoundaryEnd(source, dst, boundary);
|
||||||
|
|
||||||
|
return (n, dst, fn, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (string Name, string FileName, string ContentType) readContentDisposition(Stream stream)
|
||||||
|
{
|
||||||
|
const string UTF_FNAME = "utf-8''";
|
||||||
|
|
||||||
|
var l = readLine(stream);
|
||||||
|
if (String.IsNullOrEmpty(l))
|
||||||
|
return (null, null, null);
|
||||||
|
|
||||||
|
//(regex matches are taken from NancyFX) and modified
|
||||||
|
var n = Regex.Match(l, @"name=""?(?<n>[^\""]*)").Groups["n"].Value;
|
||||||
|
var f = Regex.Match(l, @"filename\*?=""?(?<f>[^\"";]*)").Groups["f"]?.Value;
|
||||||
|
|
||||||
|
string cType = null;
|
||||||
|
if (!String.IsNullOrEmpty(f))
|
||||||
|
{
|
||||||
|
if (f.StartsWith(UTF_FNAME))
|
||||||
|
f = Uri.UnescapeDataString(f.Substring(UTF_FNAME.Length));
|
||||||
|
|
||||||
|
l = readLine(stream);
|
||||||
|
cType = Regex.Match(l, "Content-Type: (?<cType>.+)").Groups["cType"].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (n, f, cType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseUntillBoundaryEnd(Stream source, Stream destination, string boundary)
|
||||||
|
{
|
||||||
|
var checkBuffer = new byte[boundary.Length]; //for boundary checking
|
||||||
|
|
||||||
|
int b, i = 0;
|
||||||
|
while ((b = source.ReadByte()) != -1)
|
||||||
|
{
|
||||||
|
if (i == boundary.Length) //boundary found -> go to the end of line
|
||||||
|
{
|
||||||
|
if (b == '\n') break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b == boundary[i]) //start filling the check buffer
|
||||||
|
{
|
||||||
|
checkBuffer[i] = (byte)b;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var idx = 0;
|
||||||
|
while (idx < i) //write the buffer data to stream
|
||||||
|
{
|
||||||
|
destination.WriteByte(checkBuffer[idx]);
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
destination.WriteByte((byte)b); //write the current byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string readLine(Stream stream)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
int b;
|
||||||
|
while ((b = stream.ReadByte()) != -1 && b != '\n')
|
||||||
|
sb.Append((char)b);
|
||||||
|
|
||||||
|
if (sb.Length > 0 && sb[sb.Length - 1] == '\r')
|
||||||
|
sb.Remove(sb.Length - 1, 1);
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string readAsString(Stream stream)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
int b;
|
||||||
|
while ((b = stream.ReadByte()) != -1)
|
||||||
|
sb.Append((char)b);
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP file data container.
|
||||||
|
/// </summary>
|
||||||
|
public class HttpFileQuery : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates new HTTP file data container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">File name.</param>
|
||||||
|
/// <param name="value">Data.</param>
|
||||||
|
/// <param name="contentType">Content type.</param>
|
||||||
|
internal HttpFileQuery(string fileName, Stream value, string contentType)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
FileName = fileName;
|
||||||
|
ContentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the file.
|
||||||
|
/// </summary>
|
||||||
|
public string FileName { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data.
|
||||||
|
/// <para>If a stream is created <see cref="OnFile"/> it will be closed when this HttpFile object is disposed.</para>
|
||||||
|
/// </summary>
|
||||||
|
public Stream Value { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Content type.
|
||||||
|
/// </summary>
|
||||||
|
public string ContentType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the data into a file.
|
||||||
|
/// <para>Directory path will be auto created if does not exists.</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">File path with name.</param>
|
||||||
|
/// <param name="overwrite">True to overwrite the existing file, false otherwise.</param>
|
||||||
|
/// <returns>True if the file is saved/overwritten, false otherwise.</returns>
|
||||||
|
public bool Save(string fileName, bool overwrite = false)
|
||||||
|
{
|
||||||
|
if (File.Exists(Path.GetFullPath(fileName)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var dir = Path.GetDirectoryName(Path.GetFullPath(fileName));
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
Value.Position = 0;
|
||||||
|
using (var outStream = File.OpenWrite(fileName))
|
||||||
|
Value.CopyTo(outStream);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the current instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Value != null)
|
||||||
|
{
|
||||||
|
Value?.Dispose();
|
||||||
|
Value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the current instance.
|
||||||
|
/// </summary>
|
||||||
|
~HttpFileQuery()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static class RouteQuery
|
||||||
|
{
|
||||||
|
|
||||||
|
public static void AsHtml(this HttpListenerResponse resp, HElement element)
|
||||||
|
{
|
||||||
|
resp.AsText(element.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddQuery(string url, HttpActionQueryAsync httpActionQueryAsync, string method = "GET", string queryText = "query_parms")
|
||||||
|
{
|
||||||
|
Route.Add($"{url}{{{queryText}}}", async (req, resp, args) => {
|
||||||
|
string queryparms = args[queryText];
|
||||||
|
|
||||||
|
Dictionary<string, List<string>> str = new Dictionary<string, List<string>>();
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
if (arg.Key != queryText)
|
||||||
|
{
|
||||||
|
str.AddToList(arg.Key, arg.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var queryParm in queryparms.GetQueryParams())
|
||||||
|
{
|
||||||
|
foreach (var value in queryParm.Value)
|
||||||
|
{
|
||||||
|
str.AddToList(queryParm.Key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await httpActionQueryAsync(req, resp, str);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public static void AddQuery(string url, HttpActionQuery httpActionQuery, string method = "GET", string queryText = "query_parms")
|
||||||
|
{
|
||||||
|
Route.Add($"{url}{{{queryText}}}", (req, resp, args) => {
|
||||||
|
string queryparms = args[queryText];
|
||||||
|
Dictionary<string, List<string>> str = new Dictionary<string, List<string>>();
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
if (arg.Key != queryText)
|
||||||
|
{
|
||||||
|
str.AddToList(arg.Key, arg.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var queryParm in queryparms.GetQueryParams())
|
||||||
|
{
|
||||||
|
foreach (var value in queryParm.Value)
|
||||||
|
{
|
||||||
|
str.AddToList(queryParm.Key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpActionQuery(req, resp, str);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public static void AddQuery(string url, HttpActionAsync httpActionAsync, string method = "GET", string queryText = "query_parms")
|
||||||
|
{
|
||||||
|
Route.Add($"{url}{{{queryText}}}", async (req, resp, args) => {
|
||||||
|
string queryparms = args[queryText];
|
||||||
|
foreach (var queryParm in queryparms.GetQueryParams())
|
||||||
|
{
|
||||||
|
args.Add(queryParm.Key, queryParm.Value[0]);
|
||||||
|
}
|
||||||
|
await httpActionAsync(req, resp, args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public static void AddQuery(string url, HttpAction action, string method = "GET", string queryText = "query_parms")
|
||||||
|
{
|
||||||
|
Route.Add($"{url}{{{queryText}}}", (req, resp, args) =>
|
||||||
|
{
|
||||||
|
string queryparms = args[queryText];
|
||||||
|
foreach (var queryParm in queryparms.GetQueryParams())
|
||||||
|
{
|
||||||
|
args.Add(queryParm.Key, queryParm.Value[0]);
|
||||||
|
}
|
||||||
|
action(req, resp, args);
|
||||||
|
}, method);
|
||||||
|
Route.Add(url, action, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -105,6 +105,9 @@
|
||||||
<HintPath>..\packages\Simple-HTTP.1.0.6\lib\net47\SimpleHTTP.dll</HintPath>
|
<HintPath>..\packages\Simple-HTTP.1.0.6\lib\net47\SimpleHTTP.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="Hyperlinq">
|
||||||
|
<HintPath>..\packages\Hyperlinq.1.0.7\lib\net40-client\Hyperlinq.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="MyClass.cs" />
|
<Compile Include="MyClass.cs" />
|
||||||
|
@ -118,6 +121,7 @@
|
||||||
<Compile Include="Server\Functions\Downloader.cs" />
|
<Compile Include="Server\Functions\Downloader.cs" />
|
||||||
<Compile Include="Server\Functions\ffmpeg.cs" />
|
<Compile Include="Server\Functions\ffmpeg.cs" />
|
||||||
<Compile Include="Server\Models\SavedMedia.cs" />
|
<Compile Include="Server\Models\SavedMedia.cs" />
|
||||||
|
<Compile Include="SimpleHttpExtensions.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Server\" />
|
<Folder Include="Server\" />
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
||||||
6a4cb1f3341b0b875221333595f3f3db569f2e65
|
4109925d72e51f8e54758eee44e6f087ed4fc279
|
||||||
|
|
|
@ -391,3 +391,4 @@
|
||||||
/home/mike/tytd-server/TYTD.Api/obj/Release/TYTD.Api.csproj.CoreCompileInputs.cache
|
/home/mike/tytd-server/TYTD.Api/obj/Release/TYTD.Api.csproj.CoreCompileInputs.cache
|
||||||
/home/mike/tytd-server/TYTD.Api/obj/Release/TYTD.Api.csproj.CopyComplete
|
/home/mike/tytd-server/TYTD.Api/obj/Release/TYTD.Api.csproj.CopyComplete
|
||||||
/home/mike/tytd-server/TYTD.Api/obj/Release/TYTD.Api.dll
|
/home/mike/tytd-server/TYTD.Api/obj/Release/TYTD.Api.dll
|
||||||
|
/home/mike/tytd-server/TYTD.Api/bin/Release/Hyperlinq.dll
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -3,6 +3,7 @@
|
||||||
<package id="AngleSharp" version="0.16.1" targetFramework="net47" />
|
<package id="AngleSharp" version="0.16.1" targetFramework="net47" />
|
||||||
<package id="AsyncEnumerator" version="4.0.2" targetFramework="net47" />
|
<package id="AsyncEnumerator" version="4.0.2" targetFramework="net47" />
|
||||||
<package id="CookiesTxtParser" version="1.0.1" targetFramework="net47" />
|
<package id="CookiesTxtParser" version="1.0.1" targetFramework="net47" />
|
||||||
|
<package id="Hyperlinq" version="1.0.7" targetFramework="net47" />
|
||||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="6.0.0" targetFramework="net47" />
|
<package id="Microsoft.Bcl.AsyncInterfaces" version="6.0.0" targetFramework="net47" />
|
||||||
<package id="MimeTypesMap" version="1.0.8" targetFramework="net47" />
|
<package id="MimeTypesMap" version="1.0.8" targetFramework="net47" />
|
||||||
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net47" />
|
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net47" />
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
[]
|
|
@ -0,0 +1 @@
|
||||||
|
[]
|
Binary file not shown.
|
@ -1 +1 @@
|
||||||
3a0ad01444a7c622c3bd15b537d0dc7e888f6685
|
8f38b4bfd3e4101cea207f521c4ab951a53995e7
|
||||||
|
|
|
@ -706,3 +706,4 @@
|
||||||
/home/mike/tytd-server/obj/x86/Release/youtube-downloader.exe
|
/home/mike/tytd-server/obj/x86/Release/youtube-downloader.exe
|
||||||
/home/mike/tytd-server/bin/Release/System.Text.Encoding.CodePages.xml
|
/home/mike/tytd-server/bin/Release/System.Text.Encoding.CodePages.xml
|
||||||
/home/mike/tytd-server/bin/Release/PlaylistsNET.dll
|
/home/mike/tytd-server/bin/Release/PlaylistsNET.dll
|
||||||
|
/home/mike/tytd-server/bin/Release/Hyperlinq.dll
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -3,6 +3,7 @@
|
||||||
<package id="AngleSharp" version="0.16.1" targetFramework="net47" />
|
<package id="AngleSharp" version="0.16.1" targetFramework="net47" />
|
||||||
<package id="AsyncEnumerator" version="4.0.2" targetFramework="net47" />
|
<package id="AsyncEnumerator" version="4.0.2" targetFramework="net47" />
|
||||||
<package id="CookiesTxtParser" version="1.0.1" targetFramework="net47" />
|
<package id="CookiesTxtParser" version="1.0.1" targetFramework="net47" />
|
||||||
|
<package id="Hyperlinq" version="1.0.7" targetFramework="net47" />
|
||||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="6.0.0" targetFramework="net47" />
|
<package id="Microsoft.Bcl.AsyncInterfaces" version="6.0.0" targetFramework="net47" />
|
||||||
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net47" />
|
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net47" />
|
||||||
<package id="MimeTypesMap" version="1.0.8" targetFramework="net47" />
|
<package id="MimeTypesMap" version="1.0.8" targetFramework="net47" />
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"contentHash": "ZirloBlRv604Cg4ObTRR5mdPnYThe6E1rlkEGUX9O1e4QvoNr8MFhYO/QyCjetByZPICCTT+U/XBJ2Um39CrRQ==",
|
||||||
|
"source": "https://api.nuget.org/v3/index.json"
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,405 @@
|
||||||
|
# YoutubeExplode
|
||||||
|
|
||||||
|
[![Build](https://github.com/Tyrrrz/YoutubeExplode/workflows/main/badge.svg?branch=master)](https://github.com/Tyrrrz/YoutubeExplode/actions)
|
||||||
|
[![Coverage](https://codecov.io/gh/Tyrrrz/YoutubeExplode/branch/master/graph/badge.svg)](https://codecov.io/gh/Tyrrrz/YoutubeExplode)
|
||||||
|
[![Version](https://img.shields.io/nuget/v/YoutubeExplode.svg)](https://nuget.org/packages/YoutubeExplode)
|
||||||
|
[![Downloads](https://img.shields.io/nuget/dt/YoutubeExplode.svg)](https://nuget.org/packages/YoutubeExplode)
|
||||||
|
[![Discord](https://img.shields.io/discord/869237470565392384?label=discord)](https://discord.gg/2SUWKFnHSm)
|
||||||
|
[![Donate](https://img.shields.io/badge/donate-$$$-purple.svg)](https://tyrrrz.me/donate)
|
||||||
|
|
||||||
|
⚠️ **Project status: maintenance mode**. [What does it mean?](https://github.com/Tyrrrz/.github/blob/master/docs/project-status.md)
|
||||||
|
|
||||||
|
**YoutubeExplode** is a library that provides an interface to query metadata of YouTube videos, playlists and channels, as well as to resolve and download video streams and closed caption tracks.
|
||||||
|
Behind a layer of abstraction, the library parses raw page content and uses reverse-engineered requests to retrieve information.
|
||||||
|
As it doesn't rely on the official API, there's also no need for an API key and there are no usage quotas.
|
||||||
|
|
||||||
|
✨ This library is used in [**YoutubeDownloader**](https://github.com/Tyrrrz/YoutubeDownloader) -- a desktop application for downloading YouTube videos.
|
||||||
|
|
||||||
|
💬 **If you want to chat, join my [Discord server](https://discord.gg/2SUWKFnHSm)**.
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
- 📦 [NuGet](https://nuget.org/packages/YoutubeExplode): `dotnet add package YoutubeExplode` (**main package**)
|
||||||
|
- 📦 [NuGet](https://nuget.org/packages/YoutubeExplode.Converter): `dotnet add package YoutubeExplode.Converter` (**FFmpeg integration**)
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
![demo](.screenshots/demo.png)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
**YoutubeExplode** exposes its functionality through a single entry point -- the `YoutubeClient` class.
|
||||||
|
Create an instance of this class and use the provided operations on `Videos`, `Playlists`, `Channels`, and `Search` properties to send requests.
|
||||||
|
|
||||||
|
### Videos
|
||||||
|
|
||||||
|
#### Retrieving video metadata
|
||||||
|
|
||||||
|
To retrieve metadata associated with a YouTube video, call `Videos.GetAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
// You can specify both video ID or URL
|
||||||
|
var video = await youtube.Videos.GetAsync("https://youtube.com/watch?v=u_yIGGhubZs");
|
||||||
|
|
||||||
|
var title = video.Title; // "Collections - Blender 2.80 Fundamentals"
|
||||||
|
var author = video.Author.Title; // "Blender"
|
||||||
|
var duration = video.Duration; // 00:07:20
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Downloading video streams
|
||||||
|
|
||||||
|
Every YouTube video has a number of streams available, differing in containers, video quality, bitrate, framerate, and other properties.
|
||||||
|
Additionally, depending on the content of the stream, the streams are further divided into 3 categories:
|
||||||
|
|
||||||
|
- Muxed streams -- contain both video and audio
|
||||||
|
- Audio-only streams -- contain only audio
|
||||||
|
- Video-only streams -- contain only video
|
||||||
|
|
||||||
|
You can request the manifest that lists all available streams for a particular video by calling `Videos.Streams.GetManifestAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
var streamManifest = await youtube.Videos.Streams.GetManifestAsync("u_yIGGhubZs");
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you get the manifest, you can filter through the streams and select the ones you're interested in:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Videos.Streams;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Get highest quality muxed stream
|
||||||
|
var streamInfo = streamManifest.GetMuxedStreams().GetWithHighestVideoQuality();
|
||||||
|
|
||||||
|
// ...or highest bitrate audio-only stream
|
||||||
|
var streamInfo = streamManifest.GetAudioOnlyStreams().GetWithHighestBitrate();
|
||||||
|
|
||||||
|
// ...or highest quality MP4 video-only stream
|
||||||
|
var streamInfo = streamManifest
|
||||||
|
.GetVideoOnlyStreams()
|
||||||
|
.Where(s => s.Container == Container.Mp4)
|
||||||
|
.GetWithHighestVideoQuality()
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, you can resolve the actual stream represented by the specified metadata using `Videos.Streams.GetAsync(...)` or download it directly to a file with `Videos.Streams.DownloadAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Get the actual stream
|
||||||
|
var stream = await youtube.Videos.Streams.GetAsync(streamInfo);
|
||||||
|
|
||||||
|
// Download the stream to a file
|
||||||
|
await youtube.Videos.Streams.DownloadAsync(streamInfo, $"video.{streamInfo.Container}");
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠ Muxed streams contain both audio and video, but these streams are very limited in quality (up to 720p30).
|
||||||
|
To download video in the highest available quality, you need to resolve the best audio-only and video-only streams separately and then mux them together.
|
||||||
|
This can be accomplished by using the **YoutubeExplode.Converter** package (see below).
|
||||||
|
|
||||||
|
#### Downloading video with muxing or conversion
|
||||||
|
|
||||||
|
> ⚠ Downloading with muxing or conversion requires [YoutubeExplode.Converter](https://nuget.org/packages/YoutubeExplode.Converter).
|
||||||
|
|
||||||
|
> ⚠ This package also relies on [FFmpeg](https://ffmpeg.org) CLI, which can be downloaded [here](https://ffbinaries.com/downloads).
|
||||||
|
Ensure that the FFmpeg binary is located in your application's probe directory or on the system's `PATH`, or use one of the overloads to provide a custom location directly.
|
||||||
|
|
||||||
|
You can download a video with muxing or conversion through one of the extension methods provided on `VideoClient`.
|
||||||
|
For example, to download a video in the specified format using highest quality streams, simply call `DownloadAsync(...)` with the video ID and the destination file path:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Converter;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
await youtube.Videos.DownloadAsync("https://youtube.com/watch?v=u_yIGGhubZs", "video.mp4");
|
||||||
|
```
|
||||||
|
|
||||||
|
Under the hood, this resolves the video's media streams and selects the best candidates based on format, bitrate, quality, and framerate.
|
||||||
|
If the specified output format is a known audio-only container (e.g. `mp3` or `ogg`) then only the audio stream is downloaded.
|
||||||
|
|
||||||
|
> ⚠ Stream muxing is a CPU-heavy process.
|
||||||
|
> You can reduce resource usage and execution time by using streams that don't require transcoding to the output format (e.g. `mp4` audio/video streams for `mp4` output format).
|
||||||
|
> Currently, YouTube only provides adaptive streams in `mp4` or `webm` containers, with highest quality video streams (e.g. 4K) only available in `webm`.
|
||||||
|
|
||||||
|
To configure various aspects related to the conversion process, use one of the overloads of `DownloadAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Converter;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
await youtube.Videos.DownloadAsync("https://youtube.com/watch?v=u_yIGGhubZs", "video.mp4", o => o
|
||||||
|
.SetFormat("webm") // override format
|
||||||
|
.SetPreset(ConversionPreset.UltraFast) // change preset
|
||||||
|
.SetFFmpegPath("path/to/ffmpeg") // custom FFmpeg location
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need precise control over which streams are used for muxing, you can also provide them yourself:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Videos.Streams;
|
||||||
|
using YoutubeExplode.Converter;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
// Get stream manifest
|
||||||
|
var streamManifest = await youtube.Videos.Streams.GetManifestAsync("u_yIGGhubZs");
|
||||||
|
|
||||||
|
// Select streams (1080p60 / highest bitrate audio)
|
||||||
|
var audioStreamInfo = streamManifest.GetAudioStreams().GetWithHighestBitrate();
|
||||||
|
var videoStreamInfo = streamManifest.GetVideoStreams().First(s => s.VideoQuality.Label == "1080p60");
|
||||||
|
var streamInfos = new IStreamInfo[] { audioStreamInfo, videoStreamInfo };
|
||||||
|
|
||||||
|
// Download and process them into one file
|
||||||
|
await youtube.Videos.DownloadAsync(streamInfos, new ConversionRequestBuilder("video.mp4").Build());
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Downloading closed captions
|
||||||
|
|
||||||
|
Closed captions can be downloaded in a similar way to media streams.
|
||||||
|
To get the list of available closed caption tracks, call `Videos.ClosedCaptions.GetManifestAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
var trackManifest = await youtube.Videos.ClosedCaptions.GetManifestAsync("u_yIGGhubZs");
|
||||||
|
```
|
||||||
|
|
||||||
|
Then retrieve metadata for a particular track:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Find closed caption track in English
|
||||||
|
var trackInfo = trackManifest.GetByLanguage("en");
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, use `Videos.ClosedCaptions.GetAsync(...)` to get the actual content of the track:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ...
|
||||||
|
|
||||||
|
var track = await youtube.Videos.ClosedCaptions.GetAsync(trackInfo);
|
||||||
|
|
||||||
|
// Get the caption displayed at 0:35
|
||||||
|
var caption = track.GetByTime(TimeSpan.FromSeconds(35));
|
||||||
|
var text = caption.Text; // "collection acts as the parent collection"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also download the closed caption track in SRT file format with `Videos.ClosedCaptions.DownloadAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ...
|
||||||
|
|
||||||
|
await youtube.Videos.ClosedCaptions.DownloadAsync(trackInfo, "cc_track.srt");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Playlists
|
||||||
|
|
||||||
|
#### Retrieving playlist metadata
|
||||||
|
|
||||||
|
You can get metadata associated with a YouTube playlist by calling `Playlists.GetAsync(...)` method:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
var playlist = await youtube.Playlists.GetAsync("PLa1F2ddGya_-UvuAqHAksYnB0qL9yWDO6");
|
||||||
|
|
||||||
|
var title = playlist.Title; // "First Steps - Blender 2.80 Fundamentals"
|
||||||
|
var author = playlist.Author.Title; // "Blender"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Getting videos included in a playlist
|
||||||
|
|
||||||
|
To get the videos included in a playlist, call `Playlists.GetVideosAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Common;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
// Get all playlist videos
|
||||||
|
var videos = await youtube.Playlists.GetVideosAsync("PLa1F2ddGya_-UvuAqHAksYnB0qL9yWDO6");
|
||||||
|
|
||||||
|
// Get only the first 20 playlist videos
|
||||||
|
var videosSubset = await youtube.Playlists
|
||||||
|
.GetVideosAsync(playlist.Id)
|
||||||
|
.CollectAsync(20);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also enumerate videos lazily without waiting for the whole list to load:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
await foreach (var video in youtube.Playlists.GetVideosAsync("PLa1F2ddGya_-UvuAqHAksYnB0qL9yWDO6"))
|
||||||
|
{
|
||||||
|
var title = video.Title;
|
||||||
|
var author = video.Author;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need precise control over how many requests you send to YouTube, use `Playlists.GetVideoBatchesAsync(...)` which returns videos wrapped in batches:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
// Each batch corresponds to one request
|
||||||
|
await foreach (var batch in youtube.Playlists.GetVideoBatchesAsync("PLa1F2ddGya_-UvuAqHAksYnB0qL9yWDO6"))
|
||||||
|
{
|
||||||
|
foreach (var video in batch.Items)
|
||||||
|
{
|
||||||
|
var title = video.Title;
|
||||||
|
var author = video.Author;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channels
|
||||||
|
|
||||||
|
#### Retrieving channel metadata
|
||||||
|
|
||||||
|
You can get metadata associated with a YouTube channel by calling `Channels.GetAsync(...)` method:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
var channel = await youtube.Channels.GetAsync("UCSMOQeBJ2RAnuFungnQOxLg");
|
||||||
|
|
||||||
|
var title = channel.Title; // "Blender"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also get channel metadata by username with `Channels.GetByUserAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
var channel = await youtube.Channels.GetByUserAsync("Blender");
|
||||||
|
|
||||||
|
var id = channel.Id; // "UCSMOQeBJ2RAnuFungnQOxLg"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Getting channel uploads
|
||||||
|
|
||||||
|
To get a list of videos uploaded by a channel, call `Channels.GetUploadsAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Common;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
var videos = await youtube.Channels.GetUploadsAsync("UCSMOQeBJ2RAnuFungnQOxLg");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Searching
|
||||||
|
|
||||||
|
You can execute a search query and get its results by calling `Search.GetResultsAsync(...)`.
|
||||||
|
Each result may represent either a video, a playlist, or a channel, so you need to apply pattern matching to handle the corresponding cases:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
await foreach (var result in youtube.Search.GetResultsAsync("blender tutorials"))
|
||||||
|
{
|
||||||
|
// Use pattern matching to handle different results (videos, playlists, channels)
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case VideoSearchResult video:
|
||||||
|
{
|
||||||
|
var id = video.Id;
|
||||||
|
var title = video.Title;
|
||||||
|
var duration = video.Duration;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PlaylistSearchResult playlist:
|
||||||
|
{
|
||||||
|
var id = playlist.Id;
|
||||||
|
var title = playlist.Title;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ChannelSearchResult channel:
|
||||||
|
{
|
||||||
|
var id = channel.Id;
|
||||||
|
var title = channel.Title;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To limit results to a specific type, use `Search.GetVideosAsync(...)`, `Search.GetPlaylistsAsync(...)`, or `Search.GetChannelsAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Common;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
var videos = await youtube.Search.GetVideosAsync("blender tutorials");
|
||||||
|
var playlists = await youtube.Search.GetPlaylistsAsync("blender tutorials");
|
||||||
|
var channels = await youtube.Search.GetChannelsAsync("blender tutorials");
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly to playlists, you can also enumerate results in batches by calling `Search.GetResultBatchesAsync(...)`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using YoutubeExplode;
|
||||||
|
|
||||||
|
var youtube = new YoutubeClient();
|
||||||
|
|
||||||
|
// Each batch corresponds to one request
|
||||||
|
await foreach (var batch in youtube.Search.GetResultBatchesAsync("blender tutorials"))
|
||||||
|
{
|
||||||
|
foreach (var result in batch.Items)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case VideoSearchResult videoResult:
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
case PlaylistSearchResult playlistResult:
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
case ChannelSearchResult channelResult:
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Etymology
|
||||||
|
|
||||||
|
The "Explode" in **YoutubeExplode** comes from the name of a PHP function that splits up strings, [`explode()`](https://www.php.net/manual/en/function.explode.php). When I was just starting development on this library, most of the reference source code I read was written in PHP, hence the inspiration for the name.
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
namespace youtubedownloaderapi
|
||||||
|
{
|
||||||
|
public class MyClass
|
||||||
|
{
|
||||||
|
public MyClass()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
// Information about this assembly is defined by the following attributes.
|
||||||
|
// Change them to the values specific to your project.
|
||||||
|
|
||||||
|
[assembly: AssemblyTitle("youtube-downloader-api")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("")]
|
||||||
|
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||||
|
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||||
|
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion("1.0.*")]
|
||||||
|
|
||||||
|
// The following attributes are used to specify the signing key for the assembly,
|
||||||
|
// if desired. See the Mono documentation for more information about signing.
|
||||||
|
|
||||||
|
//[assembly: AssemblyDelaySign(false)]
|
||||||
|
//[assembly: AssemblyKeyFile("")]
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
namespace youtubedownloaderapi
|
||||||
|
{
|
||||||
|
public class TYTD
|
||||||
|
{
|
||||||
|
public TYTD()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{53A600E2-E063-4137-BDE0-38348FC1B748}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<RootNamespace>youtubedownloaderapi</RootNamespace>
|
||||||
|
<AssemblyName>youtube-downloader-api</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<ConsolePause>false</ConsolePause>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release</OutputPath>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<ConsolePause>false</ConsolePause>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="MyClass.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="TYTD.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Default' ">
|
||||||
|
<StartAction>Project</StartAction>
|
||||||
|
<ConsolePause>true</ConsolePause>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
|
@ -115,6 +115,9 @@
|
||||||
<Reference Include="PlaylistsNET">
|
<Reference Include="PlaylistsNET">
|
||||||
<HintPath>packages\PlaylistsNET.1.1.3\lib\net45\PlaylistsNET.dll</HintPath>
|
<HintPath>packages\PlaylistsNET.1.1.3\lib\net45\PlaylistsNET.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="Hyperlinq">
|
||||||
|
<HintPath>packages\Hyperlinq.1.0.7\lib\net40-client\Hyperlinq.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
|
|
Loading…
Reference in New Issue