feat(audiobook): implement audiobook project management features
This commit is contained in:
@@ -3,7 +3,7 @@ from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from db.models import User, Job, VoiceCache, SystemSettings, VoiceDesign
|
||||
from db.models import User, Job, VoiceCache, SystemSettings, VoiceDesign, AudiobookProject, AudiobookCharacter, AudiobookSegment
|
||||
|
||||
def get_user_by_username(db: Session, username: str) -> Optional[User]:
|
||||
return db.query(User).filter(User.username == username).first()
|
||||
@@ -355,3 +355,200 @@ def update_voice_design_usage(db: Session, design_id: int, user_id: int) -> Opti
|
||||
db.commit()
|
||||
db.refresh(design)
|
||||
return design
|
||||
|
||||
|
||||
def update_user_llm_config(
|
||||
db: Session,
|
||||
user_id: int,
|
||||
llm_api_key: Optional[str] = None,
|
||||
llm_base_url: Optional[str] = None,
|
||||
llm_model: Optional[str] = None,
|
||||
clear: bool = False
|
||||
) -> Optional[User]:
|
||||
user = get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
return None
|
||||
if clear:
|
||||
user.llm_api_key = None
|
||||
user.llm_base_url = None
|
||||
user.llm_model = None
|
||||
else:
|
||||
if llm_api_key is not None:
|
||||
user.llm_api_key = llm_api_key
|
||||
if llm_base_url is not None:
|
||||
user.llm_base_url = llm_base_url
|
||||
if llm_model is not None:
|
||||
user.llm_model = llm_model
|
||||
user.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
def create_audiobook_project(
|
||||
db: Session,
|
||||
user_id: int,
|
||||
title: str,
|
||||
source_type: str,
|
||||
source_text: Optional[str] = None,
|
||||
source_path: Optional[str] = None,
|
||||
llm_model: Optional[str] = None,
|
||||
) -> AudiobookProject:
|
||||
project = AudiobookProject(
|
||||
user_id=user_id,
|
||||
title=title,
|
||||
source_type=source_type,
|
||||
source_text=source_text,
|
||||
source_path=source_path,
|
||||
llm_model=llm_model,
|
||||
status="pending",
|
||||
)
|
||||
db.add(project)
|
||||
db.commit()
|
||||
db.refresh(project)
|
||||
return project
|
||||
|
||||
|
||||
def get_audiobook_project(db: Session, project_id: int, user_id: int) -> Optional[AudiobookProject]:
|
||||
return db.query(AudiobookProject).filter(
|
||||
AudiobookProject.id == project_id,
|
||||
AudiobookProject.user_id == user_id
|
||||
).first()
|
||||
|
||||
|
||||
def list_audiobook_projects(db: Session, user_id: int, skip: int = 0, limit: int = 50) -> List[AudiobookProject]:
|
||||
return db.query(AudiobookProject).filter(
|
||||
AudiobookProject.user_id == user_id
|
||||
).order_by(AudiobookProject.created_at.desc()).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def update_audiobook_project_status(
|
||||
db: Session,
|
||||
project_id: int,
|
||||
status: str,
|
||||
error_message: Optional[str] = None
|
||||
) -> Optional[AudiobookProject]:
|
||||
project = db.query(AudiobookProject).filter(AudiobookProject.id == project_id).first()
|
||||
if not project:
|
||||
return None
|
||||
project.status = status
|
||||
if error_message is not None:
|
||||
project.error_message = error_message
|
||||
project.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
db.refresh(project)
|
||||
return project
|
||||
|
||||
|
||||
def delete_audiobook_project(db: Session, project_id: int, user_id: int) -> bool:
|
||||
project = get_audiobook_project(db, project_id, user_id)
|
||||
if not project:
|
||||
return False
|
||||
db.delete(project)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def create_audiobook_character(
|
||||
db: Session,
|
||||
project_id: int,
|
||||
name: str,
|
||||
description: Optional[str] = None,
|
||||
instruct: Optional[str] = None,
|
||||
voice_design_id: Optional[int] = None,
|
||||
) -> AudiobookCharacter:
|
||||
char = AudiobookCharacter(
|
||||
project_id=project_id,
|
||||
name=name,
|
||||
description=description,
|
||||
instruct=instruct,
|
||||
voice_design_id=voice_design_id,
|
||||
)
|
||||
db.add(char)
|
||||
db.commit()
|
||||
db.refresh(char)
|
||||
return char
|
||||
|
||||
|
||||
def get_audiobook_character(db: Session, char_id: int) -> Optional[AudiobookCharacter]:
|
||||
return db.query(AudiobookCharacter).filter(AudiobookCharacter.id == char_id).first()
|
||||
|
||||
|
||||
def list_audiobook_characters(db: Session, project_id: int) -> List[AudiobookCharacter]:
|
||||
return db.query(AudiobookCharacter).filter(
|
||||
AudiobookCharacter.project_id == project_id
|
||||
).all()
|
||||
|
||||
|
||||
def update_audiobook_character_voice(
|
||||
db: Session,
|
||||
char_id: int,
|
||||
voice_design_id: int
|
||||
) -> Optional[AudiobookCharacter]:
|
||||
char = db.query(AudiobookCharacter).filter(AudiobookCharacter.id == char_id).first()
|
||||
if not char:
|
||||
return None
|
||||
char.voice_design_id = voice_design_id
|
||||
db.commit()
|
||||
db.refresh(char)
|
||||
return char
|
||||
|
||||
|
||||
def create_audiobook_segment(
|
||||
db: Session,
|
||||
project_id: int,
|
||||
character_id: int,
|
||||
text: str,
|
||||
chapter_index: int = 0,
|
||||
segment_index: int = 0,
|
||||
) -> AudiobookSegment:
|
||||
seg = AudiobookSegment(
|
||||
project_id=project_id,
|
||||
character_id=character_id,
|
||||
text=text,
|
||||
chapter_index=chapter_index,
|
||||
segment_index=segment_index,
|
||||
status="pending",
|
||||
)
|
||||
db.add(seg)
|
||||
db.commit()
|
||||
db.refresh(seg)
|
||||
return seg
|
||||
|
||||
|
||||
def list_audiobook_segments(
|
||||
db: Session,
|
||||
project_id: int,
|
||||
chapter_index: Optional[int] = None
|
||||
) -> List[AudiobookSegment]:
|
||||
query = db.query(AudiobookSegment).filter(AudiobookSegment.project_id == project_id)
|
||||
if chapter_index is not None:
|
||||
query = query.filter(AudiobookSegment.chapter_index == chapter_index)
|
||||
return query.order_by(AudiobookSegment.chapter_index, AudiobookSegment.segment_index).all()
|
||||
|
||||
|
||||
def update_audiobook_segment_status(
|
||||
db: Session,
|
||||
segment_id: int,
|
||||
status: str,
|
||||
audio_path: Optional[str] = None
|
||||
) -> Optional[AudiobookSegment]:
|
||||
seg = db.query(AudiobookSegment).filter(AudiobookSegment.id == segment_id).first()
|
||||
if not seg:
|
||||
return None
|
||||
seg.status = status
|
||||
if audio_path is not None:
|
||||
seg.audio_path = audio_path
|
||||
db.commit()
|
||||
db.refresh(seg)
|
||||
return seg
|
||||
|
||||
|
||||
def delete_audiobook_segments(db: Session, project_id: int) -> None:
|
||||
db.query(AudiobookSegment).filter(AudiobookSegment.project_id == project_id).delete()
|
||||
db.commit()
|
||||
|
||||
|
||||
def delete_audiobook_characters(db: Session, project_id: int) -> None:
|
||||
db.query(AudiobookCharacter).filter(AudiobookCharacter.project_id == project_id).delete()
|
||||
db.commit()
|
||||
|
||||
@@ -11,6 +11,20 @@ class JobStatus(str, Enum):
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
class AudiobookStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
ANALYZING = "analyzing"
|
||||
READY = "ready"
|
||||
GENERATING = "generating"
|
||||
DONE = "done"
|
||||
ERROR = "error"
|
||||
|
||||
class SegmentStatus(str, Enum):
|
||||
PENDING = "pending"
|
||||
GENERATING = "generating"
|
||||
DONE = "done"
|
||||
ERROR = "error"
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
@@ -21,6 +35,9 @@ class User(Base):
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
is_superuser = Column(Boolean, default=False, nullable=False)
|
||||
aliyun_api_key = Column(Text, nullable=True)
|
||||
llm_api_key = Column(Text, nullable=True)
|
||||
llm_base_url = Column(String(500), nullable=True)
|
||||
llm_model = Column(String(200), nullable=True)
|
||||
can_use_local_model = Column(Boolean, default=False, nullable=False)
|
||||
user_preferences = Column(JSON, nullable=True, default=lambda: {"default_backend": "aliyun", "onboarding_completed": False})
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
@@ -29,6 +46,7 @@ class User(Base):
|
||||
jobs = relationship("Job", back_populates="user", cascade="all, delete-orphan")
|
||||
voice_caches = relationship("VoiceCache", back_populates="user", cascade="all, delete-orphan")
|
||||
voice_designs = relationship("VoiceDesign", back_populates="user", cascade="all, delete-orphan")
|
||||
audiobook_projects = relationship("AudiobookProject", back_populates="user", cascade="all, delete-orphan")
|
||||
|
||||
class Job(Base):
|
||||
__tablename__ = "jobs"
|
||||
@@ -104,3 +122,58 @@ class VoiceDesign(Base):
|
||||
Index('idx_user_backend', 'user_id', 'backend_type'),
|
||||
Index('idx_user_active', 'user_id', 'is_active'),
|
||||
)
|
||||
|
||||
|
||||
class AudiobookProject(Base):
|
||||
__tablename__ = "audiobook_projects"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
title = Column(String(500), nullable=False)
|
||||
source_type = Column(String(10), nullable=False)
|
||||
source_path = Column(String(500), nullable=True)
|
||||
source_text = Column(Text, nullable=True)
|
||||
status = Column(String(20), default="pending", nullable=False, index=True)
|
||||
llm_model = Column(String(200), nullable=True)
|
||||
error_message = Column(Text, nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
user = relationship("User", back_populates="audiobook_projects")
|
||||
characters = relationship("AudiobookCharacter", back_populates="project", cascade="all, delete-orphan")
|
||||
segments = relationship("AudiobookSegment", back_populates="project", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class AudiobookCharacter(Base):
|
||||
__tablename__ = "audiobook_characters"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
project_id = Column(Integer, ForeignKey("audiobook_projects.id"), nullable=False, index=True)
|
||||
name = Column(String(200), nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
instruct = Column(Text, nullable=True)
|
||||
voice_design_id = Column(Integer, ForeignKey("voice_designs.id"), nullable=True)
|
||||
|
||||
project = relationship("AudiobookProject", back_populates="characters")
|
||||
voice_design = relationship("VoiceDesign")
|
||||
segments = relationship("AudiobookSegment", back_populates="character")
|
||||
|
||||
|
||||
class AudiobookSegment(Base):
|
||||
__tablename__ = "audiobook_segments"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
project_id = Column(Integer, ForeignKey("audiobook_projects.id"), nullable=False, index=True)
|
||||
chapter_index = Column(Integer, nullable=False, default=0)
|
||||
segment_index = Column(Integer, nullable=False)
|
||||
character_id = Column(Integer, ForeignKey("audiobook_characters.id"), nullable=False)
|
||||
text = Column(Text, nullable=False)
|
||||
audio_path = Column(String(500), nullable=True)
|
||||
status = Column(String(20), default="pending", nullable=False)
|
||||
|
||||
project = relationship("AudiobookProject", back_populates="segments")
|
||||
character = relationship("AudiobookCharacter", back_populates="segments")
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_project_chapter', 'project_id', 'chapter_index', 'segment_index'),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user