Skip to content

Commit

Permalink
vimeo: use api to get video info, direct download if possible (#612)
Browse files Browse the repository at this point in the history
  • Loading branch information
dumbmoron committed Jul 7, 2024
1 parent eb65e81 commit 216529b
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 25 deletions.
119 changes: 95 additions & 24 deletions src/modules/processing/services/vimeo.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { env } from "../../config.js";
import { cleanString } from '../../sub/utils.js';
import { cleanString, merge } from '../../sub/utils.js';

import HLS from "hls-parser";

Expand All @@ -16,32 +16,70 @@ const resolutionMatch = {
"426": 240
}

export default async function(obj) {
let quality = obj.quality === "max" ? 9000 : Number(obj.quality);
if (quality < 240) quality = 240;
if (!quality || obj.isAudioOnly) quality = 9000;
const requestApiInfo = (videoId, password) => {
if (password) {
videoId += `:${password}`
}

return fetch(
`https://api.vimeo.com/videos/${videoId}`,
{
headers: {
Accept: 'application/vnd.vimeo.*+json; version=3.4.2',
'User-Agent': 'Vimeo/10.19.0 (com.vimeo; build:101900.57.0; iOS 17.5.1) Alamofire/5.9.0 VimeoNetworking/5.0.0',
Authorization: 'Basic MTMxNzViY2Y0NDE0YTQ5YzhjZTc0YmU0NjVjNDQxYzNkYWVjOWRlOTpHKzRvMmgzVUh4UkxjdU5FRW80cDNDbDhDWGR5dVJLNUJZZ055dHBHTTB4V1VzaG41bEx1a2hiN0NWYWNUcldSSW53dzRUdFRYZlJEZmFoTTArOTBUZkJHS3R4V2llYU04Qnl1bERSWWxUdXRidjNqR2J4SHFpVmtFSUcyRktuQw==',
'Accept-Language': 'en'
}
}
)
.then(a => a.json())
.catch(() => {});
}

const compareQuality = (rendition, requestedQuality) => {
const quality = parseInt(rendition);
return Math.abs(quality - requestedQuality);
}

const getDirectLink = (data, quality) => {
if (!data.files) return;

const url = new URL(`https://player.vimeo.com/video/${obj.id}/config`);
if (obj.password) {
url.searchParams.set('h', obj.password);
const match = data.files
.filter(f => f.rendition?.endsWith('p'))
.reduce((prev, next) => {
const delta = {
prev: compareQuality(prev.rendition, quality),
next: compareQuality(next.rendition, quality)
};

return delta.prev < delta.next ? prev : next;
});

if (!match) return;

return {
urls: match.link,
filenameAttributes: {
resolution: `${match.width}x${match.height}`,
qualityLabel: match.rendition,
extension: "mp4"
}
}
}

const getHLS = async (configURL, obj) => {
if (!configURL) return;

const api = await fetch(url)
const api = await fetch(configURL)
.then(r => r.json())
.catch(() => {});

if (!api) return { error: 'ErrorCouldntFetch' };

const fileMetadata = {
title: cleanString(api.video.title.trim()),
artist: cleanString(api.video.owner.name.trim()),
}

if (api.video?.duration > env.durationLimit)
if (api.video?.duration > env.durationLimit) {
return { error: ['ErrorLengthLimit', env.durationLimit / 60] };
}

const urlMasterHLS = api.request?.files?.hls?.cdns?.akfire_interconnect_quic?.url;

if (!urlMasterHLS) return { error: 'ErrorCouldntFetch' }

const masterHLS = await fetch(urlMasterHLS)
Expand All @@ -57,9 +95,9 @@ export default async function(obj) {

let bestQuality;

if (quality < resolutionMatch[variants[0]?.resolution?.width]) {
if (obj.quality < resolutionMatch[variants[0]?.resolution?.width]) {
bestQuality = variants.find(v =>
(quality === resolutionMatch[v.resolution.width])
(obj.quality === resolutionMatch[v.resolution.width])
);
}

Expand All @@ -84,15 +122,48 @@ export default async function(obj) {
return {
urls,
isM3U8: true,
fileMetadata: fileMetadata,
filenameAttributes: {
service: "vimeo",
id: obj.id,
title: fileMetadata.title,
author: fileMetadata.artist,
resolution: `${bestQuality.resolution.width}x${bestQuality.resolution.height}`,
qualityLabel: `${resolutionMatch[bestQuality.resolution.width]}p`,
extension: "mp4"
}
}
}

export default async function(obj) {
let quality = obj.quality === "max" ? 9000 : Number(obj.quality);
if (quality < 240) quality = 240;
if (!quality || obj.isAudioOnly) quality = 9000;

const info = await requestApiInfo(obj.id, obj.password);
let response;

if (obj.isAudioOnly) {
response = await getHLS(info.config_url, { ...obj, quality });
}

if (!response) response = getDirectLink(info, quality);
if (!response) response = { error: 'ErrorEmptyDownload' };

if (response.error) {
return response;
}

const fileMetadata = {
title: cleanString(info.name),
artist: cleanString(info.user.name),
};

return merge(
{
fileMetadata,
filenameAttributes: {
service: "vimeo",
id: obj.id,
title: fileMetadata.title,
author: fileMetadata.artist,
}
},
response
);
}
14 changes: 14 additions & 0 deletions src/modules/sub/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,17 @@ export function cleanHTML(html) {
clean = clean.replace(/\n/g, '');
return clean
}

export function merge(a, b) {
for (const k of Object.keys(b)) {
if (Array.isArray(b[k])) {
a[k] = [...(a[k] ?? []), ...b[k]];
} else if (typeof b[k] === 'object') {
a[k] = merge(a[k], b[k]);
} else {
a[k] = b[k];
}
}

return a;
}
2 changes: 1 addition & 1 deletion src/util/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@
"params": {},
"expected": {
"code": 200,
"status": "stream"
"status": "redirect"
}
}],
"reddit": [{
Expand Down

0 comments on commit 216529b

Please sign in to comment.