Add a schema extension API and several extensions (#42)

* Add a schema extension API and several extensions
* Update graphql-markdown to use new diffSchema function
* Update Node support
This commit is contained in:
Brian Beck 2017-10-19 01:00:21 -07:00 committed by GitHub
parent 687ca43708
commit 898ec78a6f
253 changed files with 8341 additions and 1601 deletions

1
.gitignore vendored
View file

@ -36,4 +36,5 @@ jspm_packages
# Optional REPL history # Optional REPL history
.node_repl_history .node_repl_history
.env
lib lib

View file

@ -1,14 +1,18 @@
language: node_js language: node_js
node_js: node_js:
- "4"
- "5"
- "6" - "6"
- "7" - "7"
- "8"
# Use container-based Travis infrastructure. # Use container-based Travis infrastructure.
sudo: false sudo: false
env:
global:
- FANART_API_KEY=d9e25d5beda1027a1674c1585882309e
- THEAUDIODB_API_KEY=1
branches: branches:
only: only:
- master - master

View file

@ -16,9 +16,11 @@ npm install graphbrainz --save
**[Try out the live demo!][demo]** :bulb: Use the “Docs” sidebar, the **[Try out the live demo!][demo]** :bulb: Use the “Docs” sidebar, the
[schema][], or the [types][] docs to help construct your query. [schema][], or the [types][] docs to help construct your query.
## Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Contents
- [Usage](#usage) - [Usage](#usage)
- [As a standalone server](#as-a-standalone-server) - [As a standalone server](#as-a-standalone-server)
@ -30,6 +32,7 @@ npm install graphbrainz --save
- [Pagination](#pagination) - [Pagination](#pagination)
- [Questions](#questions) - [Questions](#questions)
- [Schema](#schema) - [Schema](#schema)
- [Extending the schema](#extending-the-schema)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -101,14 +104,12 @@ GraphBrainz resolvers expect, like so:
```js ```js
import { graphql } from 'graphql'; import { graphql } from 'graphql';
import { MusicBrainz, CoverArtArchive } from 'graphbrainz/api'; import { MusicBrainz, CoverArtArchive } from 'graphbrainz/lib/api';
import createLoaders from 'graphbrainz/loaders'; import createContext from 'graphbrainz/lib/context';
import schema from 'graphbrainz/schema'; import schema from 'graphbrainz/lib/schema';
const client = new MusicBrainz(); const client = new MusicBrainz();
const coverArtClient = new CoverArtArchive(); const context = createContext({ client })
const loaders = createLoaders(client, coverArtClient);
const context = { client, coverArtClient, loaders };
graphql(schema, ` graphql(schema, `
{ {
@ -142,6 +143,8 @@ graphql(schema, `
day). day).
* **`GRAPHBRAINZ_GRAPHIQL`**: Set this to `true` if you want to force the * **`GRAPHBRAINZ_GRAPHIQL`**: Set this to `true` if you want to force the
[GraphiQL][] interface to be available even in production mode. [GraphiQL][] interface to be available even in production mode.
* **`GRAPHBRAINZ_EXTENSIONS`**: A JSON array of module paths to load as
[extensions](./docs/extensions).
* **`PORT`**: Port number to use, if running the standalone server. * **`PORT`**: Port number to use, if running the standalone server.
When running the standalone server, [dotenv][] is used to load these variables When running the standalone server, [dotenv][] is used to load these variables
@ -361,7 +364,14 @@ GraphBrainz to use that with no rate limiting.
## Schema ## Schema
See the [GraphQL schema][schema] or the [types][] documentation. The [types][] document is the easiest to browse representation of the schema, or
you can read the [schema in GraphQL syntax][schema].
### Extending the schema
The GraphBrainz schema can easily be extended to add integrations with
third-party services. See the [Extensions](./docs/extensions) docs for more
info.
[demo]: https://graphbrainz.herokuapp.com/ [demo]: https://graphbrainz.herokuapp.com/
[Express]: http://expressjs.com/ [Express]: http://expressjs.com/

View file

@ -1,4 +1,3 @@
machine: machine:
node: node:
version: 4.6.0 version: 6.0.0

227
docs/extensions/README.md Normal file
View file

@ -0,0 +1,227 @@
# Extensions
It is possible to extend the GraphBrainz schema to add integrations with
third-party services that provide more information about MusicBrainz entities.
Extensions can define new GraphQL types and use the `extend type` syntax to add
new fields to any existing GraphBrainz type, including the root query.
Several extensions are included by default, and you can install any number of
additional extensions from a package manager or [write your own](#extension-api).
## Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Loading Extensions](#loading-extensions)
- [Built-in Extensions](#built-in-extensions)
- [Extension API](#extension-api)
- [Properties](#properties)
- [name](#name)
- [description](#description)
- [extendContext](#extendcontext)
- [extendSchema](#extendschema)
- [Example](#example)
- [Extension Guidelines](#extension-guidelines)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Loading Extensions
The extensions to load are specified using the `extensions` option to the
exported `graphbrainz()` middleware function. Each extension must be an object
conforming to the [Extension API](#extension-api), or the path to a module to
load via `require()` that exports such an object.
If you are running GraphBrainz as a standalone server, you may specify
extensions via the `GRAPHBRAINZ_EXTENSIONS` environment variable, which will be
parsed as a JSON array. For example:
```console
$ export GRAPHBRAINZ_EXTENSIONS='["graphbrainz/extensions/fanart-tv"]'
$ graphbrainz
```
Note that some extensions may require additional configuration via extra options
or environment variables. Check the documentation for each extension you use.
The default value of the `extensions` option looks like this:
```js
[
'graphbrainz/extensions/cover-art-archive',
'graphbrainz/extensions/fanart-tv',
'graphbrainz/extensions/mediawiki',
'graphbrainz/extensions/the-audio-db'
]
```
## Built-in Extensions
The following extensions are included with GraphBrainz and loaded by default.
See their respective documentation pages for schema info and config options.
* [Cover Art Archive](./cover-art-archive.md): Retrieve cover art images for
releases from the Cover Art Archive.
* [fanart.tv](./fanart-tv.md): Retrieve high quality artwork for artists,
releases, and labels from fanart.tv.
* [MediaWiki](./mediawiki.md): Retrieve information from MediaWiki image pages,
like the actual image file URL and EXIF metadata.
* [TheAudioDB](./the-audio-db): Retrieve images and information about artists,
releases, and recordings from TheAudioDB.com.
## Extension API
The core idea behind extensions comes from the [schema stitching][] feature
from [graphql-tools][], although GraphBrainz does not currently use the exact
technique documented there. Instead, we call `parse` and `extendSchema` from
[GraphQL.js][], followed by [addResolveFunctionsToSchema][].
Extensions must export an object shaped like so:
```js
type Extension = {
name: string,
description?: string,
extendContext?: (context: Context, options: Options) => Context,
extendSchema?:
{ schemas: Array<string | DocumentNode>, resolvers: ResolverMap } |
(schema: GraphQLSchema, options: Options) => GraphQLSchema
}
```
### Properties
#### name
The name of the extension.
#### description
A description of the functionality that the extension provides.
#### extendContext
An optional function that accepts a base context object (the `context` argument
available to resolver functions) and returns a new context object. Extensions
that access third-party APIs should add any API client instances they need here.
The recommended way is to create a loader with [dataloader][] and add it onto
`context.loaders`.
#### extendSchema
An optional object or function to extend the GraphBrainz schema.
If it is an object, it should have a `schemas` array and a `resolvers` object.
Each schema must be a string (containing type definitions in GraphQL schema
language) or a `DocumentNode` (if the type definitions have already been
parsed). The `resolvers` object should contain a mapping of type fields to new
resolver functions for those fields. See [addResolveFunctionsToSchema][].
If it is a function, it should accept `schema` and `options` arguments and
return a new schema. Use this if youd like to perform custom schema extension
logic. This may be necessary if you already have a `GraphQLSchema` instance and
want to use [mergeSchemas][], for example. In most cases, you should keep it
simple and use the object form.
### Example
```js
module.exports = {
name: 'Hello World',
description: 'A simple example extension.',
extendSchema: {
schemas: [`
extend type Query {
helloWorld: String!
}
`],
resolvers: {
Query: {
helloWorld: {
resolve: () => 'It worked!'
}
}
}
}
}
```
This will allow the following query to be made:
```graphql
{
helloWorld
}
```
See the code for the [built-in extensions][] for more examples.
## Extension Guidelines
Extensions can load and resolve data in any manner they please, and you can
write them in any way that conforms to the API. But if you want an extra feather
in your cap, there are a few guidelines you should follow in order to maintain
consistency with GraphBrainz and the built-in extensions. Here are some tips
for writing a good extension:
* If you need to make HTTP requests, using a [Client][] subclass will get you
rate limiting, error handling, and a Promise-based API for free.
* Default to following the rate limiting rules of any APIs you wrap. If there
are no guidelines on rate limiting, consider playing nice anyway and limiting
your client to around 1 to 10 requests per second.
* Use a [DataLoader][dataloader] instance to batch and cache requests. Even if
the data source doesnt support batching, it will dedupe in-flight requests
for the same key, preventing extra requests before the response has been
cached.
* Use a configurable cache and make sure you arent caching everything
indefinitely by accident. The `cacheMap` option to DataLoader is a good place
to put it.
* Get as much configuration from environment variables as possible, so that
people can just run the standalone server instead of writing server code. If
you need more complex configuration, use a single object on the `options`
object as a namespace for your extensions options.
* Dont be afraid to rename fields returned by third-party APIs when translating
them to the GraphQL schema. Consistency with GraphQL conventions and the
GraphBrainz schema is more desirable than consistency with the original API
being wrapped. Some general rules:
* Use camel case naming and capitalize acronyms (unless they are the only
word), e.g. `id`, `url`, `artistID`, `pageURL`.
* If its ambiguous whether a field refers to an object/list vs. a scalar
summary of an object/list, consider clarifying the field name, e.g. `user`
`userID`, `members``memberCount`.
* Dont include fields that are already available in MusicBrainz, only include
whats relevant and useful.
* Add descriptions for everything: types, fields, arguments, enum values, etc.
 with Markdown links wherever theyd be helpful.
* When extending the built-in types, prefer adding a single object field that
serves as a namespace rather than adding many fields. That way its more
obvious that the data source isnt MusicBrainz itself, and youre less likely
to conflict with new MusicBrainz fields in the future.
* Prefer using a [Relay][]-compliant schema for lists of objects that (1) have
their own IDs and (2) are likely to be paginated. Feel free to add a `nodes`
shortcut field to the Connection type (for users who want to skip over
`edges`).
* If you publish your extension, consider prefixing the package name with
`graphbrainz-extension-` and having the default export of its `main` entry
point be the extension object. That way, using it is as simple as adding the
package name to the list of extensions.
* Consider using [graphql-markdown][] to document the schema created by your
extension; this will match how GraphBrainz itself is documented. You can use
the [diffSchema][] function to document only the schema updates, see
[scripts/build-extension-docs.js][build-extension-docs] for how this is done
with the built-in extensions.
[graphql-tools]: http://dev.apollodata.com/tools/graphql-tools/index.html
[schema stitching]: http://dev.apollodata.com/tools/graphql-tools/schema-stitching.html
[mergeSchemas]: http://dev.apollodata.com/tools/graphql-tools/schema-stitching.html#mergeSchemas
[dataloader]: https://github.com/facebook/dataloader
[built-in extensions]: ../../src/extensions
[Client]: ../../src/api/client.js
[graphql-markdown]: https://github.com/exogen/graphql-markdown
[diffSchema]: https://github.com/exogen/graphql-markdown#diffschemaoldschema-object-newschema-object-options-object
[build-extension-docs]: ../../scripts/build-extension-docs.js
[Relay]: https://facebook.github.io/relay/
[GraphQL.js]: http://graphql.org/graphql-js/
[addResolveFunctionsToSchema]: http://dev.apollodata.com/tools/graphql-tools/resolvers.html#addResolveFunctionsToSchema

View file

@ -0,0 +1,378 @@
# Extension: Cover Art Archive
Retrieve cover art images for releases from the [Cover Art Archive](https://coverartarchive.org/).
This extension uses its own cache, separate from the MusicBrainz loader cache.
## Configuration
This extension can be configured using environment variables:
* **`COVER_ART_ARCHIVE_BASE_URL`**: The base URL at which to access the Cover
Art Archive API. Defaults to `http://coverartarchive.org/`.
* **`COVER_ART_ARCHIVE_CACHE_SIZE`**: The number of items to keep in the cache.
Defaults to `GRAPHBRAINZ_CACHE_SIZE` if defined, or `8192`.
* **`COVER_ART_ARCHIVE_CACHE_TTL`**: The number of seconds to keep items in the
cache. Defaults to `GRAPHBRAINZ_CACHE_TTL` if defined, or `86400000` (one day).
<details>
<summary><strong>Table of Contents</strong></summary>
* [Objects](#objects)
* [CoverArtArchiveImage](#coverartarchiveimage)
* [CoverArtArchiveImageThumbnails](#coverartarchiveimagethumbnails)
* [CoverArtArchiveRelease](#coverartarchiverelease)
* [Release](#release)
* [ReleaseGroup](#releasegroup)
* [Enums](#enums)
* [CoverArtArchiveImageSize](#coverartarchiveimagesize)
</details>
## Objects
### CoverArtArchiveImage
An individual piece of album artwork from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>fileID</strong></td>
<td valign="top"><a href="../types.md#string">String</a>!</td>
<td>
The Internet Archives internal file ID for the image.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>image</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a>!</td>
<td>
The URL at which the image can be found.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>thumbnails</strong></td>
<td valign="top"><a href="#coverartarchiveimagethumbnails">CoverArtArchiveImageThumbnails</a>!</td>
<td>
A set of thumbnails for the image.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>front</strong></td>
<td valign="top"><a href="../types.md#boolean">Boolean</a>!</td>
<td>
Whether this image depicts the “main front” of the release.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>back</strong></td>
<td valign="top"><a href="../types.md#boolean">Boolean</a>!</td>
<td>
Whether this image depicts the “main back” of the release.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>types</strong></td>
<td valign="top">[<a href="../types.md#string">String</a>]!</td>
<td>
A list of [image types](https://musicbrainz.org/doc/Cover_Art/Types)
describing what part(s) of the release the image includes.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>edit</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The MusicBrainz edit ID.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>approved</strong></td>
<td valign="top"><a href="../types.md#boolean">Boolean</a></td>
<td>
Whether the image was approved by the MusicBrainz edit system.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>comment</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
A free-text comment left for the image.
</td>
</tr>
</tbody>
</table>
### CoverArtArchiveImageThumbnails
URLs for thumbnails of different sizes for a particular piece of
cover art.
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>small</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL of a small version of the cover art, where the maximum dimension is
250px.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>large</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL of a large version of the cover art, where the maximum dimension is
500px.
</td>
</tr>
</tbody>
</table>
### CoverArtArchiveRelease
An object containing a list of the cover art images for a release obtained
from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive),
as well as a summary of what artwork is available.
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>front</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL of an image depicting the album cover or “main front” of the release,
i.e. the front of the packaging of the audio recording (or in the case of a
digital release, the image associated with it in a digital media store).
In the MusicBrainz schema, this field is a Boolean value indicating the
presence of a front image, whereas here the value is the URL for the image
itself if one exists. You can check for null if you just want to determine
the presence of an image.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#coverartarchiveimagesize">CoverArtArchiveImageSize</a></td>
<td>
The size of the image to retrieve. By default, the returned image will
have its full original dimensions, but certain thumbnail sizes may be
retrieved as well.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>back</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL of an image depicting the “main back” of the release, i.e. the back
of the packaging of the audio recording.
In the MusicBrainz schema, this field is a Boolean value indicating the
presence of a back image, whereas here the value is the URL for the image
itself. You can check for null if you just want to determine the presence of
an image.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#coverartarchiveimagesize">CoverArtArchiveImageSize</a></td>
<td>
The size of the image to retrieve. By default, the returned image will
have its full original dimensions, but certain thumbnail sizes may be
retrieved as well.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>images</strong></td>
<td valign="top">[<a href="#coverartarchiveimage">CoverArtArchiveImage</a>]!</td>
<td>
A list of images depicting the different sides and surfaces of a releases
media and packaging.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>artwork</strong></td>
<td valign="top"><a href="../types.md#boolean">Boolean</a>!</td>
<td>
Whether there is artwork present for this release.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>count</strong></td>
<td valign="top"><a href="../types.md#int">Int</a>!</td>
<td>
The number of artwork images present for this release.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>release</strong></td>
<td valign="top"><a href="#release">Release</a></td>
<td>
The particular release shown in the returned cover art.
</td>
</tr>
</tbody>
</table>
### Release
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>coverArtArchive</strong></td>
<td valign="top"><a href="#coverartarchiverelease">CoverArtArchiveRelease</a></td>
<td>
An object containing a list and summary of the cover art images that are
present for this release from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
This field is provided by the Cover Art Archive extension.
</td>
</tr>
</tbody>
</table>
### ReleaseGroup
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>coverArtArchive</strong></td>
<td valign="top"><a href="#coverartarchiverelease">CoverArtArchiveRelease</a></td>
<td>
The cover art for a release in the release group, obtained from the
[Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). A
release in the release group will be chosen as representative of the release
group.
This field is provided by the Cover Art Archive extension.
</td>
</tr>
</tbody>
</table>
## Enums
### CoverArtArchiveImageSize
The image sizes that may be requested at the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
<table>
<thead>
<th align="left">Value</th>
<th align="left">Description</th>
</thead>
<tbody>
<tr>
<td valign="top"><strong>SMALL</strong></td>
<td>
A maximum dimension of 250px.
</td>
</tr>
<tr>
<td valign="top"><strong>LARGE</strong></td>
<td>
A maximum dimension of 500px.
</td>
</tr>
<tr>
<td valign="top"><strong>FULL</strong></td>
<td>
The images original dimensions, with no maximum.
</td>
</tr>
</tbody>
</table>

View file

@ -0,0 +1,473 @@
# Extension: fanart.tv
Retrieve high quality artwork for artists, releases, and labels from [fanart.tv](https://fanart.tv/).
This extension uses its own cache, separate from the MusicBrainz loader cache.
## Configuration
This extension can be configured using environment variables:
* **`FANART_API_KEY`**: The fanart.tv API key to use. This is required for any
fields added by the extension to successfully resolve.
* **`FANART_BASE_URL`**: The base URL at which to access the
fanart.tv API. Defaults to `http://webservice.fanart.tv/v3/`.
* **`FANART_CACHE_SIZE`**: The number of items to keep in the cache.
Defaults to `GRAPHBRAINZ_CACHE_SIZE` if defined, or `8192`.
* **`FANART_CACHE_TTL`**: The number of seconds to keep items in the
cache. Defaults to `GRAPHBRAINZ_CACHE_TTL` if defined, or `86400000` (one day).
<details>
<summary><strong>Table of Contents</strong></summary>
* [Objects](#objects)
* [Artist](#artist)
* [FanArtAlbum](#fanartalbum)
* [FanArtArtist](#fanartartist)
* [FanArtDiscImage](#fanartdiscimage)
* [FanArtImage](#fanartimage)
* [FanArtLabel](#fanartlabel)
* [FanArtLabelImage](#fanartlabelimage)
* [Label](#label)
* [ReleaseGroup](#releasegroup)
* [Enums](#enums)
* [FanArtImageSize](#fanartimagesize)
</details>
## Objects
### Artist
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>fanArt</strong></td>
<td valign="top"><a href="#fanartartist">FanArtArtist</a></td>
<td>
Images of the artist from [fanart.tv](https://fanart.tv/).
This field is provided by the fanart.tv extension.
</td>
</tr>
</tbody>
</table>
### FanArtAlbum
An object containing lists of the different types of release group images from
[fanart.tv](https://fanart.tv/).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>albumCovers</strong></td>
<td valign="top">[<a href="#fanartimage">FanArtImage</a>]</td>
<td>
A list of 1000x1000 JPG images of the cover artwork of the release group.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>discImages</strong></td>
<td valign="top">[<a href="#fanartdiscimage">FanArtDiscImage</a>]</td>
<td>
A list of 1000x1000 PNG images of the physical disc media for the release
group, with transparent backgrounds.
</td>
</tr>
</tbody>
</table>
### FanArtArtist
An object containing lists of the different types of artist images from
[fanart.tv](https://fanart.tv/).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>backgrounds</strong></td>
<td valign="top">[<a href="#fanartimage">FanArtImage</a>]</td>
<td>
A list of 1920x1080 JPG images picturing the artist, suitable for use as
backgrounds.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>banners</strong></td>
<td valign="top">[<a href="#fanartimage">FanArtImage</a>]</td>
<td>
A list of 1000x185 JPG images containing the artist and their logo or name.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>logos</strong></td>
<td valign="top">[<a href="#fanartimage">FanArtImage</a>]</td>
<td>
A list of 400x155 PNG images containing the artists logo or name, with
transparent backgrounds.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>logosHD</strong></td>
<td valign="top">[<a href="#fanartimage">FanArtImage</a>]</td>
<td>
A list of 800x310 PNG images containing the artists logo or name, with
transparent backgrounds.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>thumbnails</strong></td>
<td valign="top">[<a href="#fanartimage">FanArtImage</a>]</td>
<td>
A list of 1000x1000 JPG thumbnail images picturing the artist (usually
containing every member of a band).
</td>
</tr>
</tbody>
</table>
### FanArtDiscImage
A disc image from [fanart.tv](https://fanart.tv/).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>imageID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
The ID of the image on fanart.tv.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>url</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL of the image.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#fanartimagesize">FanArtImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>likeCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of likes the image has received by fanart.tv users.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>discNumber</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The disc number.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>size</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The width and height of the (square) disc image.
</td>
</tr>
</tbody>
</table>
### FanArtImage
A single image from [fanart.tv](https://fanart.tv/).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>imageID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
The ID of the image on fanart.tv.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>url</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL of the image.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#fanartimagesize">FanArtImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>likeCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of likes the image has received by fanart.tv users.
</td>
</tr>
</tbody>
</table>
### FanArtLabel
An object containing lists of the different types of label images from
[fanart.tv](https://fanart.tv/).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>logos</strong></td>
<td valign="top">[<a href="#fanartlabelimage">FanArtLabelImage</a>]</td>
<td>
A list of 400x270 PNG images containing the labels logo. There will
usually be a black version, a color version, and a white version, all with
transparent backgrounds.
</td>
</tr>
</tbody>
</table>
### FanArtLabelImage
A music label image from [fanart.tv](https://fanart.tv/).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>imageID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
The ID of the image on fanart.tv.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>url</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL of the image.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#fanartimagesize">FanArtImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>likeCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of likes the image has received by fanart.tv users.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>color</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The type of color content in the image (usually “white” or “colour”).
</td>
</tr>
</tbody>
</table>
### Label
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>fanArt</strong></td>
<td valign="top"><a href="#fanartlabel">FanArtLabel</a></td>
<td>
Images of the label from [fanart.tv](https://fanart.tv/).
This field is provided by the fanart.tv extension.
</td>
</tr>
</tbody>
</table>
### ReleaseGroup
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>fanArt</strong></td>
<td valign="top"><a href="#fanartalbum">FanArtAlbum</a></td>
<td>
Images of the release group from [fanart.tv](https://fanart.tv/).
This field is provided by the fanart.tv extension.
</td>
</tr>
</tbody>
</table>
## Enums
### FanArtImageSize
The image sizes that may be requested at [fanart.tv](https://fanart.tv/).
<table>
<thead>
<th align="left">Value</th>
<th align="left">Description</th>
</thead>
<tbody>
<tr>
<td valign="top"><strong>FULL</strong></td>
<td>
The images full original dimensions.
</td>
</tr>
<tr>
<td valign="top"><strong>PREVIEW</strong></td>
<td>
A maximum dimension of 200px.
</td>
</tr>
</tbody>
</table>

View file

@ -0,0 +1,404 @@
# Extension: MediaWiki
Retrieve information from MediaWiki image pages, like the actual image file URL and EXIF metadata.
On entities with [URL relationship types][relationships] that represent images,
this extension will find those URLs that appear to be MediaWiki image pages, and
use the [MediaWiki API][] to fetch information about the image. This information
will include the actual file URL, so you can use it as the `src` in an `<img>`
tag (for example).
MediaWiki image URLs are assumed to be those with a path that starts with
`/wiki/Image:` or `/wiki/File:`.
This extension uses its own cache, separate from the MusicBrainz loader cache.
## Configuration
This extension can be configured using environment variables:
* **`COVER_ART_ARCHIVE_BASE_URL`**: The base URL at which to access to Cover Art
Archive API. Defaults to `http://coverartarchive.org/`.
* **`COVER_ART_ARCHIVE_CACHE_SIZE`**: The number of items to keep in the cache.
Defaults to `GRAPHBRAINZ_CACHE_SIZE` if defined, or `8192`.
* **`COVER_ART_ARCHIVE_CACHE_TTL`**: The number of seconds to keep items in the
cache. Defaults to `GRAPHBRAINZ_CACHE_TTL` if defined, or `86400000` (one day).
[relationships]: https://musicbrainz.org/relationships
[MediaWiki API]: https://www.mediawiki.org/wiki/API:Main_page
<details>
<summary><strong>Table of Contents</strong></summary>
* [Objects](#objects)
* [Artist](#artist)
* [Instrument](#instrument)
* [Label](#label)
* [MediaWikiImage](#mediawikiimage)
* [MediaWikiImageMetadata](#mediawikiimagemetadata)
* [Place](#place)
</details>
## Objects
### Artist
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>mediaWikiImages</strong></td>
<td valign="top">[<a href="#mediawikiimage">MediaWikiImage</a>]!</td>
<td>
Artist images found at MediaWiki URLs in the artists URL relationships.
Defaults to URL relationships with the type “image”.
This field is provided by the MediaWiki extension.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">type</td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The type of URL relationship that will be selected to find images. See
the possible [Artist-URL relationship types](https://musicbrainz.org/relationships/artist-url).
</td>
</tr>
</tbody>
</table>
### Instrument
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>mediaWikiImages</strong></td>
<td valign="top">[<a href="#mediawikiimage">MediaWikiImage</a>]!</td>
<td>
Instrument images found at MediaWiki URLs in the instruments URL
relationships. Defaults to URL relationships with the type “image”.
This field is provided by the MediaWiki extension.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">type</td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The type of URL relationship that will be selected to find images. See the
possible [Instrument-URL relationship types](https://musicbrainz.org/relationships/instrument-url).
</td>
</tr>
</tbody>
</table>
### Label
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>mediaWikiImages</strong></td>
<td valign="top">[<a href="#mediawikiimage">MediaWikiImage</a>]!</td>
<td>
Label images found at MediaWiki URLs in the labels URL relationships.
Defaults to URL relationships with the type “logo”.
This field is provided by the MediaWiki extension.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">type</td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The type of URL relationship that will be selected to find images. See the
possible [Label-URL relationship types](https://musicbrainz.org/relationships/label-url).
</td>
</tr>
</tbody>
</table>
### MediaWikiImage
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>url</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a>!</td>
<td>
The URL of the actual image file.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>descriptionURL</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL of the wiki page describing the image.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>user</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The user who uploaded the file.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>size</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The size of the file in bytes.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>width</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The pixel width of the image.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>height</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The pixel height of the image.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>canonicalTitle</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The canonical title of the file.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>objectName</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The image title, brief description, or file name.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>descriptionHTML</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
A description of the image, potentially containing HTML.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>originalDateTimeHTML</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The original date of creation of the image. May be a description rather than
a parseable timestamp, and may contain HTML.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>categories</strong></td>
<td valign="top">[<a href="../types.md#string">String</a>]!</td>
<td>
A list of the categories of the image.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>artistHTML</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The name of the image author, potentially containing HTML.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>creditHTML</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The source of the image, potentially containing HTML.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>licenseShortName</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
A short human-readable license name.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>licenseURL</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
A web address where the license is described.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>metadata</strong></td>
<td valign="top">[<a href="#mediawikiimagemetadata">MediaWikiImageMetadata</a>]!</td>
<td>
The full list of values in the `extmetadata` field.
</td>
</tr>
</tbody>
</table>
### MediaWikiImageMetadata
An entry in the `extmetadata` field of a MediaWiki image file.
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>name</strong></td>
<td valign="top"><a href="../types.md#string">String</a>!</td>
<td>
The name of the metadata field.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>value</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The value of the metadata field. All values will be converted to strings.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>source</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The source of the value.
</td>
</tr>
</tbody>
</table>
### Place
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>mediaWikiImages</strong></td>
<td valign="top">[<a href="#mediawikiimage">MediaWikiImage</a>]!</td>
<td>
Place images found at MediaWiki URLs in the places URL relationships.
Defaults to URL relationships with the type “image”.
This field is provided by the MediaWiki extension.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">type</td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The type of URL relationship that will be selected to find images. See the
possible [Place-URL relationship types](https://musicbrainz.org/relationships/place-url).
</td>
</tr>
</tbody>
</table>

View file

@ -0,0 +1,766 @@
# Extension: TheAudioDB
Retrieve images and information about artists, releases, and recordings from [TheAudioDB.com](http://www.theaudiodb.com/).
This extension uses its own cache, separate from the MusicBrainz loader cache.
## Configuration
This extension can be configured using environment variables:
* **`THEAUDIODB_API_KEY`**: TheAudioDB API key to use. This is required for any
fields added by the extension to successfully resolve.
* **`THEAUDIODB_BASE_URL`**: The base URL at which to access TheAudioDB API.
Defaults to `http://www.theaudiodb.com/api/v1/json/`.
* **`THEAUDIODB_CACHE_SIZE`**: The number of items to keep in the cache.
Defaults to `GRAPHBRAINZ_CACHE_SIZE` if defined, or `8192`.
* **`THEAUDIODB_CACHE_TTL`**: The number of seconds to keep items in the
cache. Defaults to `GRAPHBRAINZ_CACHE_TTL` if defined, or `86400000` (one day).
<details>
<summary><strong>Table of Contents</strong></summary>
* [Objects](#objects)
* [Artist](#artist)
* [Recording](#recording)
* [ReleaseGroup](#releasegroup)
* [TheAudioDBAlbum](#theaudiodbalbum)
* [TheAudioDBArtist](#theaudiodbartist)
* [TheAudioDBMusicVideo](#theaudiodbmusicvideo)
* [TheAudioDBTrack](#theaudiodbtrack)
* [Enums](#enums)
* [TheAudioDBImageSize](#theaudiodbimagesize)
</details>
## Objects
### Artist
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>theAudioDB</strong></td>
<td valign="top"><a href="#theaudiodbartist">TheAudioDBArtist</a></td>
<td>
Data about the artist from [TheAudioDB](http://www.theaudiodb.com/), a good
source of biographical information and images.
This field is provided by TheAudioDB extension.
</td>
</tr>
</tbody>
</table>
### Recording
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>theAudioDB</strong></td>
<td valign="top"><a href="#theaudiodbtrack">TheAudioDBTrack</a></td>
<td>
Data about the recording from [TheAudioDB](http://www.theaudiodb.com/).
This field is provided by TheAudioDB extension.
</td>
</tr>
</tbody>
</table>
### ReleaseGroup
:small_blue_diamond: *This type has been extended. See the [base schema](../types.md)
for a description and additional fields.*
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>theAudioDB</strong></td>
<td valign="top"><a href="#theaudiodbalbum">TheAudioDBAlbum</a></td>
<td>
Data about the release group from [TheAudioDB](http://www.theaudiodb.com/),
a good source of descriptive information, reviews, and images.
This field is provided by TheAudioDB extension.
</td>
</tr>
</tbody>
</table>
### TheAudioDBAlbum
An album on [TheAudioDB](http://www.theaudiodb.com/) corresponding with a
MusicBrainz Release Group.
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>albumID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
TheAudioDB ID of the album.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>artistID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
TheAudioDB ID of the artist who released the album.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>description</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
A description of the album, often available in several languages.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">lang</td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The two-letter code for the language in which to retrieve the biography.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>review</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
A review of the album.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>salesCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The worldwide sales figure.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>score</strong></td>
<td valign="top"><a href="../types.md#float">Float</a></td>
<td>
The albums rating as determined by user votes, out of 10.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>scoreVotes</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of users who voted to determine the albums score.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>discImage</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
An image of the physical disc media for the album.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>spineImage</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
An image of the spine of the album packaging.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>frontImage</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
An image of the front of the album packaging.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>backImage</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
An image of the back of the album packaging.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>genre</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical genre of the album (e.g. “Alternative Rock”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>mood</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical mood of the album (e.g. “Sad”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>style</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical style of the album (e.g. “Rock/Pop”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>speed</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
A rough description of the primary musical speed of the album (e.g. “Medium”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>theme</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical theme of the album (e.g. “In Love”).
</td>
</tr>
</tbody>
</table>
### TheAudioDBArtist
An artist on [TheAudioDB](http://www.theaudiodb.com/).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>artistID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
TheAudioDB ID of the artist.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>biography</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
A biography of the artist, often available in several languages.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">lang</td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The two-letter code for the language in which to retrieve the biography.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>memberCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of members in the musical group, if applicable.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>banner</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
A 1000x185 JPG banner image containing the artist and their logo or name.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>fanArt</strong></td>
<td valign="top">[<a href="../types.md#urlstring">URLString</a>]!</td>
<td>
A list of 1280x720 or 1920x1080 JPG images depicting the artist.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the images to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>logo</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
A 400x155 PNG image containing the artists logo or name, with a transparent
background.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>thumbnail</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
A 1000x1000 JPG thumbnail image picturing the artist (usually containing
every member of a band).
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>genre</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical genre of the artist (e.g. “Alternative Rock”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>mood</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical mood of the artist (e.g. “Sad”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>style</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical style of the artist (e.g. “Rock/Pop”).
</td>
</tr>
</tbody>
</table>
### TheAudioDBMusicVideo
Details of a music video associated with a track on [TheAudioDB](http://www.theaudiodb.com/).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>url</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
The URL where the music video can be found.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>companyName</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The video production company of the music video.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>directorName</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The director of the music video.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>screenshots</strong></td>
<td valign="top">[<a href="../types.md#urlstring">URLString</a>]!</td>
<td>
A list of still images from the music video.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the images to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>viewCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of views the video has received at the given URL. This will rarely
be up to date, so use cautiously.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>likeCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of likes the video has received at the given URL. This will rarely
be up to date, so use cautiously.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>dislikeCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of dislikes the video has received at the given URL. This will
rarely be up to date, so use cautiously.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>commentCount</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of comments the video has received at the given URL. This will
rarely be up to date, so use cautiously.
</td>
</tr>
</tbody>
</table>
### TheAudioDBTrack
A track on [TheAudioDB](http://www.theaudiodb.com/) corresponding with a
MusicBrainz Recording.
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>trackID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
TheAudioDB ID of the track.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>albumID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
TheAudioDB ID of the album on which the track appears.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>artistID</strong></td>
<td valign="top"><a href="../types.md#id">ID</a></td>
<td>
TheAudioDB ID of the artist who released the track.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>description</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
A description of the track.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">lang</td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td></td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>thumbnail</strong></td>
<td valign="top"><a href="../types.md#urlstring">URLString</a></td>
<td>
A thumbnail image for the track.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#theaudiodbimagesize">TheAudioDBImageSize</a></td>
<td>
The size of the image to retrieve.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>score</strong></td>
<td valign="top"><a href="../types.md#float">Float</a></td>
<td>
The tracks rating as determined by user votes, out of 10.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>scoreVotes</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The number of users who voted to determine the albums score.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>trackNumber</strong></td>
<td valign="top"><a href="../types.md#int">Int</a></td>
<td>
The track number of the song on the album.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>musicVideo</strong></td>
<td valign="top"><a href="#theaudiodbmusicvideo">TheAudioDBMusicVideo</a></td>
<td>
The official music video for the track.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>genre</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical genre of the track (e.g. “Alternative Rock”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>mood</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical mood of the track (e.g. “Sad”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>style</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical style of the track (e.g. “Rock/Pop”).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>theme</strong></td>
<td valign="top"><a href="../types.md#string">String</a></td>
<td>
The primary musical theme of the track (e.g. “In Love”).
</td>
</tr>
</tbody>
</table>
## Enums
### TheAudioDBImageSize
The image sizes that may be requested at [TheAudioDB](http://www.theaudiodb.com/).
<table>
<thead>
<th align="left">Value</th>
<th align="left">Description</th>
</thead>
<tbody>
<tr>
<td valign="top"><strong>FULL</strong></td>
<td>
The images full original dimensions.
</td>
</tr>
<tr>
<td valign="top"><strong>PREVIEW</strong></td>
<td>
A maximum dimension of 200px.
</td>
</tr>
</tbody>
</table>

View file

@ -596,62 +596,6 @@ type Coordinates {
longitude: Degrees longitude: Degrees
} }
# An individual piece of album artwork from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
type CoverArtImage {
# The Internet Archives internal file ID for the image.
fileID: String!
# The URL at which the image can be found.
image: URLString!
# A set of thumbnails for the image.
thumbnails: CoverArtImageThumbnails
# Whether this image depicts the “main front” of the release.
front: Boolean!
# Whether this image depicts the “main back” of the release.
back: Boolean!
# A list of [image types](https://musicbrainz.org/doc/Cover_Art/Types)
# describing what part(s) of the release the image includes.
types: [String]
# The MusicBrainz edit ID.
edit: Int
# Whether the image was approved by the MusicBrainz edit system.
approved: Boolean
# A free-text comment left for the image.
comment: String
}
# The image sizes that may be requested at the [Cover Art
# Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
enum CoverArtImageSize {
# A maximum dimension of 250px.
SMALL
# A maximum dimension of 500px.
LARGE
# The images original dimensions, with no maximum.
FULL
}
# URLs for thumbnails of different sizes for a particular piece of
# cover art.
type CoverArtImageThumbnails {
# The URL of a small version of the cover art, where the
# maximum dimension is 250px.
small: URLString
# The URL of a large version of the cover art, where the
# maximum dimension is 500px.
large: URLString
}
# Year, month (optional), and day (optional) in YYYY-MM-DD format. # Year, month (optional), and day (optional) in YYYY-MM-DD format.
scalar Date scalar Date
@ -1703,10 +1647,6 @@ type Release implements Node, Entity {
# [EANs](https://en.wikipedia.org/wiki/International_Article_Number). # [EANs](https://en.wikipedia.org/wiki/International_Article_Number).
barcode: String barcode: String
# A list and summary of the cover art images that are present
# for this release from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
coverArt: ReleaseCoverArt!
# The status describes how “official” a release is. # The status describes how “official” a release is.
status: ReleaseStatus status: ReleaseStatus
@ -1775,57 +1715,6 @@ type ReleaseConnection {
totalCount: Int totalCount: Int
} }
# An object containing a list of the cover art images for a
# release obtained from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive),
# as well as a summary of what artwork is available.
type ReleaseCoverArt {
# The URL of an image depicting the album cover or “main
# front” of the release, i.e. the front of the packaging of the audio recording
# (or in the case of a digital release, the image associated with it in a digital
# media store).
#
# In the MusicBrainz schema, this field is a Boolean value indicating the presence
# of a front image, whereas here the value is the URL for the image itself if one
# exists. You can check for null if you just want to determine the presence of an
# image.
front(
# The size of the image to retrieve. By default, the returned
# image will have its full original dimensions, but certain thumbnail sizes may be
# retrieved as well.
size: CoverArtImageSize = null
): URLString
# The URL of an image depicting the “main back” of the
# release, i.e. the back of the packaging of the audio recording.
#
# In the MusicBrainz schema, this field is a Boolean value indicating the presence
# of a back image, whereas here the value is the URL for the image itself. You can
# check for null if you just want to determine the presence of an image.
back(
# The size of the image to retrieve. By default, the returned
# image will have its full original dimensions, but certain thumbnail sizes may be
# retrieved as well.
size: CoverArtImageSize = null
): URLString
# A list of images depicting the different sides and surfaces
# of a releases media and packaging.
images: [CoverArtImage]
# Whether there is artwork present for this release.
artwork: Boolean!
# Whether the Cover Art Archive has received a take-down
# request for this releases artwork, disallowing new uploads.
darkened: Boolean!
# The number of artwork images present for this release.
count: Int!
# The particular release shown in the returned cover art.
release: Release!
}
# An edge in a connection. # An edge in a connection.
type ReleaseEdge { type ReleaseEdge {
# The item at the end of the edge # The item at the end of the edge
@ -1898,10 +1787,6 @@ type ReleaseGroup implements Node, Entity {
# field. # field.
secondaryTypeIDs: [MBID] secondaryTypeIDs: [MBID]
# The cover art for a release group, obtained from the [Cover
# Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
coverArt: ReleaseGroupCoverArt
# A list of artists linked to this entity. # A list of artists linked to this entity.
artists(after: String, first: Int): ArtistConnection artists(after: String, first: Int): ArtistConnection
@ -1946,34 +1831,6 @@ type ReleaseGroupConnection {
totalCount: Int totalCount: Int
} }
# An object containing the cover art for a release group obtained
# from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). For
# release groups, just the front cover of a particular release will be selected.
type ReleaseGroupCoverArt {
# The URL of an image depicting the album cover or “main
# front” of a release in the release group, i.e. the front of the packaging of the
# audio recording (or in the case of a digital release, the image associated with
# it in a digital media store).
front(
# The size of the image to retrieve. By default, the returned
# image will have its full original dimensions, but certain thumbnail sizes may be
# retrieved as well.
size: CoverArtImageSize = null
): URLString
# A list of images returned by the [Cover Art
# Archive](https://musicbrainz.org/doc/Cover_Art_Archive) for a release group. A
# particular releases front image will be included in the list, and likely no
# others, even if other images are available.
images: [CoverArtImage]
# Whether there is artwork present for this release group.
artwork: Boolean!
# The particular release shown in the returned cover art.
release: Release!
}
# An edge in a connection. # An edge in a connection.
type ReleaseGroupEdge { type ReleaseGroupEdge {
# The item at the end of the edge # The item at the end of the edge

View file

@ -20,8 +20,6 @@ You may also be interested in reading the [schema in GraphQL syntax](schema.md).
* [CollectionConnection](#collectionconnection) * [CollectionConnection](#collectionconnection)
* [CollectionEdge](#collectionedge) * [CollectionEdge](#collectionedge)
* [Coordinates](#coordinates) * [Coordinates](#coordinates)
* [CoverArtImage](#coverartimage)
* [CoverArtImageThumbnails](#coverartimagethumbnails)
* [Disc](#disc) * [Disc](#disc)
* [Event](#event) * [Event](#event)
* [EventConnection](#eventconnection) * [EventConnection](#eventconnection)
@ -49,12 +47,10 @@ You may also be interested in reading the [schema in GraphQL syntax](schema.md).
* [Relationships](#relationships) * [Relationships](#relationships)
* [Release](#release) * [Release](#release)
* [ReleaseConnection](#releaseconnection) * [ReleaseConnection](#releaseconnection)
* [ReleaseCoverArt](#releasecoverart)
* [ReleaseEdge](#releaseedge) * [ReleaseEdge](#releaseedge)
* [ReleaseEvent](#releaseevent) * [ReleaseEvent](#releaseevent)
* [ReleaseGroup](#releasegroup) * [ReleaseGroup](#releasegroup)
* [ReleaseGroupConnection](#releasegroupconnection) * [ReleaseGroupConnection](#releasegroupconnection)
* [ReleaseGroupCoverArt](#releasegroupcoverart)
* [ReleaseGroupEdge](#releasegroupedge) * [ReleaseGroupEdge](#releasegroupedge)
* [SearchQuery](#searchquery) * [SearchQuery](#searchquery)
* [Series](#series) * [Series](#series)
@ -68,7 +64,6 @@ You may also be interested in reading the [schema in GraphQL syntax](schema.md).
* [WorkConnection](#workconnection) * [WorkConnection](#workconnection)
* [WorkEdge](#workedge) * [WorkEdge](#workedge)
* [Enums](#enums) * [Enums](#enums)
* [CoverArtImageSize](#coverartimagesize)
* [ReleaseGroupType](#releasegrouptype) * [ReleaseGroupType](#releasegrouptype)
* [ReleaseStatus](#releasestatus) * [ReleaseStatus](#releasestatus)
* [Scalars](#scalars) * [Scalars](#scalars)
@ -2178,143 +2173,6 @@ The eastwest position of a point on the Earths surface.
</tbody> </tbody>
</table> </table>
### CoverArtImage
An individual piece of album artwork from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>fileID</strong></td>
<td valign="top"><a href="#string">String</a>!</td>
<td>
The Internet Archives internal file ID for the image.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>image</strong></td>
<td valign="top"><a href="#urlstring">URLString</a>!</td>
<td>
The URL at which the image can be found.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>thumbnails</strong></td>
<td valign="top"><a href="#coverartimagethumbnails">CoverArtImageThumbnails</a></td>
<td>
A set of thumbnails for the image.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>front</strong></td>
<td valign="top"><a href="#boolean">Boolean</a>!</td>
<td>
Whether this image depicts the “main front” of the release.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>back</strong></td>
<td valign="top"><a href="#boolean">Boolean</a>!</td>
<td>
Whether this image depicts the “main back” of the release.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>types</strong></td>
<td valign="top">[<a href="#string">String</a>]</td>
<td>
A list of [image types](https://musicbrainz.org/doc/Cover_Art/Types)
describing what part(s) of the release the image includes.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>edit</strong></td>
<td valign="top"><a href="#int">Int</a></td>
<td>
The MusicBrainz edit ID.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>approved</strong></td>
<td valign="top"><a href="#boolean">Boolean</a></td>
<td>
Whether the image was approved by the MusicBrainz edit system.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>comment</strong></td>
<td valign="top"><a href="#string">String</a></td>
<td>
A free-text comment left for the image.
</td>
</tr>
</tbody>
</table>
### CoverArtImageThumbnails
URLs for thumbnails of different sizes for a particular piece of
cover art.
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>small</strong></td>
<td valign="top"><a href="#urlstring">URLString</a></td>
<td>
The URL of a small version of the cover art, where the
maximum dimension is 250px.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>large</strong></td>
<td valign="top"><a href="#urlstring">URLString</a></td>
<td>
The URL of a large version of the cover art, where the
maximum dimension is 500px.
</td>
</tr>
</tbody>
</table>
### Disc ### Disc
Information about the physical CD and releases associated with a Information about the physical CD and releases associated with a
@ -5447,16 +5305,6 @@ release has one. The most common types found on releases are 12-digit
[UPCs](https://en.wikipedia.org/wiki/Universal_Product_Code) and 13-digit [UPCs](https://en.wikipedia.org/wiki/Universal_Product_Code) and 13-digit
[EANs](https://en.wikipedia.org/wiki/International_Article_Number). [EANs](https://en.wikipedia.org/wiki/International_Article_Number).
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>coverArt</strong></td>
<td valign="top"><a href="#releasecoverart">ReleaseCoverArt</a>!</td>
<td>
A list and summary of the cover art images that are present
for this release from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
</td> </td>
</tr> </tr>
<tr> <tr>
@ -5709,125 +5557,6 @@ ignoring pagination.
</tbody> </tbody>
</table> </table>
### ReleaseCoverArt
An object containing a list of the cover art images for a
release obtained from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive),
as well as a summary of what artwork is available.
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>front</strong></td>
<td valign="top"><a href="#urlstring">URLString</a></td>
<td>
The URL of an image depicting the album cover or “main
front” of the release, i.e. the front of the packaging of the audio recording
(or in the case of a digital release, the image associated with it in a digital
media store).
In the MusicBrainz schema, this field is a Boolean value indicating the presence
of a front image, whereas here the value is the URL for the image itself if one
exists. You can check for null if you just want to determine the presence of an
image.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#coverartimagesize">CoverArtImageSize</a></td>
<td>
The size of the image to retrieve. By default, the returned
image will have its full original dimensions, but certain thumbnail sizes may be
retrieved as well.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>back</strong></td>
<td valign="top"><a href="#urlstring">URLString</a></td>
<td>
The URL of an image depicting the “main back” of the
release, i.e. the back of the packaging of the audio recording.
In the MusicBrainz schema, this field is a Boolean value indicating the presence
of a back image, whereas here the value is the URL for the image itself. You can
check for null if you just want to determine the presence of an image.
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#coverartimagesize">CoverArtImageSize</a></td>
<td>
The size of the image to retrieve. By default, the returned
image will have its full original dimensions, but certain thumbnail sizes may be
retrieved as well.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>images</strong></td>
<td valign="top">[<a href="#coverartimage">CoverArtImage</a>]</td>
<td>
A list of images depicting the different sides and surfaces
of a releases media and packaging.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>artwork</strong></td>
<td valign="top"><a href="#boolean">Boolean</a>!</td>
<td>
Whether there is artwork present for this release.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>darkened</strong></td>
<td valign="top"><a href="#boolean">Boolean</a>!</td>
<td>
Whether the Cover Art Archive has received a take-down
request for this releases artwork, disallowing new uploads.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>count</strong></td>
<td valign="top"><a href="#int">Int</a>!</td>
<td>
The number of artwork images present for this release.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>release</strong></td>
<td valign="top"><a href="#release">Release</a>!</td>
<td>
The particular release shown in the returned cover art.
</td>
</tr>
</tbody>
</table>
### ReleaseEdge ### ReleaseEdge
An edge in a connection. An edge in a connection.
@ -6045,16 +5774,6 @@ that apply to this release group.
The MBIDs associated with the values of the `secondaryTypes` The MBIDs associated with the values of the `secondaryTypes`
field. field.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>coverArt</strong></td>
<td valign="top"><a href="#releasegroupcoverart">ReleaseGroupCoverArt</a></td>
<td>
The cover art for a release group, obtained from the [Cover
Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
</td> </td>
</tr> </tr>
<tr> <tr>
@ -6227,78 +5946,6 @@ ignoring pagination.
</tbody> </tbody>
</table> </table>
### ReleaseGroupCoverArt
An object containing the cover art for a release group obtained
from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). For
release groups, just the front cover of a particular release will be selected.
<table>
<thead>
<tr>
<th align="left">Field</th>
<th align="right">Argument</th>
<th align="left">Type</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2" valign="top"><strong>front</strong></td>
<td valign="top"><a href="#urlstring">URLString</a></td>
<td>
The URL of an image depicting the album cover or “main
front” of a release in the release group, i.e. the front of the packaging of the
audio recording (or in the case of a digital release, the image associated with
it in a digital media store).
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="top">size</td>
<td valign="top"><a href="#coverartimagesize">CoverArtImageSize</a></td>
<td>
The size of the image to retrieve. By default, the returned
image will have its full original dimensions, but certain thumbnail sizes may be
retrieved as well.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>images</strong></td>
<td valign="top">[<a href="#coverartimage">CoverArtImage</a>]</td>
<td>
A list of images returned by the [Cover Art
Archive](https://musicbrainz.org/doc/Cover_Art_Archive) for a release group. A
particular releases front image will be included in the list, and likely no
others, even if other images are available.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>artwork</strong></td>
<td valign="top"><a href="#boolean">Boolean</a>!</td>
<td>
Whether there is artwork present for this release group.
</td>
</tr>
<tr>
<td colspan="2" valign="top"><strong>release</strong></td>
<td valign="top"><a href="#release">Release</a>!</td>
<td>
The particular release shown in the returned cover art.
</td>
</tr>
</tbody>
</table>
### ReleaseGroupEdge ### ReleaseGroupEdge
An edge in a connection. An edge in a connection.
@ -7374,44 +7021,6 @@ these results were found through a search.
## Enums ## Enums
### CoverArtImageSize
The image sizes that may be requested at the [Cover Art
Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
<table>
<thead>
<th align="left">Value</th>
<th align="left">Description</th>
</thead>
<tbody>
<tr>
<td valign="top"><strong>SMALL</strong></td>
<td>
A maximum dimension of 250px.
</td>
</tr>
<tr>
<td valign="top"><strong>LARGE</strong></td>
<td>
A maximum dimension of 500px.
</td>
</tr>
<tr>
<td valign="top"><strong>FULL</strong></td>
<td>
The images original dimensions, with no maximum.
</td>
</tr>
</tbody>
</table>
### ReleaseGroupType ### ReleaseGroupType
A type used to describe release groups, e.g. album, single, EP, A type used to describe release groups, e.g. album, single, EP,

View file

@ -0,0 +1 @@
module.exports = require('../lib/extensions/cover-art-archive')

1
extensions/fanart-tv.js Normal file
View file

@ -0,0 +1 @@
module.exports = require('../lib/extensions/fanart-tv')

1
extensions/mediawiki.js Normal file
View file

@ -0,0 +1 @@
module.exports = require('../lib/extensions/mediawiki')

View file

@ -0,0 +1 @@
module.exports = require('../lib/extensions/the-audio-db')

View file

@ -15,13 +15,14 @@
"yarn.lock" "yarn.lock"
], ],
"engines": { "engines": {
"node": ">=4.6.0", "node": ">=6.0.0",
"npm": ">=3.8.0" "npm": ">=3.8.0"
}, },
"scripts": { "scripts": {
"build": "npm run build:lib && npm run update-schema && npm run build:docs", "build": "npm run build:lib && npm run update-schema && npm run build:docs",
"build:docs": "npm run build:docs:readme && npm run build:docs:schema && npm run build:docs:types", "build:docs": "npm run build:docs:readme && npm run build:docs:schema && npm run build:docs:types && npm run build:docs:extensions",
"build:docs:readme": "doctoc --title \"## Contents\" README.md", "build:docs:extensions": "babel-node scripts/build-extension-docs.js",
"build:docs:readme": "doctoc --notitle README.md docs/extensions/README.md",
"build:docs:schema": "printf '# GraphQL Schema\\n\\n%s\n' \"$(npm run -s print-schema:md)\" > docs/schema.md", "build:docs:schema": "printf '# GraphQL Schema\\n\\n%s\n' \"$(npm run -s print-schema:md)\" > docs/schema.md",
"build:docs:types": "graphql-markdown --require babel-register --prologue 'You may also be interested in reading the [schema in GraphQL syntax](schema.md).' ./src/schema.js > docs/types.md", "build:docs:types": "graphql-markdown --require babel-register --prologue 'You may also be interested in reading the [schema in GraphQL syntax](schema.md).' ./src/schema.js > docs/types.md",
"build:lib": "babel --out-dir lib src", "build:lib": "babel --out-dir lib src",
@ -71,12 +72,14 @@
"dashify": "^0.2.2", "dashify": "^0.2.2",
"dataloader": "^1.3.0", "dataloader": "^1.3.0",
"debug": "^3.0.0", "debug": "^3.0.0",
"deep-diff": "^0.3.8",
"dotenv": "^4.0.0", "dotenv": "^4.0.0",
"es6-error": "^4.0.2", "es6-error": "^4.0.2",
"express": "^4.15.4", "express": "^4.15.4",
"express-graphql": "^0.6.7", "express-graphql": "^0.6.7",
"graphql": "^0.11.7", "graphql": "^0.11.7",
"graphql-relay": "^0.5.2", "graphql-relay": "^0.5.2",
"graphql-tools": "^2.5.1",
"lru-cache": "^4.1.1", "lru-cache": "^4.1.1",
"pascalcase": "^0.1.1", "pascalcase": "^0.1.1",
"postinstall-build": "^5.0.1", "postinstall-build": "^5.0.1",
@ -96,7 +99,7 @@
"coveralls": "^3.0.0", "coveralls": "^3.0.0",
"cross-env": "^5.0.5", "cross-env": "^5.0.5",
"doctoc": "^1.3.0", "doctoc": "^1.3.0",
"graphql-markdown": "^2.2.0", "graphql-markdown": "^3.1.0",
"nodemon": "^1.11.0", "nodemon": "^1.11.0",
"nyc": "^11.1.0", "nyc": "^11.1.0",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
@ -110,6 +113,7 @@
}, },
"ava": { "ava": {
"require": [ "require": [
"dotenv/config",
"babel-register" "babel-register"
] ]
}, },

View file

@ -2954,22 +2954,6 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "coverArt",
"description": "A list and summary of the cover art images that are present\nfor this release from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ReleaseCoverArt",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "status", "name": "status",
"description": "The status describes how “official” a release is.", "description": "The status describes how “official” a release is.",
@ -3332,356 +3316,6 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "ReleaseCoverArt",
"description": "An object containing a list of the cover art images for a\nrelease obtained from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive),\nas well as a summary of what artwork is available.",
"fields": [
{
"name": "front",
"description": "The URL of an image depicting the album cover or “main\nfront” of the release, i.e. the front of the packaging of the audio recording\n(or in the case of a digital release, the image associated with it in a digital\nmedia store).\n\nIn the MusicBrainz schema, this field is a Boolean value indicating the presence\nof a front image, whereas here the value is the URL for the image itself if one\nexists. You can check for null if you just want to determine the presence of an\nimage.",
"args": [
{
"name": "size",
"description": "The size of the image to retrieve. By default, the returned\nimage will have its full original dimensions, but certain thumbnail sizes may be\nretrieved as well.",
"type": {
"kind": "ENUM",
"name": "CoverArtImageSize",
"ofType": null
},
"defaultValue": "null"
}
],
"type": {
"kind": "SCALAR",
"name": "URLString",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "back",
"description": "The URL of an image depicting the “main back” of the\nrelease, i.e. the back of the packaging of the audio recording.\n\nIn the MusicBrainz schema, this field is a Boolean value indicating the presence\nof a back image, whereas here the value is the URL for the image itself. You can\ncheck for null if you just want to determine the presence of an image.",
"args": [
{
"name": "size",
"description": "The size of the image to retrieve. By default, the returned\nimage will have its full original dimensions, but certain thumbnail sizes may be\nretrieved as well.",
"type": {
"kind": "ENUM",
"name": "CoverArtImageSize",
"ofType": null
},
"defaultValue": "null"
}
],
"type": {
"kind": "SCALAR",
"name": "URLString",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "images",
"description": "A list of images depicting the different sides and surfaces\nof a releases media and packaging.",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CoverArtImage",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "artwork",
"description": "Whether there is artwork present for this release.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "darkened",
"description": "Whether the Cover Art Archive has received a take-down\nrequest for this releases artwork, disallowing new uploads.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "count",
"description": "The number of artwork images present for this release.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "release",
"description": "The particular release shown in the returned cover art.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Release",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "CoverArtImageSize",
"description": "The image sizes that may be requested at the [Cover Art\nArchive](https://musicbrainz.org/doc/Cover_Art_Archive).",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "SMALL",
"description": "A maximum dimension of 250px.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "LARGE",
"description": "A maximum dimension of 500px.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "FULL",
"description": "The images original dimensions, with no maximum.",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "URLString",
"description": "A web address.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CoverArtImage",
"description": "An individual piece of album artwork from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).",
"fields": [
{
"name": "fileID",
"description": "The Internet Archives internal file ID for the image.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "image",
"description": "The URL at which the image can be found.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "URLString",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "thumbnails",
"description": "A set of thumbnails for the image.",
"args": [],
"type": {
"kind": "OBJECT",
"name": "CoverArtImageThumbnails",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "front",
"description": "Whether this image depicts the “main front” of the release.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "back",
"description": "Whether this image depicts the “main back” of the release.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "types",
"description": "A list of [image types](https://musicbrainz.org/doc/Cover_Art/Types)\ndescribing what part(s) of the release the image includes.",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "edit",
"description": "The MusicBrainz edit ID.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "approved",
"description": "Whether the image was approved by the MusicBrainz edit system.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "comment",
"description": "A free-text comment left for the image.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CoverArtImageThumbnails",
"description": "URLs for thumbnails of different sizes for a particular piece of\ncover art.",
"fields": [
{
"name": "small",
"description": "The URL of a small version of the cover art, where the\nmaximum dimension is 250px.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "URLString",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "large",
"description": "The URL of a large version of the cover art, where the\nmaximum dimension is 500px.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "URLString",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "Medium", "name": "Medium",
@ -8106,18 +7740,6 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "coverArt",
"description": "The cover art for a release group, obtained from the [Cover\nArt Archive](https://musicbrainz.org/doc/Cover_Art_Archive).",
"args": [],
"type": {
"kind": "OBJECT",
"name": "ReleaseGroupCoverArt",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "artists", "name": "artists",
"description": "A list of artists linked to this entity.", "description": "A list of artists linked to this entity.",
@ -8319,88 +7941,6 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "ReleaseGroupCoverArt",
"description": "An object containing the cover art for a release group obtained\nfrom the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). For\nrelease groups, just the front cover of a particular release will be selected.",
"fields": [
{
"name": "front",
"description": "The URL of an image depicting the album cover or “main\nfront” of a release in the release group, i.e. the front of the packaging of the\naudio recording (or in the case of a digital release, the image associated with\nit in a digital media store).",
"args": [
{
"name": "size",
"description": "The size of the image to retrieve. By default, the returned\nimage will have its full original dimensions, but certain thumbnail sizes may be\nretrieved as well.",
"type": {
"kind": "ENUM",
"name": "CoverArtImageSize",
"ofType": null
},
"defaultValue": "null"
}
],
"type": {
"kind": "SCALAR",
"name": "URLString",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "images",
"description": "A list of images returned by the [Cover Art\nArchive](https://musicbrainz.org/doc/Cover_Art_Archive) for a release group. A\nparticular releases front image will be included in the list, and likely no\nothers, even if other images are available.",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CoverArtImage",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "artwork",
"description": "Whether there is artwork present for this release group.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "release",
"description": "The particular release shown in the returned cover art.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Release",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "SeriesConnection", "name": "SeriesConnection",
@ -9094,6 +8634,16 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "SCALAR",
"name": "URLString",
"description": "A web address.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "URL", "name": "URL",

View file

@ -0,0 +1,66 @@
import fs from 'fs'
import path from 'path'
import { graphql, introspectionQuery } from 'graphql'
import { renderSchema, diffSchema } from 'graphql-markdown'
import baseSchema, { createSchema } from '../src/schema'
const extensionModules = [
'cover-art-archive',
'fanart-tv',
'mediawiki',
'the-audio-db'
]
function getSchemaJSON (schema) {
return graphql(schema, introspectionQuery).then(result => result.data)
}
Promise.all(extensionModules.map(extensionModule => {
const extension = require(`../src/extensions/${extensionModule}`).default
console.log(`Generating docs for “${extension.name}” extension...`)
const schema = createSchema(baseSchema, { extensions: [extension] })
return Promise.all([
getSchemaJSON(baseSchema),
getSchemaJSON(schema)
]).then(([baseSchemaJSON, schemaJSON]) => {
const outputSchema = diffSchema(baseSchemaJSON, schemaJSON, {
processTypeDiff (type) {
if (type.description === undefined) {
type.description =
':small_blue_diamond: *This type has been extended. See the ' +
'[base schema](../types.md)\nfor a description and additional ' +
'fields.*'
}
return type
}
})
const outputPath = path.resolve(
__dirname,
`../docs/extensions/${extensionModule}.md`
)
const stream = fs.createWriteStream(outputPath)
const printer = (line) => stream.write(`${line}\n`)
const prologuePath = path.resolve(
__dirname,
`../src/extensions/${extensionModule}/prologue.md`
)
const prologue = fs.readFileSync(prologuePath, 'utf8')
renderSchema(outputSchema, {
title: `Extension: ${extension.name}`,
prologue: prologue.trim()
? `${extension.description}\n\n${prologue}`
: extension.description,
printer,
unknownTypeURL: '../types.md'
})
stream.end()
return new Promise((resolve, reject) => {
stream.on('error', reject)
stream.on('finish', () => resolve(extension))
})
})
})).then((extensions) => {
console.log(`Built docs for ${extensions.length} extension(s).`)
}).catch(err => {
console.log('Error:', err)
})

View file

@ -29,18 +29,12 @@ export class ClientError extends ExtendableError {
export default class Client { export default class Client {
constructor ({ constructor ({
baseURL = process.env.MUSICBRAINZ_BASE_URL || 'http://musicbrainz.org/ws/2/', baseURL,
userAgent = `${pkg.name}/${pkg.version} ` + userAgent = `${pkg.name}/${pkg.version} ` +
`( ${pkg.homepage || pkg.author.url || pkg.author.email} )`, `( ${pkg.homepage || pkg.author.url || pkg.author.email} )`,
extraHeaders = {}, extraHeaders = {},
errorClass = ClientError, errorClass = ClientError,
timeout = 60000, timeout = 60000,
// MusicBrainz API requests are limited to an *average* of 1 req/sec.
// That means if, for example, we only need to make a few API requests to
// fulfill a query, we might as well make them all at once - as long as
// we then wait a few seconds before making more. In practice this can
// seemingly be set to about 5 requests every 5 seconds before we're
// considered to exceed the rate limit.
limit = 1, limit = 1,
period = 1000, period = 1000,
concurrency = 10, concurrency = 10,

View file

@ -1,10 +1,7 @@
import MusicBrainz, { MusicBrainzError } from './musicbrainz' import MusicBrainz, { MusicBrainzError } from './musicbrainz'
import CoverArtArchive, { CoverArtArchiveError } from './cover-art-archive'
export { export {
MusicBrainz as default, MusicBrainz as default,
MusicBrainz, MusicBrainz,
MusicBrainzError, MusicBrainzError
CoverArtArchive,
CoverArtArchiveError
} }

View file

@ -7,6 +7,12 @@ export default class MusicBrainz extends Client {
constructor ({ constructor ({
baseURL = process.env.MUSICBRAINZ_BASE_URL || 'http://musicbrainz.org/ws/2/', baseURL = process.env.MUSICBRAINZ_BASE_URL || 'http://musicbrainz.org/ws/2/',
errorClass = MusicBrainzError, errorClass = MusicBrainzError,
// MusicBrainz API requests are limited to an *average* of 1 req/sec.
// That means if, for example, we only need to make a few API requests to
// fulfill a query, we might as well make them all at once - as long as
// we then wait a few seconds before making more. In practice this can
// seemingly be set to about 5 requests every 5 seconds before we're
// considered to exceed the rate limit.
limit = 5, limit = 5,
period = 5500, period = 5500,
...options ...options

27
src/context.js Normal file
View file

@ -0,0 +1,27 @@
import createLoaders from './loaders'
const debug = require('debug')('graphbrainz:context')
export function extendContext (extension, context, options) {
if (extension.extendContext) {
if (typeof extension.extendContext === 'function') {
debug(`Extending context via a function from the “${extension.name}” extension.`)
context = extension.extendContext(context, options)
} else {
throw new Error(
`Extension “${extension.name}” contains an invalid \`extendContext\` ` +
`value: ${extension.extendContext}`
)
}
}
return context
}
export function createContext (options) {
const { client } = options
const loaders = createLoaders(client)
const context = { client, loaders }
return options.extensions.reduce((context, extension) => {
return extendContext(extension, context, options)
}, context)
}

View file

@ -1,18 +1,18 @@
import Client, { ClientError } from './client' import Client from '../../api/client'
export class CoverArtArchiveError extends ClientError {} export default class CoverArtArchiveClient extends Client {
export default class CoverArtArchive extends Client {
constructor ({ constructor ({
baseURL = process.env.COVER_ART_ARCHIVE_BASE_URL || 'http://coverartarchive.org/', baseURL = process.env.COVER_ART_ARCHIVE_BASE_URL || 'http://coverartarchive.org/',
errorClass = CoverArtArchiveError,
limit = 10, limit = 10,
period = 1000, period = 1000,
...options ...options
} = {}) { } = {}) {
super({ baseURL, errorClass, limit, period, ...options }) super({ baseURL, limit, period, ...options })
} }
/**
* Sinfully attempt to parse HTML responses for the error message.
*/
parseErrorMessage (response, body) { parseErrorMessage (response, body) {
if (typeof body === 'string' && body.startsWith('<!')) { if (typeof body === 'string' && body.startsWith('<!')) {
const heading = /<h1>([^<]+)<\/h1>/i.exec(body) const heading = /<h1>([^<]+)<\/h1>/i.exec(body)
@ -22,25 +22,15 @@ export default class CoverArtArchive extends Client {
return super.parseErrorMessage(response, body) return super.parseErrorMessage(response, body)
} }
getImagesURL (entity, mbid) { images (entityType, mbid) {
return `${entity}/${mbid}` return this.get(`${entityType}/${mbid}`, { json: true })
} }
images (entity, mbid) { imageURL (entityType, mbid, typeOrID = 'front', size) {
const url = this.getImagesURL(entity, mbid) let url = `${entityType}/${mbid}/${typeOrID}`
return this.get(url, { json: true })
}
getImageURL (entity, mbid, typeOrID = 'front', size) {
let url = `${entity}/${mbid}/${typeOrID}`
if (size != null) { if (size != null) {
url += `-${size}` url += `-${size}`
} }
return url
}
imageURL (entity, mbid, typeOrID = 'front', size) {
const url = this.getImageURL(entity, mbid, typeOrID, size)
return this.get(url, { method: 'HEAD', followRedirect: false }) return this.get(url, { method: 'HEAD', followRedirect: false })
.then(headers => headers.location) .then(headers => headers.location)
} }

View file

@ -0,0 +1,36 @@
import schema from './schema'
import resolvers from './resolvers'
import createLoaders from './loaders'
import CoverArtArchiveClient from './client'
import { ONE_DAY } from '../../util'
export default {
name: 'Cover Art Archive',
description:
'Retrieve cover art images for releases from the [Cover Art Archive](https://coverartarchive.org/).',
extendContext (context, { coverArtClient, coverArtArchive = {} } = {}) {
const client = coverArtClient || new CoverArtArchiveClient(coverArtArchive)
const cacheSize = parseInt(
process.env.COVER_ART_ARCHIVE_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
10
)
const cacheTTL = parseInt(
process.env.COVER_ART_ARCHIVE_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
10
)
return {
...context,
// Add the client instance directly onto `context` for backwards
// compatibility.
coverArtClient: client,
loaders: {
...context.loaders,
...createLoaders({ client, cacheSize, cacheTTL })
}
}
},
extendSchema: {
schemas: [schema],
resolvers
}
}

View file

@ -0,0 +1,53 @@
import DataLoader from 'dataloader'
import LRUCache from 'lru-cache'
const debug = require('debug')('graphbrainz:extensions/cover-art-archive')
export default function createLoaders (options) {
const { client } = options
const cache = LRUCache({
max: options.cacheSize,
maxAge: options.cacheTTL,
dispose (key) {
debug(`Removed from cache. key=${key}`)
}
})
// Make the cache Map-like.
cache.delete = cache.del
cache.clear = cache.reset
return {
coverArtArchive: new DataLoader(keys => {
return Promise.all(keys.map(key => {
const [ entityType, id ] = key
return client.images(entityType, id)
.catch(err => {
if (err.statusCode === 404) {
return { images: [] }
}
throw err
}).then(coverArt => ({
...coverArt,
_entityType: entityType,
_id: id,
_releaseID: coverArt.release && coverArt.release.split('/').pop()
}))
}))
}, {
cacheKeyFn: ([ entityType, id ]) => `${entityType}/${id}`,
cacheMap: cache
}),
coverArtArchiveURL: new DataLoader(keys => {
return Promise.all(keys.map(key => {
const [ entityType, id, type, size ] = key
return client.imageURL(entityType, id, type, size)
}))
}, {
cacheKeyFn: ([ entityType, id, type, size ]) => {
const key = `${entityType}/${id}/${type}`
return size ? `${key}-${size}` : key
},
cacheMap: cache
})
}
}

View file

@ -0,0 +1,12 @@
This extension uses its own cache, separate from the MusicBrainz loader cache.
## Configuration
This extension can be configured using environment variables:
* **`COVER_ART_ARCHIVE_BASE_URL`**: The base URL at which to access the Cover
Art Archive API. Defaults to `http://coverartarchive.org/`.
* **`COVER_ART_ARCHIVE_CACHE_SIZE`**: The number of items to keep in the cache.
Defaults to `GRAPHBRAINZ_CACHE_SIZE` if defined, or `8192`.
* **`COVER_ART_ARCHIVE_CACHE_TTL`**: The number of seconds to keep items in the
cache. Defaults to `GRAPHBRAINZ_CACHE_TTL` if defined, or `86400000` (one day).

View file

@ -0,0 +1,75 @@
import { resolveLookup } from '../../resolvers'
const SIZES = new Map([
[null, null],
[250, 250],
[500, 500],
['FULL', null],
['SMALL', 250],
['LARGE', 500]
])
function resolveImage (coverArt, args, { loaders }, info) {
// Since migrating the schema to an extension, we lost custom enum values
// for the time being. Translate any incoming `size` arg to the old enum
// values.
const size = SIZES.get(args.size)
// Field should be `front` or `back`.
const field = info.fieldName
if (coverArt.images) {
const matches = coverArt.images.filter(image => image[field])
if (!matches.length) {
return null
} else if (matches.length === 1) {
const match = matches[0]
if (size === 250) {
return match.thumbnails.small
} else if (size === 500) {
return match.thumbnails.large
} else {
return match.image
}
}
}
const entityType = coverArt._entityType
const id = coverArt._id
const releaseID = coverArt._releaseID
if (entityType === 'release-group' && field === 'front') {
// Release groups only have an endpoint to retrieve the front image.
// If someone requests the back of a release group, return the back of the
// release that the release group's cover art response points to.
return loaders.coverArtArchiveURL.load(['release-group', id, field, size])
} else {
return loaders.coverArtArchiveURL.load(['release', releaseID, field, size])
}
}
export default {
CoverArtArchiveImage: {
fileID: image => image.id
},
CoverArtArchiveRelease: {
front: resolveImage,
back: resolveImage,
images: coverArt => coverArt.images,
artwork: coverArt => coverArt.images.length > 0,
count: coverArt => coverArt.images.length,
release: (coverArt, args, context, info) => {
const mbid = coverArt._releaseID
if (mbid) {
return resolveLookup(coverArt, { mbid }, context, info)
}
return null
}
},
Release: {
coverArtArchive: (release, args, { loaders }) => {
return loaders.coverArtArchive.load(['release', release.id])
}
},
ReleaseGroup: {
coverArtArchive: (releaseGroup, args, { loaders }) => {
return loaders.coverArtArchive.load(['release-group', releaseGroup.id])
}
}
}

View file

@ -0,0 +1,121 @@
export default `
# An individual piece of album artwork from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
type CoverArtArchiveImage {
# The Internet Archives internal file ID for the image.
fileID: String!
# The URL at which the image can be found.
image: URLString!
# A set of thumbnails for the image.
thumbnails: CoverArtArchiveImageThumbnails!
# Whether this image depicts the main front of the release.
front: Boolean!
# Whether this image depicts the main back of the release.
back: Boolean!
# A list of [image types](https://musicbrainz.org/doc/Cover_Art/Types)
# describing what part(s) of the release the image includes.
types: [String]!
# The MusicBrainz edit ID.
edit: Int
# Whether the image was approved by the MusicBrainz edit system.
approved: Boolean
# A free-text comment left for the image.
comment: String
}
# The image sizes that may be requested at the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
enum CoverArtArchiveImageSize {
# A maximum dimension of 250px.
SMALL
# A maximum dimension of 500px.
LARGE
# The images original dimensions, with no maximum.
FULL
}
# URLs for thumbnails of different sizes for a particular piece of
# cover art.
type CoverArtArchiveImageThumbnails {
# The URL of a small version of the cover art, where the maximum dimension is
# 250px.
small: URLString
# The URL of a large version of the cover art, where the maximum dimension is
# 500px.
large: URLString
}
# An object containing a list of the cover art images for a release obtained
# from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive),
# as well as a summary of what artwork is available.
type CoverArtArchiveRelease {
# The URL of an image depicting the album cover or main front of the release,
# i.e. the front of the packaging of the audio recording (or in the case of a
# digital release, the image associated with it in a digital media store).
#
# In the MusicBrainz schema, this field is a Boolean value indicating the
# presence of a front image, whereas here the value is the URL for the image
# itself if one exists. You can check for null if you just want to determine
# the presence of an image.
front(
# The size of the image to retrieve. By default, the returned image will
# have its full original dimensions, but certain thumbnail sizes may be
# retrieved as well.
size: CoverArtArchiveImageSize = FULL
): URLString
# The URL of an image depicting the main back of the release, i.e. the back
# of the packaging of the audio recording.
#
# In the MusicBrainz schema, this field is a Boolean value indicating the
# presence of a back image, whereas here the value is the URL for the image
# itself. You can check for null if you just want to determine the presence of
# an image.
back(
# The size of the image to retrieve. By default, the returned image will
# have its full original dimensions, but certain thumbnail sizes may be
# retrieved as well.
size: CoverArtArchiveImageSize = FULL
): URLString
# A list of images depicting the different sides and surfaces of a releases
# media and packaging.
images: [CoverArtArchiveImage]!
# Whether there is artwork present for this release.
artwork: Boolean!
# The number of artwork images present for this release.
count: Int!
# The particular release shown in the returned cover art.
release: Release
}
extend type Release {
# An object containing a list and summary of the cover art images that are
# present for this release from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).
# This field is provided by the Cover Art Archive extension.
coverArtArchive: CoverArtArchiveRelease
}
extend type ReleaseGroup {
# The cover art for a release in the release group, obtained from the
# [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). A
# release in the release group will be chosen as representative of the release
# group.
# This field is provided by the Cover Art Archive extension.
coverArtArchive: CoverArtArchiveRelease
}
`

View file

@ -0,0 +1,60 @@
import Client from '../../api/client'
export default class FanArtClient extends Client {
constructor ({
apiKey = process.env.FANART_API_KEY,
baseURL = process.env.FANART_BASE_URL || 'http://webservice.fanart.tv/v3/',
limit = 10,
period = 1000,
...options
} = {}) {
super({ baseURL, limit, period, ...options })
this.apiKey = apiKey
}
get (path, options = {}) {
const ClientError = this.errorClass
if (!this.apiKey) {
return Promise.reject(new ClientError(
'No API key was configured for the fanart.tv client.'
))
}
options = {
json: true,
...options,
qs: {
...options.qs,
api_key: this.apiKey
}
}
return super.get(path, options)
}
musicEntity (entityType, mbid) {
const ClientError = this.errorClass
switch (entityType) {
case 'artist':
return this.musicArtist(mbid)
case 'label':
return this.musicLabel(mbid)
case 'release-group':
return this.musicAlbum(mbid)
default:
return Promise.reject(new ClientError(
`Entity type unsupported: ${entityType}`
))
}
}
musicArtist (mbid) {
return this.get(`music/${mbid}`)
}
musicAlbum (mbid) {
return this.get(`music/albums/${mbid}`)
}
musicLabel (mbid) {
return this.get(`music/${mbid}`)
}
}

View file

@ -0,0 +1,34 @@
import schema from './schema'
import resolvers from './resolvers'
import createLoader from './loader'
import FanArtClient from './client'
import { ONE_DAY } from '../../util'
export default {
name: 'fanart.tv',
description:
'Retrieve high quality artwork for artists, releases, and labels from ' +
'[fanart.tv](https://fanart.tv/).',
extendContext (context, { fanArt = {} } = {}) {
const client = new FanArtClient(fanArt)
const cacheSize = parseInt(
process.env.FANART_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
10
)
const cacheTTL = parseInt(
process.env.FANART_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
10
)
return {
...context,
loaders: {
...context.loaders,
fanArt: createLoader({ client, cacheSize, cacheTTL })
}
}
},
extendSchema: {
schemas: [schema],
resolvers
}
}

View file

@ -0,0 +1,51 @@
import DataLoader from 'dataloader'
import LRUCache from 'lru-cache'
const debug = require('debug')('graphbrainz:extensions/fanart-tv')
export default function createLoader (options) {
const { client } = options
const cache = LRUCache({
max: options.cacheSize,
maxAge: options.cacheTTL,
dispose (key) {
debug(`Removed from cache. key=${key}`)
}
})
// Make the cache Map-like.
cache.delete = cache.del
cache.clear = cache.reset
const loader = new DataLoader(keys => {
return Promise.all(keys.map(key => {
const [ entityType, id ] = key
return client.musicEntity(entityType, id)
.catch(err => {
if (err.statusCode === 404) {
// 404s are OK, just return empty data.
return {
artistbackground: [],
artistthumb: [],
musiclogo: [],
hdmusiclogo: [],
musicbanner: [],
musiclabel: [],
albums: {}
}
}
throw err
}).then(body => {
if (entityType === 'artist') {
const releaseGroupIDs = Object.keys(body.albums)
debug(`Priming album cache with ${releaseGroupIDs.length} album(s).`)
releaseGroupIDs.forEach(key => loader.prime(['release-group', key], body))
}
return body
})
}))
}, {
cacheKeyFn: ([ entityType, id ]) => `${entityType}/${id}`,
cacheMap: cache
})
return loader
}

View file

@ -0,0 +1,14 @@
This extension uses its own cache, separate from the MusicBrainz loader cache.
## Configuration
This extension can be configured using environment variables:
* **`FANART_API_KEY`**: The fanart.tv API key to use. This is required for any
fields added by the extension to successfully resolve.
* **`FANART_BASE_URL`**: The base URL at which to access the
fanart.tv API. Defaults to `http://webservice.fanart.tv/v3/`.
* **`FANART_CACHE_SIZE`**: The number of items to keep in the cache.
Defaults to `GRAPHBRAINZ_CACHE_SIZE` if defined, or `8192`.
* **`FANART_CACHE_TTL`**: The number of seconds to keep items in the
cache. Defaults to `GRAPHBRAINZ_CACHE_TTL` if defined, or `86400000` (one day).

View file

@ -0,0 +1,63 @@
const imageResolvers = {
imageID: image => image.id,
url: (image, args) => {
return args.size === 'PREVIEW'
? image.url.replace('/fanart/', '/preview/')
: image.url
},
likeCount: image => image.likes
}
export default {
FanArtImage: {
...imageResolvers
},
FanArtDiscImage: {
...imageResolvers,
discNumber: image => image.disc
},
FanArtLabelImage: {
...imageResolvers,
color: image => image.colour
},
FanArtArtist: {
backgrounds: artist => {
return artist.artistbackground
},
thumbnails: artist => {
return artist.artistthumb
},
logos: artist => {
return artist.musiclogo
},
logosHD: artist => {
return artist.hdmusiclogo
},
banners: artist => {
return artist.musicbanner
}
},
FanArtLabel: {
logos: label => label.musiclabel
},
FanArtAlbum: {
albumCovers: album => album.albumcover || [],
discImages: album => album.cdart || []
},
Artist: {
fanArt: (artist, args, context) => {
return context.loaders.fanArt.load(['artist', artist.id])
}
},
Label: {
fanArt: (label, args, context) => {
return context.loaders.fanArt.load(['label', label.id])
}
},
ReleaseGroup: {
fanArt: (releaseGroup, args, context) => {
return context.loaders.fanArt.load(['release-group', releaseGroup.id])
.then(artist => artist.albums[releaseGroup.id])
}
}
}

View file

@ -0,0 +1,127 @@
export default `
# The image sizes that may be requested at [fanart.tv](https://fanart.tv/).
enum FanArtImageSize {
# The images full original dimensions.
FULL
# A maximum dimension of 200px.
PREVIEW
}
# A single image from [fanart.tv](https://fanart.tv/).
type FanArtImage {
# The ID of the image on fanart.tv.
imageID: ID
# The URL of the image.
url(
# The size of the image to retrieve.
size: FanArtImageSize = FULL
): URLString
# The number of likes the image has received by fanart.tv users.
likeCount: Int
}
# A disc image from [fanart.tv](https://fanart.tv/).
type FanArtDiscImage {
# The ID of the image on fanart.tv.
imageID: ID
# The URL of the image.
url(
# The size of the image to retrieve.
size: FanArtImageSize = FULL
): URLString
# The number of likes the image has received by fanart.tv users.
likeCount: Int
# The disc number.
discNumber: Int
# The width and height of the (square) disc image.
size: Int
}
# A music label image from [fanart.tv](https://fanart.tv/).
type FanArtLabelImage {
# The ID of the image on fanart.tv.
imageID: ID
# The URL of the image.
url(
# The size of the image to retrieve.
size: FanArtImageSize = FULL
): URLString
# The number of likes the image has received by fanart.tv users.
likeCount: Int
# The type of color content in the image (usually white or colour).
color: String
}
# An object containing lists of the different types of artist images from
# [fanart.tv](https://fanart.tv/).
type FanArtArtist {
# A list of 1920x1080 JPG images picturing the artist, suitable for use as
# backgrounds.
backgrounds: [FanArtImage]
# A list of 1000x185 JPG images containing the artist and their logo or name.
banners: [FanArtImage]
# A list of 400x155 PNG images containing the artists logo or name, with
# transparent backgrounds.
logos: [FanArtImage]
# A list of 800x310 PNG images containing the artists logo or name, with
# transparent backgrounds.
logosHD: [FanArtImage]
# A list of 1000x1000 JPG thumbnail images picturing the artist (usually
# containing every member of a band).
thumbnails: [FanArtImage]
}
# An object containing lists of the different types of label images from
# [fanart.tv](https://fanart.tv/).
type FanArtLabel {
# A list of 400x270 PNG images containing the labels logo. There will
# usually be a black version, a color version, and a white version, all with
# transparent backgrounds.
logos: [FanArtLabelImage]
}
# An object containing lists of the different types of release group images from
# [fanart.tv](https://fanart.tv/).
type FanArtAlbum {
# A list of 1000x1000 JPG images of the cover artwork of the release group.
albumCovers: [FanArtImage]
# A list of 1000x1000 PNG images of the physical disc media for the release
# group, with transparent backgrounds.
discImages: [FanArtDiscImage]
}
extend type Artist {
# Images of the artist from [fanart.tv](https://fanart.tv/).
# This field is provided by the fanart.tv extension.
fanArt: FanArtArtist
}
extend type Label {
# Images of the label from [fanart.tv](https://fanart.tv/).
# This field is provided by the fanart.tv extension.
fanArt: FanArtLabel
}
extend type ReleaseGroup {
# Images of the release group from [fanart.tv](https://fanart.tv/).
# This field is provided by the fanart.tv extension.
fanArt: FanArtAlbum
}
`

View file

@ -0,0 +1,54 @@
import URL from 'url'
import Client from '../../api/client'
export default class MediaWikiClient extends Client {
constructor ({
limit = 10,
period = 1000,
...options
} = {}) {
super({ limit, period, ...options })
}
imageInfo (page) {
const pageURL = URL.parse(page, true)
const ClientError = this.errorClass
if (!pageURL.pathname.startsWith('/wiki/')) {
return Promise.reject(new ClientError(
`MediaWiki page URL does not have the expected /wiki/ prefix: ${page}`
))
}
const apiURL = URL.format({
protocol: pageURL.protocol,
auth: pageURL.auth,
host: pageURL.host,
pathname: '/w/api.php',
query: {
action: 'query',
titles: pageURL.pathname.slice(6),
prop: 'imageinfo',
iiprop: 'url|size|canonicaltitle|user|extmetadata',
format: 'json'
}
})
return this.get(apiURL, { json: true })
.then(body => {
const pageIDs = Object.keys(body.query.pages)
if (pageIDs.length !== 1) {
throw new ClientError(
`Query returned multiple pages: [${pageIDs.join(', ')}]`
)
}
const imageInfo = body.query.pages[pageIDs[0]].imageinfo
if (imageInfo.length !== 1) {
throw new ClientError(
`Query returned info for ${imageInfo.length} images, expected 1.`
)
}
return imageInfo[0]
})
}
}

View file

@ -0,0 +1,34 @@
import schema from './schema'
import resolvers from './resolvers'
import createLoader from './loader'
import MediaWikiClient from './client'
import { ONE_DAY } from '../../util'
export default {
name: 'MediaWiki',
description:
'Retrieve information from MediaWiki image pages, like the actual image ' +
'file URL and EXIF metadata.',
extendContext (context, { mediaWiki = {} } = {}) {
const client = new MediaWikiClient(mediaWiki)
const cacheSize = parseInt(
process.env.MEDIAWIKI_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
10
)
const cacheTTL = parseInt(
process.env.MEDIAWIKI_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
10
)
return {
...context,
loaders: {
...context.loaders,
mediaWiki: createLoader({ client, cacheSize, cacheTTL })
}
}
},
extendSchema: {
schemas: [schema],
resolvers
}
}

View file

@ -0,0 +1,22 @@
import DataLoader from 'dataloader'
import LRUCache from 'lru-cache'
const debug = require('debug')('graphbrainz:extensions/mediawiki')
export default function createLoader (options) {
const { client } = options
const cache = LRUCache({
max: options.cacheSize,
maxAge: options.cacheTTL,
dispose (key) {
debug(`Removed from cache. key=${key}`)
}
})
// Make the cache Map-like.
cache.delete = cache.del
cache.clear = cache.reset
return new DataLoader(keys => {
return Promise.all(keys.map(key => client.imageInfo(key)))
}, { cacheMap: cache })
}

View file

@ -0,0 +1,24 @@
On entities with [URL relationship types][relationships] that represent images,
this extension will find those URLs that appear to be MediaWiki image pages, and
use the [MediaWiki API][] to fetch information about the image. This information
will include the actual file URL, so you can use it as the `src` in an `<img>`
tag (for example).
MediaWiki image URLs are assumed to be those with a path that starts with
`/wiki/Image:` or `/wiki/File:`.
This extension uses its own cache, separate from the MusicBrainz loader cache.
## Configuration
This extension can be configured using environment variables:
* **`COVER_ART_ARCHIVE_BASE_URL`**: The base URL at which to access to Cover Art
Archive API. Defaults to `http://coverartarchive.org/`.
* **`COVER_ART_ARCHIVE_CACHE_SIZE`**: The number of items to keep in the cache.
Defaults to `GRAPHBRAINZ_CACHE_SIZE` if defined, or `8192`.
* **`COVER_ART_ARCHIVE_CACHE_TTL`**: The number of seconds to keep items in the
cache. Defaults to `GRAPHBRAINZ_CACHE_TTL` if defined, or `86400000` (one day).
[relationships]: https://musicbrainz.org/relationships
[MediaWiki API]: https://www.mediawiki.org/wiki/API:Main_page

View file

@ -0,0 +1,80 @@
import URL from 'url'
function resolveMediaWikiImages (source, args, { loaders }) {
const isURL = (relation) => relation['target-type'] === 'url'
let rels = source.relations ? source.relations.filter(isURL) : []
if (!rels.length) {
rels = loaders.lookup.load([source._type, source.id, { inc: 'url-rels' }])
.then(source => source.relations.filter(isURL))
}
return Promise.resolve(rels).then(rels => {
const pages = rels.filter(rel => {
if (rel.type === args.type) {
const url = URL.parse(rel.url.resource)
if (url.pathname.match(/^\/wiki\/(File|Image):/)) {
return true
}
}
return false
}).map(rel => rel.url.resource)
return loaders.mediaWiki.loadMany(pages)
})
}
export default {
MediaWikiImage: {
descriptionURL: imageInfo => imageInfo.descriptionurl,
canonicalTitle: imageInfo => imageInfo.canonicaltitle,
objectName: imageInfo => {
const data = imageInfo.extmetadata.ObjectName
return data ? data.value : null
},
descriptionHTML: imageInfo => {
const data = imageInfo.extmetadata.ImageDescription
return data ? data.value : null
},
originalDateTimeHTML: imageInfo => {
const data = imageInfo.extmetadata.DateTimeOriginal
return data ? data.value : null
},
categories: imageInfo => {
const data = imageInfo.extmetadata.Categories
return data ? data.value.split('|') : []
},
artistHTML: imageInfo => {
const data = imageInfo.extmetadata.Artist
return data ? data.value : null
},
creditHTML: imageInfo => {
const data = imageInfo.extmetadata.Credit
return data ? data.value : null
},
licenseShortName: imageInfo => {
const data = imageInfo.extmetadata.LicenseShortName
return data ? data.value : null
},
licenseURL: imageInfo => {
const data = imageInfo.extmetadata.LicenseUrl
return data ? data.value : null
},
metadata: imageInfo => Object.keys(imageInfo.extmetadata).map(key => {
const data = imageInfo.extmetadata[key]
return { ...data, name: key }
})
},
MediaWikiImageMetadata: {
value: obj => obj.value == null ? obj.value : `${obj.value}`
},
Artist: {
mediaWikiImages: resolveMediaWikiImages
},
Instrument: {
mediaWikiImages: resolveMediaWikiImages
},
Label: {
mediaWikiImages: resolveMediaWikiImages
},
Place: {
mediaWikiImages: resolveMediaWikiImages
}
}

View file

@ -0,0 +1,108 @@
export default `
type MediaWikiImage {
# The URL of the actual image file.
url: URLString!
# The URL of the wiki page describing the image.
descriptionURL: URLString
# The user who uploaded the file.
user: String
# The size of the file in bytes.
size: Int
# The pixel width of the image.
width: Int
# The pixel height of the image.
height: Int
# The canonical title of the file.
canonicalTitle: String
# The image title, brief description, or file name.
objectName: String
# A description of the image, potentially containing HTML.
descriptionHTML: String
# The original date of creation of the image. May be a description rather than
# a parseable timestamp, and may contain HTML.
originalDateTimeHTML: String
# A list of the categories of the image.
categories: [String]!
# The name of the image author, potentially containing HTML.
artistHTML: String
# The source of the image, potentially containing HTML.
creditHTML: String
# A short human-readable license name.
licenseShortName: String
# A web address where the license is described.
licenseURL: URLString
# The full list of values in the \`extmetadata\` field.
metadata: [MediaWikiImageMetadata]!
}
# An entry in the \`extmetadata\` field of a MediaWiki image file.
type MediaWikiImageMetadata {
# The name of the metadata field.
name: String!
# The value of the metadata field. All values will be converted to strings.
value: String
# The source of the value.
source: String
}
extend type Artist {
# Artist images found at MediaWiki URLs in the artists URL relationships.
# Defaults to URL relationships with the type image.
# This field is provided by the MediaWiki extension.
mediaWikiImages(
# The type of URL relationship that will be selected to find images. See
# the possible [Artist-URL relationship types](https://musicbrainz.org/relationships/artist-url).
type: String = "image"
): [MediaWikiImage]!
}
extend type Instrument {
# Instrument images found at MediaWiki URLs in the instruments URL
# relationships. Defaults to URL relationships with the type image.
# This field is provided by the MediaWiki extension.
mediaWikiImages(
# The type of URL relationship that will be selected to find images. See the
# possible [Instrument-URL relationship types](https://musicbrainz.org/relationships/instrument-url).
type: String = "image"
): [MediaWikiImage]!
}
extend type Label {
# Label images found at MediaWiki URLs in the labels URL relationships.
# Defaults to URL relationships with the type logo.
# This field is provided by the MediaWiki extension.
mediaWikiImages(
# The type of URL relationship that will be selected to find images. See the
# possible [Label-URL relationship types](https://musicbrainz.org/relationships/label-url).
type: String = "logo"
): [MediaWikiImage]!
}
extend type Place {
# Place images found at MediaWiki URLs in the places URL relationships.
# Defaults to URL relationships with the type image.
# This field is provided by the MediaWiki extension.
mediaWikiImages(
# The type of URL relationship that will be selected to find images. See the
# possible [Place-URL relationship types](https://musicbrainz.org/relationships/place-url).
type: String = "image"
): [MediaWikiImage]!
}
`

View file

@ -0,0 +1,70 @@
import Client from '../../api/client'
export default class TheAudioDBClient extends Client {
constructor ({
apiKey = process.env.THEAUDIODB_API_KEY,
baseURL = process.env.THEAUDIODB_BASE_URL || 'http://www.theaudiodb.com/api/v1/json/',
limit = 10,
period = 1000,
...options
} = {}) {
super({ baseURL, limit, period, ...options })
this.apiKey = apiKey
}
get (path, options = {}) {
const ClientError = this.errorClass
if (!this.apiKey) {
return Promise.reject(new ClientError(
'No API key was configured for TheAudioDB client.'
))
}
return super.get(`${this.apiKey}/${path}`, { json: true, ...options })
}
entity (entityType, mbid) {
const ClientError = this.errorClass
switch (entityType) {
case 'artist':
return this.artist(mbid)
case 'release-group':
return this.album(mbid)
case 'recording':
return this.track(mbid)
default:
return Promise.reject(new ClientError(
`Entity type unsupported: ${entityType}`
))
}
}
artist (mbid) {
return this.get('artist-mb.php', { qs: { i: mbid } })
.then(body => {
if (body.artists && body.artists.length === 1) {
return body.artists[0]
}
return null
})
}
album (mbid) {
return this.get('album-mb.php', { qs: { i: mbid } })
.then(body => {
if (body.album && body.album.length === 1) {
return body.album[0]
}
return null
})
}
track (mbid) {
return this.get('track-mb.php', { qs: { i: mbid } })
.then(body => {
if (body.track && body.track.length === 1) {
return body.track[0]
}
return null
})
}
}

View file

@ -0,0 +1,34 @@
import schema from './schema'
import resolvers from './resolvers'
import createLoader from './loader'
import TheAudioDBClient from './client'
import { ONE_DAY } from '../../util'
export default {
name: 'TheAudioDB',
description:
'Retrieve images and information about artists, releases, and recordings ' +
'from [TheAudioDB.com](http://www.theaudiodb.com/).',
extendContext (context, { theAudioDB = {} } = {}) {
const client = new TheAudioDBClient(theAudioDB)
const cacheSize = parseInt(
process.env.THEAUDIODB_CACHE_SIZE || process.env.GRAPHBRAINZ_CACHE_SIZE || 8192,
10
)
const cacheTTL = parseInt(
process.env.THEAUDIODB_CACHE_TTL || process.env.GRAPHBRAINZ_CACHE_TTL || ONE_DAY,
10
)
return {
...context,
loaders: {
...context.loaders,
theAudioDB: createLoader({ client, cacheSize, cacheTTL })
}
}
},
extendSchema: {
schemas: [schema],
resolvers
}
}

View file

@ -0,0 +1,28 @@
import DataLoader from 'dataloader'
import LRUCache from 'lru-cache'
const debug = require('debug')('graphbrainz:extensions/the-audio-db')
export default function createLoader (options) {
const { client } = options
const cache = LRUCache({
max: options.cacheSize,
maxAge: options.cacheTTL,
dispose (key) {
debug(`Removed from cache. key=${key}`)
}
})
// Make the cache Map-like.
cache.delete = cache.del
cache.clear = cache.reset
return new DataLoader(keys => {
return Promise.all(keys.map(key => {
const [ entityType, id ] = key
return client.entity(entityType, id)
}))
}, {
cacheKeyFn: ([ entityType, id ]) => `${entityType}/${id}`,
cacheMap: cache
})
}

View file

@ -0,0 +1,14 @@
This extension uses its own cache, separate from the MusicBrainz loader cache.
## Configuration
This extension can be configured using environment variables:
* **`THEAUDIODB_API_KEY`**: TheAudioDB API key to use. This is required for any
fields added by the extension to successfully resolve.
* **`THEAUDIODB_BASE_URL`**: The base URL at which to access TheAudioDB API.
Defaults to `http://www.theaudiodb.com/api/v1/json/`.
* **`THEAUDIODB_CACHE_SIZE`**: The number of items to keep in the cache.
Defaults to `GRAPHBRAINZ_CACHE_SIZE` if defined, or `8192`.
* **`THEAUDIODB_CACHE_TTL`**: The number of seconds to keep items in the
cache. Defaults to `GRAPHBRAINZ_CACHE_TTL` if defined, or `86400000` (one day).

View file

@ -0,0 +1,107 @@
function handleImageSize (resolver) {
return (source, args, context, info) => {
const getURL = (url) => args.size === 'PREVIEW' ? `${url}/preview` : url
const url = resolver(source, args, context, info)
if (!url) {
return null
} else if (Array.isArray(url)) {
return url.map(getURL)
} else {
return getURL(url)
}
}
}
export default {
TheAudioDBArtist: {
artistID: artist => artist.idArtist,
biography: (artist, args) => {
const lang = args.lang.toUpperCase()
return artist[`strBiography${lang}`] || null
},
memberCount: artist => artist.intMembers,
banner: handleImageSize(artist => artist.strArtistBanner),
fanArt: handleImageSize(artist => {
return [
artist.strArtistFanart,
artist.strArtistFanart2,
artist.strArtistFanart3
].filter(Boolean)
}),
logo: handleImageSize(artist => artist.strArtistLogo),
thumbnail: handleImageSize(artist => artist.strArtistThumb),
genre: artist => artist.strGenre || null,
mood: artist => artist.strMood || null,
style: artist => artist.strStyle || null
},
TheAudioDBAlbum: {
albumID: album => album.idAlbum,
artistID: album => album.idArtist,
description: (album, args) => {
const lang = args.lang.toUpperCase()
return album[`strDescription${lang}`] || null
},
salesCount: album => album.intSales,
score: album => album.intScore,
scoreVotes: album => album.intScoreVotes,
discImage: handleImageSize(album => album.strAlbumCDart),
spineImage: handleImageSize(album => album.strAlbumSpine),
frontImage: handleImageSize(album => album.strAlbumThumb),
backImage: handleImageSize(album => album.strAlbumThumbBack),
review: album => album.strReview || null,
genre: album => album.strGenre || null,
mood: album => album.strMood || null,
style: album => album.strStyle || null,
speed: album => album.strSpeed || null,
theme: album => album.strTheme || null
},
TheAudioDBTrack: {
trackID: track => track.idTrack,
albumID: track => track.idAlbum,
artistID: track => track.idArtist,
description: (track, args) => {
const lang = args.lang.toUpperCase()
return track[`strDescription${lang}`] || null
},
thumbnail: handleImageSize(track => track.strTrackThumb),
score: track => track.intScore,
scoreVotes: track => track.intScoreVotes,
trackNumber: track => track.intTrackNumber,
musicVideo: track => track,
genre: track => track.strGenre || null,
mood: track => track.strMood || null,
style: track => track.strStyle || null,
theme: track => track.strTheme || null
},
TheAudioDBMusicVideo: {
url: track => track.strMusicVid || null,
companyName: track => track.strMusicVidCompany || null,
directorName: track => track.strMusicVidDirector || null,
screenshots: handleImageSize(track => {
return [
track.strMusicVidScreen1,
track.strMusicVidScreen2,
track.strMusicVidScreen3
].filter(Boolean)
}),
viewCount: track => track.intMusicVidViews,
likeCount: track => track.intMusicVidLikes,
dislikeCount: track => track.intMusicVidDislikes,
commentCount: track => track.intMusicVidComments
},
Artist: {
theAudioDB: (artist, args, context) => {
return context.loaders.theAudioDB.load(['artist', artist.id])
}
},
Recording: {
theAudioDB: (recording, args, context) => {
return context.loaders.theAudioDB.load(['recording', recording.id])
}
},
ReleaseGroup: {
theAudioDB: (releaseGroup, args, context) => {
return context.loaders.theAudioDB.load(['release-group', releaseGroup.id])
}
}
}

View file

@ -0,0 +1,231 @@
export default `
# The image sizes that may be requested at [TheAudioDB](http://www.theaudiodb.com/).
enum TheAudioDBImageSize {
# The images full original dimensions.
FULL
# A maximum dimension of 200px.
PREVIEW
}
# An artist on [TheAudioDB](http://www.theaudiodb.com/).
type TheAudioDBArtist {
# TheAudioDB ID of the artist.
artistID: ID
# A biography of the artist, often available in several languages.
biography(
# The two-letter code for the language in which to retrieve the biography.
lang: String = "en"
): String
# The number of members in the musical group, if applicable.
memberCount: Int
# A 1000x185 JPG banner image containing the artist and their logo or name.
banner(
# The size of the image to retrieve.
size: TheAudioDBImageSize = FULL
): URLString
# A list of 1280x720 or 1920x1080 JPG images depicting the artist.
fanArt(
# The size of the images to retrieve.
size: TheAudioDBImageSize = FULL
): [URLString]!
# A 400x155 PNG image containing the artists logo or name, with a transparent
# background.
logo(
# The size of the image to retrieve.
size: TheAudioDBImageSize = FULL
): URLString
# A 1000x1000 JPG thumbnail image picturing the artist (usually containing
# every member of a band).
thumbnail(
# The size of the image to retrieve.
size: TheAudioDBImageSize = FULL
): URLString
# The primary musical genre of the artist (e.g. Alternative Rock).
genre: String
# The primary musical mood of the artist (e.g. Sad).
mood: String
# The primary musical style of the artist (e.g. Rock/Pop).
style: String
}
# An album on [TheAudioDB](http://www.theaudiodb.com/) corresponding with a
# MusicBrainz Release Group.
type TheAudioDBAlbum {
# TheAudioDB ID of the album.
albumID: ID
# TheAudioDB ID of the artist who released the album.
artistID: ID
# A description of the album, often available in several languages.
description(
# The two-letter code for the language in which to retrieve the biography.
lang: String = "en"
): String
# A review of the album.
review: String
# The worldwide sales figure.
salesCount: Int
# The albums rating as determined by user votes, out of 10.
score: Float
# The number of users who voted to determine the albums score.
scoreVotes: Int
# An image of the physical disc media for the album.
discImage(
# The size of the image to retrieve.
size: TheAudioDBImageSize = FULL
): URLString
# An image of the spine of the album packaging.
spineImage(
# The size of the image to retrieve.
size: TheAudioDBImageSize = FULL
): URLString
# An image of the front of the album packaging.
frontImage(
# The size of the image to retrieve.
size: TheAudioDBImageSize = FULL
): URLString
# An image of the back of the album packaging.
backImage(
# The size of the image to retrieve.
size: TheAudioDBImageSize = FULL
): URLString
# The primary musical genre of the album (e.g. Alternative Rock).
genre: String
# The primary musical mood of the album (e.g. Sad).
mood: String
# The primary musical style of the album (e.g. Rock/Pop).
style: String
# A rough description of the primary musical speed of the album (e.g. Medium).
speed: String
# The primary musical theme of the album (e.g. In Love).
theme: String
}
# A track on [TheAudioDB](http://www.theaudiodb.com/) corresponding with a
# MusicBrainz Recording.
type TheAudioDBTrack {
# TheAudioDB ID of the track.
trackID: ID
# TheAudioDB ID of the album on which the track appears.
albumID: ID
# TheAudioDB ID of the artist who released the track.
artistID: ID
# A description of the track.
description(
lang: String = "en"
): String
# A thumbnail image for the track.
thumbnail(
# The size of the image to retrieve.
size: TheAudioDBImageSize = FULL
): URLString
# The tracks rating as determined by user votes, out of 10.
score: Float
# The number of users who voted to determine the albums score.
scoreVotes: Int
# The track number of the song on the album.
trackNumber: Int
# The official music video for the track.
musicVideo: TheAudioDBMusicVideo
# The primary musical genre of the track (e.g. Alternative Rock).
genre: String
# The primary musical mood of the track (e.g. Sad).
mood: String
# The primary musical style of the track (e.g. Rock/Pop).
style: String
# The primary musical theme of the track (e.g. In Love).
theme: String
}
# Details of a music video associated with a track on [TheAudioDB](http://www.theaudiodb.com/).
type TheAudioDBMusicVideo {
# The URL where the music video can be found.
url: URLString
# The video production company of the music video.
companyName: String
# The director of the music video.
directorName: String
# A list of still images from the music video.
screenshots(
# The size of the images to retrieve.
size: TheAudioDBImageSize = FULL
): [URLString]!
# The number of views the video has received at the given URL. This will rarely
# be up to date, so use cautiously.
viewCount: Int
# The number of likes the video has received at the given URL. This will rarely
# be up to date, so use cautiously.
likeCount: Int
# The number of dislikes the video has received at the given URL. This will
# rarely be up to date, so use cautiously.
dislikeCount: Int
# The number of comments the video has received at the given URL. This will
# rarely be up to date, so use cautiously.
commentCount: Int
}
extend type Artist {
# Data about the artist from [TheAudioDB](http://www.theaudiodb.com/), a good
# source of biographical information and images.
# This field is provided by TheAudioDB extension.
theAudioDB: TheAudioDBArtist
}
extend type Recording {
# Data about the recording from [TheAudioDB](http://www.theaudiodb.com/).
# This field is provided by TheAudioDB extension.
theAudioDB: TheAudioDBTrack
}
extend type ReleaseGroup {
# Data about the release group from [TheAudioDB](http://www.theaudiodb.com/),
# a good source of descriptive information, reviews, and images.
# This field is provided by TheAudioDB extension.
theAudioDB: TheAudioDBAlbum
}
`

View file

@ -1,9 +1,11 @@
import express from 'express' import express from 'express'
import graphqlHTTP from 'express-graphql' import graphqlHTTP from 'express-graphql'
import compression from 'compression' import compression from 'compression'
import MusicBrainz, { CoverArtArchive } from './api' import MusicBrainz from './api'
import schema from './schema' import schema, { createSchema } from './schema'
import createLoaders from './loaders' import { createContext } from './context'
const debug = require('debug')('graphbrainz')
const formatError = (err) => ({ const formatError = (err) => ({
message: err.message, message: err.message,
@ -13,19 +15,37 @@ const formatError = (err) => ({
const middleware = ({ const middleware = ({
client = new MusicBrainz(), client = new MusicBrainz(),
coverArtClient = new CoverArtArchive(), extensions = process.env.GRAPHBRAINZ_EXTENSIONS
...options ? JSON.parse(process.env.GRAPHBRAINZ_EXTENSIONS)
: [
'./extensions/cover-art-archive',
'./extensions/fanart-tv',
'./extensions/mediawiki',
'./extensions/the-audio-db'
],
...middlewareOptions
} = {}) => { } = {}) => {
debug(`Loading ${extensions.length} extension(s).`)
const options = {
client,
extensions: extensions.map(extensionModule => {
if (typeof extensionModule === 'object') {
return extensionModule
}
const extension = require(extensionModule)
return extension.default ? extension.default : extension
}),
...middlewareOptions
}
const DEV = process.env.NODE_ENV !== 'production' const DEV = process.env.NODE_ENV !== 'production'
const graphiql = DEV || process.env.GRAPHBRAINZ_GRAPHIQL === 'true' const graphiql = DEV || process.env.GRAPHBRAINZ_GRAPHIQL === 'true'
const loaders = createLoaders(client, coverArtClient)
return graphqlHTTP({ return graphqlHTTP({
schema, schema: createSchema(schema, options),
context: { client, coverArtClient, loaders }, context: createContext(options),
pretty: DEV, pretty: DEV,
graphiql, graphiql,
formatError: DEV ? formatError : undefined, formatError: DEV ? formatError : undefined,
...options ...middlewareOptions
}) })
} }

View file

@ -1,11 +1,11 @@
import DataLoader from 'dataloader' import DataLoader from 'dataloader'
import LRUCache from 'lru-cache' import LRUCache from 'lru-cache'
import { toPlural } from './types/helpers' import { toPlural } from './types/helpers'
import { ONE_DAY } from './util'
const debug = require('debug')('graphbrainz:loaders') const debug = require('debug')('graphbrainz:loaders')
const ONE_DAY = 24 * 60 * 60 * 1000
export default function createLoaders (client, coverArtClient) { export default function createLoaders (client) {
// All loaders share a single LRU cache that will remember 8192 responses, // All loaders share a single LRU cache that will remember 8192 responses,
// each cached for 1 day. // each cached for 1 day.
const cache = LRUCache({ const cache = LRUCache({
@ -70,36 +70,5 @@ export default function createLoaders (client, coverArtClient) {
cacheMap: cache cacheMap: cache
}) })
const coverArt = new DataLoader(keys => { return { lookup, browse, search }
return Promise.all(keys.map(key => {
const [ entityType, id ] = key
return coverArtClient.images(...key).catch(err => {
if (err.statusCode === 404) {
return { images: [] }
}
throw err
}).then(coverArt => {
coverArt._parentType = entityType
coverArt._parentID = id
if (entityType === 'release') {
coverArt._release = id
} else {
coverArt._release = coverArt.release && coverArt.release.split('/').pop()
}
return coverArt
})
}))
}, {
cacheKeyFn: key => `cover-art/${coverArtClient.getImagesURL(...key)}`,
cacheMap: cache
})
const coverArtURL = new DataLoader(keys => {
return Promise.all(keys.map(key => coverArtClient.imageURL(...key)))
}, {
cacheKeyFn: key => `cover-art/url/${coverArtClient.getImageURL(...key)}`,
cacheMap: cache
})
return { lookup, browse, search, coverArt, coverArtURL }
} }

View file

@ -1,7 +1,50 @@
import { GraphQLSchema, GraphQLObjectType } from 'graphql' import { GraphQLSchema, GraphQLObjectType, extendSchema, parse } from 'graphql'
import { addResolveFunctionsToSchema } from 'graphql-tools'
import { lookup, browse, search } from './queries' import { lookup, browse, search } from './queries'
import { nodeField } from './types/node' import { nodeField } from './types/node'
const debug = require('debug')('graphbrainz:schema')
export function applyExtension (extension, schema, options = {}) {
let outputSchema = schema
if (extension.extendSchema) {
if (typeof extension.extendSchema === 'object') {
debug(`Extending schema via an object from the “${extension.name}” extension.`)
const { schemas = [], resolvers } = extension.extendSchema
outputSchema = schemas.reduce((updatedSchema, extensionSchema) => {
if (typeof extensionSchema === 'string') {
extensionSchema = parse(extensionSchema)
}
return extendSchema(updatedSchema, extensionSchema)
}, outputSchema)
if (resolvers) {
addResolveFunctionsToSchema(outputSchema, resolvers)
}
} else if (typeof extension.extendSchema === 'function') {
debug(`Extending schema via a function from the “${extension.name}” extension.`)
outputSchema = extension.extendSchema(schema, options)
} else {
throw new Error(
`The “${extension.name}” extension contains an invalid ` +
`\`extendSchema\` value: ${extension.extendSchema}`
)
}
}
// Fix for `graphql-tools` creating a new Query type with no description.
if (outputSchema._queryType.description === undefined) {
outputSchema._queryType.description = schema._queryType.description
}
return outputSchema
}
export function createSchema (schema, options = {}) {
const extensions = options.extensions || []
return extensions.reduce((updatedSchema, extension) => {
return applyExtension(extension, updatedSchema, options)
}, schema)
}
export default new GraphQLSchema({ export default new GraphQLSchema({
query: new GraphQLObjectType({ query: new GraphQLObjectType({
name: 'Query', name: 'Query',

View file

@ -1,72 +0,0 @@
import {
GraphQLObjectType,
GraphQLNonNull,
GraphQLList,
GraphQLBoolean,
GraphQLString,
GraphQLInt
} from 'graphql/type'
import { URLString } from './scalars'
export const CoverArtImageThumbnails = new GraphQLObjectType({
name: 'CoverArtImageThumbnails',
description: `URLs for thumbnails of different sizes for a particular piece of
cover art.`,
fields: () => ({
small: {
type: URLString,
description: `The URL of a small version of the cover art, where the
maximum dimension is 250px.`
},
large: {
type: URLString,
description: `The URL of a large version of the cover art, where the
maximum dimension is 500px.`
}
})
})
export default new GraphQLObjectType({
name: 'CoverArtImage',
description: 'An individual piece of album artwork from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).',
fields: () => ({
fileID: {
type: new GraphQLNonNull(GraphQLString),
description: 'The Internet Archives internal file ID for the image.',
resolve: image => image.id
},
image: {
type: new GraphQLNonNull(URLString),
description: 'The URL at which the image can be found.'
},
thumbnails: {
type: CoverArtImageThumbnails,
description: 'A set of thumbnails for the image.'
},
front: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'Whether this image depicts the “main front” of the release.'
},
back: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'Whether this image depicts the “main back” of the release.'
},
types: {
type: new GraphQLList(GraphQLString),
description: `A list of [image types](https://musicbrainz.org/doc/Cover_Art/Types)
describing what part(s) of the release the image includes.`
},
edit: {
type: GraphQLInt,
description: 'The MusicBrainz edit ID.'
},
approved: {
type: GraphQLBoolean,
description: 'Whether the image was approved by the MusicBrainz edit system.'
},
comment: {
type: GraphQLString,
description: 'A free-text comment left for the image.'
}
})
})

View file

@ -1,188 +0,0 @@
import {
GraphQLObjectType,
GraphQLList,
GraphQLNonNull,
GraphQLBoolean,
GraphQLInt
} from 'graphql/type'
import CoverArtImage from './cover-art-image'
import { CoverArtImageSize } from './enums'
import Release from './release'
import { URLString } from './scalars'
import { resolveLookup } from '../resolvers'
import { getFields } from '../util'
/**
* Return a resolver that will call `resolveFn` only if the requested field on
* the object is null or not present.
*/
function createFallbackResolver (resolveFn) {
return function resolve (coverArt, args, context, info) {
const value = coverArt[info.fieldName]
if (value == null) {
return resolveFn(coverArt, args, context, info)
}
return value
}
}
function resolveImage (coverArt, { size }, { loaders }, info) {
if (size === 'FULL') {
size = null
}
const field = info.fieldName
if (coverArt.images) {
const matches = coverArt.images.filter(image => image[field])
if (!matches.length) {
return null
} else if (matches.length === 1) {
const match = matches[0]
if (size === 250) {
return match.thumbnails.small
} else if (size === 500) {
return match.thumbnails.large
} else {
return match.image
}
}
}
if (coverArt[field] !== false) {
const {
_parentType: entityType = 'release',
_parentID: id = coverArt._release
} = coverArt
return loaders.coverArtURL.load([entityType, id, field, size])
}
}
const size = {
type: CoverArtImageSize,
description: `The size of the image to retrieve. By default, the returned
image will have its full original dimensions, but certain thumbnail sizes may be
retrieved as well.`,
defaultValue: null
}
/**
* Get around both the circular dependency between the release and cover art
* types, and not have to define an identical `release` field twice on
* `ReleaseCoverArt` and `ReleaseGroupCoverArt`.
*/
function createReleaseField () {
return {
type: new GraphQLNonNull(Release),
description: 'The particular release shown in the returned cover art.',
resolve: (coverArt, args, context, info) => {
const id = coverArt._release
const fields = Object.keys(getFields(info))
if (fields.length > 1 || fields[0] !== 'mbid') {
return resolveLookup(coverArt, { mbid: id }, context, info)
}
return { id }
}
}
}
// This type combines two sets of data from different places. One is a *summary*
// of the images available at the Cover Art Archive, found in the `cover-art-archive`
// field on releases. The other is the actual list of images with their metadata,
// fetched from the Cover Art Archive itself rather than MusicBrainz. Depending
// on what fields are requested, we may only need to fetch one or the other, or
// both. Much of the summary data can be reconstructed if we already fetched the
// full image list, for example.
export const ReleaseCoverArt = new GraphQLObjectType({
name: 'ReleaseCoverArt',
description: `An object containing a list of the cover art images for a
release obtained from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive),
as well as a summary of what artwork is available.`,
fields: () => ({
front: {
type: URLString,
description: `The URL of an image depicting the album cover or “main
front of the release, i.e. the front of the packaging of the audio recording
(or in the case of a digital release, the image associated with it in a digital
media store).
In the MusicBrainz schema, this field is a Boolean value indicating the presence
of a front image, whereas here the value is the URL for the image itself if one
exists. You can check for null if you just want to determine the presence of an
image.`,
args: { size },
resolve: resolveImage
},
back: {
type: URLString,
description: `The URL of an image depicting the “main back” of the
release, i.e. the back of the packaging of the audio recording.
In the MusicBrainz schema, this field is a Boolean value indicating the presence
of a back image, whereas here the value is the URL for the image itself. You can
check for null if you just want to determine the presence of an image.`,
args: { size },
resolve: resolveImage
},
images: {
type: new GraphQLList(CoverArtImage),
description: `A list of images depicting the different sides and surfaces
of a releases media and packaging.`,
resolve: createFallbackResolver((coverArt, args, { loaders }) => {
if (coverArt.count === 0) {
return []
}
return loaders.coverArt.load(['release', coverArt._release])
.then(coverArt => coverArt.images)
})
},
artwork: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'Whether there is artwork present for this release.',
resolve: createFallbackResolver(coverArt => coverArt.images.length > 0)
},
darkened: {
type: new GraphQLNonNull(GraphQLBoolean),
description: `Whether the Cover Art Archive has received a take-down
request for this releases artwork, disallowing new uploads.`,
resolve: createFallbackResolver((coverArt, args, { loaders }) => {
return loaders.lookup.load(['release', coverArt._release])
.then(release => release['cover-art-archive'].darkened)
})
},
count: {
type: new GraphQLNonNull(GraphQLInt),
description: 'The number of artwork images present for this release.',
resolve: createFallbackResolver(coverArt => coverArt.images.length)
},
release: createReleaseField()
})
})
export const ReleaseGroupCoverArt = new GraphQLObjectType({
name: 'ReleaseGroupCoverArt',
description: `An object containing the cover art for a release group obtained
from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive). For
release groups, just the front cover of a particular release will be selected.`,
fields: () => ({
front: {
type: URLString,
description: `The URL of an image depicting the album cover or “main
front of a release in the release group, i.e. the front of the packaging of the
audio recording (or in the case of a digital release, the image associated with
it in a digital media store).`,
args: { size },
resolve: resolveImage
},
images: {
type: new GraphQLList(CoverArtImage),
description: `A list of images returned by the [Cover Art
Archive](https://musicbrainz.org/doc/Cover_Art_Archive) for a release group. A
particular releases front image will be included in the list, and likely no
others, even if other images are available.`
},
artwork: {
type: new GraphQLNonNull(GraphQLBoolean),
description: 'Whether there is artwork present for this release group.',
resolve: createFallbackResolver(coverArt => coverArt.images.length > 0)
},
release: createReleaseField()
})
})

View file

@ -1,7 +1,6 @@
import { GraphQLObjectType, GraphQLList } from 'graphql/type' import { GraphQLObjectType, GraphQLList } from 'graphql/type'
import Node from './node' import Node from './node'
import Entity from './entity' import Entity from './entity'
import { ReleaseGroupCoverArt } from './cover-art'
import { DateType } from './scalars' import { DateType } from './scalars'
import { ReleaseGroupType } from './enums' import { ReleaseGroupType } from './enums'
import { import {
@ -59,14 +58,6 @@ e.g. album, single, soundtrack, compilation, etc. A release group can have a
description: `Additional [types](https://musicbrainz.org/doc/Release_Group/Type) description: `Additional [types](https://musicbrainz.org/doc/Release_Group/Type)
that apply to this release group.` that apply to this release group.`
}), }),
coverArt: {
type: ReleaseGroupCoverArt,
description: `The cover art for a release group, obtained from the [Cover
Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).`,
resolve: (releaseGroup, args, { loaders }) => {
return loaders.coverArt.load(['release-group', releaseGroup.id])
}
},
artists, artists,
releases, releases,
relationships, relationships,

View file

@ -1,6 +1,5 @@
import { import {
GraphQLObjectType, GraphQLObjectType,
GraphQLNonNull,
GraphQLString, GraphQLString,
GraphQLList GraphQLList
} from 'graphql/type' } from 'graphql/type'
@ -8,7 +7,6 @@ import Node from './node'
import Entity from './entity' import Entity from './entity'
import { ASIN, DateType } from './scalars' import { ASIN, DateType } from './scalars'
import Media from './media' import Media from './media'
import { ReleaseCoverArt } from './cover-art'
import { ReleaseStatus } from './enums' import { ReleaseStatus } from './enums'
import ReleaseEvent from './release-event' import ReleaseEvent from './release-event'
import { import {
@ -74,19 +72,6 @@ release has one. The most common types found on releases are 12-digit
[UPCs](https://en.wikipedia.org/wiki/Universal_Product_Code) and 13-digit [UPCs](https://en.wikipedia.org/wiki/Universal_Product_Code) and 13-digit
[EANs](https://en.wikipedia.org/wiki/International_Article_Number).` [EANs](https://en.wikipedia.org/wiki/International_Article_Number).`
}, },
coverArt: {
type: new GraphQLNonNull(ReleaseCoverArt),
description: `A list and summary of the cover art images that are present
for this release from the [Cover Art Archive](https://musicbrainz.org/doc/Cover_Art_Archive).`,
resolve: (release, args, { loaders }) => {
const coverArt = release['cover-art-archive']
if (coverArt) {
coverArt._release = release.id
return coverArt
}
return loaders.coverArt.load(['release', release.id])
}
},
...fieldWithID('status', { ...fieldWithID('status', {
type: ReleaseStatus, type: ReleaseStatus,
description: 'The status describes how “official” a release is.' description: 'The status describes how “official” a release is.'

View file

@ -1,5 +1,7 @@
import util from 'util' import util from 'util'
export const ONE_DAY = 24 * 60 * 60 * 1000
export function getFields (info, fragments = info.fragments) { export function getFields (info, fragments = info.fragments) {
if (info.kind !== 'Field') { if (info.kind !== 'Field') {
info = info.fieldNodes[0] info = info.fieldNodes[0]
@ -13,6 +15,8 @@ export function getFields (info, fragments = info.fragments) {
throw new Error(`Fragment '${name}' was not passed to getFields()`) throw new Error(`Fragment '${name}' was not passed to getFields()`)
} }
fragment.selectionSet.selections.reduce(reducer, fields) fragment.selectionSet.selections.reduce(reducer, fields)
} else if (selection.kind === 'InlineFragment') {
selection.selectionSet.selections.reduce(reducer, fields)
} else { } else {
fields[selection.name.value] = selection fields[selection.name.value] = selection
} }

View file

@ -1,6 +1,5 @@
import test from 'ava' import test from 'ava'
import { CoverArtArchiveError } from '../../src/api' import client from '../../helpers/client/cover-art-archive'
import client from '../helpers/client/cover-art-archive'
test('can retrieve a front image URL', t => { test('can retrieve a front image URL', t => {
return client.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'front') return client.imageURL('release', '76df3287-6cda-33eb-8e9a-044b5e15ffdd', 'front')
@ -37,7 +36,7 @@ test('can retrieve a list of release images', t => {
}) })
test('throws an error if given an invalid MBID', t => { test('throws an error if given an invalid MBID', t => {
return t.throws(client.images('release', 'xyz'), CoverArtArchiveError) return t.throws(client.images('release', 'xyz'), client.errorClass)
}) })
test('uses the default error impementation if there is no HTML error', t => { test('uses the default error impementation if there is no HTML error', t => {

View file

@ -0,0 +1,174 @@
import test from 'ava'
import { graphql } from 'graphql'
import extension from '../../../src/extensions/cover-art-archive'
import baseSchema, { applyExtension } from '../../../src/schema'
import baseContext from '../../helpers/context'
const schema = applyExtension(extension, baseSchema)
const context = extension.extendContext(baseContext)
function testData (t, query, handler) {
return graphql(schema, query, null, context).then(result => {
if (result.errors !== undefined) {
console.log(result.errors)
}
t.is(result.errors, undefined)
return handler(t, result.data)
})
}
test('releases have a cover art summary', testData, `
{
lookup {
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
coverArtArchive {
artwork
count
}
}
}
}
`, (t, data) => {
const { coverArtArchive } = data.lookup.release
t.true(coverArtArchive.artwork)
t.true(coverArtArchive.count >= 10)
})
test('releases have a set of cover art images', testData, `
{
lookup {
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
coverArtArchive {
front
back
images {
front
back
types
approved
edit
comment
fileID
image
thumbnails {
small
large
}
}
}
}
}
}
`, (t, data) => {
const { coverArtArchive } = data.lookup.release
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg')
t.is(coverArtArchive.back, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798.jpg')
t.true(coverArtArchive.images.length >= 10)
t.true(coverArtArchive.images.some(image => image.front === true))
t.true(coverArtArchive.images.some(image => image.back === true))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Front') >= 0))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Back') >= 0))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Liner') >= 0))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Poster') >= 0))
t.true(coverArtArchive.images.some(image => image.types.indexOf('Medium') >= 0))
t.true(coverArtArchive.images.some(image => image.edit === 18544122))
t.true(coverArtArchive.images.some(image => image.comment === ''))
t.true(coverArtArchive.images.some(image => image.fileID === '1611507818'))
t.true(coverArtArchive.images.some(image => image.image === 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536422691.jpg'))
t.true(coverArtArchive.images.every(image => image.approved === true))
t.true(coverArtArchive.images.every(image => image.thumbnails.small))
t.true(coverArtArchive.images.every(image => image.thumbnails.large))
})
test('can request a size for front and back cover art', testData, `
{
lookup {
release(mbid: "b84ee12a-09ef-421b-82de-0441a926375b") {
coverArtArchive {
front(size: LARGE)
back(size: SMALL)
fullFront: front(size: FULL)
}
}
}
}
`, (t, data) => {
const { coverArtArchive } = data.lookup.release
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818-500.jpg')
t.is(coverArtArchive.back, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/13536418798-250.jpg')
t.is(coverArtArchive.fullFront, 'http://coverartarchive.org/release/b84ee12a-09ef-421b-82de-0441a926375b/1611507818.jpg')
})
test('release groups have a front cover art image', testData, `
{
lookup {
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
coverArtArchive {
artwork
front
images {
front
image
}
release {
mbid
title
}
}
}
}
}
`, (t, data) => {
const { coverArtArchive } = data.lookup.releaseGroup
t.true(coverArtArchive.artwork)
t.is(coverArtArchive.front, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275.jpg')
t.is(coverArtArchive.release.mbid, '25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27')
t.is(coverArtArchive.release.title, 'The Dark Side of the Moon')
t.is(coverArtArchive.images.length, 1)
t.true(coverArtArchive.images[0].front)
})
test('release groups have different cover art sizes available', testData, `
{
lookup {
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
coverArtArchive {
small: front(size: SMALL)
large: front(size: LARGE)
}
}
}
}
`, (t, data) => {
const { coverArtArchive } = data.lookup.releaseGroup
t.is(coverArtArchive.small, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-250.jpg')
t.is(coverArtArchive.large, 'http://coverartarchive.org/release/25fbfbb4-b1ee-4448-aadf-ae3bc2e2dd27/1675312275-500.jpg')
})
test('can retrieve cover art in searches', testData, `
{
search {
releases(query: "You Want It Darker") {
edges {
node {
coverArtArchive {
artwork
front
back
images {
image
}
}
}
}
}
}
}
`, (t, data) => {
const releases = data.search.releases.edges.map(edge => edge.node)
t.is(releases.length, 25)
t.true(releases.some(release => release.coverArtArchive.artwork === true))
t.true(releases.some(release => release.coverArtArchive.images.length > 0))
t.true(releases.some(release => release.coverArtArchive.front === null))
t.true(releases.some(release => release.coverArtArchive.back === null))
})

View file

@ -0,0 +1,128 @@
import test from 'ava'
import { graphql } from 'graphql'
import extension from '../../../src/extensions/fanart-tv'
import baseSchema, { applyExtension } from '../../../src/schema'
import baseContext from '../../helpers/context'
const schema = applyExtension(extension, baseSchema)
const context = extension.extendContext(baseContext)
function testData (t, query, handler) {
return graphql(schema, query, null, context).then(result => {
if (result.errors !== undefined) {
console.log(result.errors)
}
t.is(result.errors, undefined)
return handler(t, result.data)
})
}
test('artists have a fanArt field and preview images', testData, `
{
lookup {
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
fanArt {
backgrounds {
imageID
url(size: PREVIEW)
fullSizeURL: url
likeCount
}
banners {
imageID
url(size: PREVIEW)
fullSizeURL: url
likeCount
}
logos {
imageID
url(size: PREVIEW)
fullSizeURL: url
likeCount
}
logosHD {
imageID
url(size: PREVIEW)
fullSizeURL: url
likeCount
}
thumbnails {
imageID
url(size: PREVIEW)
fullSizeURL: url
likeCount
}
}
}
}
}
`, (t, data) => {
t.snapshot(data)
const { fanArt } = data.lookup.artist
const allImages = []
.concat(fanArt.backgrounds)
.concat(fanArt.banners)
.concat(fanArt.logos)
.concat(fanArt.logosHD)
.concat(fanArt.thumbnails)
allImages.forEach(image => {
t.not(image.url, image.fullSizeURL)
})
})
test('release groups have a fanArt field and preview images', testData, `
{
lookup {
releaseGroup(mbid: "f5093c06-23e3-404f-aeaa-40f72885ee3a") {
fanArt {
albumCovers {
imageID
url(size: PREVIEW)
fullSizeURL: url
likeCount
}
discImages {
imageID
url(size: PREVIEW)
fullSizeURL: url
discNumber
size
}
}
}
}
}
`, (t, data) => {
t.snapshot(data)
const { fanArt } = data.lookup.releaseGroup
const allImages = []
.concat(fanArt.albumCovers)
.concat(fanArt.discImages)
allImages.forEach(image => {
t.not(image.url, image.fullSizeURL)
})
})
test('labels have a fanArt field and preview images', testData, `
{
lookup {
label(mbid: "0cf56645-50ec-4411-aeb6-c9f4ce0f8edb") {
fanArt {
logos {
imageID
url(size: PREVIEW)
fullSizeURL: url
likeCount
color
}
}
}
}
}
`, (t, data) => {
t.snapshot(data)
const { fanArt } = data.lookup.label
fanArt.logos.forEach(image => {
t.not(image.url, image.fullSizeURL)
})
})

View file

@ -0,0 +1,424 @@
# Snapshot report for `test/extensions/fanart-tv/schema.js`
The actual snapshot is saved in `schema.js.snap`.
Generated by [AVA](https://ava.li).
## artists have a fanArt field and preview images
> Snapshot 1
{
lookup: {
artist: {
fanArt: {
backgrounds: [
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-52c91734c39dd.jpg',
imageID: '108996',
likeCount: 7,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-52c91734c39dd.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-4ddaf131354a8.jpg',
imageID: '2539',
likeCount: 7,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-4ddaf131354a8.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-524daf0607406.jpg',
imageID: '99990',
likeCount: 6,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-524daf0607406.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-4de90b913b2a1.jpg',
imageID: '4153',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-4de90b913b2a1.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-54dc843050470.jpg',
imageID: '151698',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-54dc843050470.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-52c917ac71459.jpg',
imageID: '108997',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-52c917ac71459.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-55baa96c8c47f.jpg',
imageID: '172578',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-55baa96c8c47f.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-500187e32be79.jpg',
imageID: '42530',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-500187e32be79.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-56d0287053099.jpg',
imageID: '190330',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-56d0287053099.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-56d02870536ef.jpg',
imageID: '190331',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-56d02870536ef.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-54dc845f81d1b.jpg',
imageID: '151699',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-54dc845f81d1b.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-524daf0607e44.jpg',
imageID: '99991',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-524daf0607e44.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-54ac79e578054.jpg',
imageID: '146472',
likeCount: 1,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistbackground/nirvana-54ac79e578054.jpg',
},
],
banners: [
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/musicbanner/nirvana-515f7e1a6f50b.jpg',
imageID: '78008',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/musicbanner/nirvana-515f7e1a6f50b.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/musicbanner/nirvana-591789a12da78.jpg',
imageID: '218845',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/musicbanner/nirvana-591789a12da78.jpg',
},
],
logos: [
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/musiclogo/nirvana-4e4b9bc06dcc4.png',
imageID: '8957',
likeCount: 4,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/musiclogo/nirvana-4e4b9bc06dcc4.png',
},
],
logosHD: [
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-5261733fe6c60.png',
imageID: '101686',
likeCount: 6,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-5261733fe6c60.png',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-518a696cda12f.png',
imageID: '81480',
likeCount: 4,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-518a696cda12f.png',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-518ada7d98805.png',
imageID: '81521',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-518ada7d98805.png',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-518a696c1ab0b.png',
imageID: '81479',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-518a696c1ab0b.png',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-561900ee87f11.png',
imageID: '181150',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-561900ee87f11.png',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-59039625adb45.png',
imageID: '217621',
likeCount: 1,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-59039625adb45.png',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-561900db6b999.png',
imageID: '181149',
likeCount: 1,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-561900db6b999.png',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-5619010106ffb.png',
imageID: '181151',
likeCount: 1,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/hdmusiclogo/nirvana-5619010106ffb.png',
},
],
thumbnails: [
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistthumb/nirvana-4fb61ff40a15a.jpg',
imageID: '31455',
likeCount: 4,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistthumb/nirvana-4fb61ff40a15a.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistthumb/nirvana-4fb61fd2f3204.jpg',
imageID: '31454',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistthumb/nirvana-4fb61fd2f3204.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistthumb/nirvana-4fb6205204d6e.jpg',
imageID: '31456',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistthumb/nirvana-4fb6205204d6e.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistthumb/nirvana-515ddb61b444b.jpg',
imageID: '77828',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/5b11f4ce-a62d-471e-81fc-a69a8278c7da/artistthumb/nirvana-515ddb61b444b.jpg',
},
],
},
},
},
}
## labels have a fanArt field and preview images
> Snapshot 1
{
lookup: {
label: {
fanArt: {
logos: [],
},
},
},
}
## release groups have a fanArt field and preview images
> Snapshot 1
{
lookup: {
releaseGroup: {
fanArt: {
albumCovers: [
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-4decb408b6604.jpg',
imageID: '4417',
likeCount: 11,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-4decb408b6604.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-53afcfaa65a86.jpg',
imageID: '128223',
likeCount: 6,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-53afcfaa65a86.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-5647389f76c9a.jpg',
imageID: '183166',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-5647389f76c9a.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-555319062404d.jpg',
imageID: '163522',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-555319062404d.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-535054d4e2e32.jpg',
imageID: '118729',
likeCount: 3,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-535054d4e2e32.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-53cf470795637.jpg',
imageID: '130679',
likeCount: 2,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-53cf470795637.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-55c4af535f7e9.jpg',
imageID: '173720',
likeCount: 1,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-55c4af535f7e9.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-55cbdf80ef05c.jpg',
imageID: '174560',
likeCount: 1,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-55cbdf80ef05c.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a82b1a7c6cd.jpg',
imageID: '225420',
likeCount: 0,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a82b1a7c6cd.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a82d31adb2f.jpg',
imageID: '225421',
likeCount: 0,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a82d31adb2f.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a31c7ad5931.jpg',
imageID: '225172',
likeCount: 0,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a31c7ad5931.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a48bfb008e3.jpg',
imageID: '225266',
likeCount: 0,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a48bfb008e3.jpg',
},
{
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a48df6822b4.jpg',
imageID: '225267',
likeCount: 0,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/albumcover/the-dark-side-of-the-moon-59a48df6822b4.jpg',
},
],
discImages: [
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-56047db2bbb28.png',
imageID: '179437',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-56047db2bbb28.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-56047e045d648.png',
imageID: '179438',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-56047e045d648.png',
},
{
discNumber: 3,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-50aa6521ef0f3.png',
imageID: '62047',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-50aa6521ef0f3.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-56047e56b51f8.png',
imageID: '179439',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-56047e56b51f8.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-5957ea48b6728.png',
imageID: '221698',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-5957ea48b6728.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a0791276643.png',
imageID: '225077',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a0791276643.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-501c2a49803a0.png',
imageID: '46158',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-501c2a49803a0.png',
},
{
discNumber: 2,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-501c3001b1c7c.png',
imageID: '46162',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-501c3001b1c7c.png',
},
{
discNumber: 2,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a74d8e41558.png',
imageID: '225375',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a74d8e41558.png',
},
{
discNumber: 3,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a74dbb41789.png',
imageID: '225376',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a74dbb41789.png',
},
{
discNumber: 4,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a7719de4451.png',
imageID: '225385',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a7719de4451.png',
},
{
discNumber: 5,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a81404c6562.png',
imageID: '225414',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a81404c6562.png',
},
{
discNumber: 6,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a822507d221.png',
imageID: '225416',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a822507d221.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-54c55c43410af.png',
imageID: '148887',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-54c55c43410af.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a3237b3607a.png',
imageID: '225188',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a3237b3607a.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-532569d57474d.png',
imageID: '115624',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-532569d57474d.png',
},
{
discNumber: 1,
fullSizeURL: 'https://assets.fanart.tv/fanart/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a375ff8866e.png',
imageID: '225215',
size: 1000,
url: 'https://assets.fanart.tv/preview/music/83d91898-7763-47d7-b03b-b92132375c47/cdart/the-dark-side-of-the-moon-59a375ff8866e.png',
},
],
},
},
},
}

Binary file not shown.

View file

@ -0,0 +1,101 @@
import test from 'ava'
import { graphql } from 'graphql'
import extension from '../../../src/extensions/mediawiki'
import baseSchema, { applyExtension } from '../../../src/schema'
import baseContext from '../../helpers/context'
const schema = applyExtension(extension, baseSchema)
const context = extension.extendContext(baseContext)
function testData (t, query, handler) {
return graphql(schema, query, null, context).then(result => {
if (result.errors !== undefined) {
console.log(result.errors)
}
t.is(result.errors, undefined)
return handler(t, result.data)
})
}
const fragment = `
url
descriptionURL
user
size
width
height
canonicalTitle
objectName
descriptionHTML
originalDateTimeHTML
categories
artistHTML
creditHTML
licenseShortName
licenseURL
metadata {
name
value
source
}
`
test('artists have a mediaWikiImages field', testData, `
{
lookup {
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
mediaWikiImages {
${fragment}
}
}
}
}
`, (t, data) => {
t.snapshot(data)
})
test('instruments have a mediaWikiImages field', testData, `
{
search {
instruments(query: "guitar", first: 20) {
nodes {
mediaWikiImages {
${fragment}
}
}
}
}
}
`, (t, data) => {
t.snapshot(data)
})
test('labels have a mediaWikiImages field', testData, `
{
search {
labels(query: "Sony", first: 50) {
nodes {
mediaWikiImages {
${fragment}
}
}
}
}
}
`, (t, data) => {
t.snapshot(data)
})
test('places have a mediaWikiImages field', testData, `
{
lookup {
place(mbid: "b5297256-8482-4cba-968a-25db61563faf") {
mediaWikiImages {
${fragment}
}
}
}
}
`, (t, data) => {
t.snapshot(data)
})

View file

@ -0,0 +1,580 @@
# Snapshot report for `test/extensions/mediawiki/schema.js`
The actual snapshot is saved in `schema.js.snap`.
Generated by [AVA](https://ava.li).
## artists have a mediaWikiImages field
> Snapshot 1
{
lookup: {
artist: {
mediaWikiImages: [
{
artistHTML: '<a rel="nofollow" class="external text" href="http://www.flickr.com/people/77758445@N00">P.B. Rage</a> from USA',
canonicalTitle: 'File:Nirvana around 1992.jpg',
categories: [
'1992 in California',
'1992 in television',
'Fashion in 1992',
'Flickr images reviewed by trusted users',
'Krist Novoselic',
'Kurt Cobain',
'MTV Video Music Awards',
'Music events in 1992',
'Nirvana (band)',
'Pauley Pavilion',
'September 1992 in the United States',
],
creditHTML: '<a rel="nofollow" class="external text" href="http://www.flickr.com/photos/77758445@N00/11295743/">More Kurt -- too rad</a>',
descriptionHTML: 'Kurt Cobain (front) and Krist Novoselic (left) live at the 1992 <a href="//commons.wikimedia.org/wiki/MTV_Video_Music_Awards" class="mw-redirect" title="MTV Video Music Awards">MTV Video Music Awards</a>.',
descriptionURL: 'https://commons.wikimedia.org/wiki/File:Nirvana_around_1992.jpg',
height: 346,
licenseShortName: 'CC BY-SA 2.0',
licenseURL: 'https://creativecommons.org/licenses/by-sa/2.0',
metadata: [
{
name: 'DateTime',
source: 'mediawiki-metadata',
value: '2015-11-21 12:22:55',
},
{
name: 'ObjectName',
source: 'mediawiki-metadata',
value: 'Nirvana around 1992',
},
{
name: 'CommonsMetadataExtension',
source: 'extension',
value: '1.2',
},
{
name: 'Categories',
source: 'commons-categories',
value: '1992 in California|1992 in television|Fashion in 1992|Flickr images reviewed by trusted users|Krist Novoselic|Kurt Cobain|MTV Video Music Awards|Music events in 1992|Nirvana (band)|Pauley Pavilion|September 1992 in the United States',
},
{
name: 'Assessments',
source: 'commons-categories',
value: '',
},
{
name: 'ImageDescription',
source: 'commons-desc-page',
value: 'Kurt Cobain (front) and Krist Novoselic (left) live at the 1992 <a href="//commons.wikimedia.org/wiki/MTV_Video_Music_Awards" class="mw-redirect" title="MTV Video Music Awards">MTV Video Music Awards</a>.',
},
{
name: 'DateTimeOriginal',
source: 'commons-desc-page',
value: '1992-09-09',
},
{
name: 'Credit',
source: 'commons-desc-page',
value: '<a rel="nofollow" class="external text" href="http://www.flickr.com/photos/77758445@N00/11295743/">More Kurt -- too rad</a>',
},
{
name: 'Artist',
source: 'commons-desc-page',
value: '<a rel="nofollow" class="external text" href="http://www.flickr.com/people/77758445@N00">P.B. Rage</a> from USA',
},
{
name: 'LicenseShortName',
source: 'commons-desc-page',
value: 'CC BY-SA 2.0',
},
{
name: 'UsageTerms',
source: 'commons-desc-page',
value: 'Creative Commons Attribution-Share Alike 2.0',
},
{
name: 'AttributionRequired',
source: 'commons-desc-page',
value: 'true',
},
{
name: 'LicenseUrl',
source: 'commons-desc-page',
value: 'https://creativecommons.org/licenses/by-sa/2.0',
},
{
name: 'Copyrighted',
source: 'commons-desc-page',
value: 'True',
},
{
name: 'Restrictions',
source: 'commons-desc-page',
value: '',
},
{
name: 'License',
source: 'commons-templates',
value: 'cc-by-sa-2.0',
},
],
objectName: 'Nirvana around 1992',
originalDateTimeHTML: '1992-09-09',
size: 31369,
url: 'https://upload.wikimedia.org/wikipedia/commons/1/19/Nirvana_around_1992.jpg',
user: 'Kigsz',
width: 367,
},
],
},
},
}
## instruments have a mediaWikiImages field
> Snapshot 1
{
search: {
instruments: {
nodes: [
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [
{
artistHTML: '<a href="//commons.wikimedia.org/wiki/User:TenIslands" title="User:TenIslands">TenIslands</a>',
canonicalTitle: 'File:2 Portuguese guitars.jpg',
categories: [
'Derivative versions',
'PD-self',
'Portuguese guitar',
'Self-published work',
],
creditHTML: '<span class="int-own-work" lang="en">Own work</span>',
descriptionHTML: 'Left: Coimbra Portuguese guitar.<br>Right: Lisbon Portuguese guitar.',
descriptionURL: 'https://commons.wikimedia.org/wiki/File:2_Portuguese_guitars.jpg',
height: 2304,
licenseShortName: 'Public domain',
licenseURL: null,
metadata: [
{
name: 'DateTime',
source: 'mediawiki-metadata',
value: '2008-07-20 16:57:38',
},
{
name: 'ObjectName',
source: 'mediawiki-metadata',
value: '2 Portuguese guitars',
},
{
name: 'CommonsMetadataExtension',
source: 'extension',
value: '1.2',
},
{
name: 'Categories',
source: 'commons-categories',
value: 'Derivative versions|PD-self|Portuguese guitar|Self-published work',
},
{
name: 'Assessments',
source: 'commons-categories',
value: '',
},
{
name: 'ImageDescription',
source: 'commons-desc-page',
value: 'Left: Coimbra Portuguese guitar.<br>Right: Lisbon Portuguese guitar.',
},
{
name: 'DateTimeOriginal',
source: 'commons-desc-page',
value: '2008-07-19',
},
{
name: 'Credit',
source: 'commons-desc-page',
value: '<span class="int-own-work" lang="en">Own work</span>',
},
{
name: 'Artist',
source: 'commons-desc-page',
value: '<a href="//commons.wikimedia.org/wiki/User:TenIslands" title="User:TenIslands">TenIslands</a>',
},
{
name: 'LicenseShortName',
source: 'commons-desc-page',
value: 'Public domain',
},
{
name: 'UsageTerms',
source: 'commons-desc-page',
value: 'Public domain',
},
{
name: 'AttributionRequired',
source: 'commons-desc-page',
value: 'false',
},
{
name: 'Copyrighted',
source: 'commons-desc-page',
value: 'False',
},
{
name: 'Restrictions',
source: 'commons-desc-page',
value: '',
},
{
name: 'License',
source: 'commons-templates',
value: 'pd',
},
],
objectName: '2 Portuguese guitars',
originalDateTimeHTML: '2008-07-19',
size: 2094702,
url: 'https://upload.wikimedia.org/wikipedia/commons/0/01/2_Portuguese_guitars.jpg',
user: 'TenIslands',
width: 1746,
},
],
},
],
},
},
}
## labels have a mediaWikiImages field
> Snapshot 1
{
search: {
labels: {
nodes: [
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
{
mediaWikiImages: [],
},
],
},
},
}
## places have a mediaWikiImages field
> Snapshot 1
{
lookup: {
place: {
mediaWikiImages: [
{
artistHTML: '<a href="//commons.wikimedia.org/wiki/User:Dllu" title="User:Dllu">Dllu</a>',
canonicalTitle: 'File:Paramount Theater in Seattle showing Wicked.jpg',
categories: [
'Paramount Northwest Theater',
'Self-published work',
],
creditHTML: '<span class="int-own-work" lang="en">Own work</span>',
descriptionHTML: 'The <a href="https://en.wikipedia.org/wiki/Paramount_Theatre_(Seattle)" class="extiw" title="w:Paramount Theatre (Seattle)">Paramount Theatre (Seattle)</a> showing <a href="https://en.wikipedia.org/wiki/Wicked_(musical)" class="extiw" title="w:Wicked (musical)">Wicked</a>.',
descriptionURL: 'https://commons.wikimedia.org/wiki/File:Paramount_Theater_in_Seattle_showing_Wicked.jpg',
height: 3840,
licenseShortName: 'CC BY-SA 4.0',
licenseURL: 'https://creativecommons.org/licenses/by-sa/4.0',
metadata: [
{
name: 'DateTime',
source: 'mediawiki-metadata',
value: '2015-08-01 06:59:27',
},
{
name: 'ObjectName',
source: 'mediawiki-metadata',
value: 'Paramount Theater in Seattle showing Wicked',
},
{
name: 'CommonsMetadataExtension',
source: 'extension',
value: '1.2',
},
{
name: 'Categories',
source: 'commons-categories',
value: 'Paramount Northwest Theater|Self-published work',
},
{
name: 'Assessments',
source: 'commons-categories',
value: '',
},
{
name: 'ImageDescription',
source: 'commons-desc-page',
value: 'The <a href="https://en.wikipedia.org/wiki/Paramount_Theatre_(Seattle)" class="extiw" title="w:Paramount Theatre (Seattle)">Paramount Theatre (Seattle)</a> showing <a href="https://en.wikipedia.org/wiki/Wicked_(musical)" class="extiw" title="w:Wicked (musical)">Wicked</a>.',
},
{
name: 'DateTimeOriginal',
source: 'commons-desc-page',
value: '2015-07-31 20:32:43',
},
{
name: 'Credit',
source: 'commons-desc-page',
value: '<span class="int-own-work" lang="en">Own work</span>',
},
{
name: 'Artist',
source: 'commons-desc-page',
value: '<a href="//commons.wikimedia.org/wiki/User:Dllu" title="User:Dllu">Dllu</a>',
},
{
name: 'LicenseShortName',
source: 'commons-desc-page',
value: 'CC BY-SA 4.0',
},
{
name: 'UsageTerms',
source: 'commons-desc-page',
value: 'Creative Commons Attribution-Share Alike 4.0',
},
{
name: 'AttributionRequired',
source: 'commons-desc-page',
value: 'true',
},
{
name: 'LicenseUrl',
source: 'commons-desc-page',
value: 'https://creativecommons.org/licenses/by-sa/4.0',
},
{
name: 'Copyrighted',
source: 'commons-desc-page',
value: 'True',
},
{
name: 'Restrictions',
source: 'commons-desc-page',
value: '',
},
{
name: 'License',
source: 'commons-templates',
value: 'cc-by-sa-4.0',
},
],
objectName: 'Paramount Theater in Seattle showing Wicked',
originalDateTimeHTML: '2015-07-31 20:32:43',
size: 12529142,
url: 'https://upload.wikimedia.org/wikipedia/commons/c/ce/Paramount_Theater_in_Seattle_showing_Wicked.jpg',
user: 'Dllu',
width: 2566,
},
],
},
},
}

Binary file not shown.

View file

@ -0,0 +1,118 @@
import test from 'ava'
import { graphql } from 'graphql'
import extension from '../../../src/extensions/the-audio-db'
import baseSchema, { applyExtension } from '../../../src/schema'
import baseContext from '../../helpers/context'
const schema = applyExtension(extension, baseSchema)
const context = extension.extendContext(baseContext)
function testData (t, query, handler) {
return graphql(schema, query, null, context).then(result => {
if (result.errors !== undefined) {
console.log(result.errors)
}
t.is(result.errors, undefined)
return handler(t, result.data)
})
}
test('artists have a theAudioDB field', testData, `
{
lookup {
artist(mbid: "5b11f4ce-a62d-471e-81fc-a69a8278c7da") {
theAudioDB {
artistID
biography
biographyJP: biography(lang: "jp")
memberCount
banner
bannerPreview: banner(size: PREVIEW)
fanArt
fanArtPreview: fanArt(size: PREVIEW)
logo
logoPreview: logo(size: PREVIEW)
thumbnail
thumbnailPreview: thumbnail(size: PREVIEW)
genre
mood
style
}
}
}
}
`, (t, data) => {
t.snapshot(data)
})
test('release groups have a theAudioDB field', testData, `
{
lookup {
releaseGroup(mbid: "aa997ea0-2936-40bd-884d-3af8a0e064dc") {
theAudioDB {
albumID
artistID
description
descriptionES: description(lang: "es")
review
salesCount
score
scoreVotes
discImage
discImagePreview: discImage(size: PREVIEW)
spineImage
spineImagePreview: spineImage(size: PREVIEW)
frontImage
frontImagePreview: frontImage(size: PREVIEW)
backImage
backImagePreview: backImage(size: PREVIEW)
genre
mood
style
speed
theme
}
}
}
}
`, (t, data) => {
t.snapshot(data)
})
test('recordings have a theAudioDB field', testData, `
{
lookup {
recording(mbid: "1109d8da-ce4a-4739-9414-242dc3e9b81c") {
theAudioDB {
trackID
albumID
artistID
description
descriptionES: description(lang: "es")
thumbnail
thumbnailPreview: thumbnail(size: PREVIEW)
score
scoreVotes
trackNumber
musicVideo {
url
companyName
directorName
screenshots
screenshotsPreview: screenshots(size: PREVIEW)
viewCount
likeCount
dislikeCount
commentCount
}
genre
mood
style
theme
}
}
}
}
`, (t, data) => {
t.snapshot(data)
})

View file

@ -0,0 +1,125 @@
# Snapshot report for `test/extensions/the-audio-db/schema.js`
The actual snapshot is saved in `schema.js.snap`.
Generated by [AVA](https://ava.li).
## artists have a theAudioDB field
> Snapshot 1
{
lookup: {
artist: {
theAudioDB: {
artistID: '111319',
banner: 'http://www.theaudiodb.com/images/media/artist/banner/wppvrr1365966313.jpg',
bannerPreview: 'http://www.theaudiodb.com/images/media/artist/banner/wppvrr1365966313.jpg/preview',
biography: `Nirvana was an American rock band that was formed by singer and guitarist Kurt Cobain and bassist Krist Novoselic in Aberdeen, Washington, in 1987. Nirvana went through a succession of drummers, the longest-lasting being Dave Grohl, who joined the band in 1990. Despite releasing only three full-length studio albums in their seven-year career, Nirvana has come to be regarded as one of the most influential and important rock bands of the modern era.␊
In the late 1980s Nirvana established itself as part of the Seattle grunge scene, releasing its first album Bleach for the independent record label Sub Pop in 1989. The band eventually came to develop a sound that relied on dynamic contrasts, often between quiet verses and loud, heavy choruses. After signing to major label DGC Records, Nirvana found unexpected success with "Smells Like Teen Spirit", the first single from the band's second album Nevermind (1991). Nirvana's sudden success widely popularized alternative rock as a whole, and the band's frontman Cobain found himself referred to in the media as the "spokesman of a generation", with Nirvana being considered the "flagship band" of Generation X. In response, Nirvana's third studio album, In Utero (1993), featured an abrasive, less-mainstream sound and challenged the group's audience. The album did not match the sales figures of Nevermind, but was still a commercial success and critically acclaimed.␊
Nirvana's brief run ended following the death of Kurt Cobain in 1994, but various posthumous releases have been issued since, overseen by Novoselic, Grohl, and Cobain's widow Courtney Love. Since its debut, the band has sold over 25 million records in the United States alone, and over 75 million records worldwide, making them one of the best-selling bands of all time. Nirvana was inducted into the Rock and Roll Hall of Fame in 2014, in its first year of eligibility.`,
biographyJP: `ニルヴァーナ (Nirvana) は、アメリカのバンド。1980年代終盤にシーンに出現し、1994年のカート自殺による活動停止までの数年に亘って、全世界の若者世代の圧倒的な支持を受けた。彼の死亡後も世界中のミュージシャンに多大な影響を与え続けている。単語「ニルヴァーナ」には、仏教用語の涅槃の境地という意味合いと「生け贄」という意味合いがある。␊
「スメルズ・ライク・ティーン・スピリット」の爆発的ヒットによりバンドは一気に有名になり、1990年代以降のロックに絶大な影響を与え、しばしばオルタナティヴ・ロックシーンにおいて『ニルヴァーナ以降』という言い方をされる。␊
全世界でのトータルセールスは、約7500万枚[1][2]。␊
「ローリング・ストーンの選ぶ歴史上最も偉大な100組のアーティスト」において第30位。`,
fanArt: [
'http://media.theaudiodb.com/images/media/artist/fanart/nirvana-4ddaf131354a8.jpg',
'http://media.theaudiodb.com/images/media/artist/fanart/ussvpr1342344599.jpg',
'http://media.theaudiodb.com/images/media/artist/fanart/uusxqw1342344614.jpg',
],
fanArtPreview: [
'http://media.theaudiodb.com/images/media/artist/fanart/nirvana-4ddaf131354a8.jpg/preview',
'http://media.theaudiodb.com/images/media/artist/fanart/ussvpr1342344599.jpg/preview',
'http://media.theaudiodb.com/images/media/artist/fanart/uusxqw1342344614.jpg/preview',
],
genre: 'Rock',
logo: 'http://www.theaudiodb.com/images/media/artist/logo/xyryvu1363124407.png',
logoPreview: 'http://www.theaudiodb.com/images/media/artist/logo/xyryvu1363124407.png/preview',
memberCount: 3,
mood: 'Sad',
style: 'Rock/Pop',
thumbnail: 'http://www.theaudiodb.com/images/media/artist/thumb/ryppyp1363124444.jpg',
thumbnailPreview: 'http://www.theaudiodb.com/images/media/artist/thumb/ryppyp1363124444.jpg/preview',
},
},
},
}
## recordings have a theAudioDB field
> Snapshot 1
{
lookup: {
recording: {
theAudioDB: {
albumID: '2284335',
artistID: '131613',
description: `"Despacito" (American Spanish: ; English: "Slowly") is a single by Puerto Rican singer Luis Fonsi featuring Puerto Rican rapper Daddy Yankee from Fonsi's upcoming studio album. On January 12, 2017, Universal Music Latin released "Despacito" and its music video, which shows both artists performing the song in La Perla neighborhood of Old San Juan, Puerto Rico and the local bar La Factoría. The song's music video is the first video to reach over three billion views on YouTube. The song was written by Luis Fonsi, Erika Ender and Daddy Yankee, and was produced by Andrés Torres and Mauricio Rengifo.␊
It is a reggaeton-pop song composed in common time with lyrics about having a sexual relationship, performed in a smooth and romantic way. Commercially, the song topped the charts of 47 countries and reached the top 10 of ten others, making it both Fonsi and Daddy Yankee's most successful single to date. It became the first song primarily in Spanish to top the Billboard Hot 100 since "Macarena" (Bayside Boys Mix) in 1996. The official video for "Despacito" on YouTube received its 1 billionth view on April 20, 2017 after 97 days, becoming the second-fastest video on the site to reach the milestone -- behind Adele's "Hello". It received its 2 billionth view on June 16 and its 3 billionth view on August 4 after 154 and 204 days, respectively, making it the fastest video on the site to reach both milestones. With its 3.3 million certified sales plus track-equivalent streams, "Despacito" is one of the best-selling Latin singles in the United States.`,
descriptionES: null,
genre: 'Latin',
mood: 'Sensual',
musicVideo: {
commentCount: 1449046,
companyName: null,
directorName: 'Carlos Pérez',
dislikeCount: 2168098,
likeCount: 21015918,
screenshots: [],
screenshotsPreview: [],
url: 'https://www.youtube.com/watch?v=kJQP7kiw5Fk',
viewCount: 2147483647,
},
score: 10,
scoreVotes: 1,
style: 'Latin',
theme: 'In Love',
thumbnail: 'http://media.theaudiodb.com/images/media/track/thumb/vqqpry1506425784.jpg',
thumbnailPreview: 'http://media.theaudiodb.com/images/media/track/thumb/vqqpry1506425784.jpg/preview',
trackID: '34838814',
trackNumber: 1,
},
},
},
}
## release groups have a theAudioDB field
> Snapshot 1
{
lookup: {
releaseGroup: {
theAudioDB: {
albumID: '2162908',
artistID: '111492',
backImage: null,
backImagePreview: null,
description: `Random Access Memories is the upcoming fourth studio album by French electronic music duo Daft Punk. It will be released by Daft Life under exclusive license to Columbia Records on May 17, 2013 in Australia, May 20, 2013 in the United Kingdom and on May 21, 2013 in the United States. Work started on the record concurrently with the Tron: Legacy score, without a clear plan as to what its structure would be. Shortly after Daft Punk signed with Columbia, a gradual promotional rollout began for the album including billboards, television advertising and a web series.␊
Random Access Memories pays tribute to the late 1970s and early 80s era of music in the United States, particularly the sound of Los Angeles recordings of the period. Daft Punk recorded the album largely using live instrumentation with session musicians, and limited the use of electronics to drum machines, a modular synthesizer and vintage vocoders. The album also features collaborations with Panda Bear, Julian Casablancas, Todd Edwards, DJ Falcon, Chilly Gonzales, Giorgio Moroder, Nile Rodgers, Paul Williams and Pharrell Williams. Critical reception to the album has generally been positive.`,
descriptionES: `Random Access Memories es el cuarto álbum de estudio del dúo francés Daft Punk. Fue lanzado oficialmente el 17 de mayo en Australia, lanzado después en el Reino Unido el 20 de mayo y para Estados Unidos el 21 de mayo de 2013, bajo licencia de Daft Life. El inicio de grabación de este disco inició cuando el dúo preparaba el soundtrack de la película Tron: Legacy, sin un plan claro en cuanto a lo que sería su estructura. Después de haber anunciado su nuevo contrato con Columbia Records, Daft Punk empezó a promocionar el nuevo álbum con cartéles, anuncios televisivos y series para internet.␊
Random Access Memories hace un tributo a la música estadounidense de la época de los 1970s y la primera parte de los 1980s, particularmente al sonido de Los Ángeles durante esa época. Daft Punk grabó el álbum en gran parte con orquesta en vivo con sesiónes musicales y con un uso limitado de máquinas de percusión, sintetizador modular, y con una vendimia de vocoders. El álbum contiene un gran número de colaboradores, entre ellos se destácan: Panda Bear, Chilly Gonzales, DJ Falcon, Julian Casablancas, Todd Edwards, Paul Williams, Pharrell Williams, Nile Rodgers y Ghallmarck. El álbum fue recibido con críticas positivas.␊
Durante la primera mitad de 2013, vendió 614 000 copias en los Estados Unidos, donde se convirtió en el décimo álbum más vendido durante dicho periodo.`,
discImage: 'http://www.theaudiodb.com/images/media/album/cdart/random-access-memories-5194a5974107d.png',
discImagePreview: 'http://www.theaudiodb.com/images/media/album/cdart/random-access-memories-5194a5974107d.png/preview',
frontImage: 'http://www.theaudiodb.com/images/media/album/thumb/random-access-memories-51764651042e5.jpg',
frontImagePreview: 'http://www.theaudiodb.com/images/media/album/thumb/random-access-memories-51764651042e5.jpg/preview',
genre: 'House',
mood: 'Happy',
review: null,
salesCount: 0,
score: 8.7,
scoreVotes: 6,
speed: 'Medium',
spineImage: null,
spineImagePreview: null,
style: 'Electronic',
theme: null,
},
},
},
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:23:11 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "843",
"x-ratelimit-reset": "1508394191",
"server": "Plack::Handler::Starlet",
"etag": "W/\"bc0fd422eb415020ac9aba896844f774\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/instrument/ba4705aa-ff1d-48d5-ae80-7b2046fb451e?inc=url-rels&fmt=json",
"time": 358,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:12 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "944",
"x-ratelimit-reset": "1508394913",
"server": "Plack::Handler::Starlet",
"etag": "W/\"9fdd463cf441954e7ba61de213c25c21\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/b914f217-8be8-486b-a733-82f03275704a?inc=url-rels&fmt=json",
"time": 375,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:18 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1060",
"x-ratelimit-reset": "1508394919",
"server": "Plack::Handler::Starlet",
"etag": "W/\"952c89cf2a01358c9a3c8295a61c2d06\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/d49fe768-e91c-404f-9542-3008c4ef9b51?inc=url-rels&fmt=json",
"time": 410,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:34 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "938",
"x-ratelimit-reset": "1508394935",
"server": "Plack::Handler::Starlet",
"etag": "W/\"19db13c6fdb3773a9bae8cd36693257a\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/23e04f48-06e0-4d32-911d-8f6259c62a13?inc=url-rels&fmt=json",
"time": 370,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:46 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1059",
"x-ratelimit-reset": "1508394947",
"server": "Plack::Handler::Starlet",
"etag": "W/\"2bb8aa78cfd610c5e1a7b121b54efa8f\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/place/b5297256-8482-4cba-968a-25db61563faf?fmt=json",
"time": 327,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:34:56 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1040",
"x-ratelimit-reset": "1508394897",
"server": "Plack::Handler::Starlet",
"etag": "W/\"15d3949951847ce0b26e10c0e96e96db\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/08f37a61-1c54-4257-b31d-810fa2ac5cd5?inc=url-rels&fmt=json",
"time": 378,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:40 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1062",
"x-ratelimit-reset": "1508394941",
"server": "Plack::Handler::Starlet",
"etag": "W/\"59e4337aff496a685585c597d32fe368\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/c82b5688-df49-4e21-935b-0b08d13ec98a?inc=url-rels&fmt=json",
"time": 360,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:29 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "851",
"x-ratelimit-reset": "1508394929",
"server": "Plack::Handler::Starlet",
"etag": "W/\"03f3db33b7222966888909cd6d8d1163\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/decde7d5-dbfe-498d-80ee-dce96ae032e2?inc=url-rels&fmt=json",
"time": 380,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:01 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1127",
"x-ratelimit-reset": "1508394903",
"server": "Plack::Handler::Starlet",
"etag": "W/\"cd510299cdc136110ff7fbcb1332d568\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/151085ba-42d0-477a-83f9-eed3f758c743?inc=url-rels&fmt=json",
"time": 356,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

View file

@ -0,0 +1 @@
{"type-id":"cc00f97f-cf3d-3ae2-9163-041cb1a0d726","id":"c0ea0405-ae3f-4851-bf85-277fadff80e2","type":"String instrument","relations":[],"description":"","disambiguation":"","name":"Hawaiian guitar"}

View file

@ -0,0 +1,27 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:21:19 GMT",
"content-type": "application/json; charset=utf-8",
"content-length": "198",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1101",
"x-ratelimit-reset": "1508394081",
"server": "Plack::Handler::Starlet",
"etag": "\"bada5afab5ba761514e6acb58d47f9ad\"",
"access-control-allow-origin": "*"
},
"url": "http://musicbrainz.org:80/ws/2/instrument/c0ea0405-ae3f-4851-bf85-277fadff80e2?inc=url-rels&fmt=json",
"time": 341,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,43 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 05:19:02 GMT",
"content-type": "application/json; charset=utf-8",
"content-length": "993",
"connection": "keep-alive",
"server": "mw1203.eqiad.wmnet",
"x-powered-by": "HHVM/3.18.6-dev",
"x-content-type-options": "nosniff",
"cache-control": "private, must-revalidate, max-age=0",
"p3p": "CP=\"This is not a P3P policy! See https://commons.wikimedia.org/wiki/Special:CentralAutoLogin/P3P for more info.\"",
"content-encoding": "gzip",
"x-frame-options": "DENY",
"content-disposition": "inline; filename=\"api-result.json\"",
"vary": "Accept-Encoding,Treat-as-Untrusted,X-Forwarded-Proto,Cookie,Authorization",
"backend-timing": "D=44564 t=1508390342727140",
"x-varnish": "288262213, 11547095, 566162766, 635099657",
"via": "1.1 varnish-v4, 1.1 varnish-v4, 1.1 varnish-v4, 1.1 varnish-v4",
"accept-ranges": "bytes",
"age": "0",
"x-cache": "cp1068 pass, cp2019 pass, cp4018 pass, cp4018 pass",
"x-cache-status": "pass",
"strict-transport-security": "max-age=106384710; includeSubDomains; preload",
"set-cookie": [
"WMF-Last-Access=19-Oct-2017;Path=/;HttpOnly;secure;Expires=Mon, 20 Nov 2017 00:00:00 GMT",
"GeoIP=US:WA:Seattle:47.61:-122.30:v4; Path=/; secure; Domain=.wikimedia.org"
],
"x-analytics": "ns=-1;special=Badtitle;https=1;nocookies=1",
"x-client-ip": "66.235.47.149"
},
"url": "http://commons.wikimedia.org:443/w/api.php?action=query&titles=File%3ANirvana_around_1992.jpg&prop=imageinfo&iiprop=url%7Csize%7Ccanonicaltitle%7Cuser%7Cextmetadata&format=json",
"time": 341,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "commons.wikimedia.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:40 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1065",
"x-ratelimit-reset": "1508394941",
"server": "Plack::Handler::Starlet",
"etag": "W/\"4cc0cf5456c65b053a648dab7d98aed1\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/643330aa-157a-49a2-91f1-973f958e0ddb?inc=url-rels&fmt=json",
"time": 351,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:18 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1063",
"x-ratelimit-reset": "1508394919",
"server": "Plack::Handler::Starlet",
"etag": "W/\"e74c7af5f57a0a9863635ba41bcfc213\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/069ec010-171a-433f-baaf-410d89ae8edc?inc=url-rels&fmt=json",
"time": 469,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

View file

@ -0,0 +1 @@
{"disambiguation":"","type-id":"cc00f97f-cf3d-3ae2-9163-041cb1a0d726","description":"","type":"String instrument","relations":[],"id":"8ecb065e-fa6a-4009-98bd-bd742307d0e8","name":"table steel guitar"}

View file

@ -0,0 +1,27 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:21:25 GMT",
"content-type": "application/json; charset=utf-8",
"content-length": "201",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "851",
"x-ratelimit-reset": "1508394085",
"server": "Plack::Handler::Starlet",
"etag": "\"2b6ff5fc3682605e4979b3eebccc7add\"",
"access-control-allow-origin": "*"
},
"url": "http://musicbrainz.org:80/ws/2/instrument/8ecb065e-fa6a-4009-98bd-bd742307d0e8?inc=url-rels&fmt=json",
"time": 424,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:45 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1147",
"x-ratelimit-reset": "1508394947",
"server": "Plack::Handler::Starlet",
"etag": "W/\"f11743900c546b3137d19347dbb672e1\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/8110eff2-51f0-42ba-9de5-7857576e9f6a?inc=url-rels&fmt=json",
"time": 369,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:45 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1151",
"x-ratelimit-reset": "1508394947",
"server": "Plack::Handler::Starlet",
"etag": "W/\"a7c64b03fdf2eeefe4fd599df5fe102b\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/b5f8b0b3-e7ee-4db2-a612-8b326ee28d1f?inc=url-rels&fmt=json",
"time": 367,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

View file

@ -0,0 +1,29 @@
{
"statusCode": 200,
"headers": {
"date": "Thu, 19 Oct 2017 06:35:23 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "1200",
"x-ratelimit-remaining": "1129",
"x-ratelimit-reset": "1508394925",
"server": "Plack::Handler::Starlet",
"etag": "W/\"5a0e28a18580315e9c99186aec796a62\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/label/40b9bd0d-e22f-41f8-885b-04bda967b3b9?inc=url-rels&fmt=json",
"time": 353,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/6.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more