Overview of ogg_pager
mainogg_pager is a Rust crate designed for working with OGG pages. It provides functionality to read, create, and paginate OGG pages.repository·main·Indexed 43 days ago
https://github.com/serial-ata/lofty-rsA Rust-based toolkit for parsing, converting, and writing metadata across a diverse range of audio file formats and metadata standards.
ogg_pager is a Rust crate designed for working with OGG pages. It provides functionality to read, create, and paginate OGG pages.Lofty provides two distinct categories of benchmarks to measure performance:
Converting a concrete tag to a generic Tag and back requires implementing SplitTag and MergeTag.
SplitTag: Takes the concrete tag and extracts all items that can be expressed as generic ItemKeys into a Tag. The remaining non-generic items are stored in a Remainder type (typically a tuple struct wrapper around the original tag).MergeTag: Takes the Remainder and a generic Tag and merges them back into the concrete tag format.Once these are implemented, you can provide From<FooTag> for Tag and From<Tag> for FooTag implementations using the split/merge logic.
// Example Remainder pattern
#[derive(Debug, Clone, Default)]
pub struct SplitTagRemainder(FooTag);
impl SplitTag for FooTag {
type Remainder = SplitTagRemainder;
fn split_tag(mut self) -> (Self::Remainder, Tag) {
let mut tag = Tag::new(TagType::Foo);
// ... logic to move items into generic Tag
(SplitTagRemainder(self), tag)
}
}
impl MergeTag for SplitTagRemainder {
type Merged = FooTag;
fn merge_tag(self, mut tag: Tag) -> Self::Merged {
let Self(mut merged) = self;
// ... logic to move items from Tag back to concrete struct
merged
}
}To make Lofty aware of your new format, you must update the FileType enum in src/file/file_type.rs with three specific pieces of information:
FileType enum.primary_tag_type() to return the preferred TagType for your format.from_ext() to map your file extension (e.g., "foo") to your new variant.quick_type_guess() to check the file buffer for your format's magic bytes.Finally, add the name of your format to the LOFTY_FILE_TYPES constant in lofty_attr/src/internal.rs to enable attribute macro support.
// 1. Add variant
pub enum FileType {
Foo,
// ...
}
// 2. Primary tag type
pub fn primary_tag_type(&self) -> TagType {
match self {
FileType::Foo => TagType::Id3v2,
// ...
}
}
// 3. Extension mapping
pub fn from_ext<E>(ext: E) -> Option<Self> {
match ext.as_str() {
"foo" => Some(Self::Foo),
// ...
}
}
// 4. Magic signature
fn quick_type_guess(buf: &[u8]) -> Option<Self> {
match buf[0] {
0x0F if buf.starts_with(0x0F00) => Some(Self::Foo),
// ...
}
}
// 5. Register in lofty_attr
const LOFTY_FILE_TYPES: [&str; N] = [
"Foo",
// ...
];To allow conversion between your concrete tag and the generic Tag type, you must define how your format's string keys map to Lofty's ItemKey enum. Use the gen_map! macro to create a mapping, then register it using the gen_item_keys! macro in src/tag/item.rs.
// 1. Define the mapping
gen_map!(
FOO_MAP;
"TITLE" => TrackTitle,
"ARIST" => TrackArtist,
"ALBUM" => AlbumTitle,
);
// 2. Register the map in gen_item_keys!
gen_item_keys!(
MAPS => [
// ...
[TagType::Foo, FOO_MAP]
];
// ...
);Define your concrete tag struct in src/your_tag/tag/mod.rs. Use the #[tag] attribute macro from lofty_attr to specify the tag's description and supported FileTypes. This macro automatically generates SUPPORTED_FORMATS and READ_ONLY_FORMATS constants and user-facing documentation.
To specify that a format is read-only (meaning the tag can be read or removed, but not written as a new tag), use the read_only(FileType) syntax within supported_formats.
use lofty_attr::tag;
// Standard support
#[tag(description = "A Foo tag", supported_formats(Foo))]
pub struct FooTag {
items: Vec<(String, String)>
}
// Support with read-only constraints
#[tag(description = "A Foo tag", supported_formats(Foo, read_only(Mpeg)))]
pub struct FooTag {
items: Vec<(String, String)>
}When adding a new tag format, the directory structure depends on whether the tag is specific to one audio format or generic across multiple formats.
src/.Every tag implementation requires the following files:
mod.rs: Contains the tag struct definition and module exports.read.rs: Contains the logic for reading the tag from a file.write.rs: Contains the logic for writing the tag to supported formats.src/
└── foo/
└── tag/
├── mod.rs
├── read.rs
└── write.rsWhen adding a new format, implement the following testing layers:
Mandatory unit tests for property reading must be stored in src/properties.rs.
Create a comprehensive test file in tests/files/{format}.rs. Use a sample file containing all supported tags located at tests/files/assets/minimal/full_test.{ext}.
Recommended test suite:
read(): Verify all expected information is present using crate::verify_artist().write(): Verify tag modification using crate::set_artist().remove_{tag}(): Verify tag removal using crate::remove_tag_test() for each supported tag type (e.g., remove_ape(), remove_id3v2()).Define fuzz targets in fuzz/fuzz_targets/{format}file_read_from to test the robustness of the read_from function.
// Example Fuzz Target
#![no_main]
use std::io::Cursor;
use libfuzzer_sys::fuzz_target;
use lofty::config::ParseOptions;
use lofty::file::AudioFile;
fuzz_target!(|data: Vec<u8>| {
let _ = lofty::foo::FooFile::read_from(
&mut Cursor::new(data),
ParseOptions::new().read_properties(false),
);
});When adding a new tag, follow these testing requirements:
Place binary test assets in tests/tags/assets/. Name them test.{ext} where {ext} is the tag name (e.g., test.foo). Assets should contain a representative set of tags (Title, Artist, Album, etc.).
Implement at least four unit tests in your tag's read module:
parse_{tag}: Verifies reading the asset matches a manually constructed tag.{tag}_re_read: Verifies a round-trip (Read $\rightarrow$ Write $\rightarrow$ Read) using TagExt::dump_to().{tag}_to_tag: Verifies conversion to a generic Tag using verify_tag().tag_to_{tag}: Verifies conversion from a generic Tag back to the concrete type using create_tag().If necessary, place integration tests in tests/tags/.
Feature requests should be categorized into one of two types based on their scope:
Use the [MISC] Feature Request template for features that:
Use the [API] Feature Request template if you have a specific design in mind for how the feature should be used. This template is for proposing an API design, not an implementation. Provide a code sketch of the intended interface.
pub struct MyTag {
some_field: ty,
some_other_field: ty,
// ... omit other fields if extending an existing type
}
impl MyTag {
pub fn foo(&self) -> String;
}The Accessor trait provides the primary interface for interacting with tag items (like title, artist, or album). You must implement the getters, setters, and removers for the specific items your tag supports.
Implementation Note: If your tag format allows keys to appear multiple times, implement both insert (which replaces existing occurrences) and push (which appends to the list).
impl Accessor for FooTag {
fn title(&self) -> Option<Cow<'_, str>> { /* implementation */ }
fn set_title(&mut self, value: String) { /* implementation */ }
fn remove_title(&mut self) { /* implementation */ }
// ... repeat for artist, album, etc.
}