Ben 11 달 전
부모
커밋
56d0b5b924
3개의 변경된 파일124개의 추가작업 그리고 40개의 파일을 삭제
  1. 120 36
      js/info.js
  2. 2 2
      js/nginx.conf
  3. 2 2
      js/test.js

+ 120 - 36
js/info.js

@@ -1,4 +1,56 @@
-const request = async (method, url, data = null, headers = {}) => {
+function parseCodecs(format) {
+    const mimeType = format["mimeType"]
+    if (!mimeType) {
+        return {};
+    }
+    const regex = /(?<mimetype>[^/]+\/[^;]+)(?:;\s*codecs="?(?<codecs>[^"]+))?/;
+    const match = mimeType.match(regex);
+    if (!match) {
+        return {};
+    }
+    const codecs = match.groups.codecs;
+    if (!codecs) {
+        return {};
+    }
+
+    const splitCodecs = codecs.trim().replace(/,$/, '').split(',').map(str => str.trim()).filter(Boolean);
+    let vcodec = null;
+    let acodec = null;
+
+    for (const fullCodec of splitCodecs) {
+        const codec = fullCodec.split('.')[0];
+
+        if (['avc1', 'avc2', 'avc3', 'avc4', 'vp9', 'vp8', 'hev1', 'hev2', 'h263', 'h264', 'mp4v', 'hvc1', 'av01', 'theora'].includes(codec)) {
+            if (!vcodec) {
+                vcodec = fullCodec;
+            }
+        } else if (['mp4a', 'opus', 'vorbis', 'mp3', 'aac', 'ac-3', 'ec-3', 'eac3', 'dtsc', 'dtse', 'dtsh', 'dtsl'].includes(codec)) {
+            if (!acodec) {
+                acodec = fullCodec;
+            }
+        } else {
+            console.warn(`WARNING: Unknown codec ${fullCodec}`);
+        }
+    }
+
+    if (!vcodec && !acodec) {
+        if (splitCodecs.length === 2) {
+            return {
+                vcodec: splitCodecs[0],
+                acodec: splitCodecs[1]
+            };
+        }
+    } else {
+        return {
+            vcodec: vcodec,
+            acodec: acodec
+        };
+    }
+
+    return {};
+}
+
+request = async (method, url, data = null, headers = {}) => {
     return new Promise(function (resolve, reject) {
         const xhr = new XMLHttpRequest();
         xhr.open(method, url);
@@ -24,14 +76,48 @@ const request = async (method, url, data = null, headers = {}) => {
     });
 }
 
-getVideoDetail = async (url) => {
+getStringBetween = (string, needleStart, needleEnd, offsetStart = 0, offsetEnd = 0) => {
+    const x = string.indexOf(needleStart);
+    const y = needleEnd ? string.indexOf(needleEnd, x) : string.length;
+    return string.substring(x + needleStart.length + offsetEnd, y + offsetStart);
+}
+
+getUrlFromSignature = (signatureCipher, baseContent) => {
+    const decipher = getDecipherFunction(baseContent);
+    const searchParams = new URLSearchParams(signatureCipher);
+    const [url, signature, sp] = [searchParams.get("url"), searchParams.get("s"), searchParams.get("sp")];
+    return `${url}&${sp}=${decipher(signature)}`;
+}
+
+getDecipherFunction = (string) => {
+    const js = string.replace("var _yt_player={}", "");
+    const top = getStringBetween(js, `a=a.split("")`, "};", 1, -28);
+    const beginningOfFunction =
+        "var " + getStringBetween(top, `a=a.split("")`, "(", 10, 1).split(".")[0] + "=";
+    const side = getStringBetween(js, beginningOfFunction, "};", 2, -beginningOfFunction.length);
+    return eval(side + top);
+};
+
+detail = async (url, local) => {
     const headers = {
         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Safari/537.36',
     }
-    const res = await request('GET', url, null, headers)
+    if (local) {
+        url = url.replace("https://www.youtube.com", "http://127.0.0.1");
+    }
+    const html = await request('GET', url, null, headers);
+
+    let baseJsUrl = `https://www.youtube.com${JSON.parse(html.match(/set\(({.+?})\);/)[1])["PLAYER_JS_URL"]}`
+    if (local) {
+        baseJsUrl = baseJsUrl.replace("https://www.youtube.com", "http://127.0.0.1");
+    }
+
+    console.log(baseJsUrl);
+
+    const baseContent = await request('GET', baseJsUrl, null, headers);
 
     let regex = /var ytInitialPlayerResponse\s*=\s*({.*?});/;
-    let match = res.match(regex);
+    let match = html.match(regex);
     if (!match || !match.length) {
         throw new Error('JSON not found.');
     }
@@ -48,28 +134,23 @@ getVideoDetail = async (url) => {
     }
 
     const formats = []
-    for (const item of ytInitialPlayerResponse["streamingData"]["formats"].concat(ytInitialPlayerResponse["streamingData"]["adaptiveFormats"])) {
-        if (item && item["signatureCipher"] && item["mimeType"]) {
-            let urlRegex = /url=([^&]+)/;
-            let match = item["signatureCipher"].match(urlRegex);
-            if (!match) {
-                continue;
-            }
-            const encodedUrl = match[1];
-            const decodedUrl = decodeURIComponent(encodedUrl);
-
+    for (let format of [].concat(ytInitialPlayerResponse["streamingData"]["formats"]).concat(ytInitialPlayerResponse["streamingData"]["adaptiveFormats"])) {
+        console.log(format);
+        format.url = getUrlFromSignature(format["signatureCipher"], baseContent);
+        const {vcodec, acodec} = parseCodecs(format)
+        if (vcodec && acodec) {
             formats.push({
-                "width": item["width"] + "",
-                "height": item["height"] + "",
-                "type": item["mimeType"],
-                "quality": item["quality"],
-                "itag": item["itag"],
-                "fps": item["fps"] + "",
-                "bitrate": item["bitrate"] + "",
-                "url": decodedUrl,
+                "width": format["width"] + "",
+                "height": format["height"] + "",
+                "type": format["mimeType"],
+                "quality": format["quality"],
+                "itag": format["itag"],
+                "fps": format["fps"] + "",
+                "bitrate": format["bitrate"] + "",
+                "url": format["url"],
                 "ext": "mp4",
-                "vcodec": item["mimeType"],
-                "acodec": item["mimeType"],
+                "vcodec": vcodec,
+                "acodec": acodec,
                 "vbr": "0",
                 "abr": "0",
                 "container": "mp4_dash"
@@ -78,7 +159,7 @@ getVideoDetail = async (url) => {
     }
 
     regex = /var ytInitialData\s*=\s*({.*?});/;
-    match = res.match(regex);
+    match = html.match(regex);
     if (!match || !match.length) {
         throw new Error('JSON not found.');
     }
@@ -91,17 +172,20 @@ getVideoDetail = async (url) => {
     for (const item of ytInitialData["contents"]["twoColumnWatchNextResults"]["secondaryResults"]["secondaryResults"]["results"]) {
         if (item["compactVideoRenderer"]) {
             const recommendVideo = item["compactVideoRenderer"];
-            recommendInfo.push({
-                "type": "gridVideoRenderer",
-                "videoId": recommendVideo["videoId"],
-                "title": recommendVideo["title"]["simpleText"],
-                "thumbnails": recommendVideo["thumbnail"]["thumbnails"],
-                "channelName": recommendVideo["longBylineText"]["runs"][0]["text"],
-                "publishedTimeText": recommendVideo["publishedTimeText"]["simpleText"],
-                "viewCountText": recommendVideo["viewCountText"]["simpleText"],
-                "shortViewCountText": recommendVideo["shortViewCountText"]["simpleText"],
-                "lengthText": recommendVideo["lengthText"]["simpleText"]
-            })
+            console.log(recommendVideo);
+            if (recommendVideo["videoId"]) {
+                recommendInfo.push({
+                    "type": "gridVideoRenderer",
+                    "videoId": recommendVideo["videoId"],
+                    "title": recommendVideo["title"]?.["simpleText"],
+                    "thumbnails": recommendVideo["thumbnail"]?.["thumbnails"],
+                    "channelName": recommendVideo["longBylineText"]?.["runs"]?.[0]?.["text"],
+                    "publishedTimeText": recommendVideo["publishedTimeText"]?.["simpleText"],
+                    "viewCountText": recommendVideo["viewCountText"]?.["simpleText"],
+                    "shortViewCountText": recommendVideo["shortViewCountText"]?.["simpleText"],
+                    "lengthText": recommendVideo["lengthText"]?.["simpleText"]
+                })
+            }
         }
     }
 

+ 2 - 2
js/nginx.conf

@@ -25,8 +25,8 @@ http {
         add_header 'Access-Control-Allow-Origin' '*';
         add_header 'Access-Control-Allow-Methods' '*';
         add_header 'Access-Control-Allow-Headers' '*';
-        location /watch {
-            proxy_pass https://www.youtube.com/watch;
+        location / {
+            proxy_pass https://www.youtube.com/;
         }
     }
 }

+ 2 - 2
js/test.js

@@ -1,6 +1,6 @@
 const id = "7wNb0pHyGuI"
-const url = `http://127.0.0.1/watch?v=${id}`
-getVideoDetail(url)
+const url = `https://www.youtube.com/watch?v=${id}`
+detail(url, true)
     .then(res => {
         console.log(res);
     })