mirror of
https://github.com/imputnet/cobalt.git
synced 2025-07-18 19:28:29 +00:00
improvement: filenameAttributes and quality picker in nicovideo
This commit is contained in:
parent
60f9f99568
commit
6524d4d44c
@ -187,7 +187,10 @@ export default async function(host, patternMatch, lang, obj) {
|
|||||||
r = await dailymotion(patternMatch);
|
r = await dailymotion(patternMatch);
|
||||||
break;
|
break;
|
||||||
case "nicovideo":
|
case "nicovideo":
|
||||||
r = await nicovideo({ id: patternMatch.id });
|
r = await nicovideo({
|
||||||
|
id: patternMatch.id,
|
||||||
|
quality: obj.vQuality
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return createResponse("error", {
|
return createResponse("error", {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { genericUserAgent } from "../../config.js";
|
import { genericUserAgent } from "../../config.js";
|
||||||
|
import { cleanString } from "../../sub/utils.js";
|
||||||
import HLS from "hls-parser";
|
import HLS from "hls-parser";
|
||||||
import util from "node:util";
|
import util from "node:util";
|
||||||
|
|
||||||
@ -10,34 +11,34 @@ const NICOVIDEO_EMBED_FRONTEND_HEADERS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const NICOVIDEO_EMBED_URL = "https://embed.nicovideo.jp/watch/%s";
|
const NICOVIDEO_EMBED_URL = "https://embed.nicovideo.jp/watch/%s";
|
||||||
|
const NICOVIDEO_AUTHOR_DATA_URL = "https://embed.nicovideo.jp/users/%d";
|
||||||
const NICOVIDEO_GUEST_API_URL =
|
const NICOVIDEO_GUEST_API_URL =
|
||||||
"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";
|
||||||
|
|
||||||
const ACTION_TRACK_ID_REGEXP =
|
async function getBasicVideoInformation(id) {
|
||||||
/"actionTrackId":"[A-Za-z0-9]+_[0-9]+"/;
|
|
||||||
|
|
||||||
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 },
|
||||||
}).then((response) => response.text());
|
}).then((response) => response.text());
|
||||||
|
|
||||||
if (!ACTION_TRACK_ID_REGEXP.test(page)) {
|
const data = JSON.parse(
|
||||||
throw new Error(); // we can't fetch the embed page
|
page
|
||||||
}
|
.split('data-props="')
|
||||||
|
|
||||||
const actionTrackId = page
|
|
||||||
// getting the regexp results
|
|
||||||
.match(ACTION_TRACK_ID_REGEXP)
|
|
||||||
.shift()
|
|
||||||
// getting the actionTrackId field's value
|
|
||||||
.split(":"")
|
|
||||||
.pop()
|
.pop()
|
||||||
// cleaning from double quotation mark
|
.split('" data-style-map="')
|
||||||
.replaceAll(""", "");
|
.shift()
|
||||||
|
.replaceAll(""", '"')
|
||||||
|
);
|
||||||
|
|
||||||
return actionTrackId;
|
const author = await fetch(
|
||||||
|
util.format(NICOVIDEO_AUTHOR_DATA_URL, data.videoUploaderId),
|
||||||
|
{
|
||||||
|
headers: { "user-agent": genericUserAgent },
|
||||||
|
}
|
||||||
|
).then((response) => response.json());
|
||||||
|
|
||||||
|
return { ...data, author };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchGuestData(id, actionTrackId) {
|
async function fetchGuestData(id, actionTrackId) {
|
||||||
@ -90,34 +91,60 @@ async function fetchContentURL(id, actionTrackId, accessRightKey, outputs) {
|
|||||||
return data.data.contentUrl;
|
return data.data.contentUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getHighestQualityHLS(contentURL) {
|
async function getHLSContent(contentURL, quality) {
|
||||||
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 height = quality === "max" ? 9000 : parseInt(quality, 10);
|
||||||
|
let hlsContent = hls.variants.find(
|
||||||
|
(variant) => variant.resolution.height === height
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hlsContent === undefined) {
|
||||||
|
hlsContent = hls.variants
|
||||||
.sort(
|
.sort(
|
||||||
(firstVariant, secondVariant) =>
|
(firstVariant, secondVariant) =>
|
||||||
firstVariant.bandwidth - secondVariant.bandwidth
|
firstVariant.bandwidth - secondVariant.bandwidth
|
||||||
)
|
)
|
||||||
.pop();
|
.shift();
|
||||||
|
|
||||||
return [highestQualityHLS.uri, highestQualityHLS.audio.pop().uri];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function nicovideo({ id }) {
|
return {
|
||||||
|
resolution: hlsContent.resolution,
|
||||||
|
urls: [hlsContent.uri, hlsContent.audio.pop().uri],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @synzr only audio support
|
||||||
|
// TODO @synzr better error handling
|
||||||
|
export default async function nicovideo({ id, quality }) {
|
||||||
try {
|
try {
|
||||||
const actionTrackId = await getActionTrackId(id);
|
const { actionTrackId, title, author } = await getBasicVideoInformation(id);
|
||||||
const [video, audio] = await fetchGuestData(id, actionTrackId)
|
const {
|
||||||
|
resolution,
|
||||||
|
urls: [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) => getHLSContent(contentURL, quality));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
urls: [video, audio],
|
urls: [video, audio],
|
||||||
// TODO @synzr get video information from embed page props
|
filenameAttributes: {
|
||||||
filenameAttributes: { service: "nicovideo", id, extension: "mp4" },
|
service: "nicovideo",
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
author: author.nickname,
|
||||||
|
resolution: `${resolution.width}x${resolution.height}`,
|
||||||
|
qualityLabel: `${resolution.height}p`,
|
||||||
|
extension: "mp4",
|
||||||
|
},
|
||||||
|
fileMetadata: {
|
||||||
|
title: cleanString(title.trim()),
|
||||||
|
artist: cleanString(author.nickname.trim()),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: "ErrorEmptyDownload" };
|
return { error: "ErrorEmptyDownload" };
|
||||||
|
@ -115,6 +115,7 @@
|
|||||||
"alias": "niconico videos",
|
"alias": "niconico videos",
|
||||||
"tld": "jp",
|
"tld": "jp",
|
||||||
"patterns": ["watch/:id"],
|
"patterns": ["watch/:id"],
|
||||||
|
"subdomains": ["www", "sp", "embed"],
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user