Auto-update fts index
Some checks failed
CI / Typos (push) Successful in 15s
CI / Clippy (push) Successful in 1m5s
CI / Build and test (push) Failing after 1m2s

This commit is contained in:
2026-02-21 15:55:10 -08:00
parent 5d8ad4665d
commit 141839ae55
36 changed files with 1119 additions and 275 deletions

View File

@@ -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,

View File

@@ -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())?;
}

View File

@@ -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>>,

View File

@@ -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(

View File

@@ -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(

View File

@@ -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,

View File

@@ -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;

View File

@@ -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"),
})
}
}

View File

@@ -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(());

View File

@@ -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(

View File

@@ -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

View File

@@ -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 {

View File

@@ -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>>,

View File

@@ -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>>,

View 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);
}
}

View 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);
}
}

View 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(());
}

View 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(());
}
}