fix(promotion-import): 双实例目录配对 demo-1/2;配对含仅封面目录

Made-with: Cursor
This commit is contained in:
whm
2026-03-21 23:03:17 +08:00
parent d04799db5f
commit 77febfacc7
2 changed files with 126 additions and 3 deletions

View File

@@ -2,7 +2,7 @@
`web/promotion/视频发布/` 下映射表中的文件复制到 **`{upload}/sites/{site_id}/promotion/social/`**,并在 **`site_assets`** 集合插入记录(与后台「保留原文件名」上传到 `promotion/social` 一致)。 `web/promotion/视频发布/` 下映射表中的文件复制到 **`{upload}/sites/{site_id}/promotion/social/`**,并在 **`site_assets`** 集合插入记录(与后台「保留原文件名」上传到 `promotion/social` 一致)。
对「操作与计算(一)(二)」等条目会**按顺序尝试多个源文件名**;若仍找不到且子目录内**恰好只有一个** `.mov``.jpg`,会自动选用(解决「有些视频有、有些 404」多为源文件名与映射不一致)。 对「操作与计算(一)(二)」会尝试多组路径名、半角括号、子目录内**最大** `.mov`;若仍无法按「一/二」识别文件夹,会在 `视频发布` 下找出**恰好两个**含「实例」的兄弟目录(排除 AIWord/语音/发票),排序后**第一个 → demo-1、第二个 → demo-2**(文件夹名不含「一」也能配对)。
## 参数 ## 参数

View File

@@ -17,6 +17,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"time" "time"
@@ -142,6 +143,107 @@ func discoverEpisodeDir(videoPublish, episode string) (dirName string, ok bool)
return hits[0], true return hits[0], true
} }
func dirHasMov(dir string) bool {
entries, err := os.ReadDir(dir)
if err != nil {
return false
}
for _, e := range entries {
if e.IsDir() {
continue
}
if strings.EqualFold(filepath.Ext(e.Name()), ".mov") {
return true
}
}
return false
}
func dirHasJpeg(dir string) bool {
entries, err := os.ReadDir(dir)
if err != nil {
return false
}
for _, e := range entries {
if e.IsDir() {
continue
}
ext := strings.ToLower(filepath.Ext(e.Name()))
if ext == ".jpg" || ext == ".jpeg" {
return true
}
}
return false
}
// 列出「操作与计算」类子目录:含「实例」、内有 .mov 或 .jpg避免仅封面无 mov 的(一)被漏掉),排除 AIWord/语音/发票等
func listCalcInstanceDirsForPairing(videoPublish string) []string {
entries, err := os.ReadDir(videoPublish)
if err != nil {
return nil
}
skipSubstr := []string{"AIWord", "语音", "发票", "全自动"}
var out []string
outer:
for _, e := range entries {
if !e.IsDir() {
continue
}
n := e.Name()
if !strings.Contains(n, "实例") {
continue
}
for _, s := range skipSubstr {
if strings.Contains(n, s) {
continue outer
}
}
sub := filepath.Join(videoPublish, n)
if !dirHasMov(sub) && !dirHasJpeg(sub) {
continue
}
out = append(out, n)
}
return out
}
// 将目录排序为 demo-1 在前、demo-2 在后(优先认全角/半角「一」「二」标记)
func orderCalcDirsForDemo12(dirs []string) []string {
if len(dirs) <= 1 {
return dirs
}
type scored struct {
name string
prio int
}
var xs []scored
for _, d := range dirs {
p := 100
switch {
case strings.Contains(d, "(一)") || strings.Contains(d, "(一)"):
p = 1
case strings.Contains(d, "(二)") || strings.Contains(d, "(二)"):
p = 2
case strings.Contains(d, "一") && !strings.Contains(d, "二"):
p = 5
case strings.Contains(d, "二"):
p = 6
}
xs = append(xs, scored{d, p})
}
sort.Slice(xs, func(i, j int) bool {
if xs[i].prio != xs[j].prio {
return xs[i].prio < xs[j].prio
}
return xs[i].name < xs[j].name
})
out := make([]string, len(xs))
for i, x := range xs {
out[i] = x.name
}
return out
}
func pickMediaInDir(videoPublish, dirName string, dstFile string) (absPath, relChosen string, ok bool) { func pickMediaInDir(videoPublish, dirName string, dstFile string) (absPath, relChosen string, ok bool) {
ext := strings.ToLower(filepath.Ext(dstFile)) ext := strings.ToLower(filepath.Ext(dstFile))
if ext != ".mov" && ext != ".jpg" && ext != ".jpeg" { if ext != ".mov" && ext != ".jpg" && ext != ".jpeg" {
@@ -221,7 +323,7 @@ func pickMediaInDir(videoPublish, dirName string, dstFile string) (absPath, relC
return filepath.Join(videoPublish, filepath.FromSlash(rel)), rel, true return filepath.Join(videoPublish, filepath.FromSlash(rel)), rel, true
} }
func resolveSourceFile(videoPublish string, rule importRule) (absPath, relChosen string, ok bool) { func resolveSourceFile(videoPublish string, rule importRule, calcPair []string) (absPath, relChosen string, ok bool) {
for _, rel := range rule.SrcRels { for _, rel := range rule.SrcRels {
p := filepath.Join(videoPublish, filepath.FromSlash(rel)) p := filepath.Join(videoPublish, filepath.FromSlash(rel))
if st, err := os.Stat(p); err == nil && !st.IsDir() { if st, err := os.Stat(p); err == nil && !st.IsDir() {
@@ -252,6 +354,22 @@ func resolveSourceFile(videoPublish string, rule importRule) (absPath, relChosen
return abs, rel, true return abs, rel, true
} }
} }
// 恰好两个「实例」类目录且无法按名称命中时:排序后第 1 个 -> demo-1第 2 个 -> demo-2
if rule.EpisodeScan == "一" && len(calcPair) >= 2 {
if abs, rel, ok := pickMediaInDir(videoPublish, calcPair[0], rule.Dst); ok {
return abs, rel, true
}
}
if rule.EpisodeScan == "一" && len(calcPair) == 1 {
if abs, rel, ok := pickMediaInDir(videoPublish, calcPair[0], rule.Dst); ok {
return abs, rel, true
}
}
if rule.EpisodeScan == "二" && len(calcPair) >= 2 {
if abs, rel, ok := pickMediaInDir(videoPublish, calcPair[1], rule.Dst); ok {
return abs, rel, true
}
}
return "", "", false return "", "", false
} }
@@ -281,6 +399,11 @@ func main() {
} }
videoPublish = filepath.Clean(videoPublish) videoPublish = filepath.Clean(videoPublish)
calcPair := orderCalcDirsForDemo12(listCalcInstanceDirsForPairing(videoPublish))
if *dryRun && len(calcPair) > 0 {
log.Printf("[dry-run] 操作与计算类目录配对顺序(1<-[0], 2<-[1]): %v", calcPair)
}
uploadDir := *uploadRoot uploadDir := *uploadRoot
if uploadDir == "" { if uploadDir == "" {
uploadDir = os.Getenv("UPLOAD_DIR") uploadDir = os.Getenv("UPLOAD_DIR")
@@ -325,7 +448,7 @@ func main() {
ok, skip, fail := 0, 0, 0 ok, skip, fail := 0, 0, 0
for _, m := range mappings { for _, m := range mappings {
from, srcRelUsed, found := resolveSourceFile(videoPublish, m) from, srcRelUsed, found := resolveSourceFile(videoPublish, m, calcPair)
if !found { if !found {
log.Printf("SKIP 源文件不存在(已试备选路径/扫描子目录): dst=%s episode=%s", m.Dst, m.EpisodeScan) log.Printf("SKIP 源文件不存在(已试备选路径/扫描子目录): dst=%s episode=%s", m.Dst, m.EpisodeScan)
skip++ skip++