สร้างงานถอดเสียง — POST /new-transcribe
Personal API key ใช้โควตาบัญชีเดียวกับ editor และเข้าถึงเฉพาะงานของเจ้าของ key บัญชีที่ไม่เปิดใช้งานตอบ 403 โควตาไม่พอตอบ 402 ดูวิธีสร้าง key
สร้างงานถอดเสียงใหม่จาก URL ของไฟล์เสียงหรือวิดีโอ ระบบจะตอบกลับ job_id สำหรับติดตามสถานะ แล้วประมวลผลงานในเบื้องหลัง
POST https://api.lomasub.ai/new-transcribe
Headers
| Header | ค่า | จำเป็น |
|---|---|---|
Authorization |
Bearer <API_KEY> |
ต้องระบุ |
Content-Type |
application/json |
ต้องระบุ |
Request Body
| ฟิลด์ | ชนิด | จำเป็น | คำอธิบาย |
|---|---|---|---|
source_url |
string (URL) | ต้องระบุ | URL แบบ HTTP(S) ของไฟล์เสียง/วิดีโอจาก hostname ที่เซิร์ฟเวอร์อนุญาต (ทุกรูปแบบที่ ffmpeg อ่านได้ worker แปลงเป็น 16 kHz โมโนเอง) ระบบตรวจสอบก่อนรับงานด้วย HEAD และ GET แบบ Range: bytes=0-0 เมื่อจำเป็น สูงสุด 3 ครั้ง |
filename |
string | — | ชื่อไฟล์สำหรับแสดงผล เก็บไว้กับงานและแสดงเป็น name ใน GET /jobs ระบบตัดเหลือเฉพาะชื่อไฟล์ (ไม่มีโฟลเดอร์) ค่าเริ่มต้นคือชื่อไฟล์ท้าย URL — สตริงว่างหรือยาวเกิน 255 ตัวอักษรตอบ 422 |
language |
string | "th" |
คำใบ้ภาษาให้ ASR บันทึกเป็น config.language; ไม่ส่งค่า, null หรือค่าว่างใช้ "th" ระบุ "en" เพื่อเปลี่ยนคำใบ้ได้ ข้อความภาษาอังกฤษและข้อความผสมไทย/อังกฤษจะไม่ถูกลบเพียงเพราะใช้คำใบ้ "th" |
config |
object | — | ตัวเลือกปรับพฤติกรรมการถอดเสียง ดูตารางด้านล่าง ระบบใช้เฉพาะคีย์ที่ส่งมา ค่าที่ไม่ระบุจะใช้ค่าเริ่มต้น ชื่อฟิลด์จาก API รุ่นเก่าตอบ 422 ส่วนคีย์ที่ระบบไม่เคยรู้จักจะถูกเก็บลง snapshot ตามที่ส่งมา |
duration_seconds |
number ≥ 0 | — | ความยาวสื่อ (วินาที) เก็บไว้กับงานและแสดงเป็น duration_seconds ใน GET /jobs และเป็นค่าเดียวที่ GET /jobs/match ใช้เทียบ — งานที่สร้างโดยไม่ส่งค่านี้จะไม่ถูกจับคู่เลย แม้ worker จะวัดความยาวได้เท่ากัน ไม่ใช่ job config จึงไม่อยู่ใน snapshot |
URL ที่อนุญาต
ค่าเริ่มต้นอนุญาตเฉพาะ lomafastsrt.sgp1.digitaloceanspaces.com และ
lomafastsrt.sgp1.cdn.digitaloceanspaces.com ใช้ URL จาก POST /presigned-url
หลังอัปโหลดไฟล์ หรือ URL ของไฟล์ที่อยู่บน hostname เหล่านี้
API และ worker ตรวจสอบ hostname แบบตรงตัวและตรวจว่า IP ที่ DNS ตอบกลับทุกตัวเป็นสาธารณะ
ไม่รับ URL ที่มี username/password, IP literal หรือพอร์ตที่ไม่ใช่ HTTP 80 / HTTPS 443
รองรับ redirect สูงสุด 5 ครั้ง โดยปลายทางทุกครั้งต้องผ่านเงื่อนไขเดียวกัน
URL ที่ไม่ผ่านนโยบายตอบ 400 พร้อม detail: "source_url destination is not allowed"
โดยไม่สร้างงาน ข้อมูล URL ที่ผิดรูปแบบยังตอบ 422
ผู้ดูแลเพิ่ม hostname ได้ผ่าน SOURCE_ALLOWED_HOSTS ใน .env ของ API และ worker ทุกเครื่อง
ค่านี้เป็น runtime configuration ไม่ใช่ตัวเลือกใน job config
แม้ hostname อยู่ในรายการก็ยังเข้าถึง private/loopback/link-local IP ไม่ได้
worker จำกัดขนาด source ที่ดาวน์โหลดจริงไว้ที่ 1 GiB และความยาวเสียงที่ถอดรหัสจริงไว้ที่
24 ชั่วโมง ค่า runtime SOURCE_MAX_BYTES และ MEDIA_MAX_DURATION_SECONDS
ตั้งใน .env ของ worker ไม่ใช่ job config และไม่ใช้ duration_seconds ที่ client แจ้ง
เพื่อผ่านข้อจำกัดนี้ งานที่เกินขีดจำกัดจะล้มเหลวใน SPLITTING ก่อนเรียกโมเดล
โดยไม่ตัดเสียงแล้วรับงานต่อ timeout ของแต่ละ stage ยังคงมีผลตามเดิม
โควตาและจำนวนงาน
สำหรับ session และ personal API key ค่า duration_seconds เป็นคำใบ้สำหรับจองโควตาตอนสร้างงาน:
งานจอง ความยาว × อัตราของโหมด (อย่างน้อยหนึ่งนาที) หลัง SPLITTING worker ใช้ความยาวเสียงที่ถอดรหัสจริง
เพื่อจองโควตาเต็มจำนวนก่อนเริ่มถอดเสียง ถ้าโควตาไม่พอ งานจะเป็น FAILED ใน SPLITTING
และคืนการจอง โดยยังไม่เรียกโมเดล /finish คิดค่าเสียงจริงเพียงครั้งเดียว ใช้โควตารายเดือนก่อน
แล้วจึงใช้เครดิต (เครดิตติดลบได้ถ้างานสุดท้ายใหญ่กว่ายอด)
โหมด (config.promodel) |
โควตาต่อวินาที | ขั้นต่ำต่องาน |
|---|---|---|
| ไม่ระบุ (ถอดเสียงปกติ) | 0.015 | 0.9 |
pro |
0.035 | 2.1 |
translate-* |
0.035 | 2.1 |
แต่ละบัญชีมีแพ็กเกจ — บัญชีใหม่เริ่มเป็น free และได้โควตารายเดือนตามแพ็กเกจ รีเซ็ตทุกเดือน เวลา 00:00 ตามวันครบรอบสมัครบัญชีในเวลาไทย โดยไม่สะสมส่วนที่เหลือ (วันที่ 29–31 ใช้วันสุดท้ายของเดือนที่สั้นกว่า)
| แพ็กเกจ | โควตาต่อเดือน | งานที่ยังไม่เสร็จ | เสียงต่อหนึ่งงาน | ขนาดไฟล์เสียงที่อัปโหลด |
|---|---|---|---|---|
| free (ฟรี) | 54 | 2 | 30 นาที | 100 MB |
| creator (ครีเอเตอร์) | 324 | 5 | 300 นาที (5 ชั่วโมง) | 1,000 MB |
| studio (สตูดิโอ) | 1,296 | 5 | 300 นาที (5 ชั่วโมง) | 1,000 MB |
1 MB = 1,000,000 bytes ใช้กับไฟล์เสียงที่อัปโหลดจริง ไม่ใช่วิดีโอต้นฉบับใน browser
เกินจำนวนงานตอบ 429 พร้อม Retry-After เสียงเกินเวลาตอบ 422 ไฟล์ใหญ่เกินตอบ 413
โควตาไม่พอตอบ 402 พร้อม detail ภาษาไทยที่บอกยอดคงเหลือทั้งสองส่วนและเวลารีเซ็ตครั้งถัดไป
worker ใช้ขีดจำกัดที่ต่ำกว่าระหว่างแพ็กเกจกับ runtime limit และตรวจขนาด/เวลาเสียงจริงก่อนเรียกโมเดล
การจองคงอยู่จนงานเป็น READY หรือ FAILED แม้งานจะเกิน 24 ชั่วโมงหรือ lease หมดอายุ
อัปเกรดแพ็กเกจแล้วโควตารายเดือนเพิ่มเป็นของแพ็กเกจใหม่ทันที ดาวน์เกรดมีผลกับยอดในรอบรีเซ็ตถัดไป
เมื่อจำนวนงานเดิมเกินแพ็กเกจใหม่ จะรับงานใหม่ไม่ได้จนจำนวนลดลง โดยไม่ยกเลิกงานเดิมอัตโนมัติ
shared secret ภายในไม่ถูกตรวจโควตาหรือจำกัดจำนวนงานต่อบัญชี แต่ runtime limits ของ worker ยังมีผล
ตัวอย่าง Request
curl -X POST "https://api.lomasub.ai/new-transcribe" \
-H "Authorization: Bearer $LOMA_API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_url": "https://lomafastsrt.sgp1.cdn.digitaloceanspaces.com/cancer.mp3",
"language": "th",
"config": {
"speaker_diarization": true
}
}'
ตัวเลือกใน config
ทุกฟิลด์เป็นตัวเลือกเสริม — ระบบจะบันทึกค่าเหล่านี้ไว้กับงาน (config snapshot) ตั้งแต่ตอนสร้าง โดย snapshot คือ ค่าเริ่มต้นของเซิร์ฟเวอร์ → ค่าที่คุณส่ง และ GET /status คืน object นี้กลับมาใน config ให้ตรวจสอบได้ว่า worker ใช้ค่าอะไรจริง ค่าเริ่มต้นในตารางคือค่าที่ติดตั้งมากับซอฟต์แวร์ แต่ละ deployment ปรับได้ — ปัจจุบัน api.lomasub.ai ปรับ 3 ค่า: min_vad_duration_seconds 10.0, min_srt_duration_ms 1200, srt_max_chars 48 (srt_cue_words ใช้ค่าเริ่มต้น 6)
รูปทรงของบรรทัดซับไตเติลกำหนดด้วย 3 คีย์: srt_max_chars คือเพดานตัวอักษร, srt_cue_words คือจำนวนคำที่ตัวตัดบรรทัดพยายามให้ได้, min_srt_duration_ms คือเวลาแสดงผลขั้นต่ำ ชุดค่าที่เข้ากันคือประมาณ จำนวนคำ × 5 ตัวอักษร และ จำนวนคำ × 200 มิลลิวินาที — production ใช้ 6 / 48 / 1200
การสร้างซับไตเติล
| ฟิลด์ | ชนิด | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
srt_max_chars |
int | 40 |
จำนวนตัวอักษรสูงสุดต่อบรรทัดซับไตเติล นับเฉพาะข้อความที่พูด (ไม่นับป้ายชื่อผู้พูด) บังคับใช้ทุกโหมดและทุกขั้นตอน รวมถึงตอนรวมบรรทัดสั้น (ยกเว้นกรณีคำเดียวยาวเกิน) |
srt_cue_words |
int | 6 |
จำนวนคำต่อบรรทัดที่ต้องการ (3–15) ตัวตัดบรรทัดจะพยายามให้แต่ละบรรทัดมีคำประมาณนี้ก่อนตัดที่ช่วงหยุดถัดไป — ค่าที่เข้ากันคือ srt_max_chars ≈ 5 ตัวอักษรต่อคำ และ min_srt_duration_ms ≈ 200 มิลลิวินาทีต่อคำ (production ใช้ 6 / 48 / 1200) นอกช่วง 3–15 ตอบ 422 (รูปแบบ list) |
min_srt_duration_ms |
int | 2000 |
ระยะเวลาแสดงผลขั้นต่ำของแต่ละบรรทัด (มิลลิวินาที) บรรทัดที่สั้นกว่านี้จะถูกรวมเข้ากับบรรทัดข้างเคียงที่รอยต่อเงียบน้อยกว่า (ไม่รวมถ้าผู้พูดต่างกัน รอยต่อมีช่วงเงียบ ≥ 1 วินาที หรือข้อความหลังรวมเกิน srt_max_chars) ถ้ารวมไม่ได้จะยืดเวลาสิ้นสุดเข้าไปในช่วงเงียบจนถึงเวลาเริ่มของบรรทัดถัดไป บรรทัดที่ยังสั้นกว่าเกณฑ์อยู่จะคงไว้ตามเดิมและถูกบันทึกใน log ของ worker |
promodel |
string หรือ null | null |
"pro" ตรวจแก้คำก่อน alignment; "translate-th", "translate-en", "translate-ja", "translate-ko", "translate-zh" แปลซับเป็นไทย อังกฤษ ญี่ปุ่น เกาหลี หรือจีนตัวย่อ ตามลำดับ (jp/kr เป็น alias ของ ja/ko); ไม่ส่งหรือ null ใช้ภาษาต้นฉบับ เลือก preset ผ่าน API ไม่ได้ |
forcealigner_level |
string | "default" |
ระดับการจัดตำแหน่งเวลาของคำ — "none" ปิดการจัดตำแหน่งละเอียด (ใช้เวลาแบบหยาบ), "default" จัดตำแหน่งด้วย ForceAligner แล้วตัดประโยคด้วยตัวตัดระดับคำแบบกำหนดกฎในเครื่อง (ค่าเริ่มต้น ไม่เรียก LLM ภายนอก), "llmsplitting" ตัดประโยคด้วยโมเดลภาษาภายนอกและจัดตำแหน่งเวลารอบที่สอง โหมด "llmsplitting" เพิ่มขั้นตอน LLM_SPLITTING และ FORCEDALIGN_2ND (ช้ากว่า) ส่วน "none" จะไม่มี result.karaoke.vtt และ result.words.json และ cue ใน result.debug.vtt เป็นข้อความล้วนไม่มี timestamp ระดับคำ ค่าอื่น (รวมถึง "cueseg" เดิม) และฟิลด์ boolean เก่า enable_forcealigner จะถูกปฏิเสธด้วย 422 |
ซับไตเติลแสดงต่อเนื่องเสมอ: เวลาสิ้นสุดของบรรทัดจะยืดไปจนถึงเวลาเริ่มของบรรทัดถัดไป เว้นแต่มีช่วงเงียบตั้งแต่ 1 วินาทีคั่นอยู่ (เวลาเริ่มของบรรทัดไม่ถูกขยับ) ไม่มีฟิลด์สำหรับตั้งค่าพฤติกรรมนี้
การแบ่งช่วงเสียง (VAD)
| ฟิลด์ | ชนิด | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
min_vad_duration_seconds |
float | 5.0 |
ความยาวช่วงเสียงขั้นต่ำ (วินาที) — ช่วงเสียงจะถูกเปิดค้างไว้จนมีเสียงอย่างน้อยเท่านี้ก่อนจึงปิดที่ช่วงเงียบได้ (production ใช้ 10.0) |
max_vad_duration_seconds |
float | 25.0 |
ความยาวช่วงเสียงสูงสุด (วินาที) ที่ส่งให้ Whisper — เสียงพูดต่อเนื่องจะถูกตัดที่ความยาวนี้โดยไม่มีช่องโหว่ |
vad_threshold |
float | 0.30 |
ค่าความน่าจะเป็นเสียงพูดของ Silero (ต่อเฟรม 32 ms) ที่ถือว่าเป็นเสียงพูด (0.0–1.0) ค่าต่ำช่วยเก็บบทพูดเสียงเบา ค่าสูงช่วยตัดเพลงและจิงเกิล (ค่าใน dataclass ของโค้ดคือ 0.4 แต่ค่าเริ่มต้นจาก env ตอนบูตคือ 0.30) |
การระบุผู้พูด (Speaker Diarization)
| ฟิลด์ | ชนิด | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
speaker_diarization |
bool | false |
เปิดการจำแนกและติดป้ายชื่อผู้พูดในซับไตเติล (เพิ่มขั้นตอน DIARIZING) แต่ละบรรทัดจะมีป้าย A:, B: นำหน้าใน SRT และ VTT และไฟล์ VTT จะมี embedding ของแต่ละบรรทัดซ่อนอยู่ |
speaker_local_distance_threshold |
float | 0.60 |
ระยะ cosine ที่ต่ำกว่านี้ถือว่าสองบรรทัดในกลุ่มย่อยเดียวกันเป็นผู้พูดคนเดียวกัน (0.0–2.0) ค่าต่ำ = แยกผู้พูดมากขึ้น |
speaker_merge_distance_threshold |
float | 0.35 |
ระยะที่ต่ำกว่านี้จะรวมกลุ่มผู้พูดจากคนละกลุ่มย่อยเป็นผู้พูดคนเดียวกันทั้งงาน (0.0–2.0) |
speaker_clustering_drop_dialogue_percentile |
float | 0.02 |
ผู้พูดที่มีสัดส่วนบรรทัดน้อยกว่าค่านี้ และ จำนวนบรรทัดน้อยกว่า agglomeration_min_appear จะคงข้อความไว้แต่ไม่ได้ป้ายชื่อ (0 ≤ x < 1) |
agglomeration_min_appear |
int | 5 |
กลุ่มผู้พูดขนาดเล็กที่มีบรรทัดตั้งแต่เท่านี้ขึ้นไปจะได้ป้ายชื่อเสมอ |
agglomolation_group_size |
int | 300 |
จำนวนบรรทัดต่อกลุ่มย่อยสำหรับการจัดกลุ่มแบบแบ่งช่วงเวลาในไฟล์ยาว (0 = จัดกลุ่มรอบเดียวทั้งงาน) การสะกดเป็นไปตาม API |
Response — 201 Created
{
"job_id": "0b8a4f3e-6c1d-4a9b-9f2e-7d5c3b1a8e64",
"status_url": "https://api.lomasub.ai/status?job_id=0b8a4f3e-6c1d-4a9b-9f2e-7d5c3b1a8e64",
"state": "QUEUED",
"s1_queued_ts": "2026-07-10T09:15:22.481Z"
}
| ฟิลด์ | ชนิด | คำอธิบาย |
|---|---|---|
job_id |
string (UUID) | รหัสงานสำหรับติดตามสถานะ |
status_url |
string (URL) | URL สำเร็จรูปสำหรับเรียกตรวจสอบสถานะ |
state |
string | สถานะเริ่มต้นของงาน เป็น QUEUED เสมอ |
s1_queued_ts |
string (timestamp) | เวลาที่งานเข้าคิว |
ข้อผิดพลาดที่อาจพบ
| สถานะ | สาเหตุ | ตัวอย่างข้อความ |
|---|---|---|
400 Bad Request |
source_url เข้าถึงไม่ได้ (HEAD และ GET แบบ range ล้มเหลวครบ 3 ครั้ง) |
"source_url is not reachable" |
401 Unauthorized |
ไม่มีหรือ API key ไม่ถูกต้อง | — |
422 Unprocessable Entity |
ชนิดหรือช่วงค่าไม่ถูกต้อง (เช่น srt_cue_words นอกช่วง 3–15, vad_threshold เกิน 1, filename ยาวเกิน 255) ตอบเป็น list ตามรูปแบบ FastAPI / ใช้ชื่อฟิลด์เก่าที่เปลี่ยนชื่อหรือยกเลิกแล้ว ตอบ {"detail": "<ข้อความ>"} |
ระบุฟิลด์ที่ผิดพร้อมชื่อฟิลด์ที่มาแทน |
ขั้นตอนถัดไป: นำ job_id ไปติดตามความคืบหน้าที่ ตรวจสอบสถานะงาน — GET /status
Filler removal
config.remove_filler is a boolean, true by default. Set it to false to
keep fillers. Omission or null uses the default; strings and numbers are
rejected with 422. The effective value is stored in the job snapshot and
returned by /status and /claim-job. Older queued snapshots missing the
setting also default to true; completed results are not rewritten.
For Thai and English jobs (including mixed Thai/English speech), the worker
removes only separated uh, um, เอ่อ, and อ่า, after ASR cleanup and
before optional Pro correction and alignment. Both uh/um and Uh/Um are
recognized. All-uppercase abbreviations, quoted terms, URLs, email addresses,
compound words, possible numeric um measurements, and attached Thai text
(such as อ่าน, เอ่อล้น, or อ่าตำรวจ) are preserved. Other sounds such as
อืม, อื้อ, hm, and hmm are never removed by this filter. Other job
languages are unchanged.
This is a readability option: removing hesitation can lose conversational
nuance, and อ่า sometimes expresses acknowledgement. Only adjacent filler
punctuation/whitespace is repaired; surviving words are not rewritten. Clear
filler-only chunks may become empty. If no transcript units remain in the
entire job, the existing no-content failure applies.
The option applies to all three forcealigner_level values and all generated
subtitle formats. Original ASR remains in the worker trace; filler_filter.json
records before/after text and removal spans. ASR segment timestamps are retained
when cleaned segment text agrees with the cleaned transcript. Otherwise the
worker clears stale segments; level none then uses chunk-level coarse timing.
Both CLI clients accept --remove-filler and --no-remove-filler; omission uses
the API default:
python3 runjob.py "<audio_url>" --no-remove-filler
PYTHONPATH=api-server api-server/.venv/bin/python api-server/enqueue-job.py "<audio_url>" --no-remove-filler
Pro transcript correction
config.promodel="pro" enables word correction after ASR cleanup and before
alignment. The worker sends ordered batches of 50 chunk rows to the fixed
worker preset, validates exact replacements, and waits for all batches or the
stage deadline. Valid edits are retained even if other edits/batches fail; only
unresolved portions keep their original cleaned text. Repeated exact matches
within a row are all corrected, with longer phrases taking overlap precedence.
No speaker-name inference is performed. Normal and Pro jobs use the same result
fields; no separate revised subtitle objects are created.
revise_srt, revise_speakername, and srt_revision_backend are retired and
return 422. Worker-only model, concurrency, and timeout settings cannot be
selected by a job. ASR omission recovery has been removed for every job.
Translation: one output language per job
Select exactly one mode with config.promodel (CLI: --promodel):
translate-th: Thai.translate-en: English.translate-ja: Japanese (translate-jpis accepted and normalized).translate-ko: Korean (translate-kris accepted and normalized).translate-zh: Simplified Chinese.
{"source_url":"<public audio URL>","language":"th","config":{"promodel":"translate-zh"}}
language is still the source ASR hint. Omission/null keeps original-language output;
pro retains its existing correction behavior. Unsupported modes return 422.
Translation supports forcealigner_level=default|none; combining it with
llmsplitting returns 422 because that adds another LLM processing stage.
Translation uses the same worker preset as Pro with a different prompt, combining
conservative recognition correction and translation in one stage. It runs after
source cue timing, within BUILDING_SRT, in batches of 100 cues with read-only
neighboring context. This batch size is independent of Pro correction's 50 ASR
chunk rows. It does not run Pro first. A source/target language match
uses conservative same-language correction.
Cue count, timestamps and speaker assignments stay fixed. The source splitter
still applies srt_max_chars; target text may exceed it to retain meaning, with
length overages logged. Translation never silently returns original-language
fallback cues: an incomplete batch after retries fails the job.
The existing srt_url, vtt_url, and compact_url deliver the selected language.
debug_vtt_url, when available, contains translated text and source diagnostics.
Translation jobs have no karaoke_vtt_url or words_url: source word timings
cannot be assigned to translated words. Original text remains in local worker
traces only. Quota charging is unchanged.
Job history rows include promodel. GET /jobs/match accepts promodel with the
same modes and aliases; absent/empty means normal original-language jobs only.
The editor supplies its selected mode so reusing a file cannot load another language.