diff --git a/crates/recorder/src/downloaders/qbitorrent.rs b/crates/recorder/src/downloaders/qbitorrent.rs index af84c61..4bcc966 100644 --- a/crates/recorder/src/downloaders/qbitorrent.rs +++ b/crates/recorder/src/downloaders/qbitorrent.rs @@ -416,7 +416,6 @@ impl Debug for QBittorrentDownloader { #[cfg(test)] pub mod tests { use itertools::Itertools; - use testcontainers::core::ExecCommand; use super::*; diff --git a/crates/recorder/src/models/entities/episodes.rs b/crates/recorder/src/models/entities/episodes.rs index fd36423..80ff914 100644 --- a/crates/recorder/src/models/entities/episodes.rs +++ b/crates/recorder/src/models/entities/episodes.rs @@ -1,25 +1,8 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2 -use sea_orm::{entity::prelude::*, FromJsonQueryResult}; +use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] -pub struct EpisodeFileMeta { - pub media_path: String, - pub group: Option, - pub title: String, - pub season: i32, - pub episode_index: Option, - pub extension: String, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)] -pub struct SubtitleFileMeta { - pub episode_file_meta: EpisodeFileMeta, - pub extension: String, - pub lang: Option, -} - #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "episodes")] pub struct Model { diff --git a/crates/recorder/src/parsers/defs.rs b/crates/recorder/src/parsers/defs.rs index b9d6277..f570a26 100644 --- a/crates/recorder/src/parsers/defs.rs +++ b/crates/recorder/src/parsers/defs.rs @@ -1,8 +1,8 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use fancy_regex::Regex as FancyRegex; use lazy_static::lazy_static; -use maplit::{btreemap, hashmap}; +use maplit::hashmap; use regex::Regex; const LANG_ZH_TW: &str = "zh-tw"; @@ -26,13 +26,13 @@ lazy_static! { FancyRegex::new(r"(.*)第?(\d*\.*\d*)[话話集](?:END)?(.*)").unwrap(), FancyRegex::new(r"(.*)(?:S\d{2})?EP?(\d+)(.*)").unwrap(), ]; - pub static ref SUBTITLE_LANG: BTreeMap<&'static str, Vec<&'static str>> = { - btreemap! { - LANG_ZH_TW => vec!["tc", "cht", "繁", "zh-tw"], - LANG_ZH => vec!["sc", "chs", "简", "zh", "zh-cn"], - LANG_EN => vec!["en", "eng", "英"], - LANG_JP => vec!["jp", "jpn", "日"], - } + pub static ref SUBTITLE_LANG: Vec<(&'static str, Vec<&'static str>)> = { + vec![ + (LANG_ZH_TW, vec!["tc", "cht", "繁", "zh-tw"]), + (LANG_ZH, vec!["sc", "chs", "简", "zh", "zh-cn"]), + (LANG_EN, vec!["en", "eng", "英"]), + (LANG_JP, vec!["jp", "jpn", "日"]), + ] }; pub static ref BRACKETS_REG: Regex = Regex::new(r"[\[\]()【】()]").unwrap(); pub static ref DIGIT_1PLUS_REG: Regex = Regex::new(r"\d+").unwrap(); diff --git a/crates/recorder/src/parsers/mod.rs b/crates/recorder/src/parsers/mod.rs index 00763a0..1ffecdc 100644 --- a/crates/recorder/src/parsers/mod.rs +++ b/crates/recorder/src/parsers/mod.rs @@ -5,4 +5,3 @@ pub mod mikan; pub mod raw; pub mod title_parser; pub mod torrent; -pub mod torrent_parser; diff --git a/crates/recorder/src/parsers/raw/raw_ep_parser.rs b/crates/recorder/src/parsers/raw/raw_ep_parser.rs index 3f367ff..8dcc02e 100644 --- a/crates/recorder/src/parsers/raw/raw_ep_parser.rs +++ b/crates/recorder/src/parsers/raw/raw_ep_parser.rs @@ -184,7 +184,7 @@ fn extract_name_from_title_body_name_section( let mut split_space = split[0].split(' ').collect_vec(); let mut search_indices = vec![0]; if split_space.len() > 1 { - search_indices.push(search_indices.len() - 1); + search_indices.push(split_space.len() - 1); } for i in search_indices { if NAME_ZH_TEST.is_match(split_space[i]) { @@ -288,7 +288,7 @@ pub fn parse_episode_meta_from_raw_name(s: &str) -> eyre::Result let (name_en, name_zh, name_jp) = extract_name_from_title_body_name_section(&title_body); let (name_en_no_season, name_zh_no_season, name_jp_no_season) = extract_name_from_title_body_name_section(&name_without_season); - let episode_index = extract_episode_index_from_title_episode(title_episode).unwrap_or(0); + let episode_index = extract_episode_index_from_title_episode(title_episode).unwrap_or(1); let (sub, resolution, source) = extract_tags_from_title_extra(title_extra); Ok(RawEpisodeMeta { name_en, @@ -495,7 +495,7 @@ mod tests { "name_zh": "爱丽丝与特蕾丝的虚幻工厂", "name_zh_no_season": "爱丽丝与特蕾丝的虚幻工厂", "season": 1, - "episode_index": 0, + "episode_index": 1, "sub": "简繁内封", "source": "WebRip", "fansub": "千夏字幕组", @@ -511,13 +511,10 @@ mod tests { r#"{ "name_en": "Yuru Camp Movie", "name_en_no_season": "Yuru Camp Movie", - "name_jp": null, - "name_jp_no_season": null, "name_zh": "电影 轻旅轻营 (摇曳露营)", "name_zh_no_season": "电影 轻旅轻营 (摇曳露营)", "season": 1, - "season_raw": null, - "episode_index": 0, + "episode_index": 1, "sub": "繁体", "source": "UHDRip", "fansub": "千夏字幕组&喵萌奶茶屋", @@ -526,6 +523,24 @@ mod tests { ) } + #[test] + fn test_parse_ep_with_large_episode_style() { + test_raw_ep_parser_case( + r#"[梦蓝字幕组]New Doraemon 哆啦A梦新番[747][2023.02.25][AVC][1080P][GB_JP][MP4]"#, + r#"{ + "name_en": "New Doraemon", + "name_en_no_season": "New Doraemon", + "name_zh": "哆啦A梦新番", + "name_zh_no_season": "哆啦A梦新番", + "season": 1, + "episode_index": 747, + "sub": "GB", + "fansub": "梦蓝字幕组", + "resolution": "1080P" + }"#, + ) + } + #[test] fn test_parse_ep_with_many_square_brackets_split_title() { test_raw_ep_parser_case( @@ -536,7 +551,7 @@ mod tests { "name_zh": "剧场版-摇曳露营", "name_zh_no_season": "剧场版-摇曳露营", "season": 1, - "episode_index": 0, + "episode_index": 1, "sub": "简日双语", "fansub": "MCE汉化组", "resolution": "1080P" @@ -544,6 +559,24 @@ mod tests { ) } + #[test] + fn test_parse_ep_with_implicit_lang_title_sep() { + test_raw_ep_parser_case( + r#"[织梦字幕组][尼尔:机械纪元 NieR Automata Ver1.1a][02集][1080P][AVC][简日双语]"#, + r#"{ + "name_en": "NieR Automata Ver1.1a", + "name_en_no_season": "NieR Automata Ver1.1a", + "name_zh": "尼尔:机械纪元", + "name_zh_no_season": "尼尔:机械纪元", + "season": 1, + "episode_index": 2, + "sub": "简日双语", + "fansub": "织梦字幕组", + "resolution": "1080P" + }"#, + ) + } + #[test] fn test_parse_ep_with_square_brackets_wrapped_and_space_split() { test_raw_ep_parser_case( @@ -602,9 +635,101 @@ mod tests { ) } + #[test] + fn test_parse_ep_title_leading_space_style() { + test_raw_ep_parser_case( + r#"[ANi] 16bit 的感动 ANOTHER LAYER - 01 [1080P][Baha][WEB-DL][AAC AVC][CHT][MP4]"#, + r#"{ + "name_zh": "16bit 的感动 ANOTHER LAYER", + "name_zh_no_season": "16bit 的感动 ANOTHER LAYER", + "season": 1, + "season_raw": null, + "episode_index": 1, + "sub": "CHT", + "source": "Baha", + "fansub": "ANi", + "resolution": "1080P" + }"#, + ) + } + + #[test] + fn test_parse_ep_title_leading_month_and_wrapped_brackets_style() { + test_raw_ep_parser_case( + r#"【喵萌奶茶屋】★07月新番★[银砂糖师与黑妖精 ~ Sugar Apple Fairy Tale ~][13][1080p][简日双语][招募翻译]"#, + r#"{ + "name_en": "~ Sugar Apple Fairy Tale ~", + "name_en_no_season": "~ Sugar Apple Fairy Tale ~", + "name_zh": "银砂糖师与黑妖精", + "name_zh_no_season": "银砂糖师与黑妖精", + "season": 1, + "episode_index": 13, + "sub": "简日双语", + "fansub": "喵萌奶茶屋", + "resolution": "1080p" + }"#, + ) + } + + #[test] + fn test_parse_ep_title_leading_month_style() { + test_raw_ep_parser_case( + r#"【极影字幕社】★4月新番 天国大魔境 Tengoku Daimakyou 第05话 GB 720P MP4(字幕社招人内详)"#, + r#"{ + "name_en": "Tengoku Daimakyou", + "name_en_no_season": "Tengoku Daimakyou", + "name_zh": "天国大魔境", + "name_zh_no_season": "天国大魔境", + "season": 1, + "episode_index": 5, + "sub": "字幕社招人内详", + "source": null, + "fansub": "极影字幕社", + "resolution": "720P" + }"#, + ) + } + + #[test] + fn test_parse_ep_tokusatsu_style() { + test_raw_ep_parser_case( + r#"[MagicStar] 假面骑士Geats / 仮面ライダーギーツ EP33 [WEBDL] [1080p] [TTFC]【生】"#, + r#"{ + "name_jp": "仮面ライダーギーツ", + "name_jp_no_season": "仮面ライダーギーツ", + "name_zh": "假面骑士Geats", + "name_zh_no_season": "假面骑士Geats", + "season": 1, + "episode_index": 33, + "source": "WEBDL", + "fansub": "MagicStar", + "resolution": "1080p" + }"#, + ) + } + + #[test] + fn test_parse_ep_with_multi_lang_zh_title() { + test_raw_ep_parser_case( + r#"[百冬练习组&LoliHouse] BanG Dream! 少女乐团派对!☆PICO FEVER! / Garupa Pico: Fever! - 26 [WebRip 1080p HEVC-10bit AAC][简繁内封字幕][END] [101.69 MB]"#, + r#"{ + "name_en": "Garupa Pico: Fever!", + "name_en_no_season": "Garupa Pico: Fever!", + "name_zh": "BanG Dream! 少女乐团派对!☆PICO FEVER!", + "name_zh_no_season": "BanG Dream! 少女乐团派对!☆PICO FEVER!", + "season": 1, + "episode_index": 26, + "sub": "简繁内封字幕", + "source": "WebRip", + "fansub": "百冬练习组&LoliHouse", + "resolution": "1080p" + }"#, + ) + } + // TODO: FIXME #[test] - fn test_bad_case() { + fn test_bad_cases() { test_raw_ep_parser_case( r#"[7³ACG x 桜都字幕组] 摇曳露营△ 剧场版/映画 ゆるキャン△/Eiga Yuru Camp△ [简繁字幕] BDrip 1080p x265 FLAC 2.0"#, r#"{ @@ -612,12 +737,28 @@ mod tests { "name_zh_no_season": "摇曳露营△剧场版", "season": 1, "season_raw": null, - "episode_index": 0, + "episode_index": 1, "sub": "简繁字幕", "source": "BDrip", "fansub": "7³ACG x 桜都字幕组", "resolution": "1080p" }"#, - ) + ); + + test_raw_ep_parser_case( + r#"【幻樱字幕组】【4月新番】【古见同学有交流障碍症 第二季 Komi-san wa, Komyushou Desu. S02】【22】【GB_MP4】【1920X1080】"#, + r#"{ + "name_en": "第二季 Komi-san wa, Komyushou Desu. S02", + "name_en_no_season": "Komi-san wa, Komyushou Desu.", + "name_zh": "古见同学有交流障碍症", + "name_zh_no_season": "古见同学有交流障碍症", + "season": 2, + "season_raw": "第二季", + "episode_index": 22, + "sub": "GB", + "fansub": "幻樱字幕组", + "resolution": "1920X1080" + }"#, + ); } } diff --git a/crates/recorder/src/parsers/torrent/torrent_ep_parser.rs b/crates/recorder/src/parsers/torrent/torrent_ep_parser.rs index 7bf2b3d..caa3257 100644 --- a/crates/recorder/src/parsers/torrent/torrent_ep_parser.rs +++ b/crates/recorder/src/parsers/torrent/torrent_ep_parser.rs @@ -1,45 +1,316 @@ +use eyre::OptionExt; +use fancy_regex::Regex as FancyRegex; +use lazy_static::lazy_static; +use quirks_path::Path; +use regex::Regex; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct TorrentEpisodeMediaMeta {} +use crate::parsers::defs::SUBTITLE_LANG; + +lazy_static! { + static ref TORRENT_EP_PARSE_RULES: Vec = { + vec![ + FancyRegex::new( + r"(.*) - (\d{1,4}(?!\d|p)|\d{1,4}\.\d{1,2}(?!\d|p))(?:v\d{1,2})?(?: )?(?:END)?(.*)", + ) + .unwrap(), + FancyRegex::new( + r"(.*)[\[\ E](\d{1,4}|\d{1,4}\.\d{1,2})(?:v\d{1,2})?(?: )?(?:END)?[\]\ ](.*)", + ) + .unwrap(), + FancyRegex::new(r"(.*)\[(?:第)?(\d*\.*\d*)[话集話](?:END)?\](.*)").unwrap(), + FancyRegex::new(r"(.*)第?(\d*\.*\d*)[话話集](?:END)?(.*)").unwrap(), + FancyRegex::new(r"(.*)(?:S\d{2})?EP?(\d+)(.*)").unwrap(), + ] + }; + static ref GET_FANSUB_SPLIT_RE: Regex = Regex::new(r"[\[\]()【】()]").unwrap(); + static ref GET_FANSUB_FULL_MATCH_RE: Regex = Regex::new(r"^\d+$").unwrap(); + static ref GET_SEASON_AND_TITLE_SUB_RE: Regex = Regex::new(r"([Ss]|Season )\d{1,3}").unwrap(); + static ref GET_SEASON_AND_TITLE_FIND_RE: Regex = + Regex::new(r"([Ss]|Season )(\d{1,3})").unwrap(); +} #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct TorrentEpisodeSubtitleMeta {} +pub struct TorrentEpisodeMediaMeta { + pub fansub: Option, + pub title: String, + pub season: i32, + pub episode_index: i32, + pub extname: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct TorrentEpisodeSubtitleMeta { + pub media: TorrentEpisodeMediaMeta, + pub lang: Option, +} + +fn get_fansub(group_and_title: &str) -> (Option<&str>, &str) { + let n = GET_FANSUB_SPLIT_RE + .split(group_and_title) + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .collect::>(); + + match (n.get(0), n.get(1)) { + (None, None) => (None, ""), + (Some(n0), None) => (None, *n0), + (Some(n0), Some(n1)) => { + if GET_FANSUB_FULL_MATCH_RE.is_match(*n1) { + (None, group_and_title) + } else { + (Some(*n0), *n1) + } + } + _ => unreachable!("vec contains n1 must contains n0"), + } +} + +fn get_season_and_title(season_and_title: &str) -> (String, i32) { + let replaced_title = GET_SEASON_AND_TITLE_SUB_RE.replace_all(season_and_title, ""); + let title = replaced_title.trim().to_string(); + + let season = GET_SEASON_AND_TITLE_FIND_RE + .captures(season_and_title) + .map(|m| { + m.get(2) + .unwrap_or_else(|| unreachable!("season regex should have 2 groups")) + .as_str() + .parse::() + .unwrap_or_else(|_| unreachable!("season should be a number")) + }) + .unwrap_or(1); + + (title, season) +} + +fn get_subtitle_lang(media_name: &str) -> Option<&str> { + let media_name_lower = media_name.to_lowercase(); + for (lang, lang_aliases) in SUBTITLE_LANG.iter() { + if lang_aliases + .iter() + .any(|alias| media_name_lower.contains(alias)) + { + return Some(lang); + } + } + return None; +} pub fn parse_episode_media_meta_from_torrent( - torrent_path: &str, + torrent_path: &Path, torrent_name: Option<&str>, season: Option, ) -> eyre::Result { - todo!() + let media_name = torrent_path + .file_name() + .ok_or_else(|| eyre::eyre!("failed to get file name of {}", torrent_path))?; + let mut match_obj = None; + for rule in TORRENT_EP_PARSE_RULES.iter() { + match_obj = if let Some(torrent_name) = torrent_name.as_ref() { + rule.captures(torrent_name)? + } else { + rule.captures(media_name)? + }; + if match_obj.is_some() { + break; + } + } + if let Some(match_obj) = match_obj { + let group_season_and_title = match_obj + .get(1) + .ok_or_else(|| eyre::eyre!("should have 1 group"))? + .as_str(); + let (fansub, season_and_title) = get_fansub(group_season_and_title); + let (title, season) = if let Some(season) = season { + let (title, _) = get_season_and_title(season_and_title); + (title, season) + } else { + get_season_and_title(season_and_title) + }; + let episode_index = match_obj + .get(2) + .ok_or_eyre("should have 2 group")? + .as_str() + .parse::() + .unwrap_or(1); + let extname = torrent_path + .extension() + .map(|e| format!(".{}", e)) + .unwrap_or_default(); + Ok(TorrentEpisodeMediaMeta { + fansub: fansub.map(|s| s.to_string()), + title, + season, + episode_index, + extname, + }) + } else { + Err(eyre::eyre!( + "failed to parse episode media meta from torrent_path='{}' torrent_name='{:?}'", + torrent_path, + torrent_name + )) + } } pub fn parse_episode_subtitle_meta_from_torrent( - torrent_path: &str, + torrent_path: &Path, torrent_name: Option<&str>, season: Option, -) -> eyre::Result { - todo!() +) -> eyre::Result { + let media_meta = parse_episode_media_meta_from_torrent(torrent_path, torrent_name, season)?; + let media_name = torrent_path + .file_name() + .ok_or_else(|| eyre::eyre!("failed to get file name of {}", torrent_path))?; + + let lang = get_subtitle_lang(media_name); + + Ok(TorrentEpisodeSubtitleMeta { + media: media_meta, + lang: lang.map(|s| s.to_string()), + }) } #[cfg(test)] mod tests { + use quirks_path::Path; + use super::{ parse_episode_media_meta_from_torrent, parse_episode_subtitle_meta_from_torrent, TorrentEpisodeMediaMeta, TorrentEpisodeSubtitleMeta, }; - pub fn test_torrent_ep_parser(raw_name: &str, expected: &str) { - let expected: Option = serde_json::from_str(expected).unwrap(); - let found = parse_episode_media_meta_from_torrent(raw_name, None, None).ok(); + #[test] + fn test_lilith_raws_media() { + test_torrent_ep_parser( + r#"[Lilith-Raws] Boku no Kokoro no Yabai Yatsu - 01 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4].mp4"#, + r#"{"fansub": "Lilith-Raws", "title": "Boku no Kokoro no Yabai Yatsu", "season": 1, "episode_index": 1, "extname": ".mp4"}"#, + ); + } - if expected != found { - println!( - "expected {} and found {} are not equal", - serde_json::to_string_pretty(&expected).unwrap(), - serde_json::to_string_pretty(&found).unwrap() - ) + #[test] + fn test_sakurato_media() { + test_torrent_ep_parser( + r#"[Sakurato] Tonikaku Kawaii S2 [03][AVC-8bit 1080p AAC][CHS].mp4"#, + r#"{"fansub": "Sakurato", "title": "Tonikaku Kawaii", "season": 2, "episode_index": 3, "extname": ".mp4"}"#, + ) + } + + #[test] + fn test_lolihouse_media() { + test_torrent_ep_parser( + r#"[SweetSub&LoliHouse] Heavenly Delusion - 08 [WebRip 1080p HEVC-10bit AAC ASSx2].mkv"#, + r#"{"fansub": "SweetSub&LoliHouse", "title": "Heavenly Delusion", "season": 1, "episode_index": 8, "extname": ".mkv"}"#, + ) + } + + #[test] + fn test_sbsub_media() { + test_torrent_ep_parser( + r#"[SBSUB][CONAN][1082][V2][1080P][AVC_AAC][CHS_JP](C1E4E331).mp4"#, + r#"{"fansub": "SBSUB", "title": "CONAN", "season": 1, "episode_index": 1082, "extname": ".mp4"}"#, + ) + } + + #[test] + fn test_non_fansub_media() { + test_torrent_ep_parser( + r#"海盗战记 (2019) S04E11.mp4"#, + r#"{"title": "海盗战记 (2019)", "season": 4, "episode_index": 11, "extname": ".mp4"}"#, + ) + } + + #[test] + fn test_non_fansub_media_with_dirname() { + test_torrent_ep_parser( + r#"海盗战记/海盗战记 S01E01.mp4"#, + r#"{"title": "海盗战记", "season": 1, "episode_index": 1, "extname": ".mp4"}"#, + ); + } + + #[test] + fn test_non_fansub_tc_subtitle() { + test_torrent_ep_parser( + r#"海盗战记 S01E08.zh-tw.ass"#, + r#"{"media": { "title": "海盗战记", "season": 1, "episode_index": 8, "extname": ".ass" }, "lang": "zh-tw"}"#, + ); + } + + #[test] + fn test_non_fansub_sc_subtitle() { + test_torrent_ep_parser( + r#"海盗战记 S01E01.SC.srt"#, + r#"{ "media": { "title": "海盗战记", "season": 1, "episode_index": 1, "extname": ".srt" }, "lang": "zh" }"#, + ) + } + + #[test] + fn test_non_fansub_media_with_season_zero() { + test_torrent_ep_parser( + r#"水星的魔女(2022) S00E19.mp4"#, + r#"{"fansub": null,"title": "水星的魔女(2022)","season": 0,"episode_index": 19,"extname": ".mp4"}"#, + ) + } + + #[test] + fn test_shimian_fansub_media() { + test_torrent_ep_parser( + r#"【失眠搬运组】放学后失眠的你-Kimi wa Houkago Insomnia - 06 [bilibili - 1080p AVC1 CHS-JP].mp4"#, + r#"{"fansub": "失眠搬运组","title": "放学后失眠的你-Kimi wa Houkago Insomnia","season": 1,"episode_index": 6,"extname": ".mp4"}"#, + ) + } + + pub fn test_torrent_ep_parser(raw_name: &str, expected: &str) { + let extname = Path::new(raw_name) + .extension() + .map(|e| format!(".{}", e)) + .unwrap_or_default() + .to_lowercase(); + + if extname == ".srt" || extname == ".ass" { + let expected: Option = serde_json::from_str(expected).ok(); + let found_raw = + parse_episode_subtitle_meta_from_torrent(Path::new(raw_name), None, None); + let found = found_raw.as_ref().ok().map(|s| s.clone()); + + if expected != found { + if found_raw.is_ok() { + println!( + "expected {} and found {} are not equal", + serde_json::to_string_pretty(&expected).unwrap(), + serde_json::to_string_pretty(&found).unwrap() + ) + } else { + println!( + "expected {} and found {:#?} are not equal", + serde_json::to_string_pretty(&expected).unwrap(), + found_raw + ) + } + } + assert_eq!(expected, found); + } else { + let expected: Option = serde_json::from_str(expected).ok(); + let found_raw = parse_episode_media_meta_from_torrent(Path::new(raw_name), None, None); + let found = found_raw.as_ref().ok().map(|s| s.clone()); + + if expected != found { + if found_raw.is_ok() { + println!( + "expected {} and found {} are not equal", + serde_json::to_string_pretty(&expected).unwrap(), + serde_json::to_string_pretty(&found).unwrap() + ) + } else { + println!( + "expected {} and found {:#?} are not equal", + serde_json::to_string_pretty(&expected).unwrap(), + found_raw + ) + } + } + assert_eq!(expected, found); } - assert_eq!(expected, found); } } diff --git a/crates/recorder/src/parsers/torrent_parser.rs b/crates/recorder/src/parsers/torrent_parser.rs deleted file mode 100644 index 323e65d..0000000 --- a/crates/recorder/src/parsers/torrent_parser.rs +++ /dev/null @@ -1,91 +0,0 @@ -use quirks_path::Path; - -use super::defs::{ - BRACKETS_REG, DIGIT_1PLUS_REG, SEASON_REGEX, SUBTITLE_LANG, TORRENT_PRASE_RULE_REGS, -}; - -pub fn get_path_basename(path: &Path) -> &str { - path.parent().map_or("", |s| s.as_str()) -} - -pub fn get_fansub(group_and_title: &str) -> (Option<&str>, &str) { - let n = BRACKETS_REG - .split(group_and_title) - .map(|s| s.trim()) - .filter(|s| !s.is_empty()) - .collect::>(); - - if n.len() > 1 { - if DIGIT_1PLUS_REG.is_match(n[1]) { - (None, group_and_title) - } else { - (Some(n[0]), n[1]) - } - } else { - (None, n[0]) - } -} - -pub fn get_season_and_title(season_and_title: &str) -> (String, i32) { - let title = SEASON_REGEX.replace(season_and_title, ""); - let title = title.trim().to_string(); - let mut season = 1; - if let Some(match_result) = SEASON_REGEX.captures(season_and_title) { - let season_str = match_result - .get(2) - .unwrap_or_else(|| unreachable!("season regex should have 2 groups")) - .as_str(); - season = season_str - .parse::() - .unwrap_or_else(|_| unreachable!("season should be a number")); - } - (title, season) -} - -pub fn get_subtitle_lang(subtitle_name: &str) -> Option<&'static str> { - let subtitle_name_lower = subtitle_name.to_lowercase(); - for (lang, matches) in SUBTITLE_LANG.iter() { - for m in matches { - if subtitle_name_lower.contains(m) { - return Some(lang); - } - } - } - None -} - -pub fn parse_torrent( - torrent_path: &Path, - torrent_name: Option<&str>, - season: Option, - file_type: Option<&str>, -) { - let media_name = get_path_basename(torrent_path); - for rule in TORRENT_PRASE_RULE_REGS.iter() { - let match_obj = if let Some(torrent_name) = torrent_name { - rule.captures(torrent_name) - } else { - rule.captures(media_name) - }; - - if let Ok(Some(match_obj)) = match_obj { - let group_and_title = match_obj - .get(1) - .unwrap_or_else(|| unreachable!("should have 1 group")) - .as_str(); - let (group, title) = get_fansub(group_and_title); - let season_and_title = get_season_and_title(title); - let season = season.unwrap_or(season_and_title.1); - let title = season_and_title.0; - let episode = match_obj - .get(2) - .unwrap_or_else(|| unreachable!("should have 2 group")) - .as_str() - .parse::() - .unwrap_or_else(|_| unreachable!("episode should be a number")); - - let extension = media_name; - todo!() - } - } -}