Support browsing by Disc ID, ISRC, ISWC (#10)

* Browse queries for releases, recordings, and works now support
  browsing by Disc ID, ISRC, and ISWC, respectively
* Add `DiscID`, `ISRC`, and `ISWC` scalars
* Add missing `isrcs` field to `Recording`

Fixes #7.
This commit is contained in:
Brian Beck 2016-12-11 13:09:28 -08:00 committed by GitHub
parent 195e9fdb7c
commit 01b305dd50
16 changed files with 437 additions and 8 deletions

View file

@ -363,6 +363,10 @@ type BrowseQuery {
# The MBID of a release to which the entity is linked.
release: MBID
# The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC)
# (ISRC) of the recording.
isrc: ISRC
): RecordingConnection
# Browse release entities linked to the given arguments.
@ -400,6 +404,10 @@ type BrowseQuery {
# Filter by one or more release statuses.
status: [ReleaseStatus]
# A [disc ID](https://musicbrainz.org/doc/Disc_ID)
# associated with the release.
discID: DiscID
): ReleaseConnection
# Browse release group entities linked to the given arguments.
@ -430,6 +438,10 @@ type BrowseQuery {
# The MBID of a collection in which the entity is found.
collection: MBID
# The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC)
# (ISWC) of the work.
iswc: ISWC
): WorkConnection
}
@ -448,6 +460,21 @@ scalar Date
# Decimal degrees, used for latitude and longitude.
scalar Degrees
# [Disc ID](https://musicbrainz.org/doc/Disc_ID) is the code
# number which MusicBrainz uses to link a physical CD to a [release](https://musicbrainz.org/doc/Release)
# listing.
#
# A release may have any number of disc IDs, and a disc ID may be linked to
# multiple releases. This is because disc ID calculation involves a hash of the
# frame offsets of the CD tracks.
#
# Different pressing of a CD often have slightly different frame offsets, and
# hence different disc IDs.
#
# Conversely, two different CDs may happen to have exactly the same set of frame
# offsets and hence the same disc ID.
scalar DiscID
# An entity in the MusicBrainz schema.
interface Entity {
# The MBID of the entity.
@ -605,6 +632,23 @@ scalar IPI
# contributors to media content.
scalar ISNI
# The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC)
# (ISRC) is an identification system for audio and music video recordings. It is
# standarized by the [IFPI](http://www.ifpi.org/) in ISO 3901:2001 and used by
# IFPI members to assign a unique identifier to every distinct sound recording
# they release. An ISRC identifies a particular [sound recording](https://musicbrainz.org/doc/Recording),
# not the song itself. Therefore, different recordings, edits, remixes and
# remasters of the same song will each be assigned their own ISRC. However, note
# that same recording should carry the same ISRC in all countries/territories.
# Songs are identified by analogous [International Standard Musical Work Codes](https://musicbrainz.org/doc/ISWC)
# (ISWCs).
scalar ISRC
# The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC)
# (ISWC) is an ISO standard similar to ISBNs for identifying musical works /
# compositions.
scalar ISWC
# [Labels](https://musicbrainz.org/doc/Label) represent mostly
# (but not only) imprints. To a lesser extent, a label entity may be created to
# represent a record company.
@ -952,6 +996,10 @@ type Recording implements Node, Entity {
# The main credited artist(s).
artistCredits: [ArtistCredit]
# A list of [International Standard Recording Codes](https://musicbrainz.org/doc/ISRC)
# (ISRCs) for this recording.
isrcs: [ISRC]
# An approximation to the length of the recording, calculated
# from the lengths of the tracks using it.
length: Int

View file

@ -65,9 +65,12 @@ You may also be interested in reading the [schema in GraphQL syntax](schema.md).
<li>[Boolean](#boolean)</li>
<li>[Date](#date)</li>
<li>[Degrees](#degrees)</li>
<li>[DiscID](#discid)</li>
<li>[ID](#id)</li>
<li>[IPI](#ipi)</li>
<li>[ISNI](#isni)</li>
<li>[ISRC](#isrc)</li>
<li>[ISWC](#iswc)</li>
<li>[Int](#int)</li>
<li>[Locale](#locale)</li>
<li>[MBID](#mbid)</li>
@ -1024,6 +1027,12 @@ entity.
<td valign="top"><a href="#mbid">MBID</a></td>
<td>The MBID of a release to which the entity is linked.</td>
</tr>
<tr>
<td align="right" valign="top">isrc</td>
<td valign="top"><a href="#isrc">ISRC</a></td>
<td>The <a href="https://musicbrainz.org/doc/ISRC">International Standard Recording Code</a>
(ISRC) of the recording.</td>
</tr>
<tr>
<td valign="top"><strong>releases</strong> </td>
<td valign="top"><a href="#releaseconnection">ReleaseConnection</a></td>
@ -1092,6 +1101,12 @@ release, but is not included in the credits for the release itself.</td>
<td valign="top">[<a href="#releasestatus">ReleaseStatus</a>]</td>
<td>Filter by one or more release statuses.</td>
</tr>
<tr>
<td align="right" valign="top">discID</td>
<td valign="top"><a href="#discid">DiscID</a></td>
<td>A <a href="https://musicbrainz.org/doc/Disc_ID">disc ID</a>
associated with the release.</td>
</tr>
<tr>
<td valign="top"><strong>releaseGroups</strong> </td>
<td valign="top"><a href="#releasegroupconnection">ReleaseGroupConnection</a></td>
@ -1156,6 +1171,12 @@ release, but is not included in the credits for the release itself.</td>
<td valign="top"><a href="#mbid">MBID</a></td>
<td>The MBID of a collection in which the entity is found.</td>
</tr>
<tr>
<td align="right" valign="top">iswc</td>
<td valign="top"><a href="#iswc">ISWC</a></td>
<td>The <a href="https://musicbrainz.org/doc/ISWC">International Standard Musical Work Code</a>
(ISWC) of the work.</td>
</tr>
</tbody></table>
### Coordinates
@ -2305,6 +2326,14 @@ and will be removed in a major release in the future. Use the equivalent
The main credited artist(s).
</td>
</tr>
<tr>
<td valign="top"><strong>isrcs</strong> </td>
<td valign="top">[<a href="#isrc">ISRC</a>]</td>
<td>
A list of <a href="https://musicbrainz.org/doc/ISRC">International Standard Recording Codes</a>
(ISRCs) for this recording.
</td>
</tr>
<tr>
<td valign="top"><strong>length</strong> </td>
<td valign="top"><a href="#int">Int</a></td>
@ -4715,6 +4744,22 @@ Year, month (optional), and day (optional) in YYYY-MM-DD format.
Decimal degrees, used for latitude and longitude.
### DiscID
[Disc ID](https://musicbrainz.org/doc/Disc_ID) is the code
number which MusicBrainz uses to link a physical CD to a [release](https://musicbrainz.org/doc/Release)
listing.
A release may have any number of disc IDs, and a disc ID may be linked to
multiple releases. This is because disc ID calculation involves a hash of the
frame offsets of the CD tracks.
Different pressing of a CD often have slightly different frame offsets, and
hence different disc IDs.
Conversely, two different CDs may happen to have exactly the same set of frame
offsets and hence the same disc ID.
### ID
The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.
@ -4731,6 +4776,25 @@ The [International Standard Name Identifier](https://musicbrainz.org/doc/ISNI)
(ISNI) is an ISO standard for uniquely identifying the public identities of
contributors to media content.
### ISRC
The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC)
(ISRC) is an identification system for audio and music video recordings. It is
standarized by the [IFPI](http://www.ifpi.org/) in ISO 3901:2001 and used by
IFPI members to assign a unique identifier to every distinct sound recording
they release. An ISRC identifies a particular [sound recording](https://musicbrainz.org/doc/Recording),
not the song itself. Therefore, different recordings, edits, remixes and
remasters of the same song will each be assigned their own ISRC. However, note
that same recording should carry the same ISRC in all countries/territories.
Songs are identified by analogous [International Standard Musical Work Codes](https://musicbrainz.org/doc/ISWC)
(ISWCs).
### ISWC
The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC)
(ISWC) is an ISO standard similar to ISBNs for identifying musical works /
compositions.
### Int
The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.

View file

@ -2031,6 +2031,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "isrcs",
"description": "A list of [International Standard Recording Codes](https://musicbrainz.org/doc/ISRC)\n(ISRCs) for this recording.",
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ISRC",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "length",
"description": "An approximation to the length of the recording, calculated\nfrom the lengths of the tracks using it.",
@ -2258,6 +2274,16 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "ISRC",
"description": "The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC)\n(ISRC) is an identification system for audio and music video recordings. It is\nstandarized by the [IFPI](http://www.ifpi.org/) in ISO 3901:2001 and used by\nIFPI members to assign a unique identifier to every distinct sound recording\nthey release. An ISRC identifies a particular [sound recording](https://musicbrainz.org/doc/Recording),\nnot the song itself. Therefore, different recordings, edits, remixes and\nremasters of the same song will each be assigned their own ISRC. However, note\nthat same recording should carry the same ISRC in all countries/territories.\nSongs are identified by analogous [International Standard Musical Work Codes](https://musicbrainz.org/doc/ISWC)\n(ISWCs).",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "ReleaseGroupType",
@ -7103,6 +7129,16 @@
"ofType": null
},
"defaultValue": null
},
{
"name": "isrc",
"description": "The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC)\n(ISRC) of the recording.",
"type": {
"kind": "SCALAR",
"name": "ISRC",
"ofType": null
},
"defaultValue": null
}
],
"type": {
@ -7244,6 +7280,16 @@
}
},
"defaultValue": null
},
{
"name": "discID",
"description": "A [disc ID](https://musicbrainz.org/doc/Disc_ID)\nassociated with the release.",
"type": {
"kind": "SCALAR",
"name": "DiscID",
"ofType": null
},
"defaultValue": null
}
],
"type": {
@ -7374,6 +7420,16 @@
"ofType": null
},
"defaultValue": null
},
{
"name": "iswc",
"description": "The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC)\n(ISWC) of the work.",
"type": {
"kind": "SCALAR",
"name": "ISWC",
"ofType": null
},
"defaultValue": null
}
],
"type": {
@ -7496,6 +7552,26 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "DiscID",
"description": "[Disc ID](https://musicbrainz.org/doc/Disc_ID) is the code\nnumber which MusicBrainz uses to link a physical CD to a [release](https://musicbrainz.org/doc/Release)\nlisting.\n\nA release may have any number of disc IDs, and a disc ID may be linked to\nmultiple releases. This is because disc ID calculation involves a hash of the\nframe offsets of the CD tracks.\n\nDifferent pressing of a CD often have slightly different frame offsets, and\nhence different disc IDs.\n\nConversely, two different CDs may happen to have exactly the same set of frame\noffsets and hence the same disc ID.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "ISWC",
"description": "The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC)\n(ISWC) is an ISO standard similar to ISBNs for identifying musical works /\ncompositions.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SearchQuery",

View file

@ -28,6 +28,22 @@ export default function createLoaders (client) {
// is elsewhere in the code.
entity._type = entityType
entity._inc = params.inc
if (entityType === 'discid' && entity.releases) {
entity.releases.forEach(release => {
release._type = 'release'
release._inc = params.inc
})
} else if (entityType === 'isrc' && entity.recordings) {
entity.recordings.forEach(recording => {
recording._type = 'recording'
recording._inc = params.inc
})
} else if (entityType === 'iswc' && entity.works) {
entity.works.forEach(work => {
work._type = 'work'
work._inc = params.inc
})
}
}
return entity
})

View file

@ -6,6 +6,9 @@ import {
AreaConnection,
ArtistConnection,
EventConnection,
DiscID,
ISRC,
ISWC,
LabelConnection,
PlaceConnection,
RecordingConnection,
@ -93,7 +96,12 @@ entity.`,
recordings: createBrowseField(RecordingConnection, {
artist,
collection,
release
release,
isrc: {
type: ISRC,
description: `The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC)
(ISRC) of the recording.`
}
}),
releases: createBrowseField(ReleaseConnection, {
area,
@ -115,7 +123,12 @@ release, but is not included in the credits for the release itself.`
recording,
releaseGroup,
type: releaseGroupType,
status: releaseStatus
status: releaseStatus,
discID: {
type: DiscID,
description: `A [disc ID](https://musicbrainz.org/doc/Disc_ID)
associated with the release.`
}
}),
releaseGroups: createBrowseField(ReleaseGroupConnection, {
artist,
@ -125,7 +138,12 @@ release, but is not included in the credits for the release itself.`
}),
works: createBrowseField(WorkConnection, {
artist,
collection
collection,
iswc: {
type: ISWC,
description: `The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC)
(ISWC) of the work.`
}
})
}
})

View file

@ -41,6 +41,7 @@ export function includeSubqueries (params, info, fragments = info.fragments) {
aliases: 'aliases',
artistCredit: 'artist-credits',
artistCredits: 'artist-credits',
isrcs: 'isrcs',
tags: 'tags'
}
let fields = getFields(info, fragments)
@ -79,6 +80,9 @@ export function resolveBrowse (root, {
after,
type = [],
status = [],
discID,
isrc,
iswc,
...args
}, { loaders }, info) {
const pluralName = toDashed(info.fieldName)
@ -95,13 +99,23 @@ export function resolveBrowse (root, {
const formatParam = value => value.toLowerCase().replace(/ /g, '')
params.type = params.type.map(formatParam)
params.status = params.status.map(formatParam)
return loaders.browse.load([singularName, params]).then(list => {
let request
if (discID) {
request = loaders.lookup.load(['discid', discID, params])
} else if (isrc) {
request = loaders.lookup.load(['isrc', isrc, params])
} else if (iswc) {
request = loaders.lookup.load(['iswc', iswc, params])
} else {
request = loaders.browse.load([singularName, params])
}
return request.then(list => {
// Grab the list, offet, and count from the response and use them to build
// a Relay connection object.
const {
[pluralName]: arraySlice,
[`${singularName}-offset`]: sliceStart,
[`${singularName}-count`]: arrayLength
[`${singularName}-offset`]: sliceStart = 0,
[`${singularName}-count`]: arrayLength = arraySlice.length
} = list
const meta = { sliceStart, arrayLength }
return {

View file

@ -1,4 +1,4 @@
export { MBID, DateType, IPI, URLString } from './scalars'
export { DateType, DiscID, IPI, ISRC, ISWC, MBID, URLString } from './scalars'
export { ReleaseGroupType, ReleaseStatus } from './enums'
export { default as Node } from './node'
export { default as Entity, EntityConnection } from './entity'

View file

@ -1,6 +1,12 @@
import { GraphQLObjectType, GraphQLInt, GraphQLBoolean } from 'graphql/type'
import {
GraphQLObjectType,
GraphQLList,
GraphQLInt,
GraphQLBoolean
} from 'graphql/type'
import Node from './node'
import Entity from './entity'
import { ISRC } from './scalars'
import {
id,
mbid,
@ -39,6 +45,11 @@ or mixing.`,
aliases,
artistCredit,
artistCredits,
isrcs: {
type: new GraphQLList(ISRC),
description: `A list of [International Standard Recording Codes](https://musicbrainz.org/doc/ISRC)
(ISRCs) for this recording.`
},
length: {
type: GraphQLInt,
description: `An approximation to the length of the recording, calculated

View file

@ -65,6 +65,23 @@ export const Degrees = createScalar({
description: 'Decimal degrees, used for latitude and longitude.'
})
export const DiscID = createScalar({
name: 'DiscID',
description: `[Disc ID](https://musicbrainz.org/doc/Disc_ID) is the code
number which MusicBrainz uses to link a physical CD to a [release](https://musicbrainz.org/doc/Release)
listing.
A release may have any number of disc IDs, and a disc ID may be linked to
multiple releases. This is because disc ID calculation involves a hash of the
frame offsets of the CD tracks.
Different pressing of a CD often have slightly different frame offsets, and
hence different disc IDs.
Conversely, two different CDs may happen to have exactly the same set of frame
offsets and hence the same disc ID.`
})
export const Duration = createScalar({
name: 'Duration',
description: 'A length of time, in milliseconds.',
@ -92,6 +109,20 @@ export const ISNI = createScalar({
contributors to media content.`
})
export const ISRC = createScalar({
name: 'ISRC',
description: `The [International Standard Recording Code](https://musicbrainz.org/doc/ISRC)
(ISRC) is an identification system for audio and music video recordings. It is
standarized by the [IFPI](http://www.ifpi.org/) in ISO 3901:2001 and used by
IFPI members to assign a unique identifier to every distinct sound recording
they release. An ISRC identifies a particular [sound recording](https://musicbrainz.org/doc/Recording),
not the song itself. Therefore, different recordings, edits, remixes and
remasters of the same song will each be assigned their own ISRC. However, note
that same recording should carry the same ISRC in all countries/territories.
Songs are identified by analogous [International Standard Musical Work Codes](https://musicbrainz.org/doc/ISWC)
(ISWCs).`
})
export const ISWC = createScalar({
name: 'ISWC',
description: `The [International Standard Musical Work Code](https://musicbrainz.org/doc/ISWC)

View file

@ -0,0 +1 @@
{"isrc":"USSUB0200002","recordings":[{"length":168293,"disambiguation":"","id":"b2e623ea-378b-4479-bb75-fa202aacbfc9","video":false,"title":"About a Girl","isrcs":["USSUB0200002","USUG10200084"]}]}

View file

@ -0,0 +1,27 @@
{
"statusCode": 200,
"headers": {
"date": "Sun, 11 Dec 2016 20:55:38 GMT",
"content-type": "application/json; charset=utf-8",
"content-length": "197",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"x-ratelimit-limit": "700",
"x-ratelimit-remaining": "181",
"x-ratelimit-reset": "1481489738",
"server": "Plack::Handler::Starlet",
"etag": "\"a025a168fc0d9a2e49b1a5078e300152\"",
"access-control-allow-origin": "*"
},
"url": "http://musicbrainz.org:80/ws/2/isrc/USSUB0200002?inc=isrcs&fmt=json",
"time": 403,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/4.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": "Sun, 11 Dec 2016 20:55:38 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "700",
"x-ratelimit-remaining": "177",
"x-ratelimit-reset": "1481489738",
"server": "Plack::Handler::Starlet",
"etag": "W/\"c8f35ec6a4e8478ac6c17f79ef348cf3\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/iswc/T-900.755.682-3?fmt=json",
"time": 397,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/4.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": "Sun, 11 Dec 2016 20:55:38 GMT",
"content-type": "application/json; charset=utf-8",
"transfer-encoding": "chunked",
"connection": "keep-alive",
"keep-alive": "timeout=15",
"vary": "Accept-Encoding",
"x-ratelimit-limit": "700",
"x-ratelimit-remaining": "178",
"x-ratelimit-reset": "1481489738",
"server": "Plack::Handler::Starlet",
"etag": "W/\"8ee2a57a26dbd226aa04be7142f855b9\"",
"access-control-allow-origin": "*",
"content-encoding": "gzip"
},
"url": "http://musicbrainz.org:80/ws/2/discid/XzPS7vW.HPHsYemQh0HBUGr8vuU-?fmt=json",
"time": 428,
"request": {
"method": "GET",
"headers": {
"User-Agent": "graphbrainz/4.1.0 ( https://github.com/exogen/graphbrainz )",
"host": "musicbrainz.org",
"accept-encoding": "gzip, deflate",
"accept": "application/json"
}
}
}

View file

@ -514,3 +514,68 @@ test('artistCredits is an alias for artistCredit', testData, `
])
t.deepEqual(releaseGroup.artistCredits, releaseGroup.artistCredit)
})
test('Recordings can be browsed by isrc', testData, `
{
browse {
recordings(isrc: "USSUB0200002") {
totalCount
edges {
node {
title
isrcs
}
}
}
}
}
`, (t, data) => {
const recordings = data.browse.recordings.edges.map(edge => edge.node)
t.is(data.browse.recordings.totalCount, 1)
t.deepEqual(recordings, [
{ title: 'About a Girl', isrcs: ['USSUB0200002', 'USUG10200084'] }
])
})
test('Releases can be browsed by discID', testData, `
{
browse {
releases(discID: "XzPS7vW.HPHsYemQh0HBUGr8vuU-") {
totalCount
edges {
node {
mbid
title
}
}
}
}
}
`, (t, data) => {
const releases = data.browse.releases.edges.map(edge => edge.node)
t.true(data.browse.releases.totalCount >= 2)
t.true(releases.some(release => release.mbid === '5a6e5ad7-c2bd-3484-a20e-121bf981c883'))
t.true(releases.some(release => release.mbid === '96f6f90e-d831-4f37-bf72-ce2982e459fb'))
})
test('Works can be browsed by iswc', testData, `
{
browse {
works(iswc: "T-900.755.682-3") {
totalCount
edges {
node {
title
iswcs
}
}
}
}
}
`, (t, data) => {
const works = data.browse.works.edges.map(edge => edge.node)
t.is(data.browse.works.totalCount, 1)
t.deepEqual(works, [
{ title: 'Song of the French Partisan', iswcs: ['T-900.755.682-3'] }
])
})