diff --git a/README.md b/README.md index 8bc2e72..70720d1 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,8 @@ The checked-in `docker-compose.yml` mounts local `./config.toml` there for the A ## Services And Ports - Frontend: `http://localhost:3000` -- Backend API: `http://localhost:8080` -- API docs: `http://localhost:8080/docs` +- Backend API: `http://localhost:8081` +- API docs: `http://localhost:8081/docs` - Redis: `localhost:6379` when exposed by Compose - PostgreSQL: only started by Compose when using the `local-db` profile @@ -145,7 +145,7 @@ The frontend reads the backend URL from `frontend/public/config.js` at runtime: ```js window.__EVANESCERE_FRONTEND_CONFIG__ = { - apiBaseUrl: "http://192.168.1.44:8080" + apiBaseUrl: "http://192.168.1.44:8081" }; ``` diff --git a/docker-compose.yml b/docker-compose.yml index 7dbae88..c4b3b77 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: build: . command: uvicorn evanescere.api:app --host 0.0.0.0 --port 8000 ports: - - "8080:8000" + - "8081:8000" volumes: - ./config.toml:/etc/evanescere/config.toml:ro - ./storage:/data/evanescere diff --git a/frontend/public/config.js b/frontend/public/config.js index 485c099..2e4f1de 100644 --- a/frontend/public/config.js +++ b/frontend/public/config.js @@ -1,3 +1,3 @@ window.__EVANESCERE_FRONTEND_CONFIG__ = { - apiBaseUrl: "http://192.168.1.44:8080" + apiBaseUrl: "http://192.168.1.44:8081" }; diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 8d71136..7fed10a 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -9,7 +9,7 @@ import type { const runtimeApiBase = window.__EVANESCERE_FRONTEND_CONFIG__?.apiBaseUrl; -export const apiBaseUrl = (runtimeApiBase || "http://localhost:8080").replace( +export const apiBaseUrl = (runtimeApiBase || "http://localhost:8081").replace( /\/$/, "", ); diff --git a/src/evanescere/services/asr.py b/src/evanescere/services/asr.py index a0495b4..4cda479 100644 --- a/src/evanescere/services/asr.py +++ b/src/evanescere/services/asr.py @@ -35,7 +35,7 @@ class FunAsrClient: "model": self.model, "language": "zh", "response_format": "verbose_json", - "timestamp_granularities[]": "segment", + "timestamp_granularities": "segment", }, files={"file": (audio_path.name, audio_file, "audio/wav")}, timeout=None, @@ -45,8 +45,12 @@ class FunAsrClient: logger.info("funasr request done audio=%s keys=%s", audio_path, sorted(payload.keys())) return payload - -def normalize_segments(payload: dict[str, Any]) -> list[dict[str, Any]]: +def normalize_segments( + payload: dict[str, Any], + *, + fallback_start_sec: float = 0.0, + fallback_end_sec: float | None = None, +) -> list[dict[str, Any]]: raw_segments = payload.get("segments") or payload.get("sentence_info") or [] logger.debug("normalizing asr segments raw_count=%s", len(raw_segments)) normalized: list[dict[str, Any]] = [] @@ -71,10 +75,17 @@ def normalize_segments(payload: dict[str, Any]) -> list[dict[str, Any]]: } ) if not normalized and payload.get("text"): + logger.warning( + "asr response contained text but no timestamped segments; " + "falling back to the known audio bounds" + ) + end_sec = fallback_end_sec + if end_sec is None: + end_sec = float(payload.get("duration") or fallback_start_sec + 0.1) normalized.append( { - "start_sec": 0.0, - "end_sec": 0.1, + "start_sec": fallback_start_sec, + "end_sec": max(fallback_start_sec + 0.1, end_sec), "text": str(payload["text"]).strip(), "speaker": None, "confidence": None, diff --git a/tests/test_asr.py b/tests/test_asr.py new file mode 100644 index 0000000..bfbd3a2 --- /dev/null +++ b/tests/test_asr.py @@ -0,0 +1,16 @@ +from evanescere.services.asr import normalize_segments + + +def test_normalize_segments_uses_known_audio_bounds_when_timestamps_are_missing(): + assert normalize_segments( + {"text": "你好", "segments": []}, fallback_start_sec=0, fallback_end_sec=15 + ) == [ + { + "start_sec": 0, + "end_sec": 15, + "text": "你好", + "speaker": None, + "confidence": None, + "metadata": {"text": "你好", "segments": []}, + } + ]