Spaces:
Runtime error
Runtime error
Refactor VideoTemplateEditor component: remove AI editing features and related state management, streamline template loading, and update styles for improved layout. Enhance CSS for responsive design and visual consistency across various components, including dropdowns, sync stats, and quiz elements. Remove deprecated premium badge styles and adjust layout for chat components.
66de9c8 Download backend/app/routers/video_voices.py from firebird-technologies/NoteStack-Test: direct link, hf CLI and curl.
- Browser
- Download file 5.02 kB
-
https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/main/backend/app/routers/video_voices.py
- Command line
-
hf download hf://spaces/firebird-technologies/NoteStack-Test/backend/app/routers/video_voices.py
-
curl -L -o video_voices.py https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/main/backend/app/routers/video_voices.py
5.02 kB
| """Voices for videos: the workspace's saved voices, the free built-in library, and its Notestack voices (made on the Voice | |
| page; they speak audio overviews, not videos). | |
| "Your voices" (user_saved_voices, as many as wanted) is the one list for audio overviews and videos, and lives only | |
| here: blog2video's saved-voice list is one list for the whole account. | |
| """ | |
| import logging | |
| from fastapi import APIRouter, Depends, HTTPException | |
| from pydantic import BaseModel, Field | |
| from sqlalchemy import select | |
| from app.auth import Ctx, get_ctx | |
| from app.models import UserSavedVoice | |
| from app.routers.videos import b2v_ready, upstream | |
| from app.services import blog2video as b2v | |
| from app.services.notestack_voices import notestack_voices, save_notestack_voice | |
| log = logging.getLogger(__name__) | |
| router = APIRouter(prefix="/api/video-voices", tags=["videos"]) | |
| STARTER_VOICES = 4 # a new workspace starts with this many free built-in voices saved | |
| def _labels(v: dict) -> dict: | |
| return v.get("labels") or {} | |
| def _voice_out(v: UserSavedVoice, ours: set[str] = frozenset()) -> dict: | |
| return {"voice_id": v.voice_id, "name": v.name, "preview_url": v.preview_url, "gender": v.gender, | |
| "accent": v.accent, | |
| "source": "notestack" if v.voice_id in ours else "blog2video"} | |
| def _library_out(v: dict, saved: set[str]) -> dict: | |
| labels = _labels(v) | |
| return {"voice_id": v["voice_id"], "name": (v.get("name") or "Voice").split(" - ")[0].strip(), | |
| "description": v.get("description") or labels.get("description") or "", "preview_url": v.get("preview_url"), | |
| "gender": labels.get("gender"), "accent": labels.get("accent"), "age": labels.get("age"), | |
| "saved": v["voice_id"] in saved} | |
| def _save_builtin(ctx: Ctx, v: dict) -> UserSavedVoice: | |
| labels = _labels(v) | |
| row = UserSavedVoice(workspace_id=ctx.workspace.id, voice_id=v["voice_id"], | |
| name=(v.get("name") or "Voice").split(" - ")[0].strip()[:255], | |
| preview_url=v.get("preview_url"), | |
| gender=labels.get("gender"), accent=labels.get("accent")) | |
| ctx.db.merge(row) | |
| return row | |
| def _saved(ctx: Ctx, ours: set[str] = frozenset()) -> list[UserSavedVoice]: | |
| """The saved voices that can still be used: free built-in voices and the workspace's Notestack voices. Voices that | |
| were designed or cloned for videos, and paid built-in ones, are gone.""" | |
| rows = ctx.db.scalars(select(UserSavedVoice).where(UserSavedVoice.workspace_id == ctx.workspace.id) | |
| .order_by(UserSavedVoice.created_at)) | |
| return [v for v in rows if v.voice_id in ours or not (v.is_custom or v.premium)] | |
| def voices(ctx: Ctx = Depends(get_ctx), _: None = Depends(b2v_ready)): | |
| """Your voices (audio overviews and videos), the free built-in library (to save from) and your Notestack voices.""" | |
| with upstream(): | |
| library = [v for v in b2v.prebuilt_voices() if v.get("plan") != "paid"] | |
| mine = notestack_voices(ctx.db, ctx.workspace.id) | |
| ours = {v["voice_id"] for v in mine} | |
| saved = _saved(ctx, ours) | |
| if not saved: | |
| for v in [v for v in library if v.get("voice_id")][:STARTER_VOICES]: | |
| _save_builtin(ctx, v) | |
| ctx.db.commit() | |
| saved = _saved(ctx, ours) | |
| ids = {v.voice_id for v in saved} | |
| return {"saved": [_voice_out(v, ours) for v in saved], | |
| "notestack": [{**v, "saved": v["voice_id"] in ids} for v in mine], | |
| "library": [_library_out(v, ids) for v in library if v.get("voice_id")]} | |
| class SaveIn(BaseModel): | |
| voice_id: str = Field(min_length=1, max_length=100) | |
| def save_voice(body: SaveIn, ctx: Ctx = Depends(get_ctx), _: None = Depends(b2v_ready)): | |
| if ctx.db.get(UserSavedVoice, {"workspace_id": ctx.workspace.id, "voice_id": body.voice_id}): | |
| return _voice_out(ctx.db.get(UserSavedVoice, {"workspace_id": ctx.workspace.id, "voice_id": body.voice_id})) | |
| mine = next((v for v in notestack_voices(ctx.db, ctx.workspace.id) if v["voice_id"] == body.voice_id), None) | |
| if mine: | |
| # A Notestack voice: saved on any plan, for audio overviews. No preview link (the Voice page plays it). | |
| row = save_notestack_voice(ctx.db, ctx.workspace.id, mine["voice_id"], mine["name"]) | |
| ctx.db.commit() | |
| return _voice_out(row, {row.voice_id}) | |
| with upstream(): | |
| v = next((v for v in b2v.prebuilt_voices() if v.get("voice_id") == body.voice_id), None) | |
| if not v or v.get("plan") == "paid": | |
| raise HTTPException(404, "Voice not found") | |
| row = _save_builtin(ctx, v) | |
| ctx.db.commit() | |
| return _voice_out(row) | |
| def unsave_voice(voice_id: str, ctx: Ctx = Depends(get_ctx)): | |
| row = ctx.db.get(UserSavedVoice, {"workspace_id": ctx.workspace.id, "voice_id": voice_id}) | |
| if not row: | |
| raise HTTPException(404, "Voice not found") | |
| ctx.db.delete(row) | |
| ctx.db.commit() | |
| return {"ok": True} | |