Files
MediaOrganizer/src/lib/pipeline/registry.sh
T
Shuery 0df07ad0ae refactor: 迁移至 bashly CLI 与功能域模块结构
- CLI 定义迁移至 src/bashly.yml(bashly 1.4 生成参数解析),入口收敛为 src/root_command.sh
- 源码按功能域重组:config/storage/integrate/media/pipeline,消除扁平散落
- 新增零依赖测试框架 tests/(123 用例)与 CI 流水线(lint + test + build + 产物一致性)
- build.sh 支持版本单一来源注入与 --check 产物校验;新增 lint.sh(bash -n + shellcheck 零容忍)
- 删除旧单文件 media_organizer.sh 与旧扁平模块
2026-08-14 22:41:28 +08:00

208 lines
7.5 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# shellcheck shell=bash
# shellcheck disable=SC2034 # 跨文件共享全局 map(MEDIA_DESTINATION_MAP 等,声明于 main.sh)
# 结构上不可能出现只写一半的不一致。识别池并发运行时,子进程通过 emit 输出
# 协议行(key\tTYPE\tpayload)到 POOL_EMIT_FILE,父进程合并时调用 register_*。
# 协议行类型:
# DEST <subdir|filename> → register_identified
# FALLBACK <subdir|filename> → register_fallback
# PENDING <ai_data> → register_pending(待 AI 搜索纠正)
# ARTIST_PENDING <artists|album> → register_pending_artist(音频多艺术家待 AI 判定)
# SPECIAL_PENDING <show_id|frag|show|names> → 特典映射待学习
# SKIP <reason> → register_skip(skip_type/skip_unidentified)
# REQ_FAILED → register_request_failed
################################################################################
# 登记识别成功条目(可链接)。
register_identified() {
local key="$1" subdir="$2" filename="$3"
MEDIA_DESTINATION_MAP["$key"]="${subdir}|${filename}"
MEDIA_OUTCOME_MAP["$key"]="identified"
}
# 登记回退命名条目(AI 尽力后仍失败;降级成功,可链接)。
register_fallback() {
local key="$1" subdir="$2" filename="$3"
MEDIA_DESTINATION_MAP["$key"]="${subdir}|${filename}"
MEDIA_OUTCOME_MAP["$key"]="fallback"
}
# 登记待 AI 搜索纠正条目(不进入目的映射)。
register_pending() {
local key="$1" ai_data="$2"
PENDING_AI_SEARCH["$key"]="$ai_data"
PENDING_SEARCH_COUNT=$((PENDING_SEARCH_COUNT + 1))
MEDIA_OUTCOME_MAP["$key"]="pending_ai"
}
# 登记待 AI 判定艺术家的音频条目(不进入目的映射)。
register_pending_artist() {
local key="$1" artists_album="$2"
PENDING_AI_ARTIST["$key"]="$artists_album"
PENDING_ARTIST_COUNT=$((PENDING_ARTIST_COUNT + 1))
MEDIA_OUTCOME_MAP["$key"]="pending_ai"
}
# 登记跳过条目(type=skip_type/skip_unidentified;不进入目的映射)。
# skip_unidentified(AI 尽力后仍失败 / 无 AI 密钥)→ 登记失败冷却(下次运行冷却期内跳过,
# 避免 cron 每轮全量重试;冷却过期后自动重试,语义仍可逆)。skip_type(电影特典)无成本,不冷却。
register_skip() {
local key="$1" reason="$2"
MEDIA_OUTCOME_MAP["$key"]="$reason"
if [[ "$reason" == "skip_unidentified" ]]; then
cooldown_set "$key" "$reason"
fi
}
# 登记请求失败条目(重试耗尽仍失败;不进入目的映射)。
# 网络/密钥类失败同样登记冷却:冷却期内跳过,过期后自动重试。
register_request_failed() {
local key="$1"
MEDIA_OUTCOME_MAP["$key"]="request_failed"
cooldown_set "$key" "request_failed"
}
# 登记待 AI 匹配甄别条目(搜索有结果但需甄别;不进入目的映射)。
# 同 key 重复登记(如纠正词重搜再次需甄别)不重复计数。
register_pending_match() {
local key="$1" payload="$2"
if [[ -z "${PENDING_AI_MATCH[$key]:-}" ]]; then
PENDING_MATCH_COUNT=$((PENDING_MATCH_COUNT + 1))
fi
PENDING_AI_MATCH["$key"]="$payload"
MEDIA_OUTCOME_MAP["$key"]="pending_ai"
}
# 目录链:文件父目录相对源根的路径,最多保留靠近文件的 3 层(剧名通常靠近文件)。
# 用于 AI 输入(directory 字段)与 PENDING_AI_SEARCH 存储。
dir_chain() {
local file="$1" rel
rel=$(dirname "$file")
rel="${rel#"$SOURCE_DIR"/}"
# 不在源目录下(或就是源根)→ 用父目录名
if [[ "$rel" == "$(dirname "$file")" ]]; then
rel=$(basename "$rel")
fi
local parts=() out="" p
IFS='/' read -ra parts <<<"$rel"
if ((${#parts[@]} > 3)); then
parts=("${parts[@]:${#parts[@]}-3}")
fi
for p in "${parts[@]}"; do
out="${out:+$out/}$p"
done
echo "$out"
}
# 合并协议行文件(register_* 登记)。返回前记录 MERGE_PROCESSED/FAILED/STREAK 供阈值检查。
# 用于:识别池合并(pool_run)与父进程 identify 调用(命令替换会丢 emit 副作用,需经临时文件回传)。
merge_emit_lines() {
local emit_file="$1"
MERGE_PROCESSED=0
MERGE_FAILED=0
MERGE_STREAK=0
local key type payload
while IFS=$'\t' read -r key type payload; do
[[ -z "$key" ]] && continue
MERGE_PROCESSED=$((MERGE_PROCESSED + 1))
case "$type" in
DEST)
local subdir filename
IFS='|' read -r subdir filename <<<"$payload"
register_identified "$key" "$subdir" "$filename"
;;
FALLBACK)
local subdir filename
IFS='|' read -r subdir filename <<<"$payload"
register_fallback "$key" "$subdir" "$filename"
;;
PENDING)
register_pending "$key" "$payload"
;;
ARTIST_PENDING)
register_pending_artist "$key" "$payload"
;;
SPECIAL_PENDING)
local show_id fragment show_title season0_names
IFS='|' read -r show_id fragment show_title season0_names <<<"$payload"
PENDING_AI_SPECIAL["${show_id}|${fragment}"]="${show_title}|${season0_names}"
PENDING_SPECIAL_COUNT=$((PENDING_SPECIAL_COUNT + 1))
;;
SKIP)
register_skip "$key" "$payload"
;;
REQ_FAILED)
register_request_failed "$key"
MERGE_FAILED=$((MERGE_FAILED + 1))
MERGE_STREAK=$((MERGE_STREAK + 1))
;;
MATCH)
register_pending_match "$key" "$payload"
;;
*)
MERGE_STREAK=0
;;
esac
done < <(cat "$emit_file")
}
# 协议输出:worker 模式写入 POOL_EMIT_FILE;父进程模式(无池)直接登记。
emit() {
local key="${1:-}" type="${2:-}" payload="${3:-}"
if [[ -n "${POOL_EMIT_FILE:-}" ]]; then
printf '%s\t%s\t%s\n' "$key" "$type" "$payload" >>"$POOL_EMIT_FILE"
return 0
fi
case "$type" in
SPECIAL_PENDING)
local show_id fragment show_title season0_names
IFS='|' read -r show_id fragment show_title season0_names <<<"$payload"
PENDING_AI_SPECIAL["${show_id}|${fragment}"]="${show_title}|${season0_names}"
PENDING_SPECIAL_COUNT=$((PENDING_SPECIAL_COUNT + 1))
;;
MATCH)
register_pending_match "$key" "$payload"
;;
esac
}
# 识别池:对文件列表并发执行 process_one_file,结果行经 POOL_EMIT_FILE 收集后由父进程合并。
# 并发控制:满池时等待最老的 job(bash 4.0 兼容,不用 wait -n)。
pool_run() {
local pool_dir
pool_dir=$(mktemp -d)
export POOL_EMIT_FILE="$pool_dir/emit"
: >"$POOL_EMIT_FILE"
local -a pids=()
local f workers="${MEDIA_WORKERS:-4}"
for f in "$@"; do
# worker 协议只走 POOL_EMIT_FILE,stdout 丢弃(防止识别函数 echo 泄漏到终端)
(process_one_file "$f" >/dev/null) &
pids+=("$!")
if ((${#pids[@]} >= workers)); then
wait "${pids[0]}" || true
pids=("${pids[@]:1}")
fi
done
for p in "${pids[@]}"; do
wait "$p" || true
done
# 合并协议行(register_* 登记 + 请求失败统计)
merge_emit_lines "$POOL_EMIT_FILE"
# 请求失败阈值(仅初始识别池启用):连续 ≥5 次或失败率 ≥50% → 终止,防止整库跑空
if [[ "${POOL_THRESHOLD_CHECK:-false}" == "true" ]]; then
local rate=0
((MERGE_PROCESSED > 0)) && rate=$((MERGE_FAILED * 100 / MERGE_PROCESSED))
if ((MERGE_STREAK >= 5 || (MERGE_PROCESSED >= 10 && rate >= 50))); then
_log 错误 "TMDB 请求失败过多(连续 ${MERGE_STREAK} 次 / 失败率 ${rate}%),终止运行——请检查网络或 TMDB 密钥"
exit 1
fi
fi
unset POOL_EMIT_FILE
rm -rf "$pool_dir"
}