Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audiotags2 -> Audiotags #18

Merged
merged 16 commits into from
Jul 31, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 0 additions & 64 deletions .github/workflow/main.yml

This file was deleted.

72 changes: 57 additions & 15 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,64 @@
name: Rust

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
on: [push, pull_request]

env:
CARGO_TERM_COLOR: always
name: Rust

jobs:
build:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: check

test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test

fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check

clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## [0.3.1] 2022-05-25

- Upgraded `id3` from 1.0.3 to 1.1.0

## [0.3.0] 2022-05-25

- Added support for `duration`
- Added support for `genre`
- Upgraded `id3` from 0.5.1 to 1.0.3
- Upgrade `mp4ameta` from 0.6 to 0.11
- Execute tests from tmp directory to avoid repo corruption

## [0.2.7182] 2020-10-29

- Improve docs
Expand Down Expand Up @@ -29,4 +41,4 @@

## [0.2.0] 2020-10-26

- conversion between tag types (naive and unstable implementation)
- conversion between tag types (naive and unstable implementation)
27 changes: 17 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
[package]
name = "audiotags"
version = "0.2.7182"
authors = ["Tianyi <ShiTianyi2001@outlook.com>"]
edition = "2018"
version = "0.4.0"
authors = ["Tianyi <ShiTianyi2001@outlook.com>", "Pierre de la Martinière <pierre.de.la.martiniere@gmail.com>"]
edition = "2021"
description = "Unified IO for different types of audio metadata"
license = "MIT"
repository = "https://github.com/TianyiShi2001/audiotags"
repository = "https://github.com/martpie/audiotags"
keywords = ["id3", "tag", "tags", "audio", "audiotags"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
id3 = "0.5.1"
mp4ameta = "0.6"
metaflac = "0.2"
thiserror = "1.0.21"
id3 = "1.1.0"
mp4ameta = "0.11.0"
metaflac = "0.2.5"
thiserror = "1.0.31"
audiotags-dev-macro = {path = "./audiotags-dev-macro", version = "0.1.4"}

[dev-dependencies]
tempfile = "3.3.0"

[build-dependencies]
readme-rustdocifier = "0.1.0"

[features]
defualt = ['from']
from = []
default = ['from']
from = []
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2020 Tianyi Shi
Copyright (c) 2022 Pierre de la Martinière
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to remove me, that's all fine

Copy link
Collaborator Author

@pinkforest pinkforest Jul 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a merge party :)


Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
101 changes: 44 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,63 @@
[![Crate](https://img.shields.io/crates/d/audiotags.svg)](https://crates.io/crates/audiotags)
[![Crate](https://img.shields.io/crates/l/audiotags.svg)](https://crates.io/crates/audiotags)
[![Documentation](https://docs.rs/audiotags/badge.svg)](https://docs.rs/audiotags/)
[![Manual](https://img.shields.io/badge/RTFM-Manual-blue)](https://tianyishi2001.github.io/audiotags)

**audiotags** makes it easier to **parse, convert and write metadata** (a.k.a tag) in audio files of different file types.
This crate makes it easier to parse, convert and write metadata (a.k.a tag) in audio files of different file types.

This crate aims to provide a unified trait for parsers and writers of different audio file formats. This means that you can parse tags in mp3, flac, and m4a files with a single function: `Tag::default().read_from_path()` and get fields by directly calling `.album()`, `.artist()` on its result. Without this crate, you would otherwise need to learn different APIs in **id3**, **mp4ameta** etc. in order to parse metadata in different file formats.
This crate aims to provide a unified trait for parsers and writers of different audio file formats.
This means that you can parse tags in mp3, flac, and m4a files with a single function: `Tag::default().
read_from_path()` and get fields by directly calling `.album()`, `.artist()` on its result. Without this
crate, you would otherwise need to learn different APIs in **id3**, **mp4ameta** etc. in order to parse
metadata in different file formats.

I'm relatively new to Rust and programming in general, and this is my first attempt to make a non-trivial crate. If you see anything that you think isn't right, just say it!
### Performance

## Examples
Using **audiotags** incurs a little overhead due to vtables if you want to guess the metadata format
(from file extension). Apart from this the performance is almost the same as directly calling function
provided by those 'specialized' crates.

Examples can be found in the [manual](https://tianyishi2001.github.io/audiotags).
No copies will be made if you only need to read and write metadata of one format. If you want to convert
between tags, copying is unavoidable no matter if you use **audiotags** or use getters and setters provided
by specialized libraries. **audiotags** is not making additional unnecessary copies.

## Performance

Using **audiotags** incurs a little overhead due to vtables if you want to guess the metadata format (from file extension). Apart from this the performance is almost the same as directly calling function provided by those 'specialized' crates. (It is possible to use **audiotags** *without* dynamic dispatch, in which case you need to specify the tag type but benefit from speed improvement).

No copies will be made if you only need to read and write metadata of one format. If you want to convert between tags, copying is unavoidable no matter if you use **audiotags** or use getters and setters provided by specialized libraries. **audiotags** is not making additional unnecessary copies.

Theoretically it is possible to achieve zero-copy conversions if all parsers can parse into a unified struct. However, this is going to be a lot of work. I might be able to implement them, but it will be no sooner than the Christmas vacation.

## Supported Formats
### Supported Formats

| File Fomat | Metadata Format | backend |
| ------------- | --------------------- | ----------------------------------------------------------- |
| `mp3` | id3v2.4 | [**id3**](https://github.com/polyfloyd/rust-id3) |
| `m4a/mp4/...` | MPEG-4 audio metadata | [**mp4ameta**](https://github.com/Saecki/rust-mp4ameta) |
| `flac` | Vorbis comment | [**metaflac**](https://github.com/jameshurst/rust-metaflac) |

## Getters and Setters
### Examples

Read the [manual](https://docs.rs/audiotags) for some examples, but here's a quick-one:

```rust
pub trait AudioTagEdit{
fn title(&self) -> Option<&str>;
fn set_title(&mut self, title: &str);
fn remove_title(&mut self);
fn artist(&self) -> Option<&str>;
fn remove_artist(&mut self);
fn set_artist(&mut self, artist: &str);
fn year(&self) -> Option<i32>;
fn set_year(&mut self, year: i32);
fn remove_year(&mut self);
fn album(&self) -> Option<Album>;
fn remove_album(&mut self);
fn album_title(&self) -> Option<&str>;
fn remove_album_title(&mut self);
fn album_artist(&self) -> Option<&str>;
fn remove_album_artist(&mut self);
fn album_cover(&self) -> Option<Picture>;
fn remove_album_cover(&mut self);
fn set_album(&mut self, album: Album);
fn set_album_title(&mut self, v: &str);
fn set_album_artist(&mut self, v: &str);
fn set_album_cover(&mut self, cover: Picture);
fn track(&self) -> (Option<u16>, Option<u16>);
fn set_track(&mut self, track: (u16, u16));
fn remove_track(&mut self);
fn track_number(&self) -> Option<u16>;
fn set_track_number(&mut self, track_number: u16);
fn remove_track_number(&mut self);
fn total_tracks(&self) -> Option<u16>;
fn set_total_tracks(&mut self, total_track: u16);
fn remove_total_tracks(&mut self);
fn disc(&self) -> (Option<u16>, Option<u16>);
fn set_disc(&mut self, disc: (u16, u16));
fn remove_disc(&mut self);
fn disc_number(&self) -> Option<u16>;
fn set_disc_number(&mut self, disc_number: u16);
fn remove_disc_number(&mut self);
fn total_discs(&self) -> Option<u16>;
fn set_total_discs(&mut self, total_discs: u16);
fn remove_total_discs(&mut self);
fn main() {
// using `default()` or `new()` alone so that the metadata format is
// guessed (from the file extension) (in this case, Id3v2 tag is read)
let mut tag = Tag::new().read_from_path(MP3_FILE).unwrap();

tag.set_title("foo title");
assert_eq!(tag.title(), Some("foo title"));
tag.remove_title();
assert!(tag.title().is_none());
tag.remove_title();
// trying to remove a field that's already empty won't hurt

let cover = Picture {
mime_type: MimeType::Jpeg,
data: &vec![0u8; 10],
};

tag.set_album_cover(cover.clone());
assert_eq!(tag.album_cover(), Some(cover));
tag.remove_album_cover();
assert!(tag.album_cover().is_none());
tag.remove_album_cover();

tag.write_to_path(MP3_FILE).expect("Fail to save");
}
```
```

License: MIT
Loading