diff --git a/api/README.md b/api/README.md index 3ceec038..9932d35c 100644 --- a/api/README.md +++ b/api/README.md @@ -35,7 +35,7 @@ if the desired service isn't supported yet, feel free to create an appropriate i | tumblr | ✅ | ✅ | ✅ | ❌ | ➖ | ➖ | | twitch clips | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | twitter/x | ✅ | ✅ | ✅ | ❌ | ➖ | ➖ | -| vimeo | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | +| vimeo | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | vk videos & clips | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | | youtube | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | diff --git a/api/src/processing/match.js b/api/src/processing/match.js index 71294165..c2fe2135 100644 --- a/api/src/processing/match.js +++ b/api/src/processing/match.js @@ -185,6 +185,9 @@ export default async function({ host, patternMatch, params, authType }) { quality: params.videoQuality, isAudioOnly, subtitleLang, + isCaptionOnly, + captionLanguage: params.captionLanguage || subtitleLang, + captionFormat: params.captionFormat, }); break; diff --git a/api/src/processing/service-config.js b/api/src/processing/service-config.js index d386d7a9..632b7a5f 100644 --- a/api/src/processing/service-config.js +++ b/api/src/processing/service-config.js @@ -1,7 +1,7 @@ import UrlPattern from "url-pattern"; export const audioIgnore = new Set(["vk", "ok", "loom"]); -export const captionOnlyServices = new Set(["loom", "rutube", "vk", "youtube"]); +export const captionOnlyServices = new Set(["loom", "rutube", "vimeo", "vk", "youtube"]); export const hlsExceptions = new Set(["dailymotion", "vimeo", "rutube", "bsky", "youtube"]); export const services = { diff --git a/api/src/processing/services/vimeo.js b/api/src/processing/services/vimeo.js index c12269bc..a3b54495 100644 --- a/api/src/processing/services/vimeo.js +++ b/api/src/processing/services/vimeo.js @@ -2,6 +2,10 @@ import HLS from "hls-parser"; import { env } from "../../config.js"; import { merge } from '../../misc/utils.js'; import { getCookie } from "../cookie/manager.js"; +import { + createCaptionResponse, + matchesCaptionLanguage, +} from "../helpers/captions.js"; const resolutionMatch = { "3840": 2160, @@ -76,6 +80,15 @@ const compareQuality = (rendition, requestedQuality) => { return Math.abs(quality - requestedQuality); } +const getPlayerConfig = (id, password) => { + const url = new URL(`https://player.vimeo.com/video/${id}/config`); + if (password) url.searchParams.set("h", password); + + return fetch(url) + .then(r => r.json()) + .catch(() => {}); +} + const getDirectLink = async (data, quality, subtitleLang) => { if (!data.files) return; @@ -102,7 +115,9 @@ const getDirectLink = async (data, quality, subtitleLang) => { subtitles = config.request.text_tracks.find( t => t.lang.startsWith(subtitleLang) ); - subtitles = new URL(subtitles.url, "https://player.vimeo.com/").toString(); + if (subtitles) { + subtitles = new URL(subtitles.url, "https://player.vimeo.com/").toString(); + } } } @@ -183,6 +198,26 @@ const getHLS = async (configURL, obj) => { } export default async function(obj) { + if (obj.isCaptionOnly) { + const config = await getPlayerConfig(obj.id, obj.password); + const track = config?.request?.text_tracks?.find(track => + track.url + && matchesCaptionLanguage(track.lang, obj.captionLanguage) + ); + if (!track) return { error: "fetch.empty" }; + + return createCaptionResponse({ + url: new URL(track.url, "https://player.vimeo.com/").toString(), + format: obj.captionFormat, + language: track.lang, + service: "vimeo", + id: obj.id, + title: config.video?.title, + author: config.video?.owner?.name, + source: `https://vimeo.com/${obj.id}`, + }); + } + let quality = obj.quality === "max" ? 9000 : Number(obj.quality); if (quality < 240) quality = 240; if (!quality || obj.isAudioOnly) quality = 9000; diff --git a/api/src/util/tests/vimeo.json b/api/src/util/tests/vimeo.json index e28af888..3668af3b 100644 --- a/api/src/util/tests/vimeo.json +++ b/api/src/util/tests/vimeo.json @@ -21,6 +21,19 @@ "status": "redirect" } }, + { + "name": "standalone English transcript", + "url": "https://vimeo.com/76979871", + "params": { + "downloadMode": "captions", + "captionFormat": "txt", + "captionLanguage": "en" + }, + "expected": { + "code": 200, + "status": "tunnel" + } + }, { "name": "1080p dash parcel", "url": "https://vimeo.com/967252742", @@ -63,4 +76,4 @@ "status": "redirect" } } -] \ No newline at end of file +]