diff --git a/api/README.md b/api/README.md index c34add7f..3ceec038 100644 --- a/api/README.md +++ b/api/README.md @@ -36,7 +36,7 @@ if the desired service isn't supported yet, feel free to create an appropriate i | twitch clips | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | twitter/x | ✅ | ✅ | ✅ | ❌ | ➖ | ➖ | | vimeo | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | -| vk videos & clips | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | +| vk videos & clips | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | | youtube | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | emoji | meaning | diff --git a/api/src/processing/match.js b/api/src/processing/match.js index 4eb10710..71294165 100644 --- a/api/src/processing/match.js +++ b/api/src/processing/match.js @@ -103,6 +103,9 @@ export default async function({ host, patternMatch, params, authType }) { accessKey: patternMatch.accessKey, quality: params.videoQuality, 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 9365c38d..d386d7a9 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", "youtube"]); +export const captionOnlyServices = new Set(["loom", "rutube", "vk", "youtube"]); export const hlsExceptions = new Set(["dailymotion", "vimeo", "rutube", "bsky", "youtube"]); export const services = { diff --git a/api/src/processing/services/vk.js b/api/src/processing/services/vk.js index 4e2d8548..381498ad 100644 --- a/api/src/processing/services/vk.js +++ b/api/src/processing/services/vk.js @@ -1,4 +1,8 @@ import { env } from "../../config.js"; +import { + createCaptionResponse, + matchesCaptionLanguage, +} from "../helpers/captions.js"; const resolutions = ["2160", "1440", "1080", "720", "480", "360", "240", "144"]; @@ -80,7 +84,16 @@ const getVideo = async (ownerId, videoId, accessKey) => { return video; } -export default async function ({ ownerId, videoId, accessKey, quality, subtitleLang }) { +export default async function ({ + ownerId, + videoId, + accessKey, + quality, + subtitleLang, + isCaptionOnly, + captionLanguage, + captionFormat, +}) { const token = await getToken(); if (!token) return { error: "fetch.fail" }; @@ -103,6 +116,26 @@ export default async function ({ ownerId, videoId, accessKey, quality, subtitleL return { error: "content.video.unavailable" }; } + if (isCaptionOnly) { + const subtitle = video.subtitles?.find(subtitle => + subtitle.title?.endsWith(".vtt") + && matchesCaptionLanguage(subtitle.lang, captionLanguage) + ); + if (!subtitle) return { error: "fetch.empty" }; + + const id = `${ownerId}_${videoId}${accessKey ? `_${accessKey}` : ''}`; + return createCaptionResponse({ + url: subtitle.url, + format: captionFormat, + language: subtitle.lang, + service: "vk", + id, + title: video.title?.trim(), + source: `https://vk.com/video${id}`, + headers: { "user-agent": vkClientAgent }, + }); + } + if (!video.files || !video.duration) { return { error: "fetch.fail" }; } diff --git a/api/src/util/tests/vk.json b/api/src/util/tests/vk.json index 71720af5..24ae60a2 100644 --- a/api/src/util/tests/vk.json +++ b/api/src/util/tests/vk.json @@ -61,6 +61,19 @@ "status": "tunnel" } }, + { + "name": "standalone transcript", + "url": "https://vkvideo.ru/video-26006257_456245538", + "params": { + "downloadMode": "captions", + "captionFormat": "txt", + "captionLanguage": "ru" + }, + "expected": { + "code": 200, + "status": "tunnel" + } + }, { "name": "ancient video (fallback to 240p)", "url": "https://vk.com/video-1959_28496479",