2012-03-28 22:16:10 +00:00
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MusicBrainz Import helper functions
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/ *
2015-01-18 12:34:10 +00:00
* How to use this module ?
*
* - First build a release object ( see expected format below ) that you ' ll fill in from source of data
* - Call as follows , e . g . :
2015-06-17 22:29:08 +00:00
* var parameters = MBImport . buildFormParameters ( parsedRelease , optionalEditNote ) ;
2015-01-18 12:34:10 +00:00
* - Then build the HTML that you ' ll inject into source site page :
2015-06-17 22:29:08 +00:00
* var formHtml = MBImport . buildFormHTML ( parameters ) ;
2015-01-18 12:34:10 +00:00
* - Addinionally , you can inject a search link to verify that the release is not already known by MusicBrainz :
2015-06-17 22:29:08 +00:00
* var linkHtml = MBImport . buildSearchLink ( parsedRelease ) ;
2015-01-18 12:34:10 +00:00
*
* Expected format of release object :
*
* release = {
* title ,
* artist _credit ,
* type ,
* status ,
2020-11-26 11:50:17 +00:00
* secondary _types ,
2015-01-18 12:34:10 +00:00
* language ,
* script ,
* packaging ,
* country ,
* year ,
* month ,
* day ,
* labels = [ { name , mbid , catno } , ... ] ,
* barcode ,
2018-11-28 15:45:37 +00:00
* comment ,
2020-11-26 11:50:17 +00:00
* annotation ,
2015-01-18 12:34:10 +00:00
* urls = [ { url , link _type } , ... ] ,
* discs = [
* {
* title ,
* format ,
* tracks = [
* { number , title , duration , artist _credit } ,
* ...
* ]
* } ,
* ...
* ] ,
* }
*
* where 'artist_credit' has the following format :
*
* artist _credit = [
* {
* credited _name ,
* artist _name ,
* artist _mbid ,
* joinphrase
* } ,
* ...
* ]
*
* /
2012-03-28 22:16:10 +00:00
2020-04-05 14:01:21 +00:00
var MBImport = ( function ( ) {
2012-03-28 22:16:10 +00:00
// --------------------------------------- publics ----------------------------------------- //
2018-11-20 22:18:49 +00:00
let special _artists = {
various _artists : {
name : 'Various Artists' ,
2020-04-05 14:01:21 +00:00
mbid : '89ad4ac3-39f7-470e-963a-56509c546377' ,
2018-11-20 22:18:49 +00:00
} ,
unknown : {
name : '[unknown]' ,
2020-04-05 14:01:21 +00:00
mbid : '125ec42a-7229-4250-afc5-e057484327fe' ,
} ,
2015-06-21 14:26:45 +00:00
} ;
2018-11-20 22:18:49 +00:00
let url _types = {
purchase _for _download : 74 ,
download _for _free : 75 ,
discogs : 76 ,
purchase _for _mail _order : 79 ,
other _databases : 82 ,
stream _for _free : 85 ,
2020-04-05 14:01:21 +00:00
license : 301 ,
2018-11-20 22:18:49 +00:00
} ;
2015-06-12 19:28:24 +00:00
2015-06-21 14:26:45 +00:00
function fnSpecialArtist ( key , ac ) {
2018-11-20 22:18:49 +00:00
let credited _name = '' ;
let joinphrase = '' ;
if ( typeof ac !== 'undefined' ) {
joinphrase = ac . joinphrase ;
}
return {
artist _name : special _artists [ key ] . name ,
credited _name : credited _name ,
joinphrase : joinphrase ,
2020-04-05 14:01:21 +00:00
mbid : special _artists [ key ] . mbid ,
2018-11-20 22:18:49 +00:00
} ;
2015-06-21 14:26:45 +00:00
}
2012-03-28 22:16:10 +00:00
// compute HTML of search link
2014-02-21 17:42:25 +00:00
function fnBuildSearchLink ( release ) {
2018-11-20 22:18:49 +00:00
let parameters = searchParams ( release ) ;
let url _params = [ ] ;
2020-04-05 14:01:21 +00:00
parameters . forEach ( function ( parameter ) {
2018-11-20 22:18:49 +00:00
let value = ` ${ parameter . value } ` ;
url _params . push ( encodeURI ( ` ${ parameter . name } = ${ value } ` ) ) ;
2015-06-16 13:34:02 +00:00
} ) ;
2018-11-20 22:18:49 +00:00
return ` <a class="musicbrainz_import" href="//musicbrainz.org/search? ${ url _params . join ( '&' ) } ">Search in MusicBrainz</a> ` ;
2015-06-16 13:34:02 +00:00
}
2012-03-28 22:16:10 +00:00
2015-06-16 13:34:02 +00:00
// compute HTML of search button
function fnBuildSearchButton ( release ) {
2018-11-20 22:18:49 +00:00
let parameters = searchParams ( release ) ;
2020-03-14 00:33:36 +00:00
let html = ` <form class="musicbrainz_import musicbrainz_import_search" action="//musicbrainz.org/search" method="get" target="_blank" accept-charset="UTF-8" charset=" ${ document . characterSet } "> ` ;
2020-04-05 14:01:21 +00:00
parameters . forEach ( function ( parameter ) {
2018-11-20 22:18:49 +00:00
let value = ` ${ parameter . value } ` ;
html += ` <input type='hidden' value=' ${ value . replace ( /'/g , ''' ) } ' name=' ${ parameter . name } '/> ` ;
2015-06-16 13:34:02 +00:00
} ) ;
html += '<button type="submit" title="Search for this release in MusicBrainz (open a new tab)"><span>Search in MB</span></button>' ;
html += '</form>' ;
return html ;
2012-03-28 22:16:10 +00:00
}
2015-06-14 19:57:57 +00:00
function fnSearchUrlFor ( type , what ) {
2018-11-20 22:18:49 +00:00
type = type . replace ( '-' , '_' ) ;
let params = [ ` query= ${ luceneEscape ( what ) } ` , ` type= ${ type } ` , 'indexed=1' ] ;
return ` //musicbrainz.org/search? ${ params . join ( '&' ) } ` ;
2015-06-14 19:57:57 +00:00
}
2012-03-28 22:16:10 +00:00
// compute HTML of import form
2014-02-21 17:42:25 +00:00
function fnBuildFormHTML ( parameters ) {
2014-02-21 17:46:29 +00:00
// Build form
2020-03-14 00:33:36 +00:00
let innerHTML = ` <form class="musicbrainz_import musicbrainz_import_add" action="//musicbrainz.org/release/add" method="post" target="_blank" accept-charset="UTF-8" charset=" ${ document . characterSet } "> ` ;
2020-04-05 14:01:21 +00:00
parameters . forEach ( function ( parameter ) {
2018-11-20 22:18:49 +00:00
let value = ` ${ parameter . value } ` ;
innerHTML += ` <input type='hidden' value=' ${ value . replace ( /'/g , ''' ) } ' name=' ${ parameter . name } '/> ` ;
2014-02-21 17:46:29 +00:00
} ) ;
2012-03-28 22:16:10 +00:00
2018-11-20 22:18:49 +00:00
innerHTML +=
'<button type="submit" title="Import this release into MusicBrainz (open a new tab)"><img src="//musicbrainz.org/favicon.ico" /><span>Import into MB</span></button>' ;
2014-02-21 17:46:29 +00:00
innerHTML += '</form>' ;
2012-03-28 22:16:10 +00:00
return innerHTML ;
}
// build form POST parameters that MB is waiting
2014-02-21 17:42:25 +00:00
function fnBuildFormParameters ( release , edit _note ) {
2014-02-21 17:46:29 +00:00
// Form parameters
2018-11-20 22:18:49 +00:00
let parameters = new Array ( ) ;
2014-02-21 17:46:29 +00:00
appendParameter ( parameters , 'name' , release . title ) ;
2012-03-28 22:16:10 +00:00
2014-02-21 17:46:29 +00:00
// Release Artist credits
2018-11-20 22:18:49 +00:00
buildArtistCreditsFormParameters ( parameters , '' , release . artist _credit ) ;
2012-03-28 22:16:10 +00:00
2018-11-20 22:18:49 +00:00
if ( release [ 'secondary_types' ] ) {
2019-03-18 02:57:37 +00:00
for ( let i = 0 ; i < release . secondary _types . length ; i ++ ) {
2014-08-14 23:55:30 +00:00
appendParameter ( parameters , 'type' , release . secondary _types [ i ] ) ;
}
}
2014-02-21 17:46:29 +00:00
appendParameter ( parameters , 'status' , release . status ) ;
appendParameter ( parameters , 'language' , release . language ) ;
appendParameter ( parameters , 'script' , release . script ) ;
appendParameter ( parameters , 'packaging' , release . packaging ) ;
2012-03-28 22:16:10 +00:00
2015-06-07 19:48:00 +00:00
// ReleaseGroup
appendParameter ( parameters , 'release_group' , release . release _group _mbid ) ;
2014-02-21 17:46:29 +00:00
// Date + country
appendParameter ( parameters , 'country' , release . country ) ;
2018-11-20 22:18:49 +00:00
if ( ! isNaN ( release . year ) && release . year != 0 ) {
appendParameter ( parameters , 'date.year' , release . year ) ;
}
if ( ! isNaN ( release . month ) && release . month != 0 ) {
appendParameter ( parameters , 'date.month' , release . month ) ;
}
if ( ! isNaN ( release . day ) && release . day != 0 ) {
appendParameter ( parameters , 'date.day' , release . day ) ;
}
2012-03-28 22:16:10 +00:00
// Barcode
2014-02-21 18:21:53 +00:00
appendParameter ( parameters , 'barcode' , release . barcode ) ;
2012-03-28 22:16:10 +00:00
2018-11-28 15:45:37 +00:00
// Disambiguation comment
appendParameter ( parameters , 'comment' , release . comment ) ;
2020-11-19 16:24:21 +00:00
// Annotation
appendParameter ( parameters , 'annotation' , release . annotation ) ;
2014-02-21 17:46:29 +00:00
// Label + catnos
2019-03-18 02:57:37 +00:00
for ( let i = 0 ; i < release . labels . length ; i ++ ) {
2018-11-20 22:18:49 +00:00
let label = release . labels [ i ] ;
appendParameter ( parameters , ` labels. ${ i } .name ` , label . name ) ;
appendParameter ( parameters , ` labels. ${ i } .mbid ` , label . mbid ) ;
if ( label . catno != 'none' ) {
appendParameter ( parameters , ` labels. ${ i } .catalog_number ` , label . catno ) ;
2012-03-28 22:16:10 +00:00
}
}
2014-02-21 17:42:25 +00:00
2014-02-21 19:04:38 +00:00
// URLs
2020-03-14 00:43:40 +00:00
for ( let i = 0 ; i < release . urls . length ; i ++ ) {
2018-11-20 22:18:49 +00:00
let url = release . urls [ i ] ;
appendParameter ( parameters , ` urls. ${ i } .url ` , url . url ) ;
appendParameter ( parameters , ` urls. ${ i } .link_type ` , url . link _type ) ;
2014-02-21 19:04:38 +00:00
}
2014-02-21 17:46:29 +00:00
// Mediums
2018-11-20 22:18:49 +00:00
let total _tracks = 0 ;
let total _tracks _with _duration = 0 ;
let total _duration = 0 ;
2019-03-18 02:57:37 +00:00
for ( let i = 0 ; i < release . discs . length ; i ++ ) {
2018-11-20 22:18:49 +00:00
let disc = release . discs [ i ] ;
appendParameter ( parameters , ` mediums. ${ i } .format ` , disc . format ) ;
appendParameter ( parameters , ` mediums. ${ i } .name ` , disc . title ) ;
2014-02-21 17:46:29 +00:00
// Tracks
2018-11-20 22:18:49 +00:00
for ( let j = 0 ; j < disc . tracks . length ; j ++ ) {
let track = disc . tracks [ j ] ;
2015-06-13 02:25:31 +00:00
total _tracks ++ ;
2018-11-20 22:18:49 +00:00
appendParameter ( parameters , ` mediums. ${ i } .track. ${ j } .number ` , track . number ) ;
appendParameter ( parameters , ` mediums. ${ i } .track. ${ j } .name ` , track . title ) ;
let tracklength = '?:??' ;
let duration _ms = hmsToMilliSeconds ( track . duration ) ;
2015-06-13 02:25:31 +00:00
if ( ! isNaN ( duration _ms ) ) {
2018-11-20 22:18:49 +00:00
tracklength = duration _ms ;
total _tracks _with _duration ++ ;
total _duration += duration _ms ;
2015-06-13 02:25:31 +00:00
}
2018-11-20 22:18:49 +00:00
appendParameter ( parameters , ` mediums. ${ i } .track. ${ j } .length ` , tracklength ) ;
2020-04-23 14:59:49 +00:00
appendParameter ( parameters , ` mediums. ${ i } .track. ${ j } .recording ` , track . recording ) ;
2018-11-20 22:18:49 +00:00
buildArtistCreditsFormParameters ( parameters , ` mediums. ${ i } .track. ${ j } . ` , track . artist _credit ) ;
2014-02-21 17:46:29 +00:00
}
}
2012-03-28 22:16:10 +00:00
2015-06-13 02:25:31 +00:00
// Guess release type if not given
if ( ! release . type && release . title && total _tracks == total _tracks _with _duration ) {
2018-11-20 22:18:49 +00:00
release . type = fnGuessReleaseType ( release . title , total _tracks , total _duration ) ;
2015-06-13 02:25:31 +00:00
}
appendParameter ( parameters , 'type' , release . type ) ;
2014-02-21 17:46:29 +00:00
// Add Edit note parameter
appendParameter ( parameters , 'edit_note' , edit _note ) ;
2014-02-21 17:42:25 +00:00
2014-02-21 17:46:29 +00:00
return parameters ;
2012-03-28 22:16:10 +00:00
}
2015-06-12 20:01:06 +00:00
// Convert a list of artists to a list of artist credits with joinphrases
function fnArtistCredits ( artists _list ) {
2020-04-05 14:01:21 +00:00
let artists = artists _list . map ( function ( item ) {
2018-11-20 22:18:49 +00:00
return { artist _name : item } ;
} ) ;
if ( artists . length > 2 ) {
let last = artists . pop ( ) ;
last . joinphrase = '' ;
let prev = artists . pop ( ) ;
prev . joinphrase = ' & ' ;
for ( let i = 0 ; i < artists . length ; i ++ ) {
artists [ i ] . joinphrase = ', ' ;
}
artists . push ( prev ) ;
artists . push ( last ) ;
} else if ( artists . length == 2 ) {
artists [ 0 ] . joinphrase = ' & ' ;
2015-06-12 20:01:06 +00:00
}
2018-11-20 22:18:49 +00:00
let credits = [ ] ;
// re-split artists if featuring or vs
2020-04-05 14:01:21 +00:00
artists . map ( function ( item ) {
2018-11-20 22:18:49 +00:00
let c = item . artist _name . replace ( /\s*\b(?:feat\.?|ft\.?|featuring)\s+/gi , ' feat. ' ) ;
c = c . replace ( /\s*\(( feat. )([^\)]+)\)/g , '$1$2' ) ;
c = c . replace ( /\s*\b(?:versus|vs\.?)\s+/gi , ' vs. ' ) ;
c = c . replace ( /\s+/g , ' ' ) ;
let splitted = c . split ( /( feat\. | vs\. )/ ) ;
if ( splitted . length == 1 ) {
credits . push ( item ) ; // nothing to split
2015-06-13 13:18:04 +00:00
} else {
2018-11-20 22:18:49 +00:00
let new _items = [ ] ;
let n = 0 ;
for ( let i = 0 ; i < splitted . length ; i ++ ) {
if ( n && ( splitted [ i ] == ' feat. ' || splitted [ i ] == ' vs. ' ) ) {
new _items [ n - 1 ] . joinphrase = splitted [ i ] ;
} else {
new _items [ n ++ ] = {
artist _name : splitted [ i ] . trim ( ) ,
2020-04-05 14:01:21 +00:00
joinphrase : '' ,
2018-11-20 22:18:49 +00:00
} ;
}
}
new _items [ n - 1 ] . joinphrase = item . joinphrase ;
2020-04-05 14:01:21 +00:00
new _items . map ( function ( newit ) {
2018-11-20 22:18:49 +00:00
credits . push ( newit ) ;
} ) ;
2015-06-13 13:18:04 +00:00
}
2018-11-20 22:18:49 +00:00
} ) ;
return credits ;
2015-06-12 20:01:06 +00:00
}
2015-06-13 02:25:31 +00:00
// Try to guess release type using number of tracks, title and total duration (in millisecs)
function fnGuessReleaseType ( title , num _tracks , duration _ms ) {
2018-11-20 22:18:49 +00:00
if ( num _tracks < 1 ) return '' ;
let has _single = ! ! title . match ( /\bsingle\b/i ) ;
let has _EP = ! ! title . match ( /\bEP\b/i ) ;
if ( has _single && has _EP ) {
has _single = false ;
has _EP = false ;
}
let perhaps _single = ( has _single && num _tracks <= 4 ) || num _tracks <= 2 ;
let perhaps _EP = has _EP || ( num _tracks > 2 && num _tracks <= 6 ) ;
let perhaps _album = num _tracks > 8 ;
if ( isNaN ( duration _ms ) ) {
// no duration, try to guess with title and number of tracks
if ( perhaps _single && ! perhaps _EP && ! perhaps _album ) return 'single' ;
if ( ! perhaps _single && perhaps _EP && ! perhaps _album ) return 'EP' ;
if ( ! perhaps _single && ! perhaps _EP && perhaps _album ) return 'album' ;
return '' ;
}
let duration _mn = duration _ms / ( 60 * 1000 ) ;
if ( perhaps _single && duration _mn >= 1 && duration _mn < 7 ) return 'single' ;
if ( perhaps _EP && duration _mn > 7 && duration _mn <= 30 ) return 'EP' ;
if ( perhaps _album && duration _mn > 30 ) return 'album' ;
2015-06-13 02:25:31 +00:00
return '' ;
2015-06-12 20:40:40 +00:00
}
2015-06-13 02:25:31 +00:00
// convert HH:MM:SS or MM:SS to milliseconds
function hmsToMilliSeconds ( str ) {
2019-01-05 14:01:21 +00:00
/* eslint-disable-next-line use-isnan */
2018-12-09 19:19:52 +00:00
if ( typeof str == 'undefined' || str === null || str === NaN || str === '' ) return NaN ;
2015-06-13 02:25:31 +00:00
if ( typeof str == 'number' ) return str ;
2018-11-20 22:18:49 +00:00
let t = str . split ( ':' ) ;
let s = 0 ;
let m = 1 ;
2015-06-13 02:25:31 +00:00
while ( t . length > 0 ) {
s += m * parseInt ( t . pop ( ) , 10 ) ;
m *= 60 ;
}
2018-11-20 22:18:49 +00:00
return s * 1000 ;
2015-06-13 02:25:31 +00:00
}
2015-06-15 11:59:09 +00:00
// convert ISO8601 duration (limited to hours/minutes/seconds) to milliseconds
// format looks like PT1H45M5.789S (note: floats can be used)
// https://en.wikipedia.org/wiki/ISO_8601#Durations
function fnISO8601toMilliSeconds ( str ) {
2018-11-20 22:18:49 +00:00
let regex = /^PT(?:(\d*\.?\d*)H)?(?:(\d*\.?\d*)M)?(?:(\d*\.?\d*)S)?$/ ,
2015-06-15 11:59:09 +00:00
m = str . replace ( ',' , '.' ) . match ( regex ) ;
if ( ! m ) return NaN ;
return ( 3600 * parseFloat ( m [ 1 ] || 0 ) + 60 * parseFloat ( m [ 2 ] || 0 ) + parseFloat ( m [ 3 ] || 0 ) ) * 1000 ;
}
2019-03-18 02:51:58 +00:00
function fnMakeEditNote ( release _url , importer _name , format , home = 'https://github.com/murdos/musicbrainz-userscripts' ) {
2018-11-20 22:18:49 +00:00
return ` Imported from ${ release _url } ${ format ? ` ( ${ format } ) ` : '' } using ${ importer _name } import script from ${ home } ` ;
2015-06-17 09:51:51 +00:00
}
2012-03-28 22:16:10 +00:00
// --------------------------------------- privates ----------------------------------------- //
function appendParameter ( parameters , paramName , paramValue ) {
2018-11-20 22:18:49 +00:00
if ( ! paramValue ) return ;
parameters . push ( { name : paramName , value : paramValue } ) ;
2012-03-28 22:16:10 +00:00
}
function luceneEscape ( text ) {
2018-11-20 22:18:49 +00:00
let newtext = text . replace ( /[-[\]{}()*+?~:\\^!"\/]/g , '\\$&' ) ;
return newtext . replace ( '&&' , '&&' ) . replace ( '||' , '||' ) ;
2012-03-28 22:16:10 +00:00
}
function buildArtistCreditsFormParameters ( parameters , paramPrefix , artist _credit ) {
2018-11-20 22:18:49 +00:00
if ( ! artist _credit ) return ;
for ( let i = 0 ; i < artist _credit . length ; i ++ ) {
let ac = artist _credit [ i ] ;
appendParameter ( parameters , ` ${ paramPrefix } artist_credit.names. ${ i } .name ` , ac . credited _name ) ;
appendParameter ( parameters , ` ${ paramPrefix } artist_credit.names. ${ i } .artist.name ` , ac . artist _name ) ;
appendParameter ( parameters , ` ${ paramPrefix } artist_credit.names. ${ i } .mbid ` , ac . mbid ) ;
if ( typeof ac . joinphrase != 'undefined' && ac . joinphrase != '' ) {
appendParameter ( parameters , ` ${ paramPrefix } artist_credit.names. ${ i } .join_phrase ` , ac . joinphrase ) ;
2012-03-28 22:16:10 +00:00
}
}
}
2015-06-16 13:34:02 +00:00
function searchParams ( release ) {
2018-11-20 22:18:49 +00:00
let params = [ ] ;
2015-06-16 13:34:02 +00:00
2020-03-29 12:14:08 +00:00
const totaltracks = release . discs . reduce ( ( acc , { tracks } ) => acc + tracks . length , 0 ) ;
2018-11-20 22:18:49 +00:00
let release _artist = '' ;
2019-03-18 02:57:37 +00:00
for ( let i = 0 ; i < release . artist _credit . length ; i ++ ) {
2018-11-20 22:18:49 +00:00
let ac = release . artist _credit [ i ] ;
2015-06-16 13:34:02 +00:00
release _artist += ac . artist _name ;
2018-11-20 22:18:49 +00:00
if ( typeof ac . joinphrase != 'undefined' && ac . joinphrase != '' ) {
2015-06-16 13:34:02 +00:00
release _artist += ac . joinphrase ;
} else {
2018-11-20 22:18:49 +00:00
if ( i != release . artist _credit . length - 1 ) release _artist += ', ' ;
2015-06-16 13:34:02 +00:00
}
}
2018-11-20 22:18:49 +00:00
let query =
` artist:( ${ luceneEscape ( release _artist ) } ) ` +
` release:( ${ luceneEscape ( release . title ) } ) ` +
` tracks:( ${ totaltracks } ) ${ release . country ? ` country: ${ release . country } ` : '' } ` ;
2015-06-16 13:34:02 +00:00
appendParameter ( params , 'query' , query ) ;
appendParameter ( params , 'type' , 'release' ) ;
appendParameter ( params , 'advanced' , '1' ) ;
return params ;
}
2012-03-28 22:16:10 +00:00
// ---------------------------------- expose publics here ------------------------------------ //
2014-02-21 17:46:29 +00:00
return {
2018-11-20 22:18:49 +00:00
buildSearchLink : fnBuildSearchLink ,
buildSearchButton : fnBuildSearchButton ,
buildFormHTML : fnBuildFormHTML ,
buildFormParameters : fnBuildFormParameters ,
makeArtistCredits : fnArtistCredits ,
guessReleaseType : fnGuessReleaseType ,
hmsToMilliSeconds : hmsToMilliSeconds ,
ISO8601toMilliSeconds : fnISO8601toMilliSeconds ,
makeEditNote : fnMakeEditNote ,
searchUrlFor : fnSearchUrlFor ,
URL _TYPES : url _types ,
SPECIAL _ARTISTS : special _artists ,
2020-04-05 14:01:21 +00:00
specialArtist : fnSpecialArtist ,
2015-06-12 19:28:24 +00:00
} ;
2012-03-28 22:16:10 +00:00
} ) ( ) ;