Add some api docs

This commit is contained in:
2025-11-20 18:20:07 -06:00
parent c71c73bb77
commit 9da78e364c
18 changed files with 599 additions and 125 deletions

View File

@@ -14,6 +14,6 @@
}
],
"name": "Tesses.YouTubeDownloader",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"icon": "icon.png"
}

View File

@@ -1,17 +1,38 @@
class TYTD.Downloader {
/^
The storage vfs that TYTD accesses
^/
public Storage;
/^
The directory of where the database is (should be the same physical folder as the Storage vfs if you want ffmpeg)
^/
public DatabaseDirectory;
/^
Package manager object
^/
public PackageManager = new Tesses.CrossLang.PackageManager();
public Servers = Net.Http.MountableServer({Handle=(ctx)=>false});
/^
All the plugin webpages
^/
public Servers = Net.Http.MountableServer({Handle=(ctx)=>false});
/^
vfs: The storage vfs that TYTD accesses
dbDir: The directory of where the database is (should be the same physical folder as the Storage vfs if you want ffmpeg)
^/
public Downloader(vfs,dbDir)
{
this.Storage = vfs;
this.DatabaseDirectory = dbDir;
}
/^
Download a video
id: video id or url
res: see Resolution
^/
public DownloadVideo(id,$res)
{
this.LOG($"Adding video: {TYTD.GetVideoId(id)}, original val: {id}");
switch(res)
{
case Resolution.NoDownload:
@@ -48,7 +69,11 @@ class TYTD.Downloader {
break;
}
}
/^
Download a playlist
id: playlist id or url
res: see Resolution
^/
public DownloadPlaylist(id,$res)
{
var pid = TYTD.GetPlaylistId(id);
@@ -56,6 +81,7 @@ class TYTD.Downloader {
if(pid != null)
{
this.LOG($"Adding playlist: https://www.youtube.com/playlist?list={pid}");
this.PlaylistQueue.Push(()=>{
each(var item : this.QueryPlaylistItems(pid,true))
{
@@ -68,13 +94,18 @@ class TYTD.Downloader {
});
}
}
/^
Download a channel
id: channel id or url
res: see Resolution
^/
public DownloadChannel(id,$res)
{
var cid = TYTD.GetChannelId(id);
if(cid != null)
{
this.LOG($"Adding channel: https://www.youtube.com/channel/{cid}");
this.PlaylistQueue.Push(()=>{
each(var item : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
{
@@ -86,7 +117,9 @@ class TYTD.Downloader {
});
}
}
/^
Get the thumbnail of a plugin
^/
public GetPluginThumbnail(name)
{
each(var item : this.Plugins)
@@ -98,7 +131,11 @@ class TYTD.Downloader {
return embed("package_icon.png");
}
/^
Download video, playlist or channel
url: id or url
res: see Resolution
^/
public DownloadItem(url, $res)
{
var vid = TYTD.GetVideoId(url);
@@ -106,7 +143,7 @@ class TYTD.Downloader {
var cid = TYTD.GetChannelId(url);
if(vid != null && url.Length == 11)
if(vid != null)
{
this.DownloadVideo(vid, res);
}
@@ -114,25 +151,32 @@ class TYTD.Downloader {
{
this.DownloadPlaylist(pid,res);
}
else if(vid != null)
{
this.DownloadVideo(vid, res);
}
else if(cid != null)
{
this.DownloadChannel(cid,res);
}
}
/^
Redirect url to info page
^/
public PageRedirect(url)
{
var vid = TYTD.GetVideoId(url);
var pid = TYTD.GetPlaylistId(url);
if(vid != null && url.Length == 11)
if(vid != null)
{
return $"./video?v={Net.Http.UrlEncode(vid)}";
}
else if(pid != null)
{
return $"./playlist?id={Net.Http.UrlEncode(pid)}";
}
else if(cid != null)
{
return $"./playlist?id={Net.Http.UrlEncode(cid)}";
}
return "./";
}
@@ -148,12 +192,18 @@ class TYTD.Downloader {
return $"{views/1000000000000}T views";
}
/^
Get videos
set offset to the page (starting at 0)
set count to how many items per page
^/
public GetVideos(query, offset, count)
{
this.Muxex.Lock();
var db = this.OpenDB();
var q = Sqlite.Escape($"%{query}%");
var res = Sqlite.Exec(db, $"SELECT * FROM videos v WHERE (v.title LIKE {q} OR v.shortDescription LIKE {q}) LIMIT {count} OFFSET {offset*count};");
@@ -169,6 +219,7 @@ class TYTD.Downloader {
else item.keywords = [];
item.addDate = ParseLong(item.addDate);
item.addDateStr = new DateTime(item.addDate).ToString();
item.lengthSeconds = ParseLong(item.lengthSeconds);
item.viewCount = ParseLong(item.viewCount);
item.viewCountStr = this.Views2Str(item.viewCount);
@@ -176,7 +227,11 @@ class TYTD.Downloader {
}
return res;
}
/^
Get playlists
set offset to the page (starting at 0)
set count to how many items per page
^/
public GetPlaylists(query, offset, count)
{
this.Muxex.Lock();
@@ -191,6 +246,11 @@ class TYTD.Downloader {
return res;
}
/^
Get playlist contents
set offset to the page (starting at 0)
set count to how many items per page
^/
public GetPlaylistContents(id, offset, count)
{
id = TYTD.GetPlaylistId(id);
@@ -239,6 +299,8 @@ class TYTD.Downloader {
else
item.keywords = [];
item.addDate = ParseLong(item.addDate);
item.addDateStr = new DateTime(item.addDate).ToString();
item.lengthSeconds = ParseLong(item.lengthSeconds);
item.viewCount = ParseLong(item.viewCount);
item.viewCountStr = this.Views2Str(item.viewCount);
@@ -250,6 +312,11 @@ class TYTD.Downloader {
items = res2
};
}
/^
Get channel contents
set offset to the page (starting at 0)
set count to how many items per page
^/
public GetChannelContents(id, offset, count)
{
id = TYTD.GetChannelId(id);
@@ -283,6 +350,8 @@ class TYTD.Downloader {
else
item.keywords = [];
item.addDate = ParseLong(item.addDate);
item.addDateStr = new DateTime(item.addDate).ToString();
item.lengthSeconds = ParseLong(item.lengthSeconds);
item.viewCount = ParseLong(item.viewCount);
item.viewCountStr = this.Views2Str(item.viewCount);
@@ -292,7 +361,11 @@ class TYTD.Downloader {
items = res
};
}
/^
Get channels
set offset to the page (starting at 0)
set count to how many items per page
^/
public GetChannels(query, offset, count)
{
this.Muxex.Lock();
@@ -307,7 +380,10 @@ class TYTD.Downloader {
return res;
}
/^
Get the video path
res: see Resolution
^/
public GetVideoPath(v,res)
{
var id = TYTD.GetVideoId(v);
@@ -343,16 +419,22 @@ class TYTD.Downloader {
}
/^
Get video info
vid can be a video url from youtube or just the id
^/
public GetVideo(vid)
{
var id = TYTD.GetVideoId(vid);
if(id == null) return null;
Console.WriteLine(id);
this.Muxex.Lock();
var db = this.OpenDB();
var res = Sqlite.Exec(db, $"SELECT * FROM videos WHERE videoId = {Sqlite.Escape(id)};");
var out = null;
Console.WriteLine(res);
if(TypeOf(res) == "List" && res.Length == 1) out = res[0];
@@ -365,6 +447,8 @@ class TYTD.Downloader {
else
out.keywords = [];
out.addDate = ParseLong(out.addDate);
out.addDateStr = new DateTime(out.addDate).ToString();
out.lengthSeconds = ParseLong(out.lengthSeconds);
out.viewCount = ParseLong(out.viewCount);
out.viewCountStr = this.Views2Str(out.viewCount);
@@ -374,6 +458,10 @@ class TYTD.Downloader {
/^
Get playlist info
id can be a playlist url from youtube or just the id
^/
public GetPlaylist(id)
{
var id = TYTD.GetPlaylistId(vid);
@@ -392,6 +480,10 @@ class TYTD.Downloader {
return out;
}
/^
Get channel info
id can be a channel url from youtube or just the id
^/
public GetChannel(id)
{
var id = TYTD.GetChannelId(vid);
@@ -432,6 +524,7 @@ class TYTD.Downloader {
this.Mutex.Unlock();
return items;
}
/^ Set the description of a personal list ^/
public SetPersonalListDescription(name,description)
{
this.Muxex.Lock();
@@ -441,6 +534,7 @@ class TYTD.Downloader {
Sqlite.Close(db);
this.Mutex.Unlock();
}
/^ Get the description of a personal list ^/
public GetPersonalListDescription(name)
{
this.Muxex.Lock();
@@ -452,10 +546,13 @@ class TYTD.Downloader {
if(TypeOf(res) == "List" && res.Length > 0)
{
res[0].description;
var d = res[0].description;
if(TypeOf(d)=="String")
return d;
}
return "";
}
/^ ^/
public GetPersonalListContents(name, offset, count)
{
this.Muxex.Lock();
@@ -472,6 +569,8 @@ class TYTD.Downloader {
item.keywords = Json.Decode(item.keywords);
else item.keywords = [];
item.addDate = ParseLong(item.addDate);
item.addDateStr = new DateTime(item.addDate).ToString();
item.lengthSeconds = ParseLong(item.lengthSeconds);
item.viewCount = ParseLong(item.viewCount);
item.viewCountStr = this.Views2Str(item.viewCount);
@@ -505,11 +604,19 @@ class TYTD.Downloader {
this.Mutex.Lock();
var db = this.OpenDB();
//personal_lists (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, description TEXT)
Sqlite.Exec(db, $"DELETE FROM personal_list_entries e WHERE (e.listName = {Sqlite.Escape(name)} AND e.videoId = {Sqlite.Escape(id)});");
Sqlite.Exec(db, $"DELETE FROM personal_list_entries WHERE (listName = {Sqlite.Escape(name)} AND videoId = {Sqlite.Escape(id)});");
Sqlite.Close(db);
this.Mutex.Unlock();
}
public RemovePersonalList(name)
{
this.Mutex.Lock();
var db = this.OpenDB();
//personal_lists (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, description TEXT)
Sqlite.Exec(db, $"DELETE FROM personal_list_entries WHERE listName = {Sqlite.Escape(name)}; DELETE FROM personal_lists WHERE name = {Sqlite.Escape(name)};");
Sqlite.Close(db);
this.Mutex.Unlock();
}
public SetSubscriptionBell(url, bell)
{
var cid = TYTD.GetChannelId(url);
@@ -587,7 +694,9 @@ class TYTD.Downloader {
public Plugins = [];
/^
The mutex (for database)
^/
public Mutex = new Mutex();
public Running=true;
@@ -595,6 +704,11 @@ class TYTD.Downloader {
private DownloaderThreadHandle;
private Queue = new TYTD.Queue();
/^
Get Video Queue count
^/
public getVideoQueueCount() this.Queue.Count;
private PlaylistThreadHandle;
@@ -660,7 +774,7 @@ class TYTD.Downloader {
try {info = Json.Decode(_exec.Info);} catch(ex) {}
_pkg.info = info;
_pkg.pluginName = TypeOf(info.short_name) ? info.short_name : name;
_pkg.pluginName = TypeOf(info.short_name) == "String" ? info.short_name : name;
var reso = _exec.Resources;
var ico = _exec.Icon;
_pkg.pluginIcon = ico >= 0 && i < reso.Count ? reso[ico] : embed("package_icon.png");
@@ -773,33 +887,35 @@ class TYTD.Downloader {
private lastSubPollTime = 0;
private PlaylistThread()
{
while(this.Running)
{
var res = this.PlaylistQueue.Pop();
try {
var res = this.PlaylistQueue.Pop();
if(TypeOf(res) != "Null")
{
res();
}
if(TypeOf(res) != "Null")
{
res();
}
var currentTime = DateTime.NowEpoch;
var bt = this.Config.BellTimer;
var currentTime = DateTime.NowEpoch;
var bt = this.Config.BellTimer;
if((currentTime-this.lastSubPollTime) > bt)
{
this.lastSubPollTime = currentTime;
if((currentTime-this.lastSubPollTime) > bt)
{
this.lastSubPollTime = currentTime;
this.Mutex.Lock();
var db = this.OpenDB();
var res = Sqlite.Exec(db, "SELECT * FROM subscriptions;");
//Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS subscriptions (id INTEGER PRIMARY KEY AUTOINCREMENT, channelId TEXT UNIQUE ON CONFLICT REPLACE, bell TEXT);");
this.Mutex.Lock();
var db = this.OpenDB();
var res = Sqlite.Exec(db, "SELECT * FROM subscriptions;");
//Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS subscriptions (id INTEGER PRIMARY KEY AUTOINCREMENT, channelId TEXT UNIQUE ON CONFLICT REPLACE, bell TEXT);");
this.Mutex.Unlock();
/*
/^ Disabled bell ^/
static getDisabled() "Disabled";
this.Mutex.Unlock();
/*
/^ Disabled bell ^/
static getDisabled() "Disabled";
/^ Download (Low quality) ^/
static getDownloadLow() "DownloadLow";
/^ Download (High quality) ^/
@@ -813,98 +929,123 @@ class TYTD.Downloader {
static getBell() "Bell";
*/
each(var sub : res)
{
each(var sub : res)
{
var downloadRes = Resolution.NoDownload;
var notify = false;
switch(sub.bell)
{
case SubscriptionBell.Bell:
notify=true;
break;
case SubscriptionBell.BellLow:
notify=true;
case SubscriptionBell.DownloadLow:
downloadRes = Resolution.LowVideo;
break;
case SubscriptionBell.BellHigh:
notify = true;
break;
case SubscriptionBell.DownloadHigh:
downloadRes = Resolution.MKV;
break;
}
if(!notify && downloadRes == Resolution.NoDownload) continue;
var cid = sub.channelId;
var downloadRes = Resolution.NoDownload;
var notify = false;
switch(sub.bell)
{
case SubscriptionBell.Bell:
notify=true;
break;
case SubscriptionBell.BellLow:
notify=true;
case SubscriptionBell.DownloadLow:
downloadRes = Resolution.LowVideo;
break;
case SubscriptionBell.BellHigh:
notify = true;
break;
case SubscriptionBell.DownloadHigh:
downloadRes = Resolution.MKV;
break;
}
if(!notify && downloadRes == Resolution.NoDownload) continue;
var cid = sub.channelId;
var newVideos = [];
var newVideos = [];
each(var batch : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
{
each(var videoId : batch)
each(var batch : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
{
each(var videoId : batch)
{
if(this.GetVideo(videoId) == null)
newVideos.Add(videoId);
if(this.GetVideo(videoId) == null)
newVideos.Add(videoId);
}
}
}
if(notify)
{
each(var id : newVideos)
if(notify)
{
this.PutVideoInfoIfNotExists(id);
var res = this.GetVideo(id);
if(res != null)
this.Bell.Invoke(this,{
Video = res
});
each(var id : newVideos)
{
this.PutVideoInfoIfNotExists(id);
var res = this.GetVideo(id);
if(res != null)
this.Bell.Invoke(this,{
Video = res
});
}
}
}
if(downloadRes != Resolution.NoDownload)
{
each(var id : newVideos)
if(downloadRes != Resolution.NoDownload)
{
this.DownloadVideo(id,downloadRes);
each(var id : newVideos)
{
this.DownloadVideo(id,downloadRes);
}
}
}
}
}
}
}catch(ex) {
try{
this.LOG($"Exception caught on playlist thread: {e}");
}catch(ex2){}
}
}
}
private LOGDATEFMT = "%Y%m%d_%H%M%S";
private LOGDATE = DateTime.Now;
/^
Log stuff
^/
public LOG(text)
{
this.Mutex.Lock();
this.Storage.CreateDirectory(/"Logs");
var strm = this.Storage.OpenFile(/"Logs"/$"{this.LOGDATE.ToString(this.LOGDATEFMT)}.log","a");
strm.WriteText($"[{DateTime.Now.ToString()}] {text}\n");
strm.Close();
this.Mutex.Unlock();
}
private DownloadThread()
{
while(this.Running)
{
var res = this.Queue.Pop();
try {
var res = this.Queue.Pop();
if(TypeOf(res) != "Null")
{
if(TypeOf(res) != "Null")
{
res.TYTD = this;
res.Progress = (progress)=>{
this.CurrentVideoProgress = progress;
this.VideoProgress.Invoke(this, {
Video = res.Video,
progress
res.TYTD = this;
res.Progress = (progress)=>{
this.CurrentVideoProgress = progress;
this.VideoProgress.Invoke(this, {
Video = res.Video,
progress
});
};
this.CurrentVideo = res.Video;
this.VideoStarted.Invoke(this,{
Video = res.Video
});
};
this.CurrentVideo = res.Video;
this.VideoStarted.Invoke(this,{
Video = res.Video
});
res.Start();
res.Start();
this.VideoEnded.Invoke(this,{
Video = res.Video
});
this.VideoEnded.Invoke(this,{
Video = res.Video
});
}
} catch(ex) {
try{
this.LOG($"Exception caught on download thread: {e}");
}catch(ex2){}
}
}
}
@@ -971,12 +1112,19 @@ class TYTD.Downloader {
}
return FS.ReadAllBytes(this.Storage,/"Streams"/"nullthumb.jpg");
}
private OpenDB()
/^
Open the database
^/
public OpenDB()
{
var dbFile = this.DatabaseDirectory / "tytd.db";
return Sqlite.Open(dbFile);
}
/^
Get whether package is installed
version must be the current version as a Version not string
returns 0 if not, 1 if installed or 2 if can update
^/
public PackageState(name, version)
{
each(var item : this.Plugins)
@@ -994,6 +1142,10 @@ class TYTD.Downloader {
this.PackageManager.DownloadPlugin(dir,name,version);
this.LoadPlugins();
}
/^
Install plugin
version must be a Version not a String
^/
public PackageInstall(name, version)
{
@@ -1013,7 +1165,9 @@ class TYTD.Downloader {
this.PackageDownload(name, version);
}
/^
Uninstall plugin
^/
public PackageUninstall(name)
{
each(var item : this.Plugins)
@@ -1029,11 +1183,18 @@ class TYTD.Downloader {
}
}
}
/^
Get the TYTD Tag
^/
public getTYTDTag()
{
return this.Config.TYTDTag ?? "UnknownPC";
}
/^
Download video info if it does not exist
pass in a video id or url
^/
public PutVideoInfoIfNotExists(vid)
{
var id = TYTD.GetVideoId(vid);
@@ -1048,13 +1209,22 @@ class TYTD.Downloader {
}
}
/^
Put video info from info into database
^/
public PutVideoInfo(info)
{
this.Muxex.Lock();
var db = this.OpenDB();
var d = $"INSERT INTO videos (videoId,title,lengthSeconds,keywords,channelId,shortDescription,viewCount,author,addDate,tytdTag) VALUES ({Sqlite.Escape(info.videoId)},{Sqlite.Escape(info.title)},{info.lengthSeconds},{Sqlite.Escape(info.keywords.ToString())},{Sqlite.Escape(info.channelId)},{Sqlite.Escape(info.shortDescription)},{info.viewCount},{Sqlite.Escape(info.author)},{DateTime.NowEpoch},{Sqlite.Escape(this.TYTDTag)});";
Sqlite.Exec(db, d);
var keywords = info.keywords;
if(TypeOf(keywords) != "List") keywords = [];
var keywordsStr = Sqlite.Escape(Json.Encode(keywords));
var d = $"INSERT INTO videos (videoId,title,lengthSeconds,keywords,channelId,shortDescription,viewCount,author,addDate,tytdTag) VALUES ({Sqlite.Escape(info.videoId)},{Sqlite.Escape(info.title)},{info.lengthSeconds},{keywordsStr},{Sqlite.Escape(info.channelId)},{Sqlite.Escape(info.shortDescription)},{info.viewCount},{Sqlite.Escape(info.author)},{DateTime.NowEpoch},{Sqlite.Escape(this.TYTDTag)});";
Console.WriteLine(Sqlite.Exec(db, d));
Sqlite.Exec(db, $"INSERT INTO channels (channelId,title) VALUES ({Sqlite.Escape(info.channelId)},{Sqlite.Escape(info.author)});");
Sqlite.Close(db);
this.Mutex.Unlock();
@@ -1108,6 +1278,7 @@ class TYTD.Downloader {
return config[0].value;
}
}
private DownloadChannelThumbInternal(channelId, thumbnail_url)
{
var id = TYTD.GetChannelId(channelId);
@@ -1286,7 +1457,9 @@ class TYTD.Downloader {
}
return {items};
}
/^
Make a video manifest request
^/
public ManifestRequest(vid)
{
var id = TYTD.GetVideoId(vid);
@@ -1509,7 +1682,6 @@ class TYTD.Downloader {
private rlm=new Muxex();
private RateLimit()
{
this.rlm.Lock();
@@ -1530,4 +1702,19 @@ class TYTD.Downloader {
this.lastRequest = curRequest;
this.rlm.Unlock();
}
}
/^
Send the database as http response
^/
public SendDatabase(ctx)
{
this.Mutex.Lock();
try {
var strm = FS.Local.OpenFile(this.DatabaseDirectory/"tytd.db","rb");
ctx.SendStream(strm);
strm.Close();
}catch(ex) {
Console.WriteLine($"ERROR: {ex}");
}
this.Mutex.Unlock();
}
}

View File

@@ -27,6 +27,7 @@ class TYTD.AOVideoDownload : IVideoDownload {
var req = this.tytd.ManifestRequest(id).playerResponse;
this.info.Title = req.videoDetails.title;
tytd.LOG($"Downloading: {this.info.Title} with id: {id} Highest Audio");
this.info.Channel = req.videoDetails.author;
this.info.ChannelId = req.videoDetails.channelId;

View File

@@ -35,6 +35,7 @@ class TYTD.NoConvertVideoDownload : IVideoDownload {
this.info.Title = req.videoDetails.title;
tytd.LOG($"Downloading: {this.info.Title} with id: {id} Highest Video/Audio");
this.info.Channel = req.videoDetails.author;
this.info.ChannelId = req.videoDetails.channelId;

View File

@@ -27,6 +27,7 @@ class TYTD.SDVideoDownload : IVideoDownload {
var req = this.tytd.ManifestRequest(id).playerResponse;
this.info.Title = req.videoDetails.title;
tytd.LOG($"Downloading: {this.info.Title} with id: {id} LowVideo");
this.info.Channel = req.videoDetails.author;
this.info.ChannelId = req.videoDetails.channelId;

View File

@@ -29,7 +29,7 @@ class TYTD.VOVideoDownload : IVideoDownload {
this.info.Title = req.videoDetails.title;
this.info.Channel = req.videoDetails.author;
this.info.ChannelId = req.videoDetails.channelId;
tytd.LOG($"Downloading: {this.info.Title} with id: {id} Highest Video");
this.tytd.PutVideoInfo(req.videoDetails);
var width = 0;