""" 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()