Giter Site home page Giter Site logo

rust-id3's Introduction

rust-id3

Build Status Crate Documentation

A library for reading and writing ID3 metadata.

Implemented Features

  • ID3v1 reading
  • ID3v2.2, ID3v2.3, ID3v2.4 reading/writing
  • MP3, WAV and AIFF files
  • Latin1, UTF16 and UTF8 encodings
  • Text frames
  • Extended Text frames
  • Link frames
  • Extended Link frames
  • Comment frames
  • Lyrics frames
  • Synchronised Lyrics frames
  • Picture frames
  • Encapsulated Object frames
  • Chapter frames
  • Unsynchronisation
  • Compression
  • MPEG Location Lookup Table frames
  • Unique File Identifier frames
  • Tag and File Alter Preservation bits

Examples

Reading tag frames

use id3::{Tag, TagLike};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let tag = Tag::read_from_path("testdata/id3v24.id3")?;

    // Get a bunch of frames...
    if let Some(artist) = tag.artist() {
        println!("artist: {}", artist);
    }
    if let Some(title) = tag.title() {
        println!("title: {}", title);
    }
    if let Some(album) = tag.album() {
        println!("album: {}", album);
    }

    // Get frames before getting their content for more complex tags.
    if let Some(artist) = tag.get("TPE1").and_then(|frame| frame.content().text()) {
        println!("artist: {}", artist);
    }
    Ok(())
}

Modifying any existing tag

use id3::{Error, ErrorKind, Tag, TagLike, Version};
use std::fs::copy;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let temp_file = std::env::temp_dir().join("music.mp3");
    copy("testdata/quiet.mp3", &temp_file)?;

    let mut tag = match Tag::read_from_path(&temp_file) {
        Ok(tag) => tag,
        Err(Error{kind: ErrorKind::NoTag, ..}) => Tag::new(),
        Err(err) => return Err(Box::new(err)),
    };

    tag.set_album("Fancy Album Title");

    tag.write_to_path(temp_file, Version::Id3v24)?;
    Ok(())
}

Creating a new tag, overwriting any old tag

use id3::{Tag, TagLike, Frame, Version};
use id3::frame::Content;
use std::fs::copy;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let temp_file = std::env::temp_dir().join("music.mp3");
    copy("testdata/quiet.mp3", &temp_file)?;

    let mut tag = Tag::new();
    tag.set_album("Fancy Album Title");

    // Set the album the hard way.
    tag.add_frame(Frame::text("TALB", "album"));

    tag.write_to_path(temp_file, Version::Id3v24)?;
    Ok(())
}

Handling damaged or files without a tag

use id3::{Tag, TagLike, partial_tag_ok, no_tag_ok};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let tag_result = Tag::read_from_path("testdata/id3v24.id3");

    // A partially decoded tag is set on the Err. partial_tag_ok takes it out and maps it to Ok.
    let tag_result = partial_tag_ok(tag_result);

    // no_tag_ok maps the NoTag error variant and maps it to Ok(None).
    let tag_result = no_tag_ok(tag_result);

    if let Some(tag) = tag_result? {
      // ..
    }

    Ok(())
}

Contributing

Do you think you have found a bug? Then please report it via the GitHub issue tracker. Make sure to attach any problematic files that can be used to reproduce the issue. Such files are also used to create regression tests that ensure that your bug will never return.

When submitting pull requests, please prefix your commit messages with fix: or feat: for bug fixes and new features respectively. This is the Conventional Commits scheme that is used to automate some maintenance chores such as generating the changelog and inferring the next version number.

Running tests

Tests require ffprobe (part of ffmpeg) to be present in $PATH.

cargo test --all-features

rust-id3's People

Contributors

999eagle avatar agersant avatar alyoshavasilieva avatar andrewradev avatar beckjake avatar cdown avatar danieljrmay avatar davidepedranz avatar deifactor avatar delapouite avatar dependabot[bot] avatar esgrove avatar gahag avatar holzhaus avatar jxs avatar lnicola avatar marekkon5 avatar martpie avatar mqus avatar newfla avatar orange-murker avatar paolobarbolini avatar pinkforest avatar polyfloyd avatar probablykasper avatar robfuscator avatar tehmatt avatar tianyishi2001 avatar uklotzde avatar wydengyre avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

rust-id3's Issues

StringDecoding([]): data is not valid utf-8

I'm trying to build a simple tool to embed cover art into mp3s, for my own personal usage. I'm running into an issue after writing the image -- once the Tag is written to that same file (it works, the cover art renders in my file manager and everything), the next time I try to read it with Tag::read_from_path, I get the following error:

StringDecoding([]): data is not valid utf-8

The full code is not a whole lot at the moment, and it looks like this: https://github.com/AndrewRadev/id3-image/blob/de5eae6d0c4fee18a15b5290208709780ec77bcf/src/main.rs

If I change the version to Id3v24, there's no problem writing, and then reading, tags from the file. Unfortunately, some other tools I use can't handle 2.4, and, ideally, I'd like to write tags using 2.3.

Weirdly enough, I can read embedded images in 2.3 tags in other mp3s, so maybe there's a bug when writing the image?

Cannot read chapter markers from file

I could have missed where this data might show up, but taking some sample podcasts from Mac Power Users. (e.g. https://www.podtrac.com/pts/redirect.mp3/traffic.libsyn.com/secure/relaympu/mpu554.mp3)

ffprobe on the file will output:

Input #0, mp3, from 'mpu554.mp3':
  Metadata:
    album           : Mac Power Users
    artist          : Mac Power Users
    title           : 554: Read-it-later Services
    comment         : Read-it-later services can be a great way to save and enjoy an article later, away from the noise of social media or an overflowing RSS client. This week, David and Stephen talk about some of the popular choices, and how to keep them from becoming just an
    lyrics-eng      : Read-it-later services can be a great way to save and enjoy an article later, away from the noise of social media or an overflowing RSS client. This week, David and Stephen talk about some of the popular choices, and how to keep them from becoming just an
    TLEN            : 5997000
    encoded_by      : Forecast
    date            : 2020
  Duration: 01:39:57.11, start: 0.000000, bitrate: 128 kb/s
    Chapter #0:0: start 0.000000, end 466.287000
    Metadata:
      title           : MPU 554
    Chapter #0:1: start 466.287000, end 964.340000
    Metadata:
      title           : Read-it-Later Services?
    Chapter #0:2: start 964.340000, end 1597.615000
    Metadata:
      title           : Safari Reading List
    Chapter #0:3: start 1597.615000, end 3042.409000
    Metadata:
      title           : Third-Party Services
    Chapter #0:4: start 3042.409000, end 3345.281000
    Metadata:
      title           : What Weโ€™re Using
    Chapter #0:5: start 3345.281000, end 4158.417000
    Metadata:
      title           : Davidโ€™s Research Workflow
    Chapter #0:6: start 4158.417000, end 5997.000000
    Metadata:
      title           : Appleโ€™s September
    Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 128 kb/s
    Stream #0:1: Video: mjpeg (Baseline), yuvj444p(pc, bt470bg/unknown/unknown), 1400x1400, 90k tbr, 90k tbn, 90k tbc (attached pic)
    Metadata:
      comment         : Cover (front)

I cannot figure out how to get that chapter data out of the Tag interface

Remove all pictures

There seems to be only one remove_picture_by_type method in v0.5.1. I guess it would be helpful (at least to me) to have a remove_all_pictures method. This will be a simple change and I will make a PR if we agree to implement this.

SYLT (synchronized lyrics/text) support?

Hello, and thank you for writing a really useful Rust library!

I'm currently working on a subtitle processing tool for students of foreign languages, and I'd love to add support for the id3v2.3 SYLT tag, which handles synchronized lyrics. Is there any easy way to access these fields using your library? I'd be perfectly happy to just get & set the raw binary data, if that's easiest for you, but I'd also be interested in contributing a nice API if you wish.

Thank you once again for contributing to the Rust ecosystem!

ffprobe reports junk in mp3 after retag

I'm trying to edit some tags on an MP3 file. I'm calling Tag::set_title and then Tag::write_to_path. I ran ffprobe on a file before and after changing the title, and afterwards it seems to find errors in the tag.
Before:
2020-05-21-211544
After:
2020-05-21-211530

After changing the tags using id3, there is "junk" in the file and ffprobe can no longer determine duration. In fact, the before and after differ by about 9 seconds. Furthermore, it seems that the encoding details were lost (noticed "LAME3.99r" missing from the after).

If you have any idea what's causing this, I'd love to help with a fix (or learn how I'm using the library wrong haha).

You can find the code I was using here.

Return partial tag information after decoding errors

Hello!

I use rust-id3 for https://github.com/agersant/polaris and it is very valuable to me. Thank you for writing it. A situation that has happened a few times is that users notice some of their files are not displaying any metadata in Polaris, even though they work in other media players.

The root cause is that their files have invalid tags (due to subtle encoding errors, usually about unsynchronization or synchsafe integers). When this happens, rust-id3 returns an error and no information is available - even though many valid frames may have been decoded before running into the error.

Would it be possible to expose whatever was decoded in the output of rust-id3 (either within the error or as a separate API call altogether)?

Many thanks!

StringDecoding error for an mp3 file with no tags

I'm having problems parsing this mp3 file (zipped for github): broken.zip

When running the id3 command on it (https://squell.github.io/id3/), I get no tags:

% id3 -v broken.mp3
#file	broken.mp3
#tag	ID3v2 3.0

I'm actually not sure how I removed them, but I know I did it for testing purposes for my project id3-image. My tests were running fine and updating from 0.5.1 to 0.5.3, I started getting this error in the test suite. It's also failing on the latest published version, 0.6.2. Here's some code I'm testing with

fn main() {
    let tag = id3::Tag::read_from_path("./broken.mp3").unwrap();
    println!("{:?}", tag);
}

This fails with:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: StringDecoding([]): data is not valid utf-8', src/main.rs:2:56
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

It's not a huge problem, because I just used a different file to test with that seems to work. I don't know why this one fails, though. There might be something malformed, but it does seem to parse fine in 0.5.1, and I'm not sure what changed. Note that putting "0.5.1" in the cargo file actually seems to install 0.5.3, so to test, you might need to use a local path and switch git tags.

Panic while saving an ID3v2.4 tag

Updated to latest commit from master branch and got a panic. This seems to also happen in v0.2 and v0.1.12. It seems to be easily reproducible with this file.

Stack Trace

	0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
			 at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
	1: std::sys_common::backtrace::_print
			 at /checkout/src/libstd/sys_common/backtrace.rs:71
	2: std::panicking::default_hook::{{closure}}
			 at /checkout/src/libstd/sys_common/backtrace.rs:60
			 at /checkout/src/libstd/panicking.rs:381
	3: std::panicking::default_hook
			 at /checkout/src/libstd/panicking.rs:397
	4: std::panicking::rust_panic_with_hook
			 at /checkout/src/libstd/panicking.rs:577
	5: std::panicking::begin_panic
			 at /checkout/src/libstd/panicking.rs:538
	6: std::panicking::begin_panic_fmt
			 at /checkout/src/libstd/panicking.rs:522
	7: rust_begin_unwind
			 at /checkout/src/libstd/panicking.rs:498
	8: core::panicking::panic_fmt
			 at /checkout/src/libcore/panicking.rs:71
	9: core::str::slice_error_fail
			 at /checkout/src/libcore/str/mod.rs:2218
	10: core::str::traits::<impl core::slice::SliceIndex<str> for core::ops::range::RangeTo<usize>>::index::{{closure}}
			 at /checkout/src/libcore/str/mod.rs:1941
	11: <core::option::Option<T>>::unwrap_or_else
			 at /checkout/src/libcore/option.rs:370
	12: core::str::traits::<impl core::slice::SliceIndex<str> for core::ops::range::RangeTo<usize>>::index
			 at /checkout/src/libcore/str/mod.rs:1941
	13: core::str::traits::<impl core::ops::index::Index<core::ops::range::RangeTo<usize>> for str>::index
			 at /checkout/src/libcore/str/mod.rs:1705
	14: <alloc::string::String as core::ops::index::Index<core::ops::range::RangeTo<usize>>>::index
			 at /checkout/src/liballoc/string.rs:1863
	15: id3::stream::frame::content::comment_to_bytes
			 at /home/omaha/.cargo/git/checkouts/rust-id3-61c2a240b6004f52/16e7ce7/src/stream/frame/content.rs:143
	16: id3::stream::frame::content::encode
			 at /home/omaha/.cargo/git/checkouts/rust-id3-61c2a240b6004f52/16e7ce7/src/stream/frame/content.rs:41
	17: id3::stream::frame::v4::encode
			 at /home/omaha/.cargo/git/checkouts/rust-id3-61c2a240b6004f52/16e7ce7/src/stream/frame/v4.rs:69
	18: id3::stream::frame::encode
			 at /home/omaha/.cargo/git/checkouts/rust-id3-61c2a240b6004f52/16e7ce7/src/stream/frame/mod.rs:57
	19: id3::stream::tag::Encoder::encode
			 at /home/omaha/.cargo/git/checkouts/rust-id3-61c2a240b6004f52/16e7ce7/src/stream/tag.rs:119
	20: id3::tag::Tag::write_to
			 at /home/omaha/.cargo/git/checkouts/rust-id3-61c2a240b6004f52/16e7ce7/src/tag.rs:1371
	21: id3::tag::Tag::write_to_path
			 at /home/omaha/.cargo/git/checkouts/rust-id3-61c2a240b6004f52/16e7ce7/src/tag.rs:1388

Could id3 correctly parse tag generated by audacity on CJK system?

Hi, all:

I modified one audio in the audacity, also change the metadata by using audacity editor and saved as abc.mp3:

metadata editor

here is the plain text:

  • title: ็Ž‹ๅฐๆ˜Ž็‹ฌๅ”ฑ
  • album: ็Ž‹ๅฐๆ˜Žไธ“่พ‘
  • artist: ็Ž‹ๅฐๆ˜Ž

however, I used id3 to parse the tag:

let tag = Tag::read_from_path("abc.mp3");
dbg!(tag);

it said

[src/main.rs:78] Tag::read_from_path(path) = Err(
    StringDecoding([]): data is not valid utf-8,
)

So is there a way to parse tag correctly? or it is the issue of audacity?

thanks.

Stream info

Firstly I would like to thank you for this excellent crate and for reaching version 1.

I am working on a library that unifies metadata from different file formats into on struct. I'm using https://github.com/jameshurst/rust-metaflac for reading FLAC files and it has useful streaminfo data (sample rate, channels, lenght). Apparently mp3 has streaminfo in header: https://en.wikipedia.org/wiki/MP3#File_structure. Would it be feasible to provide it in this crate too or is it out of scope?

Get the list of artists / genres more easily?

Hello ๐Ÿ‘‹

First of all, I am very new to Rust (started a couple of days ago), so I may ask something stupid.

I'm playing with rewriting Museeks with Tauri (Rust), and I see some inconsistencies between the library I'm using with Node.js and this one, and I wanted to know if this was a limitation by design, or just a missing feature :)

Example: https://github.com/Borewit/music-metadata/blob/1e1bd78dc5abeea49dfe0402681f39da64aa60b4/lib/type.ts#L90-L93

A file can have multiple artists or genres, and when getting the genres of a track, everything gets joined,

example: ["Rock", "Pop"] becomes with rust-id3 "RockPop".

Screenshot 2022-05-11 at 19 17 46

Could rust-id3 provide some helpers to return arrays instead? I think this could be done by adding plural methods artist() -> artists() so it's backwards compatible.

thanks in advance!

Support for multiple artists in ID3v2 2.3 & 2.4

The ID3v2 2.3 specification states that multiple artists can be specified in the TPE1 frame by separating with a "/" character.

In the ID3v2 2.4 specification any T* frame (apart from TXXX) can contain multiple values separated by a NULL character (dependant on character encoding).

Do you plan to support this? I'm happy to work on it and submit a pull request if you're interested in this.

Panic while reading tag

Updated to v0.2 today and it broke while going through files that worked with v0.1.12.

The culprit seems to be src\frame\mod.rs line 76. I'll be reverting back to 0.1.12 for the time being.

Stack trace

	0: std::sys_common::backtrace::_print
			 at C:\projects\rust\src\libstd\sys_common\backtrace.rs:94
	1: std::panicking::default_hook::{{closure}}
			 at C:\projects\rust\src\libstd\panicking.rs:380
	2: std::panicking::default_hook
			 at C:\projects\rust\src\libstd\panicking.rs:397
	3: std::panicking::rust_panic_with_hook
			 at C:\projects\rust\src\libstd\panicking.rs:577
	4: std::panicking::begin_panic<alloc::string::String>
			 at C:\projects\rust\src\libstd\panicking.rs:538
	5: std::panicking::begin_panic_fmt
			 at C:\projects\rust\src\libstd\panicking.rs:522
	6: std::panicking::rust_begin_panic
			 at C:\projects\rust\src\libstd\panicking.rs:498
	7: core::panicking::panic_fmt
			 at C:\projects\rust\src\libcore\panicking.rs:71
	8: core::panicking::panic
			 at C:\projects\rust\src\libcore\panicking.rs:51
	9: core::option::Option<str*>::unwrap<str*>
			 at C:\projects\rust\src\libcore\macros.rs:22
	10: id3::frame::Frame::with_content
			 at C:\Users\Javier Fajardo\.cargo\registry\src\github.com-1ecc6299db9ec823\id3-0.2.0\src\frame\mod.rs:76
	11: id3::stream::frame::v2::decode<std::io::buffered::BufReader<std::fs::File>>
			 at C:\Users\Javier Fajardo\.cargo\registry\src\github.com-1ecc6299db9ec823\id3-0.2.0\src\stream\frame\v2.rs:22
	12: id3::stream::frame::decode<std::io::buffered::BufReader<std::fs::File>>
			 at C:\Users\Javier Fajardo\.cargo\registry\src\github.com-1ecc6299db9ec823\id3-0.2.0\src\stream\frame\mod.rs:16
	13: id3::stream::tag::decode<std::io::buffered::BufReader<std::fs::File>>
			 at C:\Users\Javier Fajardo\.cargo\registry\src\github.com-1ecc6299db9ec823\id3-0.2.0\src\stream\tag.rs:76
	14: id3::tag::Tag::read_from<std::io::buffered::BufReader<std::fs::File>>
			 at C:\Users\Javier Fajardo\.cargo\registry\src\github.com-1ecc6299db9ec823\id3-0.2.0\src\tag.rs:1359
	15: id3::tag::Tag::read_from_path<std::path::Path*>
			 at C:\Users\Javier Fajardo\.cargo\registry\src\github.com-1ecc6299db9ec823\id3-0.2.0\src\tag.rs:1365

disc number / total discs support

Can support for disc number / total discs, be added?

The field for the disc number / total discs is TPOS (ID3v2.3, ID3v2.4) or TPA (ID3v2.2) and has exactly the same format as the track number.

The functions would be the as the same as the track functions i.e set_total_tracks & total_tracks but using the field for the disk number instead.

Version 1.0

Tracking issue for getting rust-id3 to a state where it can be considered stable.

ID3 API

  • CHAP support (#44)
  • Trait for sharing frame convencience methods between Tag and Chapter
  • Error description str -> String
  • Reorganise tag reading functions
  • Fix name related allow(*) annotations
  • frame::Content allows new variants to be added without API breakage
  • Frame should check whether the ID is allowed for its Content
  • #24
  • POPM Frames

Naming (crate aligns with Rust naming conventions)

  • Casing conforms to RFC 430 (C-CASE)
  • Ad-hoc conversions follow as_, to_, into_ conventions (C-CONV)
  • Getter names follow Rust convention (C-GETTER)
  • Methods on collections that produce iterators follow iter, iter_mut, into_iter (C-ITER)
  • Iterator type names match the methods that produce them (C-ITER-TY)
  • Feature names are free of placeholder words (C-FEATURE)
  • Names use a consistent word order (C-WORD-ORDER)

Interoperability (crate interacts nicely with other library functionality)

  • Types eagerly implement common traits (C-COMMON-TRAITS)
  • -> Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, Default
  • Conversions use the standard traits From, AsRef, AsMut (C-CONV-TRAITS)
  • Collections implement FromIterator and Extend (C-COLLECT)
  • Data structures implement Serde's Serialize, Deserialize (C-SERDE)
  • Types are Send and Sync where possible (C-SEND-SYNC)
  • Error types are meaningful and well-behaved (C-GOOD-ERR)
  • Binary number types provide Hex, Octal, Binary formatting (C-NUM-FMT)
  • Generic reader/writer functions take R: Read and W: Write by value (C-RW-VALUE)

Macros (crate presents well-behaved macros)

  • Input syntax is evocative of the output (C-EVOCATIVE)
  • Macros compose well with attributes (C-MACRO-ATTR)
  • Item macros work anywhere that items are allowed (C-ANYWHERE)
  • Item macros support visibility specifiers (C-MACRO-VIS)
  • Type fragments are flexible (C-MACRO-TY)

Documentation (crate is abundantly documented)

  • Crate level docs are thorough and include examples (C-CRATE-DOC)
  • All items have a rustdoc example (C-EXAMPLE)
  • Examples use ?, not try!, not unwrap (C-QUESTION-MARK)
  • Function docs include error, panic, and safety considerations (C-FAILURE)
  • Prose contains hyperlinks to relevant things (C-LINK)
  • Cargo.toml includes all common metadata (C-METADATA)
  • -> authors, description, license, homepage, documentation, repository, keywords, categories
  • Release notes document all significant changes (C-RELNOTES)
  • Rustdoc does not show unhelpful implementation details (C-HIDDEN)

Predictability (crate enables legible code that acts how it looks)

  • Smart pointers do not add inherent methods (C-SMART-PTR)
  • Conversions live on the most specific type involved (C-CONV-SPECIFIC)
  • Functions with a clear receiver are methods (C-METHOD)
  • Functions do not take out-parameters (C-NO-OUT)
  • Operator overloads are unsurprising (C-OVERLOAD)
  • Only smart pointers implement Deref and DerefMut (C-DEREF)
  • Constructors are static, inherent methods (C-CTOR)

Flexibility (crate supports diverse real-world use cases)

  • Functions expose intermediate results to avoid duplicate work (C-INTERMEDIATE)
  • Caller decides where to copy and place data (C-CALLER-CONTROL)
  • Functions minimize assumptions about parameters by using generics (C-GENERIC)
  • Traits are object-safe if they may be useful as a trait object (C-OBJECT)

Type safety (crate leverages the type system effectively)

  • Newtypes provide static distinctions (C-NEWTYPE)
  • Arguments convey meaning through types, not bool or Option (C-CUSTOM-TYPE)
  • Types for a set of flags are bitflags, not enums (C-BITFLAG)
  • Builders enable construction of complex values (C-BUILDER)

Dependability (crate is unlikely to do the wrong thing)

  • Functions validate their arguments (C-VALIDATE)
  • Destructors never fail (C-DTOR-FAIL)
  • Destructors that may block have alternatives (C-DTOR-BLOCK)

Debuggability (crate is conducive to easy debugging)

  • All public types implement Debug (C-DEBUG)
  • Debug representation is never empty (C-DEBUG-NONEMPTY)

Future proofing (crate is free to improve without breaking users' code)

  • Sealed traits protect against downstream implementations (C-SEALED)
  • Structs have private fields (C-STRUCT-PRIVATE)
  • Newtypes encapsulate implementation details (C-NEWTYPE-HIDE)
  • Data structures do not duplicate derived trait bounds (C-STRUCT-BOUNDS)

Necessities (to whom they matter, they really matter)

  • Public dependencies of a stable crate are stable (C-STABLE)
  • Crate and its dependencies have a permissive license (C-PERMISSIVE)

Unified ID3v1/ID3v2 API

Following the discussion on #86 on trying to unify the API with ID3 v1 and v2 in a single call.

I'm coming from @Borewit's awesome music-metadata, which is more the scope of rust-audiotags than rust-id3, but is an awesome source of inspiration when it comes to handling weird edgecases with file formats.

Some code pointers:

I usually work at a much higher level of abstraction (I build UIs, not interacting much low-level operating system stuff), so my knowledge is quite limited here, but if I can be of any help, please ask!

AIFF support

1st of all, love the library, works great!
But we're hoping to use it aswell with AIFF format, since it's using ID3, but with offsets.
Keep up the good work!

ID3v2.2 compression support unsupported, but I have a v2.4 file

Hi there! I'm trying to read ID3 tags as part of building a music organisation tool. This library looks great for that, but when actually trying to read an example MP3 file, I get UnsupportedFeature: id3v2.2 compression is not supported.

Looking at the related code in stream/tag.rs, it's clear that this trips over based on the flag, but mutagen, at least, says this is a id3v2.4 tag, not 2.2:

In [15]: x = mutagen.id3.ID3("/tmp/mp3/07 ไฝ“ๆธฉ.mp3")
In [16]: x.version
Out[16]: (2, 4, 0)

I thought that maybe this is really just Flags::EXTENDED_HEADER, due to the shenanigans documented in that file with it sharing the same bit flag, but commenting out the Flags::COMPRESSION check is no good (well, we no longer return an error, but now all fields just return None).

This makes me wonder whether this is really just a v2.2 thing, since it also shows up on a v2.4 file. Maybe I'm misunderstanding how this is supposed to work, though.

I'm happy to implement compression support, but I can't actually seem to find any canonical information on how v2.2 compression is actually supposed to work. I at least found this in the id3v2 informal spec:

The second bit (bit 6) is indicating whether or not compression is used; a set bit indicates usage. Since no compression scheme has been decided yet, the ID3 decoder (for now) should just ignore the entire tag if the compression bit is set.

I... don't know what to make of this statement, or what it means pragmatically. ๐Ÿ˜•

So, questions/next steps:

  1. What's up with this being listed as a v2.2 feature, but this being a v2.4 tag? Is it really v2.2+, or am I misunderstanding how this works?
  2. Since Mutagen can read this ID3 tag ok, I will keep on digging through the code there to find out where/how this is handled. Any others pointers would be much appreciated though.

Year in `TDRC`

I have an mp3 file with a year I set through iTunes, but running tag.year() returned None. The reason is that the year is in TDRC. iTunes seems to use TDRC, and VLC detected the year from TDRC as well.

rust-id3 seems to use TYER to get the year. Not sure how common TYER is, but it doesn't seem to be part of the id3v2.4 spec whereas TDRC is.

TDRC is a timestamp, which according to the spec will always start with yyyy.

Track numbers lower than 10 cannot be parsed

Steps to reproduce:

  1. Read the id3 tag from the attached example file 9.mp3
  2. Call tag.track() on the resulting tag
  3. Observe that the track number comes back as None, even though the TRCK frame has the value 9.

Sample code:

let tag = Tag::read_from_path("testdata/10.mp3").unwrap();
assert_eq!(tag.track(), Some(10)); // โœ”
let tag = Tag::read_from_path("testdata/9.mp3").unwrap();
assert_eq!(tag.track(), Some(9)); // โŒ

Looking into this a tiny bit deeper, I noticed that the frame text is read as 9\u{0} instead of just 9, which makes the parsing fail.

I am also attaching a file with track number 10, which works fine (for comparison). Both files were tagged using foobar2000 v1.6.6 on Windows. Also note that specifying a value for total tracks makes this bug go away.

track numbers.zip

MLLT support

Would you consider adding a content type for reading and writing MLLT tags?

The quirky thing about this tag is probably that the reference entries contain integers that can be dynamically sized, up to 256 bit. Their combined size has to be a multiple of four, but it seems like the integers themselves could be anything from 1 to 255 bits long (or maybe even 0 to 256? I'm not quite sure how to interpret the tag docs).

So supporting this would probably mean a fair amount of bit-twiddling and depending on something like num-bigint.

A middle way could be to support the meta fields, but decode the references into a single blob that consumers can then further encode/decode themselves.

Typo

Hello, there is an error on sample :

// print the artist the hard weay
println!("{}", tag.get("TALB").unwrap().contents.text());

it is "content", not "contents"

Display trait implementation uses debug formatter

I'm quite new to Rust, so I'm not sure if this is an actual issue - you can close it if you think it's not. Anyways, here are my thoughts:

I'm writing a library that abstracts every possible Error in a custom Error enum. When I try to read a tag from a file that does not exist, I receive an id3::Error that wraps around an io::Error. If, in my library, I format this Error using write!(f, "{}", err);, I get a String that looks like this:

Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })

If I format the same io::Error that is not wrapped up in your id3::Error, the String looks like this:

No such file or directory (os error 2)

I think this is due to your implementation of the Display trait, where you are using the debug formatter ("{:?}"):

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

I'm not sure if it is intended like this, or even best practice (as mentioned, I'm new to Rust). But as a user of your library I think it would be more convenient if I get the cleaner message using the default formatter (and maybe the 'uncleaner' message using the debug formatter).

Hope this makes sense.

--- edit:

I'd love to help on this one, if it is one ๐Ÿ˜„ .

Implement Display trait on `Tag`, `Frame`, etc?

Hello,

First of all thank you (and all the other contributors) for such a great crate.

I have written a bit of software in Rust which can examine and play DSF files. I use rust-id3 to read the ID3v2 tag which can be embedded in them. I have a collection of functions which I use to output a human readable version of Tag to the console. It currently looks like this (with some elisions for clarity):

DSF Metadata
โ€ฆ
Fmt chunk:
Channel type = Stereo: FL, FR.
Channel number = 2
Sampling frequency = 2822400 Hz
Bits per sample = 1
Sample count per channel = 648523944
Block size per channel = 4096 bytes
Calculated duration = 00:03:49;2194344 h:min:s;samples
โ€ฆ
ID3Tag:
TALB: Album/Movie/Show title = Acoustic Trio - DSD Sessions
TIT2: Title/songname/content description = Rodeo On A Ridge
TPE1: Lead performer(s)/Soloist(s) = David Elias
TCON: Content type = Independent Acoustic
TYER: Year = 2006
TDAT: Date = 1711
TIME: Time = 1048
APIC: Attached picture = 
	Format: image/jpeg
	Type: Other
	Description: 
	Size: 357673 bytes

It occurred to me that this sort of functionality might be useful to other users of rust-id3.

However, before I fork rust-id3 I wanted to ask if you were in principle interested in accepting a pull request which implemented this kind of thing?

To give you and idea of what I am thinking:

  1. Implement the Display trait on Tag, Frame, and the relevant structs and enums in the id3::frame module.
  2. Perhaps add a name() method to Frame which would return a human readable string for the frame Id. Or this could be done by a stand-alone function e.g. get_name(id: &str) -> &'static str where get_name("TALB") would return "Album/Movie/Show title", etc.

Obviously the precise format of the string returned by tag.to_string() is a bit of a matter of taste (and I am open to suggestions, I have tried to stick to the ID3v2 specs for the names), but having a default Display would certainly be useful to me, and perhaps could be useful to others.

However, you may feel that this functionality does not belong in rust-id3 and is something which is more properly done outside of it.

Let me know what you think and whether you would be interested in looking at a pull request with this sort of thing implemented.

Thank you again and best wishes,

Dan

null value separator reading doesn't work properly

Hello, if I use a null (\0) separator for my values, then save and load again, the read frame only has the first value.
Example / test:

#[test]
fn test_null() {
    let mut tag = Tag::new();
    tag.add_frame(Frame::text("TPE1", "artist 1\0artist 2\0artist 3"));
    let mut buf = vec![];
    tag.write_to(std::io::Cursor::new(&mut buf), Version::Id3v23).unwrap();
    let tag = Tag::read_from(&buf[..]).unwrap();
    assert_eq!(tag.text_for_frame_id("TPE1").unwrap(), "artist 1\0artist 2\0artist 3");
}

Result:

left: `"artist 1"`,
right: `"artist 1\u{0}artist 2\u{0}artist 3"`'

Thanks

m4a files fail to be parsed

Hi,

I've been learning rust and wanted to mess around with this library. However, all my media is in an m4a format from iTunes. The following seems to not work whatsoever:

use std::path::Path;

use id3::{Tag, Version};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // this is relative to the current directory
    let path = Path::new("./10 I Write Sins Not Tragedies.m4a");

    let tag = Tag::read_from_path(path)?;

    // Get a bunch of frames...
    if let Some(artist) = tag.artist() {
        println!("artist: {}", artist);
    }

    if let Some(title) = tag.title() {
        println!("title: {}", title);
    }

    if let Some(album) = tag.album() {
        println!("album: {}", album);
    }

    // Get frames before getting their content for more complex tags.
    if let Some(artist) = tag.get("TPE1").and_then(|frame| frame.content().text()) {
        println!("artist: {}", artist);
    }

    Ok(())
}

When I run this, I get Error: NoTag: reader does not contain an id3 tag. Though a similar library written in python can find the id3 tag for this file and all the others. I'm not sure if this is specifically due to the m4a format or what, but If there's something I did wrong, please let me know.

Use of unstable stream_position

For a project of mine I'm using rust (stable) and when building I get the following message:

error[E0658]: use of unstable library feature 'seek_convenience'
...
let id3_tag_pos = file.stream_position()?;

When checking out the function it says "this is equivalent to self.seek(SeekFrom::Current(0))." which would work in stable.
How about replacing the stream_position calls with seek(SeekFrom::Current(0)) to make this library fully compatible with stable rust?

Have you tried this/what is the reason this unstable feature is needed?

Allocations based on untrusted input

For example, src/frame/stream/v3.rs::read allocates a vector of size content_size, which is read directly from the input. As this can cause an unhandleable OOM error, it's rather inconvenient.

I've solved this for myself by putting arbitrary size bounds at the locations that are causing me problems, but there's probably a better solution.

Should duration be calculated manually if TLEN is zero?

WTTS, duration is never set in most of the files I know (and it makes little sense as duration depends on the total bytes + biterate. Having a file of 5mn but with a TLEN of 2mn is weird to me.

Do you think it should be calculated with (or fallback to?) total_samples / sample_instead instead of getting TLEN?

Buffering

Hello, when writing a tag to file it seems the process isn't buffered enough - there is a lot of 1000B reads and writes to the file, which aren't great for performance and the disk either. This only happens if the tag is bigger (for example adding new frames or making existing ones longer).

My test:

let mut tag = Tag::read_from_path("test.mp3")?;
// Originally was only `Example`
tag.set_title("Example Example Example Example");
tag.write_to_path("test.mp3", Version::Id3v24)?;

And to check:

cargo build --release
strace target/release/rustid3test 2> trace

You can see in the trace that there is a lot of small 1000B read and write syscalls. It is even more noticeable when working with actual audio files - 15MB file caused ~6800 read and write syscalls. I know that the OS buffers some, however not always and it is noticeable on slow storages. Also other libraries don't have this problem (Python's mutagen).

Is there some way to increase the buffer size or more efficiently copy the data? I've wanted to add a BufWriter to write_to_path, however - PlainStorage requires Read and Write, and BufReader or BufWriter has only one of these traits. And changing that requirement would probably require a lot of changes.

Thanks

UFID

Hey, maybe this is a dumb question but how do I get the UFID tag?

Not all tags are processed

Hi,
When using the library I noticed that some tags do not have any value even though other tools show values.

I first thought that the change below fixed it:

`diff --git a/src/stream/tag.rs b/src/stream/tag.rs
index 552954c..3d495eb 100644
--- a/src/stream/tag.rs
+++ b/src/stream/tag.rs
@@ -93,7 +93,7 @@ pub fn decode(mut reader: impl io::Read) -> crate::Result {
}
} else {
let mut tag = Tag::new();

  •    while offset < tag_size + tag_header.len() {
    
  •    while offset < tag_size * tag_header.len() {
           let rs = frame::decode(
               &mut reader,
               version,
    

`
When I debugged it I could see that, for the mp3 file I was using, tag_header contained 10 items, but only the first 6 were being processed because the while loop was exited too early.

With the above fix, 'cargo test' passed and a test I had added that previously failed now passed.

I now think my fix isn't right because it assumes that all tags are the same size and that doesn't seem to be true.
My current guess is that an APIC tag with a large amount of data is the cause of the problem.

What always happens is that the loop in stream/tag.rs exits too early.

Regards,

Graeme

Is there a commandline utility for making use of this library?

I currently use python-eyeD3 for setting id3 tags.

Is there a linux command line application/script that utilizes this library?

I normally use a bash script I wrote that utilizes eyeD3, but im wanting to try something else:

eyeD3 --quiet --no-color -2 --to-v2.3 -a "$artist" -b "$artist" -A "$album" -t "$title" -Y "$year" -n "$track" -G "$genreid" "$f"

I apologize if I somehow missed this info, did not see any tools listed that make use of the library.

Parsing of mp3 files from Beatport fails: delimiter not found

I just tested some mp3 songs from Beatport, but can't process any of them. On Tag::read_from_path() I get Error: Parsing: delimiter not found. I'm using v1.0.2, but had same issue with v0.6.4 a while ago. Didn't have problems with mp3's from other sources so far. Any help is much appreciated!

remove year, date_recorded, date_released

All readable and settable fields are also removable, except for these three fields. Is there a particular reason for this? If not I'll make a PR to add a .remove_*() method for these three fields.

Should `Tag::is_candidate` and `v1::Tag::is_candidate` consume their reader?

Tag::is_candidate and v1::Tag::is_candidate don't take a reference to the reader which means the reader can't be used anymore (correct me if I'm wrong; very new to Rust). Is there a reason for not accepting it as &mut R?

I'm asking because I'm writing some code to figure out the type of an mp3 file (bare mp3 without any tags, mp3 with only a v1 tag, or mp3 with a v2 tag). To distinguish these I want to use the methods for Tag and v1::Tag on the same reader but that doesn't work. Creating a new reader for each call seems inefficient.

v1.1.2 no rules expected / `Flags` build errors

Got a build error when upgrading from 1.1.1 to 1.1.2

error: no rules expected the token `struct`
  --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:27:5
   |
27 |     struct ExtFlags: u8 {
   |     ^^^^^^ no rules expected this token in macro call

error[E0433]: failed to resolve: use of undeclared type `Flags`
  --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:88:57
   |
88 |         if version == Version::Id3v22 && flags.contains(Flags::COMPRESSION) {
   |                                                         ^^^^^ use of undeclared type `Flags`

error[E0433]: failed to resolve: use of undeclared type `Flags`
  --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:96:29
   |
96 |         dbg!(flags.contains(Flags::EXTENDED_HEADER));
   |                             ^^^^^ use of undeclared type `Flags`

error[E0433]: failed to resolve: use of undeclared type `Flags`
  --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:97:49
   |
97 |         let ext_header_size = if flags.contains(Flags::EXTENDED_HEADER) {
   |                                                 ^^^^^ use of undeclared type `Flags`

error[E0433]: failed to resolve: use of undeclared type `ExtFlags`
   --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:109:30
    |
109 |             let _ext_flags = ExtFlags::from_bits_truncate(ext_header[5]);
    |                              ^^^^^^^^ use of undeclared type `ExtFlags`

error[E0433]: failed to resolve: use of undeclared type `Flags`
   --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:142:38
    |
142 |             if header.flags.contains(Flags::UNSYNCHRONISATION) {
    |                                      ^^^^^ use of undeclared type `Flags`

error[E0433]: failed to resolve: use of undeclared type `Flags`
   --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:151:74
    |
151 |             let mut reader: Box<dyn io::Read> = if header.flags.contains(Flags::UNSYNCHRONISATION) {
    |                                                                          ^^^^^ use of undeclared type `Flags`

error[E0433]: failed to resolve: use of undeclared type `Flags`
   --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:298:19
    |
298 |         flags.set(Flags::UNSYNCHRONISATION, self.unsynchronisation);
    |                   ^^^^^ use of undeclared type `Flags`

error[E0433]: failed to resolve: use of undeclared type `Flags`
   --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:300:23
    |
300 |             flags.set(Flags::COMPRESSION, self.compression);
    |                       ^^^^^ use of undeclared type `Flags`

error[E0412]: cannot find type `Flags` in this scope
  --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:36:12
   |
36 |     flags: Flags,
   |            ^^^^^ not found in this scope
   |
help: consider importing one of these items
   |
1  | use crate::stream::frame::v3::Flags;
   |
1  | use crate::stream::frame::v4::Flags;
   |

error[E0433]: failed to resolve: use of undeclared type `Flags`
  --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:83:21
   |
83 |         let flags = Flags::from_bits(header[5])
   |                     ^^^^^ not found in this scope
   |
help: consider importing one of these items
   |
1  | use crate::stream::frame::v3::Flags;
   |
1  | use crate::stream::frame::v4::Flags;
   |

error[E0433]: failed to resolve: use of undeclared type `Flags`
   --> /Users/k/.cargo/registry/src/github.com-1ecc6299db9ec823/id3-1.1.2/src/stream/tag.rs:297:25
    |
297 |         let mut flags = Flags::empty();
    |                         ^^^^^ not found in this scope
    |
help: consider importing one of these items
    |
1   | use crate::stream::frame::v3::Flags;
    |
1   | use crate::stream::frame::v4::Flags;

Thank you for writing this!

I'm doing an automated mass reorganization of my music library, and I needed something in rust to read MP3 tags, and this has worked great.

Thank you!

can't add frames with ids in DEFAULT_FILE_DISCARD

I have a problem trying to add a MLLT frame to an audio file.

This expression is what i'm talking about specifically:

let saved_frames = tag.frames()
    .filter(|frame| {
        !(frame.tag_alter_preservation()
          || (frame.file_alter_preservation()
              || DEFAULT_FILE_DISCARD.contains(&frame.id())))
    });

This makes it impossible to use the crate to add frames from DEFAULT_FILE_DISCARD, which contains MLLT.

This filter should probably only be applied to frames that were initially read from a file, and only if the tag is modified. I'm also not sure this needs to check the file alter preservation flag at all, since we are only modifying the tag, right?

Possible improvements

So far, this is the best Rust library for reading ID3 tags that I've seen, but it could still use some work.

I'd like to help out by refactoring the code to make it more compliant to Rust's standards. Of course I could just create a fork, but before I do so, wanted to check with you whether you're open to merging the improvements proposed I propose:

Mutable state:

  • Frame::new creates a new frame with it's content set to Unknown with no content, after this, manual initialization is needed which requires the frame to be mutable
  • Tag::set_version works by performing a lot of internal mutations, which can fail, on the frames it contains. which will let the frame end up in an undefined state. A better approach would be to introduce a new initializer like Tag::from_tag which returns a Result::Err on failure

The internals are not well hidden. The public API should expose only the types and functions absolutely needed to interact with the library. Having lots of public internals also clutters the documentation:

  • Frame::uuid and generate_uuid
  • Picture::from_u8 and Encoding::from_u8
  • Flags::from_byte and Flags::to_byte
  • Frame::content_to_bytes
  • Tag::frames breaks encapsulation by returning a reference to the contained vector instead of a slice or iterator.
  • The encoding of individual frames can be controlled with the Tag::set_*_enc functions. Cool, but is this really worth maintaining all these almost identical functions? If users really want to set the encoding per frame, the API would be a whole lot simpler if all advanced frame manipulation went through the existing Tag::push function.

Runtime validation is done where compile-time validation is possible:

  • The tag version is passed around as a single byte requiring validation. Said validation also varies across the API: Frame::read_from and Frame::write_to return an error, but Tag::with_version panics. If an enum is used, the check has to happen only once.

Integration with std:

  • Timestamp::parse could make better use of the FromStr trait
  • Tag::get_all could return an iterator instead of a vector.
  • Frame does not implement fmt::Debug

Implementation details:

  • Lengthy match expressions are used instead of the shorter and_then, map, or_else methods of Option and Result. E.g. Tag::disc_pair
  • There are a lot of macros which could be replaced with templated functions.
  • Tag is very highly coupled with the reader/writer. This requires functions mutating a tag to perform actions to keep some state that the writer uses
  • Tag::text_for_frame_id and similar functions return the contents of already existing types. E.g. Tag::lyrics returns just the text while a frame::Lyrics can return all associated data
  • Reading and writing is scattered around
  • Tag:add_* has a very confusing name because they don't just add stuff, they also remove anything with a conflicting unique identifier.
  • When updating a tag in an audio file, there does not seem to be any guarantee that a tag grown in size does not overwrite the audio data.

I'm going to stop extending this list now. If anything seems unfamiliar I'll be happy to offer an explanation. :)

Combine TIPL:producer and TIPL:engineer?

It appears that I cannot write both TIPL:producer and TIPL:engineer to the same tag.

That is, I think that if I write

    tag.add_frame(Frame::with_content("TIPL", Content::Text("producer\u{0}Barney")));
    tag.add_frame(Frame::with_content("TIPL", Content::Text("engineer\u{0}Wilma")));

then only one of these is preserved. Is this deliberate? Is there a workaround?

The letter of the spec seems a little unclear on this. Section 4.2 says:

The text information frames are often the most important frames,
containing information like artist, album and more. There may only be
one text information frame of its kind in an tag. All text
information frames supports multiple strings, stored as a null
separated list, where null is reperesented by the termination code
for the charater encoding. All text frame identifiers begin with "T".

In particular, the word kind does not seem to be defined.

GRP1 Writing

Hello, every time I wanna write a Text frame to GRP1 I get InvalidInput: Frame with ID GRP1 and content type Text can not be written as valid ID3. Looking into the source code (src/frame/mod.rs):

... 48
        // The matching groups must match the decoding groups of stream/frame/content.rs:decode().
        match (id.as_str(), &self.content) {
            ("GRP1", Content::Text(_)) if id.starts_with('T') => Ok(()),
            (id, Content::Text(_)) if id.starts_with('T') => Ok(()),
... 53

Which makes no sense to me. GRP1 will never start with T. Is there something I don't understand, or just a typo. Thank you.

ID3v1 support

Hello! Thanks for the library. Awesome work ๐Ÿ‘

Are there any plans for ID3v1 writing?
As I understand, now it removes a tag if it is present, isn't it?

AudioTag trait.

Hey,

I just came across your Reddit thread were you mentioned these libraries (id3 and metaflac). What happened to the AudioTag trait? Have you replaced it.with some other unified api to access the data?

Got a question about metaflac too. How am I supposed to read the artist of flac files using your linrary? :(

AIFF parser does not handle padding

Recently, we have implemented WAV support in Polaris using this crate, which worked like a charm. By noticing there is also the new AIFF API, we tried to also implement support for such format. When writing our tests, we noticed that rust-id3 does not handle padding in AIFF chunks, which causes it to miss the ID3 chunk if there is an odd-sized preceding chunk. Further, the code for writing tags may corrupt the file if padding is present.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.