from datetime import datetime, timezone from sqlalchemy import JSON, Boolean, Column, DateTime, Float, Integer, String, Text, UniqueConstraint, create_engine from sqlalchemy.orm import declarative_base Base = declarative_base() class UserModel(Base): __tablename__ = "users" id = Column(String(36), primary_key=True) email = Column(String(255), nullable=False, unique=True, index=True) username = Column(String(100), nullable=True, unique=True, index=True) display_name = Column(String(255), nullable=False) password_hash = Column(String(255), nullable=False) email_verified = Column(Boolean, nullable=False, default=False) email_verification_token = Column(String(255), nullable=True) password_reset_token = Column(String(255), nullable=True) password_reset_expires_at = Column(DateTime, nullable=True) last_login_at = Column(DateTime, nullable=True) last_login_ip = Column(String(50), nullable=True) # 订阅相关字段 (移到 User 级别) subscription_plan = Column(String(20), nullable=False, default="free") subscription_status = Column(String(20), nullable=False, default="active") subscription_expires_at = Column(DateTime, nullable=True) # 配额限制 (移到 User 级别) max_projects = Column(Integer, nullable=False, default=3) max_storage_gb = Column(Integer, nullable=False, default=10) used_storage_gb = Column(Integer, nullable=False, default=0) # 管理员标识 is_admin = Column(Boolean, nullable=False, default=False) # 微信登录(小程序端) wechat_openid = Column(String(128), nullable=True, unique=True, index=True) wechat_unionid = Column(String(128), nullable=True, unique=True, index=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class ProjectModel(Base): __tablename__ = "projects" id = Column(String(36), primary_key=True) owner_user_id = Column(String(36), nullable=False, index=True) name = Column(String(100), nullable=False) description = Column(Text, nullable=False, default="") shared_users = Column(JSON, nullable=False, default=list) # 被共享的用户 ID 列表 extra_meta = Column("metadata", JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class AssetLibraryModel(Base): __tablename__ = "asset_libraries" id = Column(String(36), primary_key=True) project_id = Column(String(36), nullable=True, index=True) name = Column(String(255), nullable=False) kind = Column(String(20), nullable=False, index=True) asset_count = Column(Float, nullable=False, default=0) total_size = Column(Float, nullable=False, default=0) extra_meta = Column("metadata", JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class AssetModel(Base): __tablename__ = "assets" id = Column(String(36), primary_key=True) project_id = Column(String(36), nullable=False, index=True) asset_library_id = Column(String(36), nullable=False, index=True) name = Column(String(500), nullable=False) file_type = Column(String(20), nullable=False, index=True) # file_size: 文件大小(字节),使用 Integer 类型以确保精确性 file_size = Column(Integer, nullable=False) # file_url: 完整可访问的 URL,用于客户端直接访问文件 file_url = Column(String(1000), nullable=False) thumbnail_url = Column(String(1000), nullable=True) duration = Column(Float, nullable=True) width = Column(Float, nullable=True) height = Column(Float, nullable=True) fps = Column(Float, nullable=True) codec = Column(String(50), nullable=True) status = Column(String(20), nullable=False, default="uploading", index=True) classification_status = Column(String(20), nullable=False, default="pending", index=True) classification_result = Column(Text, nullable=True) quality_score = Column(Float, nullable=True) uploaded_by_user_id = Column(String(36), nullable=False) file_hash = Column(String(64), nullable=True, index=True) extra_meta = Column("metadata", JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc), index=True) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class TagModel(Base): """标签 ORM 模型。""" __tablename__ = "tags" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) name = Column(String(100), nullable=False) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) __table_args__ = (UniqueConstraint("user_id", "name", name="uq_tags_user_name"),) class AssetTagModel(Base): """素材-标签关联表 ORM 模型。""" __tablename__ = "asset_tags" asset_id = Column(String(36), primary_key=True) tag_id = Column(String(36), primary_key=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class EditTemplateModel(Base): """Phase 8 剪辑模板 ORM 模型 全局模板库中的模板,定义剪辑风格、配置参数和预览信息。 """ __tablename__ = "edit_templates" id = Column(String(36), primary_key=True) name = Column(String(120), nullable=False) description = Column(Text, nullable=False, default="") template_type = Column(String(50), nullable=False, default="default", index=True) editing_mode = Column(String(20), nullable=False, default="one_take") config = Column(JSON, nullable=False, default=dict) preview_url = Column(String(1000), nullable=False, default="") sort_weight = Column(Integer, nullable=False, default=0, index=True) status = Column(String(20), nullable=False, default="active", index=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class EditPlanModel(Base): """Phase 8 剪辑计划 ORM 模型 基于某个 EditTemplate 创建的剪辑计划,包含具体的配置和状态追踪。 """ __tablename__ = "edit_plans" id = Column(String(36), primary_key=True) template_id = Column(String(36), nullable=False, index=True) name = Column(String(200), nullable=False) status = Column(String(20), nullable=False, default="draft", index=True) total_duration = Column(Float, nullable=False, default=0.0) config = Column(JSON, nullable=False, default=dict) source_edit_plan_id = Column(String(36), nullable=True, index=True) project_id = Column(String(36), nullable=False, default="", index=True) created_by_user_id = Column(String(36), nullable=False, default="", index=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class TemplateClipConfigModel(Base): """Phase 8 模板片段配置 ORM 模型 定义模板中每个片段的规则:类型、时长范围、文案模板、素材要求、转场效果。 """ __tablename__ = "template_clip_configs" id = Column(String(36), primary_key=True) template_id = Column(String(36), nullable=False, index=True) clip_type = Column(String(20), nullable=False, index=True) order = Column(Integer, nullable=False) min_duration = Column(Float, nullable=False, default=0.0) max_duration = Column(Float, nullable=False, default=0.0) text_template = Column(Text, nullable=False, default="") material_requirements = Column(JSON, nullable=False, default=dict) transition_effect = Column(String(20), nullable=False, default="cut") config = Column(JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class EditPlanClipModel(Base): """Phase 8 剪辑计划片段 ORM 模型 剪辑计划中的具体片段实例,关联 EditPlan 和可选的 TemplateClipConfig。 """ __tablename__ = "edit_plan_clips" id = Column(String(36), primary_key=True) plan_id = Column(String(36), nullable=False, index=True) clip_type = Column(String(20), nullable=False, index=True) order = Column(Integer, nullable=False) template_clip_config_id = Column(String(36), nullable=False, default="", index=True) asset_id = Column(String(36), nullable=False, default="", index=True) text_content = Column(Text, nullable=False, default="") start_time = Column(Float, nullable=False, default=0.0) duration = Column(Float, nullable=False, default=0.0) transition_effect = Column(String(20), nullable=False, default="cut") status = Column(String(20), nullable=False, default="pending", index=True) config = Column(JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class IngestJobModel(Base): __tablename__ = "ingest_jobs" id = Column(String(36), primary_key=True) project_id = Column(String(36), nullable=False, index=True) library_id = Column(String(36), nullable=False, index=True) storage_key = Column(String(255), nullable=False) status = Column(String(20), nullable=False, default="pending") error_message = Column(Text, nullable=False, default="") result_asset_id = Column(String(36), nullable=False, default="") file_hash = Column(String(64), nullable=True, index=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class ClassificationJobModel(Base): __tablename__ = "classification_jobs" id = Column(String(36), primary_key=True) project_id = Column(String(36), nullable=False, index=True) asset_id = Column(String(36), nullable=False, index=True) status = Column(String(20), nullable=False, default="pending") classification = Column(String(50), nullable=False, default="") confidence = Column(Float, nullable=False, default=0.0) error_message = Column(Text, nullable=False, default="") created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class GenerationTaskModel(Base): __tablename__ = "generation_tasks" id = Column(String(36), primary_key=True) project_id = Column(String(36), nullable=False, default="", index=True) strategy_id = Column(String(36), nullable=False, default="") asset_library_id = Column(String(36), nullable=False, default="", index=True) voice_library_id = Column(String(36), nullable=False, default="") template_id = Column(String(36), nullable=False, default="", index=True) asset_ids = Column(JSON, nullable=False, default=list) title_ids = Column(JSON, nullable=False, default=list) voice_ids = Column(JSON, nullable=False, default=list) editing_mode = Column( String(20), nullable=False, default="one_take", index=True ) # 剪辑模式: one_take, pip, voice_over, voice_pip status = Column(String(20), nullable=False, default="pending", index=True) progress = Column(Float, nullable=False, default=0.0) result_count = Column(Float, nullable=False, default=0) error_message = Column(Text, nullable=False, default="") started_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) created_by_user_id = Column(String(36), nullable=False, default="", index=True) source_edit_plan_id = Column(String(36), nullable=True, index=True) asset_select_mode = Column(String(20), nullable=False, default="") batch_id = Column(String(36), nullable=False, default="", index=True) extra_meta = Column("metadata", JSON, nullable=False, default=dict) logs = Column(Text, nullable=False, default="[]", server_default="[]") created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class GeneratedVideoModel(Base): __tablename__ = "generated_videos" id = Column(String(36), primary_key=True) project_id = Column(String(36), nullable=False, index=True) generation_task_id = Column(String(36), nullable=False, index=True) name = Column(String(255), nullable=False) # file_url: 完整可访问的 URL,用于客户端直接访问视频 file_url = Column(String(1000), nullable=False) # file_size: 文件大小(字节),使用 Integer 类型以确保精确性 file_size = Column(Integer, nullable=False) duration = Column(Float, nullable=False) thumbnail_url = Column(String(1000), nullable=True) width = Column(Float, nullable=False) height = Column(Float, nullable=False) fps = Column(Float, nullable=False) status = Column(String(20), nullable=False, default="completed", index=True) review_status = Column(String(20), nullable=False, default="pending_review", index=True) generation_params = Column(Text, nullable=False, default="{}") generated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=True) video_fingerprint = Column(Text, nullable=True) is_duplicate = Column(Boolean, nullable=False, default=False) duplicate_of = Column(String(36), nullable=True) class TitleLibraryModel(Base): __tablename__ = "title_libraries" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) name = Column(String(255), nullable=False) description = Column(Text, nullable=False, default="") category = Column(String(50), nullable=False, default="default", index=True) text = Column(String(500), nullable=False) tags = Column(JSON, nullable=False, default=list) usage_count = Column(Integer, nullable=False, default=0) is_active = Column(Boolean, nullable=False, default=True, index=True) extra_meta = Column("metadata", JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class VoiceCloneProfileModel(Base): """音色克隆档案 ORM 模型。""" __tablename__ = "voice_clone_profiles" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) name = Column(String(100), nullable=False) description = Column(Text, nullable=False, default="") source_audio_url = Column(Text, nullable=False, default="") voice_id = Column(String(100), nullable=False, default="") voice_model = Column(String(100), nullable=False, default="") language = Column(String(20), nullable=False, default="zh-CN") gender = Column(String(20), nullable=False, default="unknown") status = Column(String(20), nullable=False, default="pending", index=True) error_message = Column(Text, nullable=False, default="") retry_count = Column(Integer, nullable=False, default=0) max_retries = Column(Integer, nullable=False, default=3) metadata_ = Column("metadata", JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class VoiceLibraryModel(Base): __tablename__ = "voice_libraries" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) project_id = Column(String(36), nullable=True, index=True) name = Column(String(255), nullable=False) text = Column(Text, nullable=False, default="") voice_provider = Column(String(50), nullable=False, default="") voice_id = Column(String(100), nullable=False, default="") voice_name = Column(String(100), nullable=False, default="") audio_url = Column(String(1000), nullable=False, default="") duration = Column(Float, nullable=False, default=0) file_size = Column(Integer, nullable=False, default=0) status = Column(String(20), nullable=False, default="completed", index=True) tags = Column(JSON, nullable=False, default=list) extra_meta = Column("metadata", JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class DuplicationRecordModel(Base): __tablename__ = "duplication_records" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) filename = Column(String(500), nullable=False) file_size = Column(Integer, nullable=False) storage_key = Column(String(500), nullable=False) duration_seconds = Column(Float, nullable=False, default=0) status = Column(String(20), nullable=False, default="pending", index=True) duplicate_rate = Column(Float, nullable=True) duplicate_count = Column(Integer, nullable=False, default=0) video_fingerprint = Column(Text, nullable=True) error_message = Column(Text, nullable=False, default="") created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class DuplicationSegmentModel(Base): __tablename__ = "duplication_segments" id = Column(String(36), primary_key=True) record_id = Column(String(36), nullable=False, index=True) source_start = Column(Float, nullable=False) source_end = Column(Float, nullable=False) matched_video_id = Column(String(36), nullable=False) matched_video_name = Column(String(500), nullable=False, default="") matched_start = Column(Float, nullable=False) matched_end = Column(Float, nullable=False) similarity = Column(Float, nullable=False) class RecipeModel(Base): __tablename__ = "recipes" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) name = Column(String(200), nullable=False) description = Column(Text, nullable=False, default="") template_id = Column(String(36), nullable=False, default="") generation_params = Column(JSON, nullable=False, default=dict) is_active = Column(Boolean, nullable=False, default=True) extra_meta = Column("metadata", JSON, nullable=False, default=dict) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class RecipeItemModel(Base): __tablename__ = "recipe_items" id = Column(String(36), primary_key=True) recipe_id = Column(String(36), nullable=False, index=True) item_type = Column(String(20), nullable=False) item_id = Column(String(36), nullable=False) position = Column(Integer, nullable=False, default=0) extra_meta = Column("metadata", JSON, nullable=False, default=dict) class TemplateModel(Base): __tablename__ = "templates" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) name = Column(String(200), nullable=False) mode = Column(String(30), nullable=False, index=True) # EditingMode 枚举值: pip / voice_pip / one_take / voice_over category = Column(String(100), nullable=False, default="") tags = Column(JSON, nullable=False, default=list) title_config = Column(JSON, nullable=False, default=dict) subtitle_config = Column(JSON, nullable=False, default=dict) bgm_config = Column(JSON, nullable=False, default=dict) estimated_duration = Column(Float, nullable=False, default=0.0) is_active = Column(Boolean, nullable=False, default=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class TemplateSegmentModel(Base): __tablename__ = "template_segments" id = Column(String(36), primary_key=True) template_id = Column(String(36), nullable=False, index=True) segment_order = Column(Integer, nullable=False) duration_min = Column(Float, nullable=False) duration_max = Column(Float, nullable=False) material_type = Column(String(20), nullable=True) # 仅 voice_over 模式: 人物/场景 created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class TemplateCategoryModel(Base): __tablename__ = "template_categories" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) name = Column(String(100), nullable=False) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class JobModel(Base): """Phase 8 任务 2.10 — 统一异步任务 ORM 模型。""" __tablename__ = "jobs" id = Column(String(36), primary_key=True) project_id = Column(String(36), nullable=False, index=True) job_type = Column(String(30), nullable=False, index=True) status = Column(String(20), nullable=False, default="pending", index=True) progress = Column(Float, nullable=False, default=0.0) current_stage = Column(String(200), nullable=False, default="") payload = Column(JSON, nullable=False, default=dict) result = Column(JSON, nullable=False, default=dict) error_message = Column(Text, nullable=False, default="") retry_count = Column(Integer, nullable=False, default=0) max_retries = Column(Integer, nullable=False, default=3) celery_task_id = Column(String(100), nullable=False, default="") source_id = Column(String(36), nullable=False, default="", index=True) created_by_user_id = Column(String(36), nullable=False, default="", index=True) started_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class TTSJobModel(Base): """TTS 合成任务 ORM 模型。""" __tablename__ = "tts_jobs" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) input_text = Column(Text, nullable=False) voice_id = Column(String(100), nullable=False, default="") voice_model = Column(String(100), nullable=False, default="") project_id = Column(String(36), nullable=False, default="") voice_clone_profile_id = Column(String(36), nullable=False, default="") status = Column(String(20), nullable=False, default="pending", index=True) output_audio_url = Column(Text, nullable=False, default="") output_audio_key = Column(String(500), nullable=False, default="") duration = Column(Float, nullable=False, default=0.0) file_size = Column(Integer, nullable=False, default=0) sample_rate = Column(Integer, nullable=False, default=22050) format = Column(String(20), nullable=False, default="mp3") error_message = Column(Text, nullable=False, default="") retry_count = Column(Integer, nullable=False, default=0) max_retries = Column(Integer, nullable=False, default=3) metadata_ = Column("metadata", JSON, nullable=False, default=dict) started_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) class BillingRecordModel(Base): """账单记录""" __tablename__ = "billing_records" id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) plan_name = Column(String(50), nullable=False) amount = Column(Float, nullable=False) billing_cycle = Column(String(20), nullable=False) status = Column(String(20), nullable=False, default="pending") # pending, paid, failed, refunded payment_method = Column(String(50), nullable=True) payment_id = Column(String(100), nullable=True) # 第三方支付流水号 invoice_url = Column(String(500), nullable=True) created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc)) paid_at = Column(DateTime, nullable=True)