notes(asr): ffprobe duration=N/A 时回退用 ffmpeg null-muxer 解码统计
deploy notes / build-and-deploy (push) Successful in 1m58s

浏览器内 MediaRecorder 录的 webm/m4a 经常 metadata 没写 duration
(录到一半浏览器关掉 tab 没正常 finalize 文件)。ffprobe format.duration
返回 N/A。回退跑 `ffmpeg -i input -f null -`,从 stderr 最后一行
"time=HH:MM:SS.MS" parse 出实际秒数。慢一点但永远能拿到。
This commit is contained in:
Fam Zheng
2026-05-18 00:40:23 +01:00
parent a8e5100380
commit ca11a9bda7
+30 -10
View File
@@ -6,6 +6,7 @@
import json import json
import logging import logging
import os import os
import re
import shutil import shutil
import subprocess import subprocess
import tempfile import tempfile
@@ -17,6 +18,33 @@ import requests
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
def probe_duration(src: Path) -> float:
"""browser-recorded webm/m4a 经常没在 metadata 里写 duration(录到一半结束没法 finalize)。
先 try ffprobe format.durationN/A 时 fallback 让 ffmpeg null-muxer 解码一遍统计。
"""
try:
out = subprocess.check_output(
['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration',
'-of', 'csv=p=0', str(src)],
timeout=60,
).decode().strip()
if out and out != 'N/A':
return float(out)
except (subprocess.CalledProcessError, ValueError, subprocess.TimeoutExpired):
pass
log.info("ffprobe format.duration=N/A, decoding to count time")
proc = subprocess.run(
['ffmpeg', '-i', str(src), '-f', 'null', '-'],
stderr=subprocess.PIPE, stdout=subprocess.DEVNULL,
timeout=900,
)
matches = re.findall(rb'time=(\d+):(\d+):(\d+(?:\.\d+)?)', proc.stderr)
if not matches:
raise HTTPException(500, f'cannot determine duration; ffmpeg stderr tail: {proc.stderr[-300:].decode("utf-8","replace")}')
h, m, s = matches[-1]
return int(h) * 3600 + int(m) * 60 + float(s)
logging.basicConfig(level=logging.INFO, logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(levelname)s %(name)s: %(message)s') format='%(asctime)s %(levelname)s %(name)s: %(message)s')
log = logging.getLogger('feishu') log = logging.getLogger('feishu')
@@ -49,16 +77,8 @@ def transcribe(req: TranscribeReq):
tmp = Path(tempfile.gettempdir()) / f'transcribe-{uuid.uuid4().hex}' tmp = Path(tempfile.gettempdir()) / f'transcribe-{uuid.uuid4().hex}'
tmp.mkdir(parents=True) tmp.mkdir(parents=True)
try: try:
# 1) ffprobe 拿总时长 # 1) 拿总时长(ffprobe N/A 时回退 null-muxer 解码)
out = subprocess.check_output( duration = probe_duration(src)
['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration',
'-of', 'csv=p=0', str(src)],
timeout=60,
)
try:
duration = float(out.decode().strip())
except ValueError:
raise HTTPException(500, f'ffprobe duration parse: {out!r}')
log.info("duration=%.1fs", duration) log.info("duration=%.1fs", duration)
# 2) 切 chunk_seconds 段,stride = chunk_seconds - overlap_seconds # 2) 切 chunk_seconds 段,stride = chunk_seconds - overlap_seconds