From b585d371b327afbafe76f3f6123d7c09f694359b Mon Sep 17 00:00:00 2001 From: lmdev <138343225+lmdevv@users.noreply.github.com> Date: Fri, 4 Sep 2026 00:39:24 -0400 Subject: [PATCH] api/tiktok: add transcript-only downloads --- api/README.md | 2 +- api/src/processing/match.js | 3 +++ api/src/processing/service-config.js | 2 +- api/src/processing/services/tiktok.js | 31 +++++++++++++++++++++++++++ api/src/util/tests/tiktok.json | 15 ++++++++++++- 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/api/README.md b/api/README.md index 9932d35c..a700bf75 100644 --- a/api/README.md +++ b/api/README.md @@ -31,7 +31,7 @@ if the desired service isn't supported yet, feel free to create an appropriate i | snapchat | ✅ | ✅ | ✅ | ❌ | ➖ | ➖ | | soundcloud | ➖ | ✅ | ➖ | ❌ | ✅ | ✅ | | streamable | ✅ | ✅ | ✅ | ❌ | ➖ | ➖ | -| tiktok | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +| tiktok | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | | tumblr | ✅ | ✅ | ✅ | ❌ | ➖ | ➖ | | twitch clips | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | twitter/x | ✅ | ✅ | ✅ | ❌ | ➖ | ➖ | diff --git a/api/src/processing/match.js b/api/src/processing/match.js index c2fe2135..3b95364a 100644 --- a/api/src/processing/match.js +++ b/api/src/processing/match.js @@ -167,6 +167,9 @@ export default async function({ host, patternMatch, params, authType }) { h265: params.allowH265, alwaysProxy: params.alwaysProxy, 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 632b7a5f..04679ad8 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", "vimeo", "vk", "youtube"]); +export const captionOnlyServices = new Set(["loom", "rutube", "tiktok", "vimeo", "vk", "youtube"]); export const hlsExceptions = new Set(["dailymotion", "vimeo", "rutube", "bsky", "youtube"]); export const services = { diff --git a/api/src/processing/services/tiktok.js b/api/src/processing/services/tiktok.js index 1e1605e0..01a21bb4 100644 --- a/api/src/processing/services/tiktok.js +++ b/api/src/processing/services/tiktok.js @@ -5,6 +5,10 @@ import { genericUserAgent } from "../../config.js"; import { updateCookie } from "../cookie/manager.js"; import { createStream } from "../../stream/manage.js"; import { convertLanguageCode } from "../../misc/language-codes.js"; +import { + createCaptionResponse, + matchesCaptionLanguage, +} from "../helpers/captions.js"; const shortDomain = "https://vt.tiktok.com/"; @@ -72,6 +76,33 @@ export default async function(obj) { return { error: "fetch.empty" }; } + if (obj.isCaptionOnly) { + const requestedLanguage = convertLanguageCode(obj.captionLanguage) + || obj.captionLanguage; + const subtitle = detail.video?.subtitleInfos?.find(subtitle => + subtitle.Format === "webvtt" + && matchesCaptionLanguage(subtitle.LanguageCodeName, requestedLanguage) + ); + if (!subtitle) return { error: "fetch.empty" }; + + const language = convertLanguageCode(subtitle.LanguageCodeName) + || subtitle.LanguageCodeName; + return createCaptionResponse({ + url: subtitle.Url, + format: obj.captionFormat, + language, + service: "tiktok", + id: postId, + title: detail.desc?.trim(), + author: detail.author.uniqueId, + source: `https://www.tiktok.com/@${detail.author.uniqueId}/video/${postId}`, + headers: { + cookie: cookie.toString(), + "user-agent": genericUserAgent, + }, + }); + } + let video, videoFilename, audioFilename, audio, images, filenameBase = `tiktok_${detail.author?.uniqueId}_${postId}`, bestAudio; // will get defaulted to m4a later on in match-action diff --git a/api/src/util/tests/tiktok.json b/api/src/util/tests/tiktok.json index c8dbce8c..bf39bc99 100644 --- a/api/src/util/tests/tiktok.json +++ b/api/src/util/tests/tiktok.json @@ -8,6 +8,19 @@ "status": "tunnel" } }, + { + "name": "standalone English transcript", + "url": "https://www.tiktok.com/@tedtoks/video/7168241231892319531", + "params": { + "downloadMode": "captions", + "captionFormat": "txt", + "captionLanguage": "en" + }, + "expected": { + "code": 200, + "status": "tunnel" + } + }, { "name": "images", "url": "https://www.tiktok.com/@matryoshk4/video/7231234675476532526", @@ -44,4 +57,4 @@ "status": "error" } } -] \ No newline at end of file +]