diff --git a/.gitignore b/.gitignore index f4b0fee..03a6690 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /.idea/* .gstack/ +db/ diff --git a/config.yml b/config.yml index b7c5c00..abc2294 100644 --- a/config.yml +++ b/config.yml @@ -1,6 +1,6 @@ database: default: - name: short_drama.db + name: db/short_drama.db type: sqlite debug: true # (可选)开启调试模式 server: diff --git a/docker-compose.yml b/docker-compose.yml index 782b15b..e49626e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,8 @@ services: RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone WORKDIR /app COPY --from=builder /build/config.yml . + COPY --from=builder /build/prompt.md . + COPY --from=builder /build/default_first_frame.png . COPY --from=builder /build/main . EXPOSE 80 CMD ["./main"] @@ -29,5 +31,6 @@ services: ports: - "80:80" volumes: + - /data/video-factory/db:/app/db - /data/video-factory/workspace:/app/workspace restart: unless-stopped diff --git a/shortdrama/dao/character_dao.go b/shortdrama/dao/character_dao.go index f0275be..8d9754c 100644 --- a/shortdrama/dao/character_dao.go +++ b/shortdrama/dao/character_dao.go @@ -13,6 +13,29 @@ var Character = &characterDao{} type characterDao struct{} +func init() { + ctx := context.Background() + if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+public.TableNameCharacter+` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 演员ID + drama_id INTEGER NOT NULL DEFAULT 0, -- 短剧ID + name TEXT NOT NULL DEFAULT '', -- 演员名称 + description TEXT NOT NULL DEFAULT '', -- 演员描述 + voice_path TEXT NOT NULL DEFAULT '', -- 声音文件路径 + portrait_path TEXT NOT NULL DEFAULT '', -- 形象文件路径 + created_at DATETIME, -- 创建时间 + updated_at DATETIME, -- 更新时间 + deleted_at DATETIME -- 删除时间 + )`); err != nil { + g.Log().Warningf(ctx, "创建演员表失败: %v", err) + } + // 迁移:清理旧字段 + for _, col := range []string{"voice_type", "portrait_url", "image_base64"} { + if _, err := g.DB().Exec(ctx, `ALTER TABLE `+public.TableNameCharacter+` DROP COLUMN `+col); err != nil { + g.Log().Warningf(ctx, "删除演员表旧字段 %s 失败(可能已删除): %v", col, err) + } + } +} + func (d *characterDao) Insert(ctx context.Context, data *entity.Character) (id int64, err error) { m := gconv.Map(data, gconv.MapOption{Tags: []string{"orm"}}) delete(m, "id") diff --git a/shortdrama/dao/drama_dao.go b/shortdrama/dao/drama_dao.go index 202539f..4c76232 100644 --- a/shortdrama/dao/drama_dao.go +++ b/shortdrama/dao/drama_dao.go @@ -14,6 +14,28 @@ var Drama = &dramaDao{} type dramaDao struct{} +func init() { + ctx := context.Background() + // 建表(IF NOT EXISTS 对新库生效,已存在的表跳过) + if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+public.TableNameDrama+` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 短剧ID + title TEXT NOT NULL UNIQUE, -- 短剧标题 + style TEXT NOT NULL DEFAULT '', -- 短剧风格 + episode_duration INTEGER NOT NULL DEFAULT 0, -- 单集时长(秒) + episode_count INTEGER NOT NULL DEFAULT 0, -- 剧集数量 + created_at DATETIME, -- 创建时间 + updated_at DATETIME, -- 更新时间 + deleted_at DATETIME -- 删除时间 + )`); err != nil { + g.Log().Warningf(ctx, "创建短剧表失败: %v", err) + } + // 对已存在的旧表补充唯一索引 + if _, err := g.DB().Exec(ctx, + "CREATE UNIQUE INDEX IF NOT EXISTS idx_short_drama_title ON "+public.TableNameDrama+"(title)"); err != nil { + g.Log().Warningf(ctx, "创建短剧标题唯一索引失败: %v", err) + } +} + func (d *dramaDao) IncrementEpCount(ctx context.Context, dramaId int64) error { _, err := g.DB().Model(public.TableNameDrama).Ctx(ctx). Data(g.Map{"episode_count": gdb.Raw("episode_count + 1")}). diff --git a/shortdrama/dao/episode_dao.go b/shortdrama/dao/episode_dao.go index 231651d..0a1ee8f 100644 --- a/shortdrama/dao/episode_dao.go +++ b/shortdrama/dao/episode_dao.go @@ -13,6 +13,34 @@ var Episode = &episodeDao{} type episodeDao struct{} +func init() { + ctx := context.Background() + if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+public.TableNameEpisode+` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 剧集ID + drama_id INTEGER NOT NULL DEFAULT 0, -- 短剧ID + idx INTEGER NOT NULL DEFAULT 0, -- 剧集序号 + title TEXT NOT NULL DEFAULT '', -- 剧集标题 + script TEXT NOT NULL DEFAULT '', -- 剧集脚本 + status TEXT NOT NULL DEFAULT 'pending', -- 生成状态 + video_url TEXT NOT NULL DEFAULT '', -- 视频URL + created_at DATETIME, -- 创建时间 + updated_at DATETIME, -- 更新时间 + deleted_at DATETIME -- 删除时间 + )`); err != nil { + g.Log().Warningf(ctx, "创建剧集表失败: %v", err) + } + // 迁移:删除 generation_mode 列(已由模型配置动态决定) + if _, err := g.DB().Exec(ctx, `ALTER TABLE `+public.TableNameEpisode+` DROP COLUMN generation_mode`); err != nil { + g.Log().Debugf(ctx, "删除 generation_mode 列失败(可能已删除): %v", err) + } + // 迁移:清理旧字段 + for _, col := range []string{"tech_script", "script_path", "tech_script_path", "description_path", "duration"} { + if _, err := g.DB().Exec(ctx, `ALTER TABLE `+public.TableNameEpisode+` DROP COLUMN `+col); err != nil { + g.Log().Warningf(ctx, "删除剧集表旧字段 %s 失败(可能已删除): %v", col, err) + } + } +} + func (d *episodeDao) Insert(ctx context.Context, data *entity.Episode) (id int64, err error) { m := gconv.Map(data, gconv.MapOption{Tags: []string{"orm"}}) delete(m, "id") diff --git a/shortdrama/dao/generation_task_dao.go b/shortdrama/dao/generation_task_dao.go index 9a3d4fe..2bf860f 100644 --- a/shortdrama/dao/generation_task_dao.go +++ b/shortdrama/dao/generation_task_dao.go @@ -13,6 +13,62 @@ var GenerationTask = &generationTaskDao{} type generationTaskDao struct{} +func init() { + ctx := context.Background() + if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+public.TableNameGenerationTask+` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 任务ID + drama_id INTEGER NOT NULL DEFAULT 0, -- 短剧ID + episode_id INTEGER NOT NULL DEFAULT 0, -- 剧集ID + segment_idx INTEGER NOT NULL DEFAULT 0, -- 段索引 + status TEXT NOT NULL DEFAULT 'pending', -- 任务状态 + error_message TEXT NOT NULL DEFAULT '', + created_at DATETIME, + updated_at DATETIME + )`); err != nil { + g.Log().Warningf(ctx, "创建生成任务表失败: %v", err) + } + // 迁移:添加 segment_idx 列(旧表升级) + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameGenerationTask+` ADD COLUMN segment_idx INTEGER NOT NULL DEFAULT 0`); err != nil { + g.Log().Debugf(ctx, "添加 segment_idx 列失败(可能已存在): %v", err) + } + // 迁移:添加 script_path 列 + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameGenerationTask+` ADD COLUMN script_path TEXT NOT NULL DEFAULT ''`); err != nil { + g.Log().Debugf(ctx, "添加 script_path 列失败(可能已存在): %v", err) + } + // 迁移:添加 video_task_id 列 + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameGenerationTask+` ADD COLUMN video_task_id TEXT NOT NULL DEFAULT ''`); err != nil { + g.Log().Debugf(ctx, "添加 video_task_id 列失败(可能已存在): %v", err) + } + // 迁移:添加 video_url 列 + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameGenerationTask+` ADD COLUMN video_url TEXT NOT NULL DEFAULT ''`); err != nil { + g.Log().Debugf(ctx, "添加 video_url 列失败(可能已存在): %v", err) + } + // 迁移:添加 num_segments 列 + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameGenerationTask+` ADD COLUMN num_segments INTEGER NOT NULL DEFAULT 1`); err != nil { + g.Log().Debugf(ctx, "添加 num_segments 列失败(可能已存在): %v", err) + } + // 迁移:删除废弃的 current_step 列 + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameGenerationTask+` DROP COLUMN current_step`); err != nil { + g.Log().Debugf(ctx, "删除 current_step 列失败(可能已不存在): %v", err) + } + // 迁移:删除废弃的 total_steps 列 + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameGenerationTask+` DROP COLUMN total_steps`); err != nil { + g.Log().Debugf(ctx, "删除 total_steps 列失败(可能已不存在): %v", err) + } + // 迁移:删除废弃的 steps_data 列 + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameGenerationTask+` DROP COLUMN steps_data`); err != nil { + g.Log().Debugf(ctx, "删除 steps_data 列失败(可能已不存在): %v", err) + } +} + func (d *generationTaskDao) Insert(ctx context.Context, data *entity.GenerationTask) (id int64, err error) { m := gconv.Map(data, gconv.MapOption{Tags: []string{"orm"}}) delete(m, "id") diff --git a/shortdrama/dao/model_config_dao.go b/shortdrama/dao/model_config_dao.go index 86257c4..1e50f07 100644 --- a/shortdrama/dao/model_config_dao.go +++ b/shortdrama/dao/model_config_dao.go @@ -13,6 +13,35 @@ var ModelConfig = &modelConfigDao{} type modelConfigDao struct{} +func init() { + ctx := context.Background() + if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+public.TableNameModelConfig+` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + chat_api_key TEXT NOT NULL DEFAULT '', + video_api_key TEXT NOT NULL DEFAULT '', + chat_base_url TEXT NOT NULL DEFAULT '', + chat_model_name TEXT NOT NULL DEFAULT '', + max_tokens INTEGER NOT NULL DEFAULT 4096, + temperature REAL NOT NULL DEFAULT 0.8, + video_base_url TEXT NOT NULL DEFAULT '', + video_model_name TEXT NOT NULL DEFAULT '', + video_query_url TEXT NOT NULL DEFAULT '', + max_single_duration INTEGER NOT NULL DEFAULT 15, + min_single_duration INTEGER NOT NULL DEFAULT 5, + video_model_category TEXT NOT NULL DEFAULT 't2v', + video_no_duration_support INTEGER NOT NULL DEFAULT 0, + created_at DATETIME, + updated_at DATETIME + )`); err != nil { + g.Log().Warningf(ctx, "创建模型配置表失败: %v", err) + } + // 迁移:添加 video_no_duration_support 列 + if _, err := g.DB().Exec(ctx, + `ALTER TABLE `+public.TableNameModelConfig+` ADD COLUMN video_no_duration_support INTEGER NOT NULL DEFAULT 0`); err != nil { + g.Log().Debugf(ctx, "添加 video_no_duration_support 列失败(可能已存在): %v", err) + } +} + // GetFirst 获取第一条配置行 func (d *modelConfigDao) GetFirst(ctx context.Context) (res *entity.ModelConfig, err error) { r, err := g.DB().Model(public.TableNameModelConfig).Ctx(ctx).OrderAsc("id").Limit(1).One() diff --git a/shortdrama/dao/prop_dao.go b/shortdrama/dao/prop_dao.go index de605b9..792fc96 100644 --- a/shortdrama/dao/prop_dao.go +++ b/shortdrama/dao/prop_dao.go @@ -13,6 +13,22 @@ var Prop = &propDao{} type propDao struct{} +func init() { + ctx := context.Background() + if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+public.TableNameProp+` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 道具ID + drama_id INTEGER NOT NULL DEFAULT 0, -- 短剧ID + name TEXT NOT NULL DEFAULT '', -- 道具名称 + description TEXT NOT NULL DEFAULT '', -- 道具描述 + image_path TEXT NOT NULL DEFAULT '', -- 道具图片路径 + created_at DATETIME, -- 创建时间 + updated_at DATETIME, -- 更新时间 + deleted_at DATETIME -- 删除时间 + )`); err != nil { + g.Log().Warningf(ctx, "创建道具表失败: %v", err) + } +} + func (d *propDao) Insert(ctx context.Context, data *entity.Prop) (id int64, err error) { m := gconv.Map(data, gconv.MapOption{Tags: []string{"orm"}}) delete(m, "id") diff --git a/shortdrama/dao/scene_dao.go b/shortdrama/dao/scene_dao.go index bacfc5d..9000f3e 100644 --- a/shortdrama/dao/scene_dao.go +++ b/shortdrama/dao/scene_dao.go @@ -13,6 +13,22 @@ var Scene = &sceneDao{} type sceneDao struct{} +func init() { + ctx := context.Background() + if _, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS `+public.TableNameScene+` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- 场景ID + drama_id INTEGER NOT NULL DEFAULT 0, -- 短剧ID + name TEXT NOT NULL DEFAULT '', -- 场景名称 + description TEXT NOT NULL DEFAULT '', -- 场景描述 + image_path TEXT NOT NULL DEFAULT '', -- 场景图片路径 + created_at DATETIME, -- 创建时间 + updated_at DATETIME, -- 更新时间 + deleted_at DATETIME -- 删除时间 + )`); err != nil { + g.Log().Warningf(ctx, "创建场景表失败: %v", err) + } +} + func (d *sceneDao) Insert(ctx context.Context, data *entity.Scene) (id int64, err error) { m := gconv.Map(data, gconv.MapOption{Tags: []string{"orm"}}) delete(m, "id")