wip: tested implementation but broken stream

TODO @synzr ffmpeg doesn't work for no reason
This commit is contained in:
mikhail 2024-05-21 18:06:23 +05:00
parent c06fac08c0
commit 449ee1dac3
3 changed files with 19 additions and 21 deletions

View File

@ -85,6 +85,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di
case "video": case "video":
switch (host) { switch (host) {
case "bilibili": case "bilibili":
case "nicovideo":
params = { type: "render" }; params = { type: "render" };
break; break;
case "youtube": case "youtube":

View File

@ -2,9 +2,15 @@ import { genericUserAgent } from "../../config.js";
import HLS from "hls-parser"; import HLS from "hls-parser";
import util from "node:util"; import util from "node:util";
const NICOVIDEO_EMBED_FRONTEND_HEADERS = {
"x-frontend-id": "70",
"x-frontend-version": "0",
"x-niconico-langauge": "ja-jp",
"x-request-with": "https://embed.nicovideo.jp",
};
const NICOVIDEO_EMBED_URL = "https://embed.nicovideo.jp/watch/%s"; const NICOVIDEO_EMBED_URL = "https://embed.nicovideo.jp/watch/%s";
const NICOVIDEO_GUEST_API_URL = const NICOVIDEO_GUEST_API_URL =
// frontend is embed player
"https://www.nicovideo.jp/api/watch/v3_guest/%s?_frontendId=70&_frontendVersion=0&actionTrackId=%s"; "https://www.nicovideo.jp/api/watch/v3_guest/%s?_frontendId=70&_frontendVersion=0&actionTrackId=%s";
const NICOVIDEO_HLS_API_URL = const NICOVIDEO_HLS_API_URL =
"https://nvapi.nicovideo.jp/v1/watch/%s/access-rights/hls?actionTrackId=%s"; "https://nvapi.nicovideo.jp/v1/watch/%s/access-rights/hls?actionTrackId=%s";
@ -12,7 +18,6 @@ const NICOVIDEO_HLS_API_URL =
const ACTION_TRACK_ID_REGEXP = const ACTION_TRACK_ID_REGEXP =
/"actionTrackId":"[A-Za-z0-9]+_[0-9]+"/; /"actionTrackId":"[A-Za-z0-9]+_[0-9]+"/;
// working
async function getActionTrackId(id) { async function getActionTrackId(id) {
const page = await fetch(util.format(NICOVIDEO_EMBED_URL, id), { const page = await fetch(util.format(NICOVIDEO_EMBED_URL, id), {
headers: { "user-agent": genericUserAgent }, headers: { "user-agent": genericUserAgent },
@ -35,7 +40,6 @@ async function getActionTrackId(id) {
return actionTrackId; return actionTrackId;
} }
// not tested
async function fetchGuestData(id, actionTrackId) { async function fetchGuestData(id, actionTrackId) {
const data = await fetch( const data = await fetch(
util.format(NICOVIDEO_GUEST_API_URL, id, actionTrackId), util.format(NICOVIDEO_GUEST_API_URL, id, actionTrackId),
@ -45,7 +49,6 @@ async function fetchGuestData(id, actionTrackId) {
).then((response) => response.json()); ).then((response) => response.json());
if (data?.meta?.status !== 200) { if (data?.meta?.status !== 200) {
console.debug("fetchGuestData():", data)
throw new Error(); throw new Error();
} }
@ -65,7 +68,6 @@ async function fetchGuestData(id, actionTrackId) {
}; };
} }
// not tested
async function fetchContentURL(id, actionTrackId, accessRightKey, outputs) { async function fetchContentURL(id, actionTrackId, accessRightKey, outputs) {
const data = await fetch( const data = await fetch(
util.format(NICOVIDEO_HLS_API_URL, id, actionTrackId), util.format(NICOVIDEO_HLS_API_URL, id, actionTrackId),
@ -75,6 +77,7 @@ async function fetchContentURL(id, actionTrackId, accessRightKey, outputs) {
"user-agent": genericUserAgent, "user-agent": genericUserAgent,
"content-type": "application/json; charset=utf-8", "content-type": "application/json; charset=utf-8",
"x-access-right-key": accessRightKey, "x-access-right-key": accessRightKey,
...NICOVIDEO_EMBED_FRONTEND_HEADERS,
}, },
body: JSON.stringify({ outputs }), body: JSON.stringify({ outputs }),
} }
@ -84,43 +87,37 @@ async function fetchContentURL(id, actionTrackId, accessRightKey, outputs) {
throw new Error(); throw new Error();
} }
return data.data.contentURL; return data.data.contentUrl;
} }
// not tested
async function getHighestQualityHLS(contentURL) { async function getHighestQualityHLS(contentURL) {
const hls = await fetch(contentURL) const hls = await fetch(contentURL)
.then((response) => response.text()) .then((response) => response.text())
.then((response) => HLS.parse(response)); .then((response) => HLS.parse(response));
const highestQualityHLS = hls.variants const highestQualityHLS = hls.variants
.sort((firstVariant, secondVariant) => { .sort(
const firstVariantPixels = (firstVariant, secondVariant) =>
firstVariant.resolution.width * firstVariant.resolution.height; firstVariant.bandwidth - secondVariant.bandwidth
const secondVariantPixels = )
secondVariant.resolution.width * secondVariant.resolution.height;
return firstVariantPixels - secondVariantPixels;
})
.pop(); .pop();
return highestQualityHLS; return [highestQualityHLS.uri, highestQualityHLS.audio.pop().uri];
} }
export default async function nicovideo({ id }) { export default async function nicovideo({ id }) {
try { try {
const actionTrackId = await getActionTrackId(id); const actionTrackId = await getActionTrackId(id);
const highestQualityHLS = await fetchGuestData(actionTrackId) const [video, audio] = await fetchGuestData(id, actionTrackId)
.then(({ accessRightKey, outputs }) => .then(({ accessRightKey, outputs }) =>
fetchContentURL(id, actionTrackId, accessRightKey, outputs) fetchContentURL(id, actionTrackId, accessRightKey, outputs)
) )
.then((contentURL) => getHighestQualityHLS(contentURL)); .then((contentURL) => getHighestQualityHLS(contentURL));
return { return {
urls: highestQualityHLS, urls: [video, audio],
isM3U8: true,
// TODO @synzr get video information from embed page props // TODO @synzr get video information from embed page props
filenameAttributes: { service: "niconico", id }, filenameAttributes: { service: "nicovideo", id, extension: "mp4" },
}; };
} catch (error) { } catch (error) {
return { error: "ErrorEmptyDownload" }; return { error: "ErrorEmptyDownload" };

View File

@ -9,7 +9,7 @@ import { strict as assert } from "assert";
// optional dependency // optional dependency
const freebind = env.freebindCIDR && await import('freebind').catch(() => {}); const freebind = env.freebindCIDR && await import('freebind').catch(() => {});
const M3U_SERVICES = ['dailymotion', 'vimeo', 'rutube']; const M3U_SERVICES = ['dailymotion', 'vimeo', 'rutube', 'nicovideo'];
const streamCache = new NodeCache({ const streamCache = new NodeCache({
stdTTL: env.streamLifespan, stdTTL: env.streamLifespan,