Spaces:
Paused
Paused
| 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 | |
| ); | |
| } | |