forked from apezord/ord-dogecoin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmedia.rs
144 lines (123 loc) Β· 3.71 KB
/
media.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use {
super::*,
mp4::{MediaType, Mp4Reader, TrackType},
std::{fs::File, io::BufReader},
};
#[derive(Debug, PartialEq, Copy, Clone)]
pub(crate) enum Media {
Audio,
Iframe,
Image,
Pdf,
Text,
Unknown,
Video,
}
impl Media {
const TABLE: &'static [(&'static str, Media, &'static [&'static str])] = &[
("application/json", Media::Text, &["json"]),
("application/pdf", Media::Pdf, &["pdf"]),
("application/pgp-signature", Media::Text, &["asc"]),
("application/yaml", Media::Text, &["yaml", "yml"]),
("audio/flac", Media::Audio, &["flac"]),
("audio/mpeg", Media::Audio, &["mp3"]),
("audio/wav", Media::Audio, &["wav"]),
("image/apng", Media::Image, &["apng"]),
("image/avif", Media::Image, &[]),
("image/gif", Media::Image, &["gif"]),
("image/jpeg", Media::Image, &["jpg", "jpeg"]),
("image/png", Media::Image, &["png"]),
("image/svg+xml", Media::Iframe, &["svg"]),
("image/webp", Media::Image, &["webp"]),
("model/gltf-binary", Media::Unknown, &["glb"]),
("model/stl", Media::Unknown, &["stl"]),
("text/html;charset=utf-8", Media::Iframe, &["html"]),
("text/plain;charset=utf-8", Media::Text, &["txt"]),
("video/mp4", Media::Video, &["mp4"]),
("video/webm", Media::Video, &["webm"]),
];
pub(crate) fn content_type_for_path(path: &Path) -> Result<&'static str, Error> {
let extension = path
.extension()
.ok_or_else(|| anyhow!("file must have extension"))?
.to_str()
.ok_or_else(|| anyhow!("unrecognized extension"))?;
let extension = extension.to_lowercase();
if extension == "mp4" {
Media::check_mp4_codec(path)?;
}
for (content_type, _, extensions) in Self::TABLE {
if extensions.contains(&extension.as_str()) {
return Ok(content_type);
}
}
let mut extensions = Self::TABLE
.iter()
.flat_map(|(_, _, extensions)| extensions.first().cloned())
.collect::<Vec<&str>>();
extensions.sort();
Err(anyhow!(
"unsupported file extension `.{extension}`, supported extensions: {}",
extensions.join(" "),
))
}
pub(crate) fn check_mp4_codec(path: &Path) -> Result<(), Error> {
let f = File::open(path)?;
let size = f.metadata()?.len();
let reader = BufReader::new(f);
let mp4 = Mp4Reader::read_header(reader, size)?;
for track in mp4.tracks().values() {
if let TrackType::Video = track.track_type()? {
let media_type = track.media_type()?;
if media_type != MediaType::H264 {
return Err(anyhow!(
"Unsupported video codec, only H.264 is supported in MP4: {media_type}"
));
}
}
}
Ok(())
}
}
impl FromStr for Media {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
for entry in Self::TABLE {
if entry.0 == s {
return Ok(entry.1);
}
}
Err(anyhow!("unknown content type: {s}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_extension() {
assert_eq!(
Media::content_type_for_path(Path::new("pepe.jpg")).unwrap(),
"image/jpeg"
);
assert_eq!(
Media::content_type_for_path(Path::new("pepe.jpeg")).unwrap(),
"image/jpeg"
);
assert_eq!(
Media::content_type_for_path(Path::new("pepe.JPG")).unwrap(),
"image/jpeg"
);
assert_regex_match!(
Media::content_type_for_path(Path::new("pepe.foo")).unwrap_err(),
r"unsupported file extension `\.foo`, supported extensions: apng .*"
);
}
#[test]
fn h264_in_mp4_is_allowed() {
assert!(Media::check_mp4_codec(Path::new("examples/h264.mp4")).is_ok(),);
}
#[test]
fn av1_in_mp4_is_rejected() {
assert!(Media::check_mp4_codec(Path::new("examples/av1.mp4")).is_err(),);
}
}