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,
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 EmbyMovie,
113
114 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 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 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 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 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 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 pub tmdb_api_key: String,
237
238 pub cache_path: String,
240
241 pub strong_match_rules_tv_path: String,
243 pub strong_match_rules_tv: String,
244
245 pub strong_match_rules_movie_path: String,
247 pub strong_match_rules_movie: String,
248
249 pub strong_match_regex_rules_path: String,
251 pub strong_match_regex_rules: String,
252
253 pub strong_match_name_map_path: String,
255 pub strong_match_name_map: String,
256
257 pub metadata_skip_special: bool,
259
260 pub transfer_rename_format_tv: String,
275
276 pub transfer_rename_format_movie: String,
295
296 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 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 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 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 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 self.transfer_rename_format_tv = config.transfer_rename_format_tv;
322 self.transfer_rename_format_movie = config.transfer_rename_format_movie;
323
324 self.metadata_skip_special = config.metadata_skip_special;
326
327 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 cache_path: current_path.join("cache").to_str().unwrap().to_string(),
336 strong_match_rules_tv_path: current_path.join("config").join("mt_strong_match_rules_tv.json").to_str().unwrap().to_string(),
338 strong_match_rules_movie_path: current_path.join("config").join("mt_strong_match_rules_movie.json").to_str().unwrap().to_string(),
340 strong_match_regex_rules_path: current_path.join("config").join("mt_strong_match_regex_rules.json").to_str().unwrap().to_string(),
342 strong_match_name_map_path: current_path.join("config").join("mt_strong_match_name_map.json").to_str().unwrap().to_string(),
344 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 pub enable_scrape_write: bool,
362 pub enable_scrape_image: bool,
364 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 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#[derive(Debug, Clone)]
514pub enum ResourceType {
515 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#[derive(Serialize, Deserialize, Debug, Clone)]
541pub struct MetaContext {
542 pub parent_path: String,
544 pub cur_path: String,
546 pub last_path: String,
548 pub cur_file_name: String,
550 pub cur_rule: String,
552 pub error: bool,
554 pub input: String,
556 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 pub fn enable_cache(&mut self) -> bool {
576 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 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 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#[derive(Serialize, Deserialize, Debug, Clone)]
643pub struct MTMetadata {
644 pub origin_title: String,
649
650 pub title_cn: String,
655
656 pub aka_title_en: String,
661
662 pub aka_title_en_first: String,
664
665 pub aka_title_en_second: String,
667
668 pub title_en: String,
673
674 pub year: String,
679
680 pub release_year: Option<String>,
682
683 pub season: String,
693
694 pub episode: String,
699
700 pub resolution: String,
726
727 pub source: String,
759
760 pub extension: String,
785
786 pub video_codec: String,
804
805 pub audio_codec: String,
831
832 pub release_group: String,
838
839 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 if self.title_cn.is_empty() && !movie.movie.name().is_empty() {
946 let new_title = movie.movie.name().to_string();
947
948 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 if self.title_en.is_empty() && !movie.movie.original_name().is_empty() {
961 let new_title = movie.movie.original_name().to_string();
962
963 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 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 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 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 if self.season.is_empty() && !self.episode.is_empty() {
1023 if let Some(seasons) = &tv.tv.seasons {
1024 for season in seasons {
1025 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 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 if self.title_cn.is_empty() && !tv.tv.name().is_empty() {
1051 let new_title = tv.tv.name().to_string();
1052
1053 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 if self.title_en.is_empty() && !tv.tv.original_name().is_empty() {
1066 let new_title = tv.tv.original_name().to_string();
1067
1068 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 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 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 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 HardLink,
1125 SymbolLink,
1136 Copy,
1138 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 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}