| | |
| | | |
| | | from __future__ import annotations |
| | | |
| | | import json |
| | | import os |
| | | import time |
| | | from typing import Any, Optional |
| | |
| | | return os.path.join(get_project_dir(), rel) |
| | | |
| | | |
| | | def plain_transcript(text: Any) -> str: |
| | | """ASR/TTS 归档文本。FunASR 可能是 {content, speaker, ...} JSON。""" |
| | | if text is None: |
| | | return "" |
| | | raw = str(text) |
| | | stripped = raw.strip() |
| | | if stripped.startswith("{") and stripped.endswith("}"): |
| | | try: |
| | | data = json.loads(stripped) |
| | | if isinstance(data, dict) and "content" in data: |
| | | return str(data.get("content") or "") |
| | | except Exception: |
| | | pass |
| | | return raw |
| | | |
| | | |
| | | def write_sidecar_text(wav_path: str, text: Optional[str]) -> Optional[str]: |
| | | """Write UTF-8 .txt next to a wav. Same stem, suffix .txt. None skips.""" |
| | | if text is None or not wav_path: |
| | | return None |
| | | sidecar = os.path.splitext(wav_path)[0] + ".txt" |
| | | try: |
| | | body = plain_transcript(text) |
| | | with open(sidecar, "w", encoding="utf-8") as handle: |
| | | handle.write(body) |
| | | if body and not body.endswith("\n"): |
| | | handle.write("\n") |
| | | return sidecar |
| | | except Exception as exc: |
| | | logger.bind(tag=TAG).warning(f"文本归档失败: {exc}") |
| | | return None |
| | | |
| | | |
| | | def write_sidecar_for_relative(conn: Any, relative: str, text: Optional[str]) -> Optional[str]: |
| | | """Update the .txt next to an already archived wav (relative audio_id).""" |
| | | if not relative or text is None: |
| | | return None |
| | | rel = str(relative).replace("\\", "/").lstrip("/") |
| | | if "/" in rel: |
| | | path = os.path.join(archive_root(conn), *rel.split("/")) |
| | | else: |
| | | path = os.path.join(get_project_dir(), "data", "task_sessions", "audio", rel) |
| | | return write_sidecar_text(path, text) |
| | | |
| | | |
| | | def archive_wav( |
| | | conn: Any, |
| | | wav_bytes: bytes, |
| | | *, |
| | | kind: str = "asr", |
| | | extra: str = "", |
| | | text: Optional[str] = None, |
| | | ) -> Optional[str]: |
| | | """Write WAV under recordings/{device}/{mode}/{date}/{time}_{kind}_{extra}.wav.""" |
| | | """Write WAV under recordings/{device}/{mode}/{date}/{time}_{kind}_{extra}.wav. |
| | | |
| | | If text is not None, also write the same stem with .txt for later analysis. |
| | | """ |
| | | if not wav_bytes or not archive_enabled(conn): |
| | | return None |
| | | try: |
| | |
| | | path = os.path.join(directory, filename) |
| | | with open(path, "wb") as handle: |
| | | handle.write(wav_bytes) |
| | | if text is not None: |
| | | write_sidecar_text(path, text) |
| | | relative = os.path.join(device, mode, day, filename).replace("\\", "/") |
| | | logger.bind(tag=TAG).info(f"音频归档 {relative} bytes={len(wav_bytes)}") |
| | | return relative |
| | |
| | | return None |
| | | |
| | | |
| | | def archive_pcm_snapshot(conn: Any, snapshot: Any, *, extra: str = "") -> Optional[str]: |
| | | def archive_pcm_snapshot( |
| | | conn: Any, |
| | | snapshot: Any, |
| | | *, |
| | | extra: str = "", |
| | | text: Optional[str] = None, |
| | | ) -> Optional[str]: |
| | | from core.app_runtime.assessment import pcm_snapshot_to_wav |
| | | |
| | | wav_bytes = pcm_snapshot_to_wav(snapshot) |
| | | if not wav_bytes: |
| | | return None |
| | | return archive_wav(conn, wav_bytes, kind="asr", extra=extra) |
| | | return archive_wav(conn, wav_bytes, kind="asr", extra=extra, text=text) |