Apify SRT Subtitle Generator 為 65 秒 TikTok 影片加字幕收費 $0.11,同一分鐘音訊檔只需 $0.011
Subtitling a 65-Second TikTok Cost $0.11. The Same Minute as a File Cost $0.011
作者在同一次測試中使用 Apify 的 SRT Subtitle Generator,為 65.21 秒 TikTok 影片加字幕收費 $0.110,60.01 秒 m4a 檔則收費 $0.011。TikTok 費用中 82% 用於從頁面取得音訊;直接提供可存取的檔案連結可避開影片連結費,而超過計費分鐘邊界也會增加費用。字幕 cue 時間按辨識片段的字符比例推算,並非逐字時間戳。
I run a visa agency in Bali. The half of my week that is not visas goes into Apify Actors, and one of them makes subtitles: give it a video or audio link and it hands back a finished .srt or .vtt with the timings already aligned, plus the plain transcript and the timed segments behind it.
The interesting thing about that Actor is not the transcription. It is the two inputs that look identical on the invoice line "one minute of speech" and cost ten times apart. I ran both in the same run on 18 October 2026, so the numbers below are one bill, not two estimates.
One run, two kinds of link
{
"urls": [
"https://www.tiktok.com/@tiktok/video/7670293981833612574",
"https://.../audio-samples/sample-1min.m4a"
],
"quality": "fast",
"subtitleFormat": "srt",
"includeSegments": true
}
38 seconds end to end, two rows, both status: "ok". A 65.21-second TikTok and a 60.01-second m4a — near enough the same amount of speech, 183 words against 214.
The charge events the platform recorded for that run:
{ "audio-minute": 3, "file-processed": 2, "video-link": 1, "speaker-minute": 0 }
At the published prices — $0.009 per started minute, $0.002 per file, $0.09 per video link — that splits like this:
| Row | Minutes | File | Video link | Total |
|---|---|---|---|---|
sample-1min.m4a |
1 × $0.009 = $0.009 | $0.002 | — | $0.011 |
| TikTok page | 2 × $0.009 = $0.018 | $0.002 | $0.09 | $0.110 |
Same tool, same engine, same length of speech. One row is ten times the other, and on the expensive row the actual transcription is 16% of what you paid. 82% is getting the audio out of the page.
Why a page costs more than a file
A direct file link is an HTTP GET from the datacentre. tiktok.com/@tiktok/video/... is not a file at all — request it like one and you get HTML. The audio track has to be resolved (yt-dlp) and then pulled through a residential exit node, because the datacentre address gets a blocked page instead of a stream. Residential traffic is billed by the gigabyte — $8/GB at Apify's rate, $1/GB through the cheaper provider the Actor tries first — and that traffic, not the recognition, is what a video link really is.
So the Actor splits the sites by host and charges accordingly:
STREAM_SITES = re.compile(
r'(?:^|\.)(?:youtube\.com|youtu\.be|vimeo\.com|soundcloud\.com|tiktok\.com'
r'|dailymotion\.com|twitch\.tv|facebook\.com|instagram\.com|x\.com|twitter\.com'
r'|rutube\.ru|vk\.com|bilibili\.com)$', re.IGNORECASE)
Host in that list, the row pays the video-link event once. Anything else — your S3 bucket, a CDN, a raw GitHub file, your own media server — never pays it. Which gives you one decision worth making before you write any code: if the file exists anywhere you can link to directly, link to the file. Downloading 500 client videos out of TikTok and subtitling them costs $45 in page resolution before a single minute is transcribed. The same 500 files off your own storage: $1.
The five seconds that cost a whole minute
Look again at why the TikTok row billed two minutes and the m4a billed one. Recognition is charged per started minute, with one second of grace:
BILLING_GRACE_SECONDS = 1.0
def billable_minutes(duration):
return max(1, math.ceil(((duration or 60) - BILLING_GRACE_SECONDS) / 60))
That grace exists because encoders lie slightly: a ten-minute recording routinely comes back as 600.01 seconds, and nobody should pay for an eleventh minute because of a rounding artefact in a container format. My m4a is exactly that case — 60.01 s, billed as one minute.
The TikTok clip is 65.21 s and bills two. Those 5.21 seconds over the boundary cost $0.009 — four and a half times the $0.002 file fee, and almost the entire price of the first row. On a one-off that is noise. On a library of 2,000 short vertical videos that all land just past the minute, it is a line item — and the fix is nothing clever, just knowing it is there before you forecast.
A recognition segment is not a caption
The other half of the job is the shape of the file, and this is where the two rows diverge in a way I did not expect.
The model returns segments — passages between pauses, up to 30 seconds long. Subtitles have a different norm: at most two lines of 42 characters, and a cue that does not sit on screen past about seven seconds. So cues get rebuilt from segments rather than copied from them, splitting on commas and sentence ends and never exceeding two lines.
How much work that rebuild does depends entirely on how the person talks:
| Row | Segments | Avg segment | Longest segment | Cues | Cues per segment |
|---|---|---|---|---|---|
| TikTok (four people, short lines) | 18 | 3.23 s / 52 chars | 6.94 s | 20 | 1.11 |
| Read-aloud prose | 9 | 6.50 s / 128 chars | 21.94 s / 414 chars | 21 | 2.33 |
Conversational video barely needs the splitter: people talk in bursts, the pauses land where captions want to break, and 18 segments become 20 cues. The read-aloud file has one segment holding 414 characters across 21.94 seconds — paste that into an SRT writer unchanged and the viewer gets a paragraph filling the screen for twenty-two seconds. Valid SRT. Unwatchable.
That segment came out as six cues:
17
00:00:42,405 --> 00:00:46,750
fortune, must be in want of a wife.
However little known the feelings or views of
Two honest caveats about those timings, because I would rather you learn them here than from a client.
The boundaries are interpolated, not measured. The API exposes segment timings only, no word timestamps, so a segment's duration is divided between its cues in proportion to characters. The first segment of the prose file runs 0 → 4.58 s and holds 89 characters of cue text; the first cue takes 39 of them, and the split lands at 2.007 s. That number is 4.58 × 39 ÷ 89, not a moment anyone heard. On speech at a steady pace it is within a tenth of a second of right. On speech with a dramatic pause inside one segment, it drifts.
The 42-character line is a target, not a guarantee. Across 34 caption lines in the prose row, four came back at 43, 43, 43 and 45 characters. The wrapper needs a space that leaves both halves under the limit, and a long unbroken clause — that he is considered the rightful property — has none, so it falls back to the nearest space near the middle. Three characters over beats hyphenating or dropping words. The TikTok row's 33 lines all fit, because nobody speaks in Austen clauses on TikTok.
One more thing that will bite you
The row for a social video carries this:
'fileName': 'the Spider-Man stars dropped their superhero dream teams… tell us you....mp3'
For a direct link, fileName is the last path element — clean. For a page, there is no file name to read, so it comes from the page's own title, complete with a U+2026 ellipsis and a truncated tail that leaves four dots before the extension. Do not build your output path from it:
import re, requests
rows = requests.post(
"https://api.apify.com/v2/acts/lergassy~srt-subtitle-generator/run-sync-get-dataset-items",
params={"token": TOKEN},
json={"urls": urls, "quality": "fast", "subtitleFormat": "srt"},
).json()
for i, row in enumerate(rows):
if row["status"] != "ok":
print("failed:", row["source"], row["errorMessage"])
continue
open(f"subs/{i:03d}.srt", "w").write(row["subtitles"])
Name the file from your own identifier and keep source as the join key. Check status before you read subtitles — a file that cannot be fetched comes back as an error row with a reason, and is not charged. There is no per-run fee either, so hundreds of links in one run is the cheap shape; one run per link is not.
The Actor is SRT Subtitle Generator.
When it is the wrong tool. If the video is on YouTube and already has captions, transcribing it is paying to regenerate something the site will hand you for free — a transcript scraper is the right call there, and I keep a separate one for exactly that. If you need word-level or karaoke-style alignment, the cue timings here are interpolated, as above. If you need captions burned into the picture, this returns a file, not a render. Live or streaming audio: this is batch, one row per finished file. And private or signed URLs have to be reachable without a login, or nothing gets fetched at all.
The lesson I keep relearning from my own invoices: the price of a data tool is rarely the thing it is named after. Here the name says transcription and the money says networking. Ask any API you are costing out which byte is expensive, and then check whether you are handing it the expensive shape of input when a cheap one was sitting in your own bucket.
來源:DEV Community · dev.to