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