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 与旧扁平模块
This commit is contained in:
1 parent
d978a13a83
commit
0df07ad0ae
48 files changed
+5813
-6812
No files matched your search
@@ -0,0 +1,53 @@
|
||||
# ============================================================================
|
||||
# 特典命名模块测试(src/lib/ai_resolve.sh)
|
||||
# ============================================================================
|
||||
|
||||
setup() {
|
||||
FOLDER_MOVIES="/media/Movies"
|
||||
FOLDER_SHOWS="/media/Shows"
|
||||
FOLDER_UNKNOWN="/media/Unknown"
|
||||
}
|
||||
|
||||
# special_category_tag:特典类别识别
|
||||
test_special_category_tag_mapping() {
|
||||
assert_eq "Menu" "$(special_category_tag "Menu01")"
|
||||
assert_eq "NCED" "$(special_category_tag "NCED02")"
|
||||
assert_eq "NCOP" "$(special_category_tag "NCOP")"
|
||||
assert_eq "CM" "$(special_category_tag "CM01")"
|
||||
assert_eq "CM" "$(special_category_tag "TVSP")"
|
||||
assert_eq "PV" "$(special_category_tag "Announcement")"
|
||||
assert_eq "ED" "$(special_category_tag "Lyric")"
|
||||
assert_eq "Picture Drama" "$(special_category_tag "Drama")"
|
||||
assert_eq "Music Video" "$(special_category_tag "MV")"
|
||||
assert_eq "Special" "$(special_category_tag "OVA")"
|
||||
assert_eq "Special" "$(special_category_tag "未知词")"
|
||||
}
|
||||
|
||||
# special_s00_dest:S00 目标路径(show_dir 含年份段,由 NAMING_SHOW 渲染)
|
||||
test_special_s00_dest_path() {
|
||||
# 参数:show_dir + fragment + ext
|
||||
assert_eq "/media/Shows/Show (2020)/Season 00|S00Menu - Menu01.mkv" \
|
||||
"$(special_s00_dest "Show (2020)" "Menu01" "mkv")"
|
||||
assert_eq "/media/Shows/Show/Season 00|S00Special - SP.mkv" \
|
||||
"$(special_s00_dest "Show" "SP" "mkv")"
|
||||
}
|
||||
|
||||
# fallback_naming:电影回退命名
|
||||
test_fallback_naming_movie() {
|
||||
assert_eq "/media/Movies/Movie (2020)|Movie (2020).mkv" \
|
||||
"$(fallback_naming "movie" "Movie" "2020" "" "" "" "" "mkv")"
|
||||
}
|
||||
|
||||
# fallback_naming:剧集回退命名
|
||||
test_fallback_naming_tv() {
|
||||
assert_eq "/media/Shows/Show/Season 01|S01E02.mkv" \
|
||||
"$(fallback_naming "tv" "Show" "" "1" "2" "" "" "mkv")"
|
||||
assert_eq "/media/Shows/Show (2021)/Season 02|S02E03-E04.mkv" \
|
||||
"$(fallback_naming "tv" "Show" "2021" "2" "3" "4" "" "mkv")"
|
||||
}
|
||||
|
||||
# fallback_naming:特典回退命名(走 special_s00_dest)
|
||||
test_fallback_naming_special() {
|
||||
assert_eq "/media/Shows/Show/Season 00|S00CM - CM01.mkv" \
|
||||
"$(fallback_naming "tv" "Show" "" "0" "1" "" "CM01" "mkv")"
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
# ============================================================================
|
||||
# 媒体文件名解析模块测试(src/lib/filename.sh)
|
||||
# ============================================================================
|
||||
|
||||
setup() {
|
||||
TEST_DIR="$(mktemp -d)"
|
||||
SOURCE_DIR="$TEST_DIR"
|
||||
SKIP_DIRS_LOWER=(sp sps special specials cd cds bonus bonuses extra extras previews "ps3 special bd" scans fonts 特典 特番 花絮 视频 剧集 动画 合集 电影 音乐 movies series shows anime collections)
|
||||
SPECIAL_WORDS=("menu" "ncop" "nced" "mini anime" "pv" "cm" "sp" "teaser" "program" "promo" "trailer" "special" "music video" "mv" "opening" "ending" "特典" "特番" "花絮")
|
||||
VIDEO_EXTS=(mp4 mkv avi mov wmv flv m4v ts m2ts webm)
|
||||
MAIN_COUNT_CACHE=()
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf "$TEST_DIR"
|
||||
}
|
||||
|
||||
# parse_media_filename:标准 S##E## 格式
|
||||
test_parse_tv_sxxexx() {
|
||||
assert_eq "tv|Show Name||01|02||" "$(parse_media_filename "$TEST_DIR/Show Name S01E02.mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:S##E## + 年份
|
||||
test_parse_tv_with_year() {
|
||||
assert_eq "tv|Show|2020|01|01||" "$(parse_media_filename "$TEST_DIR/Show (2020) S01E01.mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:多集区间 S01E01-E02
|
||||
test_parse_tv_episode_range() {
|
||||
assert_eq "tv|Show||01|01|02|" "$(parse_media_filename "$TEST_DIR/Show S01E01-E02.mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:#x## 格式
|
||||
test_parse_tv_hash_format() {
|
||||
assert_eq "tv|Show||1|05||" "$(parse_media_filename "$TEST_DIR/Show 1x05.mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:Season 2 格式(无集号 → 0)
|
||||
test_parse_tv_season_format() {
|
||||
assert_eq "tv|Show||2|0|" "$(parse_media_filename "$TEST_DIR/Show Season 2.mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:2nd Season + [NN]
|
||||
test_parse_tv_ordinal_season() {
|
||||
assert_eq "tv|Show||2|01|" "$(parse_media_filename "$TEST_DIR/Show 2nd Season [01].mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:方括号集号 [NN]
|
||||
test_parse_tv_bracket_episode() {
|
||||
assert_eq "tv|Show||1|07||" "$(parse_media_filename "$TEST_DIR/Show [07].mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:小数集号 → 特典路径(season=0)
|
||||
test_parse_tv_decimal_episode_as_special() {
|
||||
assert_eq "tv|Show||0|||14.5" "$(parse_media_filename "$TEST_DIR/Show [14.5].mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:第 0 话 → 特典路径
|
||||
test_parse_tv_zero_episode_as_special() {
|
||||
assert_eq "tv|Show||0|||0" "$(parse_media_filename "$TEST_DIR/Show [0].mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:电影(年份)
|
||||
test_parse_movie_with_year() {
|
||||
assert_eq "movie|Movie|2020||||" "$(parse_media_filename "$TEST_DIR/Movie (2020).mkv")"
|
||||
}
|
||||
|
||||
# parse_media_filename:压制组风格电影
|
||||
test_parse_movie_group_style() {
|
||||
assert_eq "movie|Movie|2021||||" "$(parse_media_filename "$TEST_DIR/[Group] Movie (2021) [1080p].mkv")"
|
||||
}
|
||||
|
||||
# detect_special:父目录为特典目录(SPs)
|
||||
test_detect_special_parent_dir() {
|
||||
mkdir -p "$TEST_DIR/Show/SPs"
|
||||
touch "$TEST_DIR/Show/SPs/CM01.mkv"
|
||||
assert_eq "tv|Show||0|01||CM01" "$(parse_media_filename "$TEST_DIR/Show/SPs/CM01.mkv")"
|
||||
}
|
||||
|
||||
# detect_special:文件名含特典标记 [CM01]
|
||||
test_detect_special_bracket_mark() {
|
||||
# Show 目录需 ≥2 个正片(count==1 会被判为电影特典跳过)
|
||||
mkdir -p "$TEST_DIR/Show"
|
||||
touch "$TEST_DIR/Show/[CM01].mkv" "$TEST_DIR/Show/EP01.mkv"
|
||||
assert_eq "tv|Show||0|01||CM01" "$(parse_media_filename "$TEST_DIR/Show/[CM01].mkv")"
|
||||
}
|
||||
|
||||
# find_show_dir_from_path:跳过特典目录找到剧名目录
|
||||
test_find_show_dir_skips_special_dirs() {
|
||||
assert_eq "Show" "$(find_show_dir_from_path "$TEST_DIR/Show/SPs/CM01.mkv")"
|
||||
}
|
||||
|
||||
# find_show_dir_from_path:跳过 [数字 CD 子目录
|
||||
test_find_show_dir_skips_cd_dir() {
|
||||
assert_eq "Show" "$(find_show_dir_from_path "$TEST_DIR/Show/[200226] Song/01.flac")"
|
||||
}
|
||||
|
||||
# count_main_videos:统计正片、跳过特典子目录
|
||||
test_count_main_videos_counts_and_skips() {
|
||||
mkdir -p "$TEST_DIR/Show" "$TEST_DIR/Show/SPs"
|
||||
touch "$TEST_DIR/Show/EP01.mkv" "$TEST_DIR/Show/EP02.mkv" "$TEST_DIR/Show/SPs/CM01.mkv"
|
||||
assert_eq "2" "$(count_main_videos "$TEST_DIR/Show")"
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
# ============================================================================
|
||||
# 识别搜索链测试(src/lib/media/identify.sh)
|
||||
#
|
||||
# 覆盖 R1 搜索链抽象后的行为保持:统一尝试器(tv_search_once/movie_search_once)
|
||||
# 的命中/无结果/请求失败返回码、两行协议回传、识别函数的层级短路语义
|
||||
# (主搜索失败即 return 2,后续层失败忽略)、目录/MAL/别名/后缀剥离兜底。
|
||||
#
|
||||
# mock 策略:按 "endpoint|语言" 查 MOCK_RESPONSES 表;无表项 = 空哨兵(无结果);
|
||||
# MOCK_FAIL_ENDPOINTS 匹配 → 请求失败(rc=2)。tmdb_api_lang 经动态作用域
|
||||
# 透传 TMDB_LANG,mock 用它区分 zh/en 响应。
|
||||
# ============================================================================
|
||||
|
||||
# mock 响应表(文件级全局:declare 在函数内是函数局部,mock 函数访问不到)
|
||||
declare -A MOCK_RESPONSES=()
|
||||
declare -A MOCK_FAIL_ENDPOINTS=()
|
||||
|
||||
setup() {
|
||||
TEST_DIR="$(mktemp -d)"
|
||||
DRY_RUN=false
|
||||
TMDB_DELAY=0
|
||||
TMDB_LANG="zh-CN"
|
||||
SOURCE_DIR="$TEST_DIR/src"
|
||||
FOLDER_SHOWS="节目"
|
||||
FOLDER_MOVIES="电影"
|
||||
mkdir -p "$SOURCE_DIR"
|
||||
MOCK_RESPONSES=()
|
||||
MOCK_FAIL_ENDPOINTS=()
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf "$TEST_DIR"
|
||||
}
|
||||
|
||||
# mock:按 "endpoint|lang" 查表;无表项 → 无结果(rc=1);失败表命中 → rc=2
|
||||
tmdb_api() {
|
||||
local endpoint="$1"
|
||||
shift
|
||||
local key="${endpoint}|${TMDB_LANG:-}"
|
||||
if [[ -n "${MOCK_FAIL_ENDPOINTS[$endpoint]:-}" ]]; then
|
||||
return 2
|
||||
fi
|
||||
local resp="${MOCK_RESPONSES[$key]:-}"
|
||||
[[ -z "$resp" ]] && return 1
|
||||
echo "$resp"
|
||||
}
|
||||
|
||||
# mock:用户规则别名查找(真实函数读规则文件,测试桩直接返回)
|
||||
match_rule_lookup_alias() {
|
||||
echo ""
|
||||
}
|
||||
|
||||
# --- 统一尝试器 ---
|
||||
|
||||
# tv_search_once:zh 命中 → 输出 id,返回 0
|
||||
test_tv_search_once_hit() {
|
||||
MOCK_RESPONSES["/search/tv|zh-CN"]='{"results":[{"id":100,"name":"Show A"}]}'
|
||||
local id="" rc=0
|
||||
id=$(tv_search_once "Show A" "1" "" "") || rc=$?
|
||||
assert_eq "0" "$rc" "tv_search_once 命中返回码"
|
||||
assert_eq "100" "$id" "tv_search_once 命中 id"
|
||||
}
|
||||
|
||||
# tv_search_once:无结果 → 返回 1,无输出
|
||||
test_tv_search_once_miss() {
|
||||
local id="" rc=0
|
||||
id=$(tv_search_once "No Such Show" "1" "" "") || rc=$?
|
||||
assert_eq "1" "$rc" "tv_search_once 无结果返回码"
|
||||
assert_empty "$id" "tv_search_once 无结果输出"
|
||||
}
|
||||
|
||||
# tv_search_once:请求失败 → 返回 2(不重试语义由调用方处理)
|
||||
test_tv_search_once_request_failed() {
|
||||
MOCK_FAIL_ENDPOINTS["/search/tv"]=1
|
||||
local rc=0
|
||||
tv_search_once "Show A" "1" "" "" >/dev/null || rc=$?
|
||||
assert_eq "2" "$rc" "tv_search_once 请求失败返回码"
|
||||
}
|
||||
|
||||
# tv_search_once:zh 空 → en-US 命中(语言分支走 tmdb_api_lang)
|
||||
test_tv_search_once_en_fallback() {
|
||||
MOCK_RESPONSES["/search/tv|en-US"]='{"results":[{"id":200,"name":"Show A"}]}'
|
||||
local id="" rc=0
|
||||
id=$(tv_search_once "Show A" "1" "" "en-US") || rc=$?
|
||||
assert_eq "0" "$rc" "en 命中返回码"
|
||||
assert_eq "200" "$id" "en 命中 id"
|
||||
}
|
||||
|
||||
# movie_search_once:两行协议(id + 单行响应 JSON)
|
||||
test_movie_search_once_two_line_protocol() {
|
||||
MOCK_RESPONSES["/search/movie|zh-CN"]='{"results":[{"id":50,"title":"Movie X","release_date":"2020-01-01"}]}'
|
||||
local out="" rc=0
|
||||
out=$(movie_search_once "Movie X" "2020" "") || rc=$?
|
||||
assert_eq "0" "$rc" "movie_search_once 命中返回码"
|
||||
local id resp
|
||||
id=$(printf '%s\n' "$out" | sed -n '1p')
|
||||
resp=$(printf '%s\n' "$out" | sed -n '2p')
|
||||
assert_eq "50" "$id" "两行协议 id 行"
|
||||
assert_contains "$resp" '"title":"Movie X"' "两行协议响应行"
|
||||
}
|
||||
|
||||
# --- 目录兜底 ---
|
||||
|
||||
# tv_search_by_dir:源根直属散放不兜底(无输出)
|
||||
test_tv_search_by_dir_source_root_skipped() {
|
||||
local id
|
||||
id=$(tv_search_by_dir "$SOURCE_DIR/Show.mkv" "Show" "1" "")
|
||||
assert_empty "$id" "源根散放不兜底"
|
||||
}
|
||||
|
||||
# tv_search_by_dir:目录名命中(zh → en)
|
||||
test_tv_search_by_dir_hit() {
|
||||
mkdir -p "$TEST_DIR/src/[Group] Show X"
|
||||
local f="$TEST_DIR/src/[Group] Show X/01.mkv"
|
||||
MOCK_RESPONSES["/search/tv|zh-CN"]='{"results":[{"id":300,"name":"Show X"}]}'
|
||||
local id
|
||||
id=$(tv_search_by_dir "$f" "01" "1" "")
|
||||
assert_eq "300" "$id" "目录名兜底命中"
|
||||
}
|
||||
|
||||
# --- 完整识别链 ---
|
||||
|
||||
# identify_tv_show:zh 命中 → build_tv_dest 输出目标
|
||||
test_identify_tv_show_zh_hit() {
|
||||
MOCK_RESPONSES["/search/tv|zh-CN"]='{"results":[{"id":100,"name":"Show A"}]}'
|
||||
MOCK_RESPONSES["/tv/100|zh-CN"]='{"name":"Show A","first_air_date":"2020-01-01","number_of_seasons":2,"seasons":[{"season_number":1},{"season_number":2}]}'
|
||||
MOCK_RESPONSES["/tv/100/season/1|zh-CN"]='{"episodes":[{"episode_number":1,"name":"Pilot"},{"episode_number":2,"name":"Two"}]}'
|
||||
local dest="" rc=0
|
||||
dest=$(identify_tv_show "Show A" "1" "1" "" "Show A S01E01" "mkv" "" "$TEST_DIR/src/Show A/Show A S01E01.mkv") || rc=$?
|
||||
assert_eq "0" "$rc" "zh 命中识别返回码"
|
||||
assert_contains "$dest" "节目/Show A (2020)/Season 01|S01E01 - Pilot.mkv" "zh 命中目标"
|
||||
}
|
||||
|
||||
# identify_tv_show:zh 空 → en 命中
|
||||
test_identify_tv_show_zh_miss_en_hit() {
|
||||
MOCK_RESPONSES["/search/tv|en-US"]='{"results":[{"id":200,"name":"Show B"}]}'
|
||||
MOCK_RESPONSES["/tv/200|zh-CN"]='{"name":"Show B","first_air_date":"2021-01-01","number_of_seasons":1,"seasons":[{"season_number":1}]}'
|
||||
MOCK_RESPONSES["/tv/200/season/1|zh-CN"]='{"episodes":[{"episode_number":1,"name":"First"}]}'
|
||||
local dest="" rc=0
|
||||
dest=$(identify_tv_show "Show B" "1" "1" "" "Show B S01E01" "mkv" "" "$TEST_DIR/src/Show B/Show B S01E01.mkv") || rc=$?
|
||||
assert_eq "0" "$rc" "en 命中识别返回码"
|
||||
assert_contains "$dest" "Show B (2021)" "en 命中目标"
|
||||
}
|
||||
|
||||
# identify_tv_show:主搜索(zh)请求失败 → 短路 return 2(不试后续层)
|
||||
test_identify_tv_show_zh_fail_shortcircuit() {
|
||||
MOCK_FAIL_ENDPOINTS["/search/tv"]=1
|
||||
MOCK_RESPONSES["/search/tv|en-US"]='{"results":[{"id":200,"name":"Show B"}]}'
|
||||
local rc=0
|
||||
identify_tv_show "Show B" "1" "1" "" "Show B S01E01" "mkv" "" "$TEST_DIR/src/Show B/Show B S01E01.mkv" >/dev/null 2>&1 || rc=$?
|
||||
assert_eq "2" "$rc" "主搜索失败短路返回码"
|
||||
}
|
||||
|
||||
# identify_tv_show:zh/en 空 → 后缀二次剥离重搜命中("Railgun S" → "Railgun")
|
||||
test_identify_tv_show_suffix_strip() {
|
||||
MOCK_RESPONSES["/search/tv|zh-CN"]='{"results":[{"id":400,"name":"Railgun"}]}'
|
||||
MOCK_RESPONSES["/tv/400|zh-CN"]='{"name":"Railgun","first_air_date":"2020-01-01","number_of_seasons":3,"seasons":[{"season_number":1},{"season_number":2},{"season_number":3}]}'
|
||||
MOCK_RESPONSES["/tv/400/season/1|zh-CN"]='{"episodes":[{"episode_number":1,"name":"First"}]}'
|
||||
# 注意:首次搜索 "Railgun S" 无结果(mock 表无 "Railgun S" 条目),二次剥离后 "Railgun" 命中
|
||||
local dest="" rc=0
|
||||
dest=$(identify_tv_show "Railgun S" "1" "1" "" "Railgun S S01E01" "mkv" "" "$TEST_DIR/src/Railgun S/Railgun S S01E01.mkv") || rc=$?
|
||||
assert_eq "0" "$rc" "后缀剥离识别返回码"
|
||||
assert_contains "$dest" "Railgun (2020)" "后缀剥离命中目标"
|
||||
}
|
||||
|
||||
# identify_tv_show:全链失败 → return 1(无结果)
|
||||
test_identify_tv_show_all_miss() {
|
||||
local rc=0
|
||||
identify_tv_show "No Such Show" "1" "1" "" "No Such Show S01E01" "mkv" "" "$TEST_DIR/src/No Such Show/No Such Show S01E01.mkv" >/dev/null 2>&1 || rc=$?
|
||||
assert_eq "1" "$rc" "全链无结果返回码"
|
||||
}
|
||||
|
||||
# identify_movie:zh 命中 → build_movie_dest 输出目标
|
||||
test_identify_movie_zh_hit() {
|
||||
MOCK_RESPONSES["/search/movie|zh-CN"]='{"results":[{"id":50,"title":"Movie X","release_date":"2020-01-01"}]}'
|
||||
MOCK_RESPONSES["/movie/50|zh-CN"]='{"title":"Movie X","release_date":"2020-01-01"}'
|
||||
local dest="" rc=0
|
||||
dest=$(identify_movie "Movie X" "mkv" "$TEST_DIR/src/Movie X (2020).mkv" "Movie X" "2020") || rc=$?
|
||||
assert_eq "0" "$rc" "movie zh 命中返回码"
|
||||
assert_contains "$dest" "电影/Movie X (2020)|Movie X (2020).mkv" "movie zh 命中目标"
|
||||
}
|
||||
|
||||
# identify_movie:年份不符且无一致候选 → 需甄别(emit MATCH,返回 3)
|
||||
test_identify_movie_year_mismatch_match() {
|
||||
MOCK_RESPONSES["/search/movie|zh-CN"]='{"results":[{"id":50,"title":"Movie X","release_date":"2019-05-01"}]}'
|
||||
local rc=0
|
||||
identify_movie "Movie X" "mkv" "$TEST_DIR/src/Movie X (2020).mkv" "Movie X" "2020" >/dev/null 2>&1 || rc=$?
|
||||
assert_eq "3" "$rc" "年份不符需甄别返回码"
|
||||
assert_success test -n "${PENDING_AI_MATCH["$TEST_DIR/src/Movie X (2020).mkv"]:-}"
|
||||
}
|
||||
|
||||
# identify_movie:主搜索失败 → 短路 return 2
|
||||
test_identify_movie_zh_fail_shortcircuit() {
|
||||
MOCK_FAIL_ENDPOINTS["/search/movie"]=1
|
||||
local rc=0
|
||||
identify_movie "Movie X" "mkv" "$TEST_DIR/src/Movie X (2020).mkv" "Movie X" "2020" >/dev/null 2>&1 || rc=$?
|
||||
assert_eq "2" "$rc" "movie 主搜索失败短路返回码"
|
||||
}
|
||||
|
||||
# identify_movie:zh 空 → en 命中(两行协议回传年份校验通过)
|
||||
test_identify_movie_en_fallback() {
|
||||
MOCK_RESPONSES["/search/movie|en-US"]='{"results":[{"id":60,"title":"Movie Y","release_date":"2021-01-01"}]}'
|
||||
MOCK_RESPONSES["/movie/60|zh-CN"]='{"title":"Movie Y","release_date":"2021-01-01"}'
|
||||
local dest="" rc=0
|
||||
dest=$(identify_movie "Movie Y" "mkv" "$TEST_DIR/src/Movie Y (2021).mkv" "Movie Y" "2021") || rc=$?
|
||||
assert_eq "0" "$rc" "movie en 命中返回码"
|
||||
assert_contains "$dest" "Movie Y (2021)" "movie en 命中目标"
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
# ============================================================================
|
||||
# Music Videos 类目测试(src/lib/media/filename.sh / identify.sh)
|
||||
# ============================================================================
|
||||
|
||||
setup() {
|
||||
TEST_DIR="$(mktemp -d)"
|
||||
SOURCE_DIR="$TEST_DIR"
|
||||
FOLDER_MUSICVIDEOS="音乐视频"
|
||||
FOLDER_UNKNOWN="未知"
|
||||
SPECIAL_WORDS_CAT=()
|
||||
mapfile -t MUSICVIDEO_WORDS < <(echo "$MUSICVIDEO_WORDS_TEMPLATE" | jq -r '.[]')
|
||||
mapfile -t SPECIAL_WORDS < <(echo "$SPECIAL_WORDS_TEMPLATE" | jq -r '.[]')
|
||||
MAIN_COUNT_CACHE=()
|
||||
VIDEO_EXTS=(mp4 mkv)
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf "$TEST_DIR"
|
||||
}
|
||||
|
||||
# detect_musicvideo:目录信号(Music Videos / MV / Live 目录 → musicvideo)
|
||||
test_musicvideo_dir_signal() {
|
||||
mkdir -p "$TEST_DIR/Music Videos/周杰伦"
|
||||
assert_eq "musicvideo|晴天|||||" \
|
||||
"$(parse_media_filename "$TEST_DIR/Music Videos/周杰伦/晴天 [MV].mkv")"
|
||||
assert_eq "musicvideo|七里香|||||" \
|
||||
"$(parse_media_filename "$TEST_DIR/Music Videos/周杰伦/七里香.mp4")"
|
||||
mkdir -p "$TEST_DIR/Live"
|
||||
assert_eq "musicvideo|演唱会|||||" \
|
||||
"$(parse_media_filename "$TEST_DIR/Live/演唱会.mkv")"
|
||||
}
|
||||
|
||||
# detect_musicvideo:文件名信号([MV] + "歌手 - 歌名" 模式 → musicvideo)
|
||||
test_musicvideo_filename_signal() {
|
||||
assert_eq "musicvideo|周杰伦 - 晴天|||||" \
|
||||
"$(parse_media_filename "$TEST_DIR/周杰伦 - 晴天 [MV].mkv")"
|
||||
assert_eq "musicvideo|歌手 - 歌|||||" \
|
||||
"$(parse_media_filename "$TEST_DIR/歌手 - 歌 [Live].mkv")"
|
||||
}
|
||||
|
||||
# detect_musicvideo:双命中歧义([MV] 无 " - " → 保持特典路径)
|
||||
test_musicvideo_dual_hit_keeps_special() {
|
||||
assert_eq "tv|某动画||0|1||MV" \
|
||||
"$(parse_media_filename "$TEST_DIR/某动画 [MV].mkv")"
|
||||
}
|
||||
|
||||
# detect_musicvideo:目录名含词但不精确匹配 → 不误判(Muv-Luv / tmp.xxx)
|
||||
test_musicvideo_dir_exact_match_only() {
|
||||
assert_eq "tv|Muv-Luv||01|01||" \
|
||||
"$(parse_media_filename "$TEST_DIR/Muv-Luv S01E01.mkv")"
|
||||
assert_eq "tv|Show||1|07||" \
|
||||
"$(parse_media_filename "$TEST_DIR/Show [07].mkv")"
|
||||
}
|
||||
|
||||
# identify_musicvideo:目录名解析 artist("歌手 - 歌名" 取首段;无分隔符整名)
|
||||
test_identify_musicvideo_artist_from_dir() {
|
||||
mkdir -p "$TEST_DIR/Music Videos/周杰伦"
|
||||
assert_eq "音乐视频/周杰伦/晴天|晴天.mkv" \
|
||||
"$(identify_musicvideo "$TEST_DIR/Music Videos/周杰伦/晴天.mkv" "晴天" mkv)"
|
||||
mkdir -p "$TEST_DIR/歌手 - 歌名"
|
||||
assert_eq "音乐视频/歌手/x|x.mkv" \
|
||||
"$(identify_musicvideo "$TEST_DIR/歌手 - 歌名/x.mkv" "x" mkv)"
|
||||
}
|
||||
|
||||
# identify_musicvideo:无元数据/目录线索 → FOLDER_UNKNOWN 兜底
|
||||
test_identify_musicvideo_unknown_artist() {
|
||||
assert_eq "音乐视频/未知/歌|歌.mkv" \
|
||||
"$(identify_musicvideo "$TEST_DIR/歌.mkv" "歌" mkv)"
|
||||
}
|
||||
|
||||
# fallback_naming:musicvideo 分支(Unknown 艺术家)
|
||||
test_fallback_naming_musicvideo() {
|
||||
assert_eq "音乐视频/未知/歌|歌.mkv" \
|
||||
"$(fallback_naming "musicvideo" "歌" "" "" "" "" "" "mkv")"
|
||||
}
|
||||
Reference in new issue
Block a user