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

@@ -17,6 +17,7 @@ import (
"log"
"os"
"path/filepath"
"sort"
"strings"
"time"
@@ -142,6 +143,107 @@ func discoverEpisodeDir(videoPublish, episode string) (dirName string, ok bool)
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) {
ext := strings.ToLower(filepath.Ext(dstFile))
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
}
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 {
p := filepath.Join(videoPublish, filepath.FromSlash(rel))
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
}
}
// 恰好两个「实例」类目录且无法按名称命中时:排序后第 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
}
@@ -281,6 +399,11 @@ func main() {
}
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
if uploadDir == "" {
uploadDir = os.Getenv("UPLOAD_DIR")
@@ -325,7 +448,7 @@ func main() {
ok, skip, fail := 0, 0, 0
for _, m := range mappings {
from, srcRelUsed, found := resolveSourceFile(videoPublish, m)
from, srcRelUsed, found := resolveSourceFile(videoPublish, m, calcPair)
if !found {
log.Printf("SKIP 源文件不存在(已试备选路径/扫描子目录): dst=%s episode=%s", m.Dst, m.EpisodeScan)
skip++