mirror of
https://onedev.site.tesses.net/tytd2025
synced 2026-02-08 09:45:44 +00:00
First commit
This commit is contained in:
19
Tesses.YouTubeDownloader/cross.json
Normal file
19
Tesses.YouTubeDownloader/cross.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"info": {
|
||||
"description": "Download YouTube Videos (using CrossLang)",
|
||||
"maintainer": "Mike Nolan",
|
||||
"repo":"https://onedev.site.tesses.net/tytd2025",
|
||||
"homepage": "https://tesses.net/apps/tytd/2025/",
|
||||
"type": "lib",
|
||||
"license": "GPLv3"
|
||||
},
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "Tesses.CrossLang.BuildEssentials",
|
||||
"version": "1.0.0.0-prod"
|
||||
}
|
||||
],
|
||||
"name": "Tesses.YouTubeDownloader",
|
||||
"version": "1.0.0.0-prod",
|
||||
"icon": "icon.png"
|
||||
}
|
||||
BIN
Tesses.YouTubeDownloader/res/icon.png
Normal file
BIN
Tesses.YouTubeDownloader/res/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Tesses.YouTubeDownloader/res/package_icon.png
Normal file
BIN
Tesses.YouTubeDownloader/res/package_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
23
Tesses.YouTubeDownloader/res/request.json
Normal file
23
Tesses.YouTubeDownloader/res/request.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"context": {
|
||||
"client": {
|
||||
"clientName": "ANDROID",
|
||||
"clientVersion": "19.28.35",
|
||||
"clientScreen": "WATCH",
|
||||
"platform": "MOBILE",
|
||||
"osName": "Android",
|
||||
"osVersion": "15",
|
||||
"androidSdkVersion": 35,
|
||||
"hl": "en-GB",
|
||||
"gl": "US",
|
||||
"utcOffsetMinutes": 0
|
||||
},
|
||||
"request": {
|
||||
"internalExperimentFlags": [],
|
||||
"useSsl": true
|
||||
},
|
||||
"user": {
|
||||
"lockedSafetyMode": false
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Tesses.YouTubeDownloader/res/request2.json
Normal file
31
Tesses.YouTubeDownloader/res/request2.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"context": {
|
||||
"client": {
|
||||
"clientName": "ANDROID",
|
||||
"clientVersion": "19.28.35",
|
||||
"clientScreen": "WATCH",
|
||||
"platform": "MOBILE",
|
||||
"visitorData": "VISITOR_DATA",
|
||||
"osName": "Android",
|
||||
"osVersion": "15",
|
||||
"androidSdkVersion": 35,
|
||||
"hl": "en-GB",
|
||||
"gl": "US",
|
||||
"utcOffsetMinutes": 0
|
||||
},
|
||||
"request": {
|
||||
"internalExperimentFlags": [],
|
||||
"useSsl": true
|
||||
},
|
||||
"user": {
|
||||
"lockedSafetyMode": false
|
||||
}
|
||||
},
|
||||
"playerRequest": {
|
||||
"videoId": "VIDEO_ID_HERE",
|
||||
"cpn": "kDqPxSobYp3UbLvx",
|
||||
"contentCheckOk": true,
|
||||
"racyCheckOk": true
|
||||
},
|
||||
"disablePlayerResponse": false
|
||||
}
|
||||
29
Tesses.YouTubeDownloader/src/Bell.tcross
Normal file
29
Tesses.YouTubeDownloader/src/Bell.tcross
Normal file
@@ -0,0 +1,29 @@
|
||||
class SubscriptionBell
|
||||
{
|
||||
/^ Disabled bell ^/
|
||||
static getDisabled() "Disabled";
|
||||
/^ Download (Low quality) ^/
|
||||
static getDownloadLow() "DownloadLow";
|
||||
/^ Download (High quality) ^/
|
||||
static getDownloadHigh() "DownloadHigh";
|
||||
/^ Download (and notify) (Low quality) ^/
|
||||
static getBellLow() "BellLow";
|
||||
/^ Download (and notify) (High quality) ^/
|
||||
static getBellHigh() "BellHigh";
|
||||
|
||||
/^ Notify ^/
|
||||
static getBell() "Bell";
|
||||
|
||||
static getBells()
|
||||
{
|
||||
return [
|
||||
{name="Disabled",value=SubscriptionBell.Disabled},
|
||||
{name="Notify",value=SubscriptionBell.Bell},
|
||||
{name="Notify and Download (Low)", value=SubscriptionBell.BellLow},
|
||||
{name="Notify and Download (High)", value=SubscriptionBell.BellHigh},
|
||||
{name="Download (Low)", value=SubscriptionBell.DownloadLow},
|
||||
{name="Download (High)", value=SubscriptionBell.DownloadHigh}
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
27
Tesses.YouTubeDownloader/src/Event.tcross
Normal file
27
Tesses.YouTubeDownloader/src/Event.tcross
Normal file
@@ -0,0 +1,27 @@
|
||||
class TYTD.Event {
|
||||
private _events = [];
|
||||
|
||||
public operator+(e)
|
||||
{
|
||||
if (!_events.Contains(e))
|
||||
{
|
||||
this._events.Add(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public operator-(e)
|
||||
{
|
||||
this._events.Remove(e);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Invoke($$args)
|
||||
{
|
||||
each(var e : this._events)
|
||||
{
|
||||
e.Call(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Tesses.YouTubeDownloader/src/Music.tcross
Normal file
127
Tesses.YouTubeDownloader/src/Music.tcross
Normal file
@@ -0,0 +1,127 @@
|
||||
class TYTD.Music
|
||||
{
|
||||
public getUserAgent() $"TYTDMusic/{this.getUserAgent.File.Version.Major}.{this.getUserAgent.File.Version.Minor}.{this.getUserAgent.File.Version.Patch} ( tesses@tesses.net )";
|
||||
public TYTD;
|
||||
public Music(tytd)
|
||||
{
|
||||
this.TYTD = tytd;
|
||||
TYTD.Storage.CreateDirectory(/"Album Arts");
|
||||
}
|
||||
|
||||
public GetArtists(query)
|
||||
{
|
||||
|
||||
var req = {
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
Value = this.UserAgent
|
||||
}
|
||||
]
|
||||
};
|
||||
var resp = Net.Http.MakeRequest($"https://musicbrainz.org/ws/2/artist?query={Net.Http.UrlEncode(query)}&fmt=json",req);
|
||||
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
var jo = Json.Decode(resp.ReadAsString());
|
||||
var items = [];
|
||||
each(var item : jo.artists)
|
||||
{
|
||||
items.Add({
|
||||
id = item.id,
|
||||
type = item.type,
|
||||
name = item.name
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public GetArtistsAlbums(artist_id)
|
||||
{
|
||||
var req = {
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
Value = this.UserAgent
|
||||
}
|
||||
]
|
||||
};
|
||||
var resp = Net.Http.MakeRequest($"https://musicbrainz.org/ws/2/artist/{Net.Http.UrlPathEncode(artist_id)}?inc=artist-credits+releases&fmt=json",req);
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
var jo = Json.Decode(resp.ReadAsString());
|
||||
var items = [];
|
||||
each(var item : jo.releases)
|
||||
{
|
||||
items.Add({
|
||||
id = item.id,
|
||||
packaging = item.packaging,
|
||||
title = item.title,
|
||||
date = item.date
|
||||
});
|
||||
}
|
||||
|
||||
return {items,name=jo.name};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public GetAlbumArts(album_id)
|
||||
{
|
||||
var req = {
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
Value = UserAgent
|
||||
}
|
||||
],
|
||||
FollowRedirects=true
|
||||
};
|
||||
var url = $"https://coverartarchive.org/release/{Net.Http.UrlPathEncode(album_id)}";
|
||||
var resp = Net.Http.MakeRequest(url,req);
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
return Json.Decode(resp.ReadAsString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public GetAlbumArt(album_id, res)
|
||||
{
|
||||
var path = /"Album Arts"/$"{album_id}-{res}.jpg";
|
||||
if(TYTD.Storage.FileExists(path)) return FS.ReadAllBytes(TYTD.Storage,path);
|
||||
var arts = GetAlbumArts(album_id);
|
||||
if(arts == null) return null;
|
||||
|
||||
each(var img : arts.images)
|
||||
{
|
||||
if(img.front)
|
||||
{
|
||||
var url = res == "full" ? img.image : img.thumbnails.[res];
|
||||
var req = {
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
Value = UserAgent
|
||||
}
|
||||
],
|
||||
FollowRedirects=true
|
||||
};
|
||||
var resp = Net.Http.MakeRequest(url.Replace("http://","https://"),req);
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
var strm = TYTD.Storage.OpenFile(path,"wb");
|
||||
resp.CopyToStream(strm);
|
||||
strm.Close();
|
||||
return FS.ReadAllBytes(TYTD.Storage,path);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
35
Tesses.YouTubeDownloader/src/Queue.tcross
Normal file
35
Tesses.YouTubeDownloader/src/Queue.tcross
Normal file
@@ -0,0 +1,35 @@
|
||||
class TYTD.Queue
|
||||
{
|
||||
private ls = [];
|
||||
private mtx = new Muxex();
|
||||
|
||||
public getCount()
|
||||
{
|
||||
this.mtx.Lock();
|
||||
var c = this.ls.Count;
|
||||
this.mtx.Unlock();
|
||||
return c;
|
||||
}
|
||||
|
||||
public Pop()
|
||||
{
|
||||
var item = null;
|
||||
this.mtx.Lock();
|
||||
if(this.ls.Count > 0)
|
||||
{
|
||||
item = ls[ls.Count-1];
|
||||
this.ls.RemoveAt(ls.Count-1);
|
||||
}
|
||||
|
||||
|
||||
this.mtx.Unlock();
|
||||
return item;
|
||||
}
|
||||
|
||||
public Push(val)
|
||||
{
|
||||
this.mtx.Lock();
|
||||
this.ls.Add(val);
|
||||
this.mtx.Unlock();
|
||||
}
|
||||
}
|
||||
36
Tesses.YouTubeDownloader/src/Resolution.tcross
Normal file
36
Tesses.YouTubeDownloader/src/Resolution.tcross
Normal file
@@ -0,0 +1,36 @@
|
||||
class Resolution
|
||||
{
|
||||
/^ Used to fetch metadata only ^/
|
||||
static getNoDownload() "NoDownload";
|
||||
/^ Get the video/audio mp4 (muxed by YouTube) ^/
|
||||
static getLowVideo() "LowVideo";
|
||||
/^ Get the highest video stream ^/
|
||||
static getVideoOnly() "VideoOnly";
|
||||
/^ Get the highest audio stream ^/
|
||||
static getAudioOnly() "AudioOnly";
|
||||
/^ Get the highest audio stream (and convert to mp3) ^/
|
||||
static getMP3() "MP3";
|
||||
/^ Get the highest audio stream (and convert to flac) ^/
|
||||
static getFLAC() "FLAC";
|
||||
/^ Get the highest video and then audio stream (and convert to mp4) ^/
|
||||
static getMP4() "MP4";
|
||||
/^ Get the highest video and then audio stream (and mux to a mkv file) ^/
|
||||
static getMKV() "MKV";
|
||||
/^ Get the highest video and then audio stream (dont convert or mux) ^/
|
||||
static getDontConvert() "DontConvert";
|
||||
|
||||
static getResolutions()
|
||||
{
|
||||
return [
|
||||
{name="Don't Download", value=Resolution.NoDownload},
|
||||
{name="Low (muxed by YouTube)", value=Resolution.LowVideo,default=true},
|
||||
{name="Highest video (no audio)", value=Resolution.VideoOnly},
|
||||
{name="Highest audio (no video)", value=Resolution.AudioOnly},
|
||||
{name="Convert to MP3", value=Resolution.MP3},
|
||||
{name="Convert to FLAC", value=Resolution.FLAC},
|
||||
{name="Convert to MP4", value=Resolution.MP4},
|
||||
{name="Mux to MKV (no transcoding)",value=Resolution.MKV},
|
||||
{name="Don't convert or Mux",value=Resolution.DontConvert}
|
||||
];
|
||||
}
|
||||
}
|
||||
7
Tesses.YouTubeDownloader/src/VideoDownload.tcross
Normal file
7
Tesses.YouTubeDownloader/src/VideoDownload.tcross
Normal file
@@ -0,0 +1,7 @@
|
||||
class IVideoDownload {
|
||||
public setTYTD(tytd);
|
||||
public setProgress(p);
|
||||
public getVideo();
|
||||
public abstract Start();
|
||||
}
|
||||
|
||||
1550
Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross
Normal file
1550
Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross
Normal file
File diff suppressed because it is too large
Load Diff
0
Tesses.YouTubeDownloader/src/data.tcross
Normal file
0
Tesses.YouTubeDownloader/src/data.tcross
Normal file
87
Tesses.YouTubeDownloader/src/ids.tcross
Normal file
87
Tesses.YouTubeDownloader/src/ids.tcross
Normal file
@@ -0,0 +1,87 @@
|
||||
func TYTD.GetVideoId(v)
|
||||
{
|
||||
func IsValidId(_v)
|
||||
{
|
||||
if(_v.Count != 11) return false;
|
||||
each(var item : _v)
|
||||
{
|
||||
if(!(item.IsLetter() || item.IsDigit() || item == '-' || item == '_')) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if(TypeOf(v) != "String") return null;
|
||||
if(IsValidId(v)) return v;
|
||||
each(var __re : ["youtube\\..+?/watch.*?v=(.*?)(?:&|/|$)","youtu\\.be/(.*?)(?:\\?|&|/|$)","youtube\\..+?/embed/(.*?)(?:\\?|&|/|$)","youtube\\..+?/shorts/(.*?)(?:\\?|&|/|$)","youtube\\..+?/live/(.*?)(?:\\?|&|/|$)"])
|
||||
{
|
||||
var __r = new Regex(__re);
|
||||
var __s = __r.Search(v);
|
||||
if(__s.Count == 2)
|
||||
{
|
||||
__r=__s[1].Text;
|
||||
if(IsValidId(__r))
|
||||
{
|
||||
return __r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
func TYTD.GetPlaylistId(pid)
|
||||
{
|
||||
func IsValidId(v)
|
||||
{
|
||||
if(v.Count < 2) return false;
|
||||
each(var item : v)
|
||||
{
|
||||
if(!(item.IsLetter() || item.IsDigit() || item == '-' || item == '_')) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if(TypeOf(pid) != "String") return null;
|
||||
if(IsValidId(pid)) return pid;
|
||||
each(var __re : ["youtube\\..+?/playlist.*?list=(.*?)(?:&|/|$)","youtube\\..+?/watch.*?list=(.*?)(?:&|/|$)","youtu\\.be/.*?/.*?list=(.*?)(?:&|/|$)","youtube\\..+?/embed/.*?/.*?list=(.*?)(?:&|/|$)"])
|
||||
{
|
||||
var __r = new Regex(__re);
|
||||
var __s = __r.Search(pid);
|
||||
if(__s.Count == 2)
|
||||
{
|
||||
__r=__s[1].Text;
|
||||
if(IsValidId(__r))
|
||||
{
|
||||
return __r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
func TYTD.GetChannelId(cid)
|
||||
{
|
||||
func IsValidId(v)
|
||||
{
|
||||
if(v.Count != 24) return false;
|
||||
if(!v.StartsWith("UC")) return false;
|
||||
each(var item : v)
|
||||
{
|
||||
if(!(item.IsLetter() || item.IsDigit() || item == '-' || item == '_')) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if(TypeOf(cid) != "String") return null;
|
||||
if(IsValidId(cid)) return cid;
|
||||
each(var __re : ["youtube\\..+?/channel/(.*?)(?:\\?|&|/|$)"])
|
||||
{
|
||||
var __r = new Regex(__re);
|
||||
var __s = __r.Search(cid);
|
||||
if(__s.Count == 2)
|
||||
{
|
||||
__r=__s[1].Text;
|
||||
if(IsValidId(__r))
|
||||
{
|
||||
return __r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
class TYTD.AOVideoDownload : IVideoDownload {
|
||||
private info;
|
||||
private tytd;
|
||||
private progress;
|
||||
private video_stream_url;
|
||||
|
||||
private done=false;
|
||||
|
||||
public AOVideoDownload(id)
|
||||
{
|
||||
this.info = {
|
||||
Title = "",
|
||||
Channel = "",
|
||||
VideoId = TYTD.GetVideoId(id),
|
||||
ChannelId = ""
|
||||
};
|
||||
}
|
||||
public setTYTD(tytd)
|
||||
{
|
||||
this.tytd = tytd;
|
||||
var id = this.info.VideoId;
|
||||
var path = /"Streams"/id.Substring(0,4)/id.Substring(4)/"ao.bin";
|
||||
if(this.tytd.Storage.FileExists(path)) {
|
||||
this.done = true;
|
||||
return tytd;
|
||||
}
|
||||
|
||||
var req = this.tytd.ManifestRequest(id).playerResponse;
|
||||
this.info.Title = req.videoDetails.title;
|
||||
this.info.Channel = req.videoDetails.author;
|
||||
this.info.ChannelId = req.videoDetails.channelId;
|
||||
|
||||
this.tytd.PutVideoInfo(req.videoDetails);
|
||||
|
||||
var sampleRate = 0;
|
||||
var bitrate = 0;
|
||||
var url = "";
|
||||
|
||||
each(var item : req.streamingData.adaptiveFormats)
|
||||
{
|
||||
if(TypeOf(item.height) != "Long")
|
||||
{
|
||||
|
||||
item.audioSampleRate = ParseLong(item.audioSampleRate);
|
||||
if(item.audioSampleRate >= sampleRate && item.bitrate >= bitrate)
|
||||
{
|
||||
url = item.url;
|
||||
sampleRate = item.audioSampleRate;
|
||||
bitrate = item.bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.video_stream_url = url;
|
||||
|
||||
return tytd;
|
||||
}
|
||||
public setProgress(p)
|
||||
{
|
||||
this.progress = p;
|
||||
}
|
||||
public getVideo()
|
||||
{
|
||||
return this.info;
|
||||
}
|
||||
public Start()
|
||||
{
|
||||
for(var i = 0; i < 5; i++)
|
||||
{
|
||||
var req = {
|
||||
FollowRedirects = true,
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
|
||||
Value = "com.google.android.youtube/19.28.35 (Linux; U; Android 15; GB) gzip"
|
||||
}
|
||||
]
|
||||
};
|
||||
var resp = Net.Http.MakeRequest(this.video_stream_url,req);
|
||||
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
var id = this.info.VideoId;
|
||||
var path = /"Streams"/id.Substring(0,4)/id.Substring(4)/"ao.bin";
|
||||
this.tytd.Storage.CreateDirectory(path.GetParent());
|
||||
var strm = this.tytd.Storage.OpenFile(path+".part","wb");
|
||||
var src = resp.ReadAsStream();
|
||||
Helpers.CopyToProgress(src,strm,this.progress,100.0);
|
||||
strm.Close();
|
||||
src.Close();
|
||||
this.tytd.Storage.MoveFile(path+".part",path);
|
||||
break;
|
||||
}else {
|
||||
var req = this.tytd.ManifestRequest(this.info.VideoId).playerResponse;
|
||||
var sampleRate = 0;
|
||||
var bitrate = 0;
|
||||
var url = "";
|
||||
|
||||
each(var item : req.streamingData.adaptiveFormats)
|
||||
{
|
||||
if(TypeOf(item.height) != "Long")
|
||||
{
|
||||
|
||||
item.audioSampleRate = ParseLong(item.audioSampleRate);
|
||||
if(item.audioSampleRate >= sampleRate && item.bitrate >= bitrate)
|
||||
{
|
||||
url = item.url;
|
||||
sampleRate = item.audioSampleRate;
|
||||
bitrate = item.bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.video_stream_url = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
class TYTD.NoConvertVideoDownload : IVideoDownload {
|
||||
private info;
|
||||
private tytd;
|
||||
private progress;
|
||||
private video_stream_url;
|
||||
private audio_stream_url;
|
||||
|
||||
private done=false;
|
||||
|
||||
public NoConvertVideoDownload(id)
|
||||
{
|
||||
this.info = {
|
||||
Title = "",
|
||||
Channel = "",
|
||||
VideoId = TYTD.GetVideoId(id),
|
||||
ChannelId = ""
|
||||
};
|
||||
}
|
||||
public getVideo()
|
||||
{
|
||||
return this.info;
|
||||
}
|
||||
public setTYTD(tytd)
|
||||
{
|
||||
this.tytd = tytd;
|
||||
var id = this.info.VideoId;
|
||||
var path = /"Streams"/id.Substring(0,4)/id.Substring(4)/"vo.bin";
|
||||
var pathA = /"Streams"/id.Substring(0,4)/id.Substring(4)/"ao.bin";
|
||||
if(this.tytd.Storage.FileExists(path) && this.tytd.Storage.FileExists(pathA)) {
|
||||
this.done = true;
|
||||
return tytd;
|
||||
}
|
||||
|
||||
var req = this.tytd.ManifestRequest(id).playerResponse;
|
||||
|
||||
|
||||
this.info.Title = req.videoDetails.title;
|
||||
this.info.Channel = req.videoDetails.author;
|
||||
this.info.ChannelId = req.videoDetails.channelId;
|
||||
|
||||
this.tytd.PutVideoInfo(req.videoDetails);
|
||||
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
var sampleRate = 0;
|
||||
var bitrate = 0;
|
||||
var vurl = "";
|
||||
var aurl = "";
|
||||
|
||||
each(var item : req.streamingData.adaptiveFormats)
|
||||
{
|
||||
if(TypeOf(item.height) == "Long")
|
||||
{
|
||||
if(item.width >= width && item.height >= height)
|
||||
{
|
||||
vurl = item.url;
|
||||
width = item.width;
|
||||
height = item.height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
item.audioSampleRate = ParseLong(item.audioSampleRate);
|
||||
if(item.audioSampleRate >= sampleRate && item.bitrate >= bitrate)
|
||||
{
|
||||
aurl = item.url;
|
||||
sampleRate = item.audioSampleRate;
|
||||
bitrate = item.bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.video_stream_url = vurl;
|
||||
this.audio_stream_url = aurl;
|
||||
|
||||
return tytd;
|
||||
}
|
||||
public setProgress(p)
|
||||
{
|
||||
this.progress = p;
|
||||
}
|
||||
public Start()
|
||||
{
|
||||
if(this.done) return;
|
||||
|
||||
var id = this.info.VideoId;
|
||||
var path = /"Streams"/id.Substring(0,4)/id.Substring(4)/"vo.bin";
|
||||
var pathA = /"Streams"/id.Substring(0,4)/id.Substring(4)/"ao.bin";
|
||||
|
||||
if(!this.tytd.Storage.FileExists(path))
|
||||
{
|
||||
for(var i = 0; i < 5; i++)
|
||||
{
|
||||
var req = {
|
||||
FollowRedirects = true,
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
|
||||
Value = "com.google.android.youtube/19.28.35 (Linux; U; Android 15; GB) gzip"
|
||||
}
|
||||
]
|
||||
};
|
||||
var resp = Net.Http.MakeRequest(this.video_stream_url,req);
|
||||
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
this.tytd.Storage.CreateDirectory(path.GetParent());
|
||||
var strm = this.tytd.Storage.OpenFile(path+".part","wb");
|
||||
var src = resp.ReadAsStream();
|
||||
Helpers.CopyToProgress(src,strm,(p)=>{
|
||||
this.progress(p/2);
|
||||
},100.0);
|
||||
strm.Close();
|
||||
src.Close();
|
||||
this.tytd.Storage.MoveFile(path+".part",path);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
var req = this.tytd.ManifestRequest(this.info.VideoId).playerResponse;
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
var sampleRate = 0;
|
||||
var bitrate = 0;
|
||||
var vurl = "";
|
||||
var aurl = "";
|
||||
|
||||
each(var item : req.streamingData.adaptiveFormats)
|
||||
{
|
||||
if(TypeOf(item.height) == "Long")
|
||||
{
|
||||
if(item.width >= width && item.height >= height)
|
||||
{
|
||||
vurl = item.url;
|
||||
width = item.width;
|
||||
height = item.height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
item.audioSampleRate = ParseLong(item.audioSampleRate);
|
||||
if(item.audioSampleRate >= sampleRate && item.bitrate >= bitrate)
|
||||
{
|
||||
aurl = item.url;
|
||||
sampleRate = item.audioSampleRate;
|
||||
bitrate = item.bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.video_stream_url = vurl;
|
||||
this.audio_stream_url = aurl;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!this.tytd.Storage.FileExists(pathA))
|
||||
{
|
||||
for(var i = 0; i < 5; i++)
|
||||
{
|
||||
var req = {
|
||||
FollowRedirects = true,
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
|
||||
Value = "com.google.android.youtube/19.28.35 (Linux; U; Android 15; GB) gzip"
|
||||
}
|
||||
]
|
||||
};
|
||||
var resp = Net.Http.MakeRequest(this.audio_stream_url,req);
|
||||
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
this.tytd.Storage.CreateDirectory(pathA.GetParent());
|
||||
var strm = this.tytd.Storage.OpenFile(pathA+".part","wb");
|
||||
var src = resp.ReadAsStream();
|
||||
Helpers.CopyToProgress(src,strm,(p)=>{
|
||||
this.progress((p/2)+0.5);
|
||||
},100.0);
|
||||
strm.Close();
|
||||
src.Close();
|
||||
this.tytd.Storage.MoveFile(pathA+".part",pathA);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
var req = this.tytd.ManifestRequest(this.info.VideoId).playerResponse;
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
var sampleRate = 0;
|
||||
var bitrate = 0;
|
||||
var vurl = "";
|
||||
var aurl = "";
|
||||
|
||||
each(var item : req.streamingData.adaptiveFormats)
|
||||
{
|
||||
if(TypeOf(item.height) == "Long")
|
||||
{
|
||||
if(item.width >= width && item.height >= height)
|
||||
{
|
||||
vurl = item.url;
|
||||
width = item.width;
|
||||
height = item.height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
item.audioSampleRate = ParseLong(item.audioSampleRate);
|
||||
if(item.audioSampleRate >= sampleRate && item.bitrate >= bitrate)
|
||||
{
|
||||
aurl = item.url;
|
||||
sampleRate = item.audioSampleRate;
|
||||
bitrate = item.bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.video_stream_url = vurl;
|
||||
this.audio_stream_url = aurl;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
this.progress(1.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
class TYTD.SDVideoDownload : IVideoDownload {
|
||||
private info;
|
||||
private tytd;
|
||||
private progress;
|
||||
private video_stream_url;
|
||||
|
||||
private done=false;
|
||||
|
||||
public SDVideoDownload(id)
|
||||
{
|
||||
this.info = {
|
||||
Title = "",
|
||||
Channel = "",
|
||||
VideoId = TYTD.GetVideoId(id),
|
||||
ChannelId = ""
|
||||
};
|
||||
}
|
||||
public setTYTD(tytd)
|
||||
{
|
||||
this.tytd = tytd;
|
||||
var id = this.info.VideoId;
|
||||
var path = /"Streams"/id.Substring(0,4)/id.Substring(4)/"ytmux.mp4";
|
||||
if(this.tytd.Storage.FileExists(path)) {
|
||||
this.done = true;
|
||||
return tytd;
|
||||
}
|
||||
|
||||
var req = this.tytd.ManifestRequest(id).playerResponse;
|
||||
this.info.Title = req.videoDetails.title;
|
||||
this.info.Channel = req.videoDetails.author;
|
||||
this.info.ChannelId = req.videoDetails.channelId;
|
||||
|
||||
this.tytd.PutVideoInfo(req.videoDetails);
|
||||
|
||||
this.video_stream_url = req.streamingData.formats[0].url;
|
||||
|
||||
return tytd;
|
||||
}
|
||||
public setProgress(p)
|
||||
{
|
||||
this.progress = p;
|
||||
this.progress(0.0);
|
||||
}
|
||||
public getVideo()
|
||||
{
|
||||
return this.info;
|
||||
}
|
||||
public Start()
|
||||
{
|
||||
for(var i = 0; i < 5; i++)
|
||||
{
|
||||
var req = {
|
||||
FollowRedirects = true,
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
|
||||
Value = "com.google.android.youtube/19.28.35 (Linux; U; Android 15; GB) gzip"
|
||||
}
|
||||
]
|
||||
};
|
||||
var resp = Net.Http.MakeRequest(this.video_stream_url,req);
|
||||
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
var id = this.info.VideoId;
|
||||
var path = /"Streams"/id.Substring(0,4)/id.Substring(4)/"ytmux.mp4";
|
||||
this.tytd.Storage.CreateDirectory(path.GetParent());
|
||||
var strm = this.tytd.Storage.OpenFile(path+".part","wb");
|
||||
var src = resp.ReadAsStream();
|
||||
Helpers.CopyToProgress(src,strm,this.progress,100.0);
|
||||
strm.Close();
|
||||
src.Close();
|
||||
this.tytd.Storage.MoveFile(path+".part",path);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
var req = this.tytd.ManifestRequest(this.info.VideoId).playerResponse;
|
||||
this.video_stream_url = req.streamingData.formats[0].url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
class TYTD.TranscodeAudio : IVideoDownload {
|
||||
private id;
|
||||
private ncv;
|
||||
private tytd;
|
||||
private ext;
|
||||
|
||||
public TranscodeAudio(id,ext)
|
||||
{
|
||||
this.id = id;
|
||||
this.ext = ext;
|
||||
this.ncv = new TYTD.AOVideoDownload(id);
|
||||
}
|
||||
|
||||
public setTYTD(tytd)
|
||||
{
|
||||
this.tytd = tytd;
|
||||
return this.ncv.TYTD = tytd;
|
||||
}
|
||||
|
||||
public setProgress(p)
|
||||
{
|
||||
return this.ncv.Progress = p;
|
||||
}
|
||||
|
||||
public getVideo()
|
||||
{
|
||||
return this.ncv.Video;
|
||||
}
|
||||
|
||||
public Start()
|
||||
{
|
||||
var id = this.id;
|
||||
this.ncv.Start();
|
||||
|
||||
var p = new Process();
|
||||
p.FileName = Env.GetRealExecutablePath("ffmpeg").ToString();
|
||||
|
||||
var dir = this.tytd.DatabaseDirectory / "Streams"/this.id.Substring(0,4)/this.id.Substring(4);
|
||||
var ao = dir / "ao.bin";
|
||||
var out = dir / $"conv{this.ext}";
|
||||
if(FS.Local.FileExists(out)) return;
|
||||
var args=["-y","-i",ao.ToString()];
|
||||
|
||||
|
||||
args.Add("-preset");
|
||||
args.Add("ultrafast");
|
||||
args.Add(out.ToString());
|
||||
|
||||
|
||||
p.Arguments = args;
|
||||
if(p.Start())
|
||||
p.Join();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
class TYTD.TranscodeVideo : IVideoDownload {
|
||||
private id;
|
||||
private ncv;
|
||||
private tytd;
|
||||
private ext;
|
||||
|
||||
public TranscodeVideo(id,ext)
|
||||
{
|
||||
this.id = id;
|
||||
this.ext = ext;
|
||||
this.ncv = new TYTD.NoConvertVideoDownload(id);
|
||||
}
|
||||
|
||||
public setTYTD(tytd)
|
||||
{
|
||||
this.tytd = tytd;
|
||||
return this.ncv.TYTD = tytd;
|
||||
}
|
||||
|
||||
public setProgress(p)
|
||||
{
|
||||
return this.ncv.Progress = p;
|
||||
}
|
||||
|
||||
public getVideo()
|
||||
{
|
||||
return this.ncv.Video;
|
||||
}
|
||||
|
||||
public Start()
|
||||
{
|
||||
var id = this.id;
|
||||
this.ncv.Start();
|
||||
|
||||
var p = new Process();
|
||||
p.FileName = Env.GetRealExecutablePath("ffmpeg").ToString();
|
||||
|
||||
var dir = this.tytd.DatabaseDirectory / "Streams"/this.id.Substring(0,4)/this.id.Substring(4);
|
||||
var vo = dir / "vo.bin";
|
||||
var ao = dir / "ao.bin";
|
||||
var out = dir / $"conv{this.ext}";
|
||||
if(FS.Local.FileExists(out)) return;
|
||||
var args=["-y","-i",vo.ToString(),"-i",ao.ToString()];
|
||||
|
||||
if(this.ext == ".mkv")
|
||||
{
|
||||
args.Add("-c");
|
||||
args.Add("copy");
|
||||
}
|
||||
args.Add("-map");
|
||||
args.Add("0:v");
|
||||
args.Add("-map");
|
||||
args.Add("1:a");
|
||||
args.Add("-preset");
|
||||
args.Add("ultrafast");
|
||||
args.Add(out.ToString());
|
||||
|
||||
|
||||
p.Arguments = args;
|
||||
if(p.Start())
|
||||
p.Join();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
class TYTD.VOVideoDownload : IVideoDownload {
|
||||
private info;
|
||||
private tytd;
|
||||
private progress;
|
||||
private video_stream_url;
|
||||
|
||||
private done=false;
|
||||
|
||||
public VOVideoDownload(id)
|
||||
{
|
||||
this.info = {
|
||||
Title = "",
|
||||
Channel = "",
|
||||
VideoId = TYTD.GetVideoId(id),
|
||||
ChannelId = ""
|
||||
};
|
||||
}
|
||||
public setTYTD(tytd)
|
||||
{
|
||||
this.tytd = tytd;
|
||||
var id = this.info.VideoId;
|
||||
var path = /"Streams"/id.Substring(0,4)/id.Substring(4)/"vo.bin";
|
||||
if(this.tytd.Storage.FileExists(path)) {
|
||||
this.done = true;
|
||||
return tytd;
|
||||
}
|
||||
|
||||
var req = this.tytd.ManifestRequest(id).playerResponse;
|
||||
this.info.Title = req.videoDetails.title;
|
||||
this.info.Channel = req.videoDetails.author;
|
||||
this.info.ChannelId = req.videoDetails.channelId;
|
||||
|
||||
this.tytd.PutVideoInfo(req.videoDetails);
|
||||
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
var url = "";
|
||||
|
||||
each(var item : req.streamingData.adaptiveFormats)
|
||||
{
|
||||
if(TypeOf(item.height) == "Long")
|
||||
{
|
||||
if(item.width >= width && item.height >= height)
|
||||
{
|
||||
url = item.url;
|
||||
width = item.width;
|
||||
height = item.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.video_stream_url = url;
|
||||
|
||||
return tytd;
|
||||
}
|
||||
public setProgress(p)
|
||||
{
|
||||
this.progress = p;
|
||||
}
|
||||
public getVideo()
|
||||
{
|
||||
return this.info;
|
||||
}
|
||||
public Start()
|
||||
{
|
||||
var req = {
|
||||
FollowRedirects = true,
|
||||
RequestHeaders = [
|
||||
{
|
||||
Key = "User-Agent",
|
||||
|
||||
Value = "com.google.android.youtube/19.28.35 (Linux; U; Android 15; GB) gzip"
|
||||
}
|
||||
]
|
||||
};
|
||||
var resp = Net.Http.MakeRequest(this.video_stream_url,req);
|
||||
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
var id = this.info.VideoId;
|
||||
var path = /"Streams"/id.Substring(0,4)/id.Substring(4)/"vo.bin";
|
||||
this.tytd.Storage.CreateDirectory(path.GetParent());
|
||||
var strm = this.tytd.Storage.OpenFile(path+".part","wb");
|
||||
var src = resp.ReadAsStream();
|
||||
Helpers.CopyToProgress(src,strm,this.progress,100.0);
|
||||
strm.Close();
|
||||
src.Close();
|
||||
this.tytd.Storage.MoveFile(path+".part",path);
|
||||
}
|
||||
else {
|
||||
var req = this.tytd.ManifestRequest(this.info.VideoId).playerResponse;
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
var url = "";
|
||||
|
||||
each(var item : req.streamingData.adaptiveFormats)
|
||||
{
|
||||
if(TypeOf(item.height) == "Long")
|
||||
{
|
||||
if(item.width >= width && item.height >= height)
|
||||
{
|
||||
url = item.url;
|
||||
width = item.width;
|
||||
height = item.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.video_stream_url = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user