From d18fd711cd1b4f24ec24efab94e6b6bd888f540d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Thu, 16 Jul 2026 14:56:06 +0800 Subject: [PATCH] 1 --- short_drama.db | Bin 462848 -> 462848 bytes shortdrama/service/drama_service.go | 82 ++++++++++++++++++++++---- shortdrama/service/episode_service.go | 6 +- 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/short_drama.db b/short_drama.db index b9ff6fac19892467d1068568d35bd3b25b6fe41c..da91d6d1d58087078509396e485839d66960dfbd 100644 GIT binary patch delta 28744 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 diff --git a/shortdrama/service/drama_service.go b/shortdrama/service/drama_service.go index 94ba184..9ba13bc 100644 --- a/shortdrama/service/drama_service.go +++ b/shortdrama/service/drama_service.go @@ -392,7 +392,7 @@ func (s *dramaService) generateOneSegment(ctx context.Context, d *entity.Drama, // JSON 镜头脚本跳过 Agent,直接构建场景描述提交视频模型 var segOutput *model.SegmentOutput if domain.IsShotsJSON(ep.Script) && feedback == "" { - segOutput = buildSegOutputFromShots(ep.Script, segIdx, segStartTime, segDur) + segOutput = buildSegOutputFromShots(ep.Script, segIdx, segStartTime, segDur, genCtx) g.Log().Infof(ctx, "第%d集第%d段 JSON镜头直接提交: %d个镜头", ep.Index, segIdx+1, len(segOutput.Scenes)) } else { result, err := s.generateSegment(ctx, d, ep, segIdx, segDur, segStartTime, totalSegs, modelCfg, feedback, genCtx) @@ -556,7 +556,7 @@ func (s *dramaService) generateOneSegment(ctx context.Context, d *entity.Drama, // buildSegOutputFromShots 从 JSON 镜头数组直接构建 SegmentOutput(跳过 Agent) // 将本段所有镜头合并为一段连贯的场景描述,去掉"旁白/台词/事件"标记,适合视频模型理解 -func buildSegOutputFromShots(script string, segIdx, segStartTime, segDur int) *model.SegmentOutput { +func buildSegOutputFromShots(script string, segIdx, segStartTime, segDur int, genCtx *GenerationContext) *model.SegmentOutput { var allShots []domain.Shot if err := json.Unmarshal([]byte(script), &allShots); err != nil || len(allShots) == 0 { return &model.SegmentOutput{Index: segIdx, TextOutput: script} @@ -584,23 +584,80 @@ func buildSegOutputFromShots(script string, segIdx, segStartTime, segDur int) *m chars = append(chars, model.SegmentCharacter{Name: name}) } - // 将所有镜头合并为一段连贯描述(去掉旁白/台词/事件标记,自然语言拼接) + // 从 genCtx 构建角色外观描述(确保每段角色服装/外貌一致) + var appearanceDescs []string + for name := range charSet { + for _, c := range genCtx.Characters { + if c.Name == name && c.Description != "" { + appearanceDescs = append(appearanceDescs, name+":"+c.Description) + break + } + } + } + // 收集本段涉及场景的描述(确保各段画风一致) + var sceneDescs []string + seenScenes := make(map[string]bool) + for _, sh := range segShots { + if sh.Scene == "" || seenScenes[sh.Scene] { + continue + } + seenScenes[sh.Scene] = true + for _, sc := range genCtx.Scenes { + if sc.Name == sh.Scene && sc.Description != "" { + sceneDescs = append(sceneDescs, sc.Name+":"+sc.Description) + break + } + } + } + + // 将所有镜头合并为一段连贯描述,每个镜头包含场景/运镜/道具信息 descParts := make([]string, 0, len(segShots)*2) for _, sh := range segShots { - // 旁白直接作为叙述 + // 镜头头部信息:场景、运镜、道具 + var shotHeaders []string + if sh.Scene != "" { + shotHeaders = append(shotHeaders, "场景:"+sh.Scene) + } + if sh.CameraMovement != "" { + shotHeaders = append(shotHeaders, "运镜:"+sh.CameraMovement) + } + if len(sh.Props) > 0 { + shotHeaders = append(shotHeaders, "道具:"+strings.Join(sh.Props, "、")) + } + shotParts := make([]string, 0, 4) + if len(shotHeaders) > 0 { + shotParts = append(shotParts, "["+strings.Join(shotHeaders, "][")+"]") + } + // 旁白 if sh.Narration != "" { - descParts = append(descParts, sh.Narration) + shotParts = append(shotParts, sh.Narration) } // 事件描述 if sh.Event != "" { - descParts = append(descParts, sh.Event) + shotParts = append(shotParts, sh.Event) } // 对话以自然方式嵌入 if sh.Dialogue != "" { - descParts = append(descParts, sh.Dialogue) + shotParts = append(shotParts, sh.Dialogue) + } + if len(shotParts) > 0 { + descParts = append(descParts, strings.Join(shotParts, "。")) } } - fullDesc := strings.Join(descParts, ",") + // 前面拼接角色外观和场景描述,确保风格一致 + var prefixParts []string + if len(appearanceDescs) > 0 { + prefixParts = append(prefixParts, "【角色形象】"+strings.Join(appearanceDescs, ";")) + } + if len(sceneDescs) > 0 { + prefixParts = append(prefixParts, "【场景】"+strings.Join(sceneDescs, ";")) + } + var fullDesc string + if len(prefixParts) > 0 { + fullDesc = strings.Join(prefixParts, "。") + "。" + strings.Join(descParts, ",") + } else { + fullDesc = strings.Join(descParts, ",") + } return &model.SegmentOutput{ Index: segIdx, @@ -1460,8 +1517,10 @@ func (s *dramaService) submitVideoTask(ctx context.Context, d *entity.Drama, ep } } + // 固定 seed 确保各段画风/人物形象一致(基于短剧ID+剧集序号) + seed := int(d.Id)*1000 + ep.Index*10 + segIdx taskId, requestJSON, err := createVideoTask(ctx, modelCfg.VideoApiKey, modelCfg.VideoBaseUrl, modelCfg.VideoModelName, - prompt, negativePrompt, refURLs, effectiveDur, d.Resolution, d.AspectRatio, modelCfg.VideoSchema) + prompt, negativePrompt, refURLs, effectiveDur, d.Resolution, d.AspectRatio, modelCfg.VideoSchema, seed) if err != nil { return "", "", fmt.Errorf("视频合成请求失败: %w", err) } @@ -1478,7 +1537,7 @@ func (s *dramaService) submitVideoTask(ctx context.Context, d *entity.Drama, ep // // params: 额外参数(如 audio/shot_type/watermark 等) // sizes: 分辨率→尺寸字符串映射表 -func createVideoTask(ctx context.Context, apiKey, baseURL, modelName, prompt, negativePrompt string, refURLs []string, duration int, resolution, aspectRatio, videoSchema string) (string, string, error) { +func createVideoTask(ctx context.Context, apiKey, baseURL, modelName, prompt, negativePrompt string, refURLs []string, duration int, resolution, aspectRatio, videoSchema string, seed int) (string, string, error) { body := map[string]any{ "model": modelName, "input": map[string]any{ @@ -1538,6 +1597,9 @@ func createVideoTask(ctx context.Context, apiKey, baseURL, modelName, prompt, ne if duration > 0 { params["duration"] = duration } + if seed > 0 { + params["seed"] = seed + } body["parameters"] = params payload, _ := json.Marshal(body) diff --git a/shortdrama/service/episode_service.go b/shortdrama/service/episode_service.go index bed0e19..77c76bd 100644 --- a/shortdrama/service/episode_service.go +++ b/shortdrama/service/episode_service.go @@ -469,14 +469,11 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string, totalDur int } var cur _shotGroup - var prevScene string for _, sh := range allShots { shDur := parseMMSSToSeconds(sh.EndTime) - parseMMSSToSeconds(sh.StartTime) if shDur <= 0 { shDur = 1 } - // 场景切换 → 必须分段(不同场景不能合并到同一段) - sceneChanged := len(cur.shots) > 0 && sh.Scene != prevScene // 计算加入当前镜头后的候选组总时长和提示词长度 candShots := make([]domain.Shot, len(cur.shots)+1) copy(candShots, cur.shots) @@ -487,7 +484,7 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string, exceedDur := len(cur.shots) > 0 && candDur > effectiveMax exceedChars := promptMaxChars > 0 && len([]rune(candPrompt)) > promptMaxChars - if exceedDur || exceedChars || sceneChanged { + if exceedDur || exceedChars { // 当前组已满,保存并开启新组 taskGroups = append(taskGroups, _taskGroup{ promptText: buildFinalPrompt(domain.ShotsToText(cur.shots)), @@ -498,7 +495,6 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string, cur.shots = candShots cur.totalDur = candDur } - prevScene = sh.Scene } if len(cur.shots) > 0 { taskGroups = append(taskGroups, _taskGroup{