# Speech to Text from Links: MP3, MP4, Google Drive, Podcast RSS (`neverempty/speech-to-text-from-links`) Actor

Transcribe audio and video files from public links: MP3, M4A, WAV, MP4, MOV and more, Google Drive and Dropbox share links, podcast RSS feeds and Apple Podcasts. Full text, timestamped segments, SRT and VTT subtitles, 99 languages auto-detected. Pay only per transcribed minute.

- **URL**: https://apify.com/neverempty/speech-to-text-from-links.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** AI, Videos, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $11.60 / 1,000 minute of transcribed audios

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

## Speech to Text from Links - transcribe MP3, MP4, Google Drive, Dropbox and podcast audio

**Paste a link to an audio or video file and get the transcript back**: the full text, timestamped segments, ready-made **SRT and WebVTT subtitles**, and the spoken language detected automatically (99 languages).

**Works with the links you already have.** Direct file links (MP3, M4A, WAV, OGG, OPUS, FLAC, AAC, MP4, MOV, WEBM, MKV and more), **Google Drive** and **Dropbox** share links, **podcast RSS feeds** and **Apple Podcasts** show or episode pages. No upload step, no account to connect.

**Pay only for audio that became text.** You are charged per minute of transcribed audio. A link that cannot be read, a video with no sound, music with no speech, a file over your length limit - each comes back as its own row with the reason, and none of them is charged.

**Two accuracy modes, or your own key.**

| Mode | Model | Best for | Speed (one hour of audio) | Price |
|---|---|---|---|---|
| **Fast** (default) | Whisper base, runs inside the Actor | Clear English speech, quick drafts, large podcast backlogs | about 15 to 25 minutes | lowest per minute |
| **Accurate** | Whisper small, runs inside the Actor | Names, numbers, famous quotes, non-English speech | about 45 to 60 minutes | "accurate" price per minute |
| **Your own OpenAI or Groq key** (optional) | OpenAI whisper-1, or Groq whisper-large-v3-turbo / whisper-large-v3 | The largest models, billed to your own OpenAI or Groq account | depends on the provider | lowest price per minute here |

Same 20-second clip (JFK, "We choose to go to the moon"), transcribed in our test runs:

- Fast: "We choose to go to the moon *and disdicate* and do the other things, not because they are easy, but because they are *hot*."
- Accurate: "We choose to go to the moon in this decade and do the other things. Not because they are easy, but because they are hard."

**Built for podcasts on a schedule.** Add a feed, turn on *Only episodes not transcribed before*, and schedule the run: each new episode is transcribed once, as soon as it is published.

***

### What you get

One row per file:

| Field | What it holds |
|---|---|
| `text` | The full transcript as one block of text |
| `segments` | Timestamped pieces: `start`, `end` (seconds) and `text` |
| `srt`, `vtt` | Subtitle files as text, ready to save as `.srt` / `.vtt` |
| `language`, `languageProbability` | Detected language code (`en`, `es`, `ja` ...) and how sure the detection is (0-1) |
| `durationSeconds`, `chargedMinutes` | Length of the audio that was transcribed, and the minutes charged for it |
| `wordCount`, `characterCount`, `segmentCount` | Size of the transcript (`wordCount` is empty for languages written without spaces, such as Japanese, Chinese and Thai) |
| `title`, `publishedAt`, `feedUrl` | Episode title, publish date and feed for podcast episodes; the file name for other links |
| `url`, `mediaUrl`, `sourceType` | The link you gave, the file that was read, and where it came from (`direct`, `google-drive`, `dropbox`, `podcast-feed`, `apple-podcasts`) |
| `status`, `note` | `ok` for a transcript, otherwise the reason (see below) |

The dataset has three views: **Transcripts**, **Subtitles (SRT / VTT)** and **Files not transcribed**.

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

A real row from the newest episode of NASA's *Houston We Have a Podcast* feed (59 minutes 32 seconds, transcribed in about 17 minutes):

```json
{
  "url": "https://www.nasa.gov/feeds/podcasts/houston-we-have-a-podcast",
  "mediaUrl": "https://traffic.megaphone.fm/NATIONALAERONAUTICSANDSPACEADMINISTRATION6740428946.mp3",
  "sourceType": "podcast-feed",
  "feedUrl": "https://www.nasa.gov/feeds/podcasts/houston-we-have-a-podcast",
  "title": "Reflections After Artemis II with Victor and Jeremy",
  "publishedAt": "2026-10-16T13:00:00.000Z",
  "status": "ok",
  "language": "en",
  "languageProbability": 0.998,
  "durationSeconds": 3572,
  "chargedMinutes": 60,
  "text": "Houston, we have a podcast. Welcome to the official podcast of the NASA Johnson Space Center, episode 441, Reflections After Artemis II. I'm Gary Jordan, and I'll be your host today. On this podcast, we bring in the experts, scientists, engineers, and astronau ...",
  "wordCount": 11401,
  "characterCount": 59548,
  "segmentCount": 1005,
  "segments": [
    {
      "start": 0,
      "end": 8.44,
      "text": "Houston, we have a podcast. Welcome to the official podcast of the NASA Johnson Space Center, episode 441, Reflections After Artemis II."
    },
    {
      "start": 8.44,
      "end": 17.32,
      "text": "I'm Gary Jordan, and I'll be your host today. On this podcast, we bring in the experts, scientists, engineers, and astronauts all to let you know what's going on in the world of human spaceflight and more."
    }
  ],
  "srt": "1\n00:00:00,000 --> 00:00:08,440\nHouston, we have a podcast. Welcome to the official podcast of the NASA Johnson Space Ce ...",
  "vtt": "WEBVTT\n\n00:00:00.000 --> 00:00:08.440\nHouston, we have a podcast. Welcome to the official podcast of the NASA Johnson Sp ...",
  "model": "whisper-base",
  "note": null
}
```

### Input

| Field | What it does |
|---|---|
| **Audio or video file links** (`urls`) | One or more links: direct file links, Google Drive / Dropbox share links, a podcast RSS feed or an Apple Podcasts page |
| **Podcast feeds** (`podcastFeeds`) | Optional. RSS feeds or Apple Podcasts show links; the newest episodes are transcribed |
| **Episodes per feed** (`maxEpisodesPerFeed`) | How many of the newest episodes to take from each feed (default 3) |
| **Only episodes not transcribed before** (`onlyNewEpisodes`) | For scheduled runs: skip episodes already transcribed from the same feed |
| **Accuracy** (`accuracy`) | *Fast* (Whisper base, default) or *Accurate* (Whisper small: fewer wrong words, about 3 times slower, charged at the accurate price) |
| **Your OpenAI or Groq API key** (`apiKey`) | Optional and stored encrypted. With a key, recognition runs on your OpenAI or Groq account and minutes are charged at the lower own-key price. The key is sent only to that provider and never written to the results or the log |
| **Spoken language** (`language`) | *Detect automatically* (default), or pick the language for short or noisy recordings |
| **Max minutes per file** (`maxMinutesPerFile`) | Longer files are skipped and not charged (default 120, up to 240) |
| **Max total minutes per run** (`maxTotalMinutes`) | A cap on the minutes one run can charge (default 300). The default fits inside the default 6-hour run timeout in both modes; see *Which limit stops first* below |

#### Ready-made inputs

**Transcribe a few files:**

```json
{ "urls": ["https://example.com/interview.mp3", "https://drive.google.com/file/d/FILE_ID/view?usp=sharing"] }
```

**The three newest episodes of a podcast:**

```json
{ "podcastFeeds": ["https://podcasts.apple.com/us/podcast/houston-we-have-a-podcast/id1209883012"], "maxEpisodesPerFeed": 3 }
```

**Every new episode, once (schedule this daily):**

```json
{ "podcastFeeds": ["https://feeds.example.com/show.xml"], "maxEpisodesPerFeed": 5, "onlyNewEpisodes": true }
```

### Google Drive and Dropbox

Set the file's sharing to **"Anyone with the link"** and paste the share link as it is (`drive.google.com/file/d/...` or `dropbox.com/scl/fi/...`). The Actor turns it into the file's download address. A file that is not shared comes back as `not_public` with a note, and is not charged. Folders are not supported - share each file.

### When a file is not transcribed

| `status` | Meaning | Charged |
|---|---|---|
| `unsupported_site` | A YouTube, TikTok, Instagram, Facebook, X, Vimeo, Spotify, SoundCloud or similar page. These are not downloaded | No |
| `not_media` | The link opens a web page, a document or an image, not audio or video | No |
| `not_public` | Google Drive / Dropbox file that is not shared with "Anyone with the link" | No |
| `unreachable` | The server could not be reached, or answered 404 / 403 / 5xx | No |
| `no_audio` | The video has no sound track | No |
| `no_speech` | There is sound but no speech (music, silence) | No |
| `too_long` | Longer than *Max minutes per file* | No |
| `run_limit` / `charge_limit` | Left out by *Max total minutes per run* or by your maximum cost per run | No |
| `time_limit` | Left out because the run would hit its timeout before this file is finished | No |
| `no_new_episodes` | Nothing new in the feed since the last run | No |
| `bad_input` | Not a usable link (a folder, a private network address, not http/https) | No |
| `failed` | The audio could not be decoded or recognised | No |

### Good to know

- Without an API key, speech recognition runs inside the Actor (Whisper *base* in Fast mode, Whisper *small* in Accurate mode). Your audio is not sent to any other transcription service.
- With your own key, the audio is sent in 10-minute WAV pieces to OpenAI or Groq only, using your key; their usage and data rules apply, and they bill you for the recognition.
- A one-hour recording usually takes about 15 to 25 minutes in Fast mode and about 45 to 60 minutes in Accurate mode. Files are processed one after another. The default run timeout is 6 hours.
- **Which limit stops first.** The default run timeout is 6 hours. 300 minutes (the default *Max total minutes per run*) take at most about 5 hours 15 minutes in Accurate mode and about 2 hours in Fast mode, so with the defaults the minute cap stops first. If you raise *Max total minutes per run*, the run timeout can come first (about 300 minutes of audio in Accurate mode and about 800 in Fast mode fit in 6 hours). Before each file the Actor checks the time left, and a file that cannot finish in time gets a `time_limit` row and is not charged. To transcribe more in one run, raise *Timeout* in the run options as well.
- If Apify moves a run to another server while it is running, the run continues where it stopped. Files already delivered are not transcribed or charged again.
- Long recordings are recognised in pieces of about 20 minutes; timestamps run continuously across the whole file.
- Podcast hosts that count downloads will count one download per transcribed episode.

### Frequently asked

**How do I transcribe an MP3 or MP4 from a link?** Paste the link into *Audio or video file links* and run. You get one row with the full text, timestamped segments and SRT / VTT subtitles.

**How do I transcribe a file stored in Google Drive?** Share the file with "Anyone with the link", copy the share link, and paste it. The Actor downloads the file itself, not the preview page.

**How do I get a transcript of every new podcast episode?** Add the RSS feed or Apple Podcasts link under *Podcast feeds*, turn on *Only episodes not transcribed before*, and schedule the Actor. Each episode is transcribed and charged once.

**How do I make subtitles for a video?** Run the video link and copy the `srt` or `vtt` field into a `.srt` / `.vtt` file. The *Subtitles (SRT / VTT)* view shows them side by side.

**Which languages does it understand?** 99 languages, detected automatically. Accuracy is highest for English and other widely spoken languages; for other languages choose *Accurate* or use your own key. See Limitations.

**Fast or Accurate - which should I pick?** Fast for clear English and large backlogs. Accurate when names, numbers or quotes must be right, or the speech is not English. In our tests Accurate fixed a famous 20-second quote that Fast got wrong (see the table at the top).

**Can I use my own OpenAI or Groq account?** Yes. Paste your API key in *Your OpenAI or Groq API key*. Recognition then runs on your account and this Actor charges its lower own-key price per minute. Leave the field empty to use the built-in models.

**Am I charged for files that fail?** No. Only files that produce a transcript are charged, by the minute of audio, rounded up per file.

### Limitations

- Fast mode makes real mistakes even on famous English audio: in our test it heard JFK's "in this decade ... because they are hard" as "and disdicate ... because they are hot", and wrote crew names Wiseman / Koch / Hansen as Weizmann / Cook / Hanson. Accurate mode got the JFK clip right, but still wrote "Koch" as "Cook".
- Non-English speech is harder. On a Japanese reading of a 1920 short story (Akutagawa, "Toshishun"), Fast turned the title name and old place names into nonsense (杜子春 as トッシュン, 唐の都洛陽 as 東の宮校楽洋, 日暮れ as ヒムレ). Accurate fixed common words (日暮れ, 憐れな身分) but still missed the rare names (杜子春 as 年春, 洛陽 as 楽園). For names and rare words in non-English audio, use your own OpenAI or Groq key (larger models).
- Speakers are not separated or labelled (no "Speaker 1 / Speaker 2").
- A word that falls exactly on the boundary between two 20-minute pieces can be cut or repeated.
- When a server does not report the file length, the length is known only after the audio is read; a file over *Max minutes per file* is then skipped at that point, still without charge.
- Video and social platforms (YouTube, TikTok, Instagram and similar) are not supported. Use a direct link to a file you are allowed to download.
- Files must be publicly downloadable without signing in. Files up to 240 minutes.

### Pricing

Pay per event: you are charged for each **minute of transcribed audio**, rounded up per file (a 2 minute 5 second file is 3 minutes). There are three per-minute prices, one per mode: **Fast**, **Accurate**, and **own key** (the lowest; OpenAI or Groq bill you separately for the recognition). One run uses one mode. Rows that explain a problem - unreadable links, no audio, no speech, too long, skipped by your limits - are never charged.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Need another source, field or format?** Tell us in the **Issues** tab.
- **Found a wrong transcript or a bug?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

# Actor input Schema

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

Public links to audio or video files: direct links (MP3, M4A, WAV, OGG, OPUS, FLAC, MP4, MOV, WEBM, MKV and more), Google Drive or Dropbox share links ("Anyone with the link"), or a podcast RSS feed / Apple Podcasts page. One transcript row per file. Links to YouTube, TikTok, Instagram and other video platforms are not downloaded.

## `podcastFeeds` (type: `array`):

Optional. A podcast RSS feed address or an Apple Podcasts show or episode link. The newest episodes are transcribed (see "Episodes per feed").

## `accuracy` (type: `string`):

"Fast" uses the Whisper base model: quick and the lowest price, good for clear English speech. "Accurate" uses the larger Whisper small model: fewer wrong words (especially names, numbers and non-English speech), takes about 3 times longer, and is charged at the "accurate" price per minute.

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

How many of the newest episodes to transcribe from each podcast feed.

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

For scheduled runs: skip episodes this account already transcribed from the same feed, so each episode is transcribed and charged once.

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

Leave on "Detect automatically" unless you know the language; setting it can help with short or noisy recordings.

## `maxMinutesPerFile` (type: `integer`):

Longer files are skipped with a "too_long" row and are not charged.

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

A cap on the audio minutes charged in one run. Files that would go over it get a "run_limit" row and are not charged. The default 300 fits in the default 6-hour run timeout; if you raise this, also raise "Timeout" in the run options (files that cannot finish before the timeout get a "time_limit" row and are not charged).

## `apiKey` (type: `string`):

Optional. An OpenAI key (starts with "sk-", uses whisper-1) or a Groq key (starts with "gsk\_", uses whisper-large-v3-turbo, or whisper-large-v3 when Accuracy is "Accurate"). The key is stored encrypted, is sent only to that provider, and is never written to the results or the log.

## Actor input object example

```json
{
  "urls": [
    "https://www.nasa.gov/wp-content/uploads/2015/01/590325main_ringtone_kennedy_WeChoose.mp3"
  ],
  "accuracy": "fast",
  "maxEpisodesPerFeed": 1,
  "onlyNewEpisodes": false,
  "language": "auto",
  "maxMinutesPerFile": 120,
  "maxTotalMinutes": 300
}
```

# Actor output Schema

## `results` (type: `string`):

One row per audio or video file: the full transcript text, timestamped segments (start, end, text), ready-made SRT and WebVTT subtitles, the detected language with its confidence, the duration and the minutes charged. Podcast episodes also carry the episode title, publish date and feed. Files that could not be read, links to unsupported sites, files with no audio or no speech, files over your length limit and files left out by your run cap each come back as their own row with the reason, and are not charged.

# 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://www.nasa.gov/wp-content/uploads/2015/01/590325main_ringtone_kennedy_WeChoose.mp3"
    ],
    "maxEpisodesPerFeed": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/speech-to-text-from-links").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://www.nasa.gov/wp-content/uploads/2015/01/590325main_ringtone_kennedy_WeChoose.mp3"],
    "maxEpisodesPerFeed": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/speech-to-text-from-links").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://www.nasa.gov/wp-content/uploads/2015/01/590325main_ringtone_kennedy_WeChoose.mp3"
  ],
  "maxEpisodesPerFeed": 1
}' |
apify call neverempty/speech-to-text-from-links --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/speech-to-text-from-links"
        }
    }
}
```

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/9hKnIgjT9T3famAcM/builds/D7mSrBIiROe4AIbb4/openapi.json
