mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-19 06:40:38 -07:00
Add source version selection when applying effects, voices tab overhaul with inline inspector
This commit is contained in:
@@ -104,6 +104,7 @@ class GenerationVersion(Base):
|
||||
label = Column(String, nullable=False) # "clean", "processed", or user-defined
|
||||
audio_path = Column(String, nullable=False)
|
||||
effects_chain = Column(Text, nullable=True) # JSON-serialized effects config, null for clean
|
||||
source_version_id = Column(String, ForeignKey("generation_versions.id"), nullable=True) # Which version was used as input
|
||||
is_default = Column(Boolean, default=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
@@ -387,6 +388,16 @@ def _run_migrations(engine):
|
||||
conn.commit()
|
||||
print("Added version_id column to story_items")
|
||||
|
||||
# Migration: Add source_version_id to generation_versions table
|
||||
if 'generation_versions' in inspector.get_table_names():
|
||||
columns = {col['name'] for col in inspector.get_columns('generation_versions')}
|
||||
if 'source_version_id' not in columns:
|
||||
print("Migrating generation_versions: adding source_version_id column")
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE generation_versions ADD COLUMN source_version_id VARCHAR"))
|
||||
conn.commit()
|
||||
print("Added source_version_id column to generation_versions")
|
||||
|
||||
if 'generations' in inspector.get_table_names():
|
||||
columns = {col['name'] for col in inspector.get_columns('generations')}
|
||||
if 'is_favorited' not in columns:
|
||||
|
||||
+18
-8
@@ -1863,16 +1863,25 @@ async def apply_effects_to_generation(
|
||||
if error:
|
||||
raise HTTPException(status_code=400, detail=error)
|
||||
|
||||
# Find the original unprocessed version (no effects applied)
|
||||
# Determine source audio: use specified version, or fall back to clean/original
|
||||
all_versions = versions_mod.list_versions(generation_id, db)
|
||||
clean_version = next(
|
||||
(v for v in all_versions if v.effects_chain is None), None
|
||||
)
|
||||
if not clean_version:
|
||||
# Fallback: use the generation's audio_path directly
|
||||
source_path = gen.audio_path
|
||||
source_version_id = data.source_version_id
|
||||
if source_version_id:
|
||||
source_version = next(
|
||||
(v for v in all_versions if v.id == source_version_id), None
|
||||
)
|
||||
if not source_version:
|
||||
raise HTTPException(status_code=404, detail="Source version not found")
|
||||
source_path = source_version.audio_path
|
||||
else:
|
||||
source_path = clean_version.audio_path
|
||||
clean_version = next(
|
||||
(v for v in all_versions if v.effects_chain is None), None
|
||||
)
|
||||
if not clean_version:
|
||||
source_path = gen.audio_path
|
||||
else:
|
||||
source_path = clean_version.audio_path
|
||||
source_version_id = clean_version.id
|
||||
|
||||
if not source_path or not Path(source_path).exists():
|
||||
raise HTTPException(status_code=404, detail="Source audio file not found")
|
||||
@@ -1896,6 +1905,7 @@ async def apply_effects_to_generation(
|
||||
db=db,
|
||||
effects_chain=chain_dicts,
|
||||
is_default=data.set_as_default,
|
||||
source_version_id=source_version_id,
|
||||
)
|
||||
|
||||
return version
|
||||
|
||||
@@ -22,6 +22,8 @@ class VoiceProfileResponse(BaseModel):
|
||||
language: str
|
||||
avatar_path: Optional[str] = None
|
||||
effects_chain: Optional[List["EffectConfig"]] = None
|
||||
generation_count: int = 0
|
||||
sample_count: int = 0
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@@ -408,6 +410,7 @@ class GenerationVersionResponse(BaseModel):
|
||||
label: str
|
||||
audio_path: str
|
||||
effects_chain: Optional[List[EffectConfig]] = None
|
||||
source_version_id: Optional[str] = None
|
||||
is_default: bool
|
||||
created_at: datetime
|
||||
|
||||
@@ -418,6 +421,7 @@ class GenerationVersionResponse(BaseModel):
|
||||
class ApplyEffectsRequest(BaseModel):
|
||||
"""Request to apply effects to an existing generation."""
|
||||
effects_chain: List[EffectConfig]
|
||||
source_version_id: Optional[str] = Field(None, description="Version to use as source audio (defaults to clean/original)")
|
||||
label: Optional[str] = Field(None, max_length=100, description="Label for this version (auto-generated if omitted)")
|
||||
set_as_default: bool = Field(default=True, description="Set this version as the default")
|
||||
|
||||
|
||||
+38
-5
@@ -8,7 +8,7 @@ import uuid
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from .models import (
|
||||
VoiceProfileCreate,
|
||||
@@ -19,6 +19,7 @@ from .models import (
|
||||
from .database import (
|
||||
VoiceProfile as DBVoiceProfile,
|
||||
ProfileSample as DBProfileSample,
|
||||
Generation as DBGeneration,
|
||||
)
|
||||
from .models import EffectConfig
|
||||
from .utils.audio import validate_reference_audio, load_audio, save_audio
|
||||
@@ -29,7 +30,11 @@ from . import config
|
||||
import json as _json
|
||||
|
||||
|
||||
def _profile_to_response(profile: DBVoiceProfile) -> VoiceProfileResponse:
|
||||
def _profile_to_response(
|
||||
profile: DBVoiceProfile,
|
||||
generation_count: int = 0,
|
||||
sample_count: int = 0,
|
||||
) -> VoiceProfileResponse:
|
||||
"""Convert a DB profile to a VoiceProfileResponse, deserializing effects_chain."""
|
||||
effects_chain = None
|
||||
if profile.effects_chain:
|
||||
@@ -46,6 +51,8 @@ def _profile_to_response(profile: DBVoiceProfile) -> VoiceProfileResponse:
|
||||
language=profile.language,
|
||||
avatar_path=profile.avatar_path,
|
||||
effects_chain=effects_chain,
|
||||
generation_count=generation_count,
|
||||
sample_count=sample_count,
|
||||
created_at=profile.created_at,
|
||||
updated_at=profile.updated_at,
|
||||
)
|
||||
@@ -201,7 +208,7 @@ async def get_profile_samples(
|
||||
|
||||
async def list_profiles(db: Session) -> List[VoiceProfileResponse]:
|
||||
"""
|
||||
List all voice profiles.
|
||||
List all voice profiles with generation and sample counts.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
@@ -212,8 +219,34 @@ async def list_profiles(db: Session) -> List[VoiceProfileResponse]:
|
||||
profiles = db.query(DBVoiceProfile).order_by(
|
||||
DBVoiceProfile.created_at.desc()
|
||||
).all()
|
||||
|
||||
return [_profile_to_response(p) for p in profiles]
|
||||
|
||||
if not profiles:
|
||||
return []
|
||||
|
||||
# Batch-fetch generation counts
|
||||
gen_counts_rows = (
|
||||
db.query(DBGeneration.profile_id, func.count(DBGeneration.id))
|
||||
.group_by(DBGeneration.profile_id)
|
||||
.all()
|
||||
)
|
||||
gen_counts = {row[0]: row[1] for row in gen_counts_rows}
|
||||
|
||||
# Batch-fetch sample counts
|
||||
sample_counts_rows = (
|
||||
db.query(DBProfileSample.profile_id, func.count(DBProfileSample.id))
|
||||
.group_by(DBProfileSample.profile_id)
|
||||
.all()
|
||||
)
|
||||
sample_counts = {row[0]: row[1] for row in sample_counts_rows}
|
||||
|
||||
return [
|
||||
_profile_to_response(
|
||||
p,
|
||||
generation_count=gen_counts.get(p.id, 0),
|
||||
sample_count=sample_counts.get(p.id, 0),
|
||||
)
|
||||
for p in profiles
|
||||
]
|
||||
|
||||
|
||||
async def update_profile(
|
||||
|
||||
@@ -34,6 +34,7 @@ def _version_response(v: DBGenerationVersion) -> GenerationVersionResponse:
|
||||
label=v.label,
|
||||
audio_path=v.audio_path,
|
||||
effects_chain=effects_chain,
|
||||
source_version_id=v.source_version_id,
|
||||
is_default=v.is_default,
|
||||
created_at=v.created_at,
|
||||
)
|
||||
@@ -85,6 +86,7 @@ def create_version(
|
||||
db: Session,
|
||||
effects_chain: Optional[List[dict]] = None,
|
||||
is_default: bool = False,
|
||||
source_version_id: Optional[str] = None,
|
||||
) -> GenerationVersionResponse:
|
||||
"""Create a new version for a generation.
|
||||
|
||||
@@ -100,6 +102,7 @@ def create_version(
|
||||
label=label,
|
||||
audio_path=audio_path,
|
||||
effects_chain=json.dumps(effects_chain) if effects_chain else None,
|
||||
source_version_id=source_version_id,
|
||||
is_default=is_default,
|
||||
)
|
||||
db.add(version)
|
||||
|
||||
Reference in New Issue
Block a user