Skip to content

Commit d70cf00

Browse files
committed
aud_io: Move text utils from lofty
1 parent c9fe1d0 commit d70cf00

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+267
-190
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ lofty_attr = { version = "0.11.1", path = "lofty_attr" }
2424
ogg_pager = { version = "0.7.0", path = "ogg_pager" }
2525

2626
byteorder = "1.5.0"
27+
log = "0.4.28"
28+
test-log = "0.2.18"
2729

2830
[workspace.lints.rust]
2931
missing_docs = "deny"

aud_io/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ license.workspace = true
1212
repository.workspace = true
1313

1414
[dependencies]
15+
byteorder = { workspace = true }
16+
log = { workspace = true }
17+
18+
[dev-dependencies]
19+
test-log = { workspace = true }
1520

1621
# TODO
1722
#[lints]

aud_io/src/error.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::fmt::{Display, Formatter};
2+
3+
/// Alias for `Result<T, AudioError>`
4+
pub type Result<T> = std::result::Result<T, AudioError>;
5+
6+
#[derive(Debug)]
7+
pub enum AudioError {
8+
/// Errors that arise while decoding text
9+
TextDecode(&'static str),
10+
11+
// Conversions for external errors
12+
/// Represents all cases of [`std::io::Error`].
13+
Io(std::io::Error),
14+
/// Unable to convert bytes to a String
15+
StringFromUtf8(std::string::FromUtf8Error),
16+
/// Unable to convert bytes to a str
17+
StrFromUtf8(std::str::Utf8Error),
18+
}
19+
20+
impl From<std::io::Error> for AudioError {
21+
fn from(input: std::io::Error) -> Self {
22+
AudioError::Io(input)
23+
}
24+
}
25+
26+
impl From<std::string::FromUtf8Error> for AudioError {
27+
fn from(input: std::string::FromUtf8Error) -> Self {
28+
AudioError::StringFromUtf8(input)
29+
}
30+
}
31+
32+
impl From<std::str::Utf8Error> for AudioError {
33+
fn from(input: std::str::Utf8Error) -> Self {
34+
AudioError::StrFromUtf8(input)
35+
}
36+
}
37+
38+
impl Display for AudioError {
39+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40+
match self {
41+
// Conversions
42+
AudioError::StringFromUtf8(err) => write!(f, "{err}"),
43+
AudioError::StrFromUtf8(err) => write!(f, "{err}"),
44+
AudioError::Io(err) => write!(f, "{err}"),
45+
AudioError::TextDecode(message) => write!(f, "Text decoding: {message}"),
46+
}
47+
}
48+
}
49+
50+
impl core::error::Error for AudioError {}

aud_io/src/lib.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,3 @@
1-
pub fn add(left: u64, right: u64) -> u64 {
2-
left + right
3-
}
4-
5-
#[cfg(test)]
6-
mod tests {
7-
use super::*;
8-
9-
#[test]
10-
fn it_works() {
11-
let result = add(2, 2);
12-
assert_eq!(result, 4);
13-
}
14-
}
1+
pub mod text;
2+
pub mod error;
3+
pub(crate) mod macros;

aud_io/src/macros.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Shorthand for `return Err(AudioError::Foo)`
2+
//
3+
// Usage:
4+
// - err!(Variant) -> return Err(AudioError::::Variant)
5+
// - err!(Variant(Message)) -> return Err(AudioError::Variant(Message))
6+
#[macro_export]
7+
macro_rules! err {
8+
($variant:ident) => {
9+
return Err($crate::error::AudioError::$variant.into())
10+
};
11+
($variant:ident($reason:literal)) => {
12+
return Err($crate::error::AudioError::$variant($reason).into())
13+
};
14+
}
Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::error::{ErrorKind, LoftyError, Result};
2-
use crate::macros::err;
1+
use crate::error::{AudioError, Result};
2+
use crate::err;
33

44
use std::io::Read;
55

@@ -31,36 +31,20 @@ impl TextEncoding {
3131
}
3232
}
3333

34-
pub(crate) fn verify_latin1(text: &str) -> bool {
34+
pub fn verify_latin1(text: &str) -> bool {
3535
text.chars().all(|c| c as u32 <= 255)
3636
}
37-
38-
/// ID3v2.4 introduced two new text encodings.
39-
///
40-
/// When writing ID3v2.3, we just substitute with UTF-16.
41-
pub(crate) fn to_id3v23(self) -> Self {
42-
match self {
43-
Self::UTF8 | Self::UTF16BE => {
44-
log::warn!(
45-
"Text encoding {:?} is not supported in ID3v2.3, substituting with UTF-16",
46-
self
47-
);
48-
Self::UTF16
49-
},
50-
_ => self,
51-
}
52-
}
5337
}
5438

5539
#[derive(Eq, PartialEq, Debug)]
56-
pub(crate) struct DecodeTextResult {
57-
pub(crate) content: String,
58-
pub(crate) bytes_read: usize,
59-
pub(crate) bom: [u8; 2],
40+
pub struct DecodeTextResult {
41+
pub content: String,
42+
pub bytes_read: usize,
43+
pub bom: [u8; 2],
6044
}
6145

6246
impl DecodeTextResult {
63-
pub(crate) fn text_or_none(self) -> Option<String> {
47+
pub fn text_or_none(self) -> Option<String> {
6448
if self.content.is_empty() {
6549
return None;
6650
}
@@ -83,28 +67,28 @@ const EMPTY_DECODED_TEXT: DecodeTextResult = DecodeTextResult {
8367
/// * Not expect the text to be null terminated
8468
/// * Have no byte order mark
8569
#[derive(Copy, Clone, Debug)]
86-
pub(crate) struct TextDecodeOptions {
70+
pub struct TextDecodeOptions {
8771
pub encoding: TextEncoding,
8872
pub terminated: bool,
8973
pub bom: [u8; 2],
9074
}
9175

9276
impl TextDecodeOptions {
93-
pub(crate) fn new() -> Self {
77+
pub fn new() -> Self {
9478
Self::default()
9579
}
9680

97-
pub(crate) fn encoding(mut self, encoding: TextEncoding) -> Self {
81+
pub fn encoding(mut self, encoding: TextEncoding) -> Self {
9882
self.encoding = encoding;
9983
self
10084
}
10185

102-
pub(crate) fn terminated(mut self, terminated: bool) -> Self {
86+
pub fn terminated(mut self, terminated: bool) -> Self {
10387
self.terminated = terminated;
10488
self
10589
}
10690

107-
pub(crate) fn bom(mut self, bom: [u8; 2]) -> Self {
91+
pub fn bom(mut self, bom: [u8; 2]) -> Self {
10892
self.bom = bom;
10993
self
11094
}
@@ -120,7 +104,7 @@ impl Default for TextDecodeOptions {
120104
}
121105
}
122106

123-
pub(crate) fn decode_text<R>(reader: &mut R, options: TextDecodeOptions) -> Result<DecodeTextResult>
107+
pub fn decode_text<R>(reader: &mut R, options: TextDecodeOptions) -> Result<DecodeTextResult>
124108
where
125109
R: Read,
126110
{
@@ -174,7 +158,7 @@ where
174158
},
175159
TextEncoding::UTF16BE => utf16_decode_bytes(raw_bytes.as_slice(), u16::from_be_bytes)?,
176160
TextEncoding::UTF8 => utf8_decode(raw_bytes)
177-
.map_err(|_| LoftyError::new(ErrorKind::TextDecode("Expected a UTF-8 string")))?,
161+
.map_err(|_| AudioError::TextDecode("Expected a UTF-8 string"))?,
178162
};
179163

180164
Ok(DecodeTextResult {
@@ -224,7 +208,7 @@ pub(crate) fn latin1_decode(bytes: &[u8]) -> String {
224208
text
225209
}
226210

227-
pub(crate) fn utf8_decode(bytes: Vec<u8>) -> Result<String> {
211+
pub fn utf8_decode(bytes: Vec<u8>) -> Result<String> {
228212
String::from_utf8(bytes)
229213
.map(|mut text| {
230214
trim_end_nulls(&mut text);
@@ -233,22 +217,22 @@ pub(crate) fn utf8_decode(bytes: Vec<u8>) -> Result<String> {
233217
.map_err(Into::into)
234218
}
235219

236-
pub(crate) fn utf8_decode_str(bytes: &[u8]) -> Result<&str> {
220+
pub fn utf8_decode_str(bytes: &[u8]) -> Result<&str> {
237221
std::str::from_utf8(bytes)
238222
.map(trim_end_nulls_str)
239223
.map_err(Into::into)
240224
}
241225

242-
pub(crate) fn utf16_decode(words: &[u16]) -> Result<String> {
226+
pub fn utf16_decode(words: &[u16]) -> Result<String> {
243227
String::from_utf16(words)
244228
.map(|mut text| {
245229
trim_end_nulls(&mut text);
246230
text
247231
})
248-
.map_err(|_| LoftyError::new(ErrorKind::TextDecode("Given an invalid UTF-16 string")))
232+
.map_err(|_| AudioError::TextDecode("Given an invalid UTF-16 string"))
249233
}
250234

251-
pub(crate) fn utf16_decode_bytes(bytes: &[u8], endianness: fn([u8; 2]) -> u16) -> Result<String> {
235+
pub fn utf16_decode_bytes(bytes: &[u8], endianness: fn([u8; 2]) -> u16) -> Result<String> {
252236
if bytes.is_empty() {
253237
return Ok(String::new());
254238
}
@@ -277,7 +261,7 @@ pub(crate) fn utf16_decode_bytes(bytes: &[u8], endianness: fn([u8; 2]) -> u16) -
277261
/// with a BOM.
278262
///
279263
/// If no BOM is present, the string will be decoded using `endianness`.
280-
pub(crate) fn utf16_decode_terminated_maybe_bom<R>(
264+
pub fn utf16_decode_terminated_maybe_bom<R>(
281265
reader: &mut R,
282266
endianness: fn([u8; 2]) -> u16,
283267
) -> Result<(String, usize)>
@@ -297,7 +281,7 @@ where
297281
decoded.map(|d| (d, bytes_read))
298282
}
299283

300-
pub(crate) fn encode_text(text: &str, text_encoding: TextEncoding, terminated: bool) -> Vec<u8> {
284+
pub fn encode_text(text: &str, text_encoding: TextEncoding, terminated: bool) -> Vec<u8> {
301285
match text_encoding {
302286
TextEncoding::Latin1 => {
303287
let mut out = text.chars().map(|c| c as u8).collect::<Vec<u8>>();
@@ -322,14 +306,14 @@ pub(crate) fn encode_text(text: &str, text_encoding: TextEncoding, terminated: b
322306
}
323307
}
324308

325-
pub(crate) fn trim_end_nulls(text: &mut String) {
309+
pub fn trim_end_nulls(text: &mut String) {
326310
if text.ends_with('\0') {
327311
let new_len = text.trim_end_matches('\0').len();
328312
text.truncate(new_len);
329313
}
330314
}
331315

332-
pub(crate) fn trim_end_nulls_str(text: &str) -> &str {
316+
pub fn trim_end_nulls_str(text: &str) -> &str {
333317
text.trim_end_matches('\0')
334318
}
335319

@@ -358,7 +342,7 @@ fn utf16_encode(
358342

359343
#[cfg(test)]
360344
mod tests {
361-
use crate::util::text::{TextDecodeOptions, TextEncoding};
345+
use super::{TextDecodeOptions, TextEncoding};
362346
use std::io::Cursor;
363347

364348
const TEST_STRING: &str = "l\u{00f8}ft\u{00a5}";
@@ -372,7 +356,7 @@ mod tests {
372356
],
373357
u16::from_be_bytes,
374358
)
375-
.unwrap();
359+
.unwrap();
376360

377361
assert_eq!(utf16_decode, TEST_STRING.to_string());
378362

@@ -383,14 +367,14 @@ mod tests {
383367
]),
384368
TextDecodeOptions::new().encoding(TextEncoding::UTF16),
385369
)
386-
.unwrap();
370+
.unwrap();
387371
let le_utf16_decode = super::decode_text(
388372
&mut Cursor::new(&[
389373
0xFF, 0xFE, 0x6C, 0x00, 0xF8, 0x00, 0x66, 0x00, 0x74, 0x00, 0xA5, 0x00, 0x00, 0x00,
390374
]),
391375
TextDecodeOptions::new().encoding(TextEncoding::UTF16),
392376
)
393-
.unwrap();
377+
.unwrap();
394378

395379
assert_eq!(be_utf16_decode.content, le_utf16_decode.content);
396380
assert_eq!(be_utf16_decode.bytes_read, le_utf16_decode.bytes_read);
@@ -400,7 +384,7 @@ mod tests {
400384
&mut TEST_STRING.as_bytes(),
401385
TextDecodeOptions::new().encoding(TextEncoding::UTF8),
402386
)
403-
.unwrap();
387+
.unwrap();
404388

405389
assert_eq!(utf8_decode.content, TEST_STRING.to_string());
406390
}

lofty/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ flate2 = { version = "1.0.30", optional = true }
2323
# Proc macros
2424
lofty_attr = { workspace = true }
2525
# Debug logging
26-
log = "0.4.22"
26+
log = { workspace = true }
2727
# OGG Vorbis/Opus
2828
ogg_pager = "0.7.0"
2929
# Key maps
@@ -48,7 +48,7 @@ rusty-fork = "0.3.0"
4848
# tag_writer example
4949
structopt = { version = "0.3.26", default-features = false }
5050
tempfile = "3.15.0"
51-
test-log = "0.2.16"
51+
test-log = { workspace = true }
5252
gungraun = "0.17.0"
5353

5454
[lints]

lofty/src/ape/tag/read.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ use crate::config::ParseOptions;
77
use crate::error::Result;
88
use crate::macros::{decode_err, err, try_vec};
99
use crate::tag::ItemValue;
10-
use crate::util::text::utf8_decode;
1110

1211
use std::io::{Read, Seek, SeekFrom};
1312

13+
use aud_io::text::utf8_decode;
1414
use byteorder::{LittleEndian, ReadBytesExt};
1515

1616
pub(crate) fn read_ape_tag_with_header<R>(

0 commit comments

Comments
 (0)