# Audio & Video to Text: YouTube & Podcast Transcription (`firsthand/audio-video-transcription`) Actor

Audio and video transcription of YouTube videos, podcasts, MP3, MP4, Google Drive and Dropbox links. Speech to text (Whisper) with timestamps, SRT and VTT subtitles, speaker labels, translation into up to 5 languages, mention search. Author captions and podcast transcripts are used at no minute fee.

- **URL**: https://apify.com/firsthand/audio-video-transcription.md
- **Developed by:** [Volodymyr Cherniaga](https://apify.com/firsthand) (community)
- **Categories:** AI, Videos, Automation
- **Stats:** 2 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.50 / 1,000 audio minute transcribeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Audio & Video to Text: YouTube & Podcast Transcription

Transcribe audio and video to text from a link. Paste a YouTube video, channel or playlist, a podcast feed,
an Apple Podcasts link, a direct mp3 or mp4, or a TikTok, Instagram or Facebook video, and get the full
transcript with timestamps and ready SRT and VTT subtitles. Hours-long episodes work. Turn on
**Speaker labels** to see who said what, **Translate to** to get the transcript and subtitles in
up to 5 other languages, and **Summary, chapters and key points** to also get a short summary and
timestamped chapters.

$0.005 per audio minute, $0.0045 on Apify's Business plan and higher. Recordings without
speech and links that fail are not charged.

### Why use it

- **One field for every kind of link.** YouTube videos, channels and playlists, files, podcast
  RSS feeds, Apple Podcasts shows and episodes, video pages. The actor works out what is behind
  each link.
- **YouTube without cookies or proxies.** Videos, Shorts, a channel's newest videos or a
  playlist, at the same price per minute, with no per-video fee.
- **Long recordings.** A 3.6-hour podcast episode was transcribed in 9 minutes at the default memory.
  Recordings are split at pauses, so words are not cut in half, and the timestamps run
  continuously through the whole recording.
- **About 100 languages**, detected automatically. Tested on English, Spanish, German and Russian.
- **Speaker labels.** Every line can be marked Speaker 1, Speaker 2 and so on, also in the
  subtitles, with the whole dialogue in one field. A 3.3-hour interview is labelled in one pass, so
  the numbers stay the same from start to end.
- **Whole shows in one run.** A feed or a show link gives its newest episodes; set how many, and
  keep only episodes whose title contains (or does not contain) the words you give.
- **Word timestamps, free.** Every word with its start and end time, for karaoke captions,
  cutting clips or word-accurate search (for recordings the actor transcribes).
- **Find mentions, free.** List brands, names or topics in **Find mentions of**, and every mention
  comes back with its time, the phrase, the speaker and a link to that moment in the recording.
- **Free when the podcast or video already has a transcript.** Many podcast feeds list a
  transcript for each episode (Podcasting 2.0), and many YouTube videos have captions uploaded by
  their author (TED, for example). The actor uses them instead of transcribing, and the audio
  minutes are not charged.
- **Translation** into up to 5 languages in one run, each with its own text and SRT and VTT
  subtitles on the same timestamps.
- **Only new episodes on a schedule.** Turn on **Only new episodes** and every later run
  transcribes and charges only what it has not seen before.
- **No charge for silence or instrumental music.** A recording with no speech comes back with
  `speech: false` and is not billed. Songs with vocals count as speech: the lyrics are transcribed.
- **No API keys, no login, no proxies to set up.**

### What you get

One row per recording.

| Field | What it is |
|---|---|
| `text` | the full transcript |
| `segments` | the transcript in lines, each with `start` and `end` in seconds, and `speaker` with the speaker option; with **Word timestamps**, `words`: every word with its `start` and `end` |
| `wordsIncomplete` | only with **Word timestamps**, when some lines have no `words`: the podcast's own transcript and YouTube author captions have no word times, and rarely a part of a recording comes back from the speech engine without them |
| `srt`, `vtt` | subtitle files as text |
| `language` | detected language code, like `en` |
| `durationSeconds`, `wordCount` | length of the recording and number of words |
| `speech` | `false` when the recording has no speech |
| `title`, `author`, `publishedAt`, `url` | episode or video data, when the source has it |
| `inputUrl`, `sourceType` | the link you gave and what it turned out to be |
| `transcriptSource`, `transcriptFormat` | `publisher` when the podcast's own transcript was used, `youtube-captions` when the YouTube video's author captions were used, and the format (vtt, srt, json, html, plain) |
| `translatedText`, `translatedSegments`, `translatedSrt`, `translatedVtt`, `translationLanguage`, `translationStatus` | with **Translate to**: the first language |
| `translations`, `translationFailedLanguages` | with two or more languages: for each language code (the first one too) its `status` (`ok`, `same_language` or `error`), `text`, and `srt` and `vtt` when the transcript has timestamps and subtitles are on; and the languages that failed |
| `speakerCount`, `speakerTranscript` | with the speaker option: how many voices (for a recording labelled in one pass), and the dialogue as `Speaker 1: ...` lines |
| `summary`, `chapters`, `keyPoints`, `keywords`, `speakersNamed` | with the summary option |
| `chargedMinutes`, `status`, `error` | what was charged, and why a link failed |
| `mentions`, `mentionCounts` | with **Find mentions of**: each mention with `term`, `start`, `end`, `text` (the phrase around it), `speaker` and `link`, and the number per term |
| `trimmedFields` | only on a row that was over Apify's 9 MB limit: the fields left out |

```json
{
  "inputUrl": "https://archive.org/download/gettysburg_johng_librivox/gettysburg_address_64kb.mp3",
  "sourceType": "file",
  "title": "gettysburg_address_64kb.mp3",
  "durationSeconds": 157.9,
  "language": "en",
  "speech": true,
  "wordCount": 304,
  "text": "This is a LibriVox recording. All LibriVox recordings are in the public domain. ... The Gettysburg Address... by Abraham Lincoln. November 19th 1863 Four score and seven years ago our fathers brought forth on this continent a new nation, ...",
  "segments": [
    {"start": 0.33, "end": 1.93, "text": "This is a LibriVox recording."},
    {"start": 2.43, "end": 4.85, "text": "All LibriVox recordings are in the public domain."}
  ],
  "srt": "1\n00:00:00,330 --> 00:00:01,930\nThis is a LibriVox recording.\n\n2\n...",
  "summary": "The Gettysburg Address is a historic speech delivered by Abraham Lincoln during the American Civil War at the dedication of the Soldiers' National Cemetery. ...",
  "chapters": [
    {"start": 0, "title": "Introduction and Historical Context"},
    {"start": 65, "title": "Honoring the Fallen and the Call to Action"}
  ],
  "keywords": ["Gettysburg Address", "Abraham Lincoln", "Civil War", "Liberty", "Equality"],
  "chargedMinutes": 3,
  "status": "ok"
}
```

A real row from a test run, shortened.

### How to transcribe audio and video to text

1. Paste links into **Media links**, one per line.
2. For podcast feeds and channels, set **Episodes per podcast feed or channel** (3 by default).
3. Turn on **Speaker labels**, set **Translate to**, or turn on **Summary, chapters and key points**
   if you need them.
4. Run, then export to JSON, CSV or Excel, or connect via API, Make, Zapier or n8n.

#### Which links work: YouTube, podcasts, MP3, MP4, Google Drive

| Link | Example | What is transcribed |
|---|---|---|
| Audio or video file | `https://example.com/episode.mp3` | the file: mp3, m4a, wav, ogg, flac, mp4, mov, webm and more |
| Google Drive or Dropbox file | a share link, `https://drive.google.com/file/d/...` or `https://www.dropbox.com/scl/fi/...` | the file, when it is shared with anyone who has the link |
| Podcast RSS feed | `https://feeds.megaphone.fm/hubermanlab` | the newest episodes |
| Apple Podcasts show | `https://podcasts.apple.com/us/podcast/the-daily/id1200361736` | the newest episodes, from the show's feed |
| Apple Podcasts episode | a link with `?i=` | that episode |
| YouTube video | `https://www.youtube.com/watch?v=...`, `youtu.be/...`, Shorts, a past live stream | the video's audio |
| YouTube channel | `https://www.youtube.com/@TED` | the channel's newest videos |
| YouTube playlist | `https://www.youtube.com/playlist?list=...` | the first videos of the playlist |
| Video page | TikTok, Instagram, Facebook, SoundCloud, Dailymotion | the video's audio |

Other video sites often work too. A link that fails is not charged. A SoundCloud artist link gives
the artist's newest tracks; TikTok profile links are not supported. A YouTube video link with
`&list=` is one video, not the playlist.

**Not supported:** Vimeo (needs a login), age-restricted and members-only YouTube videos,
Spotify (audio is DRM protected; use the show's RSS feed or Apple Podcasts link instead), private
or login-only videos, and live streams that have not ended. These come back as rows with
`status: "error"` and are not charged.

#### Only new episodes, on a schedule

1. Put a podcast feed, Apple Podcasts show or YouTube channel link into **Media links** and set
   **Episodes per podcast feed or channel**, for example 3.
2. Turn on **Only new episodes** and give the schedule a **List name**, for example `my-podcast`.
3. Save the input as a task and add it to an Apify schedule, for example daily.

The first run transcribes the newest episodes and remembers them. Each later run reads up to 500
newest episodes of the feed (60 newest videos of a YouTube channel) and takes every episode
published since the newest one it remembers, however many came out; episodes on the list are not transcribed, not charged and not added to the
dataset. A YouTube playlist is not watched this way: each run looks at its first videos only, so
videos added at the end of a playlist are not picked up; use the channel link instead. A recording that failed is not remembered and is tried again while it is among the newest
episodes. The list is kept in a key-value store named `audio-video-transcription-monitor` in your
account, in the record `seen-<list name>`; delete the record to start over. Runs with different list
names do not see each other's lists. Do not start two runs with the same list name at the same time:
both would transcribe the same new episode.

If you start runs with a scoped API token (Make, n8n, your own code), the token needs permission to
create, read and write key-value stores.

**Published after** takes only feed episodes and channel videos from a date (`2026-09-01`) or a
window back from now (`30 days`, `2 weeks`). Episodes without a date in the feed are kept. YouTube
gives channel videos an approximate date ("3 days ago"), so a video near the boundary can fall on
either side of it.

#### Only some episodes, by title

**Episode title contains** keeps only episodes whose title contains one of the texts, for example
`interview`; **Episode title does not contain** skips episodes with `trailer`, `rerun` or `best of`
in the title. Both work on podcast feeds, Apple Podcasts shows, YouTube channels and playlists and
SoundCloud artist links. Put one text per line; a comma is part of the text, so `interview, Q&A` in
one line looks for exactly that. A text is found anywhere in the title, also inside words (`ad` also
matches "Bad news", so prefer whole words like `ad break`). Case and accents are ignored (`cafe`
keeps `Café`), and `*` matches anything (`interview*ceo` keeps "Interview with the CEO"). **Episodes per podcast feed or channel**
counts only episodes that pass: with 3 and `interview`, you get the 3 newest interviews, not the
interviews among the 3 newest episodes. Skipped episodes are not charged, not added to the dataset
and not remembered by **Only new episodes**. A YouTube channel or playlist is searched among at least
its 200 newest (first) videos, a SoundCloud artist among at least 200 tracks. A site that lists
recordings without titles gives an error row for that link instead of an empty result. A direct link to one episode or video is never filtered.

#### Find mentions of brands and topics

Put terms in **Find mentions of**, one per line (or several in one line, separated by a comma
and a space). Input:

```json
{"urls": ["https://feeds.megaphone.fm/darknetdiaries", "https://www.youtube.com/watch?v=jNQXAC9IVRw"],
 "maxEpisodesPerFeed": 1, "searchTerms": ["FBI", "hack*", "elephants", "password"]}
```

Each row gets the mentions in that recording. The YouTube row of a test run (17 October 2026):

```json
"mentions": [{"term": "elephants", "start": 0, "end": 4,
  "text": "Alright, so here we are, one of the elephants.",
  "link": "https://www.youtube.com/watch?v=jNQXAC9IVRw&t=0s"}],
"mentionCounts": {"FBI": 0, "hack*": 0, "elephants": 1, "password": 0}
```

The podcast episode of the same run got `{"FBI": 1, "hack*": 35, "elephants": 0, "password": 2}`,
each mention with a link to the episode's audio file at that second, like `...mp3#t=143`, and the
run's `MENTIONS` record had the totals: `{"FBI": 1, "hack*": 35, "elephants": 1, "password": 2}`.

- `text` is the phrase around the mention: the line of the transcript, or up to 150 characters on
  each side when the line is longer.
- `link` opens the recording at that second: a YouTube video (Shorts too) as a normal watch link
  with the time, other recordings as a link to the audio file itself with `#t=` and the second,
  which browser players understand. A video page (TikTok, Instagram and others) and a podcast
  transcript without timestamps give mentions without a link; the latter also without time.
- Case is ignored, and so are accents on Latin letters (`cafe` finds `Café`); `е` finds `ё`. Other
  marks stay, because they change the letter: `мой` does not find `мои`, `パン` does not find `バン`.
- Whole words are matched: `apify` does not find `apifying`, `apif*` does; `*` works anywhere in a
  word. A number is not found inside a bigger one (`100` does not find `1,100` or `100,000`). In
  Chinese, Japanese and Korean a term is found anywhere in the text.
- A term of several words is found also when it runs over several lines of the transcript.
- Up to 50 terms. Search runs on the finished transcript, so it costs nothing extra and works on
  transcribed recordings, podcast transcripts from feeds and YouTube videos.

The run's key-value store has `MENTIONS` with the totals per term for the whole run, and
`MENTIONS.csv` (UTF-8 with BOM, opens in Excel) with the mentions of the run, up to 20,000. A row
lists up to 2,000 mentions (`mentionsTruncated: true` when there were more, and only those go to
the CSV); `mentionCounts` always has the full numbers.

#### Transcribe a file from your computer

Put the file in Google Drive or Dropbox, share it, and paste the share link into **Media links**:

- **Google Drive:** right-click the file, **Share**, set **General access** to **Anyone with the link**,
  then **Copy link**.
- **Dropbox:** **Share**, **Copy link**, and paste the whole link, including the `rlkey=` part.

A file that is not shared comes back as an uncharged row saying how to share it. The actor does not
read files from your Apify storage: it runs with limited permissions and cannot open them.

#### Chain it after another actor

Put the dataset ID of a finished run of a podcast, TikTok or video scraper into **Dataset ID**, or
pass its rows as `datasetItems`. In each row the actor takes the first field that holds a link:
`audioUrl`, `mediaUrl`, `videoUrl`, `episodeUrl`, `url`, `link` and similar. With the "Connect
actor" integration put `{{resource.defaultDatasetId}}` into `datasetId`. One run reads up to
10,000 rows.

#### Input fields of other transcription actors are read too

If your API calls were built for another transcription actor, you can send the same input. Links
are read from `urls`, `mediaUrls`, `audioUrls`, `podcastUrls`, `podcastFeeds`, `sources`,
`media_urls`, `feed_urls` and `episode_urls`, as strings or `{"url": ...}` objects. `maxEpisodesPerShow`, `episodesPerFeed` and `rss_episodes` set episodes
per feed; `maxMinutesPerFile` and `maxDurationMinutes` set the per-recording limit;
`outputFormats` without `srt` and `vtt` turns subtitles off; `speakerLabels` and `diarize` turn
speaker labels on; `translateToEnglish: true` works like **Translate to** `en`; a list of languages in
`subtitleLanguages` or `targetLanguages` works like **Translate to** (in **Translate to** itself,
separate languages with commas);
`usePublisherTranscripts: false` always transcribes the audio; `aiInsights` and `generateChapters` turn the summary on. `newItemsOnly` and
`newEpisodesOnly` turn on only new episodes, with
`watchlistId` as the list name; `episodesSince` and `published_after` work like **Published after**.
`includeWords` and `word_timestamps` work like **Word timestamps**. Vocabulary hints (`vocabulary`,
`vocabularyHint`, `keyterms`, `initialPrompt`) are accepted but ignored: the speech engine does not
take them. `episodeTitleContains` (alternatives separated by `|`), `titleContains` and `title_contains` work like
**Episode title contains**; `episodeTitleExcludes` and `title_excludes` like **Episode title does not
contain**. The output fields are this actor's own (see above).

### How much does transcription cost?

| Event | Price |
|---|---|
| Audio minute transcribed | $0.005 per started minute of a recording with speech; $0.0045 on the Business plan and higher (Apify's GOLD discount tier and above) |
| Speaker labels | $0.003 per started minute, only with the speaker option and only when the labels were made |
| Translation | $0.002 per started minute of the recording for each language, only with **Translate to** and only for languages that were translated |
| AI summary and chapters | $0.01 per started hour of a recording, only with the summary option |

Examples: a 30-minute podcast episode costs $0.15, $0.24 with speaker labels, or $0.27 translated
into Spanish and German; a 3-hour one
$0.90 ($0.81 on Business and higher); a 20-second TikTok $0.005. Apify's $5 free credit covers about
1,000 audio minutes. The other events cost the same on every plan.

Not charged: audio minutes of episodes with the podcast's own transcript and of YouTube videos with
their author's captions, recordings without speech,
links that fail or are not supported, duplicates in one run, summaries and translations that fail
(a failed language is named in the row; the other languages are still charged), and translation of
a recording into its own language (when the language is known: a podcast transcript from a feed
without a language tag is translated and charged for every language you set).

**Cap your spending:** set the run's maximum cost, **Max minutes per run**, or **Max minutes per
recording**. With translation into two or more languages, a recording counts toward **Max minutes
per run** once per language; translated minutes of podcast transcripts count too. The actor checks the limit before each recording and does not start one that would go
over it; that row says `status: "skipped"`.

### Good to know

- **The podcast's own transcript.** When the feed lists one for an episode, the actor takes it in
  the best format offered: VTT or SRT (with timestamps, and speaker names when the transcript has
  them), Podcasting 2.0 JSON, then HTML or plain text (text only, no timestamps or subtitles). It
  is the publisher's text, not ours; turn off **Use the podcast's or video's own transcript** to always
  transcribe the audio. With speaker labels on, the audio is always transcribed. The podcast's
  transcript has no word timestamps (`wordsIncomplete: true`); turn it off to get them. In a test on 16
  podcast feeds, 6 had transcripts (Talk Python, Darknet Diaries, Changelog and a Buzzsprout show
  for all or most episodes, Radio Ambulante and Acquired for some); the biggest shows tested (Lex Fridman, Huberman
  Lab, The Daily) had none.

- **Translation** is made from the finished transcript by GPT-6 Luna through OpenRouter, in batches
  of lines, so each translated line keeps its timestamps and speaker. Tested Spanish to English
  and English to Russian; 15 minutes of speech took about 20 to 50 seconds to translate. A
  podcast transcript without timestamps (HTML or text) is translated by paragraphs and gets no
  translated subtitles. Several languages are translated at the same time, two at once; a language
  given twice (`es` and `Spanish`) is translated once, regional variants are one language (`pt-BR`
  and `pt-PT` give Portuguese, `zh` is Simplified Chinese), and languages after the fifth are
  skipped with a warning in the log. A row over Apify's 9 MB limit (five translations of a recording
  of about five hours or more) leaves out repeated data first: the copy of the first language in
  `translations`, then VTT subtitles, segments and SRT subtitles, and names what was left out in
  `trimmedFields`. Translated text is kept; if even that does not fit, the last languages are left
  out, named in `translationFailedLanguages` and not charged.

- Speech is transcribed by Whisper large-v3 turbo. When it returns nothing on a part that has sound,
  a second model checks that part. With speaker labels it is transcribed by ElevenLabs Scribe v2,
  which also tells the voices apart; if Scribe fails, the recording is transcribed by Whisper
  without labels, and the labels are not charged (the row says `speakerError`).

- **YouTube author captions.** When the video's author uploaded captions in the language of the
  video, the row is made from them: text, timestamps and subtitles, `transcriptSource:
  "youtube-captions"`, no audio minutes charged. YouTube's automatic captions are not used (they
  have no punctuation), and neither are captions in another language (those are translations).
  When YouTube does not say the video's language, or the captions are far too short for the
  video, the audio is transcribed as usual. The captions are the author's text, sound notes
  like "(Applause)" included; turn off **Use the podcast's or video's own transcript** to always
  transcribe. With speaker labels on, the audio is transcribed. Summary, translation and mentions
  work as with a podcast's own transcript. In a test of 10 videos (TED talks and tech reviews) on 18 October 2026, 7 came back
  from author captions in 3 to 5 seconds each; the other 3 were transcribed.

- A recording is transcribed up to 600 minutes, or up to **Max minutes per recording**; a longer
  one is transcribed from the start up to the limit and the row says `truncated: true`. A YouTube
  video longer than 10 hours is not downloaded at all and comes back as an uncharged row, unless
  it has author captions (then the captions are used up to the same limit).

- File size: up to about 500 MB per file at the default 1 GB memory. The run's disk grows with its
  memory, so give the run more memory for bigger video files (2 GB at most). A podcast episode is
  usually 30 to 200 MB.

- **YouTube** refuses downloads from cloud servers such as Apify's (in our test 1 video out of 20
  came through), so YouTube audio is downloaded by our own server and passed to the run. The server
  gets only the YouTube link over an encrypted connection, keeps the audio only until the run has
  received it, does not log links, and downloads one video at a time. When YouTube slows it down, those videos come back as uncharged rows that
  say to run again later.

- The audio is sent to a speech recognition service (Whisper, or ElevenLabs Scribe with speaker
  labels, through OpenRouter) to be transcribed. With **Translate to** the transcript text is sent
  to an AI model (GPT-6 Luna, or Gemini when Luna fails, through OpenRouter) to be translated. With the summary option the transcript text is also sent to an AI model (Gemini
  through OpenRouter). Nothing else from your account is sent.

- Timestamps are in seconds from the start of the recording. Pieces are split at pauses, so a
  sentence is rarely cut in two.

- The language is detected for every 10-minute piece, so an English ad at the start of a Spanish
  episode does not turn the episode into English. `language` is the language of most of the speech.
  Set **Language** only if detection gets it wrong.

- Speaker labels are numbers, not names: Speaker 1 is the first voice heard. With the summary
  option, `speakersNamed` lists the people the speakers name. A recording up to about 3.3 hours is
  labelled in one pass; a longer one in parts of up to 3.3 hours, numbered separately in each part
  (Speaker 1 (part 2) and so on, the row says so in `speakerNote`). A line is now and then given to the wrong voice, and
  Scribe writes numbers as words ("eighteen sixty-three").

- In test runs speaker labels were right on an 18-minute two-person interview, told apart the
  host, the reporter, the guest and the ad voice on a 30-minute news podcast, and labelled a
  3.3-hour interview as two voices in one pass.

- The default run memory is 1 GB. In test runs a 3.6-hour episode took 9 minutes from start to
  finish, and files of 2 to 18 minutes took 10 to 20 seconds.

- A run times out after 2 hours by default. For a big batch, such as a whole podcast back
  catalogue, raise the run timeout in the run options or split the batch.

### FAQ

**Can I transcribe a whole podcast?** Yes. Put its RSS feed or Apple Podcasts show link in
**Media links** and set **Episodes per podcast feed or channel**, up to 500. Long back catalogues
take time (a 3.6-hour episode is about 9 minutes), so raise the run timeout for big batches.

**Why did my YouTube link fail?** The row's `error` says why. Private, removed, age-restricted
and members-only videos cannot be downloaded without an account, and this actor does not use
yours. Live streams work after they end. When YouTube pauses our downloader for a few minutes, the
row says to run again later. Failed YouTube links are not charged.

**Is my file stored?** Files are downloaded into the run's temporary storage and deleted after the
transcript is made. Only the transcript is saved, to your dataset. With **Only new episodes** the
run also keeps a list of short hashes of the recordings it transcribed, in your account.

Found a bug or need a field? Open an issue on the Issues tab.

# Actor input Schema

## `urls` (type: `array`):

Links to transcribe, one per line. Works with: YouTube videos, Shorts, channels and playlists; direct audio and video files (mp3, m4a, wav, ogg, mp4, webm, mov and more) on any public host; Google Drive and Dropbox files shared with anyone who has the link; podcast RSS feeds (the newest episodes are taken); Apple Podcasts show and episode links; and video pages on sites like TikTok, Instagram, Facebook, SoundCloud and Dailymotion. A SoundCloud artist link gives the newest tracks. Vimeo and Spotify are not supported.

## `maxEpisodesPerFeed` (type: `integer`):

How many of the newest episodes to take from each podcast feed or Apple Podcasts show link, newest videos from a YouTube channel or first videos of a playlist, and tracks from a SoundCloud artist link.

## `titleIncludes` (type: `array`):

Only episodes of feeds, Apple Podcasts shows, YouTube channels and playlists whose title contains one of these texts, one per line, like interview. The text is found anywhere in the title, also inside words (ad also keeps Bad news). Case and accents are ignored; \* matches anything, like interview\*ceo. Episodes per feed counts only the episodes that pass. Direct links to one episode or video are not filtered.

## `titleExcludes` (type: `array`):

Skip episodes whose title contains one of these texts, one per line, like trailer, rerun or best of. The text is found anywhere in the title, also inside words. Skipped episodes are not charged.

## `language` (type: `string`):

Language of the speech as a two-letter code (en, es, de, ru) or a name (Spanish). Leave empty to detect it automatically for every 10-minute piece.

## `usePublisherTranscripts` (type: `boolean`):

When a podcast feed lists a transcript for an episode (Podcasting 2.0), or a YouTube video has captions uploaded by its author in the video's language (not YouTube's automatic captions), they are used instead of transcribing, and the audio minutes are free. Turn off to always transcribe the audio. Speaker labels always transcribe the audio.

## `summarize` (type: `boolean`):

Adds a short summary, timestamped chapters, key points, keywords and the names of people mentioned, written by AI from the transcript. Charged per started hour of recording with speech.

## `speakerLabels` (type: `boolean`):

Marks every line with the person speaking (Speaker 1, Speaker 2 and so on), also in the subtitles, and adds speakerTranscript with the whole dialogue. Speech is then transcribed by ElevenLabs Scribe instead of Whisper. Adds $0.003 per audio minute.

## `translateTo` (type: `string`):

Also translate the transcript into up to 5 languages, separated by commas: codes like en, es, de or names like Spanish. The first language fills translatedText and translated SRT and VTT subtitles with the same timestamps; with two or more, translations has text and subtitles for each language. $0.002 per audio minute per language; a language the recording is already in, or one that fails, is not charged. Empty means no translation.

## `searchTerms` (type: `array`):

Brands, names or topics to find in the transcripts, one per line, like Apify or customer churn. Each mention comes back in mentions with its time, the phrase around it, the speaker and a link that opens the recording at that moment; mentionCounts has the number per term, and the run's totals and a CSV of the mentions are in the key-value store (MENTIONS, MENTIONS.csv). Case and accents on Latin letters are ignored and whole words are matched (Chinese, Japanese, Korean: anywhere); \* matches any letters, like transcri\*. Up to 50 terms. Free.

## `subtitles` (type: `boolean`):

Adds ready subtitle files as text in the srt and vtt fields. Free.

## `includeSegments` (type: `boolean`):

Adds the transcript split into lines with start and end seconds. Free.

## `wordTimestamps` (type: `boolean`):

Add the start and end time of every word inside each segment (segments\[].words), for karaoke captions, clip cutting or word-accurate search. Free. Only for recordings the actor transcribes, not for podcast transcripts taken from the feed or YouTube author captions. Turns segments on.

## `maxMinutesPerItem` (type: `integer`):

Longer recordings are transcribed up to this length and charged only for it; the row says truncated. Empty means up to 600 minutes.

## `maxTotalMinutes` (type: `integer`):

A recording that would take the run over this many charged audio minutes is skipped; shorter ones that still fit are taken. You can also cap spending with the run's maximum cost.

## `onlyNewEpisodes` (type: `boolean`):

Remember what was transcribed and leave it out of later runs with the same list name. Put the actor on a schedule with a podcast feed or a YouTube channel: each run transcribes and charges only episodes it has not seen before, all episodes published since the last run, however many. A recording that failed is tried again while it is among the newest episodes.

## `monitorName` (type: `string`):

Runs with the same name share the list of transcribed recordings, for example one name per podcast schedule. Letters, digits, dashes and underscores. Empty means 'default'.

## `publishedAfter` (type: `string`):

Take only feed episodes published on or after this date (2026-09-01, midnight UTC) or within a window back from now (30 days, 2 weeks, 12 hours; a month is 30 days). Applies to podcast feeds, Apple Podcasts shows and YouTube channels (YouTube gives channel videos approximate dates, like 3 days ago); episodes without a date are kept.

## `datasetId` (type: `string`):

Transcribe the links found in a dataset, for example the output of a podcast, TikTok or video scraper. In each row the actor takes the first field that holds a link (audioUrl, mediaUrl, videoUrl, url and similar). Up to 10,000 rows.

## `datasetItems` (type: `array`):

Rows of another actor's output pasted as JSON; a link is taken from each row the same way as with Dataset ID.

## Actor input object example

```json
{
  "urls": [
    "https://archive.org/download/gettysburg_johng_librivox/gettysburg_address_64kb.mp3"
  ],
  "maxEpisodesPerFeed": 3,
  "usePublisherTranscripts": true,
  "summarize": false,
  "speakerLabels": false,
  "subtitles": true,
  "includeSegments": true,
  "wordTimestamps": false,
  "onlyNewEpisodes": false
}
```

# Actor output Schema

## `transcripts` (type: `string`):

Transcript and recording data for every link.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://archive.org/download/gettysburg_johng_librivox/gettysburg_address_64kb.mp3"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("firsthand/audio-video-transcription").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": ["https://archive.org/download/gettysburg_johng_librivox/gettysburg_address_64kb.mp3"] }

# Run the Actor and wait for it to finish
run = client.actor("firsthand/audio-video-transcription").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://archive.org/download/gettysburg_johng_librivox/gettysburg_address_64kb.mp3"
  ]
}' |
apify call firsthand/audio-video-transcription --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,firsthand/audio-video-transcription"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/fehJLVKhWFSdYCSFT/builds/wJewAxKvSRzdweRZi/openapi.json
