soda_resource_tools_lib/soda/
entity.rs

1use std::collections::{HashMap, HashSet};
2use std::path::{Path, PathBuf};
3
4use regex::Regex;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use tracing::span::{self, Id};
8
9use crate::soda::extension_option::OptionExtensions;
10
11use super::tmdb::entity::{TmdbCast, TmdbCrew, TmdbEpisode, TmdbGenre, TmdbMovie, TmdbMovieInfo, TmdbSeason, TmdbSeasonInfo, TmdbTV, TmdbTVInfo};
12use std::error::Error;
13use std::fmt::{self, Display, Formatter};
14
15#[derive(Debug)]
16pub enum SodaError {
17    Io(std::io::Error),
18    Parse(std::num::ParseIntError),
19    Request(reqwest::Error),
20    Json(serde_json::Error),
21    Str(&'static str),
22    String(String),
23}
24
25impl Display for SodaError {
26    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
27        match *self {
28            SodaError::Io(ref err) => write!(f, "IO error: {}", err),
29            SodaError::Parse(ref err) => write!(f, "Parse error: {}", err),
30            SodaError::Request(ref err) => write!(f, "Request error: {}", err),
31            SodaError::Json(ref err) => write!(f, "Json error: {}", err),
32            SodaError::Str(ref err) => write!(f, "Biz error: {}", err),
33            SodaError::String(ref err) => write!(f, "Biz error: {}", err),
34        }
35    }
36}
37
38impl From<String> for SodaError {
39    fn from(err: String) -> Self {
40        SodaError::String(err)
41    }
42}
43
44impl From<&'static str> for SodaError {
45    fn from(err: &'static str) -> Self {
46        SodaError::Str(err)
47    }
48}
49
50impl From<std::io::Error> for SodaError {
51    fn from(err: std::io::Error) -> Self {
52        SodaError::Io(err)
53    }
54}
55
56impl From<std::num::ParseIntError> for SodaError {
57    fn from(err: std::num::ParseIntError) -> Self {
58        SodaError::Parse(err)
59    }
60}
61
62impl From<reqwest::Error> for SodaError {
63    fn from(err: reqwest::Error) -> Self {
64        SodaError::Request(err)
65    }
66}
67
68impl From<serde_json::Error> for SodaError {
69    fn from(err: serde_json::Error) -> Self {
70        SodaError::Json(err)
71    }
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub enum RenameStyle {
76    /// Emby 重命名格式
77    Emby,
78}
79
80impl std::fmt::Display for RenameStyle {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        let s = match self {
83            RenameStyle::Emby => "emby",
84        };
85        s.fmt(f)
86    }
87}
88
89impl std::str::FromStr for RenameStyle {
90    type Err = String;
91
92    fn from_str(s: &str) -> Result<Self, Self::Err> {
93        match s {
94            "emby" => Ok(Self::Emby),
95            _ => Err(format!("Unknown type: {s}")),
96        }
97    }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub enum EmbyRenameStyle {
102    /// 影视 - 电影 重命名格式
103    /// ```text
104    /// https://emby.media/support/articles/Movie-Naming.html
105    ///
106    /// 按照如下格式和顺序重命名
107    ///
108    /// /300 (2006)/300 (2006) - 1080p.mkv
109    /// /300 (2006)/300 (2006).mkv
110    /// /300/300.mkv
111    /// ```
112    EmbyMovie,
113
114    /// 影视 - 电视剧 重命名格式
115    /// ```text
116    /// https://emby.media/support/articles/TV-Naming.html
117    ///
118    /// 按照如下格式和顺序重命名
119    ///
120    /// \Glee (2009)\Season 1\S01E01.mp4
121    /// \Glee\Season 1\S01E01.mp4
122    ///
123    /// ```
124    EmbyTV,
125}
126impl EmbyRenameStyle {
127    pub(crate) fn rename(&self, mt_meta: &MTMetadata) -> PathBuf {
128        tracing::debug!("emby rename style = {:?}", self);
129
130        match &self {
131            EmbyRenameStyle::EmbyMovie => {
132                let title = if !mt_meta.title_cn.is_empty() {
133                    mt_meta.title_cn.clone()
134                } else {
135                    mt_meta.title_en.clone()
136                };
137
138                if !title.is_empty() && !mt_meta.year.is_empty() && !mt_meta.resolution.is_empty() && !mt_meta.extension.is_empty() {
139                    // /300 (2006)/300 (2006) - 1080p.mkv
140
141                    let path = PathBuf::new()
142                        .join(format!("{} ({})", title, mt_meta.year))
143                        .join(format!("{} ({}) - {}.{}", title, mt_meta.year, mt_meta.resolution, mt_meta.extension));
144
145                    tracing::debug!("emby EmbyMovie style = {:?}", path.to_str().unwrap());
146
147                    return path;
148                }
149
150                if !title.is_empty() && !mt_meta.year.is_empty() && !mt_meta.extension.is_empty() {
151                    // /300 (2006)/300 (2006).mkv
152
153                    let path = PathBuf::new()
154                        .join(format!("{} ({})", title, mt_meta.year))
155                        .join(format!("{} ({}).{}", title, mt_meta.year, mt_meta.extension));
156
157                    tracing::debug!("emby EmbyMovie style = {:?}", path.to_str().unwrap());
158
159                    return path;
160                }
161
162                if !title.is_empty() && !mt_meta.extension.is_empty() {
163                    // /300/300.mkv
164
165                    let path = PathBuf::new().join(format!("{}", title)).join(format!("{}.{}", title, mt_meta.extension));
166
167                    tracing::debug!("emby EmbyMovie style = {:?}", path.to_str().unwrap());
168
169                    return path;
170                }
171
172                return unreachable!("emby movie rename not implement");
173            }
174            EmbyRenameStyle::EmbyTV => {
175                let title = if !mt_meta.title_cn.is_empty() {
176                    mt_meta.title_cn.clone()
177                } else {
178                    mt_meta.title_en.clone()
179                };
180
181                let release_year = mt_meta.release_year.clone().unwrap_or(mt_meta.year.clone());
182
183                // \Glee (2009)\Season 1\S01E01.mp4
184                if !title.is_empty()
185                    && !release_year.is_empty()
186                    && !mt_meta.season.is_empty()
187                    && !mt_meta.episode.is_empty()
188                    && !mt_meta.extension.is_empty()
189                {
190                    let path = PathBuf::new()
191                        .join(format!("{} ({})", title, release_year))
192                        .join(format!("Season {}", mt_meta.season_number().unwrap_or(1)))
193                        .join(format!(
194                            "S{:02}E{:02}.{}",
195                            mt_meta.season_number().unwrap_or(1),
196                            mt_meta.episode_number().unwrap_or(1),
197                            mt_meta.extension
198                        ));
199
200                    tracing::debug!("emby EmbyTV style = {:?}", path.to_str().unwrap());
201
202                    return path;
203                }
204
205                // \Glee\Season 1\S01E01.mp4
206                if !title.is_empty()
207                    && release_year.is_empty()
208                    && (mt_meta.season.is_empty() || !mt_meta.season.is_empty())
209                    && !mt_meta.episode.is_empty()
210                    && !mt_meta.extension.is_empty()
211                {
212                    let path = PathBuf::new()
213                        .join(format!("{}", title))
214                        .join(format!("Season {}", mt_meta.season_number().unwrap_or(1)))
215                        .join(format!(
216                            "S{:02}E{:02}.{}",
217                            mt_meta.season_number().unwrap_or(1),
218                            mt_meta.episode_number().unwrap_or(1),
219                            mt_meta.extension
220                        ));
221
222                    tracing::debug!("emby EmbyTV style = {:?}", path.to_str().unwrap());
223
224                    return path;
225                }
226
227                return unreachable!("emby tv rename not implement");
228            }
229        }
230    }
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct LibConfig {
235    /// TMDB API Key
236    pub tmdb_api_key: String,
237
238    /// 缓存路径
239    pub cache_path: String,
240
241    /// 剧集 强匹配规则路径
242    pub strong_match_rules_tv_path: String,
243    pub strong_match_rules_tv: String,
244
245    /// 电影 强匹配规则路径
246    pub strong_match_rules_movie_path: String,
247    pub strong_match_rules_movie: String,
248
249    /// 强匹配正则规则路径
250    pub strong_match_regex_rules_path: String,
251    pub strong_match_regex_rules: String,
252
253    /// 强匹配名称映射路径
254    pub strong_match_name_map_path: String,
255    pub strong_match_name_map: String,
256
257    /// 是否跳过特典
258    pub metadata_skip_special: bool,
259
260    /// 影视 - 电视剧 重命名格式
261    /// ```text
262    /// https://emby.media/support/articles/TV-Naming.html
263    ///
264    ///  \TV
265    /// \Glee (2009)
266    /// \Season 1
267    ///    Glee S01E01.mp4
268    ///    Glee S01E02.mp4
269    /// \TV
270    /// \Seinfeld (1989)
271    ///     Seinfeld S01E01.mp4
272    ///     Seinfeld S01E02.mp4
273    /// ```
274    pub transfer_rename_format_tv: String,
275
276    /// 影视 - 电影 重命名格式
277    /// ```text
278    /// https://emby.media/support/articles/Movie-Naming.html
279    ///
280    /// \Movies\Avatar (2009)\Avatar (2009).mkv
281    /// \Movies\Pulp Fiction (1994)\Pulp Fiction (1994).mp4
282    /// \Movies\Reservoir Dogs (1992)\Reservoir Dogs (1992).mp4
283    /// \Movies\The Usual Suspects (1995)\The Usual Suspects (1995).mkv
284    /// \Movies\Top Gun (1986)\Top Gun (1986).mp4
285    /// /Movies
286    /// /300 (2006)
287    /// /300 (2006)/300 (2006) - 1080p.mkv
288    /// /300 (2006)/300 (2006) - 4K.mkv
289    /// /300 (2006)/300 (2006) - 720p.mp4
290    /// /300 (2006)/300 (2006) - extended edition.mp4
291    /// /300 (2006)/300 (2006) - directors cut.mp4
292    /// /300 (2006)/300 (2006) - 3D.hsbs.mp4
293    /// ```
294    pub transfer_rename_format_movie: String,
295
296    /// 电影和电视剧重命名格式
297    pub rename_style: Option<RenameStyle>,
298}
299
300impl LibConfig {
301    pub fn update(&mut self, config: LibConfig) {
302        self.cache_path = config.cache_path;
303
304        //
305        self.strong_match_regex_rules_path = config.strong_match_regex_rules_path;
306        self.strong_match_regex_rules = config.strong_match_regex_rules;
307
308        //
309        self.strong_match_rules_tv_path = config.strong_match_rules_tv_path;
310        self.strong_match_rules_tv = config.strong_match_rules_tv;
311
312        //
313        self.strong_match_rules_movie_path = config.strong_match_rules_movie_path;
314        self.strong_match_rules_movie = config.strong_match_rules_movie;
315
316        //
317        self.strong_match_name_map_path = config.strong_match_name_map_path;
318        self.strong_match_name_map = config.strong_match_name_map;
319
320        //
321        self.transfer_rename_format_tv = config.transfer_rename_format_tv;
322        self.transfer_rename_format_movie = config.transfer_rename_format_movie;
323
324        //
325        self.metadata_skip_special = config.metadata_skip_special;
326
327        //
328        self.rename_style = config.rename_style;
329    }
330
331    pub fn new() -> LibConfig {
332        let current_path = std::env::current_dir().unwrap();
333        return LibConfig {
334            //
335            cache_path: current_path.join("cache").to_str().unwrap().to_string(),
336            // 
337            strong_match_rules_tv_path: current_path.join("config").join("mt_strong_match_rules_tv.json").to_str().unwrap().to_string(),
338            //
339            strong_match_rules_movie_path: current_path.join("config").join("mt_strong_match_rules_movie.json").to_str().unwrap().to_string(),
340            //
341            strong_match_regex_rules_path: current_path.join("config").join("mt_strong_match_regex_rules.json").to_str().unwrap().to_string(),
342            //
343            strong_match_name_map_path: current_path.join("config").join("mt_strong_match_name_map.json").to_str().unwrap().to_string(),
344            //
345            transfer_rename_format_tv: "$title_cn$.$title_en$.$release_year$/$title_cn$.$title_en$.$year$.$season$.$resolution$.$source$.$video_codec$.$audio_codec$/$title_cn$.$title_en$.$year$.$season$$episode$.$resolution$.$source$.$video_codec$.$audio_codec$.$extension$".to_string(),
346            transfer_rename_format_movie: "$title_cn$.$title_en$.$year$.$resolution$.$source$.$video_codec$.$audio_codec$/$title_cn$.$title_en$.$year$.$resolution$.$source$.$video_codec$.$audio_codec$.$extension$".to_string(),
347            metadata_skip_special: false,
348            strong_match_rules_tv: "".to_string(),
349            strong_match_rules_movie: "".to_string(),
350            strong_match_regex_rules: "".to_string(),
351            strong_match_name_map: "".to_string(),
352            rename_style: None,
353            tmdb_api_key: "6f5b96d0d7253117c44963a0ce8aa6f2".to_string(),
354        };
355    }
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct ScrapeConfig {
360    /// 是否将刮削信息写入文件,nfo、image
361    pub enable_scrape_write: bool,
362    /// 是否刮削图片,从网络获取图片
363    pub enable_scrape_image: bool,
364    /// 是否识别媒体资源
365    pub enable_recognize: bool,
366}
367
368impl ScrapeConfig {
369    pub fn new() -> ScrapeConfig {
370        return ScrapeConfig {
371            enable_scrape_image: true,
372            enable_recognize: true,
373            enable_scrape_write: true,
374        };
375    }
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
379pub enum MTInfo {
380    MOVIE(MovieType),
381    TV(TVType),
382}
383
384impl MTInfo {
385    pub(crate) fn new_movie(tmdb_movie: TmdbMovie) -> MTInfo {
386        MTInfo::MOVIE(MovieType::TMDB(TmdbMovieInfo::new(tmdb_movie)))
387    }
388
389    pub(crate) fn new_tv(tmdb_tv: TmdbTV) -> MTInfo {
390        MTInfo::TV(TVType::TMDB(TmdbTVInfo::new(tmdb_tv)))
391    }
392
393    pub(crate) fn title(&self) -> &str {
394        match self {
395            MTInfo::MOVIE(movie) => match movie {
396                MovieType::TMDB(movie) => movie.movie.name(),
397            },
398            MTInfo::TV(tv) => match tv {
399                TVType::TMDB(tv) => tv.tv.name(),
400            },
401        }
402    }
403
404    pub(crate) fn original_title(&self) -> &str {
405        match self {
406            MTInfo::MOVIE(movie) => match movie {
407                MovieType::TMDB(movie) => "",
408            },
409            MTInfo::TV(tv) => match tv {
410                TVType::TMDB(tv) => tv.tv.original_name(),
411            },
412        }
413    }
414
415    /// TMDB ID
416    pub(crate) fn tmdb_id(&self) -> i64 {
417        match self {
418            MTInfo::MOVIE(movie) => match movie {
419                MovieType::TMDB(movie) => movie.movie.id.clone(),
420            },
421            MTInfo::TV(tv) => match tv {
422                TVType::TMDB(tv) => tv.tv.id.clone(),
423            },
424        }
425    }
426
427    pub(crate) fn tvdb_id(&self) -> Option<String> {
428        match self {
429            MTInfo::TV(tv) => match tv {
430                TVType::TMDB(tv) => {
431                    return tv.tv.tvdb_id();
432                }
433            },
434            MTInfo::MOVIE(movie) => match movie {
435                MovieType::TMDB(movie) => {
436                    return movie.movie.tvdb_id();
437                }
438            },
439        }
440    }
441
442    pub(crate) fn imdb_id(&self) -> Option<&str> {
443        match self {
444            MTInfo::MOVIE(movie) => match movie {
445                MovieType::TMDB(movie) => {
446                    return movie.movie.imdb_id();
447                }
448            },
449            MTInfo::TV(tv) => match tv {
450                TVType::TMDB(tv) => {
451                    return tv.tv.imdb_id();
452                }
453            },
454        }
455    }
456
457    pub(crate) fn insert_tv_season_episode(&mut self, season_number: i64, episode_number: i64, tmdb_episode: TmdbEpisode) {
458        match self {
459            MTInfo::TV(tv) => match tv {
460                TVType::TMDB(tv) => match tv.tv_seasons.get_mut(&season_number) {
461                    Some(season_info) => {
462                        season_info.tv_episodes.insert(episode_number, tmdb_episode);
463                    }
464                    None => {
465                        tracing::error!("tv_seasons not found season_number = {}", season_number);
466                    }
467                },
468            },
469            _ => {}
470        }
471    }
472
473    pub(crate) fn insert_tv_season(&mut self, season_number: i64, tmdb_season: TmdbSeason) {
474        match self {
475            MTInfo::TV(tv) => match tv {
476                TVType::TMDB(tv) => {
477                    tv.tv_seasons.insert(season_number, TmdbSeasonInfo::new(tmdb_season));
478                }
479            },
480            _ => {}
481        }
482    }
483
484    pub(crate) fn tmdb_id_str(&self) -> String {
485        match self {
486            MTInfo::MOVIE(movie) => match movie {
487                MovieType::TMDB(movie) => "".to_string(),
488            },
489            MTInfo::TV(tv) => match tv {
490                TVType::TMDB(tv) => tv.tv.id.to_string(),
491            },
492        }
493    }
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub enum MovieType {
498    TMDB(TmdbMovieInfo),
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
502pub enum TVType {
503    TMDB(TmdbTVInfo),
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize)]
507pub enum MTType {
508    MOVIE,
509    TV,
510}
511
512/// 资源类型
513#[derive(Debug, Clone)]
514pub enum ResourceType {
515    /// 影视 MT
516    /// Movie or TV
517    MT,
518}
519
520impl std::fmt::Display for ResourceType {
521    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
522        let s = match self {
523            ResourceType::MT => "mt",
524        };
525        s.fmt(f)
526    }
527}
528impl std::str::FromStr for ResourceType {
529    type Err = String;
530
531    fn from_str(s: &str) -> Result<Self, Self::Err> {
532        match s {
533            "mt" => Ok(Self::MT),
534            _ => Err(format!("Unknown type: {s}")),
535        }
536    }
537}
538
539/// 文件名识别上下文,用于实现一些能力
540#[derive(Serialize, Deserialize, Debug, Clone)]
541pub struct MetaContext {
542    // 父刮削的路径
543    pub parent_path: String,
544    // 当前刮削的路径
545    pub cur_path: String,
546    // 上一次刮削的路径
547    pub last_path: String,
548    // 当前刮削的文件名
549    pub cur_file_name: String,
550    // 当前刮削的规则
551    pub cur_rule: String,
552    // 是否出错
553    pub error: bool,
554    // 当前刮削的输入
555    pub input: String,
556    // 最后一次使用的解析规则
557    pub last_rule: Option<Rule>,
558}
559
560impl MetaContext {
561    pub fn new() -> MetaContext {
562        return MetaContext {
563            cur_path: "".to_string(),
564            cur_file_name: "".to_string(),
565            cur_rule: "".to_string(),
566            last_path: "".to_string(),
567            parent_path: "".to_string(),
568            input: "".to_string(),
569            error: false,
570            last_rule: None,
571        };
572    }
573
574    // 同一个父路径可以复用上一次的规则
575    pub fn enable_cache(&mut self) -> bool {
576        // todo
577        // 同一个父路径可以复用上一次的规则
578        if let Some(parent) = Path::new(&self.cur_path).parent() {
579            let parent_path = parent.to_str().unwrap();
580            let enable_cache = if parent_path.is_empty() {
581                false
582            } else {
583                if self.last_path.is_empty() {
584                    self.last_path = self.cur_path.clone();
585                    false
586                } else {
587                    let last_parent_path = Path::new(&self.last_path).parent().unwrap().to_str().unwrap();
588                    if last_parent_path == parent_path {
589                        tracing::debug!(
590                            "build_mt_file_tokens parent_path = {} last_parent_path = {}",
591                            parent_path,
592                            last_parent_path
593                        );
594                        true
595                    } else {
596                        self.last_path = self.cur_path.clone();
597                        false
598                    }
599                }
600            };
601            return enable_cache;
602        }
603        return false;
604    }
605
606    pub(crate) fn reset(&mut self) {
607        self.cur_path = "".to_string();
608        self.cur_file_name = "".to_string();
609        self.cur_rule = "".to_string();
610        self.last_path = "".to_string();
611        self.parent_path = "".to_string();
612        self.error = false;
613    }
614
615    pub fn init(&mut self, src_path: &str) -> bool {
616        self.reset();
617
618        let path = Path::new(&src_path);
619        let parent_path = path.parent().unwrap().to_str().unwrap().to_string();
620
621        // 检查是否有错误,如果有错误,那么跳过
622        if !self.parent_path.is_empty() && parent_path == self.parent_path && self.error {
623            tracing::info!(target: "soda::info","刮削失败,跳过: {}", src_path);
624            return false;
625        } else {
626            self.error = false;
627        }
628
629        // 更新刮削上下文
630        self.parent_path = parent_path;
631        self.cur_path = src_path.to_string();
632        self.cur_file_name = path.file_name().unwrap().to_str().unwrap().to_string();
633
634        return true;
635    }
636}
637
638/// The metadata parsed from the soda file name.
639///
640/// eg: 凡人修仙传.The.Mortal.Ascention.2020.S01E01.2160p.WEB-DL.H264.AAC-OurTV.mp4
641///
642#[derive(Serialize, Deserialize, Debug, Clone)]
643pub struct MTMetadata {
644    /// origin_title
645    ///
646    /// eg: 凡人修仙传.The.Mortal.Ascention.2020.S01E01.2160p.WEB-DL.H264.AAC-OurTV.mp4
647    ///
648    pub origin_title: String,
649
650    /// chinese title
651    ///
652    /// eg: 凡人修仙传
653    ///a
654    pub title_cn: String,
655
656    /// english title aka
657    ///
658    /// Sseo-ni.AKA.Sunny
659    ///
660    pub aka_title_en: String,
661
662    /// aka part 1
663    pub aka_title_en_first: String,
664
665    /// aka part 2
666    pub aka_title_en_second: String,
667
668    /// english title
669    ///
670    /// The.Mortal.Ascention
671    ///
672    pub title_en: String,
673
674    /// year
675    ///
676    /// 2020
677    ///
678    pub year: String,
679
680    /// 发布年,TMDB补充
681    pub release_year: Option<String>,
682
683    /// season
684    ///
685    /// eg: S01
686    ///
687    /// "S01E01" is a common format used to represent the season and episode number of a television show or series.
688    /// Here's the breakdown of its meaning:
689    /// - "S01": This indicates Season 1 of the show. It represents the specific season or series of episodes that the particular episode belongs to.
690    /// - "E01": This indicates Episode 1 within the specified season. It represents the sequential order of the episode within the season.
691    ///
692    pub season: String,
693
694    /// episode
695    ///
696    /// eg: E01
697    ///
698    pub episode: String,
699
700    /// resolution
701    ///
702    /// eg: 2160p
703    ///
704    /// "1080P" refers to a video resolution commonly known as Full HD or 1080p. The "1080" represents the vertical resolution of the video, which is 1080 pixels. The "P" stands for progressive scan, indicating that the video is displayed progressively line by line.
705    ///
706    /// Other similar text that indicates video resolutions include:
707    /// - 720P: This refers to a video resolution of 1280x720 pixels, also known as HD or 720p.
708    /// - 4K: This refers to a video resolution of approximately 3840x2160 pixels, also known as Ultra HD or 4K UHD.
709    /// - 8K: This refers to a video resolution of approximately 7680x4320 pixels, providing even higher resolution than 4K.
710    /// - all resolution text:
711    ///     - 240p: 426 x 240 pixels
712    ///     - 360p: 640 x 360 pixels
713    ///     - 480p: 854 x 480 pixels
714    ///     - 720p: 1280 x 720 pixels
715    ///     - 1080p: 1920 x 1080 pixels
716    ///     - 1440p (2K): 2560 x 1440 pixels
717    ///     - 2160p (4K UHD): 3840 x 2160 pixels
718    ///     - 4320p (8K UHD): 7680 x 4320 pixels
719    ///
720    /// what is  the meaning of UHD
721    /// UHD stands for "Ultra High Definition." It is a video resolution standard that provides a higher pixel count and improved image quality compared to traditional high-definition (HD) resolutions. UHD typically refers to a resolution of 3840 × 2160 pixels, commonly known as 4K UHD. This higher resolution allows for more detailed and sharper images, providing a more immersive viewing experience for video content.
722    ///
723    /// These terms are used to describe the clarity and detail of the video image, with higher numbers indicating higher resolutions and finer details.
724    ///
725    pub resolution: String,
726
727    /// source
728    ///
729    /// eg: WEB-DL/AMZN WEB-DL
730    ///
731    /// "WEB-DL" stands for Web Download or Web-Digital Copy. It refers to a video file that has been obtained by directly downloading it from an online source, typically a streaming platform or a digital distribution service. WEB-DL files are often of high quality and can be in various video formats, such as MP4 or MKV, and may include audio codecs like AAC or AC3.
732    ///
733    /// Other common video formats that you may come across include:
734    ///
735    /// - BluRay or BD: Refers to a video sourced from a Blu-ray disc, known for its high-quality video and audio.
736    /// - DVDRip: Refers to a video that has been ripped or copied from a DVD source.
737    /// - HDTVRip: Similar to HDTV, it represents a video that has been ripped or captured from an HDTV broadcast.
738    /// - BRRip/BDRip: These terms indicate a video that has been ripped or copied from a Blu-ray disc source, similar to BluRay.
739    /// - HDRip: Refers to a video that has been ripped or captured from a source with HDR (High Dynamic Range) content, offering enhanced contrast and color.
740    /// - CAM: Represents a video recorded using a handheld camera in a theater during a movie screening. The quality is generally lower in CAM recordings.
741    /// - HDTV: Stands for High Definition Television, indicating that the video has been captured or broadcasted in high-definition format.
742    ///
743    /// - REMUX: "REMUX" is a term commonly used in the video industry, referring to the process of remultiplexing or remuxing video and audio streams without re-encoding them. In other words, it involves extracting the video and audio streams from the original source and repackaging them into a new container file without any compression or re-encoding. This helps to preserve the original quality and characteristics of the video and audio while reducing file size.
744    ///     - The purpose of REMUX is typically to create compressed versions while maintaining high quality from high-definition sources, such as Blu-ray. By remuxing, the video and audio can be preserved with minimal loss in quality compared to re-encoding.
745    ///     - It's important to note that REMUX is not a specific video encoding format but rather a process of remultiplexing or remuxing video and audio streams. Therefore, the file format of a REMUXed file can vary and depends on the container format used for repackaging, such as MKV, MP4, etc.
746    ///
747    /// - BluRay.Remux: "BluRay.Remux" refers to a specific type of video release. Let's break down the components:
748    ///     - "BluRay": This indicates that the source of the video is a Blu-ray disc, which is a high-definition optical disc format.
749    ///     - "Remux": It stands for "remultiplexing" and refers to a process where the video and audio streams from the original Blu-ray disc are extracted and then combined into a new container format, without any re-encoding. The purpose of a remux is to preserve the original video and audio quality while reducing the file size by removing unnecessary data.
750    ///
751    /// - BDRemux refers to a type of video file that has been created by remuxing (remultiplexing) the content from a Blu-ray Disc (BD) without any loss of quality. It is a process that involves extracting the original video, audio, and subtitle streams from a Blu-ray Disc and then packaging them into a new container format, typically in an MKV (Matroska) or M2TS (MPEG-2 Transport Stream) format.
752    ///     - The term "BDRemux" indicates that the video file retains the original video and audio streams directly from the Blu-ray source, without re-encoding or compressing the content. As a result, BDRemux files provide the highest quality available, preserving the original video and audio fidelity of the Blu-ray Disc.
753    ///     - BDRemux files are typically large in size because they maintain the original video and audio bitrates and quality. They are preferred by enthusiasts and collectors who desire the best possible video and audio experience from Blu-ray content.
754    ///     - It's worth noting that BDRemux files require compatible soda players or devices that can handle the specific container format and codecs used in the remuxed file.
755    ///
756    /// It's important to note that the availability and usage of these formats may vary, and some may be more common in certain contexts or regions.
757    ///
758    pub source: String,
759
760    ///
761    /// format
762    ///
763    /// eg: .mp4
764    ///
765    /// ".mp4" is a file extension that indicates the video is encoded in the MPEG-4 Part 14 format. MPEG-4 is a widely used video compression standard that provides efficient video encoding and is compatible with various devices and platforms. MP4 files can contain both video and audio data.
766    ///
767    /// Here are some common video format suffixes (file extensions) and their corresponding formats:
768    ///
769    /// - .mp4: MPEG-4 Part 14 video format
770    /// - .avi: Audio Video Interleave format
771    /// - .mkv: Matroska multimedia container format
772    /// - .mov: QuickTime movie format
773    /// - .wmv: Windows Media Video format
774    /// - .flv: Flash Video format
775    /// - .webm: WebM multimedia container format
776    /// - .m4v: MPEG-4 video format (similar to .mp4)
777    /// - .3gp: 3GPP multimedia format (commonly used for mobile devices)
778    /// - .m2ts: MPEG-2 Transport Stream format (often used for Blu-ray Discs)
779    /// - .ogv: Ogg Video format
780    /// - .rmvb: RealMedia Variable Bitrate format
781    ///
782    /// These are just a few examples, and there are many more video formats available. The choice of video format depends on factors such as compatibility, quality, and intended use.
783    ///
784    pub extension: String,
785
786    /// video_codec
787    ///
788    /// eg: H264/H265/x264
789    ///
790    /// H.264, also known as AVC (Advanced Video Coding), is a widely used video compression standard. It is a popular video codec that efficiently compresses video data while maintaining good visual quality. H.264 is supported by a wide range of devices and platforms, making it suitable for various applications such as streaming, video conferencing, and video storage.
791    ///
792    /// Here are some commonly used video encoder formats:
793    ///
794    /// - H.264/AVC: Advanced Video Coding, widely used for high-quality video compression.
795    /// - H.265/HEVC: High-Efficiency Video Coding, a successor to H.264, providing better compression efficiency and improved video quality.
796    /// - VP9: Developed by Google, a video codec designed to provide high-quality video compression with better performance than older codecs like H.264.
797    /// - AV1: A royalty-free video codec developed by the Alliance for Open Media (AOMedia), designed to provide efficient compression and high video quality.
798    /// - MPEG-2: An older video compression standard commonly used for DVD video and broadcast television.
799    /// - MPEG-4: A versatile video compression standard that includes various codecs such as MPEG-4 Part 2 (DivX, Xvid) and MPEG-4 Part 10 (AVC/H.264).
800    /// - VC-1: A video codec developed by Microsoft, used in formats like Blu-ray and Windows Media Video (WMV).
801    ///
802    /// These are some of the commonly used video encoder formats, each with its own characteristics, compression efficiency, and compatibility with different devices and platforms. The choice of encoder format depends on factors such as video quality requirements, device support, and intended usage.
803    pub video_codec: String,
804
805    /// audio_codec
806    ///
807    /// eg: AAC/Atmos.DDP5.1/DTS.2Audios/DD+2.0(Dolby Digital Plus 2.0)
808    ///
809    /// AAC stands for Advanced Audio Coding. It is a widely used audio encoding format that provides high-quality audio compression. AAC is known for its efficiency in compressing audio data while maintaining good sound quality.
810    ///
811    /// Here are some commonly used audio encoder formats:
812    ///
813    /// - MP3 (MPEG-1 Audio Layer 3): One of the most popular audio encoding formats, known for its widespread compatibility and good balance between file size and audio quality.
814    /// - AAC (Advanced Audio Coding): Designed to offer better sound quality than MP3 at similar bit rates. It is commonly used for audio streaming, online music services, and various multimedia applications.
815    /// - AC3 (Dolby Digital): Developed by Dolby Laboratories, AC3 is a widely used audio coding format for surround sound in movies, DVDs, and digital broadcasting.
816    /// - FLAC (Free Lossless Audio Codec): A lossless audio compression format that allows for bit-perfect audio reproduction while reducing file size without any loss in quality. FLAC files are often used for high-quality audio archiving and playback.
817    /// - Opus: A highly versatile and efficient audio codec suitable for a wide range of applications, including real-time communication, music streaming, and internet telephony.
818    /// - DTS (Digital Theater Systems): A popular audio codec used for surround sound in home theater systems and Blu-ray discs.
819    /// - PCM (Pulse Code Modulation): Represents uncompressed audio in raw form, without any compression or encoding. PCM is commonly used in audio CDs and as an intermediate format for audio processing.
820    ///
821    /// These are some of the commonly used audio encoder formats, each with its own characteristics, compression techniques, and applications. The choice of audio format depends on factors such as desired audio quality, file size considerations, and compatibility with playback devices.
822    ///
823    /// special format: AVC.FLAC2.0
824    /// - AVC.FLAC2.0 refers to the video and audio formats used in a soda file.
825    /// - AVC stands for Advanced Video Coding, which is commonly known as H.264. It is a video compression standard widely used for high-quality video encoding. AVC provides efficient compression while maintaining good video quality.
826    /// - FLAC stands for Free Lossless Audio Codec. It is an audio compression format that allows for lossless compression, meaning the audio can be compressed without any loss in quality. FLAC is known for its high audio fidelity and is often used for archiving or preserving audio quality.
827    /// - 2.0 refers to the audio channels or audio configuration. In this case, 2.0 indicates stereo audio, which means there are two audio channels: left and right.
828    /// - So, AVC.FLAC2.0 signifies that the video in the soda file is encoded using AVC (H.264) video compression, and the audio is encoded in FLAC format with stereo (2.0) audio channels.
829    ///
830    pub audio_codec: String,
831
832    /// source_or_group
833    ///
834    /// eg: OurTV/PTerWEB/lolice-mix/7³ACG@OurBits
835    ///
836    ///
837    pub release_group: String,
838
839    /// 特典
840    /// IT狂人说明书(幕后特辑).The.IT.Crowd.Manual.2014.720p.HDTV.x265.AC3£cXcY@FRDS
841    /// The.IT.Crowd.Manual.2014.720p.HDTV.x265.10bit.AC3£cXcY@FRDS.mkv
842    pub special: String,
843}
844
845impl MTMetadata {
846    pub fn empty(title: &str) -> MTMetadata {
847        return MTMetadata {
848            origin_title: title.to_string(),
849            title_cn: "".to_string(),
850            title_en: "".to_string(),
851            year: "".to_string(),
852            season: "".to_string(),
853            episode: "".to_string(),
854            resolution: "".to_string(),
855            source: "".to_string(),
856            extension: "".to_string(),
857            video_codec: "".to_string(),
858            audio_codec: "".to_string(),
859            release_group: "".to_string(),
860            special: "".to_string(),
861            release_year: None,
862            aka_title_en: "".to_string(),
863            aka_title_en_first: "".to_string(),
864            aka_title_en_second: "".to_string(),
865        };
866    }
867
868    pub fn is_movie(&self) -> bool {
869        return self.season.is_empty() && self.episode.is_empty();
870    }
871
872    pub fn is_tv(&self) -> bool {
873        return !self.is_movie();
874    }
875
876    pub fn is_empty(&self) -> bool {
877        return self.title_cn.is_empty()
878            && self.title_en.is_empty()
879            && self.year.is_empty()
880            && self.season.is_empty()
881            && self.episode.is_empty()
882            && self.resolution.is_empty()
883            && self.source.is_empty()
884            && self.extension.is_empty()
885            && self.video_codec.is_empty()
886            && self.audio_codec.is_empty()
887            && self.release_group.is_empty();
888    }
889
890    pub fn title(&self) -> &str {
891        return if self.title_cn.is_empty() { &self.title_en } else { &self.title_cn };
892    }
893
894    pub fn episode_number_format(&self) -> String {
895        if self.episode.is_empty() {
896            return "".to_string();
897        }
898        return format!("E{:02}", self.episode_number().unwrap_or(0));
899    }
900
901    pub fn season_number_format(&self) -> String {
902        if self.season.is_empty() {
903            return "".to_string();
904        }
905        return format!("S{:02}", self.season_number().unwrap_or(0));
906    }
907
908    pub fn season_number(&self) -> Option<i64> {
909        if self.season.is_empty() {
910            return None;
911        }
912        let season_number = self
913            .season
914            .split("S")
915            .collect::<Vec<&str>>()
916            .get(1)
917            .unwrap()
918            .to_string()
919            .parse::<i64>()
920            .expect("season number parse error");
921        return Some(season_number);
922    }
923
924    pub(crate) fn episode_number(&self) -> Option<i64> {
925        if self.episode.is_empty() {
926            return None;
927        }
928        let episode_number = self
929            .episode
930            .split("E")
931            .collect::<Vec<&str>>()
932            .get(1)
933            .unwrap()
934            .to_string()
935            .parse::<i64>()
936            .expect("episode number parse error");
937        return Some(episode_number);
938    }
939
940    pub(crate) fn merge_movie(&mut self, info: &mut MTInfo) {
941        if let MTInfo::MOVIE(movie) = info {
942            if let MovieType::TMDB(movie) = movie {
943                if self.is_movie() {
944                    // 如果没有中文名,则合并中文名
945                    if self.title_cn.is_empty() && !movie.movie.name().is_empty() {
946                        let new_title = movie.movie.name().to_string();
947
948                        // 新标题不等于英文名才合并
949                        if new_title != self.title_en {
950                            if contains_invalid_chars(&new_title) {
951                                tracing::debug!("invalid title_cn, title = {}", new_title);
952                            } else {
953                                tracing::debug!("merge title_cn, old_title = {} new_title = {}", self.title_cn, new_title);
954                                self.title_cn = new_title;
955                            }
956                        }
957                    }
958
959                    // 如果没有英文名,则合并英文名
960                    if self.title_en.is_empty() && !movie.movie.original_name().is_empty() {
961                        let new_title = movie.movie.original_name().to_string();
962
963                        // 新标题不等于中文名才合并
964                        if new_title != self.title_cn {
965                            tracing::debug!("merge title_en, old_title = {} new_title = {}", self.title_en, new_title);
966                            self.title_en = new_title;
967                        }
968                    }
969
970                    // 补充发布年
971                    if self.release_year.is_none() && !movie.movie.first_air_date().is_empty() && movie.movie.first_air_date().len() > 4 {
972                        self.release_year = Some(movie.movie.first_air_date()[0..4].to_string());
973                        tracing::debug!(
974                            "merge release_year, release_year = {:?} first_air_date = {:?}",
975                            self.release_year,
976                            movie.movie.first_air_date()
977                        );
978                    }
979
980                    // 如果英文名不一致,但是小写是一直的,那么合并英文名
981                    // 锻刀大赛.Forged.in.Fire.2022.S09 == 锻刀大赛.forged.in.fire.2022.S09
982                    if !self.title_en.is_empty()
983                        && !movie.movie.original_name().is_empty()
984                        && self.title_en.to_lowercase() == movie.movie.original_name().to_lowercase()
985                        && self.title_en != movie.movie.original_name()
986                    {
987                        tracing::debug!(
988                            "merge title_en, old_title = {} new_title = {}",
989                            self.title_en,
990                            movie.movie.original_name()
991                        );
992                        self.title_en = movie.movie.original_name().to_string();
993                    }
994                }
995            }
996        }
997    }
998
999    pub(crate) fn merge_tv(&mut self, info: &mut MTInfo) {
1000        if let MTInfo::TV(tv) = info {
1001            if let TVType::TMDB(tv) = tv {
1002                if self.is_tv() {
1003                    // 如果季相同则更新年份
1004                    if let Some(seasons) = &tv.tv.seasons {
1005                        for season in seasons {
1006                            if season.season_number() == self.season_number().unwrap_or(0).to_string()
1007                                && !self.year.is_empty()
1008                                && !season.air_date().is_empty()
1009                                && season.air_date().len() > 4
1010                            {
1011                                let year: String = self.year.to_string();
1012                                let season_release_year = season.air_date()[0..4].to_string();
1013                                if year != season_release_year {
1014                                    self.year = season_release_year;
1015                                    tracing::debug!("merge year, old_year = {} new_year = {}", year, self.year);
1016                                }
1017                            }
1018                        }
1019                    }
1020
1021                    // 如果有集无季需要补充季信息
1022                    if self.season.is_empty() && !self.episode.is_empty() {
1023                        if let Some(seasons) = &tv.tv.seasons {
1024                            for season in seasons {
1025                                // 有年有季的信息
1026                                if !self.year.is_empty() && !season.air_date().is_empty() && season.air_date().len() > 4 {
1027                                    let year = self.year.to_string();
1028                                    let season_release_year = season.air_date()[0..4].to_string();
1029                                    if year == season_release_year {
1030                                        if let Some(season_number) = season.season_number {
1031                                            self.season = format!("S{:02}", season_number);
1032                                            tracing::debug!("merge season info, name = {} season = {}", self.origin_title, self.season);
1033                                            break;
1034                                        }
1035                                    }
1036                                }
1037                                // 无年有季的信息
1038                                else if self.year.is_empty() && !season.air_date().is_empty() && season.air_date().len() > 4 {
1039                                    if let Some(season_number) = season.season_number {
1040                                        self.season = format!("S{:02}", season_number);
1041                                        tracing::debug!("merge season info, name = {} season = {}", self.origin_title, self.season);
1042                                        break;
1043                                    }
1044                                }
1045                            }
1046                        }
1047                    }
1048
1049                    // 如果没有中文名,则合并中文名
1050                    if self.title_cn.is_empty() && !tv.tv.name().is_empty() {
1051                        let new_title = tv.tv.name().to_string();
1052
1053                        // 新标题不等于英文名才合并
1054                        if new_title != self.title_en {
1055                            if contains_invalid_chars(&new_title) {
1056                                tracing::debug!("invalid title_cn, title = {}", new_title);
1057                            } else {
1058                                tracing::debug!("merge title_cn, old_title = {} new_title = {}", self.title_cn, new_title);
1059                                self.title_cn = new_title;
1060                            }
1061                        }
1062                    }
1063
1064                    // 如果没有英文名,则合并英文名
1065                    if self.title_en.is_empty() && !tv.tv.original_name().is_empty() {
1066                        let new_title = tv.tv.original_name().to_string();
1067
1068                        // 新标题不等于中文名才合并
1069                        if new_title != self.title_cn {
1070                            tracing::debug!("merge title_en, old_title = {} new_title = {}", self.title_en, new_title);
1071                            self.title_en = new_title;
1072                        }
1073                    }
1074
1075                    // 补充发布年
1076                    if self.release_year.is_none() && !tv.tv.first_air_date().is_empty() && tv.tv.first_air_date().len() > 4 {
1077                        self.release_year = Some(tv.tv.first_air_date()[0..4].to_string());
1078                        tracing::debug!(
1079                            "merge release_year, release_year = {:?} first_air_date = {:?}",
1080                            self.release_year,
1081                            tv.tv.first_air_date()
1082                        );
1083                    }
1084
1085                    // 如果英文名不一致,但是小写是一直的,那么合并英文名
1086                    // 锻刀大赛.Forged.in.Fire.2022.S09 == 锻刀大赛.forged.in.fire.2022.S09
1087                    if !self.title_en.is_empty()
1088                        && !tv.tv.original_name().is_empty()
1089                        && self.title_en.to_lowercase() == tv.tv.original_name().to_lowercase()
1090                        && self.title_en != tv.tv.original_name()
1091                    {
1092                        tracing::debug!("merge title_en, old_title = {} new_title = {}", self.title_en, tv.tv.original_name());
1093                        self.title_en = tv.tv.original_name().to_string();
1094                    }
1095                }
1096            }
1097        }
1098    }
1099
1100    pub(crate) fn merge_season(&mut self, season_detail: &TmdbSeason) {
1101        // 如果季没有年份则合并一下年份
1102        if self.year.is_empty() && season_detail.air_date().len() > 4 {
1103            self.year = season_detail.air_date()[0..4].to_string();
1104        }
1105    }
1106}
1107
1108fn contains_invalid_chars(s: &str) -> bool {
1109    let invalid_chars = ['<', '>', ':', '"', '|', '?', '*'];
1110    s.chars().any(|c| invalid_chars.contains(&c))
1111}
1112
1113#[derive(Debug, Clone)]
1114pub enum TransferType {
1115    /// what is meaning of HardLink?
1116    ///
1117    /// A hard link is a file system feature that allows multiple file entries (directory entries) to point to the same underlying data on disk. In other words, it creates multiple references to the same file. Each hard link is essentially a directory entry that points directly to the inode (index node) of the file on the file system.
1118    ///
1119    /// Unlike symbolic links (soft links), hard links are not separate files that store the path to the original file. Instead, all hard links are equal and point to the same data on disk. Any changes made to the file content or metadata through one hard link are immediately visible through all other hard links pointing to the same file.
1120    ///
1121    /// From the perspective of the file system, there is no distinction between the original file and its hard links. They all share the same file data and are considered equivalent references to that data. If any hard link is deleted, the file data remains intact as long as at least one hard link still exists.
1122    ///
1123    /// It's worth noting that hard links can only be created within the same file system, as they rely on the same inode structure.
1124    HardLink,
1125    /// what is meaning of soft link?
1126    /// A symbolic link, also known as a soft link or symlink, is a special type of file that acts as a pointer or reference to another file or directory. Unlike a hard link, a symbolic link is a separate file that contains the path to the target file or directory.
1127    ///
1128    /// When you access a symbolic link, the operating system transparently redirects you to the target file or directory. It essentially provides an indirect way to access a file or directory, even if it is located in a different location or has a different name.
1129    ///
1130    /// Symbolic links are commonly used for various purposes, such as creating shortcuts, organizing file systems, and providing flexibility in file and directory management. They can be created across different file systems and even across networked machines.
1131    ///
1132    /// One important characteristic of symbolic links is that they continue to work even if the target file or directory is moved or renamed. This can be both advantageous and potentially risky, as deleting or moving a target file or directory may result in a broken symbolic link.
1133    ///
1134    /// In summary, symbolic links provide a flexible way to reference files or directories by creating a separate file that points to the target. They allow for easy management and management of files and directories within a file system.
1135    SymbolLink,
1136    /// 复制
1137    Copy,
1138    /// 移动
1139    Move,
1140}
1141
1142impl std::fmt::Display for TransferType {
1143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1144        let s = match self {
1145            TransferType::HardLink => "hard_link",
1146            TransferType::SymbolLink => "symbol_link",
1147            TransferType::Copy => "copy",
1148            TransferType::Move => "move",
1149        };
1150        s.fmt(f)
1151    }
1152}
1153
1154impl std::str::FromStr for TransferType {
1155    type Err = String;
1156
1157    fn from_str(s: &str) -> Result<Self, Self::Err> {
1158        match s {
1159            "hard_link" => Ok(Self::HardLink),
1160            "symbol_link" => Ok(Self::SymbolLink),
1161            "copy" => Ok(Self::Copy),
1162            "move" => Ok(Self::Move),
1163            _ => Err(format!("Unknown type: {s}")),
1164        }
1165    }
1166}
1167
1168#[derive(Debug, Clone)]
1169pub struct Token {
1170    pub tokens: HashMap<String, String>,
1171}
1172
1173impl Token {
1174    pub fn new() -> Token {
1175        return Token { tokens: HashMap::new() };
1176    }
1177}
1178
1179#[cfg(test)]
1180mod entity_tests {}
1181
1182#[derive(Serialize, Deserialize, Debug, Clone)]
1183pub struct Rule {
1184    pub rule: String,
1185    pub replaces: Option<Vec<RuleReplaces>>,
1186    pub regex_replaces: Option<Vec<RuleReplaces>>,
1187}
1188
1189impl Rule {
1190    pub fn update(&mut self, rule: Rule) {
1191        self.rule = rule.rule;
1192        self.replaces = rule.replaces;
1193    }
1194}
1195
1196#[derive(Serialize, Deserialize, Debug, Clone)]
1197pub struct RuleReplaces {
1198    pub src: String,
1199    pub target: String,
1200}
1201
1202#[derive(Serialize, Deserialize, Debug, Clone)]
1203pub struct MatchRule {
1204    pub before_replaces: Option<Vec<RuleReplaces>>,
1205    pub rules: Vec<Rule>,
1206}
1207
1208#[derive(Serialize, Deserialize, Debug, Clone)]
1209pub struct RegexRule {
1210    // pub original_title_en: Vec<String>,
1211    pub edition: Vec<String>,
1212    pub version: Vec<String>,
1213    pub resolution_cn: Vec<String>,
1214    pub episode_title_jp: Vec<String>,
1215    pub episode_title_cn: Vec<String>,
1216    pub episode_title_en: Vec<String>,
1217    pub title_number_en: Vec<String>,
1218    pub title_en: Vec<String>,
1219    pub title_number_cn: Vec<String>,
1220    pub season_title_cn: Vec<String>,
1221    pub title_cn: Vec<String>,
1222    pub subtitle_en: Vec<String>,
1223    pub country: Vec<String>,
1224    pub resolution: Vec<String>,
1225    pub source: Vec<String>,
1226    pub company: Vec<String>,
1227    pub video_codec: Vec<String>,
1228    pub color: Vec<String>,
1229    pub audio_codec: Vec<String>,
1230    pub release_group: Vec<String>,
1231}
1232
1233#[derive(Serialize, Deserialize, Debug, Clone)]
1234pub(crate) struct NameMap {
1235    pub(crate) src: NameInfo,
1236    pub(crate) target: NameInfo,
1237}
1238
1239#[derive(Serialize, Deserialize, Debug, Clone)]
1240pub(crate) struct NameInfo {
1241    pub(crate) title_cn: String,
1242    pub(crate) title_en: String,
1243    pub(crate) release_year: String,
1244}
1245
1246#[derive(Serialize, Deserialize, Debug, Clone)]
1247pub(crate) struct NamesMap {
1248    pub(crate) names: Vec<NameMap>,
1249}
1250
1251pub(crate) const KEY_ORIGIN_TITLE: &str = "origin_title";
1252pub(crate) const KEY_TITLE_CN: &str = "title_cn";
1253pub(crate) const KEY_TITLE_EN: &str = "title_en";
1254pub(crate) const KEY_AKA_TITLE_EN: &str = "aka_title_en";
1255pub(crate) const KEY_AKA_TITLE_EN_FIRST: &str = "aka_title_en_first";
1256pub(crate) const KEY_AKA_TITLE_EN_SECOND: &str = "aka_title_en_second";
1257pub(crate) const KEY_VIDEO_CODEC: &str = "video_codec";
1258pub(crate) const KEY_AUDIO_CODEC: &str = "audio_codec";
1259pub(crate) const KEY_SOURCE: &str = "source";
1260pub(crate) const KEY_RESOLUTION: &str = "resolution";
1261pub(crate) const KEY_YEAR: &str = "year";
1262pub(crate) const KEY_SEASON: &str = "season";
1263pub(crate) const KEY_EPISODE: &str = "episode";
1264pub(crate) const KEY_EPISODE_1: &str = "episode1";
1265pub(crate) const KEY_EPISODE_2: &str = "episode2";
1266pub(crate) const KEY_RELEASE_GROUP: &str = "release_group";
1267pub(crate) const KEY_SPECIAL: &str = "special";
1268pub(crate) const KEY_COMPANY: &str = "company";
1269pub(crate) const KEY_COLOR: &str = "color";
1270pub(crate) const KEY_EDITION: &str = "edition";
1271
1272pub(crate) const KEY_SEASON_TITLE_CN: &str = "season_title_cn";
1273pub(crate) const KEY_TITLE_NUMBER_CN: &str = "title_number_cn";
1274pub(crate) const KEY_TITLE_NUMBER_EN: &str = "title_number_en";
1275pub(crate) const KEY_EPISODE_TITLE_EN: &str = "episode_title_en";
1276pub(crate) const KEY_EPISODE_TITLE_CN: &str = "episode_title_cn";
1277pub(crate) const KEY_EPISODE_TITLE_JP: &str = "episode_title_jp";
1278pub(crate) const KEY_TITLE_YEAR: &str = "title_year";
1279pub(crate) const KEY_TEXT_CN: &str = "text_cn";
1280pub(crate) const KEY_YEAR_START_TO_END: &str = "year_start_to_end";
1281pub(crate) const KEY_YEAR_MONTH_DAY: &str = "year_month_day";
1282pub(crate) const KEY_SEASON_EPISODE: &str = "season_episode";
1283pub(crate) const KEY_SEASON_EPISODE_EPISODE: &str = "season_episode_episode";
1284pub(crate) const KEY_SEASON_NUMBER: &str = "season_number";
1285pub(crate) const KEY_EPISODE_NUMBER: &str = "episode_number";
1286pub(crate) const KEY_SEASON_CN: &str = "season_cn";
1287pub(crate) const KEY_EPISODE_CN: &str = "episode_cn";
1288pub(crate) const KEY_SEASON_ALL_CN: &str = "season_all_cn";
1289pub(crate) const KEY_SEASON_START_TO_END_CN: &str = "season_start_to_end_cn";
1290pub(crate) const KEY_SEASON_START_TO_END_EN: &str = "season_start_to_end_en";
1291pub(crate) const KEY_RESOLUTION_CN: &str = "resolution_cn";
1292pub(crate) const KEY_VERSION: &str = "version";
1293pub(crate) const KEY_SUBTITLE_EN: &str = "subtitle_en";
1294pub(crate) const KEY_SUBTITLE_CN: &str = "subtitle_cn";
1295pub(crate) const KEY_SUBTITLE: &str = "subtitle";
1296pub(crate) const KEY_AUDIO_CN: &str = "audio_cn";
1297pub(crate) const KEY_ANYTHING: &str = "anything";
1298pub(crate) const KEY_COUNTRY: &str = "country";
1299pub(crate) const KEY_MIX_NUMBERS_LETTERS: &str = "mix_numbers_letters";
1300pub(crate) const KEY_NUMBER: &str = "number";
1301pub(crate) const KEY_EXTENSION: &str = "extension";
1302pub(crate) const KEY_AKA: &str = "AKA";
1303
1304pub(crate) fn wrap(s: &str) -> String {
1305    return format!("${}$", s);
1306}