BerandaBlog → Cara membangun pipeline transkripsi untuk arsip audio

Cara membangun pipeline transkripsi untuk arsip audio

Diterbitkan 2026-08-18 · 7 menit baca

Satu rekaman adalah satu baris kode. Sepuluh ribu rekaman adalah persoalan lain: sesuatu akan gagal di tengah jalan, prosesnya akan dimatikan, separuh berkas sudah selesai, dan Anda perlu tahu separuh yang mana. Beginilah bentuk pipeline massal yang selamat dari semua itu.

Apa yang rusak saat naik dari satu berkas ke sepuluh ribu

Perulangan atas satu folder bekerja baik sampai arsipnya membesar. Setelah itu empat hal mulai salah:

Setiap butir di bawah ada untuk memperbaiki salah satu dari keempatnya.

Langkah 1. Buat kemajuan yang tahan mati

Keputusan terpenting: catatan tentang apa yang sudah selesai harus selamat melewati prosesnya. Satu direktori berisi berkas hasil sudah cukup — keberadaan keluaran adalah keadaannya.

import pathlib

SRC = pathlib.Path("rekaman")
OUT = pathlib.Path("transkrip")
OUT.mkdir(exist_ok=True)

def tertunda():
    for p in sorted(SRC.glob("*.mp3")):
        if not (OUT / (p.stem + ".txt")).exists():
            yield p

Sekarang pekerjaannya idempoten: jalankan, matikan, jalankan lagi — ia menyambung persis dari tempat berhenti dan tidak pernah membayar dua kali untuk berkas yang sama. Untuk arsip lebih besar, tabel SQLite dengan kolom status bekerja sama saja dan memberi Anda kueri; prinsipnya tidak berubah.

Tulis keluarannya secara atomik, atau proses yang mati di tengah penulisan meninggalkan berkas terpotong yang tampak selesai:

tmp = out.with_suffix(".part")
tmp.write_text(teks, encoding="utf-8")
tmp.rename(out)          # atomik: tidak ada berkas baru, atau berkas yang utuh

Langkah 2. Proses beberapa berkas sekaligus

Transkripsi adalah menunggu jaringan, bukan kerja CPU lokal, jadi utas adalah alat yang tepat dan satu kolam utas sudah cukup:

import os
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI

client = OpenAI(base_url="https://voicesscribe.com/v1", api_key=os.environ["VS_KEY"], max_retries=0)

def transkrip(path):
    with path.open("rb") as f:
        return client.audio.transcriptions.create(model="whisper-1", file=f).text

with ThreadPoolExecutor(max_workers=4) as pool:
    for path, teks in zip(tertunda(), pool.map(transkrip, tertunda())):
        (OUT / (path.stem + ".txt")).write_text(teks, encoding="utf-8")

Pilih max_workers menurut batas permintaan per detik pada paket Anda, bukan menurut jumlah inti prosesor. Melebihi batas tidak mempercepat apa pun — ia hanya mengubah permintaan yang berhasil menjadi 429.

Langkah 3. Ulangi yang gagal, bukan yang berhasil

Dua kelas galat butuh penanganan yang berlawanan. 5xx atau kehabisan waktu layak diulang; 400 pada berkas rusak akan gagal dengan cara yang sama selamanya.

import time
from openai import APIStatusError, APIConnectionError

def dengan_ulang(path, percobaan=3):
    for n in range(percobaan):
        try:
            return transkrip(path)
        except APIConnectionError:
            pass                                  # gangguan jaringan — ulangi
        except APIStatusError as e:
            if e.status_code == 429 or e.status_code >= 500:
                pass                              # limit atau galat server — ulangi
            else:
                raise                             # 400/401 — mengulang tidak menolong
        time.sleep(2 ** n)                        # 1 dtk, 2 dtk, 4 dtk
    raise RuntimeError(f"gagal setelah {percobaan}: {path}")

Jeda yang membesar penting khusus untuk 429: mengulang seketika membuat Anda tetap di atas batas. Perhatikan max_retries=0 pada klien di atas — tanpa itu SDK juga mengulang dan kedua kebijakan saling bertabrakan.

Jangan biarkan satu berkas buruk menghentikan seluruh jalannya. Catat, lanjutkan, dan urus daftar kegagalannya di akhir — biasanya dua atau tiga berkas dari ribuan.

Langkah 4. Siapkan audionya sebelum dikirim

Di sinilah tagihan sebenarnya ditentukan. Dua aturan mengerjakan sebagian besarnya:

# mengonversi seluruh arsip dalam satu jalan
find rekaman -name '*.wav' -print0 |
  xargs -0 -P4 -I{} ffmpeg -loglevel error -i {} -ac 1 -ar 16000 -b:a 48k {}.mp3

Berkas di atas 25 MB tetap harus dipotong; dengan mono 48 kbit/dtk ambang itu setara sekitar satu jam audio, jadi setelah konversi kebanyakan arsip berhenti membutuhkan pemotongan sama sekali.

Langkah 5. Ketahui biayanya sebelum mulai

Penagihan per menit audio, jadi totalnya bisa diketahui di muka — ukur arsipnya alih-alih menebak:

ffprobe -v error -show_entries format=duration -of csv=p=0 berkas.mp3
import subprocess

def menit(path):
    out = subprocess.run(["ffprobe", "-v", "error", "-show_entries",
                          "format=duration", "-of", "csv=p=0", str(path)],
                         capture_output=True, text=True).stdout
    return float(out) / 60

total = sum(menit(p) for p in SRC.glob("*.mp3"))
print(f"{total:.0f} menit, {total * TARIF:.2f} pada tarif saat ini")

Jalankan ini sebelum pipeline, bukan sesudahnya. Butuh satu menit, tidak berbiaya, dan itulah beda antara pengeluaran yang direncanakan dan kejutan.

Langkah 6. Awasi jalannya

Proses massal yang panjang butuh tiga angka yang terlihat: selesai, gagal, dan laju. Satu penghitung yang dicetak tiap seratus berkas sudah cukup:

selesai = gagal = 0
for path in tertunda():
    try:
        simpan(path, dengan_ulang(path))
        selesai += 1
    except Exception as e:
        gagal += 1
        print("GAGAL", path.name, e)
    if (selesai + gagal) % 100 == 0:
        print(f"{selesai} selesai, {gagal} gagal")

Area klien menampilkan jalannya yang sama dari sisi lain — menit terpakai, jumlah permintaan, dan riwayat per permintaan — dan itu cara tercepat memastikan pipeline melakukan persis seperti yang Anda kira.

Yang tidak perlu dibangun

Coba dengan rekaman Anda sendiri. Pendaftaran memakan waktu satu menit, dan menit gratisnya cukup untuk menilai kualitasnya.

Ambil kunci API gratis

Pertanyaan yang sering diajukan

Berapa banyak berkas yang bisa dikirim sekaligus?

Konkurensinya dibatasi oleh batas permintaan per detik pada paket Anda, bukan oleh layanan. Setel kolam utas sedikit di bawah batas itu; melebihinya hanya menghasilkan respons 429.

Apa yang terjadi kalau prosesnya mati di tengah?

Tidak ada yang hilang kalau kemajuan diturunkan dari berkas keluaran: pada jalan berikutnya pipeline melewati semua yang sudah punya transkrip dan menyambung dari sana.

Apakah saya membayar lagi untuk berkas yang diulang?

Permintaan yang mengembalikan galat tidak menagih menit audio; transkripsi yang berhasil menagih. Karena itulah melewati berkas yang sudah selesai penting — mengulangi pekerjaan yang berhasil itulah yang memakan biaya.

Perlukah audio dikonversi sebelum dikirim?

Untuk arsip, ya, dan untungnya dobel: mono 16 kHz terunggah jauh lebih cepat, dan berkas berhenti melewati batas 25 MB sehingga Anda tidak perlu memotongnya.

Bagaimana memperkirakan biaya arsip besar?

Jumlahkan durasinya dengan ffprobe dan kalikan dengan tarif per menit Anda. Penagihan dihitung per menit audio, jadi perkiraan sebelum jalan adalah angka yang benar-benar akan Anda bayar.

Bacaan terkait