# Podcast & Video Transcriber for AI — RAG-ready Text (`tiger19935/podcast-video-transcriber-rag`) Actor

Transcribe podcasts (RSS or Apple Podcasts), audio/video files and TikTok, X, Vimeo, Loom videos into text with timestamps, SRT/VTT and RAG-ready chunks. New-episode monitoring. $0.015 per audio minute.

- **URL**: https://apify.com/tiger19935/podcast-video-transcriber-rag.md
- **Developed by:** [Andrew Boiko](https://apify.com/tiger19935) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.00 / 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

## Podcast & Video Transcriber for AI - RAG-ready Text

Turn **podcast episodes, audio files and videos into clean, timestamped text** that AI agents and vector databases can use right away. Paste a podcast RSS feed, an Apple Podcasts link, an MP3/MP4 URL or a TikTok / X / Vimeo / Loom video - get back the **full transcript, timestamped segments, optional SRT / WebVTT subtitles** and, if you want, a **RAG pack**: time-coded chunks sized for your embedding model, chapters and a short summary.

Schedule it with **Only new episodes** and it becomes a podcast monitor: every new episode of your feeds is transcribed once and only once.

### Who is it for?

- **AI engineers and agent builders** - put spoken content into a RAG pipeline, a vector database or an MCP-connected agent without running Whisper yourself.
- **Content teams** - turn episodes and videos into articles, newsletters, quotes, show notes, captions and clips.
- **Researchers, analysts and PR teams** - search what people said on podcasts, track mentions of a brand, a competitor or a topic.
- **No-code builders** - "new episode -> transcript -> Notion / Google Sheets / Slack" with Make, Zapier or n8n.

### What can you feed it?

| Source | How it is handled |
|---|---|
| **Podcast RSS feeds** | Newest episodes of every feed (also very large feeds with 1,000+ episodes), with title, publication date, GUID and show title. |
| **Apple Podcasts** show links (`podcasts.apple.com/.../id123456789`) | Resolved to the show's public RSS feed through Apple's lookup. Links to a single episode are treated as the show link (newest episodes of that show). |
| **Direct audio / video files** | mp3, m4a, aac, wav, ogg, opus, flac, mp4, webm, mov, m4v - any public URL. Links without a file extension are recognised by their content type. |
| **TikTok, X (Twitter), Vimeo, Loom, Twitch** and other video pages | Audio is fetched with `yt-dlp` - **best effort**: these platforms change often and some block data-center traffic. When blocked, the Actor retries once through Apify Proxy and otherwise reports a clear reason. |
| **YouTube** | **Existing captions only** (manual captions, or YouTube's automatic ones). YouTube audio/video is never downloaded - not even when a web page embeds a YouTube player or the link goes through a mirror. Videos without captions are skipped and not charged. |
| **Another Apify dataset** | Chain Actors: read the video URLs from the output of a TikTok / X / Instagram-style scraper (`datasetId` + `urlField`). |

Not in v1: Instagram and Facebook videos, DRM-protected or private media, live streams, and playlists / channel or profile pages / posts with several videos (they are skipped with `reason: "unsupported_url"` - give the URL of one video).

### What do you get?

One dataset item per episode / file / video. **Every field is always present** (`null` or empty when it does not apply):

| Field | Example |
|---|---|
| `recordType`, `sourceUrl`, `sourceType`, `platform` | `transcript`, the URL you gave, `podcast` / `file` / `video` / `youtube`, `tiktok` / `x` / `vimeo` / `loom` / `twitch` / `other` |
| `title`, `publishedAt`, `feedUrl`, `feedTitle`, `episodeGuid` | episode metadata from the feed or the video page |
| `durationSec`, `language`, `providers[]` | `2745.3`, `en`, `["cloudflare"]` (or `["captions"]`) |
| `text` | the full transcript |
| `segments[]` | `{ "start": 12.4, "end": 17.9, "text": "..." }` - rounded to 0.01 s |
| `srt`, `vtt` | subtitle files as text (only with *Subtitle formats*) |
| `rag` | `{ chunks[], chapters[], summary }` (only with *RAG pack*) |
| `status`, `reason`, `message` | `ok`, `no_speech`, `skipped` or `failed`, plus a machine-readable reason and a short explanation |
| `charged` | `{ "event": "audio-minute", "units": 46 }` - what this item cost |
| `processedAt` | ISO timestamp |

#### Example output (one episode, shortened)

```json
{
  "recordType": "transcript",
  "sourceUrl": "https://traffic.libsyn.com/syntax/Syntax-1044.mp3",
  "sourceType": "podcast",
  "platform": null,
  "title": "1044: Hasty Hot Takes",
  "publishedAt": "2026-10-05T07:00:00.000Z",
  "feedUrl": "https://feed.syntax.fm/rss",
  "feedTitle": "Syntax - Tasty Web Development Treats",
  "episodeGuid": "a1f6e0d2-4a0c-4d6a-8f19-2f7b3c9e0b11",
  "durationSec": 2745.3,
  "language": "en",
  "providers": ["cloudflare"],
  "text": "Welcome to Syntax, the podcast with the tastiest web development treats ...",
  "segments": [{ "start": 0, "end": 4.8, "text": "Welcome to Syntax, the podcast with the tastiest web development treats." }],
  "srt": null,
  "vtt": null,
  "rag": null,
  "status": "ok",
  "reason": null,
  "message": null,
  "charged": { "event": "audio-minute", "units": 46 },
  "processedAt": "2026-10-06T09:14:03.511Z"
}
```

#### The RAG pack

With **RAG pack** switched on, `rag` contains:

```json
{
  "chunks": [
    {
      "id": "2f3a9c-0001",
      "start": 0,
      "end": 212.4,
      "text": "Welcome to Syntax ... and that is how we ship it.",
      "tokens": 598,
      "url": "https://www.tiktok.com/@creator/video/123#t=0"
    }
  ],
  "chapters": [{ "start": 0, "title": "Intro and news" }, { "start": 612.5, "title": "Why we moved to a monorepo" }],
  "summary": "The hosts discuss ..."
}
```

- **Chunks** end on segment boundaries (never in the middle of a sentence) and are about *RAG chunk size* tokens long (about 1.33 tokens per word; CJK text is counted per character). Start/end are in seconds, so a search hit can jump straight to the moment.
- `url` is a deep link with `#t=<seconds>` for video pages and direct files (YouTube uses `?t=<seconds>s`); it is `null` for podcast episodes, which have no time-coded page.
- **Chapters** and the **summary** are written by a small LLM from a compressed, time-stamped version of the transcript. If the LLM is unavailable the chapters fall back to one chapter per 10 minutes (title = first sentence), the summary is `null` and the RAG pack is **not charged**.

### How to use it

1. Add **Media URLs** (files, videos, RSS feeds, Apple Podcasts links) and/or **Podcast feeds**.
2. Choose how many episodes per feed, the language (or leave it on *auto*), subtitles and the RAG pack.
3. Click **Start**. Download JSON, CSV or Excel, or read the transcript from the API.

#### Example input - latest 3 episodes of a podcast with RAG chunks and subtitles

```json
{
  "feeds": ["https://feed.syntax.fm/rss"],
  "maxEpisodesPerFeed": 3,
  "ragPack": true,
  "chunkTokens": 600,
  "subtitleFormats": ["srt", "vtt"]
}
```

#### Example input - monitor two podcasts every morning

```json
{
  "feeds": ["https://feed.syntax.fm/rss", "https://podcasts.apple.com/us/podcast/the-daily/id1200361736"],
  "maxEpisodesPerFeed": 5,
  "onlyNew": true,
  "publishedAfter": "7 days",
  "ragPack": true,
  "maxTotalMinutes": 300
}
```

#### Example input - transcribe videos found by another Actor, with your own Groq key

```json
{
  "datasetId": "my-tiktok-scraper-run",
  "urlField": "videoMeta.downloadUrl",
  "language": "auto",
  "ownProvider": "groq",
  "ownApiKey": "gsk_...",
  "maxMinutesPerItem": 10
}
```

### Monitoring: only new episodes

Set **Only new episodes** and add an Apify **schedule**. The Actor remembers the episodes it has already handled in a named key-value store (`media-monitor-<hash>`) and delivers each episode once:

- For every feed it takes the **newest N episodes** (*Episodes per feed*), applies *Published after*, and then drops the ones it has already seen. Set N at least as high as the number of episodes that can appear between two runs.
- **Failed** episodes and episodes skipped because of your **budget** or **total-minutes limit** are *not* remembered, so the next run tries them again. Episodes skipped as `too_long` are remembered (they would be skipped again).
- The memory belongs to the **exact set of feeds** of the run (feed order does not matter). A different set of feeds - for example a saved task with other feeds - has its own, independent history. Up to 5,000 episode IDs are kept per feed.
- An episode's identity is its feed GUID, or its audio URL when the feed has no GUID.
- Apple Podcasts links and RSS URLs that lead to the same feed count once.

### Pricing

Pay per event - you pay for results, not for attempts:

| Event | Price | When |
|---|---|---|
| `audio-minute` | **$0.015** | per started audio minute transcribed with the built-in speech recognition |
| `short-video` | **$0.04** | one social video of up to 3 minutes (TikTok, X, Vimeo, Loom, Twitch) - instead of per-minute billing |
| `caption-transcript` | **$0.001** | existing captions used (YouTube), no speech recognition needed |
| `audio-minute-byok` | **$0.003** | per started minute when you use **your own** Groq / OpenAI / Cloudflare key |
| `rag-pack` | **$0.01** | RAG pack **with a summary** for one item |

plus the small per-run start fee shown on the *Pricing* tab. Platform usage is included.

Examples:

- A **1-hour podcast** episode: 60 x $0.015 = **$0.90** ($0.91 with the RAG pack).
- The same hour with **your own key**: 60 x $0.003 = **$0.18** (and you pay your provider directly - Groq's Whisper turbo costs a few cents per hour).
- A 45-second **TikTok**: **$0.04**. A YouTube video with captions: **$0.001**.
- A daily monitor for a weekly podcast: about **$0.90 per week**.

You are **never charged** for failed items, items without speech, duplicates, items skipped by a limit, or a RAG pack whose summary could not be produced. Your **Maximum cost per run** is respected: the Actor checks the budget before downloading and skips what it cannot afford (`reason: "budget"`). Failed and budget-skipped episodes are retried in the next `onlyNew` run.

#### Limits that protect you

- **Max minutes per item** (default 180): longer items are skipped (`too_long`) before any speech recognition is paid for.
- **Max total minutes per run** (default 600): the run stops transcribing once reached.
- The duration is read from the feed or the video page first and from the file itself (ffmpeg) before the first paid call.
- Files larger than 1.5 GB are skipped (`too_large`).

### Your own key (optional)

Choose **Groq**, **OpenAI** or **Cloudflare Workers AI** and enter your API key (Cloudflare: `accountId:apiToken`). Then **only your provider** is used - there is no fallback to the built-in one, so a wrong or exhausted key makes the items fail instead of silently costing you something else. Items are billed with the cheaper `audio-minute-byok` event. The key is stored encrypted by Apify and is never written to logs, the dataset or the run summary. The summary and chapters of the RAG pack use the same provider's chat model.

### Subtitles

Switch on **SRT** and/or **WebVTT** to get `srt` / `vtt` text in every record - ready to save as a `.srt` / `.vtt` file or to upload next to the video. For YouTube the original caption cues are used; for everything else the cues come from the speech recognition segments.

### Use it from code, agents and automations

```bash
curl -X POST "https://api.apify.com/v2/acts/<USERNAME>~podcast-video-transcriber-rag/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://upload.wikimedia.org/wikipedia/commons/a/a1/Gettysburg_by_Britton.ogg"],"ragPack":true}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_TOKEN>")
run = client.actor("<USERNAME>/podcast-video-transcriber-rag").call(run_input={"feeds": ["https://feed.syntax.fm/rss"], "maxEpisodesPerFeed": 2, "ragPack": True})
for ep in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(ep["title"], ep["durationSec"], ep["status"], len(ep["rag"]["chunks"]) if ep["rag"] else 0)
```

**Ask an AI agent (Apify MCP server):** *"Transcribe the three newest episodes of https://feed.syntax.fm/rss with the podcast-video-transcriber-rag Actor, turn on the RAG pack, and tell me which episode talks about edge databases and at what time."* The agent reads `rag.chunks[].start` and `rag.chapters[]` to answer with timestamps.

**n8n / Make / Zapier idea - "new episode -> Notion":** schedule the Actor daily with `onlyNew: true`, add an *Apify - Run finished* trigger, fetch the dataset items, and create a Notion page per item with `title`, `rag.summary`, the chapters as a bullet list and `text` as the page body. The same flow works for Slack digests or a Google Sheet of show notes.

**Vector database:** every `rag.chunks[]` entry already carries `id`, `text`, `start`, `end` and `url` - embed `text`, store the rest as metadata.

### Run summary

Besides the dataset every run writes `OUTPUT` (counts per status, audio minutes, charged events per type, skipped and failed items with reasons, feed details, speech providers used, warnings) and `RESULTS_MD` (the same as short Markdown) to the key-value store.

Reason codes you may meet in `reason`: `too_long`, `too_large`, `max_total_minutes`, `budget`, `no_captions`, `youtube_blocked`, `unavailable`, `unsupported_url`, `live_stream`, `invalid_url`, `no_speech`, `no_audio_stream`, `download_failed`, `ytdlp_failed`, `blocked_by_site`, `invalid_media`, `ffmpeg_failed`, `no_asr_provider`, `asr_unavailable`, `asr_failed`, `feed_failed`, `apple_lookup_failed`, `ytdlp_missing`, `ffmpeg_missing`, `tool_missing`, `unexpected_error`.

### FAQ

**Am I charged when something fails?** No. Failed items, files without speech, duplicates, items over a limit and RAG packs without a summary are free. Only delivered transcripts are billed.

**Is my media stored?** No. Audio is downloaded to a temporary folder, converted, sent to the speech-to-text provider and deleted when the item is done. Media never goes into the dataset or the key-value store.

**Which speech recognition is used?** Whisper large-v3-turbo through Cloudflare Workers AI, with automatic fall-back to Groq and OpenAI Whisper when a provider is rate-limited or out of quota. Summaries and chapters use Llama 3.1 8B (Cloudflare or Groq). With your own key only your provider is used.

**How accurate is it? Which languages?** Whisper supports about 100 languages and detects the language automatically. If you know the language, set it (`en`, `de`, `uk`, `pt`...) - this improves accuracy for accents and short clips. Speaker labels (diarization) are not included.

**Why are some TikTok / X videos skipped?** These platforms block automated access from time to time. The Actor then retries once through Apify Proxy; if the platform still refuses, the item is reported with `reason: "blocked_by_site"` and costs nothing. **Apify Proxy traffic used by the fallback is billed by Apify as usual** (it is small: a page and one audio file). Direct file and podcast URLs never use a proxy.

**Why are YouTube videos "captions only"?** Downloading YouTube media is not supported by this Actor. If a video has captions (including automatic ones) you get them as a transcript for $0.001; if not, the video is skipped (`no_captions`).

**Do I need to own the content?** You are responsible for having the right to transcribe and reuse the content you process, and for following the terms of the platforms you read from. The Actor only reads publicly reachable media and pages.

**A very long feed?** Feeds with thousands of episodes (20+ MB of XML) are streamed and parsed item by item, so memory stays low. Use *Episodes per feed* and *Published after* to choose what to transcribe.

**Something is missing?** Open an issue in the *Issues* tab - requests for more sources and output formats are welcome.

# Changelog

This Actor's version history is a separate document: https://apify.com/tiger19935/podcast-video-transcriber-rag/changelog.md

# Actor input Schema

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

Direct audio/video files (mp3, m4a, wav, ogg, mp4, webm...), podcast RSS feed URLs, Apple Podcasts show URLs (podcasts.apple.com/.../id123456789), and video links from TikTok, X (Twitter), Vimeo, Loom or Twitch. YouTube links use the video's existing captions only (YouTube media is never downloaded). Each entry is one item; feeds are expanded into episodes.

## `feeds` (type: `array`):

Podcast RSS feed URLs or Apple Podcasts show URLs. The newest episodes of every feed are transcribed (see 'Episodes per feed'). Use together with 'Only new episodes' to monitor a podcast on a schedule.

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

How many of the newest episodes of each feed to consider (newest first by publication date). With 'Only new episodes' the already-transcribed ones inside this window are skipped, so the Actor never walks back into the old catalogue; set it at least as high as the number of episodes that can appear between two scheduled runs.

## `onlyNew` (type: `boolean`):

Skip episodes that an earlier run with the same set of feeds already processed. The memory is kept in a named key-value store that belongs to the exact set of feeds. Failed and budget-skipped episodes are retried on the next run.

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

Only feed episodes published on or after this date. Use an absolute date (2026-10-01) or a relative one such as '7 days' or '1 month'. Episodes without a valid publication date are skipped when this is set.

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

ID or name of another Apify dataset to read media URLs from, e.g. the output of a TikTok or X scraper. The URLs are processed together with 'Media URLs'.

## `urlField` (type: `string`):

Name of the field that holds the URL in every dataset item. Nested fields use a dot, e.g. 'media.url'. The field may contain a URL or a list of URLs.

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

'auto' detects the language. Or give an ISO-639-1 code (en, de, es, uk, pt...) to improve accuracy for a known language. Also selects the caption language for YouTube.

## `subtitleFormats` (type: `array`):

Add ready-made subtitle files as text fields 'srt' and/or 'vtt' to every transcript.

## `ragPack` (type: `boolean`):

Adds 'rag': time-coded chunks of about the configured token size ready for a vector database, chapters and a short summary. The RAG pack is billed only when the summary was produced.

## `chunkTokens` (type: `integer`):

Target size of one RAG chunk in tokens (about 1.33 tokens per word). Chunks always end on a segment boundary.

## `ownProvider` (type: `string`):

'None' uses the built-in speech recognition. With Groq, OpenAI or Cloudflare Workers AI only YOUR provider is used (no fallback to the built-in one) and the cheaper 'audio-minute-byok' event is billed.

## `ownApiKey` (type: `string`):

API key of the provider selected above. For Cloudflare use the format accountId:apiToken. Stored encrypted; never written to logs or the dataset.

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

Items longer than this are skipped (status 'skipped', reason 'too_long') and not charged.

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

Stop transcribing once this many audio minutes were processed in the run; the remaining items are skipped and not charged. Your 'Maximum cost per run' is always respected as well.

## Actor input object example

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/a/a1/Gettysburg_by_Britton.ogg"
  ],
  "feeds": [],
  "maxEpisodesPerFeed": 3,
  "onlyNew": false,
  "urlField": "videoUrl",
  "language": "auto",
  "subtitleFormats": [],
  "ragPack": false,
  "chunkTokens": 600,
  "ownProvider": "none",
  "maxMinutesPerItem": 180,
  "maxTotalMinutes": 600
}
```

# Actor output Schema

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

One record per episode / file / video: text, timestamped segments, optional SRT/VTT and RAG pack, status and charged events.

## `summary` (type: `string`):

Counts per status, charged events, skipped and failed items with reasons, feed details, speech-to-text providers used and warnings.

## `markdown` (type: `string`):

Short human- and agent-readable summary of the run.

# 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://upload.wikimedia.org/wikipedia/commons/a/a1/Gettysburg_by_Britton.ogg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tiger19935/podcast-video-transcriber-rag").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://upload.wikimedia.org/wikipedia/commons/a/a1/Gettysburg_by_Britton.ogg"] }

# Run the Actor and wait for it to finish
run = client.actor("tiger19935/podcast-video-transcriber-rag").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://upload.wikimedia.org/wikipedia/commons/a/a1/Gettysburg_by_Britton.ogg"
  ]
}' |
apify call tiger19935/podcast-video-transcriber-rag --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tiger19935/podcast-video-transcriber-rag"
        }
    }
}
```

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/nbN4LldnuoL0GPGrS/builds/kf3TTDIpWycK9SXpO/openapi.json
