1
This commit is contained in:
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user