Release
This commit is contained in:
parent
d972d03e7f
commit
4ef759a9d1
|
@ -0,0 +1,146 @@
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
|
||||||
|
[Dd]ebug/
|
||||||
|
[Rr]elease/
|
||||||
|
x64/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.log
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.Publish.xml
|
||||||
|
*.pubxml
|
||||||
|
*.azurePubxml
|
||||||
|
|
||||||
|
# NuGet Packages Directory
|
||||||
|
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||||
|
packages/
|
||||||
|
## TODO: If the tool you use requires repositories.config, also uncomment the next line
|
||||||
|
!packages/repositories.config
|
||||||
|
|
||||||
|
# Windows Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Windows Store app package directory
|
||||||
|
AppPackages/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
sql/
|
||||||
|
*.Cache
|
||||||
|
ClientBin/
|
||||||
|
[Ss]tyle[Cc]op.*
|
||||||
|
![Ss]tyle[Cc]op.targets
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
|
||||||
|
*.publishsettings
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file to a newer
|
||||||
|
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
App_Data/*.mdf
|
||||||
|
App_Data/*.ldf
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Windows detritus
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Mac desktop service store files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
_NCrunch*
|
||||||
|
|
||||||
|
Tesses.YouTubeDownloader.Net6/AudioOnly/
|
||||||
|
Tesses.YouTubeDownloader.Net6/VideoOnly/
|
||||||
|
Tesses.YouTubeDownloader.Net6/Muxed/
|
||||||
|
Tesses.YouTubeDownloader.Net6/Info/
|
||||||
|
Tesses.YouTubeDownloader.Net6/Thumbnails/
|
||||||
|
Tesses.YouTubeDownloader.Net6/StreamInfo/
|
||||||
|
Tesses.YouTubeDownloader.Net6/PreMuxed/
|
||||||
|
Tesses.YouTubeDownloader.Net6/config/
|
||||||
|
Tesses.YouTubeDownloader.Net6/Playlist/
|
||||||
|
Tesses.YouTubeDownloader.Net6/Channel/
|
||||||
|
Tesses.YouTubeDownloader.Net6/Subscriptions/
|
||||||
|
push
|
|
@ -0,0 +1,16 @@
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
RUN git clone https://gitlab.tesses.net/tesses50/tytd_proxy .
|
||||||
|
WORKDIR /app/TYTDProxy
|
||||||
|
# Restore as distinct layers
|
||||||
|
RUN dotnet restore
|
||||||
|
# Build and publish a release
|
||||||
|
RUN dotnet publish -c Release -o /app/out
|
||||||
|
|
||||||
|
# Build runtime image
|
||||||
|
FROM mcr.microsoft.com/dotnet/runtime:6.0
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 3252
|
||||||
|
COPY --from=build-env /app/out .
|
||||||
|
ENTRYPOINT ["dotnet", "TYTDProxy.dll","--docker"]
|
|
@ -0,0 +1,24 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Darko Jurić
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
Licensing for JSON.NET dependency also applies: https://www.newtonsoft.com/json
|
||||||
|
Licensing for SimpleHttp also applies: https://github.com/dajuric/simple-http
|
|
@ -0,0 +1,183 @@
|
||||||
|
using System.Net;
|
||||||
|
using Tesses.WebServer;
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Search;
|
||||||
|
using YoutubeExplode.Videos;
|
||||||
|
using YoutubeExplode.Videos.Streams;
|
||||||
|
public class TYTDProxy
|
||||||
|
{
|
||||||
|
static YoutubeClient Youtube = new YoutubeClient();
|
||||||
|
public static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
|
||||||
|
using (var token = new CancellationTokenSource())
|
||||||
|
{
|
||||||
|
Console.CancelKeyPress += (sender,e)=>{
|
||||||
|
token.Cancel();
|
||||||
|
};
|
||||||
|
StaticServer static_server=new StaticServer("WebSite");
|
||||||
|
RouteServer api_server=new RouteServer();
|
||||||
|
RouteApi(api_server);
|
||||||
|
MountableServer mountable_server=new MountableServer(static_server);
|
||||||
|
mountable_server.Mount("/api/",api_server);
|
||||||
|
|
||||||
|
HttpServerListener listener=new HttpServerListener(new System.Net.IPEndPoint(System.Net.IPAddress.Any,3253),mountable_server);
|
||||||
|
await listener.ListenAsync(token.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RouteApi(RouteServer api)
|
||||||
|
{
|
||||||
|
api.Add("/Video.json",Video);
|
||||||
|
api.Add("/Video.json",Video,"POST");
|
||||||
|
api.Add("/PreMuxed",PreMuxed);
|
||||||
|
api.Add("/VideoOnly",VideoOnly);
|
||||||
|
api.Add("/AudioOnly",AudioOnly);
|
||||||
|
api.Add("/PreMuxed",PreMuxed,"POST");
|
||||||
|
api.Add("/VideoOnly",VideoOnly,"POST");
|
||||||
|
api.Add("/AudioOnly",AudioOnly,"POST");
|
||||||
|
api.Add("/DownloadYTStream",DownloadYTStream,"POST");
|
||||||
|
api.Add("/Search.json",Search,"POST");
|
||||||
|
api.Add("/Search.json",Search);
|
||||||
|
}
|
||||||
|
public class StreamResult
|
||||||
|
{
|
||||||
|
public StreamResult(IStreamInfo info)
|
||||||
|
{
|
||||||
|
url = info.Url;
|
||||||
|
ext=info.Container.Name;
|
||||||
|
size = info.Size.Bytes;
|
||||||
|
}
|
||||||
|
public string url {get;set;}
|
||||||
|
|
||||||
|
public string ext {get;set;}
|
||||||
|
|
||||||
|
public long size {get;set;}
|
||||||
|
}
|
||||||
|
public class VideoResult
|
||||||
|
{
|
||||||
|
public VideoResult(StreamResult muxed,StreamResult video_only,StreamResult audio_only,SavedVideo video)
|
||||||
|
{
|
||||||
|
this.muxed=muxed;
|
||||||
|
this.video_only=video_only;
|
||||||
|
this.audio_only = audio_only;
|
||||||
|
this.video_info=video;
|
||||||
|
}
|
||||||
|
public StreamResult muxed {get;set;}
|
||||||
|
|
||||||
|
public StreamResult video_only {get;set;}
|
||||||
|
|
||||||
|
public StreamResult audio_only {get;set;}
|
||||||
|
|
||||||
|
public SavedVideo video_info {get;set;}
|
||||||
|
}
|
||||||
|
public static async Task Search(ServerContext ctx)
|
||||||
|
{
|
||||||
|
string q;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("q",out q)){
|
||||||
|
if(ctx.Method == "POST") ctx.ParseBody();
|
||||||
|
if(ctx.Method != "POST") q=WebUtility.UrlDecode(q);
|
||||||
|
{
|
||||||
|
List<VideoSearchResult> searchResults=new List<VideoSearchResult>();
|
||||||
|
await foreach(var item in Youtube.Search.GetVideosAsync(q))
|
||||||
|
{
|
||||||
|
searchResults.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.SendJsonAsync(searchResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task PreMuxed(ServerContext ctx)
|
||||||
|
{
|
||||||
|
string v;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("v",out v)){
|
||||||
|
if(ctx.Method == "POST") ctx.ParseBody();
|
||||||
|
if(ctx.Method != "POST") v=WebUtility.UrlDecode(v);
|
||||||
|
var vid=await Youtube.Videos.GetAsync(v);
|
||||||
|
var res= await Youtube.Videos.Streams.GetManifestAsync(v);
|
||||||
|
var bestMuxed=res.GetMuxedStreams().GetWithHighestVideoQuality();
|
||||||
|
System.Net.Mime.ContentDisposition contentDisposition=new System.Net.Mime.ContentDisposition();
|
||||||
|
contentDisposition.FileName = vid.Title;
|
||||||
|
await ctx.SendStreamAsync(await Youtube.Videos.Streams.GetAsync(bestMuxed),$"file.{bestMuxed.Container.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task VideoOnly(ServerContext ctx)
|
||||||
|
{
|
||||||
|
string v;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("v",out v)){
|
||||||
|
if(ctx.Method == "POST") ctx.ParseBody();
|
||||||
|
if(ctx.Method != "POST") v=WebUtility.UrlDecode(v);
|
||||||
|
var vid=await Youtube.Videos.GetAsync(v);
|
||||||
|
var res= await Youtube.Videos.Streams.GetManifestAsync(v);
|
||||||
|
var bestVideoOnly=res.GetVideoOnlyStreams().GetWithHighestVideoQuality();
|
||||||
|
System.Net.Mime.ContentDisposition contentDisposition=new System.Net.Mime.ContentDisposition();
|
||||||
|
contentDisposition.FileName = vid.Title;
|
||||||
|
await ctx.SendStreamAsync(await Youtube.Videos.Streams.GetAsync(bestVideoOnly),$"file.{bestVideoOnly.Container.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task AudioOnly(ServerContext ctx)
|
||||||
|
{
|
||||||
|
string v;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("v",out v)){
|
||||||
|
if(ctx.Method == "POST") ctx.ParseBody();
|
||||||
|
if(ctx.Method != "POST") v=WebUtility.UrlDecode(v);
|
||||||
|
var vid=await Youtube.Videos.GetAsync(v);
|
||||||
|
var res= await Youtube.Videos.Streams.GetManifestAsync(v);
|
||||||
|
var bestAudioOnly=res.GetAudioOnlyStreams().GetWithHighestBitrate();
|
||||||
|
System.Net.Mime.ContentDisposition contentDisposition=new System.Net.Mime.ContentDisposition();
|
||||||
|
contentDisposition.FileName = vid.Title;
|
||||||
|
await ctx.SendStreamAsync(await Youtube.Videos.Streams.GetAsync(bestAudioOnly),$"file.{bestAudioOnly.Container.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class DummyStr : IStreamInfo
|
||||||
|
{
|
||||||
|
public DummyStr(string url,long len)
|
||||||
|
{
|
||||||
|
this.url = url;
|
||||||
|
this.len=len;
|
||||||
|
|
||||||
|
}
|
||||||
|
long len;
|
||||||
|
string url;
|
||||||
|
public string Url => url;
|
||||||
|
|
||||||
|
public Container Container => Container.Mp4;
|
||||||
|
public FileSize Size => new FileSize(len);
|
||||||
|
|
||||||
|
public Bitrate Bitrate => new Bitrate(420000);
|
||||||
|
}
|
||||||
|
public static async Task DownloadYTStream(ServerContext ctx)
|
||||||
|
{
|
||||||
|
string v;
|
||||||
|
string lenTxt;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("url",out v)){
|
||||||
|
if(ctx.QueryParams.TryGetFirst("flen",out lenTxt)){
|
||||||
|
if(ctx.Method == "POST") ctx.ParseBody();
|
||||||
|
if(ctx.Method != "POST") v=WebUtility.UrlDecode(v);
|
||||||
|
long len;
|
||||||
|
if(long.TryParse(lenTxt,out len)){
|
||||||
|
|
||||||
|
await ctx.SendStreamAsync(await Youtube.Videos.Streams.GetAsync(new DummyStr(v,len)),HeyRed.Mime.MimeTypesMap.GetMimeType("file.bin"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async Task Video(ServerContext ctx)
|
||||||
|
{
|
||||||
|
string v;
|
||||||
|
if(ctx.QueryParams.TryGetFirst("v",out v)){
|
||||||
|
if(ctx.Method == "POST") ctx.ParseBody();
|
||||||
|
if(ctx.Method != "POST") v=WebUtility.UrlDecode(v);
|
||||||
|
|
||||||
|
var res= await Youtube.Videos.Streams.GetManifestAsync(v);
|
||||||
|
var bestMuxed=new StreamResult(res.GetMuxedStreams().GetWithHighestVideoQuality());
|
||||||
|
var bestVideoOnly=new StreamResult(res.GetVideoOnlyStreams().GetWithHighestVideoQuality());
|
||||||
|
var bestAudioOnly=new StreamResult(res.GetAudioOnlyStreams().GetWithHighestBitrate());
|
||||||
|
SavedVideo video = new SavedVideo(await Youtube.Videos.GetAsync(v));
|
||||||
|
VideoResult result = new VideoResult(bestMuxed,bestVideoOnly,bestAudioOnly,video);
|
||||||
|
await ctx.SendJsonAsync(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System.Text;
|
||||||
|
using YoutubeExplode.Common;
|
||||||
|
using YoutubeExplode.Videos;
|
||||||
|
|
||||||
|
public class SavedVideo
|
||||||
|
{
|
||||||
|
public SavedVideo()
|
||||||
|
{
|
||||||
|
Id = "";
|
||||||
|
Title = "";
|
||||||
|
AuthorChannelId = "";
|
||||||
|
AuthorTitle = "";
|
||||||
|
Description = "";
|
||||||
|
Keywords = new string[0];
|
||||||
|
Likes = 0;
|
||||||
|
Dislikes = 0;
|
||||||
|
Views = 0;
|
||||||
|
Duration = TimeSpan.Zero;
|
||||||
|
UploadDate=new DateTime(1992,8,20);
|
||||||
|
AddDate=DateTime.Now;
|
||||||
|
LegacyVideo=false;
|
||||||
|
}
|
||||||
|
public SavedVideo(Video video)
|
||||||
|
{
|
||||||
|
Id=video.Id;
|
||||||
|
Title = video.Title;
|
||||||
|
AuthorChannelId = video.Author.ChannelId;
|
||||||
|
AuthorTitle = video.Author.ChannelTitle;
|
||||||
|
Description = video.Description;
|
||||||
|
Keywords=video.Keywords.ToArray();
|
||||||
|
Likes=video.Engagement.LikeCount;
|
||||||
|
Dislikes = video.Engagement.DislikeCount;
|
||||||
|
Views = video.Engagement.ViewCount;
|
||||||
|
Duration = video.Duration.Value;
|
||||||
|
UploadDate = video.UploadDate.DateTime;
|
||||||
|
AddDate=DateTime.Now;
|
||||||
|
LegacyVideo=false;
|
||||||
|
}
|
||||||
|
public bool LegacyVideo {get;set;}
|
||||||
|
|
||||||
|
|
||||||
|
public Video ToVideo()
|
||||||
|
{
|
||||||
|
List<Thumbnail> thumbnails=new List<Thumbnail>();
|
||||||
|
thumbnails.Add(new Thumbnail($"https://s.ytimg.com/vi/{Id}/default.jpg",new YoutubeExplode.Common.Resolution(120,90)));
|
||||||
|
thumbnails.Add(new Thumbnail($"https://s.ytimg.com/vi/{Id}/hqdefault.jpg",new YoutubeExplode.Common.Resolution(480,360)));
|
||||||
|
thumbnails.Add(new Thumbnail($"https://s.ytimg.com/vi/{Id}/mqdefault.jpg",new YoutubeExplode.Common.Resolution(320,180)));
|
||||||
|
|
||||||
|
return new Video(Id,Title,new YoutubeExplode.Common.Author(AuthorChannelId,AuthorTitle),new DateTimeOffset(UploadDate),Description,Duration,thumbnails,Keywords.ToList(),new Engagement(Views,Likes,Dislikes));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public DateTime AddDate {get;set;}
|
||||||
|
public string Title { get; set; }
|
||||||
|
public DateTime UploadDate { get; set; }
|
||||||
|
public string[] Keywords { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string AuthorTitle { get; set; }
|
||||||
|
public string AuthorChannelId { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan Duration { get; set; }
|
||||||
|
|
||||||
|
public long Views { get; set; }
|
||||||
|
public long Likes { get; set; }
|
||||||
|
public long Dislikes { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder b=new StringBuilder();
|
||||||
|
b.AppendLine($"Title: {Title}");
|
||||||
|
b.AppendLine($"AuthorTitle: {AuthorTitle}");
|
||||||
|
DateTime date=UploadDate;
|
||||||
|
|
||||||
|
b.AppendLine($"Upload Date: {date.ToShortDateString()}");
|
||||||
|
|
||||||
|
b.AppendLine($"Likes: {Likes}, Dislikes: {Dislikes}, Views: {Views}");
|
||||||
|
b.AppendLine($"Duration: {Duration.ToString()}");
|
||||||
|
b.AppendLine($"Tags: {string.Join(", ",Keywords)}");
|
||||||
|
b.AppendLine("Description:");
|
||||||
|
b.AppendLine(Description);
|
||||||
|
return b.ToString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Tesses.WebServer" Version="1.0.3.3" />
|
||||||
|
<PackageReference Include="YouTubeExplode" Version="6.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>YouTube Downloader</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Download YouTube Videos</h1>
|
||||||
|
|
||||||
|
<form method="POST" action="api/PreMuxed">
|
||||||
|
<input type="text" name="v" placeholder="Url or Id">
|
||||||
|
<input type="submit" value="Download">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,11 @@
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
tytd:
|
||||||
|
build: Dockerfile
|
||||||
|
image: tytd-proxy-ogc
|
||||||
|
ports:
|
||||||
|
- 3253:3253
|
||||||
|
volumes:
|
||||||
|
- tytd-data:/data
|
||||||
|
volumes:
|
||||||
|
tytd-data:
|
Loading…
Reference in New Issue