aboutsummaryrefslogtreecommitdiffstats
path: root/src/server/YoutubeFallback.hx
diff options
context:
space:
mode:
authorRblSb <msrblsb@gmail.com>2025-01-16 03:07:31 +0300
committerRblSb <msrblsb@gmail.com>2025-01-17 01:00:09 +0300
commitd9ca7beaa9494cf34590853901cf8be44e243775 (patch)
treef09ce979460bdf28363a922298283dfee0c506fb /src/server/YoutubeFallback.hx
parentf84fdc40ba817b6a2d907484b1e1500197ceeafe (diff)
Cache on server feature
Server will download video from supported players and add as raw video to playlist (only youtube is supported for now). Cache for YT player is available after installing optional dependencies, see readme. For cache size see `cacheStorageLimitGiB ` in config. There is also minor ux improvement, latest checkbox states will be keeped in local storage now.
Diffstat (limited to 'src/server/YoutubeFallback.hx')
-rw-r--r--src/server/YoutubeFallback.hx142
1 files changed, 0 insertions, 142 deletions
diff --git a/src/server/YoutubeFallback.hx b/src/server/YoutubeFallback.hx
deleted file mode 100644
index 5360283..0000000
--- a/src/server/YoutubeFallback.hx
+++ /dev/null
@@ -1,142 +0,0 @@
-package server;
-
-import haxe.Json;
-import js.lib.Function;
-import js.lib.Object;
-import js.lib.Promise;
-import js.node.Https.Https;
-import js.node.Https.HttpsRequestOptions;
-import js.node.url.URLSearchParams;
-import utils.YoutubeUtils;
-
-class YoutubeFallback {
- static function httpsGet(
- url:String,
- ?options:HttpsRequestOptions,
- ?callback:(status:Int, data:String) -> Void
- ):Void {
- final request = Https.get(url, options, res -> {
- var data = "";
- res.on("data", chunk -> data += chunk.toString());
- res.on("end", () -> callback(res.statusCode, data));
- });
- request.on("error", err -> {
- trace(url);
- trace("request error: ", err);
- });
- }
-
- public static function resolvePlayerResponse(watchHtml:String):String {
- if (watchHtml == null) return "";
- final resReg = ~/ytInitialPlayerResponse = (.*)}}};/;
- var matches = resReg.match(watchHtml);
- return matches ? resReg.matched(1) + "}}}" : "";
- }
-
- public static function resoleM3U8Link(watchHtml:String):Null<String> {
- if (watchHtml == null) return null;
- final hlsReg = ~/hlsManifestUrl":"(.*\/file\/index\.m3u8)/;
- return hlsReg.match(watchHtml) ? hlsReg.matched(1) : null;
- }
-
- public static function buildDecoder(watchHtml:String, callback:(decoder:(cipher:String) -> String) -> Void):Void {
- if (watchHtml == null) return callback(null);
-
- final jsFileUrlReg = ~/\/s\/player\/[A-Za-z0-9]+\/[A-Za-z0-9_.]+\/[A-Za-z0-9_]+\/base\.js/;
- if (!jsFileUrlReg.match(watchHtml)) return callback(null);
-
- final url = "https://www.youtube.com" + jsFileUrlReg.matched(0);
- httpsGet(url, {}, (status, jsFileContent) -> {
- final funcReg = ~/function.*\.split\(""\).*\.join\(""\)}/;
- if (!funcReg.match(jsFileContent)) return callback(null);
-
- final decodeFunction = funcReg.matched(0);
- final varNameReg = ~/\.split\(""\);([a-zA-Z0-9]+)\./i;
- if (!varNameReg.match(decodeFunction)) return callback(null);
-
- final varStartIndex = jsFileContent.indexOf("var " + varNameReg.matched(1) + "={");
- if (varStartIndex < 0) return callback(null);
-
- final varEndIndex = jsFileContent.indexOf("}};", varStartIndex);
- if (varEndIndex < 0) return callback(null);
-
- final varDeclares = jsFileContent.substring(varStartIndex, varEndIndex + 3);
- if (varDeclares.length == 0) return callback(null);
-
- callback(signatureCipher -> {
- final params = new URLSearchParams(signatureCipher);
- final obj = Object.fromEntries(params);
- final signature = obj.s;
- final signatureParam = obj.sp ?? "signature";
- final url = obj.url;
- final decodedSignature = new Function('
- "use strict";
- $varDeclares
- return ($decodeFunction)("$signature");
- ').call(null);
- return '$url&$signatureParam=${untyped encodeURIComponent(decodedSignature)}';
- });
- });
- }
-
- public static function getInfo(url:String, callback:(info:Null<YouTubeVideoInfo>) -> Void):Void {
- final videoId = YoutubeUtils.extractVideoId(url);
- if (videoId.length == 0) {
- trace("youtube videoId is not found");
- return callback(null);
- }
-
- final url = 'https://www.youtube.com/watch?v=$videoId';
- httpsGet(url, {}, (status, data) -> {
- if (status != 200 || data.length == 0) {
- trace("Cannot get youtube video response");
- return callback(null);
- }
-
- final ytInitialPlayerResponse = resolvePlayerResponse(data);
- final parsedResponse = Json.parse(ytInitialPlayerResponse);
- final streamingData:YouTubeVideoInfo = parsedResponse.streamingData ?? cast {};
- streamingData.formats ??= [];
- streamingData.adaptiveFormats ??= [];
- var formats:Array<YoutubeVideoFormat> = streamingData.formats.concat(streamingData.adaptiveFormats);
-
- final promises:Array<Promise<Any>> = [];
-
- final isEncryptedVideo = formats.exists(it -> it.signatureCipher != null);
- if (isEncryptedVideo) {
- final promise = new Promise((resolve, reject) -> {
- buildDecoder(data, decoder -> {
- if (decoder != null) {
- formats = formats.map(item -> {
- if (item.url != null || item.signatureCipher == null) return item;
-
- item.url = decoder(item.signatureCipher);
- item.signatureCipher = null;
- return item;
- });
- }
- resolve(null);
- });
- });
- promises.push(promise);
- }
-
- Promise.all(promises).then(_ -> {
- final result:YouTubeVideoInfo = {
- videoDetails: parsedResponse.videoDetails ?? cast {},
- formats: formats.filter(format -> format.url != null)
- };
- if (result.videoDetails.isLiveContent) {
- final m3u8Link = resoleM3U8Link(data);
- try {
- result.liveData = {
- manifestUrl: m3u8Link,
- // data: m3u8Parser.getResult()
- };
- }
- }
- callback(result);
- });
- });
- }
-}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage