Spaces:
Running
Running
import HLS from "hls-parser"; | |
import { env } from "../../config.js"; | |
import { cleanString, merge } from '../../misc/utils.js'; | |
const resolutionMatch = { | |
"3840": 2160, | |
"2732": 1440, | |
"2560": 1440, | |
"2048": 1080, | |
"1920": 1080, | |
"1366": 720, | |
"1280": 720, | |
"960": 480, | |
"640": 360, | |
"426": 240 | |
} | |
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 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" | |
}, | |
bestAudio: "mp3", | |
} | |
} | |
const getHLS = async (configURL, obj) => { | |
if (!configURL) return; | |
const api = await fetch(configURL) | |
.then(r => r.json()) | |
.catch(() => {}); | |
if (!api) return { error: "fetch.fail" }; | |
if (api.video?.duration > env.durationLimit) { | |
return { error: "content.too_long" }; | |
} | |
const urlMasterHLS = api.request?.files?.hls?.cdns?.akfire_interconnect_quic?.url; | |
if (!urlMasterHLS) return { error: "fetch.fail" }; | |
const masterHLS = await fetch(urlMasterHLS) | |
.then(r => r.text()) | |
.catch(() => {}); | |
if (!masterHLS) return { error: "fetch.fail" }; | |
const variants = HLS.parse(masterHLS)?.variants?.sort( | |
(a, b) => Number(b.bandwidth) - Number(a.bandwidth) | |
); | |
if (!variants || variants.length === 0) return { error: "fetch.empty" }; | |
let bestQuality; | |
if (obj.quality < resolutionMatch[variants[0]?.resolution?.width]) { | |
bestQuality = variants.find(v => | |
(obj.quality === resolutionMatch[v.resolution.width]) | |
); | |
} | |
if (!bestQuality) bestQuality = variants[0]; | |
const expandLink = (path) => { | |
return new URL(path, urlMasterHLS).toString(); | |
}; | |
let urls = expandLink(bestQuality.uri); | |
const audioPath = bestQuality?.audio[0]?.uri; | |
if (audioPath) { | |
urls = [ | |
urls, | |
expandLink(audioPath) | |
] | |
} else if (obj.isAudioOnly) { | |
return { error: "fetch.empty" }; | |
} | |
return { | |
urls, | |
isM3U8: true, | |
filenameAttributes: { | |
resolution: `${bestQuality.resolution.width}x${bestQuality.resolution.height}`, | |
qualityLabel: `${resolutionMatch[bestQuality.resolution.width]}p`, | |
extension: "mp4" | |
}, | |
bestAudio: "mp3", | |
} | |
} | |
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: "fetch.empty" }; | |
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 | |
); | |
} | |