# Media URL Transcriber - Video, Podcast RSS, Vimeo, Loom to Text (`kaz_kakyo/media-url-transcriber`) Actor

Transcribe any media URL via API, MCP, or schedule — direct audio/video files, HLS/m3u8, podcast RSS feeds, Vimeo, Loom. Deepgram Nova-3 STT plus free caption reuse when available. Timestamped segments, SRT/VTT subtitles, summaries. Zero setup from $0.005/item; BYOK discount. One JSON row per item.

- **URL**: https://apify.com/kaz\_kakyo/media-url-transcriber.md
- **Developed by:** [Heim AI](https://apify.com/kaz_kakyo) (community)
- **Categories:** Videos, Automation, Agents
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.00 / 1,000 stt minute (zero-setup)s

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Media URL Transcriber — Video, Podcast RSS, Vimeo, Loom to Text

**Any cooperative media URL → transcript.** Pass direct files, HLS VOD playlists, Vimeo videos, Loom shares, or podcast RSS feeds; get one JSON dataset row per media item. Zero setup for captioned Vimeo; Deepgram Nova-3 for everything else. Built for **MCP agents, API clients, and scheduled pipelines**.

| | |
|---|---|
| **Actor id** | `kaz_kakyo/media-url-transcriber` |
| **Minimal input** | `{ "mediaUrls": ["https://vimeo.com/76979871"] }` |
| **Cost** | **$0.012/min** STT zero-setup · **$0.005/min** BYOK · **$0.005** caption item · $0.005/run start |
| **Output** | Dataset rows with `type: "transcript"` or `type: "error"` |

### Supported inputs

| Input | How it works |
|---|---|
| Direct audio/video URL (mp3, mp4, m4a, wav, flac, ogg, opus, aac, webm, mov, mkv) | Deepgram fetches the file (STT) |
| HLS `.m3u8` VOD playlist | Actor assembles audio, sends to Deepgram |
| Vimeo (`vimeo.com/{id}`, player, unlisted hash) | Published captions ($0.005) when available; else STT |
| Loom share/embed (`loom.com/share/{32hex}`) | Signed mp4 → STT (no keyless captions) |
| Podcast RSS/Atom feed | Latest N episode enclosures → STT |

**Use something else instead:**

| You have… | Use instead |
|---|---|
| YouTube watch/shorts/youtu.be | [`kaz_kakyo/youtube-transcripts`](https://apify.com/kaz_kakyo/youtube-transcripts) |
| TikTok / Instagram / Facebook / X page | A dedicated platform transcript actor from the Apify Store |
| Spotify / SoundCloud / Apple Podcasts **page** | The podcast's **RSS feed URL** (this actor accepts feeds directly) |
| Google Drive / Dropbox **share** page | Direct-download / `?dl=1` / `raw=1` file URL |
| Only a direct media file URL | Also fine here — or [`kaz_kakyo/audio-transcriber`](https://apify.com/kaz_kakyo/audio-transcriber) |

### Pricing

| Event | Price | When |
|---|---|---|
| Actor start | **$0.005** | Once per run |
| STT minute (zero-setup) | **$0.012** | No BYOK key — transcription included |
| STT minute (BYOK) | **$0.005** | `deepgramApiKey` set — you also pay Deepgram ~$0.0043/min |
| Caption item | **$0.005** | Vimeo delivered via published captions (no STT) |

Minutes round **up per item**. Worked examples:

- **30-min podcast episode (zero-setup)** → 30 × $0.012 + $0.005 start ≈ **$0.365**.
- **30-min podcast episode (BYOK)** → **$0.15 = $0.005×30 min actor fee + ~$0.13 Deepgram wholesale** (plus $0.005 start → ~$0.155 actor-side; ~$0.285 all-in with Deepgram).
- **Captioned Vimeo** (preferCaptions) → $0.005 caption + $0.005 start = **$0.01** total.

### Call it (MCP / API / schedule)

#### MCP (agents)

```json
{
  "actor": "kaz_kakyo/media-url-transcriber",
  "input": {
    "mediaUrls": [
      "https://dpgr.am/spacewalk.wav",
      "https://vimeo.com/76979871"
    ]
  }
}
```

Optional extras:

```json
{
  "mediaUrls": ["https://feeds.npr.org/510289/podcast.xml"],
  "episodesPerFeed": 3,
  "preferCaptions": true,
  "includeSrt": true,
  "diarize": true
}
```

After the run, read the default dataset. Filter `type === "transcript"`; treat `type === "error"` as per-item failure. Bad/unsupported URLs become error rows and the run still **SUCCEEDS**. The run fails on Deepgram auth/credit errors, post-charge delivery failure, or when every STT item is `stt-unavailable` and nothing was delivered (no key configured).

#### API / `apify-client`

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('kaz_kakyo/media-url-transcriber').call(
  { mediaUrls: ['https://vimeo.com/76979871'], includeVtt: true },
  { maxTotalChargeUsd: 1.0 },
);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const transcripts = items.filter((i) => i.type === 'transcript');
```

REST: `POST /v2/acts/kaz_kakyo~media-url-transcriber/runs` with your token.

#### Schedule / chain

1. **Save a Task** with fixed options (`preferCaptions`, `episodesPerFeed`, BYOK key).
2. **Schedule the Task** for recurring podcast feeds or URL lists.
3. **Webhook on `SUCCEEDED`** — process only `type === "transcript"` rows.
4. **Cap spend** with `maxTotalChargeUsd`. Over-budget items become uncharged `over-budget` / skipped rows.
5. **Chain**: RSS scraper or CMS export → this actor. YouTube URLs → `youtube-transcripts` instead.

### Output contract

Success row:

```json
{
  "type": "transcript",
  "url": "https://vimeo.com/76979871",
  "source": "vimeo-captions",
  "title": "The New Vimeo Player…",
  "videoId": "76979871",
  "transcript": "…",
  "segments": [{ "start": 0.0, "end": 2.4, "speaker": null, "text": "…" }],
  "srt": "1\n00:00:00,000 --> …",
  "vtt": "WEBVTT\n\n…",
  "durationSeconds": 62,
  "language": "en",
  "billedEvent": "caption-item",
  "billedCount": 1
}
```

| Field | Notes |
|---|---|
| `source` | `direct` | `hls` | `vimeo-captions` | `vimeo-stt` | `loom` | `podcast-rss` |
| `segments` | Present on success; if the row exceeds ~4 MB, `segments` (and/or `srt`/`vtt`) may be spilled to the key-value store and replaced by `segmentsUrl` / `srtUrl` / `vttUrl` |
| `srt` / `vtt` | Per `includeSrt` (default true) / `includeVtt` (default false); same KVS spill as above when oversized |
| `summary` | STT + `summarize` + English only; never on caption path |
| `confidence`, `model` | STT only (`model` is always `nova-3`) |
| `feedUrl`, `episodeTitle`, `episodeGuid`, `pubDate` | Podcast RSS rows |
| `billedEvent`, `billedCount` | Always present |

Dataset **views** (Transcripts / Errors) select columns for display — they do **not** filter by row `type`. Both transcript and error rows appear in the underlying dataset; filter client-side on `type`.

Error row (never charged):

```json
{
  "type": "error",
  "url": "https://…",
  "errorCode": "unsupported-platform",
  "error": "…pointed FIX…",
  "billedEvent": null,
  "billedCount": 0
}
```

`errorCode` values: `unsupported-platform` · `not-media` · `not-found` · `private` · `too-long` · `too-large` · `live-stream` · `encrypted-hls` · `stt-unavailable` · `stt-upstream` · `no-audio` · `fetch-failed` · `feed-empty` · `invalid-url` · `over-budget` · `skipped`

**When the run itself fails vs. reports a row.** Anything wrong with one input (`not-found`, `too-long`, `no-audio`, …) becomes an error row and the run still succeeds — check `type` per row. Anything that made transcription impossible for the whole run (`stt-unavailable` = no key available, `stt-upstream` = Deepgram throttling or down) fails the run **when nothing at all was delivered**, so an automated caller can retry on status instead of parsing rows. A run that delivered some transcripts still succeeds, with the blocked items recorded as rows.

**Retrying after `stt-upstream` or `stt-unavailable`.** Items blocked because speech-to-text could not run are not checkpointed, so **resurrecting the run re-attempts them** once the cause clears — you are not charged twice, because charges only ever land on delivery. A re-attempted item keeps its original error row and gains a new row, so deduplicate on `url` and prefer the row with `type: "transcript"`.

### Honest limits

- **Loom = STT only** — no keyless caption endpoint.
- **HLS is beta**: fMP4 and MPEG-TS/AAC (or MP3) covered; encrypted, live, and byte-range playlists are rejected. Assembly capped at **512 MB** (sequential stream-to-disk).
- **No YouTube / TikTok / Instagram / Facebook / X** — use dedicated actors.
- **Direct-file duration** is not knowable pre-STT — clamped by file size (2048 MB) only.
- **Summaries are English-only** (Deepgram `summarize=v2`).
- **Caption quality** = whatever the Vimeo publisher uploaded.
- **Privacy**: transcripts contain whatever is spoken (or captioned) in the media URLs you submit — that is the product. Treat outputs as sensitive if the source media is.
- Caps: 100 URLs/run, 200 items after feed expansion, default `maxDurationMinutes` 180.

### Input (agent-first)

- **`mediaUrls`** (required) — see Supported inputs. Prefill demo: spacewalk.wav + public Vimeo (caption path works keyless).
- **`episodesPerFeed`** — default 1 (max 25).
- **`preferCaptions`** — default true (Vimeo caption path).
- **`captionLanguage`** — BCP-47 prefix; fallback `en`, then first track.
- **`deepgramApiKey`** — optional BYOK; encrypted; sent only to `api.deepgram.com`.
- **`language` / `detectLanguage` / `diarize` / `smartFormat` / `paragraphs` / `summarize` / `includeSrt` / `includeVtt` / `maxDurationMinutes`** — see Input tab; every field states its default there.

See the **Input** tab for the full schema. See the **API** tab for run/dataset endpoints.

# Actor input Schema

## `mediaUrls` (type: `array`):

Required. Accepted: direct audio/video file URLs (mp3, mp4, m4a, wav, flac, ogg, opus, aac, webm, mov, mkv), HLS .m3u8 VOD playlists, Vimeo video URLs, Loom share/embed URLs, podcast RSS/Atom feed URLs (expand to episodesPerFeed rows). One dataset row per media item. Max 100 URLs/run and 200 items after feed expansion. REJECTED (error rows, run still succeeds): YouTube → use kaz\_kakyo/youtube-transcripts; TikTok/Instagram/Facebook/X page links; Spotify/SoundCloud/Apple Podcasts page links (pass the podcast RSS feed instead); Google Drive/Dropbox share pages; any URL that serves text/html. Bad URLs become type:error rows and the run still succeeds.

## `episodesPerFeed` (type: `integer`):

Default: 1. Latest N episodes per podcast RSS/Atom feed URL (min 1, max 25). Feed items sorted by pubDate when parseable.

## `preferCaptions` (type: `boolean`):

Default: true. For Vimeo: reuse the published caption track ($0.005 flat caption-item) instead of STT. Set false to force Deepgram STT.

## `captionLanguage` (type: `string`):

Default: empty. Preferred Vimeo caption track language (BCP-47 prefix match). Fallback: en, then first available track.

## `deepgramApiKey` (type: `string`):

Optional. Leave empty for zero-setup STT at $0.012/min (requires actor DEEPGRAM\_API\_KEY). Bring your own free Deepgram key (console.deepgram.com — $200 credit, no card) to pay $0.005/min here plus Deepgram wholesale ~$0.0043/min. Caption-path Vimeo items work without any key. Stored encrypted; sent only to api.deepgram.com.

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

Default: empty (Deepgram default English). Optional BCP-47 code (e.g. en, es, de). Or set detectLanguage.

## `detectLanguage` (type: `boolean`):

Default: false. When true, detect the dominant language per item and transcribe in it. Overrides language.

## `diarize` (type: `boolean`):

Default: false. When true, labels speakers on segments/SRT. STT path only.

## `smartFormat` (type: `boolean`):

Default: true. Punctuation plus formatted dates, numbers, currency, phones, and emails in transcript. STT path only.

## `paragraphs` (type: `boolean`):

Default: true. Split transcript into readable paragraphs instead of one wall of text. STT path only.

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

Default: false. When true, adds summary (short abstract) on STT items. English audio only — ignored for explicit non-English language. Caption-path items never have summaries.

## `includeSrt` (type: `boolean`):

Default: true. When true, adds srt (ready-to-save .srt string built from segments).

## `includeVtt` (type: `boolean`):

Default: false. When true, adds vtt. Caption path serves the cleaned native VTT; STT path builds VTT from utterances.

## `maxDurationMinutes` (type: `integer`):

Default: 180. Skip items longer than this when duration is knowable pre-STT (Vimeo video.duration, RSS itunes:duration, HLS EXTINF sum) → too-long error row. Direct files without duration metadata are clamped by file size only (2048 MB Deepgram limit), not duration.

## Actor input object example

```json
{
  "mediaUrls": [
    "https://dpgr.am/spacewalk.wav",
    "https://vimeo.com/76979871"
  ],
  "episodesPerFeed": 1,
  "preferCaptions": true,
  "captionLanguage": "",
  "language": "",
  "detectLanguage": false,
  "diarize": false,
  "smartFormat": true,
  "paragraphs": true,
  "summarize": false,
  "includeSrt": true,
  "includeVtt": false,
  "maxDurationMinutes": 180
}
```

# Actor output Schema

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

No description

## `errors` (type: `string`):

No description

# 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 = {
    "mediaUrls": [
        "https://dpgr.am/spacewalk.wav",
        "https://vimeo.com/76979871"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kaz_kakyo/media-url-transcriber").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 = { "mediaUrls": [
        "https://dpgr.am/spacewalk.wav",
        "https://vimeo.com/76979871",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("kaz_kakyo/media-url-transcriber").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "mediaUrls": [
    "https://dpgr.am/spacewalk.wav",
    "https://vimeo.com/76979871"
  ]
}' |
apify call kaz_kakyo/media-url-transcriber --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=kaz_kakyo/media-url-transcriber",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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