# CJK-Accurate Audio Transcriber (Whisper) - Traditional Chinese (`flyingfishclaw/cjk-transcriber`) Actor

Transcribe audio/video URLs with Whisper large-v3-turbo via Groq. Correct Traditional Chinese (Taiwan phrasing) or Simplified output via OpenCC, plus SRT/VTT/segments. Pay per audio minute; bring your own Groq key for a lower rate.

- **URL**: https://apify.com/flyingfishclaw/cjk-transcriber.md
- **Developed by:** [FlyingfishClaw](https://apify.com/flyingfishclaw) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 minute of audio transcribed (hosted key)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/actors/running/actors-in-store.md#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

## CJK-Accurate Audio & Video Transcriber (Whisper) — Traditional Chinese, SRT/VTT

Turn any audio or video URL into a clean transcript with **correct Chinese script**.
Whisper is excellent at Mandarin, but its output randomly mixes Simplified and Traditional
characters and uses mainland phrasing (软件, 网络). This Actor fixes that at the source
(Traditional-Chinese prompting) and at the output (OpenCC `s2twp` → 軟體, 網路), so
Taiwan / Hong Kong readers get text they would actually publish. It also drops the
boilerplate Whisper hallucinates over intros, jingles and silence (請不吝點贊…, Amara.org
credits, prompt echoes), which most Whisper wrappers pass straight through. Works for any
language Whisper supports — the CJK handling only kicks in when Chinese is detected.

### What you get

One dataset item per URL:

```json
{
  "url": "https://example.com/episode.mp3",
  "index": 0,
  "status": "ok",
  "language": "Chinese",
  "durationSec": 1834.2,
  "billableMinutes": 31,
  "chunks": 4,
  "scriptConversion": "s2twp",
  "droppedSegments": 1,
  "text": "第一句…\n第二句…",
  "segments": [{ "start": 0.0, "end": 4.2, "text": "第一句…" }],
  "srtUrl": "https://api.apify.com/v2/key-value-stores/…/records/0000.srt",
  "vttUrl": "https://api.apify.com/v2/key-value-stores/…/records/0000.vtt"
}
```

`status` is one of `ok`, `failed` (with a fixed `error` code: `download_failed`, `too_large`,
`too_long`, `probe_failed`, `transcode_failed`, `auth_failed`, `bad_request`,
`upstream_unavailable`, `internal_error`), `skipped_charge_limit` (your run budget could not
cover the file — nothing was transcribed, nothing charged) or `partial_charge`.
`language`, `chunks`, `scriptConversion`, `droppedSegments` and the output fields only exist
on `ok` / `partial_charge` items; `skipped_charge_limit` items carry `url`, `index`, `status`,
`durationSec`, `billableMinutes` and nothing else; `failed` items carry `url`, `index`,
`status`, `error` and nothing else — read those keys conditionally. Two optional flags mark an
empty result on an `ok` / `partial_charge` item:
`emptyTranscript: true` whenever no segment survived (audio without speech, or everything
filtered) and additionally `allSegmentsDropped: true` when the hallucination filter is what
removed them.

### Input

| Field | Default | Notes |
|---|---|---|
| `audioUrls` | — | Direct http(s) links: mp3, m4a, wav, mp4, mkv, podcast enclosures |
| `language` | `auto` | ISO 639-1. Use `zh` for Chinese audio: enables the Traditional prompt |
| `chineseScript` | `auto` | `auto`/`traditional` → Taiwan Traditional (s2twp); `simplified`; `none` |
| `outputFormats` | `text, segments, srt` | `vtt` also available; subtitle files land in the key-value store |
| `model` | `whisper-large-v3-turbo` | or `whisper-large-v3` |
| `initialPrompt` | empty | ≤500 chars of names / jargon; auto Taiwan prompt when `zh` + Traditional |
| `filterHallucinations` | `true` | Drop segments Whisper invents over silence/music (see Notes); count in `droppedSegments` |
| `chunkMinutes` | 10 | Long files are split; 10 min keeps chunks under Groq's 25 MB cap |
| `maxFileSizeMb` | 500 | Rejected before any cost is incurred |
| `maxDurationMinutes` | 240 | Rejected before any cost is incurred |
| `groqApiKey` | empty | Bring your own key → billed at the cheaper `audio-minute-byok` event |

### Pricing (pay per event)

| Event | When |
|---|---|
| `audio-minute` | per started minute of audio, hosted key |
| `audio-minute-byok` | per started minute of audio, your own Groq key |

Minutes are rounded up per file. Files that would exceed your run's max charge are skipped
(`skipped_charge_limit`) instead of being half-transcribed.

### Notes

- Only transcribe media you own or are allowed to process. The Actor downloads the URL you
  give it and sends audio to Groq's Whisper API; no media is retained after the run.
- Speech recognition is done by `whisper-large-v3-turbo` on Groq; accuracy on noisy audio,
  heavy code-switching or Taiwanese Hokkien is limited by the model. Proper nouns and
  programme names are best fixed by listing them in `initialPrompt`.
- Hallucination filter (`filterHallucinations`): a segment is dropped when it contains known
  subtitle boilerplate (明鏡與點點欄目, 點贊/點讚訂閱轉發打賞, Amara.org, 優優獨播劇場; matched
  script-insensitively), is nothing but punctuation or music glyphs (♪), echoes the built-in
  Traditional-Chinese prompt (at least 60 % of it once punctuation and spaces are removed —
  your own `initialPrompt` is never echo-checked, because you list words the speaker will
  actually say), spans ≥ 15 s with under 1 letter/character per second (punctuation and spaces
  not counted), or trips Whisper's own no-speech (> 0.6 with logprob < −1.0) or repetition
  (compression ratio > 2.4) thresholds. `droppedSegments` reports the count (`null` when the
  filter is off); an empty result is flagged with `emptyTranscript: true`, plus
  `allSegmentsDropped: true` when the filter removed everything. Set the filter to `false` for
  very sparse speech (guided meditation, long pauses).
- No speaker diarization yet (planned).

### Development

```bash
python3.12 -m venv .venv && ./.venv/bin/pip install -r requirements.txt pytest pytest-asyncio
./.venv/bin/python -m pytest -q                      # 130 tests, real ffmpeg, no network
mkdir -p storage/key_value_stores/default
cp example_input.json storage/key_value_stores/default/INPUT.json   # edit the URL
bash scripts/set-groq-key.sh                          # once: key from clipboard → ~/.config/cjk-transcriber.env (0600)
set -a; source ~/.config/cjk-transcriber.env; set +a  # never put the key on a command line
./.venv/bin/python -m src                             # local run; output in storage/datasets/default
```

`ffmpeg`/`ffprobe` must be on PATH locally (the Docker image installs them).
Set `ACTOR_TEST_PAY_PER_EVENT=1` to exercise the charging code path locally.

### Deploy to Apify

1. `npm i -g apify-cli && apify login`
2. `apify push` from this directory (builds the Dockerfile on Apify).
3. Console → Actor → Settings → Environment variables: add `GROQ_API_KEY` as a **secret**.
4. Console → Monetization: pay-per-event with events `audio-minute` and `audio-minute-byok`
   (suggested launch prices $0.008 and $0.003 per minute; Groq turbo costs ~$0.0007/min).
5. Publish to Store with this README, a title, categories (AI, Audio, Transcription) and one
   example run.

# Actor input Schema

## `audioUrls` (type: `array`):

Direct http(s) links to media files (mp3, m4a, wav, ogg, mp4, mkv, podcast enclosures...). One dataset item is produced per URL. The prefilled sample is a 1-minute Mandarin spoken-Wikipedia clip (CC licence) so you can try the Actor immediately.

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

ISO 639-1 code. 'auto' lets Whisper detect it. For Chinese audio set 'zh' - it enables the Traditional-Chinese prompt and gives the best script consistency.

## `chineseScript` (type: `string`):

Applied only when the detected/selected language is Chinese. 'auto' and 'traditional' convert to Traditional Chinese with Taiwan phrasing (OpenCC s2twp, e.g. 软件→軟體). 'simplified' converts to Simplified. 'none' leaves Whisper output untouched.

## `outputFormats` (type: `array`):

text and segments go into the dataset item; srt/vtt files are saved to the key-value store and linked from the item.

## `model` (type: `string`):

Groq-hosted Whisper model. large-v3-turbo is faster and cheaper; large-v3 is slightly more accurate on difficult audio.

## `initialPrompt` (type: `string`):

Up to 500 characters of context (names, jargon, preferred spelling). For zh + Traditional a Taiwan-style prompt is used automatically when left empty.

## `filterHallucinations` (type: `boolean`):

Removes text Whisper invents over silence or music: known subtitle boilerplate (e.g. 請不吝點贊…明鏡與點點欄目, Amara.org credits), punctuation-only segments, segments that echo the built-in Chinese prompt (your own initialPrompt is never echo-checked), segments of 15 s or more with under 1 letter/character per second (punctuation not counted), and Whisper's own no-speech / repetition-loop signals. The count is reported as droppedSegments (null when this is off); an empty result carries emptyTranscript: true, plus allSegmentsDropped: true when the filter removed everything. Turn off for very sparse speech.

## `chunkMinutes` (type: `integer`):

Long files are split into chunks of this length before transcription (1-20). 10 keeps every chunk under Groq's 25 MB limit.

## `maxFileSizeMb` (type: `integer`):

Downloads larger than this are rejected before any transcription cost is incurred.

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

Files longer than this are rejected (status too\_long).

## `groqApiKey` (type: `string`):

Bring your own key to be billed at the lower 'audio-minute-byok' event price. Leave empty to use the hosted key.

## Actor input object example

```json
{
  "audioUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/d/d0/%E7%B6%AD%E5%9F%BA%E7%99%BE%E7%A7%91-%E4%B8%AD%E8%8F%AF%E6%B0%91%E5%9C%8B.ogg"
  ],
  "language": "auto",
  "chineseScript": "auto",
  "outputFormats": [
    "text",
    "segments",
    "srt"
  ],
  "model": "whisper-large-v3-turbo",
  "filterHallucinations": true,
  "chunkMinutes": 10,
  "maxFileSizeMb": 500,
  "maxDurationMinutes": 240
}
```

# Actor output Schema

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

No description

## `subtitles` (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 = {
    "audioUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/d/d0/%E7%B6%AD%E5%9F%BA%E7%99%BE%E7%A7%91-%E4%B8%AD%E8%8F%AF%E6%B0%91%E5%9C%8B.ogg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("flyingfishclaw/cjk-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 = { "audioUrls": ["https://upload.wikimedia.org/wikipedia/commons/d/d0/%E7%B6%AD%E5%9F%BA%E7%99%BE%E7%A7%91-%E4%B8%AD%E8%8F%AF%E6%B0%91%E5%9C%8B.ogg"] }

# Run the Actor and wait for it to finish
run = client.actor("flyingfishclaw/cjk-transcriber").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 '{
  "audioUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/d/d0/%E7%B6%AD%E5%9F%BA%E7%99%BE%E7%A7%91-%E4%B8%AD%E8%8F%AF%E6%B0%91%E5%9C%8B.ogg"
  ]
}' |
apify call flyingfishclaw/cjk-transcriber --silent --output-dataset

```

## MCP server setup

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

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/zqfQGRAhIYbiSbvmP/builds/WRSRDtuY0JcRmUq3a/openapi.json
