continue fixing
This commit is contained in:
@@ -432,9 +432,20 @@ def create_app(
|
||||
audio_name = f"{uuid.uuid4().hex}{suffix}"
|
||||
audio_path = service_settings.dub_shares_dir / audio_name
|
||||
await run_in_threadpool(_persist_upload, upload.file, audio_path, 50 * 1024 * 1024)
|
||||
await run_in_threadpool(_trim_leading_silence, audio_path)
|
||||
await run_in_threadpool(_align_share_audio, audio_path, boundary.end_ms / 1000, moss)
|
||||
saved_files.append(audio_path)
|
||||
score = scores_by_index.get(sentence_index, {})
|
||||
score_details = {
|
||||
key: float(score[key])
|
||||
for key in (
|
||||
"content_score",
|
||||
"fluency_score",
|
||||
"duration_score",
|
||||
"pause_score",
|
||||
"speech_rate_score",
|
||||
)
|
||||
if score.get(key) is not None
|
||||
}
|
||||
prepared_segments.append({
|
||||
"sentence_index": sentence_index,
|
||||
"start_ms": boundary.start_ms,
|
||||
@@ -444,6 +455,7 @@ def create_app(
|
||||
"audio_size_bytes": audio_path.stat().st_size,
|
||||
"overall_score": float(score.get("overall_score", 0)),
|
||||
"recognized_text": str(score.get("recognized_text") or ""),
|
||||
"score_details": json.dumps(score_details, ensure_ascii=False),
|
||||
})
|
||||
share = video_repository.create_dub_share(
|
||||
video_hash=video_hash.lower(),
|
||||
@@ -485,6 +497,7 @@ def create_app(
|
||||
"text": row["text"],
|
||||
"overall_score": row["overall_score"],
|
||||
"recognized_text": row["recognized_text"],
|
||||
"score_details": json.loads(row["score_details"] or "{}"),
|
||||
"audio_url": f"/api/v1/dub-shares/{share_id}/audio/{row['audio_filename']}",
|
||||
}
|
||||
for row in share["segments"]
|
||||
@@ -540,32 +553,73 @@ def _persist_upload(source: BinaryIO, destination: Path, max_bytes: int) -> tupl
|
||||
return digest.hexdigest(), size
|
||||
|
||||
|
||||
def _trim_leading_silence(audio_path: Path) -> None:
|
||||
def _align_share_audio(audio_path: Path, target_seconds: float, transcriber: Transcriber) -> None:
|
||||
ffmpeg = shutil.which("ffmpeg")
|
||||
if ffmpeg is None:
|
||||
return
|
||||
trimmed_path = audio_path.with_name(f"{audio_path.stem}-trimmed{audio_path.suffix}")
|
||||
command = [
|
||||
ffmpeg,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"error",
|
||||
"-y",
|
||||
"-i",
|
||||
str(audio_path),
|
||||
"-af",
|
||||
"silenceremove=start_periods=1:start_threshold=-45dB:start_silence=0.08",
|
||||
"-c:a",
|
||||
"aac",
|
||||
str(trimmed_path),
|
||||
]
|
||||
aligned_path = audio_path.with_name(f"{audio_path.stem}-aligned{audio_path.suffix}")
|
||||
try:
|
||||
transcript = transcriber.transcribe(audio_path)
|
||||
speech_segments = [segment for segment in transcript.segments if segment.text.strip()]
|
||||
if not speech_segments:
|
||||
return
|
||||
start_seconds = max(0.0, min(segment.start_seconds for segment in speech_segments) - 0.05)
|
||||
end_seconds = min(
|
||||
_media_duration_seconds(audio_path),
|
||||
max(segment.end_seconds for segment in speech_segments) + 0.08,
|
||||
)
|
||||
if end_seconds <= start_seconds + 0.1:
|
||||
return
|
||||
|
||||
filters: List[str] = [
|
||||
"silenceremove=start_periods=1:start_threshold=-45dB:start_silence=0.03"
|
||||
]
|
||||
if target_seconds > 0.2:
|
||||
speed_factor = (end_seconds - start_seconds) / target_seconds
|
||||
if 0.5 <= speed_factor <= 2.0:
|
||||
filters.append(f"atempo={speed_factor:.6f}")
|
||||
|
||||
command = [
|
||||
ffmpeg,
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"error",
|
||||
"-y",
|
||||
"-ss",
|
||||
f"{start_seconds:.3f}",
|
||||
"-t",
|
||||
f"{end_seconds - start_seconds:.3f}",
|
||||
"-i",
|
||||
str(audio_path),
|
||||
"-af",
|
||||
",".join(filters),
|
||||
"-c:a",
|
||||
"aac",
|
||||
str(aligned_path),
|
||||
]
|
||||
subprocess.run(command, check=True)
|
||||
if trimmed_path.stat().st_size == 0:
|
||||
raise RuntimeError("Trimmed audio is empty.")
|
||||
trimmed_path.replace(audio_path)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
trimmed_path.unlink(missing_ok=True)
|
||||
if aligned_path.stat().st_size > 0:
|
||||
aligned_path.replace(audio_path)
|
||||
except Exception:
|
||||
logger.warning("Could not align share audio %s; keeping original.", audio_path.name, exc_info=True)
|
||||
finally:
|
||||
aligned_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def _media_duration_seconds(audio_path: Path) -> float:
|
||||
ffprobe = shutil.which("ffprobe")
|
||||
if ffprobe is None:
|
||||
return 0.0
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
[ffprobe, "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", str(audio_path)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return float(completed.stdout.strip())
|
||||
except (OSError, ValueError, subprocess.SubprocessError):
|
||||
return 0.0
|
||||
|
||||
|
||||
async def _persist_request_stream(
|
||||
|
||||
Reference in New Issue
Block a user