mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-15 12:50:42 -07:00
- Added new components for managing audio channels, including creation, updating, and deletion of channels. - Introduced a new AudioTab for channel management and integrated it into the main application layout. - Updated the API client to support audio channel operations and added corresponding backend endpoints. - Enhanced the player store to handle audio playback routing through assigned channels. - Refactored existing components to accommodate the new audio channel functionality, including updates to the HistoryTable and GenerationForm for profile-channel associations. - Improved sidebar navigation to include new tabs for Voices and Audio management.
146 lines
4.6 KiB
Python
146 lines
4.6 KiB
Python
"""
|
|
SQLite database ORM using SQLAlchemy.
|
|
"""
|
|
|
|
from sqlalchemy import create_engine, Column, String, Integer, Float, DateTime, Text, ForeignKey, Boolean
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import sessionmaker, Session
|
|
from datetime import datetime
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
from . import config
|
|
|
|
Base = declarative_base()
|
|
|
|
|
|
class VoiceProfile(Base):
|
|
"""Voice profile database model."""
|
|
__tablename__ = "profiles"
|
|
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
name = Column(String, unique=True, nullable=False)
|
|
description = Column(Text)
|
|
language = Column(String, default="en")
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
|
|
class ProfileSample(Base):
|
|
"""Voice profile sample database model."""
|
|
__tablename__ = "profile_samples"
|
|
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
profile_id = Column(String, ForeignKey("profiles.id"), nullable=False)
|
|
audio_path = Column(String, nullable=False)
|
|
reference_text = Column(Text, nullable=False)
|
|
|
|
|
|
class Generation(Base):
|
|
"""Generation history database model."""
|
|
__tablename__ = "generations"
|
|
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
profile_id = Column(String, ForeignKey("profiles.id"), nullable=False)
|
|
text = Column(Text, nullable=False)
|
|
language = Column(String, default="en")
|
|
audio_path = Column(String, nullable=False)
|
|
duration = Column(Float, nullable=False)
|
|
seed = Column(Integer)
|
|
instruct = Column(Text)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
|
|
|
|
class Project(Base):
|
|
"""Audio studio project database model."""
|
|
__tablename__ = "projects"
|
|
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
name = Column(String, nullable=False)
|
|
data = Column(Text) # JSON string
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
|
|
class AudioChannel(Base):
|
|
"""Audio channel (bus) database model."""
|
|
__tablename__ = "audio_channels"
|
|
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
name = Column(String, nullable=False)
|
|
is_default = Column(Boolean, default=False)
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
|
|
|
|
class ChannelDeviceMapping(Base):
|
|
"""Mapping between channels and OS audio devices."""
|
|
__tablename__ = "channel_device_mappings"
|
|
|
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
channel_id = Column(String, ForeignKey("audio_channels.id"), nullable=False)
|
|
device_id = Column(String, nullable=False) # OS device identifier
|
|
|
|
|
|
class ProfileChannelMapping(Base):
|
|
"""Mapping between voice profiles and audio channels (many-to-many)."""
|
|
__tablename__ = "profile_channel_mappings"
|
|
|
|
profile_id = Column(String, ForeignKey("profiles.id"), primary_key=True)
|
|
channel_id = Column(String, ForeignKey("audio_channels.id"), primary_key=True)
|
|
|
|
|
|
# Database setup will be initialized in init_db()
|
|
engine = None
|
|
SessionLocal = None
|
|
_db_path = None
|
|
|
|
|
|
def init_db():
|
|
"""Initialize database tables."""
|
|
global engine, SessionLocal, _db_path
|
|
|
|
_db_path = config.get_db_path()
|
|
_db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
engine = create_engine(
|
|
f"sqlite:///{_db_path}",
|
|
connect_args={"check_same_thread": False},
|
|
)
|
|
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
Base.metadata.create_all(bind=engine)
|
|
|
|
# Create default channel if it doesn't exist
|
|
db = SessionLocal()
|
|
try:
|
|
default_channel = db.query(AudioChannel).filter(AudioChannel.is_default == True).first()
|
|
if not default_channel:
|
|
default_channel = AudioChannel(
|
|
id=str(uuid.uuid4()),
|
|
name="Default",
|
|
is_default=True
|
|
)
|
|
db.add(default_channel)
|
|
|
|
# Assign all existing profiles to default channel
|
|
profiles = db.query(VoiceProfile).all()
|
|
for profile in profiles:
|
|
mapping = ProfileChannelMapping(
|
|
profile_id=profile.id,
|
|
channel_id=default_channel.id
|
|
)
|
|
db.add(mapping)
|
|
|
|
db.commit()
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def get_db():
|
|
"""Get database session (generator for dependency injection)."""
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|