Giter Site home page Giter Site logo

musicbrainz_rs's Introduction

MusicBrainz Rust โ€ƒ

Latest Version Build Status codecov GitHub tag (latest by date) Conventional Commits License

MusicBrainz rust is a utility crate for the the MusicBrainz API.


you may be looking for :

Usage

You can choose to use either the default async client or a blocking one.

async client:

musicbrainz_rs = "0.5.0"

blocking client:

musicbrainz_rs = { version = "0.5.0", features = ["blocking] }

Features

Note: All the example below use the blocking feature for the sake of conciseness.

Fetch query

To perform a lookups via fetch queries, you need to import the Fetch trait. This can be done using musicbrainz_rs::prelude

use musicbrainz_rs::entity::artist;
use musicbrainz_rs::entity::artist::*;
use musicbrainz_rs::prelude::*;

fn main() {
    let nirvana = Artist::fetch()
        .id("5b11f4ce-a62d-471e-81fc-a69a8278c7da")
        .execute();

    assert_eq!(nirvana.unwrap().name, "Nirvana".to_string());
}

Include parameters

You can also use includes to get more detail about a resource :

Every Musicbrainz resource has allowed include parameters.

use musicbrainz_rs::entity::label::*;
use musicbrainz_rs::prelude::*;

fn main() {
    let ninja_tune = Label::fetch()
        .id("dc940013-b8a8-4362-a465-291026c04b42")
        .with_tags()
        .with_ratings()
        .execute()
        .unwrap();

    assert!(ninja_tune
        .tags
        .unwrap()
        .iter()
        .any(|tag| tag.name == "independent"));

    assert!(ninja_tune.rating.is_some());
}

CoverArt query

Release and ReleaseGroup entities in MusicBrainz also allow you to make CoverArt queries on them:

use musicbrainz_rs::entity::release::*;
use musicbrainz_rs::entity::CoverartResponse;
use musicbrainz_rs::prelude::*;
use musicbrainz_rs::FetchCoverart;

fn main() {
    // CoverArt Query for a Release.
    let in_utero_coverart = Release::fetch_coverart()
        .id("76df3287-6cda-33eb-8e9a-044b5e15ffdd")
        .execute()
        .expect("Unable to get cover art");

    if let CoverartResponse::Json(coverart) = in_utero_coverart {
        assert!(!coverart.images[0].back);
        assert_eq!(
            coverart.images[0].image,
            "http://coverartarchive.org/release/76df3287-6cda-33eb-8e9a-044b5e15ffdd/829521842.jpg"
        );
    } else {
        assert!(false);
    }

    let in_utero = Release::fetch()
        .id("76df3287-6cda-33eb-8e9a-044b5e15ffdd")
        .execute()
        .expect("Unable to get release");

    // Calling `get_coverart()` method on an already fetched Release entity.
    let in_utero_coverart = in_utero
        .get_coverart()
        .execute()
        .expect("Unable to get coverart");

    if let CoverartResponse::Json(coverart) = in_utero_coverart {
        assert!(!coverart.images[0].back);
        assert_eq!(
            coverart.images[0].image,
            "http://coverartarchive.org/release/76df3287-6cda-33eb-8e9a-044b5e15ffdd/829521842.jpg"
        );
    } else {
        assert!(false);
    }

    // CoverArt Query Builder to fetch a specific resource.
    let in_utero_500px_front_coverart = Release::fetch_coverart()
        .id("76df3287-6cda-33eb-8e9a-044b5e15ffdd")
        .res_500()
        .back()
        .execute()
        .expect("Unable to get cover art");

    if let CoverartResponse::Url(coverart_url) = in_utero_500px_front_coverart {
        println!("{}", coverart_url);
    } else {
        assert!(false);
    }
}

Browse query

Use musicbrainz_rs::Browse or bring it in scope using musicbrainz_rs::prelude to perform a browse query. Just like Include every muscibrainz resource has allowable linked entities for such queries.

use musicbrainz_rs::entity::artist;
use musicbrainz_rs::entity::artist::Artist;
use musicbrainz_rs::prelude::*;

fn main() {
    let artists_on_in_utero_release = Artist::browse()
        .by_release("18d4e9b4-9247-4b44-914a-8ddec3502103")
        .execute();

    let artists_on_in_utero_release = artists_on_in_utero_release.unwrap();

    artists_on_in_utero_release
        .entities
        .iter()
        .for_each(|artist| println!("{:?}", artist.name));
}

Search query

Use musicbrainz_rs::Search to perform a search query.

use musicbrainz_rs::entity::artist::Artist;
use musicbrainz_rs::prelude::*;

fn main() {
    musicbrainz_rs::config::set_user_agent("my_awesome_app/1.0");

    let query = Artist::query_builder()
        .artist("Miles Davis")
        .and()
        .country("US")
        .build();

    let query_result = Artist::search(query).execute().unwrap();
    let query_result: Vec<String> = query_result.entities
        .iter()
        .map(|artist| artist.name.clone()).collect();

    assert!(query_result.contains(&"Miles Davis".to_string()));
    assert!(query_result.contains(&"Miles Davis Quintet".to_string()));
}

Custom user agent

You can set your application user-agent as recommended in the musicbrainz documentation :

use musicbrainz_rs::entity::artist::Artist;
use musicbrainz_rs::prelude::*;

fn main() {
    musicbrainz_rs::config::set_user_agent("my_awesome_app/1.0");

    let nirvana = Artist::fetch()
        .id("5b11f4ce-a62d-471e-81fc-a69a8278c7da")
        .execute();

    assert_eq!(nirvana.unwrap().name, "Nirvana".to_string());
}

Examples

To see what is currently implemented in the crate you can look at the tests directory.

You can run examples with cargo run --example example_name

Contributing

All contributions are welcome, if find a bug or have a feature request don't hesitate to open an issue!

Credits

Most of this crate documentation is taken from the official MusicBrainz doc, thanks to the MetaBrainz Foundation and its sponsors and supporters. Cover Art provided by the Cover Art Archive.

musicbrainz_rs's People

Contributors

nickzana avatar oknozor avatar rfilmyer avatar ritiek avatar snylonue avatar yesterday17 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

musicbrainz_rs's Issues

GPS coordinate on place entity behave randomly

It seems sometimes Musicbrainz api returns a float for gps coordinate, sometimes a string. This happens randomly on the same entity :

---- should_get_place stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error(Json(Error("invalid type: floating point `41.882059`, expected a string", line: 1, column: 36)))', src/libcore/result.rs:1084:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.


failures:
    should_get_place

Add non-core lookup

Currently the crate only support MBID lookup, when adding non-core lookup we might want to rename the fetch method to lookup so it remains coherent with the MusicBrainz api.

  • discid lookup
  • isrc lookup
  • iswc lookup

Invalid date format in search_deserializer

When trying your example code with the current 'stable' of your library, I always get an error when the created-date is tried to be read:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseError(BadFormat)', /home/hypfvieh/.cargo/registry/src/github.com-1ecc6299db9ec823/musicbrainz_rs-0.3.0/src/deserialization/search_deserializer.rs:148:79

When changing the FORMAT from %Y-%m-%dT%H:%M:%S%.f%Z to %Y-%m-%dT%H:%M:%S%.fZ (not the missing % before 'Z') everything is working fine.
I'm not sure if this is a proper fix, but the current version is unusable because every query for an artist will fail.

Enum usage

Many attributes returned by the MusicBrainz could be de-serialized into rust enums. This would make the crate much easier to use and would allow simpler implementation for several features and better documentation, but for that we need to be sure variant on the API side are consistent.

For example Release Packaging consist of several values that could be easily de-serialized into enum. This would be feasible only if the APi output is a 100% consistent.

Although we need to know if the actual string returned by the API can contain backspace and special character, if so, this would require a custom deserializer.
for instance : Cassette case, Super Jewel Box/Case

Unfortunately it the MusicBrainz documentation doesn't provide think kind of information.

Other attributes that might be candidate for enum de-serialization:

  • country
  • packaging
  • release-group primary/secondary type

Attributes currently de-serialized as enum that should be tested :

  • release script
  • release language
  • release quality
  • release status
  • artist type
  • gender

Query builder does not fit musicbrainz lucene query for certain entities

The QueryBuilder derive macro we use for lucene query does not fit with some of the search queries :

For instance release-groups search accept the following fields :

  • artist : (part of) the combined credited artist name for the release group, including join phrases (e.g. "Artist X feat.")
  • artistname : (part of) the name of any of the release group artists
  • creditname : (part of) the credited name of any of the release group artists on this particular release group

While top level fields of the ReleaseGroup entity (on musicbrainz_rs side) only refers to artists via artist_credit.

We end up with a single query parameter artist_credit in the release group generated builder. We can rename it with the current implementation but we still lacks two artists related fields.

In my opinion the best approach would be to implement this manually and get rid of the query builder macro.

Implement retry on 503 server response, using `Retry-After` Header value

Rate limiting cause integration tests to fail randomly, for now a quick'n dirty fix has been made with thread sleeps on every test.

Adding a proper user agent header as described in MusicBrainz rate limiting documentation might solve the issue.

This still need to be figured out but implementing a retry on HTTP 503 responses might also be a good idea.

Covert art response is panicking

Code here just unwraps the reqwest response, despite that function returns Result. It's annoying because it seems downloading cover art frequently results in HTTP 429 (I don't know why but that's separate issue).

Could you please just propagate that result so the calling app can handle it appropriately?

rename_all serialisation/deserialization inconsistency

Is there a reason why Serde's rename_all is only specified for deserialization? This messes up serialisation/deserialization of any structs that use MusicBrainz entities in their fields, since the fields of the entities don't match.

Add relation level

This allow to display relations for sub entities in a query, for instance loading relations for all recordings in a release.

  • recording-level-rels
  • work-level-rels

Support for browse pagination?

I'm attempting to use this library to page through the browse API, but I don't see anything which supports this built in. Am I missing something?

https://beta.musicbrainz.org/doc/MusicBrainz_API#Browse

Paging
Browse requests are the only requests which support paging: any browse request supports an 'offset=' argument to get more results. Browse requests also support 'limit=': the default limit is 25, and you can increase that up to 100.

Special note for releases: To ensure requests can complete without timing out, we limit the number of releases returned such that the entire list contains no more than 500 tracks. (However, at least one full release is always returned, even if it has more than 500 tracks; we don't return "partial" releases.) This means that you may not get 100 releases per page if you set limit=100; in fact the number will vary per page depending on the size of the releases. In order to page through the results properly, increment offset by the number of releases you get from each response, rather than the (larger, fixed) limit size.

Use stub tests for API calls

cargo test takes a significant amount of time to run, most of which is spent making about 150 calls to the MusicBrainz API. We could probably replace the actual API calls with JSON for cargo test, and save the actual API querying for cargo test --all

Add misc includes param

  • aliases
  • annotation
  • tags
  • ratings
  • genre

note : user-tags and user rating will be implemented with authentication after v0.2.0

Add browse queries

  • [ ] area
    • [ ] collection
  • artist
    • area,
    • [ ] collection,
    • recording,
    • release,
    • release-group,
    • work
  • [ ] collection
    • [ ] area,
    • [ ] artist,
    • [ ] editor,
    • [ ] event,
    • [ ] label,
    • [ ] place,
    • [ ] recording,
    • [ ] release,
    • [ ] release-group,
    • [ ] work
  • event
    • area,
    • artist,
    • [ ] collection,
    • place
  • [ ] instrument
    • [ ] collection
  • label
    • area,
    • [ ] collection,
    • release
  • place
    • area,
    • [ ] collection
  • recording
    • artist,
    • [ ] collection,
    • release,
    • work
  • release
    • area,
    • artist,
    • [ ] collection,
    • label,
    • track,
    • track_artist,
    • recording,
    • release-group
  • release-group
    • artist,
    • [ ] collection,
    • release
  • [ ] series
    • [ ] collection
  • work
    • artist,
    • [ ] collection
  • [ ] url
    • [ ] resource

Add relations to include params

Currently the Relation logic is implemented in the model but we still need to implement every variant.

  • area-rels
  • artist-rels
  • event-rels
  • instrument-rels
  • label-rels
  • place-rels
  • recording-rels
  • release-rels
  • release-group-rels
  • series-rels
  • url-rels
  • work-rels

Add authentication

MusicBrainz requires users to be authenticated in order to be able to submit data to their database. MusicBrainz currently supports two methods for user authentication, which are HTTP Digest Access Authentication and OAuth2. It would be a good idea to implement these authentication methods (and then later also wrapping up the submission endpoint) in our library.

Expose chrono API

So far, this crate uses chrono's [NaiveDate](https://docs.rs/chrono/0.4.19/chrono/naive/struct.NaiveDate.html) to represent date.

However, chrono implements some apis like year(), month() and day() on its trait Datelike, making it impossible to get them without explicitly importing chrono, which is the most important parts of a date.

Thus, it would be convenient to expose such trait via e.g. pub use chrono::Datalike or add getters on entity types.

Failing artist test case

It seems like the database entry for the artist id b0122194-c49a-46a1-ade7-84d1d76bd8e9 was updated or something happened recently as some tests for this artist id are failing. This is when I ran the test suite locally:

$ cargo test -- --test-threads 1
...
failures:

---- artist::artist_includes::should_get_artist_recordings stdout ----
thread 'main' panicked at 'assertion failed: recordings.iter().any(|recording| recording.title == \"A Little Bit Higher\")', tests/artist/artist_includes.rs:85:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- artist::artist_includes::should_get_artist_recordings_test stdout ----
thread 'main' panicked at 'assertion failed: recordings.iter().any(|recording| recording.title == \"A Little Bit Higher\")', tests/artist/artist_includes.rs:18:5

---- artist::artist_includes::should_get_artist_release_groups stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error(Json(Error("invalid type: null, expected a string", line: 1, column: 640)))', tests/artist/artist_includes.rs:63:10


failures:
    artist::artist_includes::should_get_artist_recordings
    artist::artist_includes::should_get_artist_recordings_test
    artist::artist_includes::should_get_artist_release_groups

test result: FAILED. 111 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 144.20s

We could probably update the recording title in should_get_artist_recordings, and we could get rid of the next test case (should_get_artist_recordings_test) since it's exactly the same as this test case.

In the final test case (should_get_artist_release_groups), it seems like we aren't able to correctly parse the json response. Any ideas on how should I debug further on what json parameter(s) with the null value are breaking our parser?

Use Default::default instead of optionals everywhere

Search, browse and fetch routes, entities are often inconsistent and we have to use Option almost every where to avoid de-serialization panic.

I am not a 100% sure this is the right approach but an alternative would be to have a Default implementation on every entities and
use #[serde(default)] (on the struct not its fields) to avoid optional everywhere.

One possible downside is that we will have default values instead of json null. For instance using default on a missing string fields would produce "" instead off null -> None. This probably make it a little bit tedious to consume query results for end users since they will need to check for empty strings instead of Option.

On the other hand this is really convenient to populate vecs from empty or missing json arrays.

Maybe we should use both the default implementation and carefully chose which field should be Option.

Support release with incomplete date

For instance, release 2e6374f8-420b-353b-8c42-dbb249bfac1c's date is simply "1984" without the month and day. When retrieved with this crate, it is converted to a NaiveDate with value "1984-01-01", supposedly due to this logic. Doesn't seem to have a way to restore the original "1984" value.

Sure, we can heuristically say if the date's month and day are both 1, most likely they can be discarded. But there could be case where the release date is really on the new year's day.

Maybe instead of deserialize the date to a NaiveDate, simply keep it as String, and let user choose which library to use?

Browse requests have invalid include parameters

When doing a Browse request on the entity, the crates allow to add includes. However, those includes are the same as a Fetch request, while the API is requesting other parameters.

So when doing Recording::browse().by_artist("410c9baf-5469-44f6-9852-826524b80c61").with_artists().execute(), the crate send this request:
https://musicbrainz.org/ws/2/recording?artist=410c9baf-5469-44f6-9852-826524b80c61&inc=artist

instead of:
https://musicbrainz.org/ws/2/recording?artist=410c9baf-5469-44f6-9852-826524b80c61&inc=artist-credits

Improve query builder generated function by adding doc

When declaring a struct dedicated to search queries all field are private, therefor the field documentation is not picked by cargo doc :
image

We need to modify the derive macro generating the builder so it grab the field documentation and add it to the builder method :

image

This shall be done in lucene_query_builder macro_derive implementation.

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.