Proper way to setup Popularimeter? #581
-
|
So for insertion I've done match tag.tag_type() {
// Check if the tag is an ID3 tag
TagType::Id3v2 => {
// Parse the rating value
let rating_value: u8 = match value.parse() {
Ok(val) => val,
Err(_) => {
return Err(LoftyError::new(ErrorKind::Id3v2(Id3v2Error::new(Id3v2ErrorKind::BadFrame("POPM".to_string(), "Input was not a valid rating value"))))); // Adjust with a meaningful error message
}
};
// Create a PopularimeterFrame
let popularimeter = PopularimeterFrame::new(String::new(), rating_value, 0).as_bytes()?;
// Insert the new PopularimeterFrame into the tag
tag.insert(TagItem::new(ItemKey::Popularimeter, ItemValue::Binary(popularimeter)));
}
_ => {
tag.insert_text(ItemKey::Popularimeter, value.clone());
}
}and this W.I.P for reading metadata.rating = match tag.tag_type() {
// Check if the tag is an ID3 tag
TagType::Id3v2 => {
// Attempt to retrieve the binary data for the Popularimeter frame
let binary_data = tag.get_binary(&ItemKey::Popularimeter, true);
// Check if binary_data is Some and has sufficient length
if let Some(data) = binary_data {
// Create a cursor to read from the binary data
let mut reader = Cursor::new(data);
// Parse the binary data into a PopularimeterFrame
match PopularimeterFrame::parse(&mut reader, FrameFlags::default()) {
Ok(popularimeter) => Some(popularimeter.rating),
Err(e) => {
println!("Error parsing PopularimeterFrame: {:?}", e); // Print the error
None // Return None on parse error
}
}
} else {
// Print an error if the binary data retrieval fails
println!("Error: Popularimeter frame not found in the tag.");
None // Return None if the frame is not found
}
}
_ => tag.get_string(&ItemKey::Popularimeter).and_then(|v| v.parse::<u8>().ok()),
};however, so far reading the even though the tag is there if I use external editors So what is the correct way to get this working? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 4 replies
-
|
So the docs about the POPM frames are actually outdated. They haven't been converted into The issue is some formats just use a single numeric star rating, others use an email + star rating, then there's ID3v2 which uses a star rating, email, and a play counter. I guess I could encode them in a special string format like So I'm imagining: // Rating scales are also different between formats, so
// they'll need to be unified in some way.
//
// Unfortunately, that *probably* means disallowing fractional ratings, since
// as far as I know, only some formats support them.
enum StarRating {
Zero,
One,
Two,
Three,
Four,
Five,
}
impl TryFrom<char> for StarRating {
type Error = ();
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'0' => Ok(Self::Zero),
'1' => Ok(Self::One),
'2' => Ok(Self::Two),
'3' => Ok(Self::Three),
'4' => Ok(Self::Four),
'5' => Ok(Self::Five),
_ => Err(()),
}
}
}
impl Display for StarRating {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(
f,
"{}",
match value {
Self::Zero => '0',
Self::One => '1',
Self::Two => '2',
Self::Three => '3',
Self::Four => '4',
Self::Five => '5',
}
)
}
}
// Generic popularimeter, almost identical to `PopularimeterFrame`, since
// it's the most feature packed implementation
struct Popularimeter {
email: Option<String>,
rating: StarRating,
/// NOTE: Only used in ID3v2, ...
counter: u64,
}
impl Display for Popularimeter {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "{}|{}|{}", self.email.unwrap_or(""), self.rating, self.counter)
}
}
impl Popularimeter {
// won't accept partial inputs, to encourage users to properly encode them
pub fn parse(input: &str) -> Option<Self> {
let mut parts = input.splitn(3, '|');
let Some(email) = parts.next() else {
return None;
};
let Some(rating) = parts
.next()
.and_then(|rating_str| rating_str.chars().next())
.and_then(|rating| StarRating::try_from(rating).ok())
else {
return None;
};
let Some(counter) = parts.next().and_then(|counter| counter.parse().ok()) else {
return None;
};
Some(Self {
email,
rating,
counter,
})
}
}ConversionSo if you read an ID3v2 tag with a POPM frame: PopularimeterFrame {
email: "foo@bar.com",
rating: 128,
counter: 50,
}It gets converted into a let popularimeter = Popularimeter {
email: "foo@bar.com",
rating: StarRating::Three,
counter: 50,
};Then into a string TagItem::new(ItemKey::Popularimeter, ItemValue::Text(popularimeter.to_string()));UsageThen the docs on impl Tag {
pub fn popularimeter(&self) -> Option<Popularimeter> {
self.get_string(ItemKey::Popularimeter).and_then(Popularimeter::parse)
}
}And then encourage insertion like: tag.insert_text(ItemKey::Popularimeter, Popularimeter {
email: "foo@bar.com",
rating: StarRating::Three,
counter: 50,
}.to_string());Rather than manual construction of the encoded string. Let me know if that flow makes sense to you. Sorry for the code dump, just jotting down the first thing that comes to mind. |
Beta Was this translation helpful? Give feedback.
-
|
@UnknownSuperficialNight Generic popularimeter support was added in #597 |
Beta Was this translation helpful? Give feedback.
@UnknownSuperficialNight Generic popularimeter support was added in #597