From 44c87e1e509da2147520a28e6b2f8ff232300a4e Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Mon, 2 Mar 2026 02:23:46 -0600 Subject: [PATCH] Make the downloader more resiliant --- Tesses.YouTubeDownloader.Server/cross.json | 2 +- .../src/main.tcross | 11 +++ Tesses.YouTubeDownloader/cross.json | 2 +- .../src/YouTubeDownloader.tcross | 71 ++++++++++++++++++- Tesses.YouTubeDownloader/src/error.tcross | 16 +++++ 5 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 Tesses.YouTubeDownloader/src/error.tcross diff --git a/Tesses.YouTubeDownloader.Server/cross.json b/Tesses.YouTubeDownloader.Server/cross.json index 7b6c4df..266845e 100644 --- a/Tesses.YouTubeDownloader.Server/cross.json +++ b/Tesses.YouTubeDownloader.Server/cross.json @@ -14,6 +14,6 @@ "project_dependencies": [ "..\/Tesses.YouTubeDownloader" ], - "version": "1.0.0.2-prod", + "version": "1.0.0.3-prod", "compTime": "secure" } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/main.tcross b/Tesses.YouTubeDownloader.Server/src/main.tcross index 24cdada..9f3adf0 100644 --- a/Tesses.YouTubeDownloader.Server/src/main.tcross +++ b/Tesses.YouTubeDownloader.Server/src/main.tcross @@ -623,6 +623,17 @@ class TYTDApp { } } + else if(ctx.Path == "/api/v1/manifest.json") + { + const v = ctx.QueryParams.TryGetFirst("v"); + if(TypeIsString(v)) + { + const resp=this.TYTD.ManifestRequest(v); + Console.WriteLine(resp); + ctx.WithMimeType("application/json").SendJson(resp); + return true; + } + } else if(ctx.Path == "/api/v1/personal") { /* diff --git a/Tesses.YouTubeDownloader/cross.json b/Tesses.YouTubeDownloader/cross.json index 9072691..9f56f08 100644 --- a/Tesses.YouTubeDownloader/cross.json +++ b/Tesses.YouTubeDownloader/cross.json @@ -14,6 +14,6 @@ } ], "name": "Tesses.YouTubeDownloader", - "version": "1.0.0.2-prod", + "version": "1.0.0.3-prod", "icon": "icon.png" } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross b/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross index f5096d3..873e5f3 100644 --- a/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross +++ b/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross @@ -1341,11 +1341,51 @@ class TYTD.Downloader { if(e == null) { var req = this.ManifestRequest(id); + this.DownloadCaptions(req); this.PutVideoInfo(req.playerResponse.videoDetails); } } } + private DownloadCaptions(req) + { + const tracks = req.playerResponse.playerCaptionsTracklistRenderer.captionTracks; + if(TypeIsList(tracks)) + { + each(var item : tracks) + { + if(!TypeIsString(item.languageCode)) continue; + if(!TypeIsString(item.baseUrl)) continue; + try { + var path = /"Streams"/id.Substring(0,4) / id.Substring(4) / item.languageCode; + var resp = Net.Http.MakeRequest(url,{FollowRedirects=true}); + if(resp.StatusCode >= 200 && resp.StatusCode <= 299) + { + const strm=this.Storage.OpenFile(path+".xml","wb"); + resp.CopyToStream(strm); + strm.Close(); + } + resp = Net.Http.MakeRequest(url.Replace("fmt=srv3","fmt=vtt"),{FollowRedirects=true}); + if(resp.StatusCode >= 200 && resp.StatusCode <= 299) + { + const strm=this.Storage.OpenFile(path+".vtt","wb"); + resp.CopyToStream(strm); + strm.Close(); + } + resp = Net.Http.MakeRequest(url.Replace("fmt=srv3","fmt=srt"),{FollowRedirects=true}); + if(resp.StatusCode >= 200 && resp.StatusCode <= 299) + { + const strm=this.Storage.OpenFile(path+".srt","wb"); + resp.CopyToStream(strm); + strm.Close(); + } + + } catch(ex) { + + } + } + } + } /^ Put video info from info into database ^/ @@ -1608,6 +1648,7 @@ class TYTD.Downloader { ^/ public ManifestRequest(vid) { + while(true) { var id = TYTD.GetVideoId(vid); if(id == null) return null; TryDownloadVideoThumbnail(id,"0"); @@ -1651,9 +1692,35 @@ class TYTD.Downloader { }; this.RateLimit(); var response = Net.Http.MakeRequest(url,requestData); - if(response.StatusCode < 200 || response.StatusCode > 299) return null; - return Json.Decode(response.ReadAsString()); + Console.WriteLine(response.StatusCode); + + if(response.StatusCode < 200 || response.StatusCode > 299) continue; + const respText = response.ReadAsString(); + const jsonResp = Json.Decode(respText); + if(!TypeIsDictionary(jsonResp.playerResponse)) continue; + + if(TypeIsDictionary(jsonResp.playerResponse.playabilityStatus)) + { + if(jsonResp.playerResponse.playabilityStatus.status == "ERROR") + { + throw new VideoDownloadError(id, jsonResp.playerResponse.playabilityStatus.reason); + } + } else { + throw new VideoDownloadError(id, "playabilityStatus is missing"); + } + if(!TypeIsDictionary(jsonResp.playerResponse.videoDetails)) + { + throw new VideoDownloadError(id, "videoDetails is missing"); + } + + if(!TypeIsList(jsonResp.playerResponse.streamingData.adaptiveFormats)) + { + throw new VideoDownloadError(id, "adaptiveFormats is missing"); + } + + return jsonResp; + } } diff --git a/Tesses.YouTubeDownloader/src/error.tcross b/Tesses.YouTubeDownloader/src/error.tcross new file mode 100644 index 0000000..18cfacd --- /dev/null +++ b/Tesses.YouTubeDownloader/src/error.tcross @@ -0,0 +1,16 @@ +class VideoDownloadError { + public VideoDownloadError(id, error) + { + Id = id; + Error = error; + } + + public Id; + + public Error; + + public ToString() + { + return $"Download error {Error} with Id: {Id}"; + } +} \ No newline at end of file