bilibili_extractor_lib/
combiner.rs1use crate::{
2 error::Result,
3 metadata::{NormalEpisodeMetadata, SpecialEpisodeMetadata},
4 subtitle::SubtitleType,
5};
6use std::{
7 ffi::OsStr,
8 path::Path,
9 process::{Command, ExitStatus},
10};
11
12pub trait Combinable {
13 fn combine(
15 &self,
16 subtitle_path: impl AsRef<Path>,
17 subtitle_language: &str,
18 subtitle_type: SubtitleType,
19 ) -> Result<ExitStatus>;
20}
21
22impl<P: AsRef<Path>> Combinable for NormalEpisodeMetadata<P> {
23 fn combine(
24 &self,
25 subtitle_path: impl AsRef<Path>,
26 subtitle_language: &str,
27 subtitle_type: SubtitleType,
28 ) -> Result<ExitStatus> {
29 let video_path = self
30 .path
31 .as_ref()
32 .ok_or(format!(
33 "Episode {} of {} doesn't have a path.",
34 self.episode, self.title
35 ))?
36 .as_ref()
37 .join(&self.type_tag)
38 .join("video.m4s");
39
40 let audio_path = self
41 .path
42 .as_ref()
43 .unwrap()
44 .as_ref()
45 .join(&self.type_tag)
46 .join("audio.m4s");
47
48 let output_path = self
49 .path
50 .as_ref()
51 .unwrap()
52 .as_ref()
53 .join(&self.type_tag)
54 .join("episode.mkv");
55
56 let mut binding = Command::new("ffmpeg");
57 binding
58 .arg("-y")
59 .args(["-hide_banner", "-loglevel", "error"]) .args([OsStr::new("-i"), video_path.as_os_str()])
61 .args([OsStr::new("-i"), audio_path.as_os_str()]);
62
63 match subtitle_type {
64 SubtitleType::Hard => Ok(binding
65 .args([
66 "-vf",
67 &format!("subtitles={}", subtitle_path.as_ref().display()),
68 ])
69 .arg(output_path)
70 .status()?),
71 SubtitleType::Soft => Ok(binding
72 .args([OsStr::new("-i"), subtitle_path.as_ref().as_os_str()])
73 .args(["-map", "0"])
74 .args(["-map", "1:a:0"])
75 .args(["-map", "2"])
76 .args(["-metadata:s:s:0", &format!("language={subtitle_language}")])
77 .args(["-codec", "copy"])
78 .arg(output_path)
79 .status()?),
80 }
81 }
82}
83
84impl<P: AsRef<Path>> Combinable for SpecialEpisodeMetadata<P> {
85 fn combine(
86 &self,
87 subtitle_path: impl AsRef<Path>,
88 subtitle_language: &str,
89 subtitle_type: SubtitleType,
90 ) -> Result<ExitStatus> {
91 let video_path = self
92 .path
93 .as_ref()
94 .ok_or(format!(
95 "{} of {} doesn't have a path.",
96 self.episode_name, self.title
97 ))?
98 .as_ref()
99 .join(&self.type_tag)
100 .join("video.m4s");
101
102 let audio_path = self
103 .path
104 .as_ref()
105 .unwrap()
106 .as_ref()
107 .join(&self.type_tag)
108 .join("audio.m4s");
109
110 let output_path = self
111 .path
112 .as_ref()
113 .unwrap()
114 .as_ref()
115 .join(&self.type_tag)
116 .join("episode.mkv");
117
118 let mut binding = Command::new("ffmpeg");
119 binding
120 .arg("-y")
121 .args(["-hide_banner", "-loglevel", "error"]) .args([OsStr::new("-i"), video_path.as_os_str()])
123 .args([OsStr::new("-i"), audio_path.as_os_str()]);
124
125 match subtitle_type {
126 SubtitleType::Hard => Ok(binding
127 .args([
128 "-vf",
129 &format!("subtitles={}", subtitle_path.as_ref().display()),
130 ])
131 .arg(output_path)
132 .status()?),
133 SubtitleType::Soft => Ok(binding
134 .args([OsStr::new("-i"), subtitle_path.as_ref().as_os_str()])
135 .args(["-map", "0"])
136 .args(["-map", "1:a:0"])
137 .args(["-map", "2"])
138 .args(["-metadata:s:s:0", &format!("language={subtitle_language}")])
139 .args(["-codec", "copy"])
140 .arg(output_path)
141 .status()?),
142 }
143 }
144}