Auto-update fts index
This commit is contained in:
@@ -17,7 +17,7 @@ impl Display for PictureTypeError {
|
||||
impl std::error::Error for PictureTypeError {}
|
||||
|
||||
/// A picture type according to the ID3v2 APIC frame
|
||||
#[allow(missing_docs)]
|
||||
#[expect(missing_docs)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum PictureType {
|
||||
Other,
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::flac::blocks::{FlacMetablockDecode, FlacMetablockEncode, FlacPictureB
|
||||
use super::tagtype::TagType;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_docs)]
|
||||
#[expect(missing_docs)]
|
||||
pub enum VorbisCommentDecodeError {
|
||||
/// We encountered an IoError while processing a block
|
||||
IoError(std::io::Error),
|
||||
@@ -74,7 +74,7 @@ impl From<FromUtf8Error> for VorbisCommentDecodeError {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_docs)]
|
||||
#[expect(missing_docs)]
|
||||
pub enum VorbisCommentEncodeError {
|
||||
/// We encountered an IoError while processing a block
|
||||
IoError(std::io::Error),
|
||||
@@ -132,39 +132,52 @@ impl VorbisComment {
|
||||
let mut block = [0u8; 4];
|
||||
|
||||
let vendor = {
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut block)
|
||||
.map_err(|_| VorbisCommentDecodeError::MalformedData)?;
|
||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
||||
|
||||
let length = u32::from_le_bytes(block);
|
||||
let mut text = vec![0u8; length.try_into().unwrap()];
|
||||
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
#[expect(clippy::expect_used)]
|
||||
let mut text = vec![
|
||||
0u8;
|
||||
length
|
||||
.try_into()
|
||||
.expect("vendor length does not fit into usize")
|
||||
];
|
||||
|
||||
d.read_exact(&mut text)
|
||||
.map_err(|_| VorbisCommentDecodeError::MalformedData)?;
|
||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
||||
|
||||
String::from_utf8(text)?
|
||||
};
|
||||
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut block)
|
||||
.map_err(|_| VorbisCommentDecodeError::MalformedData)?;
|
||||
let n_comments: usize = u32::from_le_bytes(block).try_into().unwrap();
|
||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
||||
|
||||
#[expect(clippy::expect_used)]
|
||||
let n_comments: usize = u32::from_le_bytes(block)
|
||||
.try_into()
|
||||
.expect("comment count does not fit into usize");
|
||||
|
||||
let mut comments = Vec::new();
|
||||
let mut pictures = Vec::new();
|
||||
for _ in 0..n_comments {
|
||||
let comment = {
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut block)
|
||||
.map_err(|_| VorbisCommentDecodeError::MalformedData)?;
|
||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
||||
|
||||
let length = u32::from_le_bytes(block);
|
||||
let mut text = vec![0u8; length.try_into().unwrap()];
|
||||
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
#[expect(clippy::expect_used)]
|
||||
let mut text = vec![
|
||||
0u8;
|
||||
length
|
||||
.try_into()
|
||||
.expect("comment length does not fit into usize")
|
||||
];
|
||||
|
||||
d.read_exact(&mut text)
|
||||
.map_err(|_| VorbisCommentDecodeError::MalformedData)?;
|
||||
.map_err(|_err| VorbisCommentDecodeError::MalformedData)?;
|
||||
|
||||
String::from_utf8(text)?
|
||||
};
|
||||
@@ -218,9 +231,10 @@ impl VorbisComment {
|
||||
|
||||
impl VorbisComment {
|
||||
/// Get the number of bytes that `encode()` will write.
|
||||
#[expect(clippy::expect_used)]
|
||||
pub fn get_len(&self) -> u32 {
|
||||
let mut sum: u32 = 0;
|
||||
sum += u32::try_from(self.vendor.len()).unwrap() + 4;
|
||||
sum += u32::try_from(self.vendor.len()).expect("vendor length does not fit into u32") + 4;
|
||||
sum += 4;
|
||||
|
||||
for (tagtype, value) in &self.comments {
|
||||
@@ -244,7 +258,8 @@ impl VorbisComment {
|
||||
.to_uppercase();
|
||||
|
||||
let str = format!("{tagtype_str}={value}");
|
||||
sum += 4 + u32::try_from(str.len()).unwrap();
|
||||
sum +=
|
||||
4 + u32::try_from(str.len()).expect("comment string length does not fit into u32");
|
||||
}
|
||||
|
||||
for p in &self.pictures {
|
||||
@@ -271,13 +286,18 @@ impl VorbisComment {
|
||||
}
|
||||
|
||||
/// Try to encode this vorbis comment
|
||||
#[expect(clippy::expect_used)]
|
||||
pub fn encode(&self, target: &mut impl Write) -> Result<(), VorbisCommentEncodeError> {
|
||||
target.write_all(&u32::try_from(self.vendor.len()).unwrap().to_le_bytes())?;
|
||||
target.write_all(
|
||||
&u32::try_from(self.vendor.len())
|
||||
.expect("vendor length does not fit into u32")
|
||||
.to_le_bytes(),
|
||||
)?;
|
||||
target.write_all(self.vendor.as_bytes())?;
|
||||
|
||||
target.write_all(
|
||||
&u32::try_from(self.comments.len() + self.pictures.len())
|
||||
.unwrap()
|
||||
.expect("total comment count does not fit into u32")
|
||||
.to_le_bytes(),
|
||||
)?;
|
||||
|
||||
@@ -302,7 +322,11 @@ impl VorbisComment {
|
||||
.to_uppercase();
|
||||
|
||||
let str = format!("{tagtype_str}={value}");
|
||||
target.write_all(&u32::try_from(str.len()).unwrap().to_le_bytes())?;
|
||||
target.write_all(
|
||||
&u32::try_from(str.len())
|
||||
.expect("comment string length does not fit into u32")
|
||||
.to_le_bytes(),
|
||||
)?;
|
||||
target.write_all(str.as_bytes())?;
|
||||
}
|
||||
|
||||
@@ -318,7 +342,11 @@ impl VorbisComment {
|
||||
&base64::prelude::BASE64_STANDARD.encode(&pic_data)
|
||||
);
|
||||
|
||||
target.write_all(&u32::try_from(pic_string.len()).unwrap().to_le_bytes())?;
|
||||
target.write_all(
|
||||
&u32::try_from(pic_string.len())
|
||||
.expect("picture string length does not fit into u32")
|
||||
.to_le_bytes(),
|
||||
)?;
|
||||
target.write_all(pic_string.as_bytes())?;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ enum FlacBlockType {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_docs)]
|
||||
#[expect(missing_docs)]
|
||||
pub enum FlacBlock {
|
||||
Streaminfo(FlacStreaminfoBlock),
|
||||
Picture(FlacPictureBlock),
|
||||
@@ -212,9 +212,16 @@ impl FlacBlockReader {
|
||||
}
|
||||
|
||||
'outer: while last_read_size != 0 {
|
||||
match self.current_block.as_mut().unwrap() {
|
||||
#[expect(clippy::expect_used)]
|
||||
match self
|
||||
.current_block
|
||||
.as_mut()
|
||||
.expect("current_block is Some, checked above")
|
||||
{
|
||||
FlacBlockType::MagicBits { data, left_to_read } => {
|
||||
last_read_size = buf.read(&mut data[4 - *left_to_read..4]).unwrap();
|
||||
last_read_size = buf
|
||||
.read(&mut data[4 - *left_to_read..4])
|
||||
.map_err(FlacDecodeError::from)?;
|
||||
*left_to_read -= last_read_size;
|
||||
|
||||
if *left_to_read == 0 {
|
||||
@@ -235,7 +242,9 @@ impl FlacBlockReader {
|
||||
data,
|
||||
left_to_read,
|
||||
} => {
|
||||
last_read_size = buf.read(&mut data[4 - *left_to_read..4]).unwrap();
|
||||
last_read_size = buf
|
||||
.read(&mut data[4 - *left_to_read..4])
|
||||
.map_err(FlacDecodeError::from)?;
|
||||
*left_to_read -= last_read_size;
|
||||
|
||||
if *left_to_read == 0 {
|
||||
@@ -253,13 +262,24 @@ impl FlacBlockReader {
|
||||
}
|
||||
|
||||
FlacBlockType::MetaBlock { header, data } => {
|
||||
last_read_size = buf
|
||||
.by_ref()
|
||||
.take(u64::from(header.length) - u64::try_from(data.len()).unwrap())
|
||||
.read_to_end(data)
|
||||
.unwrap();
|
||||
#[expect(clippy::expect_used)]
|
||||
{
|
||||
last_read_size = buf
|
||||
.by_ref()
|
||||
.take(
|
||||
u64::from(header.length)
|
||||
- u64::try_from(data.len())
|
||||
.expect("data length does not fit into u64"),
|
||||
)
|
||||
.read_to_end(data)
|
||||
.map_err(FlacDecodeError::from)?;
|
||||
}
|
||||
|
||||
if data.len() == usize::try_from(header.length).unwrap() {
|
||||
#[expect(clippy::expect_used)]
|
||||
if data.len()
|
||||
== usize::try_from(header.length)
|
||||
.expect("header length does not fit into usize")
|
||||
{
|
||||
// If we picked this block type, add it to the queue
|
||||
if self.selector.should_pick_meta(header.block_type) {
|
||||
let b = FlacBlock::decode(header.block_type, data)?;
|
||||
@@ -283,7 +303,11 @@ impl FlacBlockReader {
|
||||
// Limit the number of bytes we read at once, so we don't re-clone
|
||||
// large amounts of data if `buf` contains multiple sync sequences.
|
||||
// 5kb is a pretty reasonable frame size.
|
||||
last_read_size = buf.by_ref().take(5_000).read_to_end(data).unwrap();
|
||||
last_read_size = buf
|
||||
.by_ref()
|
||||
.take(5_000)
|
||||
.read_to_end(data)
|
||||
.map_err(FlacDecodeError::from)?;
|
||||
if last_read_size == 0 {
|
||||
continue 'outer;
|
||||
}
|
||||
@@ -335,9 +359,10 @@ impl FlacBlockReader {
|
||||
|
||||
// Backtrack to the first bit AFTER this new sync sequence
|
||||
buf.seek(std::io::SeekFrom::Current(
|
||||
-i64::try_from(data.len() - i).unwrap(),
|
||||
-i64::try_from(data.len() - i)
|
||||
.expect("seek offset does not fit into i64"),
|
||||
))
|
||||
.unwrap();
|
||||
.map_err(FlacDecodeError::from)?;
|
||||
|
||||
self.current_block = Some(FlacBlockType::AudioData {
|
||||
data: {
|
||||
@@ -406,6 +431,7 @@ mod tests {
|
||||
flac::tests::{FlacBlockOutput, FlacTestCase, VorbisCommentTestValue, manifest},
|
||||
};
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
fn read_file(
|
||||
test_case: &FlacTestCase,
|
||||
fragment_size_range: Option<Range<usize>>,
|
||||
@@ -447,6 +473,7 @@ mod tests {
|
||||
return Ok(out_blocks);
|
||||
}
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
fn test_identical(
|
||||
test_case: &FlacTestCase,
|
||||
fragment_size_range: Option<Range<usize>>,
|
||||
|
||||
@@ -50,8 +50,11 @@ impl FlacMetablockDecode for FlacApplicationBlock {
|
||||
}
|
||||
|
||||
impl FlacMetablockEncode for FlacApplicationBlock {
|
||||
#[expect(clippy::expect_used)]
|
||||
fn get_len(&self) -> u32 {
|
||||
(self.data.len() + 4).try_into().unwrap()
|
||||
(self.data.len() + 4)
|
||||
.try_into()
|
||||
.expect("application block size does not fit into u32")
|
||||
}
|
||||
|
||||
fn encode(
|
||||
|
||||
@@ -25,8 +25,12 @@ impl FlacMetablockDecode for FlacCuesheetBlock {
|
||||
}
|
||||
|
||||
impl FlacMetablockEncode for FlacCuesheetBlock {
|
||||
#[expect(clippy::expect_used)]
|
||||
fn get_len(&self) -> u32 {
|
||||
self.data.len().try_into().unwrap()
|
||||
self.data
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("cuesheet size does not fit into u32")
|
||||
}
|
||||
|
||||
fn encode(
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::fmt::Debug;
|
||||
use crate::flac::errors::{FlacDecodeError, FlacEncodeError};
|
||||
|
||||
/// A type of flac metadata block
|
||||
#[allow(missing_docs)]
|
||||
#[expect(missing_docs)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum FlacMetablockType {
|
||||
Streaminfo,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Read and write impelementations for all flac block types
|
||||
//! Read and write implementations for all flac block types
|
||||
|
||||
// Not metadata blocks
|
||||
mod header;
|
||||
|
||||
@@ -12,13 +12,17 @@ pub struct FlacPaddingBlock {
|
||||
}
|
||||
|
||||
impl FlacMetablockDecode for FlacPaddingBlock {
|
||||
#[expect(clippy::expect_used)]
|
||||
fn decode(data: &[u8]) -> Result<Self, FlacDecodeError> {
|
||||
if data.iter().any(|x| *x != 0u8) {
|
||||
return Err(FlacDecodeError::MalformedBlock);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
size: data.len().try_into().unwrap(),
|
||||
size: data
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("padding size does not fit into u32"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,16 +63,17 @@ impl FlacMetablockDecode for FlacPictureBlock {
|
||||
|
||||
// Image format
|
||||
let mime = {
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut block)
|
||||
.map_err(|_| FlacDecodeError::MalformedBlock)?;
|
||||
.map_err(|_err| FlacDecodeError::MalformedBlock)?;
|
||||
|
||||
let mime_length = u32::from_be_bytes(block).try_into().unwrap();
|
||||
#[expect(clippy::expect_used)]
|
||||
let mime_length = u32::from_be_bytes(block)
|
||||
.try_into()
|
||||
.expect("mime length does not fit into usize");
|
||||
let mut mime = vec![0u8; mime_length];
|
||||
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut mime)
|
||||
.map_err(|_| FlacDecodeError::MalformedBlock)?;
|
||||
.map_err(|_err| FlacDecodeError::MalformedBlock)?;
|
||||
|
||||
String::from_utf8(mime)
|
||||
.ok()
|
||||
@@ -82,16 +83,17 @@ impl FlacMetablockDecode for FlacPictureBlock {
|
||||
|
||||
// Image description
|
||||
let description = {
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut block)
|
||||
.map_err(|_| FlacDecodeError::MalformedBlock)?;
|
||||
.map_err(|_err| FlacDecodeError::MalformedBlock)?;
|
||||
|
||||
let desc_length = u32::from_be_bytes(block).try_into().unwrap();
|
||||
#[expect(clippy::expect_used)]
|
||||
let desc_length = u32::from_be_bytes(block)
|
||||
.try_into()
|
||||
.expect("description length does not fit into usize");
|
||||
let mut desc = vec![0u8; desc_length];
|
||||
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut desc)
|
||||
.map_err(|_| FlacDecodeError::MalformedBlock)?;
|
||||
.map_err(|_err| FlacDecodeError::MalformedBlock)?;
|
||||
|
||||
String::from_utf8(desc)?
|
||||
};
|
||||
@@ -122,16 +124,17 @@ impl FlacMetablockDecode for FlacPictureBlock {
|
||||
|
||||
// Image data length
|
||||
let img_data = {
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut block)
|
||||
.map_err(|_| FlacDecodeError::MalformedBlock)?;
|
||||
.map_err(|_err| FlacDecodeError::MalformedBlock)?;
|
||||
|
||||
let data_length = u32::from_be_bytes(block).try_into().unwrap();
|
||||
#[expect(clippy::expect_used)]
|
||||
let data_length = u32::from_be_bytes(block)
|
||||
.try_into()
|
||||
.expect("image data length does not fit into usize");
|
||||
let mut img_data = vec![0u8; data_length];
|
||||
|
||||
#[expect(clippy::map_err_ignore)]
|
||||
d.read_exact(&mut img_data)
|
||||
.map_err(|_| FlacDecodeError::MalformedBlock)?;
|
||||
.map_err(|_err| FlacDecodeError::MalformedBlock)?;
|
||||
|
||||
img_data
|
||||
};
|
||||
@@ -150,13 +153,14 @@ impl FlacMetablockDecode for FlacPictureBlock {
|
||||
}
|
||||
|
||||
impl FlacMetablockEncode for FlacPictureBlock {
|
||||
#[expect(clippy::expect_used)]
|
||||
fn get_len(&self) -> u32 {
|
||||
(4 + (4 + self.mime.to_string().len())
|
||||
+ (4 + self.description.len())
|
||||
+ 4 + 4 + 4
|
||||
+ 4 + (4 + self.img_data.len()))
|
||||
.try_into()
|
||||
.unwrap()
|
||||
.expect("picture block size does not fit into u32")
|
||||
}
|
||||
|
||||
fn encode(
|
||||
@@ -176,20 +180,35 @@ impl FlacMetablockEncode for FlacPictureBlock {
|
||||
|
||||
target.write_all(&self.picture_type.to_idx().to_be_bytes())?;
|
||||
|
||||
let mime = self.mime.to_string();
|
||||
target.write_all(&u32::try_from(mime.len()).unwrap().to_be_bytes())?;
|
||||
target.write_all(self.mime.to_string().as_bytes())?;
|
||||
drop(mime);
|
||||
#[expect(clippy::expect_used)]
|
||||
{
|
||||
let mime = self.mime.to_string();
|
||||
target.write_all(
|
||||
&u32::try_from(mime.len())
|
||||
.expect("mime length does not fit into u32")
|
||||
.to_be_bytes(),
|
||||
)?;
|
||||
target.write_all(self.mime.to_string().as_bytes())?;
|
||||
drop(mime);
|
||||
|
||||
target.write_all(&u32::try_from(self.description.len()).unwrap().to_be_bytes())?;
|
||||
target.write_all(self.description.as_bytes())?;
|
||||
target.write_all(
|
||||
&u32::try_from(self.description.len())
|
||||
.expect("description length does not fit into u32")
|
||||
.to_be_bytes(),
|
||||
)?;
|
||||
target.write_all(self.description.as_bytes())?;
|
||||
|
||||
target.write_all(&self.width.to_be_bytes())?;
|
||||
target.write_all(&self.height.to_be_bytes())?;
|
||||
target.write_all(&self.bit_depth.to_be_bytes())?;
|
||||
target.write_all(&self.color_count.to_be_bytes())?;
|
||||
target.write_all(&self.width.to_be_bytes())?;
|
||||
target.write_all(&self.height.to_be_bytes())?;
|
||||
target.write_all(&self.bit_depth.to_be_bytes())?;
|
||||
target.write_all(&self.color_count.to_be_bytes())?;
|
||||
|
||||
target.write_all(&u32::try_from(self.img_data.len()).unwrap().to_be_bytes())?;
|
||||
target.write_all(
|
||||
&u32::try_from(self.img_data.len())
|
||||
.expect("image data length does not fit into u32")
|
||||
.to_be_bytes(),
|
||||
)?;
|
||||
}
|
||||
target.write_all(&self.img_data)?;
|
||||
|
||||
return Ok(());
|
||||
|
||||
@@ -25,8 +25,12 @@ impl FlacMetablockDecode for FlacSeektableBlock {
|
||||
}
|
||||
|
||||
impl FlacMetablockEncode for FlacSeektableBlock {
|
||||
#[expect(clippy::expect_used)]
|
||||
fn get_len(&self) -> u32 {
|
||||
self.data.len().try_into().unwrap()
|
||||
self.data
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("seektable size does not fit into u32")
|
||||
}
|
||||
|
||||
fn encode(
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::common::{
|
||||
use std::string::FromUtf8Error;
|
||||
use thiserror::Error;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[expect(missing_docs)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FlacDecodeError {
|
||||
/// FLAC does not start with 0x66 0x4C 0x61 0x43
|
||||
@@ -46,7 +46,7 @@ pub enum FlacDecodeError {
|
||||
PictureTypeError(#[from] PictureTypeError),
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[expect(missing_docs)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FlacEncodeError {
|
||||
/// We encountered an i/o error while processing
|
||||
|
||||
@@ -213,6 +213,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// A list of test files and their expected output
|
||||
#[expect(clippy::unwrap_used)]
|
||||
pub fn manifest() -> [FlacTestCase; 23] {
|
||||
[
|
||||
FlacTestCase::Error {
|
||||
|
||||
@@ -88,7 +88,11 @@ impl FlacMetaStrip {
|
||||
// We don't need to store audioframes in our last_block buffer,
|
||||
// since they do not have an `is_last` flag.
|
||||
if matches!(self.last_block, Some(FlacBlock::AudioFrame(_))) {
|
||||
let x = self.last_block.take().unwrap();
|
||||
#[expect(clippy::expect_used)]
|
||||
let x = self
|
||||
.last_block
|
||||
.take()
|
||||
.expect("last_block is Some(AudioFrame), just matched");
|
||||
x.encode(false, true, target)?;
|
||||
}
|
||||
|
||||
@@ -107,6 +111,7 @@ mod tests {
|
||||
tests::manifest,
|
||||
};
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
fn test_strip(
|
||||
test_case: &FlacTestCase,
|
||||
fragment_size_range: Option<std::ops::Range<usize>>,
|
||||
|
||||
@@ -90,6 +90,7 @@ mod tests {
|
||||
tests::{FlacBlockOutput, FlacTestCase, manifest},
|
||||
};
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
fn test_pictures(
|
||||
test_case: &FlacTestCase,
|
||||
fragment_size_range: Option<std::ops::Range<usize>>,
|
||||
|
||||
112
crates/pile-audio/src/nodes/extractcovers.rs
Normal file
112
crates/pile-audio/src/nodes/extractcovers.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use crate::flac::proc::pictures::FlacPictureReader;
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
pub struct ExtractCovers {}
|
||||
|
||||
impl NodeBuilder for ExtractCovers {
|
||||
fn build<'ctx>(&self) -> Box<dyn Node<'ctx>> {
|
||||
Box::new(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
// Inputs: "data" - Bytes
|
||||
#[async_trait]
|
||||
impl<'ctx> Node<'ctx> for ExtractCovers {
|
||||
async fn run(
|
||||
&self,
|
||||
ctx: &CopperContext<'ctx>,
|
||||
this_node: ThisNodeInfo,
|
||||
params: NodeParameters,
|
||||
mut input: BTreeMap<PortName, Option<PipeData>>,
|
||||
) -> Result<BTreeMap<PortName, PipeData>, RunNodeError> {
|
||||
//
|
||||
// Extract parameters
|
||||
//
|
||||
params.err_if_not_empty()?;
|
||||
|
||||
//
|
||||
// Extract arguments
|
||||
//
|
||||
let data = input.remove(&PortName::new("data"));
|
||||
if data.is_none() {
|
||||
return Err(RunNodeError::MissingInput {
|
||||
port: PortName::new("data"),
|
||||
});
|
||||
}
|
||||
if let Some((port, _)) = input.pop_first() {
|
||||
return Err(RunNodeError::UnrecognizedInput { port });
|
||||
}
|
||||
|
||||
trace!(
|
||||
message = "Inputs ready, preparing reader",
|
||||
node_id = ?this_node.id
|
||||
);
|
||||
|
||||
let mut reader = match data.unwrap() {
|
||||
None => {
|
||||
return Err(RunNodeError::RequiredInputNull {
|
||||
port: PortName::new("data"),
|
||||
});
|
||||
}
|
||||
|
||||
Some(PipeData::Blob { source, .. }) => source.build(ctx).await?,
|
||||
|
||||
_ => {
|
||||
return Err(RunNodeError::BadInputType {
|
||||
port: PortName::new("data"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Setup is done, extract covers
|
||||
//
|
||||
debug!(
|
||||
message = "Extracting covers",
|
||||
node_id = ?this_node.id
|
||||
);
|
||||
let mut picreader = FlacPictureReader::new();
|
||||
|
||||
while let Some(data) = reader.next_fragment().await? {
|
||||
picreader
|
||||
.push_data(&data)
|
||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
||||
}
|
||||
|
||||
picreader
|
||||
.finish()
|
||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
||||
|
||||
//
|
||||
// Send the first cover we find
|
||||
//
|
||||
|
||||
let mut output = BTreeMap::new();
|
||||
|
||||
if let Some(picture) = picreader.pop_picture() {
|
||||
debug!(
|
||||
message = "Found a cover, sending",
|
||||
node_id = ?this_node.id,
|
||||
picture = ?picture
|
||||
);
|
||||
|
||||
output.insert(
|
||||
PortName::new("cover_data"),
|
||||
PipeData::Blob {
|
||||
source: BytesProcessorBuilder::new(RawBytesSource::Array {
|
||||
mime: picture.mime.clone(),
|
||||
data: Arc::new(picture.img_data),
|
||||
}),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
message = "Did not find a cover, sending None",
|
||||
node_id = ?this_node.id
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(output);
|
||||
}
|
||||
}
|
||||
164
crates/pile-audio/src/nodes/extracttags.rs
Normal file
164
crates/pile-audio/src/nodes/extracttags.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use crate::{
|
||||
common::tagtype::TagType,
|
||||
flac::blockread::{FlacBlock, FlacBlockReader, FlacBlockSelector},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use copper_piper::{
|
||||
base::{Node, NodeBuilder, NodeParameterValue, PortName, RunNodeError, ThisNodeInfo},
|
||||
data::PipeData,
|
||||
helpers::NodeParameters,
|
||||
CopperContext,
|
||||
};
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
/// Extract tags from audio metadata
|
||||
pub struct ExtractTags {}
|
||||
|
||||
impl NodeBuilder for ExtractTags {
|
||||
fn build<'ctx>(&self) -> Box<dyn Node<'ctx>> {
|
||||
Box::new(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
// Inputs: "data" - Bytes
|
||||
// Outputs: variable, depends on tags
|
||||
#[async_trait]
|
||||
impl<'ctx> Node<'ctx> for ExtractTags {
|
||||
async fn run(
|
||||
&self,
|
||||
ctx: &CopperContext<'ctx>,
|
||||
this_node: ThisNodeInfo,
|
||||
mut params: NodeParameters,
|
||||
mut input: BTreeMap<PortName, Option<PipeData>>,
|
||||
) -> Result<BTreeMap<PortName, PipeData>, RunNodeError> {
|
||||
//
|
||||
// Extract parameters
|
||||
//
|
||||
|
||||
let tags = {
|
||||
let mut tags: BTreeMap<PortName, TagType> = BTreeMap::new();
|
||||
let val = params.pop_val("tags")?;
|
||||
|
||||
match val {
|
||||
NodeParameterValue::List(list) => {
|
||||
for t in list {
|
||||
match t {
|
||||
NodeParameterValue::String(s) => {
|
||||
tags.insert(PortName::new(s.as_str()), s.as_str().into());
|
||||
}
|
||||
_ => {
|
||||
return Err(RunNodeError::BadParameterType {
|
||||
parameter: "tags".into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(RunNodeError::BadParameterType {
|
||||
parameter: "tags".into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
tags
|
||||
};
|
||||
|
||||
params.err_if_not_empty()?;
|
||||
|
||||
//
|
||||
// Extract arguments
|
||||
//
|
||||
let data = input.remove(&PortName::new("data"));
|
||||
if data.is_none() {
|
||||
return Err(RunNodeError::MissingInput {
|
||||
port: PortName::new("data"),
|
||||
});
|
||||
}
|
||||
if let Some((port, _)) = input.pop_first() {
|
||||
return Err(RunNodeError::UnrecognizedInput { port });
|
||||
}
|
||||
|
||||
trace!(
|
||||
message = "Inputs ready, preparing reader",
|
||||
node_id = ?this_node.id
|
||||
);
|
||||
|
||||
let mut reader = match data.unwrap() {
|
||||
None => {
|
||||
return Err(RunNodeError::RequiredInputNull {
|
||||
port: PortName::new("data"),
|
||||
})
|
||||
}
|
||||
|
||||
Some(PipeData::Blob { source, .. }) => source.build(ctx).await?,
|
||||
|
||||
_ => {
|
||||
return Err(RunNodeError::BadInputType {
|
||||
port: PortName::new("data"),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Setup is done, extract tags
|
||||
//
|
||||
debug!(
|
||||
message = "Extracting tags",
|
||||
node_id = ?this_node.id
|
||||
);
|
||||
|
||||
let mut block_reader = FlacBlockReader::new(FlacBlockSelector {
|
||||
pick_vorbiscomment: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
while let Some(data) = reader.next_fragment().await? {
|
||||
block_reader
|
||||
.push_data(&data)
|
||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
||||
}
|
||||
|
||||
block_reader
|
||||
.finish()
|
||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
||||
|
||||
//
|
||||
// Return tags
|
||||
//
|
||||
|
||||
let mut output = BTreeMap::new();
|
||||
|
||||
while block_reader.has_block() {
|
||||
let b = block_reader.pop_block().unwrap();
|
||||
match b {
|
||||
FlacBlock::VorbisComment(comment) => {
|
||||
for (port, tag_type) in tags.iter() {
|
||||
if let Some((_, tag_value)) =
|
||||
comment.comment.comments.iter().find(|(t, _)| t == tag_type)
|
||||
{
|
||||
let x = output.insert(
|
||||
port.clone(),
|
||||
PipeData::Text {
|
||||
value: tag_value.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
// Each insertion should be new
|
||||
assert!(x.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `reader` filters blocks for us
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// We should only have one comment block
|
||||
assert!(!block_reader.has_block());
|
||||
}
|
||||
|
||||
return Ok(output);
|
||||
}
|
||||
}
|
||||
36
crates/pile-audio/src/nodes/mod.rs
Normal file
36
crates/pile-audio/src/nodes/mod.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
//! Pipeline nodes for processing audio files
|
||||
use copper_piper::base::{NodeDispatcher, RegisterNodeError};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
mod extractcovers;
|
||||
mod extracttags;
|
||||
mod striptags;
|
||||
|
||||
/// Register all nodes in this module into the given dispatcher
|
||||
pub fn register(dispatcher: &mut NodeDispatcher) -> Result<(), RegisterNodeError> {
|
||||
dispatcher
|
||||
.register_node(
|
||||
"StripTags",
|
||||
BTreeMap::new(),
|
||||
Box::new(striptags::StripTags {}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
dispatcher
|
||||
.register_node(
|
||||
"ExtractCovers",
|
||||
BTreeMap::new(),
|
||||
Box::new(extractcovers::ExtractCovers {}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
dispatcher
|
||||
.register_node(
|
||||
"ExtractTags",
|
||||
BTreeMap::new(),
|
||||
Box::new(extracttags::ExtractTags {}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
181
crates/pile-audio/src/nodes/striptags.rs
Normal file
181
crates/pile-audio/src/nodes/striptags.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
//! Strip all tags from an audio file
|
||||
|
||||
use crate::flac::proc::metastrip::FlacMetaStrip;
|
||||
use async_trait::async_trait;
|
||||
use copper_piper::{
|
||||
base::{Node, NodeBuilder, NodeId, PortName, RunNodeError, ThisNodeInfo},
|
||||
data::PipeData,
|
||||
helpers::{
|
||||
processor::{StreamProcessor, StreamProcessorBuilder},
|
||||
NodeParameters,
|
||||
},
|
||||
CopperContext,
|
||||
};
|
||||
use copper_util::MimeType;
|
||||
use smartstring::{LazyCompact, SmartString};
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tracing::debug;
|
||||
|
||||
/// Strip all metadata from an audio file
|
||||
pub struct StripTags {}
|
||||
|
||||
impl NodeBuilder for StripTags {
|
||||
fn build<'ctx>(&self) -> Box<dyn Node<'ctx>> {
|
||||
Box::new(Self {})
|
||||
}
|
||||
}
|
||||
|
||||
// Input: "data" - Blob
|
||||
// Output: "out" - Blob
|
||||
#[async_trait]
|
||||
impl<'ctx> Node<'ctx> for StripTags {
|
||||
async fn run(
|
||||
&self,
|
||||
_ctx: &CopperContext<'ctx>,
|
||||
this_node: ThisNodeInfo,
|
||||
params: NodeParameters,
|
||||
mut input: BTreeMap<PortName, Option<PipeData>>,
|
||||
) -> Result<BTreeMap<PortName, PipeData>, RunNodeError> {
|
||||
//
|
||||
// Extract parameters
|
||||
//
|
||||
params.err_if_not_empty()?;
|
||||
|
||||
//
|
||||
// Extract arguments
|
||||
//
|
||||
let data = input.remove(&PortName::new("data"));
|
||||
if data.is_none() {
|
||||
return Err(RunNodeError::MissingInput {
|
||||
port: PortName::new("data"),
|
||||
});
|
||||
}
|
||||
if let Some((port, _)) = input.pop_first() {
|
||||
return Err(RunNodeError::UnrecognizedInput { port });
|
||||
}
|
||||
|
||||
let source = match data.unwrap() {
|
||||
None => {
|
||||
return Err(RunNodeError::RequiredInputNull {
|
||||
port: PortName::new("data"),
|
||||
})
|
||||
}
|
||||
|
||||
Some(PipeData::Blob { source, .. }) => source,
|
||||
|
||||
_ => {
|
||||
return Err(RunNodeError::BadInputType {
|
||||
port: PortName::new("data"),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
debug!(
|
||||
message = "Setup done, stripping tags",
|
||||
node_id = ?this_node.id
|
||||
);
|
||||
|
||||
let mut output = BTreeMap::new();
|
||||
|
||||
output.insert(
|
||||
PortName::new("out"),
|
||||
PipeData::Blob {
|
||||
source: source.add_processor(Arc::new(TagStripProcessor {
|
||||
node_id: this_node.id.clone(),
|
||||
node_type: this_node.node_type.clone(),
|
||||
})),
|
||||
},
|
||||
);
|
||||
|
||||
return Ok(output);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TagStripProcessor {
|
||||
node_id: NodeId,
|
||||
node_type: SmartString<LazyCompact>,
|
||||
}
|
||||
|
||||
impl StreamProcessorBuilder for TagStripProcessor {
|
||||
fn build(&self) -> Box<dyn StreamProcessor> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StreamProcessor for TagStripProcessor {
|
||||
fn mime(&self) -> &MimeType {
|
||||
return &MimeType::Flac;
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"TagStripProcessor"
|
||||
}
|
||||
|
||||
fn source_node_id(&self) -> &NodeId {
|
||||
&self.node_id
|
||||
}
|
||||
|
||||
/// Return the type of the node that created this processor
|
||||
fn source_node_type(&self) -> &str {
|
||||
&self.node_type
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
mut source: Receiver<Arc<Vec<u8>>>,
|
||||
sink: Sender<Arc<Vec<u8>>>,
|
||||
max_buffer_size: usize,
|
||||
) -> Result<(), RunNodeError> {
|
||||
//
|
||||
// Strip tags
|
||||
//
|
||||
|
||||
let mut strip = FlacMetaStrip::new();
|
||||
let mut out_bytes = Vec::new();
|
||||
|
||||
while let Some(data) = source.recv().await {
|
||||
strip
|
||||
.push_data(&data)
|
||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
||||
|
||||
strip
|
||||
.read_data(&mut out_bytes)
|
||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
||||
|
||||
if out_bytes.len() >= max_buffer_size {
|
||||
let x = std::mem::take(&mut out_bytes);
|
||||
|
||||
match sink.send(Arc::new(x)).await {
|
||||
Ok(()) => {}
|
||||
|
||||
// Not an error, our receiver was dropped.
|
||||
// Exit early if that happens!
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
strip
|
||||
.finish()
|
||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
||||
|
||||
while strip.has_data() {
|
||||
strip
|
||||
.read_data(&mut out_bytes)
|
||||
.map_err(|e| RunNodeError::Other(Arc::new(e)))?;
|
||||
}
|
||||
|
||||
match sink.send(Arc::new(out_bytes)).await {
|
||||
Ok(()) => {}
|
||||
|
||||
// Not an error, our receiver was dropped.
|
||||
// Exit early if that happens!
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user