libfreemkv/mux/
resolve.rs1use super::disc::DiscStream;
19use super::network::NetworkStream;
20use super::null::NullStream;
21use super::stdio::StdioStream;
22use super::{M2tsStream, MkvStream};
23use std::io;
24use std::path::{Path, PathBuf};
25
26const IO_BUF_SIZE: usize = 4 * 1024 * 1024;
28
29pub enum StreamUrl {
31 Disc { device: Option<PathBuf> },
33 M2ts { path: PathBuf },
35 Mkv { path: PathBuf },
37 Network { addr: String },
39 Stdio,
41 Iso { path: PathBuf },
43 Null,
45 Unknown { raw: String },
47}
48
49impl StreamUrl {
50 pub fn scheme(&self) -> &str {
52 match self {
53 StreamUrl::Disc { .. } => "disc",
54 StreamUrl::M2ts { .. } => "m2ts",
55 StreamUrl::Mkv { .. } => "mkv",
56 StreamUrl::Network { .. } => "network",
57 StreamUrl::Stdio => "stdio",
58 StreamUrl::Iso { .. } => "iso",
59 StreamUrl::Null => "null",
60 StreamUrl::Unknown { .. } => "unknown",
61 }
62 }
63
64 pub fn path_str(&self) -> &str {
66 match self {
67 StreamUrl::Disc { device: Some(p) } => p.to_str().unwrap_or(""),
68 StreamUrl::Disc { device: None } => "",
69 StreamUrl::M2ts { path } | StreamUrl::Mkv { path } | StreamUrl::Iso { path } => {
70 path.to_str().unwrap_or("")
71 }
72 StreamUrl::Network { addr } => addr,
73 StreamUrl::Stdio | StreamUrl::Null => "",
74 StreamUrl::Unknown { raw } => raw,
75 }
76 }
77
78 pub fn is_disc_source(&self) -> bool {
80 matches!(self, StreamUrl::Disc { .. } | StreamUrl::Iso { .. })
81 }
82}
83
84pub fn parse_url(url: &str) -> StreamUrl {
86 if let Some(rest) = url.strip_prefix("disc://") {
87 return if rest.is_empty() {
88 StreamUrl::Disc { device: None }
89 } else {
90 StreamUrl::Disc {
91 device: Some(PathBuf::from(rest)),
92 }
93 };
94 }
95 if let Some(rest) = url.strip_prefix("m2ts://") {
96 return StreamUrl::M2ts {
97 path: PathBuf::from(rest),
98 };
99 }
100 if let Some(rest) = url.strip_prefix("mkv://") {
101 return StreamUrl::Mkv {
102 path: PathBuf::from(rest),
103 };
104 }
105 if let Some(rest) = url.strip_prefix("network://") {
106 return StreamUrl::Network {
107 addr: rest.to_string(),
108 };
109 }
110 if url == "null://" || url.starts_with("null://") {
111 return StreamUrl::Null;
112 }
113 if url == "stdio://" || url.starts_with("stdio://") {
114 return StreamUrl::Stdio;
115 }
116 if let Some(rest) = url.strip_prefix("iso://") {
117 return StreamUrl::Iso {
118 path: PathBuf::from(rest),
119 };
120 }
121 StreamUrl::Unknown {
122 raw: url.to_string(),
123 }
124}
125
126fn validate_file_path(path: &Path, scheme: &str) -> io::Result<()> {
128 if path.as_os_str().is_empty() {
129 return Err(crate::error::Error::StreamUrlMissingPath {
130 scheme: scheme.to_string(),
131 }
132 .into());
133 }
134 if path.file_name().is_none() {
135 return Err(crate::error::Error::StreamUrlInvalid {
136 url: format!("{scheme}://{}", path.display()),
137 }
138 .into());
139 }
140 Ok(())
141}
142
143fn validate_network_addr(addr: &str) -> io::Result<()> {
145 if addr.is_empty() {
146 return Err(crate::error::Error::StreamUrlMissingPath {
147 scheme: "network".to_string(),
148 }
149 .into());
150 }
151 if !addr.contains(':') {
152 return Err(crate::error::Error::StreamUrlMissingPort {
153 addr: addr.to_string(),
154 }
155 .into());
156 }
157 Ok(())
158}
159
160#[derive(Default)]
162pub struct InputOptions {
163 pub keydb_path: Option<String>,
164 pub title_index: Option<usize>,
165 pub raw: bool,
167}
168
169pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::Stream>> {
171 let parsed = parse_url(url);
172 match parsed {
173 StreamUrl::Disc { device } => {
174 let mut drive = match device {
176 Some(ref d) => {
177 crate::drive::Drive::open(d).map_err(|e| -> io::Error { e.into() })?
178 }
179 None => crate::drive::find_drive().ok_or_else(|| -> io::Error {
180 crate::error::Error::DeviceNotFound {
181 path: String::new(),
182 }
183 .into()
184 })?,
185 };
186 let _ = drive.wait_ready();
187 let _ = drive.init();
188 let _ = drive.probe_disc();
189 let (mut stream, _disc) = DiscStream::open_drive(
190 drive,
191 opts.keydb_path.as_deref(),
192 opts.title_index.unwrap_or(0),
193 )
194 .map_err(|e| -> io::Error { e.into() })?;
195 if opts.raw {
196 stream.set_raw();
197 }
198 Ok(Box::new(stream))
199 }
200 StreamUrl::Iso { ref path } => {
201 validate_file_path(path, "iso")?;
202 let scan_opts = match &opts.keydb_path {
203 Some(p) => crate::disc::ScanOptions::with_keydb(p),
204 None => crate::disc::ScanOptions::default(),
205 };
206 let mut stream =
207 DiscStream::open_iso(&path.to_string_lossy(), opts.title_index, &scan_opts)?;
208 if opts.raw {
209 stream.set_raw();
210 }
211 Ok(Box::new(stream))
212 }
213 StreamUrl::M2ts { ref path } => {
214 validate_file_path(path, "m2ts")?;
215 let file = std::fs::File::open(path).map_err(|e| {
216 io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e))
217 })?;
218 let reader = std::io::BufReader::with_capacity(IO_BUF_SIZE, file);
219 Ok(Box::new(M2tsStream::open(reader)?))
220 }
221 StreamUrl::Mkv { ref path } => {
222 validate_file_path(path, "mkv")?;
223 let file = std::fs::File::open(path).map_err(|e| {
224 io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e))
225 })?;
226 let reader = std::io::BufReader::with_capacity(IO_BUF_SIZE, file);
227 Ok(Box::new(MkvStream::open(reader)?))
228 }
229 StreamUrl::Network { ref addr } => {
230 validate_network_addr(addr)?;
231 Ok(Box::new(NetworkStream::listen(addr)?))
232 }
233 StreamUrl::Stdio => Ok(Box::new(StdioStream::input())),
234 StreamUrl::Null => Err(crate::error::Error::StreamWriteOnly.into()),
235 StreamUrl::Unknown { ref raw } => {
236 Err(crate::error::Error::StreamUrlInvalid { url: raw.clone() }.into())
237 }
238 }
239}
240
241pub fn output(
243 url: &str,
244 title: &crate::disc::DiscTitle,
245) -> io::Result<Box<dyn crate::pes::Stream>> {
246 let parsed = parse_url(url);
247 match parsed {
248 StreamUrl::Mkv { ref path } => {
249 validate_file_path(path, "mkv")?;
250 let file = std::fs::File::create(path).map_err(|e| {
251 io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e))
252 })?;
253 let writer: Box<dyn super::WriteSeek> =
254 Box::new(std::io::BufWriter::with_capacity(IO_BUF_SIZE, file));
255 Ok(Box::new(MkvStream::create(writer, title)?))
256 }
257 StreamUrl::M2ts { ref path } => {
258 validate_file_path(path, "m2ts")?;
259 let file = std::fs::File::create(path).map_err(|e| {
260 io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e))
261 })?;
262 let writer = std::io::BufWriter::with_capacity(IO_BUF_SIZE, file);
263 Ok(Box::new(M2tsStream::create(writer, title)?))
264 }
265 StreamUrl::Network { ref addr } => {
266 validate_network_addr(addr)?;
267 Ok(Box::new(NetworkStream::connect(addr)?.meta(title)))
268 }
269 StreamUrl::Stdio => Ok(Box::new(StdioStream::output(title))),
270 StreamUrl::Null => Ok(Box::new(NullStream::new(title))),
271 StreamUrl::Disc { .. } => Err(crate::error::Error::StreamReadOnly.into()),
272 StreamUrl::Iso { .. } => Err(crate::error::Error::StreamReadOnly.into()),
273 StreamUrl::Unknown { ref raw } => {
274 Err(crate::error::Error::StreamUrlInvalid { url: raw.clone() }.into())
275 }
276 }
277}