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
use crate::{
backup::Backup,
error::{Error, FileReadFailure, TagReadFailure, TagWriteFailure},
};
use id3::{self, Tag};
use mediatype::{
names::{BMP, GIF, IMAGE, JPEG, PNG, SVG, WEBP},
MediaType,
};
use pipe_trait::Pipe;
use sha2::{Digest, Sha256};
use std::{fmt::Debug, fs::read as read_file, path::Path};
use typed_builder::TypedBuilder;
pub(crate) fn no_tag_to_empty_tag(error: id3::Error) -> id3::Result<Tag> {
if matches!(error.kind, id3::ErrorKind::NoTag) {
Ok(Tag::new())
} else {
Err(error)
}
}
pub fn read_tag_from_path(path: impl AsRef<Path>) -> Result<Tag, Error> {
path.pipe_as_ref(read_file)
.map_err(|error| FileReadFailure {
file: path.as_ref().to_path_buf(),
error,
})?
.pipe(read_tag_from_data)
.map_err(TagReadFailure::from)
.map_err(Error::from)
}
pub(crate) fn read_tag_from_data(data: impl AsRef<[u8]>) -> id3::Result<Tag> {
data.pipe_as_ref(Tag::read_from)
.or_else(no_tag_to_empty_tag)
}
pub(crate) fn get_image_extension(mime: MediaType) -> Option<&str> {
if mime.ty != IMAGE {
return None;
}
macro_rules! map_subty_ext {
($subty:ident -> $ext:literal) => {
if mime.subty.as_str() == $subty.as_str() {
return Some($ext);
}
};
}
map_subty_ext!(BMP -> "bmp");
map_subty_ext!(GIF -> "gif");
map_subty_ext!(JPEG -> "jpg");
map_subty_ext!(PNG -> "png");
map_subty_ext!(SVG -> "svg");
map_subty_ext!(WEBP -> "webp");
None
}
pub fn sha256_data(data: impl AsRef<[u8]>) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:x}", hasher.finalize())
}
#[derive(Debug, Clone, Copy, TypedBuilder)]
pub(crate) struct ModifyTags<'a> {
pub no_backup: bool,
pub target_audio: &'a Path,
}
impl<'a> ModifyTags<'a> {
pub fn run<Callback, Value>(self, callback: Callback) -> Result<Value, Error>
where
Callback: FnOnce(&mut Tag) -> Value,
{
let ModifyTags {
no_backup,
target_audio,
} = self;
let audio_content = read_file(&target_audio).map_err(|error| FileReadFailure {
file: target_audio.to_path_buf(),
error,
})?;
if !no_backup {
Backup::builder()
.source_file_path(target_audio)
.source_file_hash(&sha256_data(&audio_content))
.build()
.backup()?;
}
let mut tag = read_tag_from_data(&audio_content).map_err(TagReadFailure::from)?;
let version = tag.version();
let value = callback(&mut tag);
tag.write_to_path(target_audio, version)
.map_err(TagWriteFailure::from)?;
Ok(value)
}
}