1
This commit is contained in:
Binary file not shown.
@@ -846,55 +846,101 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string,
|
||||
name string
|
||||
}
|
||||
var namedRefs []_namedRef
|
||||
|
||||
chars, _, _ := dao.Character.ListPageByDrama(ctx, dramaId, 1, -1)
|
||||
for _, ch := range chars {
|
||||
if ch.PortraitPath == "" {
|
||||
continue
|
||||
}
|
||||
namedRefs = append(namedRefs, _namedRef{path: ch.PortraitPath, name: ch.Name})
|
||||
}
|
||||
scenes, _ := dao.Scene.ListByDrama(ctx, dramaId)
|
||||
for _, sc := range scenes {
|
||||
if sc.ImagePath == "" {
|
||||
continue
|
||||
}
|
||||
namedRefs = append(namedRefs, _namedRef{path: sc.ImagePath, name: sc.Name})
|
||||
}
|
||||
props, _ := dao.Prop.ListByDrama(ctx, dramaId)
|
||||
for _, p := range props {
|
||||
if p.ImagePath == "" {
|
||||
continue
|
||||
}
|
||||
namedRefs = append(namedRefs, _namedRef{path: p.ImagePath, name: p.Name})
|
||||
}
|
||||
|
||||
refURLs := make([]string, len(namedRefs))
|
||||
for i, nr := range namedRefs {
|
||||
refURLs[i] = nr.path
|
||||
}
|
||||
// 将绝对路径转为相对路径(以当前工作目录为基准),便于后续 base64 转换
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
for i, u := range refURLs {
|
||||
if rel, e := filepath.Rel(cwd, u); e == nil {
|
||||
refURLs[i] = rel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建名称替换映射(使用模型配置的 reference_template,如 "图%d")
|
||||
type _nameLabel struct{ name, label string }
|
||||
var nameLabels []_nameLabel
|
||||
refLabelTemplate := modelCfg.ReferenceTemplate
|
||||
if refLabelTemplate != "" {
|
||||
for i, nr := range namedRefs {
|
||||
nameLabels = append(nameLabels, _nameLabel{nr.name, fmt.Sprintf(refLabelTemplate, i+1)})
|
||||
var refURLs []string
|
||||
|
||||
// loadEntityRefs 从数据库加载角色/场景/道具记录,构建引用路径。
|
||||
// 对于尚未上传照片的记录,构造预期的 workspace 路径作为占位,确保引用的完整性。
|
||||
// 根据 schema 中的 media.max_items 限制总引用数,角色优先于场景优先于道具。
|
||||
loadEntityRefs := func() []*entity.Character {
|
||||
// 从 schema_mapping 中的 max_media_items 读取 media 数组长度上限
|
||||
maxRefItems := 5
|
||||
if modelCfg.SchemaMapping != "" && strings.HasPrefix(modelCfg.SchemaMapping, "{") {
|
||||
var sm struct {
|
||||
MaxMediaItems string `json:"max_media_items"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(modelCfg.SchemaMapping), &sm); err == nil && sm.MaxMediaItems != "" {
|
||||
// schema_mapping 中的路径相对于 request body(如 input.media.max_items),
|
||||
// schema 定义在 body 节点下,导航时需要补 body. 前缀
|
||||
path := sm.MaxMediaItems
|
||||
if !strings.HasPrefix(path, "body.") {
|
||||
path = "body." + path
|
||||
}
|
||||
if v := nested(vs, strings.Split(path, ".")...); v != nil {
|
||||
if f, ok := v.(float64); ok {
|
||||
maxRefItems = int(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(nameLabels, func(a, b int) bool {
|
||||
return len(nameLabels[a].name) > len(nameLabels[b].name)
|
||||
})
|
||||
|
||||
chars, _, _ := dao.Character.ListPageByDrama(ctx, dramaId, 1, -1)
|
||||
namedRefs = nil
|
||||
for _, ch := range chars {
|
||||
if len(namedRefs) >= maxRefItems {
|
||||
break
|
||||
}
|
||||
path := ch.PortraitPath
|
||||
if path == "" {
|
||||
path = filepath.Join(WorkspaceDir(d.Title), "演员形象", sanitizeDirName(ch.Name)+".jpeg")
|
||||
}
|
||||
namedRefs = append(namedRefs, _namedRef{path: path, name: ch.Name})
|
||||
}
|
||||
if len(namedRefs) < maxRefItems {
|
||||
scenes, _ := dao.Scene.ListByDrama(ctx, dramaId)
|
||||
for _, sc := range scenes {
|
||||
if len(namedRefs) >= maxRefItems {
|
||||
break
|
||||
}
|
||||
path := sc.ImagePath
|
||||
if path == "" {
|
||||
path = filepath.Join(WorkspaceDir(d.Title), "场景", sanitizeDirName(sc.Name)+".jpeg")
|
||||
}
|
||||
namedRefs = append(namedRefs, _namedRef{path: path, name: sc.Name})
|
||||
}
|
||||
}
|
||||
if len(namedRefs) < maxRefItems {
|
||||
props, _ := dao.Prop.ListByDrama(ctx, dramaId)
|
||||
for _, p := range props {
|
||||
if len(namedRefs) >= maxRefItems {
|
||||
break
|
||||
}
|
||||
path := p.ImagePath
|
||||
if path == "" {
|
||||
path = filepath.Join(WorkspaceDir(d.Title), "道具", sanitizeDirName(p.Name)+".jpeg")
|
||||
}
|
||||
namedRefs = append(namedRefs, _namedRef{path: path, name: p.Name})
|
||||
}
|
||||
}
|
||||
|
||||
refURLs = make([]string, len(namedRefs))
|
||||
for i, nr := range namedRefs {
|
||||
refURLs[i] = nr.path
|
||||
}
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
for i, u := range refURLs {
|
||||
if rel, e := filepath.Rel(cwd, u); e == nil {
|
||||
refURLs[i] = rel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nameLabels = nil
|
||||
refLabelTemplate := modelCfg.ReferenceTemplate
|
||||
if refLabelTemplate != "" {
|
||||
for i, nr := range namedRefs {
|
||||
nameLabels = append(nameLabels, _nameLabel{nr.name, fmt.Sprintf(refLabelTemplate, i+1)})
|
||||
}
|
||||
sort.Slice(nameLabels, func(a, b int) bool {
|
||||
return len(nameLabels[a].name) > len(nameLabels[b].name)
|
||||
})
|
||||
}
|
||||
return chars
|
||||
}
|
||||
|
||||
chars := loadEntityRefs()
|
||||
|
||||
// 读取反向提示词
|
||||
negativePrompt := ""
|
||||
if data, err := os.ReadFile(getDataPath("negative_prompt.md")); err == nil {
|
||||
@@ -940,6 +986,11 @@ func createPendingTasks(ctx context.Context, dramaId, epId int64, script string,
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 从镜头脚本提取演员/场景/道具,自动写入数据库(避免重复)============
|
||||
extractAndSaveEntities(ctx, dramaId, allShots)
|
||||
// 重新加载实体引用,确保新增记录也被纳入后续的 media/prompt 构建
|
||||
chars = loadEntityRefs()
|
||||
|
||||
// ============ 直接为各分段创建生成任务(script 按模型配置的 API 请求体格式构建)============
|
||||
segDurs := calcSegDurs(d.EpisodeDuration, modelCfg)
|
||||
// 按镜头数量均分到各分段(避免时间窗口切分导致的空段或信息不全)
|
||||
@@ -1120,6 +1171,151 @@ func buildCharacterGuide(charNames []string, chars []*entity.Character, labelOf
|
||||
return guide
|
||||
}
|
||||
|
||||
// extractAndSaveEntities 从镜头数组中提取演员/场景/道具,去重后写入数据库。
|
||||
// 如果同 drama 下已存在同名记录,跳过插入(避免重复)。
|
||||
// 描述信息从镜头的 event 画面描述中提取,保证是视觉/外观描述。
|
||||
func extractAndSaveEntities(ctx context.Context, dramaId int64, shots []domain.Shot) {
|
||||
if len(shots) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
charSet := make(map[string]bool)
|
||||
sceneSet := make(map[string]bool)
|
||||
propSet := make(map[string]bool)
|
||||
// name → []event 片段
|
||||
charEvents := make(map[string][]string)
|
||||
sceneEvents := make(map[string][]string)
|
||||
propEvents := make(map[string][]string)
|
||||
|
||||
for _, sh := range shots {
|
||||
for _, c := range sh.Characters {
|
||||
if c == "" {
|
||||
continue
|
||||
}
|
||||
if !charSet[c] {
|
||||
charSet[c] = true
|
||||
}
|
||||
if sh.Event != "" {
|
||||
charEvents[c] = append(charEvents[c], sh.Event)
|
||||
}
|
||||
}
|
||||
if sh.Scene != "" {
|
||||
sceneSet[sh.Scene] = true
|
||||
if sh.Event != "" {
|
||||
sceneEvents[sh.Scene] = append(sceneEvents[sh.Scene], sh.Event)
|
||||
}
|
||||
}
|
||||
for _, p := range sh.Props {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if !propSet[p] {
|
||||
propSet[p] = true
|
||||
}
|
||||
if sh.Event != "" {
|
||||
propEvents[p] = append(propEvents[p], sh.Event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 查询已有记录
|
||||
existingChars, _, _ := dao.Character.ListPageByDrama(ctx, dramaId, 1, -1)
|
||||
existingScenes, _ := dao.Scene.ListByDrama(ctx, dramaId)
|
||||
existingProps, _ := dao.Prop.ListByDrama(ctx, dramaId)
|
||||
|
||||
existName := func(list interface{}, name string) bool {
|
||||
switch l := list.(type) {
|
||||
case []*entity.Character:
|
||||
for _, c := range l {
|
||||
if c.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []*entity.Scene:
|
||||
for _, s := range l {
|
||||
if s.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []*entity.Prop:
|
||||
for _, p := range l {
|
||||
if p.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
buildDesc := func(events []string) string {
|
||||
seen := make(map[string]bool)
|
||||
var parts []string
|
||||
for _, e := range events {
|
||||
if e == "" || seen[e] {
|
||||
continue
|
||||
}
|
||||
seen[e] = true
|
||||
parts = append(parts, e)
|
||||
if len(parts) >= 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
result := strings.Join(parts, ";")
|
||||
runes := []rune(result)
|
||||
if len(runes) > 300 {
|
||||
result = string(runes[:300]) + "……"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
for name := range charSet {
|
||||
if existName(existingChars, name) {
|
||||
continue
|
||||
}
|
||||
desc := buildDesc(charEvents[name])
|
||||
_, _ = g.DB().Model(consts.TableNameCharacter).Ctx(ctx).Data(g.Map{
|
||||
"drama_id": dramaId,
|
||||
"name": name,
|
||||
"description": desc,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
}).Insert()
|
||||
}
|
||||
|
||||
for name := range sceneSet {
|
||||
if existName(existingScenes, name) {
|
||||
continue
|
||||
}
|
||||
desc := buildDesc(sceneEvents[name])
|
||||
_, _ = g.DB().Model(consts.TableNameScene).Ctx(ctx).Data(g.Map{
|
||||
"drama_id": dramaId,
|
||||
"name": name,
|
||||
"description": desc,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
}).Insert()
|
||||
}
|
||||
|
||||
for name := range propSet {
|
||||
if existName(existingProps, name) {
|
||||
continue
|
||||
}
|
||||
desc := buildDesc(propEvents[name])
|
||||
_, _ = g.DB().Model(consts.TableNameProp).Ctx(ctx).Data(g.Map{
|
||||
"drama_id": dramaId,
|
||||
"name": name,
|
||||
"description": desc,
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
}).Insert()
|
||||
}
|
||||
}
|
||||
|
||||
// inferCharPerSecond 根据台词情绪推断语速(字/秒)
|
||||
func inferCharPerSecond(text string, normal, fast, slow int) int {
|
||||
runes := []rune(text)
|
||||
|
||||
Reference in New Issue
Block a user