Greasy Fork is available in English.

RED/NWCD Upload Assistant

Accurate filling of new upload/request and group/request edit forms based on foobar2000's playlist selection (via pasted output of copy command), release integrity check, two tracklist layouts, colours customization, featured artists extraction, classical works formatting, coverart fetching from store, checking for previous upload and more. As alternative to pasted playlist, e.g. for requests creation, valid URL to product page on supported web can be used -- see below.

Verze ze dne 06. 12. 2019. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         RED/NWCD Upload Assistant
// @namespace    https://greasyfork.org/users/321857-anakunda
// @version      1.188
// @description  Accurate filling of new upload/request and group/request edit forms based on foobar2000's playlist selection (via pasted output of copy command), release integrity check, two tracklist layouts, colours customization, featured artists extraction, classical works formatting, coverart fetching from store, checking for previous upload and more. As alternative to pasted playlist, e.g. for requests creation, valid URL to product page on supported web can be used -- see below.
// @author       Anakunda
// @iconURL      https://redacted.ch/favicon.ico
// @match        https://redacted.ch/upload.php*
// @match        https://redacted.ch/torrents.php?action=editgroup*
// @match        https://redacted.ch/requests.php?action=new*
// @match        https://redacted.ch/requests.php?action=edit*
// @match        https://notwhat.cd/upload.php*
// @match        https://notwhat.cd/torrents.php?action=editgroup*
// @match        https://notwhat.cd/requests.php?action=new*
// @match        https://notwhat.cd/requests.php?action=edit*
// @match        https://orpheus.network/upload.php*
// @match        https://orpheus.network/torrents.php?action=editgroup*
// @match        https://orpheus.network/requests.php?action=new*
// @match        https://orpheus.network/requests.php?action=edit*
// @connect      file://*
// @connect      *
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_log
// //@require      https://connect.soundcloud.com/sdk/sdk-3.3.2.js
// ==/UserScript==

// Additional setup: to work, set the pattern below as built-in foobar2000 copy command or custom Text Tools plugin quick copy command
//   [$fix_eol(%album artist%,)]$char(30)[$fix_eol(%album%,)]$char(30)[$if3(%date%,%ORIGINAL RELEASE DATE%,%year%)]$char(30)[$if3(%releasedate%,%retail date%,%date%,%year%)]$char(30)[$fix_eol($if3(%label%,%publisher%,%COPYRIGHT%),)]$char(30)[$fix_eol($if3(%catalog%,%CATALOGNUMBER%,%CATALOG NUMBER%,%labelno%,%catalog #%,%SKU%,%barcode%,%UPC%,%EAN%,%MCN%),)]$char(30)[%country%]$char(30)%__encoding%$char(30)%__codec%$char(30)[%__codec_profile%]$char(30)[%__bitrate%]$char(30)[%__bitspersample%]$char(30)[%__samplerate%]$char(30)[%__channels%]$char(30)[$if3(%media%,%format%,%source%,%MEDIATYPE%,%SOURCEMEDIA%,%discogs_format%)]$char(30)[$fix_eol(%genre%,)[|$fix_eol(%style%,)]]$char(30)[$num(%discnumber%,0)]$char(30)[$num($if2(%totaldiscs%,%disctotal%),0)]$char(30)[$fix_eol(%discsubtitle%,)]$char(30)[%track number%]$char(30)[$num($if2(%totaltracks%,%TRACKTOTAL%),0)]$char(30)[$fix_eol(%title%,)]$char(30)[$fix_eol(%track artist%,)]$char(30)[$if($strcmp($fix_eol(%performer%,),$fix_eol(%artist%,)),,$fix_eol(%performer%,))]$char(30)[$fix_eol($if3(%composer%,%writer%,%SONGWRITER%,%author%,%LYRICIST%),)]$char(30)[$fix_eol(%conductor%,)]$char(30)[$fix_eol(%remixer%,)]$char(30)[$fix_eol($if2(%compiler%,%mixer%),)]$char(30)[$fix_eol($if2(%producer%,%producedby%),)]$char(30)%length_seconds_fp%$char(30)%length_samples%$char(30)[%filesize%]$char(30)[%replaygain_album_gain%]$char(30)[%album dynamic range%]$char(30)[%__tool%][ | %ENCODER%][ | %ENCODER_OPTIONS%]$char(30)[$fix_eol($if2(%url%,%www%),)]$char(30)$directory_path(%path%)$char(30)[$replace($replace($if2(%comment%,%description%),$char(13),$char(29)),$char(10),$char(28))]$char(30)$trim([RELEASETYPE=$replace($if2(%RELEASETYPE%,%RELEASE TYPE%), ,_) ][COMPILATION=%compilation% ][ISRC=%isrc% ][EXPLICIT=%EXPLICIT% ][ORIGINALFORMAT=%ORIGINALFORMAT% ][ASIN=%ASIN% ][DISCOGS_ID=%discogs_release_id% ][SOURCEID=%SOURCEID% ][BPM=%BPM% ])
//
// List of supported domains for online capturing of release details:
//
// Music releases:
// - qobuz.com
// - highresaudio.com
// - bandcamp.com
// - prestomusic.com
// - discogs.com
// - supraphonline.cz
// - bontonland.cz (closing soon)
// - nativedsd.com
// - junodownload.com
// - hdtracks.com
// - deezer.com
// - spotify.com
// - prostudiomasters.com
// - play.google.com
// - 7digital.com
//
// Ebooks releases:
// - martinus.cz, martinus.sk
// - goodreads.com
// - databazeknih.cz
//
// Application releases:
// - sanet.st

'use strict';

const isRED = document.domain.toLowerCase().endsWith('redacted.ch');
const isNWCD = document.domain.toLowerCase().endsWith('notwhat.cd');
const isOrpheus = document.domain.toLowerCase().endsWith('orpheus.network');

const isUpload = document.URL.toLowerCase().includes('/upload\.php');
const isEdit = document.URL.toLowerCase().includes('/torrents.php?action=editgroup');
const isRequestNew = document.URL.toLowerCase().includes('/requests.php?action=new');
const isRequestEdit = document.URL.toLowerCase().includes('/requests.php?action=edit');

var prefs = {
  set: function(prop, def) { this[prop] = GM_getValue(prop, def) },
  save: function() {
	for (var iter in this) {
	  if (typeof this[iter] != 'function' && this[iter] != undefined) GM_setValue(iter, this[iter]);
	}
  },
};
prefs.set('autfill_delay', 1000); // delay in ms to autofill form after pasting text into box, 0 to disable
prefs.set('clean_on_apply', 0); // clean the input box on successfull fill
prefs.set('keep_meaningles_composers', 0); // keep composers from file tags also for non-composer emphasing genres
prefs.set('single_threshold', 8 * 60); // For autodetection of release type: max length of single in s
prefs.set('EP_threshold', 29 * 60); // For autodetection of release type: max time of EP in s
prefs.set('auto_preview_cover', 1);
prefs.set('auto_rehost_cover', 1); // PTPIMG / using 3rd party script
prefs.set('fetch_tags_from_artist', 0); // add N most used tags from release artist (if one) - experimental/may inject nonsense tags for coinciding artists; 0 for disable
prefs.set('request_default_bounty', 0); // set this bounty in MB after successfull fill of request form / 0 for disable
prefs.set('always_request_perfect_flac', 0);
prefs.set('estimate_decade_tag', 1); // deduce decade tag (1980s, etc.) from album year for regular albums
prefs.set('dragdrop_patch_to_ptpimgit', 1);
prefs.set('sacd_decoder', 'foobar2000\'s SACD decoder (direct-fp64)');
prefs.set('ptpimg_api_key');
prefs.set('spotify_clientid');
prefs.set('spotify_clientsecret');
prefs.set('soundcloud_clientid');
prefs.set('catbox_userhash');
prefs.set('upcoming_tags', ''); // add this tag(s) to upcoming releases (requests); empty to disable
prefs.set('remap_texttools_newlines', 0); // convert underscores to linebreaks (ambiguous)
// tracklist specific
prefs.set('tracklist_style', 1); // 1: classical, 2: propertional right aligned
prefs.set('tracklist_size', 2);
prefs.set('max_tracklist_width', 80); // right margin of the right aligned tracklist. should not exceed the group description width on any device
prefs.set('title_separator', '. '); // divisor of track# and title
prefs.set('pad_leader', ' ');
prefs.set('tracklist_head_color', '#4682B4'); // #a7bdd0
prefs.set('tracklist_single_color', '#708080');
// classical tracklist only components colouring
prefs.set('tracklist_disctitle_color', '#2bb7b7'); // #bb831c
prefs.set('tracklist_work_color', 'Olive'); //#b16890
prefs.set('tracklist_tracknumber_color', '#8899AA');
prefs.set('tracklist_composer_color', '#8ca014');
prefs.set('tracklist_artist_color', '#bd8218');
prefs.set('tracklist_duration_color', '#33a6cc'); // #2196f3

document.head.appendChild(document.createElement('style')).innerHTML = `
.ua-messages { text-indent: -2em; margin-left: 2em; }
.ua-messages-bg { padding: 15px; text-align: left; background-color: darkslategray; }
.ua-critical { color: red; font-weight: bold; }
.ua-warning { color: #ff8d00; font-weight: 500; }
.ua-info { color: white; }

.ua-button { vertical-align: middle; background-color: transparent; }
.ua-input {
  width: 610px; height: 3em;
  margin-top: 8px; margin-bottom: 8px;
  background-color: antiquewhite;
  font-size: small;
}
`;

var ref, tbl, elem, child, tb, rehostItBtn;
var spotifyCredentials = {}, siteArtistsCache = {}, notSiteArtistsCache = [];
var messages = null, autofill = false, domParser = new DOMParser(), dom;

if (isUpload) {
  ref = document.querySelector('form#upload_table > div#dynamic_form');
  if (ref == null) return;
  common1();
  let x = [];
  x.push(document.createElement('tr'));
  x[0].classList.add('ua-button');
  child = document.createElement('input');
  child.id = 'fill-from-text';
  child.value = 'Fill from text (overwrite)';
  child.type = 'button';
  child.style.width = '13em';
  child.onclick = fillFromText_Music;
  x[0].append(child);
  elem.append(x[0]);
  x.push(document.createElement('tr'));
  x[1].classList.add('ua-button');
  child = document.createElement('input');
  child.id = 'fill-from-text-weak';
  child.value = 'Fill from text (keep values)';
  child.type = 'button';
  child.style.width = '13em';
  child.onclick = fillFromText_Music;
  x[1].append(child);
  elem.append(x[1]);
  common2();
  ref.parentNode.insertBefore(tbl, ref);
} else if (isEdit) {
  ref = document.querySelector('form.edit_form > div > div > input[type="submit"]');
  if (ref == null) return;
  ref = ref.parentNode;
  ref.parentNode.insertBefore(document.createElement('br'), ref);
  common1();
  child = document.createElement('input');
  child.id = 'append-from-text';
  child.value = 'Fill from text (append)';
  child.type = 'button';
  child.onclick = fillFromText_Music;
  elem.append(child);
  common2();
  tbl.style.marginBottom = '10px';
  ref.parentNode.insertBefore(tbl, ref);
} else if (isRequestNew) {
  ref = document.getElementById('categories');
  if (ref == null) return;
  ref = ref.parentNode.parentNode.nextElementSibling;
  ref.parentNode.insertBefore(document.createElement('br'), ref);
  common1();
  child = document.createElement('input');
  child.id = 'fill-from-text-weak';
  child.value = 'Fill from URL';
  child.type = 'button';
  child.onclick = fillFromText_Music;
  elem.append(child);
  common2();
  child = document.createElement('td');
  child.colSpan = 2;
  child.append(tbl);
  elem = document.createElement('tr');
  elem.append(child);
  ref.parentNode.insertBefore(elem, ref);
} else if (isRequestEdit) {
  ref = document.querySelector('input#button[type="submit"]');
  if (ref == null) return;
  ref = ref.parentNode.parentNode;
  ref.parentNode.insertBefore(document.createElement('br'), ref);
  common1();
  child = document.createElement('input');
  child.id = 'append-from-text';
  child.value = 'Fill from text (append)';
  child.type = 'button';
  child.onclick = fillFromText_Music;
  elem.append(child);
  common2();
  tbl.style.marginBottom = '10px';
  elem = document.createElement('tr');
  child = document.createElement('td');
  child.colSpan = 2;
  child.append(tbl);
  elem.append(child);
  ref.parentNode.insertBefore(elem, ref);
}

if ((ref = document.getElementById('image') || document.querySelector('input[name="image"]')) != null) {
  ref.ondblclick = clear0;
  ref.onmousedown = clear1;
  ref.ondrop = clear0;
}

function clear0() { this.value = '' }
function clear1(e) { if (e.button == 1) this.value = '' }
function autoFill(e) {
  autofill = true;
  setTimeout(fillFromText_Music, prefs.autfill_delay);
}

function common1() {
  tbl = document.createElement('tr');
  tbl.style.backgroundColor = 'darkgoldenrod';
  tbl.style.verticalAlign = 'middle';
  elem = document.createElement('td');
  elem.style.textAlign = 'center';
  child = document.createElement('textarea');
  child.id = 'UA data';
  child.name = 'UA data';
  child.classList.add('ua-input');
  child.spellcheck = false;
  //child.ondblclick = clear0;
  child.onmousedown = clear1;
  child.ondrop = clear0;
  if (prefs.autfill_delay > 0) {
	child.onpaste = autoFill;
	child.ondrop = autoFill;
  }
  elem.append(child);
  tbl.append(elem);
  elem = document.createElement('td');
  elem.style.textAlign = 'center';
}
function common2() {
  tbl.append(elem);
  tb = document.createElement('tbody');
  tb.append(tbl);
  tbl = document.createElement('table');
  tbl.id = 'upload assistant';
  tbl.append(tb);
}

if ((ref = document.getElementById('yadg_input')) != null) ref.ondrop = clear0;

Array.prototype.includesCaseless = function(str) {
  return typeof str == 'string' && this.find(it => typeof it == 'string' && it.toLowerCase() == str.toLowerCase()) != undefined;
};
Array.prototype.pushUnique = function(...items) {
  items.forEach(it => { if (!this.includes(it)) this.push(it) });
  return this.length;
};
Array.prototype.pushUniqueCaseless = function(...items) {
  items.forEach(it => { if (!this.includesCaseless(it)) this.push(it) });
  return this.length;
};
// Array.prototype.getUnique = function(prop) {
//   return this.every((it) => it[prop] && it[prop] == this[0][prop]) ? this[0][prop] : null;
// };
Array.prototype.equalTo = function(arr) {
  return arr instanceof Array && arr.length == this.length && arr.sort().toString() == this.sort().toString();
};
String.prototype.toASCII = function() {
  return this.normalize("NFKD").replace(/[\x00-\x1F\u0080-\uFFFF]/g, '');
};
String.prototype.trueLength = function() {
  return this.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').length;
//   var index = 0, width = 0, len = 0;
//   while (index < this.length) {
// 	var point = this.codePointAt(index);
// 	width = 0;
// 	while (point) {
// 	  ++width;
// 	  point = point >> 8;
// 	}
// 	index += Math.round(width / 2);
// 	++len;
//   }
//   return len;
};
Date.prototype.getDateValue = function() {
  return Math.floor((this.getTime() / 1000 / 60 - this.getTimezoneOffset()) / 60 / 24);
};

const excludedCountries = [
  /\b(?:United\s+States|USA?)\b/,
  /\b(?:United\s+Kingdom|(?:Great\s+)?Britain|England|GB|UK)\b/,
  /\b(?:Europe|European\s+Union|EU)\b/,
  /\b(?:Unknown)\b/,
];

class TagManager extends Array {
  constructor(...tags) {
	super();
	this.presubstitutions = [
	  [/\b(?:Singer\/Songwriter)\b/i, 'singer.songwriter'],
	  [/\b(?:Pop\/Rock)\b/i, 'pop.rock'],
	  [/\b(?:Folk\/Rock)\b/i, 'folk.rock'],
	];
	this.substitutions = [
	  [/^Pop\s*(?:[\-\−\—\–]\s*)?Rock$/i, 'pop.rock'],
	  [/^Rock\s*(?:[\-\−\—\–]\s*)?Pop$/i, 'pop.rock'],
	  [/^Rock\s+n\s+Roll$/i, 'rock.and.roll'],
	  ['AOR', 'album.oriented.rock'],
	  [/^(?:Prog)\.?\s*(?:Rock)$/i, 'progressive.rock'],
	  [/^Synth[\s\-\−\—\–]+Pop$/i, 'synthpop'],
	  [/^World(?:\s+and\s+|\s*[&+]\s*)Country$/i, 'world.music', 'country'],
	  ['World', 'world.music'],
	  [/^(?:Singer(?:\s+and\s+|\s*[&+]\s*))?Songwriter$/i, 'singer.songwriter'],
	  [/^(?:R\s*(?:[\'\’\`][Nn](?:\s+|[\'\’\`]\s*)|&\s*)B|RnB)$/i, 'rhytm.and.blues'],
	  [/\b(?:Soundtracks?)$/i, 'score'],
	  ['Electro', 'electronic'],
	  ['Metal', 'heavy.metal'],
	  ['NonFiction', 'non.fiction'],
	  ['Rap', 'hip.hop'],
	  ['NeoSoul', 'neo.soul'],
	  ['NuJazz', 'nu.jazz'],
	  ['Hardcore', 'hardcore.punk'],
	  ['Garage', 'garage.rock'],
	  [/^(?:Neo[\s\-\−\—\–]+Classical)$/i, 'neoclassical'],
	  [/^(?:Bluesy[\s\-\−\—\–]+Rock)$/i, 'blues.rock'],
	  [/^(?:Be[\s\-\−\—\–]+Bop)$/i, 'bebop'],
	  [/^(?:Chill)[\s\-\−\—\–]+(?:Out)$/i, 'chillout'],
	  [/^(?:Atmospheric)[\s\-\−\—\–]+(?:Black)$/i, 'atmospheric.black.metal'],
	  ['GoaTrance', 'goa.trance'],
	  [/^Female\s+Vocal\w*$/i, 'female.vocalist'],
	  // Country aliases
	  ['Canada', 'canadian'],
	  ['Australia', 'australian'],
	  ['Japan', 'japanese'],
	  ['Taiwan', 'thai'],
	  ['China', 'chinese'],
	  ['Singapore', 'singaporean'],
	  [/^(?:Russia|Russian\s+Federation|Россия|USSR|СССР)$/i, 'russian'],
	  ['Turkey', 'turkish'],
	  ['France', 'french'],
	  ['Germany', 'german'],
	  ['Spain', 'spanish'],
	  ['Italy', 'italian'],
	  ['Sweden', 'swedish'],
	  ['Norway', 'norwegian'],
	  ['Finland', 'finnish'],
	  ['Greece', 'greek'],
	  [/^(?:Netherlands|Holland)$/i, 'dutch'],
	  ['Belgium', 'belgian'],
	  ['Luxembourg', 'luxembourgish'],
	  ['Denmark', 'danish'],
	  ['Switzerland', 'swiss'],
	  ['Austria', 'austrian'],
	  ['Portugal', 'portugese'],
	  ['Ireland', 'irish'],
	  ['Scotland', 'scotish'],
	  ['Iceland', 'icelandic'],
	  [/^(?:Czech\s+Republic|Czechia)$/i, 'czech'],
	  [/^(?:Slovak\s+Republic|Slovakia)$/i, 'slovak'],
	  ['Hungary', 'hungarian'],
	  ['Poland', 'polish'],
	  ['Estonia', 'estonian'],
	  ['Latvia', 'latvian'],
	  ['Lithuania', 'lithuanian'],
	  ['Moldova', 'moldovan'],
	  ['Armenia', 'armenian'],
	  ['Ukraine', 'ukrainian'],
	  ['Yugoslavia', 'yugoslav'],
	  ['Serbia', 'serbian'],
	  ['Slovenia', 'slovenian'],
	  ['Croatia', 'croatian'],
	  ['Macedonia', 'macedonian'],
	  ['Montenegro', 'montenegrin'],
	  ['Romania', 'romanian'],
	  ['Malta', 'maltese'],
	  ['Brazil', 'brazilian'],
	  ['Mexico', 'mexican'],
	  ['Argentina', 'argentinean'],
	  ['Jamaica', 'jamaican'],
	  // Books
	  ['Beletrie', 'fiction'],
	  ['Satira', 'satire'],
	  ['Komiks', 'comics'],
	  ['Komix', 'comics'],
	  // Removals
	  ['Unknown'],
	  ['Other'],
	  ['New'],
	  ['Ostatni'],
	  ['Knihy'],
	  ['Audioknihy'],
	  [/^(?:Audio\s*kniha|Audio\s*Book)$/i],
	].concat(excludedCountries.map(it => [it]));
	this.splits = [
	  ['Alternative', 'Indie'],
	  ['Rock', 'Pop'],
	  ['Soul', 'Funk'],
	  ['Ska', 'Rocksteady'],
	  ['Jazz Fusion', 'Jazz Rock'],
	  ['Rock', 'Pop'],
	  ['Jazz', 'Funk'],
	];
	this.additions = [
	  [/^(?:(?:(?:Be|Post|Neo)[\s\-\−\—\–]*)?Bop|Modal|Fusion|Free[\s\-\−\—\–]+Improvisation|Jazz[\s\-\−\—\–]+Fusion|Big[\s\-\−\—\–]*Band)$/i, 'jazz'],
	  [/^(?:(?:Free|Cool|Avant[\s\-\−\—\–]*Garde|Contemporary|Vocal|Instrumental|Crossover|Modal|Mainstream|Modern|Soul|Smooth|Piano|Latin|Afro[\s\-\−\—\–]*Cuban)[\s\-\−\—\–]+Jazz)$/i, 'jazz'],
	  [/^(?:Opera)$/i, 'classical'],
	  [/\b(?:Chamber[\s\-\−\—\–]+Music)\b/i, 'classical'],
	  [/\b(?:Orchestral[\s\-\−\—\–]+Music)\b/i, 'classical'],
	  [/^(?:Symphony)$/i, 'classical'],
	  [/^(?:Sacred\s+Vocal)\b/i, 'classical'],
	  [/\b(?:Soundtracks?|Films?|Games?|Video|Series?|Theatre|Musical)\b/i, 'score'],
	];
	if (tags.length > 0) this.add(...tags);
  }

  add(...tags) {
	var added = 0;
	for (var tag of tags) {
	  if (typeof tag != 'string') continue;
	  qobuzTranslations().forEach(function(it) { if (tag == it[0]) tag = it[1] });
	  this.presubstitutions.forEach(k => { if (k[0].test(tag)) tag = tag.replace(k[0], k[1]) });
	  tag.split(/\s*[\,\/\;\>\|]+\s*/).forEach(function(tag) {
		//qobuzTranslations().forEach(function(it) { if (tag == it[0]) tag = it[1] });
		tag = tag.toASCII().replace(/\(.*?\)|\[.*?\]|\{.*?\}/g, '').trim();
		if (tag.length <= 0 || tag == '?') return null;
		function test(obj) {
		  return typeof obj == 'string' && tag.toLowerCase() == obj.toLowerCase()
		  	|| obj instanceof RegExp && obj.test(tag);
		}
		for (var k of this.substitutions) {
		  if (test(k[0])) {
			if (k.length >= 1) added += this.add(...k.slice(1));
			else addMessage('Warning: invalid tag \'' + tag + '\' found', 'ua-warning');
			return;
		  }
		}
		for (k of this.additions) {
		  if (test(k[0])) added += this.add(...k.slice(1));
		}
		for (k of this.splits) {
		  if (new RegExp('^' + k[0] + '(?:\\s+and\\s+|\\s*[&+]\\s*)' + k[1] + '$', 'i').test(tag)) {
			added += this.add(k[0], k[1]); return;
		  }
		  if (new RegExp('^' + k[1] + '(?:\\s+and\\s+|\\s*[&+]\\s*)' + k[0] + '$', 'i').test(tag)) {
			added += this.add(k[0], k[1]); return;
		  }
		}
		tag = tag.
		  replace(/^(?:Alt\.)\s*(\w+)$/i, 'Alternative $1').
		  replace(/\b(?:Alt\.)(?=\s+)/i, 'Alternative').
		  replace(/^[3-9]0s$/i, '19$0').
		  replace(/^[0-2]0s$/i, '20$0').
		  replace(/\b(Psy)[\s\-\−\—\–]+(Trance|Core|Chill)\b/i, '$1$2').
		  replace(/\s*(?:[\'\’\`][Nn](?:\s+|[\'\’\`]\s*)|[\&\+]\s*)/, ' and ').
		  replace(/[\s\-\−\—\–\_\.\,\'\`\~]+/g, '.').
		  replace(/[^\w\.]+/g, '').
		  toLowerCase();
		if (tag.length >= 2 && !this.includes(tag)) {
		  this.push(tag);
		  ++added;
		}
	  }.bind(this));
	}
	return added;
  }
  toString() { return this.sort().join(', ') }
};

if ((ref = document.getElementById('categories')) != null) {
  ref.addEventListener('change', function(e) {
	elem = document.getElementById('upload assistant');
	if (elem != null) elem.style.visibility = this.value < 4
		|| ['Music', 'Applications', 'E-Books', 'Audiobooks'].includes(this.value) ? 'visible' : 'collapse';
  });
}

if (!isNWCD) {
  rehostItBtn = document.querySelector('input.rehost_it_cover[type="button"]');
  if (prefs.dragdrop_patch_to_ptpimgit && rehostItBtn != null && !isNWCD) {
	rehostItBtn.ondragover = voidDragHandler;
	rehostItBtn.ondrop = imageDropHandler;
  }
}

return;

function fillFromText_Music(e) {
  if (e == undefined && !autofill) return;
  autofill = false;
  var overwrite = this.id == 'fill-from-text';
  var clipBoard = document.getElementById('UA data');
  if (clipBoard == null) return false;
  const urlParser = /^\s*(https?:\/\/[\S]+)\s*$/i;
  const VA = 'Various Artists';
  messages = document.getElementById('UA messages');
  //let promise = clientInformation.clipboard.readText().then(text => clipBoard = text);
  //if (typeof clipBoard != 'string') return false;
  var i, matches, url, category = document.getElementById('categories');
  if (category == null && document.getElementById('releasetype') != null
	  || category != null && (category.value == 0 || category.value == 'Music')) return fillFromText_Music();
  if (category != null && (category.value == 1 || category.value == 'Applications')) return fillFromText_Apps();
  if (category != null && (category.value == 2 || category.value == 3
	|| category.value == 'E-Books' || category.value == 'Audiobooks')) return fillFromText_Ebooks();
  return category == null ? fillFromText_Apps(true) || fillFromText_Ebooks() : false;

  function fillFromText_Music() {
	if (messages != null) messages.parentNode.removeChild(messages);
	const divs = ['—', '⸺', '⸻'];
	const vaParser = /^(?:Various(?:\s+Artists)?|VA|\<various\s+artists\>|Různí(?:\s+interpreti)?)$/i;
	const multiArtistParsers = [
	  /(?:\s+[\/\|\×]|\s*(?:;|,(?!\s*(?:[JjSs]r)\b)(?:\s*[Aa]nd\s+)?))\s+/,
	];
	const ampersandParsers = [
	  /\s+(?:meets|vs\.?|X)\s+/i,
	  /\s*[;\/\|\×]\s*/,
	  /\s+(?:[\&\+]|and)\s+(?!:his\b|her\b|Friends$|Strings$)/i, // /\s+(?:[\&\+]|and)\s+(?!(?:The|his|her|Friends)\b)/i,
	  /\s*\+\s*(?!(?:his\b|her\b|Friends$|Strings$))/i,
	];
	const featParsers = [
	  /\s+(?:meets)\s+(.*?)\s*$/i,
	  /\s+(?:[Ww]ith)\s+(?!:his\b|her\b|Friends$|Strings$)(.*?)\s*$/,
	  /\s+(?:[Ff](?:eat(?:\.|uring)|t\.))\s+(.*?)\s*$/,
	  /\s+\[\s*f(?:eat(?:\.|uring)|t\.)\s+([^\[\]]+?)\s*\]/i,
	  /\s+\(\s*f(?:eat(?:\.|uring)|t\.)\s+([^\(\)]+?)\s*\)/i,
	  /\s+\[\s*with\s+(?!:his\b|her\b|Friends$|Strings$)([^\[\]]+?)\s*\]/i,
	  /\s+\(\s*with\s+(?!:his\b|her\b|Friends$|Strings$)([^\(\)]+?)\s*\)/i,
	  /\s+\[\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\[\]]+?)\s*\]/i,
	  /\s+\(\s*(?:(?:en\s+)?duo\s+)?avec\s+([^\(\)]+?)\s*\)/i,
	];
	const remixParsers = [
	  /\s+\((?:The\s+)Remix(?:e[sd])?\)/i,
	  /\s+\[(?:The\s+)Remix(?:e[sd])?\]/i,
	  /\s+(?:The\s+)Remix(?:e[sd])?\s*$/i,
	  /\s+\(([^\(\)]+?)(?:[\'\’\`]s)?\s+(?:(?:Extended|Enhanced)\s+)?Remix\)/i,
	  /\s+\[([^\[\]]+?)(?:[\'\’\`]s)?\s+(?:(?:Extended|Enhanced)\s+)?Remix\]/i,
	  /\s+\(\s*(?:(Extended|Enhanced)\s+)?Remix(?:ed)?\s+by\s+([^\(\)]+)\)/i,
	  /\s+\[\s*(?:(Extended|Enhanced)\s+)?Remix(?:ed)?\s+by\s+([^\[\]]+)\]/i,
	];
	const otherArtistsParsers = [
	  [/^(.*?)\s+(?:under|(?:conducted)\s+by)\s+(.*)$/, 4],
	  [/^()(.*?)\s+\(conductor\)$/i, 4],
	  //[/^()(.*?)\s+\(.*\)$/i, 1],
	];
	const pseudoArtistParsers = [
	  /^(?:#?N\/?A|[JS]r\.?)$/i,
	  /^(?:traditional|lidová)$/i,
	  /\b(?:traditional|lidová)$/,
	  /^(?:tradiční|lidová)\s+/,
	  /^(?:[Aa]nonym)/,
	  /^(?:[Ll]iturgical\b|[Ll]iturgick[áý])/,
	  /^(?:auditorium|[Oo]becenstvo|[Pp]ublikum)$/,
	  /^(?:Various\s+Composers)$/i,
	];
	const artistStrips = [
	  /\s+(?:aka|AKA)\.?\s+(.*)/,
	  /\s+\([^\(\)]+\)$/,
	  /\s+\[[^\[\]]+\]$/,
	  /\s+\{[^\{\}]+\}$/,
	];
	var track, tracks = [], totalDiscs = 1, media, xhr = new XMLHttpRequest();
	if (urlParser.test(clipBoard.value)) return initFromUrl_Music(RegExp.$1);
	function ruleLink(rule) { return ' (<a href="https://redacted.ch/rules.php?p=upload#r' + rule + '" target="_blank">' + rule + '</a>)' }
	var albumBitrate = 0, totalTime = 0, albumSize = 0;
	for (iter of clipBoard.value.split(/[\r\n]+/)) {
	  if (!iter.trim()) continue; // skip empty lines
	  let metaData = iter.split('\x1E');
	  track = {
		artist: metaData.shift().trim() || undefined,
		album: metaData.shift().trim() || undefined,
		album_year: safeParseYear(metaData.shift().trim()),
		release_date: metaData.shift().trim() || undefined,
		label: metaData.shift().trim() || undefined,
		catalog: metaData.shift().trim() || undefined,
		country: metaData.shift().trim() || undefined,
		encoding: metaData.shift().trim() || undefined,
		codec: metaData.shift().trim() || undefined,
		codec_profile: metaData.shift().trim() || undefined,
		bitrate: safeParseInt(metaData.shift()),
		bd: safeParseInt(metaData.shift()),
		sr: safeParseInt(metaData.shift()),
		channels: safeParseInt(metaData.shift()),
		media: metaData.shift().trim() || undefined,
		genre: metaData.shift().trim() || undefined,
		discnumber: metaData.shift().trim() || undefined,
		totaldiscs: safeParseInt(metaData.shift()),
		discsubtitle: metaData.shift().trim() || undefined,
		tracknumber: metaData.shift().trim() || undefined,
		totaltracks: safeParseInt(metaData.shift()),
		title: metaData.shift().trim() || undefined,
		track_artist: metaData.shift().trim() || undefined,
		performer: metaData.shift().trim() || undefined,
		composer: metaData.shift().trim() || undefined,
		conductor: metaData.shift().trim() || undefined,
		remixer: metaData.shift().trim() || undefined,
		compiler: metaData.shift().trim() || undefined,
		producer: metaData.shift().trim() || undefined,
		duration: safeParseFloat(metaData.shift()),
		samples: safeParseInt(metaData.shift()),
		filesize: safeParseInt(metaData.shift()),
		rg: metaData.shift().trim() || undefined,
		dr: metaData.shift().trim() || undefined,
		vendor: metaData.shift().trim() || undefined,
		url: metaData.shift().trim() || undefined,
		dirpath: metaData.shift() || undefined,
		comment: metaData.shift().trim() || undefined,
		identifiers: {},
	  };
	  if (!track.artist) {
		addMessage('FATAL: main artist must be defined in every track' + ruleLink('2.3.16.4'), 'ua-critical', true);
		clipBoard.value = '';
		throw new Error('artist missing');
	  }
	  if (!track.album) {
		addMessage('FATAL: album title must be defined in every track' + ruleLink('2.3.16.4'), 'ua-critical', true);
		clipBoard.value = '';
		throw new Error('album mising');
	  }
	  if (!track.tracknumber) {
		addMessage('FATAL: all track numbers must be defined' + ruleLink('2.3.16.4'), 'ua-critical', true);
		clipBoard.value = '';
		throw new Error('tracknumber missing');
	  }
	  if (!track.title) {
		addMessage('FATAL: all track titles must be defined' + ruleLink('2.3.16.4'), 'ua-critical', true);
		clipBoard.value = '';
		throw new Error('track title missing');
	  }
	  if (track.duration != undefined && isUpload && (isNaN(track.duration) || track.duration <= 0)) {
		addMessage('FATAL: invalid track #' + track.tracknumber + ' length: ' + track.duration, 'ua-critical');
		clipBoard.value = '';
		throw new Error('invalid duration');
	  }
	  if (track.codec && !['FLAC', 'MP3', 'AAC', 'DTS', 'AC3'].includes(track.codec)) {
		addMessage('FATAL: disallowed codec present (' + track.codec + ')', 'ua-critical');
		clipBoard.value = '';
		throw new Error('invalid format');
	  }
	  if (/^(\d+)\s*[\/]\s*(\d+)$/.test(track.tracknumber)) { // track/totaltracks
		addMessage('Warning: nonstandard track number formatting for track ' + RegExp.$1 + ': ' + track.tracknumber, 'ua-warning');
		track.tracknumber = RegExp.$1;
		if (!track.totaltracks) track.totaltracks = parseInt(RegExp.$2);
	  } else if (/^(\d+)[\.\-](\d+)$/.test(track.tracknumber)) { // discnumber.tracknumber
		addMessage('Warning: nonstandard track number formatting for track ' + RegExp.$2 + ': ' + track.tracknumber, 'ua-warning');
		if (!track.discnumber) track.discnumber = parseInt(RegExp.$1);
		track.tracknumber = RegExp.$2;
	  }
	  if (track.discnumber) {
		if (/^(\d+)\s*\/\s*(\d+)/.test(track.discnumber)) {
		  addMessage('Warning: nonstandard disc number formatting for track ' + track.tracknumber + ': ' + track.discnumber, 'ua-warning');
		  track.discnumber = RegExp.$1;
		  if (!track.totaldiscs) track.totaldiscs = RegExp.$2;
		} else track.discnumber = parseInt(track.discnumber);
		if (isNaN(track.discnumber)) {
		  addMessage('Warning: invalid disc numbering for track ' + track.tracknumber, 'ua-warning');
		  track.discnumber = undefined;
		}
	  	if (track.discnumber > totalDiscs) totalDiscs = track.discnumber;
	  }
	  if (track.comment == '.') track.comment = undefined;
	  if (track.comment) {
		track.comment = track.comment.replace(/\x1D/g, '\r').replace(/\x1C/g, '\n');
		if (prefs.remap_texttools_newlines) track.comment = track.comment.replace(/__/g, '\r\n').replace(/_/g, '\n') // ambiguous
		track.comment = track.comment.replace(/(?:[ \t]*\r?\n){3,}/g, '\n\n'); // reduce excessive empty lines
	  }
	  if (track.dr != null) track.dr = parseInt(track.dr); // DR0
	  metaData.shift().trim().split(/\s+/).forEach(function(it) {
		if (/([\w\-]+)[=:](.*)/.test(it)) track.identifiers[RegExp.$1.toUpperCase()] = RegExp.$2.replace(/\x1B/g, ' ');
	  });
	  totalTime += track.duration || NaN;
	  albumBitrate += (track.duration || NaN) * (track.bitrate || NaN);
	  albumSize += track.filesize;

	  tracks.push(track);

	  function safeParseInt(x) { return typeof x != 'string' ? null : x.length <= 0 ? undefined : parseInt(x) }
	  function safeParseFloat(x) { return typeof x != 'string' ? null : x.length <= 0 ? undefined : parseFloat(x) }
	  function safeParseYear(x) { return typeof x != 'string' ? null : x.length <= 0 ? undefined : extractYear(x) || NaN }
	}
	if (tracks.length <= 0) {
	  addMessage('FATAL: no tracks found', 'ua-critical', true);
	  clipBoard.value = '';
	  throw new Error('no tracks');
	}
	if (!tracks.every(it => it.discnumber > 0) && !tracks.every(it => !it.discnumber)) {
	  addMessage('FATAL: inconsistent release (mix of tracks with and without disc number)', 'ua-critical', true);
	  clipBoard.value = '';
	  throw new Error('inconsistent disc numbering');
	}
	if (totalDiscs > 1 && tracks.some(it => it.totaldiscs != totalDiscs))
	  addMessage('Info: at least one track not having properly set TOTALDISCS (' + totalDiscs + ')', 'ua-info');

	var release = {};
	['catalogs', 'bds', 'genres', 'srs', 'urls', 'comments', 'trackArtists', 'bitrates',
	 'drs', 'rgs', 'dirpaths', 'composers'].forEach(it => { release[it] = [] });
	function setUniqueProperty(propName, propNameLiteral) {
	  let homogenous = new Set(tracks.map(it => it[propName]).filter(it => it != undefined && it != null));
	  if (homogenous.size > 1) {
		var diverses = '', it = homogenous.values(), val;
		while (!(val = it.next()).done) diverses += '<br>\t' + val.value;
		addMessage('FATAL: mixed releases not accepted (' + propNameLiteral + ') - supposedly user compilation' + diverses, 'ua-critical', true);
		clipBoard.value = '';
		throw new Error('mixed release (' + propNameLiteral + ')');
	  }
	  release[propName] = homogenous.values().next().value;
	}
	setUniqueProperty('artist', 'album artist');
	setUniqueProperty('album', 'album title');
	setUniqueProperty('album_year', 'album year');
	setUniqueProperty('release_date', 'release date');
	setUniqueProperty('encoding', 'encoding');
	setUniqueProperty('codec', 'codec');
	setUniqueProperty('codec_profile', 'codec profile');
	setUniqueProperty('vendor', 'vendor');
	setUniqueProperty('media', 'media');
	setUniqueProperty('channels', 'channels');
	setUniqueProperty('label', 'label');
	setUniqueProperty('country', 'country');

	tracks.forEach(function(iter) {
	  push_unique('trackArtists', 'track_artist');
	  push_unique('composers', 'composer');
	  push_unique('catalogs', 'catalog');
	  push_unique('bitrates', 'bitrate');
	  push_unique('bds', 'bd');
	  push_unique('rgs', 'rg');
	  push_unique('drs', 'dr');
	  if (iter.sr) {
		if (typeof release.srs[iter.sr] != 'number') {
		  release.srs[iter.sr] = iter.duration;
		} else {
		  release.srs[iter.sr] += iter.duration;
		}
	  }
	  push_unique('dirpaths', 'dirpath');
	  push_unique('comments', 'comment');
	  push_unique('genres', 'genre');
	  push_unique('urls', 'url');

	  function push_unique(relProp, prop) {
		if (iter[prop] !== undefined && iter[prop] !== null && (typeof iter[prop] != 'string'
			|| iter[prop].length > 0) && !release[relProp].includes(iter[prop])) release[relProp].push(iter[prop]);
	  }
	});
	function validatorFunc(arr, validator, str) {
	  if (arr.length <= 0 || !arr.some(validator)) return true;
	  addMessage('FATAL: disallowed ' + str + ' present (' + arr.filter(validator) + ')', 'ua-critical');
	  clipBoard.value = '';
	  throw new Error('disallowed ' + str);
	}
	validatorFunc(release.bds, bd => ![16, 24].includes(bd), 'bit depths');
	validatorFunc(Object.keys(release.srs),
		sr => sr < 44100 || sr > 192000 || sr % 44100 != 0 && sr % 48000 != 0, 'sample rates');

	var composerEmphasis = false, isFromDSD = false, isClassical = false;
	var yadg_prefil = '', releaseType, editionTitle, isVA, iter, rx;
	var tags = new TagManager();
	albumBitrate /= totalTime;
	if (tracks.every(it => /^(?:single)$/i.test(it.identifiers.RELEASETYPE))
	   || tracks.length == 1 && totalTime > 0 && totalTime <= prefs.single_threshold) {
	  releaseType = getReleaseIndex('Single');
	} else if (tracks.every(it => it.identifiers.RELEASETYPE == 'EP')) {
	  releaseType = getReleaseIndex('EP');
	} else if (tracks.every(it => /^soundtrack$/i.test(it.identifiers.RELEASETYPE))) {
	  releaseType = getReleaseIndex('Soundtrack');
	  tags.add('score');
	  composerEmphasis = true;
	}

	// Processing artists: recognition, splitting and dividing to categores
	var roleCollisions = [
	  [4, 5], // main
	  [0, 4], // guest
	  [], // remixer
	  [], // composer
	  [], // conductor
	  [], // DJ/compiler
	  [], // producer
	];
	isVA = vaParser.test(release.artist);
	var artists = [];
	for (i = 0; i < 7; ++i) artists[i] = [];

	if (!isVA) addArtists(0, yadg_prefil = spliceGuests(release.artist));
	var albumGuests = artists[1].slice();

	featParsers.slice(3).forEach(function(rx, __a, ndx) {
	  matches = rx.exec(release.album);
	  if (matches != null && (ndx < 2 || splitArtists(matches[1]).every((artist) => looksLikeTrueName(artist, 1)))) {
		addArtists(1, matches[1]);
		addMessage('Warning: featured artist(s) in album title (' + release.album + ')', 'ua-warning');
		release.album = release.album.replace(rx, '');
	  }
	});
	remixParsers.slice(3).forEach(function(rx) {
	  if (rx.test(release.album)) addArtists(2, RegExp.$1);
	})
	if (isVA && (/\s+\(compiled\s+by\s+(.*?)\)\s*$/i.test(release.album)
		|| /\s+compiled\s+by\s+(.*?)\s*$/i.test(release.album))) {
	  addArtists(5, RegExp.$1);
	  if (!releaseType) releaseType = getReleaseIndex('Compilation');
	}

	for (iter of tracks) {
	  addTrackPerformers(iter.track_artist);
	  addTrackPerformers(iter.performer);
	  addArtists(2, iter.remixer);
	  addArtists(3, iter.composer);
	  addArtists(4, iter.conductor);
	  addArtists(5, iter.compiler);
	  addArtists(6, iter.producer);

	  if (iter.title) {
		featParsers.slice(3).forEach(function(rx, __a, ndx) {
		  matches = rx.exec(iter.title);
		  if (matches != null && (ndx < 2 || splitArtists(matches[1]).every((artist) => looksLikeTrueName(artist, 1)))) {
			iter.track_artist = (!isVA && (!iter.track_artist || iter.track_artist.includes(matches[1])) ?
								 iter.artist : iter.track_artist) + ' feat. ' + matches[1];
			addArtists(1, matches[1]);
			addMessage('Warning: featured artist(s) in track title (#' + iter.tracknumber + ': ' + iter.title + ')', 'ua-warning');
			iter.title = iter.title.replace(rx, '');
		  }
		});
		remixParsers.slice(3).forEach(rx => { if (rx.test(iter.title)) addArtists(2, RegExp.$1) });
	  }
	}
	for (i = 0; i < Math.round(tracks.length / 2); ++i) splitAmpersands();

	function addArtists(ndx, str) {
	  if (str) splitArtists(str).forEach(function(artist) {
		artist = ndx != 0 ? strip(artist) : guessOtherArtists(artist);
		if (artist.length > 0 && !pseudoArtistParsers.some(rx => rx.test(artist))
			&& !artists[ndx].includesCaseless(artist)
			&& !roleCollisions[ndx].some(n => artists[n].includesCaseless(artist))) artists[ndx].push(artist);
	  });
	}
	function addTrackPerformers(str) {
	  if (str) splitArtists(spliceGuests(str, 1)).forEach(function(artist) {
		artist = guessOtherArtists(artist);
		if (artist.length > 0 && !pseudoArtistParsers.some(rx => rx.test(artist))
			&& !artists[0].includesCaseless(artist)
			&& (isVA || !artists[1].includesCaseless(artist))) artists[isVA ? 0 : 1].push(artist);
	  });
	}
	function splitArtists(str) {
	  var result = [str];
	  multiArtistParsers.forEach(function(multiArtistParser) {
		for (i = result.length; i > 0; --i) {
		  var j = result[i - 1].split(multiArtistParser);
		  if (j.length >= 2 && j.every(twoOrMore)
			  && !j.some(artist => pseudoArtistParsers.some(rx => rx.test(artist)))
			  	&& !getSiteArtist(result[i - 1])) result.splice(i - 1, 1, ...j);
		}
	  });
	  return result;
	}
	function splitAmpersands() {
	  for (var ndx = 0; ndx < artists.length; ++ndx) {
		ampersandParsers.forEach(function(ampersandParser) {
		  for (var i = artists[ndx].length; i > 0; --i) {
			var j = artists[ndx][i - 1].split(ampersandParser);
			if (j.length >= 2 && j.every(twoOrMore) && !getSiteArtist(artists[ndx][i - 1])
				&& (j.some(it1 => artists.some(it2 => it2.includesCaseless(it1))) || j.every(looksLikeTrueName))) {
			  artists[ndx].splice(i - 1, 1, ...j.filter(function(artist) {
				return !artists[ndx].includesCaseless(artist)
					&& !pseudoArtistParsers.some(rx => rx.test(artist))
					&& !roleCollisions[ndx].some(n => artists[n].includesCaseless(artist));
			  }));
			}
		  }
		});
	  }
	}
	function spliceGuests(str, level = 1) {
	  (level ? featParsers.slice(level) : featParsers).forEach(function(it) {
		if (it.test(str)) {
		  addArtists(1, RegExp.$1);
		  str = str.replace(it, '');
		}
	  });
	  return str;
	}
	function guessOtherArtists(name) {
	  otherArtistsParsers.forEach(function(it) {
		if (!it[0].test(name)) return;
		addArtists(it[1], RegExp.$2);
		name = RegExp.$1;
	  });
	  return strip(name);
	}
	function getSiteArtist(artist) {
	  if (!artist || notSiteArtistsCache.includesCaseless(artist)) return null;
	  var key = Object.keys(siteArtistsCache).find(it => it.toLowerCase() == artist.toLowerCase());
	  if (key) return siteArtistsCache[key];
	  xhr.open('GET', 'https://' + document.domain + '/ajax.php?action=artist&artistname=' + encodeURIComponent(artist), false);
	  xhr.send();
	  if (xhr.readyState != 4 || xhr.status != 200) {
		console.log('getSiteArtist("' + artist + '"): XMLHttpRequest readyState:' + xhr.readyState + ' status:' + xhr.status);
		return undefined; // error
	  }
	  var response = JSON.parse(xhr.responseText);
	  if (response.status != 'success') {
		notSiteArtistsCache.pushUniqueCaseless(artist);
		return null;
	  }
	  return (siteArtistsCache[artist] = response.response);
	}
	function twoOrMore(artist) { return artist.length >= 2 && !pseudoArtistParsers.some(rx => rx.test(artist)) };
	function looksLikeTrueName(artist, index = 0) {
	  return (index == 0 || !/^(?:his\b|her\b|Friends$|Strings$)/i.test(artist)) && artist.split(/\s+/).length >= 2
	  	&& !pseudoArtistParsers.some(rx => rx.test(artist)) || getSiteArtist(artist);
	}
	function strip(art) { return artistStrips.reduce((acc, it) => acc.replace(it, ''), art) }
	function getRealTrackArtist(track) {
	  if (typeof track != 'object') return null;
	  if (track.track_artist == release.artist) return undefined;
	  var trackArtist = track.track_artist;
	  if (trackArtist && !isVA) {
		let trackArtists = [], trackGuests = [], ta = trackArtist;
		featParsers.slice(1).forEach(function(it) {
		  if (!it.test(ta)) return;
		  trackGuests.pushUniqueCaseless(RegExp.$1);
		  ta = ta.replace(it, '');
		});
		splitArtists(ta).forEach(function(artist) {
		  otherArtistsParsers.forEach(it => { if (it[0].test(artist)) artist = RegExp.$1 });
		  artist = strip(artist);
		  if (artist.length > 0 && !pseudoArtistParsers.some(rx => rx.test(artist))) trackArtists.pushUniqueCaseless(artist);
		});
		splitAmpersands(trackArtists);
		splitAmpersands(trackGuests);
		if (trackArtists.equalTo(artists[0]) && trackGuests.equalTo(albumGuests)) trackArtist = undefined;

		function splitAmpersands(array) {
		  ampersandParsers.forEach(function(ampersandParser) {
			for (var i = array.length; i > 0; --i) {
			  var j = array[i - 1].split(ampersandParser);
			  if (j.length >= 2 && j.every(twoOrMore) && !getSiteArtist(array[i - 1])
				  && (j.some(it1 => artists.some(it2 => it2.includesCaseless(it1))) || j.every(looksLikeTrueName))) {
				array.splice(i - 1, 1, ...j.filter(function(artist) {
				  return !array.includesCaseless(artist) && !pseudoArtistParsers.some(rx => rx.test(artist));
				}));
			  }
			}
		  });
		}
	  }
	  return trackArtist;
	}

	if (elementWritable(document.getElementById('artist'))) {
	  let artistIndex = 0;
	  catLoop: for (i = 0; i < 7; ++i) for (iter of artists[i]
		  .filter(artist => !roleCollisions[i].some(n => artists[n].includesCaseless(artist)))
		  .sort((a, b) => a.localeCompare(b))) {
		if (isUpload) {
		  var id = 'artist';
		  if (artistIndex > 0) id += '_' + artistIndex;
		  while ((ref = document.getElementById(id)) == null) addArtistField();
		} else {
		  while ((ref = document.querySelectorAll('input[name="artists[]"]')).length <= artistIndex) addArtistField();
		  ref = ref[artistIndex];
		}
		if (ref == null) throw new Error('Failed to allocate artist fields');
		ref.value = iter;
		ref.nextElementSibling.value = i + 1;
		if (++artistIndex >= 200) break catLoop;
	  }
	  if (overwrite && artistIndex > 0) while (document.getElementById('artist_' + artistIndex) != null) {
		removeArtistField();
	  }
	}

	// Processing album title
	const remasterParsers = [
	  /\s+\(((?:Remaster(?:ed)?|Reissu(?:ed)?|Deluxe|Enhanced|Expanded|Limited)\b[^\(\)]*|[^\(\)]*\b(?:Edition|Version|Promo|Release))\)$/i,
	  /\s+\[((?:Remaster(?:ed)?|Reissu(?:ed)?|Deluxe|Enhanced|Expanded|Limited)\b[^\[\]]*|[^\[\]]*\b(?:Edition|Version|Promo|Release))\]$/i,
	  /\s+-\s+([^\[\]\(\)\-\−\—\–]*\b(?:(?:Remaster(?:ed)?|Bonus\s+Track)\b[^\[\]\(\)\-\−\—\–]*|Reissue|Edition|Version|Promo|Enhanced|Release))$/i
	];
	const mediaParsers = [
	  [/\s+(?:\[(?:LP|Vinyl|12"|7")\]|\((?:LP|Vinyl|12"|7")\))$/, 'Vinyl'],
	  [/\s+(?:\[SA-?CD\]|\(SA-?CD\))$/, 'SACD'],
	  [/\s+(?:\[(?:Blu[\s\-\−\—\–]?Ray|BD|BRD?)\]|\((?:Blu[\s\-\−\—\–]?Ray|BD|BRD?)\))$/, 'Blu-Ray'],
	  [/\s+(?:\[DVD(?:-?A)?\]|\(DVD(?:-?A)?\))$/, 'DVD'],
	];
	const releaseTypeParsers = [
	  [/\s+(?:-\s+Single|\[Single\]|\(Single\))$/i, 'Single', true, true],
	  [/\s+(?:(?:-\s+)?EP|\[EP\]|\(EP\))$/, 'EP', true, true],
	  [/\s+\((?:Live|En\s+directo?|Ao\s+Vivo)\b[^\(\)]*\)$/i, 'Live album', false, false],
	  [/\s+\[(?:Live|En\s+directo?|Ao\s+Vivo)\b[^\[\]]*\]$/i, 'Live album', false, false],
	  [/(?:^Live\s+(?:[aA]t|[Ii]n)\b|^Directo?\s+[Ee]n\b|\bUnplugged\b|\bAcoustic\s+Stage\b|\s+Live$)/, 'Live album', false, false],
	  [/\b(?:(?:Best [Oo]f|Greatest\s+Hits|Complete\s+(.+?\s+)(?:Albums|Recordings))\b|Collection$)/, 'Anthology', false, false],
	];
	var album = release.album;
	releaseTypeParsers.forEach(function(it) {
	  if (it[0].test(album)) {
		if (it[2] || !releaseType) releaseType = getReleaseIndex(it[1]);
		if (it[3]) album = album.replace(it[0], '');
	  }
	});
	rx = '\\b(?:Soundtrack|Score|Motion\\s+Picture|Series|Television|Original(?:\\s+\\w+)?\\s+Cast|Music\\s+from|(?:Musique|Bande)\\s+originale)\\b';
	if (reInParenthesis(rx).test(album) || reInBrackets(rx).test(album)) {
	  if (!releaseType) releaseType = getReleaseIndex('Soundtrack');
	  tags.add('score');
	  composerEmphasis = true;
	}
	remixParsers.forEach(function(rx) {
	  if (rx.test(album) && !releaseType) releaseType = getReleaseIndex('Remix');
	});
	remasterParsers.forEach(function(rx) {
	  if (rx.test(album)) {
		album = album.replace(rx, '');
		editionTitle = RegExp.$1;
	  }
	});
	mediaParsers.forEach(function(it) {
	  if (it[0].test(album)) {
		album = album.replace(it[0], '');
		media = it[1];
	  }
	});
	if (elementWritable(ref = document.getElementById('title') || document.querySelector('input[name="title"]'))) {
	  ref.value = album;
	}

	if (yadg_prefil) yadg_prefil += ' ';
	yadg_prefil += album;
	if (elementWritable(ref = document.getElementById('yadg_input'))) {
	  ref.value = yadg_prefil || '';
	  if (yadg_prefil && (ref = document.getElementById('yadg_submit')) != null && !ref.disabled) ref.click();
	}

	if (!release.album_year && tracks.every(function(tr) {
		  return tr.identifiers.PUBYEAR && tr.identifiers.PUBYEAR == tracks[0].identifiers.PUBYEAR;
		})) release.album_year = parseInt(tracks[0].identifiers.PUBYEAR);
	if (elementWritable(ref = document.getElementById('year'))) {
	  ref.value = release.album_year || '';
	}
	i = release.release_date && extractYear(release.release_date);
	if (elementWritable(ref = document.getElementById('remaster_year'))
		|| !isUpload && i > 0 && (ref = document.querySelector('input[name="year"]')) != null && !ref.disabled) {
	  ref.value = i || '';
	}
	//if (tracks.every(it => it.identifiers.EXPLICIT == '0')) editionTitle = 'Clean' + (editionTitle ? ' / ' + editionTitle : '');
	if (!editionTitle && (tracks.every(it => it.title.endsWith(' (Remastered)') || it.title.endsWith(' [Remastered]')))) {
	  editionTitle = 'Remastered';
	}
	if (elementWritable(ref = document.getElementById('remaster_title'))) {
	  ref.value = editionTitle || '';
	}
	if (elementWritable(ref = document.getElementById('remaster_record_label') || document.querySelector('input[name="recordlabel"]'))) {
	  ref.value = release.label && release.label.split(/\s*;\s*/g)
		.map(it => isVA || it != release.artist ? it : 'self-released').join(' / ') || '';
	}
	if (elementWritable(ref = document.getElementById('remaster_catalogue_number') || document.querySelector('input[name="cataloguenumber"]'))) {
	  let barcode = tracks.every(function(it, ndx, arr) {
		return it.identifiers.BARCODE && it.identifiers.BARCODE == arr[0].identifiers.BARCODE;
	  }) && tracks[0].identifiers.BARCODE;
	  ref.value = release.catalogs.length >= 1 && release.catalogs.map(it => it.replace(/\s*;\s*/g, ' / ')).join(' / ')
	  	|| barcode || '';
	}
	var br_isSet = (ref = document.getElementById('bitrate')) != null && ref.value;
	if (elementWritable(ref = document.getElementById('format'))) {
	  ref.value = release.codec || '';
	  ref.onchange(); //exec(function() { Format() });
	}
	if (isRequestNew) {
	  if (prefs.always_request_perfect_flac) reqSelectFormats('FLAC');
	  else if (release.codec) reqSelectFormats(release.codec);
	}
	var sel;
	if (release.encoding == 'lossless') {
	  sel = release.bds.includes(24) ? '24bit Lossless' : 'Lossless';
	} else if (release.bitrates.length >= 1) {
	  let lame_version = release.codec == 'MP3' && /^LAME(\d+)\.(\d+)/i.test(release.vendor) ?
		  parseInt(RegExp.$1) * 1000 + parseInt(RegExp.$2) : undefined;
	  if (release.codec == 'MP3' && release.codec_profile == 'VBR V0') {
		sel = lame_version >= 3094 ? 'V0 (VBR)' : 'APX (VBR)'
	  } else if (release.codec == 'MP3' && release.codec_profile == 'VBR V1') {
		sel = 'V1 (VBR)'
	  } else if (release.codec == 'MP3' && release.codec_profile == 'VBR V2') {
		sel = lame_version >= 3094 ? sel = 'V2 (VBR)' : 'APS (VBR)'
	  } else if (release.bitrates.length == 1 && [192, 256, 320].includes(Math.round(release.bitrates[0]))) {
		sel = Math.round(release.bitrates[0]);
	  } else {
		sel = 'Other';
	  }
	}
	if ((ref = document.getElementById('bitrate')) != null && !ref.disabled && (overwrite || !br_isSet)) {
	  ref.value = sel || '';
	  ref.onchange(); //exec(function() { Bitrate() });
	  if (sel == 'Other' && (ref = document.getElementById('other_bitrate')) != null) {
		ref.value = Math.round(release.bitrates.length == 1 ? release.bitrates[0] : albumBitrate);
		if ((ref = document.getElementById('vbr')) != null) ref.checked = release.bitrates.length > 1;
	  }
	}
	if (isRequestNew) {
	  if (prefs.always_request_perfect_flac) {
		reqSelectBitrates('Lossless', '24bit Lossless');
	  } else if (sel) reqSelectBitrates(sel);
	}
	if (release.media) {
	  sel = undefined;
	  [
		[/\b(?:WEB|File|Download|digital\s+media)\b/i, 'WEB'],
		[/\bCD\b/, 'CD'],
		[/\b(?:SA-?CD|[Hh]ybrid)\b/, 'SACD'],
		[/\b(?:[Bb]lu[\-\−\—\–\s]?[Rr]ay|BRD?|BD)\b/, 'Blu-Ray'],
		[/\bDVD(?:-?A)?\b/, 'DVD'],
		[/\b(?:[Vv]inyl\b|LP\b|12"|7")/, 'Vinyl'],
	  ].forEach(k => { if (k[0].test(release.media)) sel = k[1] });
	  media = sel || media;
	}
	if (!media) {
	  if (tracks.every(isRedBook)) {
		addMessage('Info: media not determined - CD estimated', 'ua-info');
		media = 'CD';
	  } else if (tracks.some(t => t.bd > 16 || (t.sr > 0 && t.sr != 44100) || t.samples > 0 && t.samples % 588 != 0)) {
		addMessage('Info: media not determined - NOT CD', 'ua-info');
	  }
	} else if (media != 'CD' && tracks.every(isRedBook)) {
	  addMessage('Info: CD as source media is estimated (' + media + ')', 'ua-info');
	}
	if (elementWritable(ref = document.getElementById('media'))) ref.value = media || '';
	if (isRequestNew) {
	  if (prefs.always_request_perfect_flac) reqSelectMedias('WEB', 'CD', 'Blu-Ray', 'DVD', 'SACD')
	  else if (media) reqSelectMedias(media);
	}
	function isRedBook(t) {
	  return t.bd == 16 && t.sr == 44100 && t.channels == 2 && t.samples > 0 && t.samples % 588 == 0;
	}
	if (media == 'WEB') for (iter of tracks) {
	  if (iter.duration > 29.5 && iter.duration < 30.5) {
		addMessage('Warning: track ' + iter.tracknumber + ' possible preview', 'ua-warning');
	  }
	}
	if (tracks.every(it => it.identifiers.ORIGINALFORMAT && it.identifiers.ORIGINALFORMAT.includes('DSD'))) {
	  isFromDSD = true;
	}
	// Release type
	if (!releaseType) {
	  if (tracks.every(it => it.title.endsWith(' (Live)') || it.title.endsWith(' [Live]'))) {
		releaseType = getReleaseIndex('Live album');
	  } else if (/\b(?:Mixtape)\b/i.test(release.album)) {
		releaseType = getReleaseIndex('Mixtape');
	  } else if (isVA) {
		releaseType = getReleaseIndex('Compilation');
	  } else if (tracks.every(it => it.identifiers.COMPILATION == 1)) {
		releaseType = getReleaseIndex('Anthology');
	  }
	}
	if ((!releaseType || releaseType == 5) && totalTime <= prefs.EP_threshold && tracks.every(function(track) {
	  const rxs = [/\s+\([^\(\)]+\)\s*$/, /\s+\[[^\[\]]+\]\s*$/];
	  return rxs.reduce((acc, rx) => acc.replace(rx, ''), track.title)
		== rxs.reduce((acc, rx) => acc.replace(rx, ''), tracks[0].title);
	})) {
	  releaseType = getReleaseIndex('Single');
	}
	if (!releaseType) if (totalTime > 0 && totalTime <= prefs.single_threshold) {
	  releaseType = getReleaseIndex('Single');
	} else if (totalTime > 0 && totalTime <= prefs.EP_threshold) {
	  releaseType = getReleaseIndex('EP');
	}
	if ((ref = document.getElementById('releasetype')) != null && !ref.disabled
		&& (overwrite || ref.value == 0 || ref.value == '---')) ref.value = releaseType || getReleaseIndex('Album');
	// Tags
	if (release.genres.length > 0) {
	  const classicalGenreParsers = [
		/\b(?:Classical|Classique|Klassik|Symphony|Symphonic(?:al)?|Operas?|Operettas?|Ballets?|(?:Violin|Cello|Piano)\s+Solos?|Chamber|Choral|Choirs?|Orchestral|Etudes?|Duets|Concertos?|Cantatas?|Requiems?|Passions?|Mass(?:es)?|Oratorios?|Poems?|Sacred|Secular|Vocal\s+Music)\b/i,
	  ];
	  release.genres.forEach(function(genre) {
		classicalGenreParsers.forEach(function(classicalGenreParser) {
		  if (classicalGenreParser.test(genre) && !/\b(?:metal|rock|pop)\b/i.test(genre)) {
			composerEmphasis = true;
			isClassical = true
		  }
		});
		if (/\b(?:Jazz|Vocal)\b/i.test(genre) && !/\b(?:Nu|Future|Acid)[\s\-\−\—\–]*Jazz\b/i.test(genre)
		   && !/\bElectr(?:o|ic)[\s\-\−\—\–]?Swing\b/i.test(genre)) {
		  composerEmphasis = true;
		}
		if (/\b(?:Soundtracks?|Score|Films?|Games?|Video|Series?|Theatre|Musical)\b/i.test(genre)) {
		  if (!releaseType) releaseType = getReleaseIndex('Soundtrack');
		  composerEmphasis = true;
		}
		if (/\b(?:Christmas\s+Music)\b/i.test(genre)) {
		  composerEmphasis = true;
		}
	  	tags.add(...genre.split(/\s*\|\s*/));
	  });
	  if (release.genres.length > 1) {
		addMessage('Warning: inconsistent genre accross album: ' + release.genres, 'ua-warning');
	  }
	}
	if (prefs.estimate_decade_tag && (isNaN(totalTime) || totalTime < 2 * 60 * 60)
		&& release.album_year > 1900 && [1, 3, 5, 9, 13, undefined].includes(releaseType)
		&& !/\b(?:Remaster(?:ed)?|Reissue|Anniversary|Collector(?:'?s)?)\b/i.test(editionTitle))
	  		tags.add(Math.floor(release.album_year/10) * 10 + 's'); // experimental
	if (release.country) {
	  if (!excludedCountries.some(it => it.test(release.country))) tags.add(release.country);
	}
	if (elementWritable(ref = document.getElementById('tags'))) {
	  ref.value = tags.toString();
	  if (artists[0].length == 1 && prefs.fetch_tags_from_artist > 0) setTimeout(function() {
		var artist = getSiteArtist(artists[0][0]);
		if (!artist) return;
		tags.add(...artist.tags.sort((a, b) => b.count - a.count).map(it => it.name).slice(0, prefs.fetch_tags_from_artist));
		var ref = document.getElementById('tags');
		ref.value = tags.toString();
	  }, 3000);
	}

	if (isClassical && !tracks.every(it => it.composer)) {
	  addMessage('Warning: all tracks composers must be defined for clasical music' + ruleLink('2.3.17'), 'ua-warning', true);
	  //return false;
	}
	if (!composerEmphasis && !prefs.keep_meaningles_composers) {
	  document.querySelectorAll('input[name="artists[]"]').forEach(function(i) {
		if (['4', '5'].includes(i.nextElementSibling.value)) i.value = '';
	  });
	}
	const doubleParsParsers = [
	  /\(+(\([^\(\)]*\))\)+/,
	  /\[+(\[[^\[\]]*\])\]+/,
	  /\{+(\{[^\{\}]*\})\}+/,
	];
	for (iter of tracks) {
	  doubleParsParsers.forEach(function(rx) {
	  	if (rx.test(iter.title)) {
		  addMessage('Warning: doubled parentheses in track #' + iter.tracknumber +
					 ' title ("' + iter.title + '")', 'ua-warning');
		  //iter.title.replace(rx, RegExp.$1);
		}
	  });
	}
	if (tracks.length > 1 && array_homogenous(tracks.map(k => k.title))) {
	  addMessage('Warning: all tracks having same title: ' + tracks[0].title, 'ua-warning');
	}
	if (isUpload) findPreviousUploads();
	// Album description
	url = release.urls.length == 1 && release.urls[0] || getStoreUrl();
	const vinylTest = /^((?:Vinyl|LP) rip by\s+)(.*)$/im;
	const vinyltrackParser = /^([A-Z])[\-\.\s]?((\d+)(?:\.\d+)?)$/;
	const classicalWorkParsers = [
	  /^(.*\S):\s+(.*)$/,
	  /^(.+?):\s+([IVXC]+\.\s+.*)$/,
	];
	var description;
	if (isRequestNew || isRequestEdit) { // request
	  description = []
	  if (release.release_date) {
		i = new Date(release.release_date);
		let today = new Date(new Date().toDateString());
		description.push((isNaN(i) || i < today ? 'Released' : 'Releasing') + ' ' +
			(isNaN(i) ? release.release_date : i.toDateString()));
		if ((ref = document.getElementById('tags')) != null && !ref.disabled) {
		  let tags = new TagManager(ref.value);
		  if (prefs.upcoming_tags && i >= today) tags.add(prefs.upcoming_tags);
		  ref.value = tags.toString();
		}
	  }
	  let summary = '';
	  if (totalDiscs > 1) summary += totalDiscs + 'discs, ';
	  summary += tracks.length + ' track(s)';
	  if (totalTime > 0) summary += ', ' + makeTimeString(totalTime);
	  description.push(summary);
	  if (url) description.push('[url]' + url + '[/url]');
	  if (release.catalogs.length == 1 && /^\d{10,}$/.test(release.catalogs[0])
		  || tracks.every(it => it.identifiers.BARCODE && it.identifiers.BARCODE == tracks[0].identifiers.BARCODE)
			&& /^\d{10,}$/.test(tracks[0].identifiers.BARCODE)) {
		description.push('[url=https://www.google.com/search?q=' + RegExp.lastMatch + ']Find more stores...[/url]');
	  }
	  if (release.comments.length == 1) description.push(release.comments[0]);
	  description = genAlbumHeader().concat(description.join('\n\n'));
	  if (description.length > 0) {
		ref = document.getElementById('description');
		if (elementWritable(ref)) {
		  ref.value = description;
		} else if (isRequestEdit && ref != null && !ref.disabled) {
		  ref.value = ref.textLength > 0 ? ref.value.concat('\n\n', description) : ref.value = description;
		  preview(0);
		}
	  }
	} else { // upload
	  description = '';
	  let vinylRipInfo;
	  if (release.comments.length == 1 && release.comments[0]) {
		description = '\n\n';
		if ((matches = vinylTest.exec(release.comments[0])) != null) {
		  vinylRipInfo = release.comments[0].slice(matches.index).trim().split(/(?:[ \t]*\r?\n)+/);
		  description += release.comments[0].slice(0, matches.index).trim();
		} else description += release.comments[0];
	  }
	  if (elementWritable(ref = document.getElementById('album_desc'))) {
		ref.value = genPlaylist().concat(description);
		preview(0);
	  }
	  if ((ref = document.getElementById('body')) != null && !ref.disabled) {
		if (ref.textLength == 0) ref.value = genPlaylist().concat(description); else {
		  let editioninfo = '';
		  if (editionTitle) {
			editioninfo = '[size=5][b]' + editionTitle;
			if (release.release_date && (i = extractYear(release.release_date)) > 0) editioninfo += ' (' + i + ')';
			editioninfo += '[/b][/size]\n\n';
		  }
		  ref.value = ref.value.concat('\n\n', editioninfo, genPlaylist(false, false), description);
		}
		preview(0);
	  }
	  // Release description
	  var lineage = '', comment = '', drinfo, srcinfo;
	  if (elementWritable(ref = document.getElementById('release_samplerate'))) {
		ref.value = Object.keys(release.srs).length == 1 ? Math.floor(Object.keys(release.srs)[0] / 1000) :
		Object.keys(release.srs).length > 1 ? '999' : null;
	  }
	  if (Object.keys(release.srs).length > 0) {
		let kHz = Object.keys(release.srs).sort((a, b) => release.srs[b] - release.srs[a])
			.map(f => f / 1000).join('/').concat('kHz');
		if (release.bds.some(bd => bd > 16)) {
		  drinfo = '[hide=DR' + (release.drs.length == 1 ? release.drs[0] : '') + '][pre][/pre]';
		  if (media == 'Vinyl') {
			let hassr = ref == null || Object.keys(release.srs).length > 1;
			lineage = hassr ? kHz + ' ' : '';
			if (vinylRipInfo) {
			  vinylRipInfo[0] = vinylRipInfo[0].replace(vinylTest, '$1[color=blue]$2[/color]');
			  if (hassr) { vinylRipInfo[0] = vinylRipInfo[0].replace(/^Vinyl\b/, 'vinyl') }
			  lineage += vinylRipInfo[0] + '\n\n[u]Lineage:[/u]' +
				vinylRipInfo.slice(1).map(function(l) {
				  return '\n' + l
					// russian translation
					.replace('Код класса состояния винила:', 'Vinyl condition class:')
					.replace('Устройство воспроизведения:', 'Turntable:')
					.replace('Головка звукоснимателя:', 'Cartridge:')
					.replace('Предварительный усилитель:', 'Preamplifier:')
					.replace('АЦП:', 'ADC:')
					.replace('Программа-оцифровщик:', 'Software:')
					.replace('Обработка:', 'Post-processing:');
				}).join('');
			} else {
			  lineage += (hassr ? 'Vinyl' : ' vinyl') + ' rip by [color=blue][/color]\n\n[u]Lineage:[/u]\n';
			}
			drinfo += '\n\n[img][/img]\n[img][/img]\n[img][/img][/hide]';
		  } else if (['Blu-Ray', 'DVD', 'SACD'].includes(media)) {
			lineage = ref ? '' : kHz;
			if (release.channels) add_channel_info();
			if (media == 'SACD' || isFromDSD) {
			  lineage += ' from DSD64';
			  if (prefs.sacd_decoder) lineage += ' using ' + prefs.sacd_decoder;
			  lineage += '\nOutput gain +0dB';
			}
			drinfo += '[/hide]';
			//add_rg_info();
		  } else { // WEB Hi-Res
			if (ref == null || Object.keys(release.srs).length > 1) lineage = kHz;
			if (release.channels && release.channels != 2) add_channel_info();
			if (isFromDSD) {
			  lineage += ' from DSD64';
			  if (prefs.sacd_decoder) lineage += ' using ' + prefs.sacd_decoder;
			  lineage += '\nOutput gain +0dB';
			} else {
			  add_dr_info();
			}
			//if (lineage.length > 0) add_rg_info();
			if (release.bds.length > 1) release.bds.filter(bd => bd != 24).forEach(function(bd) {
			  let hybrid_tracks = tracks.filter(it => it.bd == bd).sort(trackComparer).map(function(it) {
				return (totalDiscs > 1 && it.discnumber ? it.discnumber + '-' : '').concat(it.tracknumber);
			  });
			  if (hybrid_tracks.length < 1) return;
			  if (lineage) lineage += '\n';
			  lineage += 'Note: track';
			  if (hybrid_tracks.length > 1) lineage += 's';
			  lineage += ' #' + hybrid_tracks.join(', ') +
				(hybrid_tracks.length > 1 ? ' are' : ' is') + ' ' + bd + 'bit lossless';
			});
			if (Object.keys(release.srs).length == 1 && Object.keys(release.srs)[0] == 88200 || isFromDSD) {
			  drinfo += '[/hide]';
			} else {
			  drinfo = null;
			}
		  }
		} else { // 16bit or lossy
		  if (Object.keys(release.srs).some(f => f != 44100)) lineage = kHz;
		  if (release.channels && release.channels != 2) add_channel_info();
		  //add_dr_info();
		  //if (lineage.length > 0) add_rg_info();
		  if (release.codec == 'MP3' && release.vendor) {
			// TODO: parse mp3 vendor string
		  } else if (['AAC', 'Opus', 'Vorbis'].includes(release.codec) && release.vendor) {
			let _encoder_settings = release.vendor;
			if (release.codec == 'AAC' && /^qaac\s+[\d\.]+/i.test(release.vendor)) {
			  let enc = [];
			  if (matches = release.vendor.match(/\bqaac\s+([\d\.]+)\b/i)) enc[0] = matches[1];
			  if (matches = release.vendor.match(/\bCoreAudioToolbox\s+([\d\.]+)\b/i)) enc[1] = matches[1];
			  if (matches = release.vendor.match(/\b(AAC-\S+)\s+Encoder\b/i)) enc[2] = matches[1];
			  if (matches = release.vendor.match(/\b([TC]VBR|ABR|CBR)\s+(\S+)\b/)) { enc[3] = matches[1]; enc[4] = matches[2]; }
			  if (matches = release.vendor.match(/\bQuality\s+(\d+)\b/i)) enc[5] = matches[1];
			  _encoder_settings = 'Converted by Apple\'s ' + enc[2] + ' encoder (' + enc[3] + '-' + enc[4] + ')';
			}
			if (lineage) lineage += '\n\n';
			lineage += _encoder_settings;
		  }
		}
	  }
	  function add_dr_info() {
		if (release.drs.length != 1 || document.getElementById('release_dynamicrange') != null) return false;
		if (lineage.length > 0) lineage += ' | ';
		if (release.drs[0] < 4) lineage += '[color=red]';
		lineage += 'DR' + release.drs[0];
		if (release.drs[0] < 4) lineage += '[/color]';
		return true;
	  }
	  function add_rg_info() {
		if (release.rgs.length != 1) return false;
		if (lineage.length > 0) lineage += ' | ';
		lineage += 'RG'; //lineage += 'RG ' + rgs[0];
		return true;
	  }
	  function add_channel_info() {
		if (!release.channels) return false;
		let chi = getChanString(release.channels);
		if (lineage.length > 0 && chi.length > 0) lineage += ', ';
		lineage += chi;
		return chi.length > 0;
	  }
	  if (url) srcinfo = '[url]' + url + '[/url]';
	  if ((ref = document.getElementById('release_lineage')) != null) {
		if (elementWritable(ref)) {
		  if (drinfo) comment = drinfo;
		  if (lineage && srcinfo) lineage += '\n\n';
		  if (srcinfo) lineage += srcinfo;
		  ref.value = lineage;
		  preview(1);
		}
	  } else {
		comment = lineage;
		if (comment && drinfo) comment += '\n\n';
		if (drinfo) comment += drinfo;
		if (comment && srcinfo) comment += '\n\n';
		if (srcinfo) comment += srcinfo;
	  }
	  if (elementWritable(ref = document.getElementById('release_desc'))) {
		ref.value = comment;
		if (comment.length > 0) preview(isNWCD ? 2 : 1);
	  }
	  if (release.encoding == 'lossless' && release.codec == 'FLAC'
		  && release.bds.includes(24) && release.dirpaths.length == 1) {
		var uri = new URL(release.dirpaths[0] + '\\foo_dr.txt');
		GM_xmlhttpRequest({
		  method: 'GET',
		  url: uri.href,
		  responseType: 'blob',
		  onload: function(response) {
			if (response.readyState != 4 || !response.responseText) return;
			var rlsDesc = document.getElementById('release_lineage') || document.getElementById('release_desc');
			if (rlsDesc == null) return;
			var value = rlsDesc.value;
			matches = value.match(/(^\[hide=DR\d*\]\[pre\])\[\/pre\]/im);
			if (matches == null) return;
			var index = matches.index + matches[1].length;
			rlsDesc.value = value.slice(0, index).concat(response.responseText, value.slice(index));
		  }
		});
	  }
	}
	if (!isNWCD && elementWritable(document.getElementById('image') || document.querySelector('input[name="image"]'))) {
	  if (tracks.every(track => track.identifiers.IMGURL && track.identifiers.IMGURL == tracks[0].identifiers.IMGURL)) {
		setImage(tracks[0].identifiers.IMGURL);
	  } else {
		getCoverOnline(/^https?:\/\/(\w+\.)?discogs\.com\/release\/[\w\-]+\/?$/i.test(url) ? url.concat('/images') : url);
	  }
	}
	if (elementWritable(ref = document.getElementById('release_dynamicrange'))) {
	  ref.value = release.drs.length == 1 ? release.drs[0] : '';
	}
	if (isRequestNew && prefs.request_default_bounty > 0) {
	  let amount = prefs.request_default_bounty < 1024 ? prefs.request_default_bounty : prefs.request_default_bounty / 1024;
	  if ((ref = document.getElementById('amount_box')) != null && !ref.disabled) ref.value = amount;
	  if ((ref = document.getElementById('unit')) != null && !ref.disabled) {
		ref.value = prefs.request_default_bounty < 1024 ? 'mb' : 'gb';
	  }
	  exec(function() { Calculate() });
	}
	if (prefs.clean_on_apply) clipBoard.value = '';
	prefs.save();
	return true;

	function genPlaylist(pad = true, header = true) {
	  if (prefs.tracklist_style <= 0) return null;
	  var playlist = '';
	  if (tracks.length > 1) {
		if (pad && isRED) playlist += '[pad=5|0|0|0]';
		if (header) playlist += genAlbumHeader();
		playlist += '[size=4][color=' + prefs.tracklist_head_color + '][b]Tracklisting[/b][/color][/size]';
		if (pad && isRED) playlist += '[/pad]';
		playlist += '\n'; //'[hr]';
		let lastDisc, lastSubtitle, lastWork, lastSide, vinylTrackWidth;
		let block = 0, classicalWorks = new Map();
		if (composerEmphasis /*isClassical*/ && !tracks.some(it => it.discsubtitle)) {
		  tracks.forEach(function(track) {
			if (!track.composer) return;
			(isClassical ? classicalWorkParsers : classicalWorkParsers.slice(1)).forEach(function(classicalWorkParser) {
			  if (track.classical_work || !classicalWorkParser.test(track.title)) return;
			  classicalWorks.set(track.classical_work = RegExp.$1, {});
			  track.classical_title = RegExp.$2;
			});
		  });
		  for (iter of classicalWorks.keys()) {
			let work = tracks.filter(track => track.classical_work == iter);
			if (array_homogenous(work.map(it => it.track_artist))) {
			  classicalWorks.get(iter).performer = work[0].track_artist;
			}
			if (array_homogenous(work.map(it => it.composer))) {
			  classicalWorks.get(iter).composer = work[0].composer;
			}
		  }
		}
		let duration, volumes = new Map(tracks.map(it => [it.discnumber, undefined])), tnOffset = 0;
		volumes.forEach(function(val, key) {
		  volumes.set(key, new Set(tracks.filter(it => it.discnumber == key).map(it => it.discsubtitle)).size)
		});
		if (!tracks.every(it => !isNaN(parseInt(it.tracknumber)))
			&& !tracks.every(it => vinyltrackParser.test(it.tracknumber.toUpperCase()))) {
		  addMessage('Warning: inconsistent tracks numbering (' + tracks.map(it => it.tracknumber) + ')', 'ua-warning');
		}
		vinylTrackWidth = tracks.reduce(function(acc, it) {
		  return Math.max(vinyltrackParser.test(it.tracknumber.toUpperCase()) && parseInt(RegExp.$3), acc);
		}, 0);
		if (vinylTrackWidth) {
		  vinylTrackWidth = vinylTrackWidth.toString().length;
		  tracks.forEach(function(it) {
			if (vinyltrackParser.test(it.tracknumber.toUpperCase()) != null)
			  it.tracknumber = RegExp.$1 + RegExp.$3.padStart(vinylTrackWidth, '0');
		  });
		  ++vinylTrackWidth;
		}
		if (totalDiscs < 2 && tracks.reduce(computeLowestTrack, undefined) - 1)
		  addMessage('Info: volume ' + iter.discnumber + ' track numbering not starting from 1', 'ua-info');
		const padStart = '[pad=0|0|5|0]';
		for (iter of tracks.sort(trackComparer)) {
		  let title = '', trackArtist = getRealTrackArtist(iter);
		  let ttwidth = vinylTrackWidth || (totalDiscs > 1 && iter.discnumber ?
			tracks.filter(it => it.discnumber == iter.discnumber) : tracks).reduce(function (accumulator, it) {
			  return Math.max(accumulator, (parseInt(it.tracknumber) || it.tracknumber).toString().length);
			}, 2);
		  function realTrackNumber() {
			var tn = parseInt(iter.tracknumber);
			return isNaN(tn) ? iter.tracknumber : (tn - tnOffset).toString().padStart(ttwidth, '0');
		  }
		  switch (prefs.tracklist_style) {
			case 1: {
			  prologue('[size=' + prefs.tracklist_size + ']', '[/size]\n');
			  track = '[b][color=' + prefs.tracklist_tracknumber_color + ']';
			  track += realTrackNumber();
			  track += '[/color][/b]' + prefs.title_separator;
			  if (trackArtist && (!iter.classical_work || !classicalWorks.get(iter.classical_work).performer)) {
				title = '[color=' + prefs.tracklist_artist_color + ']' + trackArtist + '[/color] - ';
			  }
			  title += iter.classical_title || iter.title;
			  if (iter.composer && composerEmphasis && release.composers.length != 1
				  && (!iter.classical_work || !classicalWorks.get(iter.classical_work).composer)) {
				title = title.concat(' [color=', prefs.tracklist_composer_color, '](', iter.composer, ')[/color]');
			  }
			  playlist += track + title;
			  if (iter.duration) playlist += ' [i][color=' + prefs.tracklist_duration_color +'][' +
				makeTimeString(iter.duration) + '][/color][/i]';
			  break;
			}
			case 2: {
			  prologue('[size=' + prefs.tracklist_size + '][pre]', '[/pre][/size]');
			  track = realTrackNumber();
			  track += prefs.title_separator;
			  if (trackArtist && (!iter.classical_work || !classicalWorks.get(iter.classical_work).performer)) {
				title = trackArtist + ' - ';
			  }
			  title += iter.classical_title || iter.title;
			  if (composerEmphasis && iter.composer && release.composers.length != 1
				  && (!iter.classical_work || !classicalWorks.get(iter.classical_work).composer)) {
				title = title.concat(' (', iter.composer, ')');
			  }
			  let l = 0, j, left, padding, spc;
			  duration = iter.duration ? '[' + makeTimeString(iter.duration) + ']' : null;
			  let width = prefs.max_tracklist_width - track.length;
			  if (duration) width -= duration.length + 1;
			  while (title.trueLength() > 0) {
				j = width;
				if (title.trueLength() > width) {
				  while (j > 0 && title[j] != ' ') { --j }
				  if (j <= 0) j = width;
				}
				left = title.slice(0, j).trim();
				if (++l <= 1) {
				  playlist += track + left;
				  if (duration) {
					spc = width - left.trueLength();
					padding = (spc < 2 ? ' '.repeat(spc) : ' ' + prefs.pad_leader.repeat(spc - 1)) + ' ';
					playlist += padding + duration;
				  }
				  width = prefs.max_tracklist_width - track.length - 2;
				} else {
				  playlist += '\n' + ' '.repeat(track.length) + left;
				}
				title = title.slice(j).trim();
			  }
			  break;
			}
		  }
		}
		switch (prefs.tracklist_style) {
		  case 1:
			if (totalTime > 0) playlist += '\n\n' + divs[0].repeat(10) + '\n[color=' + prefs.tracklist_duration_color +
			  ']Total time: [i]' + makeTimeString(totalTime) + '[/i][/color][/size]';
			break;
		  case 2:
			if (totalTime > 0) {
			  duration = '[' + makeTimeString(totalTime) + ']';
			  playlist += '\n\n' + divs[0].repeat(32).padStart(prefs.max_tracklist_width);
			  playlist += '\n' + 'Total time:'.padEnd(prefs.max_tracklist_width - duration.length) + duration;
			}
			playlist += '[/pre][/size]';
			break;
		}

		function computeLowestTrack(acc, track) {
		  if (Number.isNaN(acc)) return NaN;
		  var tn = parseInt(track.tracknumber);
		  if (isNaN(tn)) return NaN;
		  return isNaN(acc) || tn < acc ? tn : acc;
		}

		function prologue(prefix, postfix) {
		  function block1() {
			if (block == 3) playlist += postfix;
			playlist += '\n';
			if (isRED && ![1, 2].includes(block)) playlist += padStart;
			block = 1;
		  }
		  function block2() {
			if (block == 3) playlist += postfix;
			playlist += '\n';
			if (isRED && ![1, 2].includes(block)) playlist += padStart;
			block = 2;
		  }
		  function block3() {
			//if (block == 2 && isRED) playlist += '[hr]';
			if (isRED && [1, 2].includes(block)) playlist += '[/pad]';
			playlist += '\n';
			if (block != 3) playlist += prefix;
			block = 3;
		  }
		  if (totalDiscs > 1 && iter.discnumber != lastDisc) {
			block1();
			lastDisc = iter.discnumber;
			lastSubtitle = lastWork = undefined;
			playlist += '[color=' + prefs.tracklist_disctitle_color + '][size=3][b]';
			if (iter.identifiers.VOL_MEDIA && tracks.filter(it => it.discnumber == iter.discnumber)
				.every(it => it.identifiers.VOL_MEDIA == iter.identifiers.VOL_MEDIA)) {
			  playlist += iter.identifiers.VOL_MEDIA.toUpperCase() + ' ';
			}
			playlist += 'Disc ' + iter.discnumber;
			if (iter.discsubtitle && (volumes.get(iter.discnumber) || 0) == 1) {
			  playlist += ' – ' + iter.discsubtitle;
			  lastSubtitle = iter.discsubtitle;
			}
			playlist += '[/b][/size]';
			duration = tracks.filter(it => it.discnumber == iter.discnumber)
			  .reduce((acc, it) => acc + it.duration, 0);
			if (duration > 0) playlist += ' [size=2][i][' + makeTimeString(duration) + '][/i][/size]';
			playlist += '[/color]';
			tnOffset = tracks.filter(track => track.discnumber == iter.discnumber).reduce(computeLowestTrack, undefined) - 1 || 0;
			if (tnOffset) addMessage('Info: volume ' + iter.discnumber + ' track numbering not starting from 1', 'ua-info');
		  }
		  if (iter.discsubtitle != lastSubtitle) {
			if (block != 1 || iter.discsubtitle) block1();
			if (iter.discsubtitle) {
			  playlist += '[color=' + prefs.tracklist_work_color + '][size=2][b]' + iter.discsubtitle + '[/b][/size]';
			  duration = tracks.filter(it => it.discsubtitle == iter.discsubtitle)
				.reduce((acc, it) => acc + it.duration, 0);
			  if (duration > 0) playlist += ' [size=1][i][' + makeTimeString(duration) + '][/i][/size]';
			  playlist += '[/color]';
			}
			lastSubtitle = iter.discsubtitle;
		  }
		  if (iter.classical_work != lastWork) {
			if (iter.classical_work) {
			  block2();
			  playlist += '[color=' + prefs.tracklist_work_color + '][size=2][b]';
			  if (release.composers.length != 1 && classicalWorks.get(iter.classical_work).composer) {
				playlist += classicalWorks.get(iter.classical_work).composer + ': ';
			  }
			  playlist += iter.classical_work;
			  playlist += '[/b]';
			  if (classicalWorks.get(iter.classical_work).performer
				  && classicalWorks.get(iter.classical_work).performer != release.artist) {
				playlist += ' (' + classicalWorks.get(iter.classical_work).performer + ')';
			  }
			  playlist += '[/size]';
			  duration = tracks.filter(it => it.classical_work == iter.classical_work)
				.reduce((acc, it) => acc + it.duration, 0);
			  if (duration > 0) playlist += ' [size=1][i][' + makeTimeString(duration) + '][/i][/size]';
			  playlist += '[/color]';
			} else {
			  if (block > 2) block1();
			}
			lastWork = iter.classical_work;
		  }
		  if (vinyltrackParser.test(iter.tracknumber)) {
			if (block == 3 && lastSide && RegExp.$1 != lastSide) playlist += '\n';
			lastSide = RegExp.$1;
		  }
		  block3();
		} // prologue
	  } else { // single
		playlist += '[align=center]';
		playlist += isRED ? '[pad=20|20|20|20]' : '';
		playlist += '[size=4][b][color=' + prefs.tracklist_artist_color + ']' + release.artist + '[/color]';
		playlist += isRED ? '[hr]' : divs[0].repeat(32);
		//playlist += '[color=' + prefs.tracklist_single_color + ']';
		playlist += tracks[0].title;
		//playlist += '[/color]'
		playlist += '[/b]';
		if (tracks[0].composer) {
		  playlist += '\n[i][color=' + prefs.tracklist_composer_color + '](' + tracks[0].composer + ')[/color][/i]';
		}
		playlist += '\n\n[color=' + prefs.tracklist_duration_color +'][' + makeTimeString(tracks[0].duration) + '][/color][/size]';
		if (isRED) playlist += '[/pad]';
		playlist += '[/align]';
	  }
	  return playlist;
	}

	function genAlbumHeader() {
	  return !isVA && artists[0].length >= 3 ? '[size=4]' +
		joinArtists(artists[0], artist => '[artist]' + artist + '[/artist]') + ' – ' + release.album + '[/size]\n\n' : '';
	}

	function findPreviousUploads() {
	  let search = new URLSearchParams(document.location.search);
	  if (search.get('groupid')) GM_xmlhttpRequest({
		method: 'GET',
		url: document.location.origin + '/torrents.php?action=grouplog&groupid=' + search.get('groupid'),
		responseType: 'document',
		onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) return;
		  var dom = domParser.parseFromString(response.responseText, "text/html");
		  dom.querySelectorAll('table > tbody > tr.rowa').forEach(function(tr) {
			if (/^\s*deleted\b/i.test(tr.children[3].textContent))
			  scanLog('Torrent ' + tr.children[1].firstChild.textContent);
		  });
		},
	  }); else {
		let query = '';
		if (!isVA && artists[0].length >= 1 && artists[0].length <= 3) query = artists[0].join(', ') + ' - ';
		query += release.album;
		scanLog(query);
	  }

	  function scanLog(query) {
		GM_xmlhttpRequest({
		  method: 'GET',
		  url: document.location.origin + '/log.php?search=' + encodeURIComponent(query),
		  responseType: 'document',
		  onload: function (response) {
			if (response.readyState != 4 || response.status != 200) return;
			var dom = domParser.parseFromString(response.responseText, "text/html");
			dom.querySelectorAll('table > tbody > tr.rowb').forEach(function(tr) {
			  var size, msg = tr.children[1].textContent.trim();
			  if (/\b[\d\s]+(?:\.\d+)?\s*(?:([KMGT])I?)?B\b/.test(msg)) size = get_size_from_string(RegExp.lastMatch);
			  if (!msg.includes('deleted') || (/\[(.*)\/(.*)\/(.*)\]/.test(msg) ?
				!release.codec || release.codec != RegExp.$1
				//|| !release.encoding || release.encoding != RegExp.$2
				|| !media || media != RegExp.$3 :
				!size || !albumSize || Math.abs(albumSize / size - 1) >= 0.1)) return;
			  addMessage('Warning: possibly same release previously uploaded and deleted: ' + msg, 'ua-warning');
			});
		  },
		});
	  }

	  function get_size_from_string(str) {
		var matches = /\b([\d\s]+(?:\.\d+)?)\s*(?:([KMGT])I?)?B\b/.exec(str.replace(',', '.').toUpperCase());
		if (!matches) return null;
		var size = parseFloat(matches[1].replace(/\s+/g, ''));
		if (matches[2] == 'K') { size *= Math.pow(1024, 1) }
		else if (matches[2] == 'M') { size *= Math.pow(1024, 2) }
		else if (matches[2] == 'G') { size *= Math.pow(1024, 3) }
		else if (matches[2] == 'T') { size *= Math.pow(1024, 4) }
		return Math.round(size);
	  }
	}

	function initFromUrl_Music(url, weak = false) {
	  if (!/^https?:\/\//i.test(url)) return false;
	  var artist, album, albumYear, releaseDate, channels, label, composer, bd, sr = 44.1,
		  description, compiler, producer, totalTracks, discSubtitle, discNumber, trackNumber,
		  title, trackArtist, catalogue, encoding, format, bitrate, duration, country;
	  if (url.toLowerCase().includes('qobuz.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  var error = new Error('Error parsing Qobus release page'), mainArtist;
		  if ((ref = dom.querySelector('div.album-meta > h2.album-meta__artist')) == null) throw error;
		  artist = ref.title || ref.textContent.trim();
		  if ((ref = dom.querySelector('div.album-meta > h1.album-meta__title')) == null) throw error;
		  album = ref.title || ref.textContent.trim();
		  ref = dom.querySelector('div.album-meta > ul > li:first-of-type');
		  if (ref != null) releaseDate = normalizeDate(ref.textContent);
		  ref = dom.querySelector('div.album-meta > ul > li:nth-of-type(2) > a');
		  if (ref != null) mainArtist = ref.title || ref.textContent.trim();
		  ref = dom.querySelector('p.album-about__copyright');
		  albumYear = ref != null && extractYear(ref.textContent) || extractYear(releaseDate);
		  let genres = [];
		  dom.querySelectorAll('section#about > ul > li').forEach(function(it) {
			function matchLabel(lbl) { return it.textContent.trimLeft().startsWith(lbl) }
			if (/\b(\d+)\s*(?:dis[ck]|disco|disque)/i.test(it.textContent)) totalDiscs = parseInt(RegExp.$1);
			if (/\b(\d+)\s*(?:track|pist[ae]|tracce|traccia)/i.test(it.textContent)) totalTracks = parseInt(RegExp.$1);
			if (['Label', 'Etichetta', 'Sello'].some(l => it.textContent.trimLeft().startsWith(l))) label = it.children[0].textContent.trim()
			else if (['Composer', 'Compositeur', 'Komponist', 'Compositore', 'Compositor'].some(matchLabel)) {
			  composer = it.children[0].textContent.trim();
			  if (/\bVarious\b/i.test(composer)) composer = null;
			} else if (['Genre', 'Genere', 'Género'].some(g => it.textContent.startsWith(g)) && it.children.length > 0) {
			  it.querySelectorAll('a').forEach(it => { genres.push(it.textContent.trim()) });
			  /*
			  if (genres.length >= 1 && ['Pop/Rock'].includes(genres[0])) genres.shift();
			  if (genres.length >= 2 && ['Alternative & Indie'].includes(genres[genres.length - 1])) genres.shift();
			  if (genres.length >= 1 && ['Metal', 'Heavy Metal'].some(genre => genres.includes(genre))) {
				while (genres.length > 1) genres.shift();
			  }
			  */
			  while (genres.length > 1) genres.shift();
			}
		  });
  		  bd = 16; channels = 2;
		  dom.querySelectorAll('span.album-quality__info').forEach(function(k) {
			if (/\b([\d\.\,]+)\s*kHz\b/i.test(k.textContent) != null) sr = parseFloat(RegExp.$1.replace(',', '.'));
			if (/\b(\d+)[\-\s]*Bits?\b/i.test(k.textContent) != null) bd = parseInt(RegExp.$1);
			if (/\b(?:Stereo)\b/i.test(k.textContent)) channels = 2;
			if (/\b(\d)\.(\d)\b/.test(k.textContent)) channels = parseInt(RegExp.$1) + parseInt(RegExp.$2);
		  });
		  getDescFromNode('section#description > p', response.finalUrl, true);
		  if ((ref = dom.querySelector('a[title="Qobuzissime"]')) != null) {
			description += '\x1C[align=center][url=https://www.qobuz.com' + ref.pathname +
			  '][img]https://ptpimg.me/4z35uj.png[/img][/url][/align]';
		  }
		  ref = dom.querySelectorAll('div.player__tracks > div.track > div.track__items');
		  let works = dom.querySelectorAll('div.player__item > p.player__work');
		  if (!totalTracks) totalTracks = ref.length;
		  ref.forEach(function(k) {
			discSubtitle = null;
			works.forEach(function(j) {
			  if (j.compareDocumentPosition(k) == Node.DOCUMENT_POSITION_FOLLOWING) discSubtitle = j
			});
			discSubtitle = discSubtitle != null ? discSubtitle.textContent.trim() : undefined;
			if (/^\s*(?:DIS[CK]|DISCO|DISQUE)\s+(\d+)\s*$/i.test(discSubtitle)) {
			  discNumber = parseInt(RegExp.$1);
			  discSubtitle = undefined;
			} else discNumber = undefined;
			if (discNumber > totalDiscs) totalDiscs = discNumber;
			trackNumber = parseInt(k.querySelector('span[itemprop="position"]').textContent.trim());
			title = k.querySelector('span.track__item--name').textContent.trim().replace(/\s+/g, ' ');
			duration = timeStringToTime(k.querySelector('span.track__item--duration').textContent);
			trackArtist = undefined;
			tracks.push([
			  artist,
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  undefined, // catalogue
			  undefined, // country
			  'lossless',
			  'FLAC',
			  undefined,
			  undefined,
			  bd,
			  sr * 1000,
			  channels,
			  'WEB',
			  genres.join('; '),
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  response.finalUrl,
			  undefined,
			  description,
			  undefined,
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		} });
		return true;
	  } else if (url.toLowerCase().includes('highresaudio.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  ref = dom.querySelector('h1 > span.artist');
		  if (ref != null) artist = ref.textContent.trim();
		  ref = dom.getElementById('h1-album-title');
		  if (ref != null) album = ref.firstChild.textContent.trim();
		  let genres = [], format;
		  dom.querySelectorAll('div.album-col-info-data > div > p').forEach(function(k) {
			if (/\b(?:Genre|Subgenre)\b/i.test(k.firstChild.textContent)) genres.push(k.lastChild.textContent.trim());
			if (/\b(?:Label)\b/i.test(k.firstChild.textContent)) label = k.lastChild.textContent.trim();
			if (/\b(?:Album[\s\-]Release)\b/i.test(k.firstChild.textContent)) {
			  albumYear = normalizeDate(k.lastChild.textContent);
			}
			if (/\b(?:HRA[\s\-]Release)\b/i.test(k.firstChild.textContent)) {
			  releaseDate = normalizeDate(k.lastChild.textContent);
			}
		  });
		  i = 0;
		  dom.querySelectorAll('tbody > tr > td.col-format').forEach(function(k) {
			if (/^(FLAC)\s*([\d\.\,]+)\b/.exec(k.textContent) != null) {
			  format = RegExp.$1;
			  sr = parseFloat(RegExp.$2.replace(/,/g, '.'));
			  ++i;
			}
		  });
		  if (i > 1) sr = undefined; // ambiguous
		  getDescFromNode('div#albumtab-info > p', response.finalUrl);
		  ref = dom.querySelectorAll('ul.playlist > li.pltrack');
		  totalTracks = ref.length;
		  ref.forEach(function(k) {
			discSubtitle = k;
			while ((discSubtitle = discSubtitle.previousElementSibling) != null) {
			  if (discSubtitle.nodeName == 'LI' && discSubtitle.className == 'plinfo') {
				discSubtitle = discSubtitle.textContent.replace(/\s*:$/, '').trim();
				if (/\b(?:DIS[CK]|Volume|CD)\s*(\d+)\b/i.exec(discSubtitle)) discNumber = parseInt(RegExp.$1);
				break;
			  }
			}
			//if (discnumber > totalDiscs) totalDiscs = discnumber;
			trackNumber = parseInt(k.querySelector('span.track').textContent.trim());
			title = k.querySelector('span.title').textContent.trim().replace(/\s+/g, ' ');
			duration = timeStringToTime(k.querySelector('span.time').textContent);
			trackArtist = undefined;
			tracks.push([
			  artist,
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  undefined, // catalogue
			  undefined, // country
			  'lossless',
			  'FLAC', //format,
			  undefined,
			  undefined,
			  24,
			  sr * 1000,
			  2,
			  'WEB',
			  genres.join('; '),
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  response.finalUrl,
			  undefined,
			  description,
			  undefined,
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		} });
		return true;
	  } else if (url.toLowerCase().includes('bandcamp.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  ref = dom.querySelector('span[itemprop="byArtist"] > a');
		  if (ref != null) artist = ref.textContent.trim();
		  ref = dom.querySelector('h2[itemprop="name"]');
		  if (ref != null) album = ref.textContent.trim();
		  ref = dom.querySelector('div.tralbum-credits');
		  if (ref != null && /\breleased\s+(.*?\b\d{4})\b/i.test(ref.textContent)) {
			releaseDate = RegExp.$1;
			albumYear = releaseDate;
		  }
		  ref = dom.querySelector('span.back-link-text > br');
		  if (ref != null && ref.nextSibling != null) label = ref.nextSibling.textContent.trim(); else {
			ref = dom.querySelector('p#band-name-location > span.title');
			if (ref != null) label = ref.textContent.trim();
		  }
		  let tags = new TagManager;
		  dom.querySelectorAll('div.tralbum-tags > a.tag').forEach(function(tag) {
			if ([
			  artist,
			].every(t => tag.textContent.trim().toLowerCase() != t.toLowerCase())) tags.add(tag.textContent.trim());
		  });
		  description = [];
		  dom.querySelectorAll('div.tralbumData').forEach(function(k) {
			if (!k.classList.contains('tralbum-tags')) description.push(html2php(k, response.finalUrl))
		  });
		  description = description.join('\n\n').replace(/\n/g, '\x1C').replace(/\r/g, '\x1D');
		  ref = dom.querySelectorAll('table.track_list > tbody > tr[itemprop="tracks"]');
		  totalTracks = ref.length;
		  ref.forEach(function(k) {
			trackNumber = parseInt(k.querySelector('div.track_number').textContent);
			title = k.querySelector('span.track-title').textContent.trim().replace(/\s+/g, ' ');
			duration = timeStringToTime(k.querySelector('span.time').textContent);
			trackArtist = undefined;
			tracks.push([
			  artist,
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  undefined, // catalogue
			  undefined, // country
			  undefined, //'lossless',
			  undefined, //'FLAC',
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  2,
			  'WEB',
			  tags.toString(),
			  discNumber,
			  totalDiscs,
			  undefined,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  response.finalUrl,
			  undefined,
			  description,
			  undefined,
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		} });
		return true;
	  } else if (url.toLowerCase().includes('prestomusic.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  artist = getArtists(dom.querySelectorAll('div.c-product-block__contributors > p'));
		  ref = dom.querySelector('h1.c-product-block__title');
		  if (ref != null) album = ref.lastChild.textContent.trim();
		  dom.querySelectorAll('div.c-product-block__metadata > ul > li').forEach(function(k) {
			if (k.firstChild.textContent.includes('Release Date')) {
			  releaseDate = extractYear(k.lastChild.textContent);
			} else if (k.firstChild.textContent.includes('Label')) {
			  label = k.lastChild.textContent.trim();
			} else if (k.firstChild.textContent.includes('Catalogue No')) {
			  catalogue = k.lastChild.textContent.trim();
			}
		  });
		  albumYear = releaseDate;
		  var genre;
		  if (/\/jazz\//i.test(response.finalUrl)) genre = 'Jazz';
		  if (/\/classical\//i.test(response.finalUrl)) genre = 'Classical';
		  getDescFromNode('div#about > div > p', response.finalUrl, true);
		  ref = dom.querySelectorAll('div#related > div > ul > li');
		  composer = [];
		  ref.forEach(function(k) {
			if (k.parentNode.previousElementSibling.textContent.includes('Composers')) {
			  composer.push(k.firstChild.textContent.trim().replace(/^(.*?)\s*,\s+(.*)$/, '$2 $1'));
			}
		  });
		  composer = composer.join(', ') || undefined;
		  ref = dom.querySelectorAll('div.has--sample');
		  totalTracks = ref.length;
		  trackNumber = 0;
		  ref.forEach(function(it) {
			trackNumber = ++trackNumber;
			title = it.querySelector('p.c-track__title').textContent.trim().replace(/\s+/g, ' ');
			duration = timeStringToTime(it.querySelector('div.c-track__duration').textContent);
			let parent = it;
			if (it.classList.contains('c-track')) {
			  parent = it.parentNode.parentNode;
			  if (parent.classList.contains('c-expander')) parent = parent.parentNode;
			  discSubtitle = parent.querySelector(':scope > div > div > div > p.c-track__title');
			  discSubtitle = discSubtitle != null ? discSubtitle.textContent.trim().replace(/\s+/g, ' ') : undefined;
			} else {
			  discSubtitle = null;
			}
			trackArtist = getArtists(parent.querySelectorAll(':scope > div.c-track__details > ul > li'));
			if (trackArtist.equalTo(artist)) trackArtist = [];
			tracks.push([
			  artist.join('; '),
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  catalogue,
			  undefined, // country
			  undefined, // encoding
			  undefined, // format
			  undefined,
			  undefined, // bitrate
			  undefined, // BD
			  undefined, // SR
			  2,
			  'WEB',
			  genre,
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist.join(', '),
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  response.finalUrl,
			  undefined,
			  description,
			  undefined,
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();

		  function getArtists(nodeList) {
			var artists = [];
			nodeList.forEach(function(it) {
			  if (it.textContent.startsWith('Record')) return;
			  splitArtists(it.textContent.trim()).forEach(function(it) {
				artists.push(it.replace(/\s*\([^\(\)]*\)$/, ''));
			  });
			});
			return artists;
		  }
		} });
		return true;
	  } else if (url.toLowerCase().includes('discogs.com/') && /\/releases?\/(\d+)\b/i.test(url)) {
		GM_xmlhttpRequest({
		  method: 'GET',
		  url: 'https://api.discogs.com/releases/' + RegExp.$1,
		  responseType: 'json',
		  onload: function(response) {
			if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
				'): readyState=' + response.readyState + ', status=' + response.status);
			var json = response.response;
			const removeArtistNdx = /\s*\(\d+\)$/;
			function getArtists(root) {
			  function filterArtists(rx, anv = true) {
				return Array.isArray(root.extraartists) && rx instanceof RegExp ?
				  root.extraartists.filter(it => rx.test(it.role))
					.map(it => (anv && it.anv || it.name || '').replace(removeArtistNdx, '')) : [];
			  }
			  var artists = [];
			  for (var ndx = 0; ndx < 7; ++ndx) artists[ndx] = [];
			  ndx = 0;
			  if (root.artists) root.artists.forEach(function(it) {
				artists[ndx].push((it.anv || it.name).replace(removeArtistNdx, ''));
				if (/^feat/i.test(it.join)) ndx = 1;
			  });
			  return [
				artists[0],
				artists[1].concat(filterArtists(/^(?:featuring)$/i)),
				artists[2].concat(filterArtists(/\b(?:Remixed[\s\-]By|Remixer)\b/i)),
				artists[3].concat(filterArtists(/\b(?:(?:Written|Composed)[\s\-]By|Composer)\b/i, false)),
				artists[4].concat(filterArtists(/\b(?:Conducted[\s\-]By|Conductor)\b/i)),
				artists[5].concat(filterArtists(/\b(?:Compiled[\s\-]By|Compiler)\b/i)),
				artists[6].concat(filterArtists(/\b(?:Produced[\s\-]By|Producer)\b/i)),
				// filter off from performers
				filterArtists(/\b(?:(?:Mixed)[\s\-]By|Mixer)\b/i),
				filterArtists(/\b(?:(?:Written|Composed)[\s\-]By|Composer)\b/i, true),
			  ];
			}
			var albumArtists = getArtists(json);
			if (albumArtists[0].length > 0) {
			  artist = albumArtists[0].join('; ');
			  if (albumArtists[1].length > 0) artist += ' feat. ' + albumArtists[1].join('; ');
			}
			album = json.title;
			var editions = [];
			if (editions.length > 0) album += ' (' + editions.join(' / ') + ')';
			label = [];
			catalogue = [];
			json.labels.forEach(function(it) {
			  //if (it.entity_type_name != 'Label') return;
			  if (!/^Not On Label\b/i.test(it.name)) label.pushUniqueCaseless(it.name.replace(removeArtistNdx, ''));
			  catalogue.pushUniqueCaseless(it.catno);
			});
			description = '';
			if (json.companies && json.companies.length > 0) {
			  description = '[b]Companies, etc.[/b]\n';
			  let type_names = new Set(json.companies.map(it => it.entity_type_name));
			  type_names.forEach(function(type_name) {
				description += '\n' + type_name + ' – ' + json.companies
				  .filter(it => it.entity_type_name == type_name)
				  .map(function(it) {
					var result = '[url=https://www.discogs.com/label/' + it.id + ']' +
						it.name.replace(removeArtistNdx, '') + '[/url]';
					if (it.catno) result += ' – ' + it.catno;
					return result;
				  })
				  .join(', ');
			  });
			}
			if (json.extraartists && json.extraartists.length > 0) {
			  if (description) description += '\n\n';
			  description += '[b]Credits[/b]\n';
			  let roles = new Set(json.extraartists.map(it => it.role));
			  roles.forEach(function(role) {
				description += '\n' + role + ' – ' + json.extraartists
				  .filter(it => it.role == role)
				  .map(function(it) {
					var result = '[url=https://www.discogs.com/artist/' + it.id + ']' +
						(it.anv || it.name).replace(removeArtistNdx, '') + '[/url]';
					if (it.tracks) result += ' (tracks: ' + it.tracks + ')';
					return result;
				  })
				  .join(', ');
			  });
			}
			if (json.notes) {
			  if (description) description += '\n\n';
			  description += '[b]Notes[/b]\n\n' + json.notes.trim();
			}
			if (json.identifiers && json.identifiers.length > 0) {
			  if (description) description += '\n\n';
			  description += '[b]Barcode and Other Identifiers[/b]\n';
			  json.identifiers.forEach(function(it) {
				description += '\n' + it.type;
				if (it.description) description += ' (' + it.description + ')';
				description += ': ' + it.value;
			  });
			}
			var identifiers = ['DISCOGS_ID=' + json.id];
			[
			  ['Single', 'Single'],
			  ['EP', 'EP'],
			  ['Compilation', 'Compilation'],
			  ['Soundtrack', 'Soundtrack'],
			].forEach(function(k) {
			  if (json.formats.every(it => it.descriptions && it.descriptions.includesCaseless(k[0]))) {
				identifiers.push('RELEASETYPE=' + k[1]);
			  }
			});
			json.identifiers.forEach(function(it) {
			  identifiers.push(it.type.replace(/\W+/g, '_').toUpperCase() + '=' + it.value.replace(/\s/g, '\x1B'));
			});
			json.formats.forEach(function(it) {
			  if (it.descriptions) it.descriptions.forEach(function(it) {
				if (/^(?:.+?\s+Edition|Remaster(?:ed)|Reissue|.+?\s+Release|Enhanced|Promo)$/.test(it)) {
				  editions.push(it);
				}
			  });
			  if (media) return;
			  if (it.name.includes('File')) {
				if (['FLAC', 'WAV', 'AIF', 'AIFF', 'PCM'].some(k => it.descriptions.includes(k))) {
				  media = 'WEB'; encoding = 'lossless'; format = 'FLAC';
				} else if (it.descriptions.includes('AAC')) {
				  media = 'WEB'; encoding = 'lossy'; format = 'AAC'; bd = undefined;
				  if (/(\d+)\s*kbps\b/i.test(it.text)) bitrate = parseInt(RegExp.$1);
				} else if (it.descriptions.includes('MP3')) {
				  media = 'WEB'; encoding = 'lossy'; format = 'MP3'; bd = undefined;
				  if (/(\d+)\s*kbps\b/i.test(it.text)) bitrate = parseInt(RegExp.$1);
				}
			  } else if (['CD', 'DVD', 'Vinyl', 'LP', '7"', '12"', '10"', '5"', 'SACD', 'Hybrid', 'Blu',
				'Cassette','Cartridge', 'Laserdisc', 'VCD'].some(k => it.name.includes(k))) media = it.name;
			});
			if (json.master_url) {
			  var yearWritable = elementWritable(document.getElementById('year'));
			  var tagsWritable = elementWritable(document.getElementById('tags'));
			  if (yearWritable || tagsWritable) GM_xmlhttpRequest({
				method: 'GET',
				url: json.master_url,
				responseType: 'json',
				onload: function(response) {
				  if (response.readyState != 4 || response.status != 200) return;
				  if (yearWritable && (albumYear = response.response.year) > 0) {
					document.getElementById('year').value = albumYear;
				  }
				  if (tagsWritable) {
					var tags = new TagManager();
					if (json.genres) tags.add(...json.genres);
					if (json.styles) tags.add(...json.styles);
					if (response.response.genres) tags.add(...response.response.genres);
					if (response.response.styles) tags.add(...response.response.styles);
					if (tags.length > 0) document.getElementById('tags').value = tags.toString();
				  }
				},
			  });
			}
			json.tracklist.forEach(function(track) {
			  if (track.type_.toLowerCase() == 'heading') {
				discSubtitle = track.title;
			  } else if (track.type_.toLowerCase() == 'track') {
				if (/^([a-zA-Z]+)?(\d+)-(\w+)$/.test(track.position)) {
				  if (RegExp.$1) identifiers.push('VOL_MEDIA=' + RegExp.$1.replace(/\s/g, '\x1B'));
				  discNumber = RegExp.$2;
				  trackNumber = RegExp.$3;
				} else {
				  discNumber = undefined;
				  trackNumber = track.position;
				}
				let trackArtists = getArtists(track);
				if (trackArtists[0].length > 0 && !trackArtists[0].equalTo(albumArtists[0])
					|| trackArtists[1].length > 0 && !trackArtists[1].equalTo(albumArtists[1])) {
				  trackArtist = (trackArtists[0].length > 0 ? trackArtists : albumArtists)[0].join('; ');
				  if (trackArtists[1].length > 0) trackArtist += ' feat. ' + trackArtists[1].join('; ');
				} else {
				  trackArtist = null;
				}
				let performer = Array.isArray(track.extraartists) && track.extraartists
				  .map(artist => (artist.anv || artist.name).replace(removeArtistNdx, ''))
				  .filter(function(artist) {
					return !albumArtists.slice(2).some(it => Array.isArray(it) && it.includes(artist))
					&& !trackArtists.slice(2).some(it => Array.isArray(it) && it.includes(artist))
				  });
				tracks.push([
				  artist,
				  album,
				  undefined, //json.year,
				  json.released,
				  label.join(' / '),
				  catalogue.join(' / '),
				  json.country,
				  encoding,
				  format,
				  undefined,
				  bitrate,
				  bd,
				  undefined, // samplerate
				  undefined, // channels
				  media,
				  (json.genres ? json.genres.join('; ') : '') + (json.styles ? ' | ' + json.styles.join('; ') : ''),
				  discNumber,
				  json.format_quantity,
				  discSubtitle,
				  trackNumber,
				  json.tracklist.length,
				  track.title,
				  trackArtist,
				  Array.isArray(performer) && performer.join('; ') || undefined,
				  stringyfyRole(3), // composers
				  stringyfyRole(4), // conductors
				  stringyfyRole(2), // remixers
				  stringyfyRole(5), // DJs/compilers
				  stringyfyRole(6), // producers
				  timeStringToTime(track.duration),
				  undefined,
				  undefined,
				  undefined,
				  undefined,
				  undefined,
				  undefined, //'https://www.discogs.com/release/' + json.id,
				  undefined,
				  description.replace(/\n/g, '\x1C').replace(/\r/g, '\x1D'),
				  identifiers.join(' '),
				].join('\x1E'));

				function stringyfyRole(ndx) {
				  return (Array.isArray(trackArtists[ndx]) && trackArtists[ndx].length > 0 ?
						  trackArtists : albumArtists)[ndx].join('; ');
				}
			  }
			});
			clipBoard.value = tracks.join('\n');
			fillFromText_Music();
		  },
		});
		return true;
	  } else if (url.toLowerCase().includes('supraphonline.cz')) {
		GM_xmlhttpRequest({ method: 'GET', url: url.replace(/\?.*$/, ''), onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  const copyrightParser = /^(?:\([PC]\)|℗|©)$/i;
		  var genre, ndx, conductor = [], origin = new URL(response.finalUrl).origin;
		  artist = [];
		  dom.querySelectorAll('h2.album-artist > a').forEach(function(it) {
			artist.pushUnique(it.title);
		  });
		  isVA = false;
		  if (artist.length == 0 && (ref = dom.querySelector('h2.album-artist[title]')) != null) {
			if (vaParser.test(ref.title)) isVA = true;
		  }
		  ref = dom.querySelector('span[itemprop="byArtist"] > meta[itemprop="name"]');
		  if (ref != null && vaParser.test(ref.content)) isVA = true;
		  if (isVA) artist = [];
		  if ((ref = dom.querySelector('h1[itemprop="name"]')) != null) album = ref.firstChild.data.trim();
		  if ((ref = dom.querySelector('meta[itemprop="numTracks"]')) != null) totalTracks = parseInt(ref.content);
		  if ((ref = dom.querySelector('meta[itemprop="genre"]')) != null) genre = ref.content;
		  if ((ref = dom.querySelector('li.album-version > div.selected > div')) != null) {
			if (/\b(?:MP3)\b/.test(ref.textContent)) { media = 'WEB'; encoding = 'lossy'; format = 'MP3'; }
			if (/\b(?:FLAC)\b/.test(ref.textContent)) { media = 'WEB'; encoding = 'lossless'; format = 'FLAC'; bd = 16; }
			if (/\b(?:Hi[\s\-]*Res)\b/.test(ref.textContent)) { media = 'WEB'; encoding = 'lossless'; format = 'FLAC'; bd = 24; }
			if (/\b(?:CD)\b/.test(ref.textContent)) { media = 'CD'; }
			if (/\b(?:LP)\b/.test(ref.textContent)) { media = 'Vinyl'; }
		  }
		  dom.querySelectorAll('ul.summary > li').forEach(function(it) {
			if (it.children.length < 1) return;
			if (it.children[0].textContent.includes('Nosič')) media = it.lastChild.textContent.trim();
			if (it.children[0].textContent.includes('Datum vydání')) releaseDate = normalizeDate(it.lastChild.textContent);
			if (it.children[0].textContent.includes('První vydání')) albumYear = extractYear(it.lastChild.data);
			//if (it.children[0].textContent.includes('Žánr')) genre = it.lastChild.textContent.trim();
			if (it.children[0].textContent.includes('Vydavatel')) label = it.lastChild.textContent.trim();
			if (it.children[0].textContent.includes('Katalogové číslo')) catalogue = it.lastChild.textContent.trim();
			if (it.children[0].textContent.includes('Formát')) {
			  if (/\b(?:FLAC|WAV|AIFF?)\b/.test(it.lastChild.textContent)) { encoding = 'lossless'; format = 'FLAC'; }
			  if (/\b(\d+)[\-\s]?bits?\b/i.test(it.lastChild.textContent)) bd = parseInt(RegExp.$1);
			  if (/\b([\d\.\,]+)[\-\s]?kHz\b/.test(it.lastChild.textContent)) sr = parseFloat(RegExp.$1.replace(',', '.'));
			}
			if (it.children[0].textContent.includes('Celková stopáž')) totalTime = timeStringToTime(it.lastChild.textContent.trim());
			if (copyrightParser.test(it.children[0].textContent) && !albumYear) albumYear = extractYear(it.lastChild.data);
		  });
		  const creators = ['autoři', 'interpreti', 'tělesa', 'digitalizace'];
		  artists = [];
		  for (i = 0; i < 4; ++i) artists[i] = {};
		  dom.querySelectorAll('ul.sidebar-artist > li').forEach(function(it) {
			if ((ref = it.querySelector('h3')) != null) {
			  ndx = undefined;
			  creators.forEach((it, _ndx) => { if (ref.textContent.includes(it)) ndx = _ndx });
			} else {
			  if (typeof ndx != 'number') return;
			  let role;
			  if (ndx == 2) role = 'ensemble';
			  	else if ((ref = it.querySelector('span')) != null) role = translateRole(ref);
			  if ((ref = it.querySelector('a')) != null) {
				if (!Array.isArray(artists[ndx][role])) artists[ndx][role] = [];
				var href = new URL(ref.href);
				artists[ndx][role].pushUnique([ref.textContent.trim(), origin + href.pathname]);
			  }
			}
		  });
		  getDescFromNode('div[itemprop="description"] p', response.finalUrl, true);
		  composer = [];
		  var performers = [], DJs = [];
		  function dumpArtist(ndx, role) {
			if (!role || role == 'undefined') return;
			if (description.length > 0) description += '\x1C' ;
			description += '[color=#9576b1]' + role + '[/color] – ';
			//description += artists[ndx][role].map(artist => '[artist]' + artist[0] + '[/artist]').join(', ');
			description += artists[ndx][role].map(artist => '[url=' + artist[1] + ']' + artist[0] + '[/url]').join(', ');
		  }
		  for (i = 1; i < 3; ++i) Object.keys(artists[i]).forEach(function(role) { // performers
			var a = artists[i][role].map(a => a[0]);
			artist.pushUnique(...a);
			(['conductor', 'choirmaster'].includes(role) ? conductor : role == 'DJ' ? DJs : performers).pushUnique(...a);
			if (i != 2) dumpArtist(i, role);
		  });
		  Object.keys(artists[0]).forEach(function(role) { // composers
			composer.pushUnique(...artists[0][role].map(it => it[0])
				.filter(it => !pseudoArtistParsers.some(rx => rx.test(it))));
			dumpArtist(0, role);
		  });
		  Object.keys(artists[3]).forEach(role => { dumpArtist(3, role) }); // ADC & mastering
		  var promises = [];
		  dom.querySelectorAll('table.table-tracklist > tbody > tr').forEach(function(row) {
			promises.push(row.id && (ref = row.querySelector('td > a.trackdetail')) != null ? new Promise(function(resolve, reject) {
			  var id = parseInt(row.id.replace(/^track-/i, ''));
			  GM_xmlhttpRequest({
				method: 'GET',
				url: origin + ref.pathname + ref.search,
				context: id,
				onload: function(response) {
				  if (response.readyState == 4 || response.status == 200) {
					var domDetail = domParser.parseFromString(response.responseText, 'text/html');
					var track = domDetail.getElementById('track-' + response.context);
					if (track != null) {
					  resolve([track, domDetail.querySelector('div[data-swap="trackdetail-' + response.context + '"] > div > div.row')]);
					} else reject('Track detail not located');
				  } else {
					reject('Response error ' + response.status + ' (' + response.statusText + ')');
				  }
				},
				onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
				ontimeout: function() { reject('Timeout') },
			  });
			}) : Promise.resolve([row, null]));
		  });
		  Promise.all(promises).then(function(rows) {
			rows.forEach(function(tr) {
			  if (!(tr[0] instanceof HTMLElement)) throw new Error('Assertion failed: tr[0] != HTMLElement');
			  if (tr[0].id && tr[0].classList.contains('track')) {
				tr[2] = [];
				for (i = 0; i < 8; ++i) tr[2][i] = [];
				if (!(tr[1] instanceof HTMLElement)) return;
				tr[1].querySelectorAll('div[class]:nth-of-type(2) > ul > li > span').forEach(function(li) {
				  function oneOf(...arr) { return arr.some(role => key == role) }
				  var key = translateRole(li);
				  var val = li.nextElementSibling.textContent.trim();
				  if (pseudoArtistParsers.some(rx => rx.test(val))) return;
				  if (key.startsWith('remix')) {
					tr[2][2].pushUnique(val);
				  } else if (oneOf('music', 'lyrics', 'music+lyrics', 'original lyrics', 'czech lyrics', 'libreto', 'music improvisation', 'author')) {
					tr[2][3].pushUnique(val);
				  } else if (oneOf('conductor', 'choirmaster')) {
					tr[2][4].pushUnique(val);
				  } else if (key == 'DJ') {
					tr[2][5].pushUnique(val);
				  } else if (key == 'produced by') {
					tr[2][6].pushUnique(val);
				  } else if (key == 'recorded by') {
				  } else {
					tr[2][7].pushUnique(val);
				  }
				});
			  }
			});
			var guests = rows.filter(tr => tr.length >= 3).map(it => it[2][7])
				.reduce((acc, trpf) => trpf.filter(trpf => acc.includes(trpf)))
				.filter(it => !artist.includes(it));
			rows.forEach(function(tr) {
			  if (tr[0].classList.contains('cd-header')) {
				discNumber = /\b\d+\b/.test(tr[0].querySelector('h3').firstChild.data.trim())
				&& parseInt(RegExp.lastMatch) || undefined;
			  }
			  if (tr[0].classList.contains('song-header')) {
				discSubtitle = tr[0].children[0].title.trim() || undefined;
			  }
			  if (tr[0].id && tr[0].classList.contains('track')) {
				var copyright, trackGenre, trackYear, recordPlace, recordDate, identifiers = '';
				if (/^track-(\d+)$/i.test(tr[0].id)) identifiers = 'TRACK_ID=' + RegExp.$1;
				trackNumber = /^\s*(\d+)\.?\s*$/.test(tr[0].children[0].firstChild.textContent) ?
				  parseInt(RegExp.$1) : undefined;
				title = tr[0].querySelector('meta[itemprop="name"]').content;
				duration = (ref = tr[0].querySelector('meta[itemprop="duration"]')) != null
					&& /^PT(\d+)H(\d+)M(\d+)S$/i.test(ref.content) ?
				  parseInt(RegExp.$1 || 0) * 60**2 + parseInt(RegExp.$2 || 0) * 60 + parseInt(RegExp.$3 || 0) : undefined;
				if (tr[1] instanceof HTMLElement) {
				  tr[1].querySelectorAll('div[class]:nth-of-type(1) > ul > li > span').forEach(function(li) {
					if (li.textContent.startsWith('Nahrávka dokončena')) {
					  identifiers += ' RECYEAR=' + extractYear(recordDate = li.nextSibling.data.trim());
					}
					if (li.textContent.startsWith('Místo nahrání')) {
					  recordPlace = li.nextSibling.data.trim();
					}
					if (li.textContent.startsWith('Rok prvního vydání')) {
					  identifiers += ' PUBYEAR=' + (trackYear = parseInt(li.nextSibling.data));
					}
					//if (copyrightParser.test(li.textContent)) copyright = li.nextSibling.data.trim();
					if (li.textContent.startsWith('Žánr')) trackGenre = li.nextSibling.data.trim();
				  });
				}
				if (!isVA && tr[2][0].equalTo(artist)) tr[2][0] = [];
				tracks.push([
				  isVA ? VA : artist.join('; '),
				  album,
				  /*trackYear || */albumYear || undefined,
				  releaseDate,
				  label,
				  catalogue,
				  undefined, // country
				  encoding,
				  format,
				  undefined,
				  undefined,
				  bd,
				  sr * 1000,
				  2,
				  media,
				  translateGenre(genre) + ' | ' + translateGenre(trackGenre),
				  discNumber,
				  totalDiscs,
				  discSubtitle,
				  trackNumber,
				  totalTracks,
				  title,
				  joinArtists(tr[2][0]),
				  tr[2][7].join('; ') || performers.join('; '),
				  tr[2][3].join(', ') || composer.join(', '),
				  tr[2][4].join('; ') || conductor.join('; '),
				  tr[2][2].join('; '),
				  tr[2][5].join('; ') || DJs.join('; '),
				  tr[2][6].join('; '),
				  duration,
				  undefined,
				  undefined,
				  undefined,
				  undefined,
				  undefined,
				  response.finalUrl,
				  undefined,
				  description,
				  identifiers,
				].join('\x1E'));
			  }
			});
			clipBoard.value = tracks.join('\n');
			fillFromText_Music();
		  }).catch(e => { alert(e) });

		  function translateGenre(genre) {
			if (!genre || typeof genre != 'string') return undefined;
			[
			  ['Orchestrální hudba', 'Orchestral Music'],
			  ['Komorní hudba', 'Chamber Music'],
			  ['Vokální', 'Classical, Vocal'],
			  ['Klasická hudba', 'Classical'],
			  ['Melodram', 'Classical, Melodram'],
			  ['Symfonie', 'Symphony'],
			  ['Vánoční hudba', 'Christmas Music'],
			  [/^(?:Alternativ(?:ní|a))$/i, 'Alternative'],
			  ['Dechová hudba', 'Brass Music'],
			  ['Elektronika', 'Electronic'],
			  ['Folklor', 'Folclore, World Music'],
			  ['Instrumentální hudba', 'Instrumental'],
			  ['Latinské rytmy', 'Latin'],
			  ['Meditační hudba', 'Meditative'],
			  ['Vojenská hudba', 'Military Music'],
			  ['Pro děti', 'Children'],
			  ['Pro dospělé', 'Adult'],
			  ['Mluvené slovo', 'Spoken Word'],
			  ['Audiokniha', 'audiobook'],
			  ['Humor', 'humour'],
			  ['Pohádka', 'Fairy-Tale'],
			].forEach(function(subst) {
			  if (typeof subst[0] == 'string' && genre.toLowerCase() == subst[0].toLowerCase()
				 || subst[0] instanceof RegExp && subst[0].test(genre)) genre = subst[1];
			});
			return genre;
		  }
		  function translateRole(elem) {
			if (!(elem instanceof HTMLElement)) return undefined;
			var role = elem.textContent.trim().toLowerCase().replace(/\s*:.*$/, '');
			[
			  [/\b(?:klavír)\b/, 'piano'],
			  [/\b(?:housle)\b/, 'violin'],
			  [/\b(?:varhany)\b/, 'organ'],
			  [/\b(?:cembalo)\b/, 'harpsichord'],
			  [/\b(?:trubka)\b/, 'trumpet'],
			  [/\b(?:soprán)\b/, 'soprano'],
			  [/\b(?:alt)\b/, 'alto'],
			  [/\b(?:baryton)\b/, 'baritone'],
			  [/\b(?:bas)\b/, 'basso'],
			  [/\b(?:syntezátor)\b/, 'synthesizer'],
			  [/\b(?:zpěv)\b/, 'vocals'],
			  [/^(?:čte|četba)$/, 'narration'],
			  ['vypravuje', 'narration'],
			  ['komentář', 'commentary'],
			  ['hovoří', 'spoken by'],
			  ['hovoří a zpívá', 'speaks and sings'],
			  ['improvizace', 'improvisation'],
			  ['hudební těleso', 'ensemble'],
			  ['hudba', 'music'],
			  ['text', 'lyrics'],
			  ['hudba+text', 'music+lyrics'],
			  ['původní text', 'original lyrics'],
			  ['český text', 'czech lyrics'],
			  ['hudební improvizace', 'music improvisation'],
			  ['autor', 'author'],
			  ['účinkuje', 'participating'],
			  ['řídí', 'conductor'],
			  ['dirigent', 'conductor'],
			  ['sbormistr', 'choirmaster'],
			  ['produkce', 'produced by'],
			  ['nahrál', 'recorded by'],
			  ['digitální přepis', 'A/D transfer'],
			].forEach(function(subst) {
			  if (typeof subst[0] == 'string' && role.toLowerCase() == subst[0].toLowerCase()
				 || subst[0] instanceof RegExp && subst[0].test(role)) role = role.replace(subst[0], subst[1]);
			});
			return role;
		  }
		} });
		return true;
	  } else if (url.toLowerCase().includes('bontonland.cz')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  ref = dom.querySelector('div#detailheader > h1');
		  if (ref != null && /^(.*?)\s*:\s*(.*)$/.test(ref.textContent.trim())) {
			artist = RegExp.$1;
			album = RegExp.$2;
		  }
		  var EAN;
		  dom.querySelectorAll('table > tbody > tr > td.nazevparametru').forEach(function(it) {
			if (it.textContent.includes('Datum vydání')) {
			  releaseDate = normalizeDate(it.nextElementSibling.textContent);
			  albumYear = extractYear(it.nextElementSibling.textContent);
			} else if (it.textContent.includes('Nosič / počet')) {
			  if (/^(.*?)\s*\/\s*(.*)$/.test(it.nextElementSibling.textContent)) {
				media = RegExp.$1;
				totalDiscs = RegExp.$2;
			  }
			} else if (it.textContent.includes('Interpret')) {
			  artist = it.nextElementSibling.textContent.trim();
			} else if (it.textContent.includes('EAN')) {
			  EAN = 'BARCODE=' + it.nextElementSibling.textContent.trim();
			}
		  });
		  getDescFromNode('div#detailtabpopis > div[class^="pravy"] > div > p:not(:last-of-type)', response.finalUrl, true);
		  const plParser = /^(\d+)(?:\s*[\/\.\-\:\)])?\s+(.*?)(?:\s+((?:(?:\d+:)?\d+:)?\d+))?$/;
		  ref = dom.querySelector('div#detailtabpopis > div[class^="pravy"] > div > p:last-of-type');
		  if (ref == null) throw new Error('Playlist not located');
		  var trackList = html2php(ref).split(/[\r\n]+/);
		  trackList = trackList.filter(it => plParser.test(it.trim())).map(it => plParser.exec(it.trim()));
		  totalTracks = trackList.length;
		  if (!totalTracks) throw new Error('Playlist empty');
		  trackList.forEach(function(it) {
			trackNumber = it[1];
			title = it[2];
			duration = timeStringToTime(it[3]);
			trackArtist = undefined;
			tracks.push([
			  artist,
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  undefined, // catalogue
			  undefined, // country
			  undefined, // encoding
			  undefined, // format
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  'CD', // media
			  undefined, // genre
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  undefined, // composer
			  undefined,
			  undefined,
			  undefined, // compiler
			  undefined, // producer
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  response.finalUrl,
			  undefined,
			  description,
			  EAN,
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		} });
		return true;
	  } else if (url.toLowerCase().includes('nativedsd.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  var NDSD_ID = 'ORIGINALFORMAT=DSD', genre;
		  ref = dom.querySelector('div.the-content > header > h2');
		  if (ref != null) artist = ref.firstChild.data.trim();
		  ref = dom.querySelector('div.the-content > header > h1');
		  if (ref != null) album = ref.firstChild.data.trim();
		  ref = dom.querySelector('div.the-content > header > h3');
		  if (ref != null) composer = ref.firstChild.data.trim();
		  ref = dom.querySelector('div.the-content > header > h1 > small');
		  if (ref != null) albumYear = extractYear(ref.firstChild.data);
		  releaseDate = albumYear; // weak
		  ref = dom.querySelector('div#breadcrumbs > div[class] > a:nth-of-type(2)');
		  if (ref != null) label = ref.firstChild.data.trim();
		  ref = dom.querySelector('h2#sku');
		  if (ref != null) {
			if (/^Catalog Number: (.*)$/m.test(ref.firstChild.textContent)) catalogue = RegExp.$1;
			if (/^ID: (.*)$/m.test(ref.lastChild.textContent)) NDSD_ID += ' NATIVEDSD_ID=' + RegExp.$1;
		  }
		  getDescFromNode('div.the-content > div.entry > p', response.finalUrl, false);
		  ref = dom.querySelector('div#repertoire > div > p');
		  if (ref != null) {
			let repertoire = html2php(ref, url).trim();
			let ndx = repertoire.indexOf('\n[b]Track');
			if (description) description += '\x1C\x1C';
			description += (ndx >= 0 ? repertoire.slice(0, ndx).trim() : repertoire)
			  .replace(/\n/g, '\x1C').replace(/\r/g, '\x1D');
		  }
		  ref = dom.querySelectorAll('div#techspecs > table > tbody > tr');
		  if (ref.length > 0) {
			if (description) description += '\x1C\x1C';
			description += '[b][u]Tech specs[/u][/b]';
			ref.forEach(function(it) {
			  description += '\n[b]'.concat(it.children[0].textContent.trim(), '[/b] ',
				it.children[1].textContent.trim()).replace(/\n/g, '\x1C').replace(/\r/g, '\x1D');
			});
		  }
		  ref = dom.querySelectorAll('div#track-list > table > tbody > tr[id^="track"]');
		  totalTracks = ref.length;
		  ref.forEach(function(it) {
			ref = it.children[0].children[0];
			if (ref != null) trackNumber = parseInt(ref.firstChild.data.trim().replace(/\..*$/, ''));
			let trackComposer;
			ref = it.children[1];
			if (ref != null) {
			  title = ref.firstChild.textContent.trim();
			  trackComposer = ref.childNodes[2] && ref.childNodes[2].textContent.trim() || undefined;
			}
			ref = it.children[2];
			if (ref != null) duration = timeStringToTime(ref.firstChild.data);
			tracks.push([
			  artist,
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  catalogue,
			  undefined, // country
			  'lossless', // encoding
			  'FLAC', // format
			  undefined,
			  undefined, // bitrate
			  24, //bd,
			  88200,
			  2,
			  'WEB',
			  genre, // 'Jazz'
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  trackComposer || composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  response.finalUrl,
			  undefined,
			  description,
			  NDSD_ID + ' TRACK_ID=' + it.id.replace(/^track-/i, ''),
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();

		  function getArtists(elem) {
			if (elem == null) return undefined;
			var artists = [];
			splitArtists(elem.textContent.trim()).forEach(function(it) {
			  artists.push(it.replace(/\s*\([^\(\)]*\)$/, ''));
			});
			return artists.join(', ');
		  }
		} });
		return true;
	  } else if (url.toLowerCase().includes('junodownload.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  var ID = '';
		  if (/\/([\d\-]+)\/?$/.test(response.finalUrl)) ID = 'JUNODOWNLOAD_ID=' + RegExp.$1 + ' ';
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  ref = dom.querySelector('h2.product-artist > a');
		  if (ref != null) artist = titleCase(ref.firstChild.data.trim());
		  ref = dom.querySelector('meta[itemprop="name"]');
		  if (ref != null) album = ref.content;
		  ref = dom.querySelector('meta[itemprop="author"]');
		  if (ref != null) label = ref.content;
		  ref = dom.querySelector('span[itemprop="datePublished"]');
		  if (ref != null) releaseDate = ref.firstChild.data.trim();
		  var genres = [];
		  dom.querySelectorAll('div.mb-3 > strong').forEach(function(it) {
			if (it.textContent.startsWith('Genre')) {
			  ref = it;
			  while ((ref = ref.nextElementSibling) != null && ref.nodeName == 'A') {
				genres.push(ref.textContent.trim());
			  }
			} else if (it.textContent.startsWith('Cat')) {
			  if ((ref = it.nextSibling) != null && ref.nodeType == 3) catalogue = ref.data;
			}
		  });

		  ref = dom.querySelectorAll('div.product-tracklist > div[itemprop="track"]');
		  totalTracks = ref.length;
		  ref.forEach(function(it) {
			trackNumber = it.querySelector('div.track-title').firstChild.data.trim();
			if (/^(\d+)\./.test(trackNumber)) trackNumber = parseInt(RegExp.$1);
			title = it.querySelector('span[itemprop="name"]').textContent.trim();
			i = it.querySelector('meta[itemprop="duration"]');
			duration = i != null && /^P(\d+)H(\d+)M(\d+)S$/i.test(i.content) ?
			  (parseInt(RegExp.$1) || 0) * 60**2 + (parseInt(RegExp.$2) || 0) * 60 + (parseInt(RegExp.$3) || 0) : undefined;
			tracks.push([
			  artist,
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  catalogue,
			  undefined, // country
			  undefined, // encoding
			  undefined, // format
			  undefined,
			  undefined, // bitrate
			  undefined, //bd,
			  undefined, // SR
			  2,
			  'WEB',
			  genres.join('; '),
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  ID ? undefined : response.finalUrl,
			  undefined,
			  undefined, // description
			  ID + 'BPM=' + it.children[2].textContent.trim(),
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();

		  function getArtists(elem) {
			if (elem == null) return undefined;
			var artists = [];
			splitArtists(elem.textContent.trim()).forEach(function(it) {
			  artists.push(it.replace(/\s*\([^\(\)]*\)$/, ''));
			});
			return artists.join(', ');
		  }
		} });
		return true;
	  } else if (url.toLowerCase().includes('hdtracks.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  var genres = [];
		  dom.querySelectorAll('div.album-main-details > ul > li > span').forEach(function(it) {
			if (it.textContent.startsWith('Title')) album = it.nextSibling.data.trim();
			if (it.textContent.startsWith('Artist')) artist = it.nextElementSibling.textContent.trim();
			if (it.textContent.startsWith('Genre')) {
			  ref = it;
			  while ((ref = ref.nextElementSibling) != null) genres.push(ref.textContent.trim());
			}
			if (it.textContent.startsWith('Label')) label = it.nextElementSibling.textContent.trim();
			if (it.textContent.startsWith('Release Date')) releaseDate = normalizeDate(it.nextSibling.data.trim());
		  });
		  if (!albumYear) albumYear = extractYear(releaseDate);
		  ref = dom.querySelectorAll('table#track-table > tbody > tr[id^="track"]');
		  totalTracks = ref.length;
		  ref.forEach(function(it) {
			trackNumber = parseInt(it.querySelector('td:first-of-type').textContent.trim());
			title = it.querySelector('td.track-name').textContent.trim();
			duration = timeStringToTime(it.querySelector('td:nth-of-type(3)').textContent.trim());
			format = it.querySelector('td:nth-of-type(4) > span').textContent.trim();
			sr = it.querySelector('td:nth-of-type(5)').textContent.trim().replace(/\/.*/, '');
			if (/^([\d\.\,]+)\s*\/\s*(\d+)$/.test(sr)) {
			  sr = Math.round(parseFloat(RegExp.$1.replace(',', '.')) * 1000);
			  bd = parseInt(RegExp.$2);
			} else sr = Math.round(parseFloat(sr) * 1000);
			tracks.push([
			  artist,
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  catalogue,
			  undefined, // country
			  'lossless',
			  undefined, // format
			  undefined,
			  undefined, // bitrate
			  bd || 24,
			  sr || undefined,
			  2,
			  'WEB',
			  genres.join('; '),
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  response.finalUrl,
			  undefined,
			  undefined, // description
			  undefined,
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		} });
		return true;
	  } else if (/^https?:\/\/(?:\w+\.)?deezer\.com\/(?:\w+\/)*album\/(\d+)/i.test(url)) {
		GM_xmlhttpRequest({
		  method: 'GET',
		  url: 'https://api.deezer.com/album/' + RegExp.$1,
		  responseType: 'json',
		  onload: function(response) {
			if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
				'): readyState=' + response.readyState + ', status=' + response.status);
			var json = response.response;
			isVA = vaParser.test(json.artist.name);
			var identifiers = 'DEEZER_ID=' + json.id + ' RELEASETYPE=' + json.record_type;
			json.tracks.data.forEach(function(track, ndx) {
			  trackArtist = track.artist.name;
			  if (!isVA && trackArtist && trackArtist == json.artist.name) trackArtist = undefined;
			  tracks.push([
				isVA ? VA : json.artist.name,
				json.title,
				undefined, //extractYear(json.release_date),
				json.release_date,
				json.label,
				json.upc,
				undefined, // country
				undefined, // encoding
				undefined, // format
				undefined,
				undefined, // bitrate
				undefined, //bd,
				undefined, // SR
				2,
				'WEB',
				json.genres.data.map(it => it.name).join('; '),
				discNumber,
				totalDiscs,
				discSubtitle,
				ndx + 1,
				json.nb_tracks,
				track.title,
				trackArtist,
				undefined,
				composer,
				undefined,
				undefined,
				compiler,
				producer,
				track.duration,
				undefined,
				undefined,
				undefined,
				undefined,
				undefined,
				undefined, //'https://www.deezer.com/album/' + json.id,
				undefined,
				undefined, // description
				identifiers + ' TRACK_ID=' + track.id,
			  ].join('\x1E'));
			});
			clipBoard.value = tracks.join('\n');
			fillFromText_Music();
		  },
		});
		return true;
	  } else if (/^https?:\/\/(?:\w+\.)?spotify\.com\/(?:\w+\/)*albums?\/(\w+)/i.test(url)) {
		querySpotifyAPI('https://api.spotify.com/v1/albums/' + RegExp.$1).then(function(json) {
		  isVA = json.artists.length == 1 && vaParser.test(json.artists[0].name);
		  artist = json.artists.map(artist => artist.name);
		  totalDiscs = json.tracks.items.reduce((acc, track) => Math.max(acc, track.disc_number), 0);
		  var identifiers = 'RELEASETYPE=' + json.album_type + ' SPOTIFY_ID=' + json.id;
		  var image = json.images.reduce((acc, image) => image.width * image.height > acc.width * acc.height ? image : acc);
		  //if (image) identifiers += ' IMGURL=' + image.url;
		  json.tracks.items.forEach(function(track, ndx) {
			trackArtist = track.artists.map(artist => artist.name);
			if (!isVA && json.artists.length > 0 && trackArtist.equalTo(artist)) trackArtist = [];
			tracks.push([
			  isVA ? VA : joinArtists(artist),
			  json.name,
			  undefined, //extractYear(json.release_date),
			  json.release_date,
			  json.label,
			  json.external_ids.upc,
			  undefined, // country
			  undefined, // encoding
			  undefined, // format
			  undefined,
			  undefined, // bitrate
			  undefined, // BD
			  undefined, // SR
			  2,
			  'WEB',
			  json.genres.join('; '),
			  totalDiscs > 1 ? track.disc_number : undefined,
			  totalDiscs > 1 ? totalDiscs : undefined,
			  undefined, // discSubtitle
			  track.track_number,
			  json.total_tracks,
			  track.name,
			  joinArtists(trackArtist),
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  track.duration_ms / 1000,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined, //'https://open.spotify.com/album/' + json.id,
			  undefined,
			  undefined, // description
			  identifiers +
			  	' EXPLICIT=' + Number(track.explicit) +
			  	' TRACK_ID=' + track.id,
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		}).catch(e => { alert(e) });
		return true;

		function querySpotifyAPI(api_url) {
		  if (!api_url) return Promise.reject('No API URL');
		  return setToken().then(function(credentials) {
			return new Promise(function(resolve, reject) {
			  GM_xmlhttpRequest({
				method: 'GET',
				url: api_url,
				headers: {
				  'Accept': 'application/json',
				  'Authorization': credentials.token_type + ' ' + credentials.access_token,
				},
				responseType: 'json',
				onload: function(response) {
				  if (response.readyState == 4 && response.status == 200) resolve(response.response);
				  	else reject('GM_xmlhttpRequest(' + response.finalUrl + '): readyState=' + response.readyState + ', status=' + response.status);
				},
				onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
				ontimeout: function() { reject('Timeout') },
			  });
			});
		  });
		}
		function setToken() {
		  if (isTokenValid()) return Promise.resolve(spotifyCredentials);
		  if (!prefs.spotify_clientid || !prefs.spotify_clientsecret) return Promise.reject('Spotify credentials not set');
		  return new Promise(function(resolve, reject) {
			const data = new URLSearchParams({
			  grant_type: 'client_credentials',
			});
			GM_xmlhttpRequest({
			  method: 'POST',
			  url: 'https://accounts.spotify.com/api/token',
			  headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Content-Length': data.toString().length,
				'Authorization': 'Basic ' + btoa(prefs.spotify_clientid + ':' + prefs.spotify_clientsecret),
			  },
			  responseType: 'json',
			  data: data.toString(),
			  onload: function(response) {
				if (response.readyState == 4 && response.status == 200) {
				  spotifyCredentials = response.response;
				  spotifyCredentials.expires = new Date().getTime() + spotifyCredentials.expires_in;
				  if (isTokenValid()) resolve(spotifyCredentials); else reject('Invalid token');
				} else reject('Response error ' + response.status + ' (' + JSON.parse(response.response).error + ')');
			  },
			  onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
			  ontimeout: function() { reject('Timeout') },
			});
		  });
		}
		function isTokenValid() {
		  return spotifyCredentials.token_type && spotifyCredentials.token_type.toLowerCase() == 'bearer'
		  	&& spotifyCredentials.access_token && spotifyCredentials.expires >= new Date().getTime() + 30;
		}
	  } else if (url.toLowerCase().includes('prostudiomasters.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'document', onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  var ID = '';
		  if (/\/page\/(\d+)$/i.test(response.finalUrl)) ID = 'PROSTUDIOMASTERS_ID=' + RegExp.$1 + ' ';
		  artist = Array.from(dom.querySelectorAll('h2.ArtistName > a')).map(node => node.textContent.trim());
		  if ((ref = dom.querySelector('h3.AlbumName')) != null) album = ref.textContent.trim();
		  if ((ref = dom.querySelector('div.pline')) != null
			  && /^(?:[℗©]\s*)+(\d{4})\s+(.+)/.test(ref.textContent.trim())) {
			releaseDate = RegExp.$1;
			label = RegExp.$2;
		  }
		  getDescFromNode('div.album-info', response.finalUrl, false);
		  //albumYear = extractYear(releaseDate);
		  var trs = dom.querySelectorAll('div.album-tracks > div > table > tbody > tr.track-playable');
		  totalTracks = trs.length;
		  trs.forEach(function(tr) {
			trackArtist = undefined; sr = undefined; bd = undefined; format = undefined;
			title = undefined; trackNumber = undefined; duration = undefined;
			var trackId = tr.getAttribute('data-track-id');
			if (trackId) trackId = 'TRACK_ID=' + trackId;
			if ((ref = tr.querySelector('div.num')) != null) trackNumber = parseInt(ref.firstChild.textContent.trim());
			if ((ref = tr.querySelector('td.track-name > div.name')) != null) {
			  title = ref.firstChild.textContent.trim();
			  if ((ref = ref.querySelector(':scope small')) != null) {
				trackArtist = splitArtists(ref.firstChild.textContent);
				trackArtist = trackArtist.equalTo(artist) ? undefined : joinArtists(trackArtist);
			  }
			}
			if ((ref = tr.querySelector('td:last-of-type')) != null) duration = timeStringToTime(ref.firstChild.data);
			if ((ref = tr.querySelector('span.track-format')) != null && /^(\d+(?:[,\.]\d+)?)\s*([kM]Hz)(?:\s+(\d+)-bit)?\s*\|\s*(\S+)$/i.test(ref.textContent.trim())) {
			  sr = parseFloat(RegExp.$1);
			  if (RegExp.$2 == 'kHz') sr *= 1024;
			  if (RegExp.$2 == 'MHz') sr *= 1024**2;
			  sr = Math.round(sr);
			  bd = parseInt(RegExp.$3) || undefined;
			  format = RegExp.$4;
			}
			tracks.push([
			  artist.join('; '),
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  catalogue,
			  undefined, // country
			  undefined, //'lossless', // encoding
			  format,
			  undefined,
			  undefined, // bitrate
			  bd,
			  sr,
			  2, // channels
			  'WEB',
			  undefined, // genre
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  ID ? undefined : response.finalUrl,
			  undefined,
			  description,
			  ID.concat(trackId).trim(),
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		} });
		return true;
	  } else if (url.toLowerCase().includes('soundcloud.com') && prefs.soundcloud_clientid) {
// 		SC.initialize({
// 		  client_id: prefs.soundcloud_clientid,
// 		  redirect_uri: 'https://dont.spam.me/',
// 		});
		SC.connect().then(function() { return SC.resolve(url) }).then(function(json) {
		  isVA = vaParser.test(json.artist.name);
		  var identifiers = 'SOUNDCLOUD_ID=' + json.id + ' RELEASETYPE=' + json.record_type;
		  json.tracks.data.forEach(function(track, ndx) {
			trackArtist = track.artist.name;
			if (!isVA && trackArtist && trackArtist == json.artist.name) trackArtist = undefined;
			tracks.push([
			  isVA ? VA : json.artist.name,
			  json.title,
			  undefined, //extractYear(json.release_date),
			  json.release_date,
			  json.label,
			  json.upc,
			  undefined, // country
			  undefined, // encoding
			  undefined, // format
			  undefined,
			  undefined, // bitrate
			  undefined, //bd,
			  undefined, // SR
			  2,
			  'WEB',
			  json.genres.data.map(it => it.name).join('; '),
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  ndx + 1,
			  json.nb_tracks,
			  track.title,
			  trackArtist,
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  track.duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined, //'https://www.deezer.com/album/' + json.id,
			  undefined,
			  undefined, // description
			  identifiers + ' TRACK_ID=' + track.id,
			].join('\x1E'));
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		});
		return true;
	  } else if (url.toLowerCase().includes('play.google.com/store/music/album/')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'document', onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  var search = new URLSearchParams(new URL(response.finalUrl).search);
		  var ID = search.get('id'), trackID, aggregateRating, genres = [];
		  ID = ID ? 'GOOGLE_ID=' + ID + ' ' : '';
		  var root = dom.querySelector('div[itemtype="https://schema.org/MusicAlbum"]');
		  if (root == null) throw new Error('Unexpected Google Play metadata structure');
		  if ((ref = root.querySelector('div[itemprop="byArtist"]')) != null) {
			artist = Array.from(ref.querySelectorAll('meta[itemprop="name"]')).map(it => it.content);
			isVA = artist.length == 1 && vaParser.test(artist[0]);
		  }
		  if ((ref = root.querySelector('meta[itemprop="name"]')) != null) album = ref.content;
		  root.querySelectorAll('meta[itemprop="genre"]').forEach(g => { genres.push(g.content) });
		  if ((ref = root.querySelector('meta[itemprop="datePublished"]')) != null) releaseDate = ref.content;
		  //albumYear = extractYear(releaseDate);
		  if ((ref = root.querySelector('meta[itemprop="numTracks"]')) != null) totalTracks = parseInt(ref.content);
		  if ((ref = root.querySelector('meta[itemprop="ratingValue"]')) != null) aggregateRating = parseFloat(ref.content);
		  //getDescFromNode('???', response.finalUrl, false);
		  if ((ref = dom.querySelector('h1[class][itemprop="name"] > span')) != null
			 && (ref = ref.parentNode.parentNode.querySelector('div[class] > span[class]')) != null
			 && /\bExplicit/i.test(ref.textContent)) ID += 'EXPLICIT=1 ';
		  if ((ref = dom.querySelector('span > a[itemprop="genre"]')) != null) try {
			label = ref.parentNode.nextElementSibling.textContent.trim().replace(/^(?:[©℗]|\([cCpP]\))\s*\d{4}\s+/, '');
		  } catch(e) {
			console.warn('Unexpected HTML structure (' + e + ')');
		  }
		  var volumes = dom.querySelectorAll('c-wiz > div > h2');
		  if (volumes.length <= 0) {
			//dom.querySelectorAll('c-wiz > div > table > tbody > tr[class]').forEach(scanPlaylist);
			trackNumber = 0;
			root.querySelectorAll('div[itemprop="track"]').forEach(function(tr) {
			  trackArtist = undefined; title = undefined; duration = undefined;
			  trackID = '';
			  if ((ref = tr.querySelector('meta[itemprop="url"]')) != null) {
				search = new URLSearchParams(new URL(ref.content).search);
				trackID = search.get('tid');
				trackID = trackID ? 'TRACK_ID=' + trackID : '';
			  }
			  ++trackNumber;
			  if ((ref = tr.querySelector('div[itemprop="byArtist"]')) != null) {
				trackArtist = Array.from(ref.querySelectorAll('meta[itemprop="name"]')).map(it => it.content);
				trackArtist = (isVA || !Array.isArray(artist) || !trackArtist.equalTo(artist)) && joinArtists(trackArtist) || undefined;
			  }
			  if ((ref = tr.querySelector('meta[itemprop="name"]')) != null) title = ref.content;
			  if ((ref = tr.querySelector('meta[itemprop="duration"]')) != null) duration = timeStringToTime(ref.content);
			  addTrack();
			});
		  } else volumes.forEach(function(volume) {
			discSubtitle = volume.textContent.trim();
			if (/^(?:Dis[ck]|Disco|Disque)\s+(\d+)$/i.test(discSubtitle)) {
			  discNumber = parseInt(RegExp.$1);
			  discSubtitle = undefined;
			} else discNumber = undefined;
			volume.nextElementSibling.querySelectorAll('tbody > tr[class]').forEach(scanPlaylist);
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();

		  function scanPlaylist(tr) {
			trackArtist = undefined; title = undefined; duration = undefined;
			if ((ref = tr.querySelector('td:nth-of-type(1) > div')) != null) trackNumber = parseInt(ref.textContent);
			if ((ref = tr.querySelector('td[itemprop="name"]')) != null) title = ref.textContent.trim();
			if ((ref = tr.querySelector('td:nth-of-type(3)')) != null) duration = timeStringToTime(ref.textContent);
			trackArtist = Array.from(tr.querySelectorAll('td:nth-of-type(4) > a')).map(it => it.textContent.trim());
			trackArtist = (isVA || !Array.isArray(artist) || !trackArtist.equalTo(artist)) && joinArtists(trackArtist) || undefined;
			addTrack();
		  }
		  function addTrack() {
			tracks.push([
			  isVA ? VA : artist.join('; '),
			  album,
			  albumYear,
			  releaseDate,
			  label,
			  catalogue,
			  undefined, // country
			  undefined, //'lossless', // encoding
			  undefined, // format
			  undefined,
			  undefined, // bitrate
			  undefined, // bd
			  undefined, // sr
			  2, // channels
			  'WEB',
			  genres.join('; '),
			  discNumber,
			  totalDiscs,
			  discSubtitle,
			  trackNumber,
			  totalTracks,
			  title,
			  trackArtist,
			  undefined,
			  composer,
			  undefined,
			  undefined,
			  compiler,
			  producer,
			  duration,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  undefined,
			  ID ? undefined : response.finalUrl,
			  undefined,
			  description,
			  ID.concat(trackID).trim(),
			].join('\x1E'));
		  }
		} });
		return true;
	  } else if (url.toLowerCase().includes('7digital.com')) {
		GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'document', onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest(' + response.finalUrl +
			'): readyState=' + response.readyState + ', status=' + response.status);
		  dom = domParser.parseFromString(response.responseText, 'text/html');
		  ref = dom.querySelector('table.release-track-list');
		  var ID = ref != null ? '7DIGITAL_ID=' + ref.dataset.releaseid + ' ' : '';
		  artist = Array.from(dom.querySelectorAll('h2.release-info-artist > span[itemprop="byArtist"] > meta[itemprop="name"]')).map(node => node.content);
		  if ((ref = dom.querySelector('h1.release-info-title')) != null) album = ref.textContent.trim();
		  if ((ref = dom.querySelector('div.release-date-info > p')) != null) releaseDate = normalizeDate(ref.textContent);
		  //albumYear = extractYear(releaseDate);
		  if ((ref = dom.querySelector('div.release-label-info > p')) != null) label = ref.textContent.trim();
		  var genres = [];
		  dom.querySelectorAll('dl.release-data > dt.release-data-label').forEach(function(dt) {
			if (/\bGenres?:/.test(dt.textContent)) genres = Array.from(dt.nextElementSibling.querySelectorAll('a')).map(a => a.textContent.trim());
		  });
		  //getDescFromNode('div.album-info', response.finalUrl, false);
		  totalTracks = dom.querySelectorAll('table.release-track-list > tbody > tr.release-track').length;
		  dom.querySelectorAll('table.release-track-list').forEach(function(table) {
			discNumber = undefined;
			if ((ref = table.querySelector('caption > h4.release-disc-info')) != null) {
			  discSubtitle = ref.textContent.trim();
			  if (/\bDisc\s+(\d+)(?:\s+of\s+(\d+))?\b/i.test(discSubtitle)) {
				discNumber = parseInt(RegExp.$1);
				totalDiscs = parseInt(RegExp.$2);
				discSubtitle = undefined;
			  }
			} else discSubtitle = undefined;
			table.querySelectorAll('tbody > tr.release-track').forEach(function(tr) {
			  trackArtist = undefined; sr = undefined; bd = undefined; format = undefined;
			  title = undefined; trackNumber = undefined; duration = undefined;
			  var trackId = tr.dataset.trackid;
			  if (trackId) trackId = 'TRACK_ID=' + trackId;
			  if ((ref = tr.querySelector('td.release-track-preview > em.release-track-preview-text')) != null) trackNumber = ref.textContent.trim();
			  if ((ref = tr.querySelector('td.release-track-name > meta[itemprop="name"]')) != null) title = ref.content;
			  if ((ref = tr.querySelector('td.release-track-time > meta[itemprop="duration"]')) != null
				  && /^PT(?:(?:(\d+)H)?(\d+)M)?(\d+)S$/.test(ref.content)) {
				duration = (parseInt(RegExp.$1) || 0) * 60**2 + (parseInt(RegExp.$2) || 0) * 60 + (parseInt(RegExp.$3) || 0);
			  }
			  tracks.push([
				artist.join('; '),
				album,
				albumYear,
				releaseDate,
				label,
				catalogue,
				undefined, // country
				undefined, //'lossless', // encoding
				format,
				undefined,
				undefined, // bitrate
				undefined, // bd
				undefined,// sr
				2, // channels
				'WEB',
				genres.join('; '),
				discNumber,
				totalDiscs,
				discSubtitle,
				trackNumber,
				totalTracks,
				title,
				trackArtist,
				undefined,
				composer,
				undefined,
				undefined,
				compiler,
				producer,
				duration,
				undefined,
				undefined,
				undefined,
				undefined,
				undefined,
				response.finalUrl,
				undefined,
				description,
				ID.concat(trackId).trim(),
			  ].join('\x1E'));
			});
		  });
		  clipBoard.value = tracks.join('\n');
		  fillFromText_Music();
		} });
		return true;
	  }
	  if (!weak) {
		addMessage('This domain not supported', 'ua-critical');
		clipBoard.value = '';
	  }
	  return false;

	  function getDescFromNode(selector, url, quote = false) {
		description = [];
		dom.querySelectorAll(selector).forEach(k => { description.push(html2php(k, url).trim()) });
		description = description.join('\n\n').trim().replace(/\n/g, '\x1C').replace(/\r/g, '\x1D');
		if (quote && description.length > 0) description = '[quote]' + description + '[/quote]';
	  }
	} // initFromUrl_Music

	function trackComparer(a, b) {
	  var cmp = a.discnumber - b.discnumber;
	  if (!isNaN(cmp) && cmp != 0) return cmp;
	  cmp = (a.discsubtitle || '').localeCompare(b.discsubtitle || '');
	  if (cmp != 0) return cmp;
	  cmp = parseInt(a.tracknumber) - parseInt(b.tracknumber);
	  if (!isNaN(cmp)) return cmp;
	  var m1 = vinyltrackParser.exec(a.tracknumber.toUpperCase());
	  var m2 = vinyltrackParser.exec(b.tracknumber.toUpperCase());
	  return m1 != null && m2 != null ?
		m1[1].localeCompare(m2[1]) || parseFloat(m1[2]) - parseFloat(m2[2]) :
		a.tracknumber.toUpperCase().localeCompare(b.tracknumber.toUpperCase());
	}

	function normalizeDate(str) {
	  if (typeof str != 'string') return null;
	  if (/\b(d{4}-\d+-\d+|\d{1,2}\/\d{1,2}\/\d{2})\b/.test(str)) return RegExp.$1; // US (clash with BE, IT)
	  if (/\b(\d{1,2})\/(\d{1,2})\/(\d{4})\b/.test(str)) return RegExp.$2 + '/' + RegExp.$1 + '/' + RegExp.$3; // UK, IRL, FR
	  if (/\b(\d{1,2})-(\d{1,2})-(\d{2})\b/.test(str)) return RegExp.$2 + '/' + RegExp.$1 + '/' + RegExp.$3; // NL
	  if (/\b(\d{1,2})\.\s?(\d{1,2})\.\s?(\d{2}|\d{4})\b/.test(str)) return RegExp.$2 + '/' + RegExp.$1 + '/' + RegExp.$3; // AT, CH, DE, LU, CE
	  return extractYear(str);
	}

	function getStoreUrl() {
	  function makeUrlFromId(identifier, urlBase) {
		return tracks.length > 0 && tracks.every(function(track) {
		  return track.identifiers[identifier] && track.identifiers[identifier] == tracks[0].identifiers[identifier];
		}) ? urlBase + tracks[0].identifiers[identifier] : undefined;
	  }
	  return makeUrlFromId('DISCOGS_ID', 'https://www.discogs.com/release/')
		|| makeUrlFromId('ITUNES_ID', 'https://music.apple.com/album/')
		|| makeUrlFromId('APPLE_ID', 'https://music.apple.com/album/')
		|| makeUrlFromId('SPOTIFY_ID', 'https://open.spotify.com/album/')
		|| makeUrlFromId('DEEZER_ID', 'https://www.deezer.com/album/')
		|| makeUrlFromId('JUNODOWNLOAD_ID', 'https://www.junodownload.com/products/')
		|| makeUrlFromId('PROSTUDIOMASTERS_ID', 'https://www.prostudiomasters.com/album/page/')
		|| makeUrlFromId('GOOGLE_ID', 'https://play.google.com/store/music/album/?id=');
	}

	function getCoverOnline(url) {
	  if (url) GM_xmlhttpRequest({
		method: 'GET',
		url: url,
		onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) return;
		  var ref, dom = domParser.parseFromString(response.responseText, 'text/html');
		  function testDomain(url, selector) {
			return typeof url == 'string' && response.finalUrl.toLowerCase().includes(url.toLowerCase()) ?
			  dom.querySelector(selector) : null;
		  }
		  if ((ref = testDomain('qobuz.com', 'div.album-cover > img')) != null) {
			setImage(ref.src);
		  } else if ((ref = testDomain('highresaudio.com', 'div.albumbody > img.cover[data-pin-media]')) != null) {
			setImage(ref.dataset.pinMedia);
		  } else if ((ref = testDomain('bandcamp.com', 'div#tralbumArt > a.popupImage')) != null) {
			setImage(ref.href);
		  } else if ((ref = testDomain('7digital.com', 'span.release-packshot-image > img[itemprop="image"]')) != null) {
			setImage(ref.src);
		  } else if ((ref = testDomain('hdtracks.com', 'p.product-image > img')) != null) {
			setImage(ref.src);
		  } else if ((ref = testDomain('discogs.com', 'div#view_images > p:first-of-type > span > img')) != null) {
			setImage(ref.src);
		  } else if ((ref = testDomain('junodownload.com', 'a.productimage')) != null) {
			setImage(ref.href);
		  } else if ((ref = testDomain('supraphonline.cz', 'meta[itemprop="image"]')) != null) {
			setImage(ref.content.replace(/\?.*$/, ''));
		  } else if ((ref = testDomain('prestomusic.com', 'div.c-product-block__aside > a')) != null) {
			setImage(ref.href.replace(/\?\d+$/, ''));
		  } else if ((ref = testDomain('bontonland.cz', 'a.detailzoom')) != null) {
			setImage(ref.href);
		  } else if ((ref = testDomain('nativedsd.com', 'a#album-cover')) != null) {
			setImage(ref.href);
		  } else if ((ref = testDomain('deezer.com', 'meta[property="og:image"]')) != null) {
			setImage(ref.content);
		  } else if ((ref = testDomain('spotify.com', 'c')) != null) {
			setImage(ref.content);
		  } else if ((ref = testDomain('prostudiomasters.com', 'img.album-art')) != null) {
			setImage(ref.currentSrc || ref.src);
		  } else if ((ref = testDomain('play.google.com/store/music/album/', 'meta[itemprop="image"]')) != null) {
			setImage(ref.content);
		  }
		},
		//onerror: response => { throw new Error('Response error ' + response.status + ' (' + response.statusText + ')') },
		//ontimeout: function() { throw new Error('Timeout') },
	  });
	}

	function reqSelectFormats(...vals) {
	  vals.forEach(function(val) {
		[
		  ['MP3', 0],
		  ['FLAC', 1],
		  ['AAC', 2],
		  ['AC3', 3],
		  ['DTS', 4],
		].forEach(function(fmt) {
		  if (val.toLowerCase() == fmt[0].toLowerCase()
			  && (ref = document.getElementById('format_' + fmt[1])) != null) {
			ref.checked = true;
			ref.onchange();
		  }
		});
	  });
	}

	function reqSelectBitrates(...vals) {
	  vals.forEach(function(val) {
		var ndx = 10;
		[
		  [192, 0],
		  ['APS (VBR)', 1],
		  ['V2 (VBR)', 2],
		  ['V1 (VBR)', 3],
		  [256, 4],
		  ['APX (VBR)', 5],
		  ['V0 (VBR)', 6],
		  [320, 7],
		  ['Lossless', 8],
		  ['24bit Lossless', 9],
		  ['Other', 10],
		].forEach(function(it) {
		  if ((typeof val == 'string' ? val.toLowerCase() : val)
			  == (typeof it[0] == 'string' ? it[0].toLowerCase() : it[0])) ndx = it[1]
		});
		if ((ref = document.getElementById('bitrate_' + ndx)) != null) {
		  ref.checked = true;
		  ref.onchange();
		}
	  });
	}

	function reqSelectMedias(...vals) {
	  vals.forEach(function(val) {
		[
		  ['CD', 0],
		  ['DVD', 1],
		  ['Vinyl', 2],
		  ['Soundboard', 3],
		  ['SACD', 4],
		  ['DAT', 5],
		  ['Cassette', 6],
		  ['WEB', 7],
		  ['Blu-Ray', 8],
		].forEach(function(med) {
		  if (val == med[0] && (ref = document.getElementById('media_' + med[1])) != null) {
			ref.checked = true;
			ref.onchange();
		  }
		});
		if (val == 'CD') {
		  if ((ref = document.getElementById('needlog')) != null) {
			ref.checked = true;
			ref.onchange();
			if ((ref = document.getElementById('minlogscore')) != null) ref.value = 100;
		  }
		  if ((ref = document.getElementById('needcue')) != null) ref.checked = true;
		  //if ((ref = document.getElementById('needchecksum')) != null) ref.checked = true;
		}
	  });
	}

  	function getReleaseIndex(str) {
	  var ndx;
	  [
		['Album', 1],
		['Soundtrack', 3],
		['EP', 5],
		['Anthology', 6],
		['Compilation', 7],
		['Single', 9],
		['Live album', 11],
		['Remix', 13],
		['Bootleg', 14],
		['Interview', 15],
		['Mixtape', 16],
		['Demo', 17],
		['Concert Recording', 18],
		['DJ Mix', 19],
		['Unknown', 21],
	  ].forEach(k => { if (str.toLowerCase() == k[0].toLowerCase()) ndx = k[1] });
	  return ndx || 21;
	}

	function getChanString(n) {
	  if (!n) return null;
	  const chanmap = [
		'mono',
		'stereo',
		'2.1',
		'4.0 surround sound',
		'5.0 surround sound',
		'5.1 surround sound',
		'7.0 surround sound',
		'7.1 surround sound',
	  ];
	  return n >= 1 && n <= 8 ? chanmap[n - 1] : n + 'chn surround sound';
	}

	function joinArtists(arr, decorator = artist => artist) {
	  if (!Array.isArray(arr)) return null;
	  if (arr.some(artist => artist.includes('&'))) return arr.map(decorator).join(', ');
	  if (arr.length < 3) return arr.map(decorator).join(' & ');
	  return arr.slice(0, -1).map(decorator).join(', ') + ' & ' + decorator(arr.slice(-1).pop());
	}
  } // fillFromText_Music

  function fillFromText_Apps(weak = false) {
	if (messages != null) messages.parentNode.removeChild(messages);
	if (!urlParser.test(clipBoard.value)) {
	  addMessage('Only URL accepted for this category', 'ua-critical');
	  return false;
	}
	url = RegExp.$1;
	var description, tags = new TagManager();
	if (url.toLowerCase().includes('://sanet')) {
	  GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		if (response.readyState != 4 || response.status != 200)
		  throw new Error('GM_xmlhttpRequest readyState=' + response.status + ', status=' + response.status);
		dom = domParser.parseFromString(response.responseText, 'text/html');
		i = dom.querySelector('h1.item_title > span');
		if (elementWritable(ref = document.getElementById('title'))) {
		  ref.value = i != null ? i.textContent.
			replace(/\(x64\)$/i, '(64-bit)').
			replace(/\b(?:Build)\s+(\d+)/, 'build $1').
			replace(/\b(?:Multilingual|Multilanguage)\b/, 'multilingual') : null;
		}
		description = html2php(dom.querySelector('section.descr'), response.finalUrl);
		if (/\s*^(?:\[i\]\[\/i\])?Homepage$.*/m.test(description)) description = RegExp.leftContext;
		description = description.trim().split(/\n/).slice(5)
		  .map(k => k.trimRight()).join('\n').replace(/(?:[ \t]*\r?\n){3,}/, '\n\n').trim();
		ref = dom.querySelector('section.descr > div.release-info');
		var releaseInfo = ref != null && ref.textContent.trim();
		if (/\b(?:Languages?)\s*:\s*(.*?)\s*(?:$|\|)/i.exec(releaseInfo) != null) {
		  description += '\n\n[b]Languages:[/b]\n' + RegExp.$1;
		}
		ref = dom.querySelector('div.txtleft > a');
		if (ref != null) description += '\n\n[b]Product page:[/b]\n[url]' + deAnonymize(ref.href) + '[/url]';
		writeDescription(description);
		if ((ref = dom.querySelector('section.descr > div.center > a.mfp-image')) != null) {
		  setImage(ref.href);
		} else {
		  ref = dom.querySelector('section.descr > div.center > img[data-src]');
		  if (ref != null) setImage(ref.dataset.src);
		}
		var cat = dom.querySelector('a.cat:last-of-type > span');
		if (cat != null) {
		  if (cat.textContent.toLowerCase() == 'windows') {
			tags.add('apps.windows');
			if (/\b(?:x64)\b/i.test(releaseInfo)) tags.add('win64');
			if (/\b(?:x86)\b/i.test(releaseInfo)) tags.add('win32');
		  }
		  if (cat.textContent.toLowerCase() == 'macos') tags.add('apps.mac');
		  if (cat.textContent.toLowerCase() == 'linux' || cat.textContent.toLowerCase() == 'unix') tags.add('apps.linux');
		  if (cat.textContent.toLowerCase() == 'android') tags.add('apps.android');
		  if (cat.textContent.toLowerCase() == 'ios') tags.add('apps.ios');
		}
		if (tags.length > 0 && elementWritable(ref = document.getElementById('tags'))) {
		  ref.value = tags.toString();
		}
	  }, });
	  return true;
	}
	if (!weak) {
	  addMessage('This domain not supported', 'ua-critical');
	  clipBoard.value = '';
	}
	return false;
  }

  function fillFromText_Ebooks(weak = false) {
	if (messages != null) messages.parentNode.removeChild(messages);
	if (!urlParser.test(clipBoard.value)) {
	  addMessage('Only URL accepted for this category', 'ua-critical');
	  return false;
	}
	url = RegExp.$1;
	var description, tags = new TagManager();
	if (url.toLowerCase().includes('martinus.cz') || url.toLowerCase().includes('martinus.sk')) {
	  GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest readyState=' + response.status + ', status=' + response.status);
		dom = domParser.parseFromString(response.responseText, 'text/html');
		function get_detail(x, y) {
		  var ref = dom.querySelector('section#details > div > div > div:first-of-type > div:nth-child(' +
			x + ') > dl:nth-child(' + y + ') > dd');
		  return ref != null ? ref.textContent.trim() : null;
		}

		i = dom.querySelectorAll('article > ul > li > a');
		if (i.length > 0 && elementWritable(ref = document.getElementById('title') || document.querySelector('input[name="title"]'))) {
		  description = joinAuthors(i);
		  if ((i = dom.querySelector('article > h1')) != null) description += ' – ' + i.textContent.trim();
		  i = dom.querySelector('div.bar.mb-medium > div:nth-child(1) > dl > dd > span');
		  if (i != null && (i = extractYear(i.textContent))) description += ' (' + i + ')';
		  ref.value = description;
		}

		description = '[quote]' + html2php(dom.querySelector('section#description > div')).
			replace(/^\s*\[img\].*?\[\/img\]\s*/i, '') + '[/quote]';
		const translation_map = [
		  [/\b(?:originál)/i, 'Original title'],
		  [/\b(?:datum|dátum|rok)\b/i, 'Release date'],
		  [/\b(?:katalog|katalóg)/i, 'Catalogue #'],
		  [/\b(?:stran|strán)\b/i, 'Page count'],
		  [/\bjazyk/i, 'Language'],
		  [/\b(?:nakladatel|vydavatel)/i, 'Publisher'],
		  [/\b(?:doporuč|ODPORÚČ)/i, 'Age rating'],
		];
		dom.querySelectorAll('section#details > div > div > div:first-of-type > div > dl').forEach(function(detail) {
		  var lbl = detail.children[0].textContent.trim();
		  var val = detail.children[1].textContent.trim();
		  if (/\b(?:rozm)/i.test(lbl) || /\b(?:vazba|vázba)\b/i.test(lbl)) return;
		  translation_map.forEach(k => { if (k[0].test(lbl)) lbl = k[1] });
		  if (/\b(?:ISBN)\b/i.test(lbl)) {
			url = new URL('https://www.worldcat.org/isbn/' + detail.children[1].textContent.trim());
			val = '[url=' + url.href + ']' + detail.children[1].textContent.trim() + '[/url]';
			findOCLC(url);
// 		  } else if (/\b(?:ISBN)\b/i.test(lbl)) {
// 			val = '[url=https://www.goodreads.com/search/search?q=' + detail.children[1].textContent.trim() +
// 			  '&search_type=books]' + detail.children[1].textContent.trim() + '[/url]';
		  }
		  description += '\n[b]' + lbl + ':[/b] ' + val;
		});
		url = new URL(response.finalUrl);
		description += '\n\n[b]More info:[/b]\n[url]' + url.href + '[/url]';
		writeDescription(description);

		if ((i = dom.querySelector('a.mj-product-preview > img')) != null) {
		  setImage(i.src.replace(/\?.*/, ''));
		} else if ((i = dom.querySelector('head > meta[property="og:image"]')) != null) {
		  setImage(i.content.replace(/\?.*/, ''));
		}

		dom.querySelectorAll('dd > ul > li > a').forEach(x => { tags.add(x.textContent) });
		if (tags.length > 0 && elementWritable(ref = document.getElementById('tags'))) {
		  ref.value = tags.toString();
		}
	  }, });
	  return true;
	} else if (url.toLowerCase().includes('goodreads.com')) {
	  GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest readyState=' + response.status + ', status=' + response.status);
		dom = domParser.parseFromString(response.responseText, 'text/html');
		i = dom.querySelectorAll('a.authorName > span');
		if (i.length > 0 && elementWritable(ref = document.getElementById('title') || document.querySelector('input[name="title"]'))) {
		  description = joinAuthors(i);
		  if ((i = dom.querySelector('h1#bookTitle')) != null) description += ' – ' + i.textContent.trim();
		  if ((i = dom.querySelector('div#details > div.row:nth-of-type(2)')) != null
			  && (i = extractYear(i.textContent))) description += ' (' + i + ')';
		  ref.value = description;
		}

		var description = [];
		dom.querySelectorAll('div#description span:last-of-type').forEach(function(it) {
		  description = html2php(it, response.finalUrl);
		});
		description = '[quote]' + description.trim() + '[/quote]';

		function strip(str) {
		  return typeof str == 'string' ?
			str.replace(/\s{2,}/g, ' ').replace(/[\n\r]+/, '').replace(/\s*\.{3}(?:less|more)\b/g, '').trim() : null;
		}

		dom.querySelectorAll('div#details > div.row').forEach(k => { description += '\n' + strip(k.innerText) });
		description += '\n';

		dom.querySelectorAll('div#bookDataBox > div.clearFloats').forEach(function(detail) {
		  var lbl = detail.children[0].textContent.trim();
		  var val = strip(detail.children[1].textContent);
		  if (/\b(?:ISBN)\b/i.test(lbl) && (/\b(\d{13})\b/.test(val) || /\b(\d{10})\b/.test(val))) {
			url = new URL('https://www.worldcat.org/isbn/' + RegExp.$1);
			val = '[url=' + url.href + ']' + strip(detail.children[1].textContent) + '[/url]';
			findOCLC(url);
		  }
		  description += '\n[b]' + lbl + ':[/b] ' + val;
		});
		if ((ref = dom.querySelector('span[itemprop="ratingValue"]')) != null) {
		  description += '\n[b]Rating:[/b] ' + Math.round(parseFloat(ref.firstChild.textContent) * 20) + '%';
		}
		url = new URL(response.finalUrl);
// 		if ((ref = dom.querySelector('div#buyButtonContainer > ul > li > a.buttonBar')) != null) {
// 		  let u = new URL(ref.href);
// 		  description += '\n[url=' + url.origin + u.pathname + '?' + u.search + ']Libraries[/url]';
// 		}
		description += '\n\n[b]More info and reviews:[/b]\n[url]' + url.origin + url.pathname + '[/url]';
		dom.querySelectorAll('div.clearFloats.bigBox').forEach(function(bigBox) {
		  if (bigBox.id == 'aboutAuthor' && (ref = bigBox.querySelector('h2 > a')) != null) {
			description += '\n\n[b][url=' + ref.href + ']' + ref.textContent.trim() + '[/url][/b]';
			if ((ref = bigBox.querySelector('div.bigBoxBody a > div[style*="background-image"]')) != null) {
			}
			if ((ref = bigBox.querySelector('div.bookAuthorProfile__about > span[id]:last-of-type')) != null) {
			  description += '\n' + html2php(ref, response.finalUrl).replace(/^\[i\]Librarian\s+Note:.*?\[\/i\]\s+/i, '');
			}
		  } else if ((ref = bigBox.querySelector('h2 > a[href^="/trivia/"]')) != null) {
			description += '\n\n[b][url=' + ref.href + ']' + ref.textContent.trim() + '[/url][/b]';
			if ((ref = bigBox.querySelector('div.bigBoxContent > div.mediumText')) != null) {
			  description += '\n' + ref.firstChild.textContent.trim();
			}
// 		  } else if ((ref = bigBox.querySelector('h2 > a[href^="/work/quotes/"]')) != null) {
// 			description += '\n\n[b][url=' + ref.href + ']' + ref.textContent.trim() + '[/url][/b]';
// 			bigBox.querySelectorAll('div.bigBoxContent > div.stacked > span.readable').forEach(function(quote) {
// 			  description += '\n' + ref.firstChild.textContent.trim();
// 			});
		  }
		});
		writeDescription(description);
		if ((ref = dom.querySelector('div.editionCover > img')) != null) setImage(ref.src.replace(/\?.*/, ''));
		dom.querySelectorAll('div.elementList > div.left').forEach(tag => { tags.add(tag.textContent.trim()) });
		if (tags.length > 0 && elementWritable(ref = document.getElementById('tags'))) ref.value = tags.toString();
	  }, });
	  return true;
	} else if (url.toLowerCase().includes('databazeknih.cz')) {
	  if (!url.toLowerCase().includes('show=alldesc')) {
		if (!url.includes('?')) { url += '?show=alldesc' } else { url += '&show=alldesc' }
	  }
	  GM_xmlhttpRequest({ method: 'GET', url: url, onload: function(response) {
		if (response.readyState != 4 || response.status != 200) throw new Error('GM_xmlhttpRequest readyState=' + response.status + ', status=' + response.status);
		dom = domParser.parseFromString(response.responseText, 'text/html');
		i = dom.querySelectorAll('span[itemprop="author"] > a');
		if (i != null && elementWritable(ref = document.getElementById('title') || document.querySelector('input[name="title"]'))) {
		  description = joinAuthors(i);
		  if ((i = dom.querySelector('h1[itemprop="name"]')) != null) description += ' – ' + i.textContent.trim();
		  i = dom.querySelector('span[itemprop="datePublished"]');
		  if (i != null && (i = extractYear(i.textContent))) description += ' (' + i + ')';
		  ref.value = description;
		}

		description = '[quote]' + html2php(dom.querySelector('p[itemprop="description"]'), response.finalUrl) + '[/quote]';
		const translation_map = [
		  [/\b(?:orig)/i, 'Original title'],
		  [/\b(?:série)\b/i, 'Series'],
		  [/\b(?:vydáno)\b/i, 'Released'],
		  [/\b(?:stran)\b/i, 'Page count'],
		  [/\b(?:jazyk)\b/i, 'Language'],
		  [/\b(?:překlad)/i, 'Translation'],
		  [/\b(?:autor obálky)\b/i, 'Cover author'],
		];
		dom.querySelectorAll('table.bdetail tr').forEach(function(detail) {
		  var lbl = detail.children[0].textContent.trim();
		  var val = detail.children[1].textContent.trim();
		  if (/(?:žánr|\bvazba)\b/i.test(lbl)) return;
		  translation_map.forEach(k => { if (k[0].test(lbl)) lbl = k[1] });
		  if (/\b(?:ISBN)\b/i.test(lbl) && /\b(\d+(?:-\d+)*)\b/.exec(val) != null) {
			url = new URL('https://www.worldcat.org/isbn/' + RegExp.$1.replace(/-/g, ''));
			val = '[url=' + url.href + ']' + detail.children[1].textContent.trim() + '[/url]';
			findOCLC(url);
		  }
		  description += '\n[b]' + lbl + '[/b] ' + val;
		});

		url = new URL(response.finalUrl);
		description += '\n\n[b]More info:[/b]\n[url]' + url.origin + url.pathname + '[/url]';
		writeDescription(description);

		if ((ref = dom.querySelector('div#icover_mid > a')) != null) setImage(ref.href.replace(/\?.*/, ''));
		if ((ref = dom.querySelector('div#lbImage')) != null && /\burl\("(.*)"\)/i.test(i.style.backgroundImage)) {
		  setImage(RegExp.$1.replace(/\?.*/, ''));
		}

		dom.querySelectorAll('h5[itemprop="genre"] > a').forEach(tag => { tags.add(tag.textContent.trim()) });
		dom.querySelectorAll('a.tag').forEach(tag => { tags.add(tag.textContent.trim()) });
		if (tags.length > 0 && elementWritable(ref = document.getElementById('tags'))) ref.value = tags.toString();
	  }, });
	  return true;
	}
	if (!weak) {
	  addMessage('This domain not supported', 'ua-critical');
	  clipBoard.value = '';
	}
	return false;

	function joinAuthors(nodeList) {
	  if (typeof nodeList != 'object') return null;
	  return Array.from(nodeList).map(it => it.textContent.trim()).join(' & ');
	}

	function findOCLC(url) {
	  if (!url) return false;
	  var oclc = document.querySelector('input[name="oclc"]');
	  if (!elementWritable(oclc)) return false;
	  GM_xmlhttpRequest({
		method: 'GET',
		url: url,
		onload: function(response) {
		  if (response.readyState != 4 || response.status != 200) return;
		  var dom = domParser.parseFromString(response.responseText, 'text/html');
		  var ref = dom.querySelector('tr#details-oclcno > td:last-of-type');
		  if (ref != null) oclc.value = ref.textContent.trim();
		},
	  });
	  return true;
	}
  }

  function preview(n) {
	if (!prefs.auto_preview) return;
	var btn = document.querySelector('input.button_preview_' + n + '[type="button"][value="Preview"]');
	if (btn != null) btn.click();
  }

  function html2php(node, url) {
	var php = '';
	if (node instanceof HTMLElement) node.childNodes.forEach(function(ch) {
	  if (ch.nodeType == 3) {
		php += ch.data.replace(/\s+/g, ' ');
	  } else if (ch.nodeName == 'P') {
		php += '\n' + html2php(ch, url);
	  } else if (ch.nodeName == 'DIV') {
		php += '\n\n' + html2php(ch, url) + '\n\n';
	  } else if (ch.nodeName == 'LABEL') {
		php += '\n\n[b]' + html2php(ch, url) + '[/b]';
	  } else if (ch.nodeName == 'SPAN') {
		php += html2php(ch, url);
	  } else if (ch.nodeName == 'BR' || ch.nodeName == 'HR') {
		php += '\n';
	  } else if (ch.nodeName == 'B' || ch.nodeName == 'STRONG') {
		php += '[b]' + html2php(ch, url) + '[/b]';
	  } else if (ch.nodeName == 'I' || ch.nodeName == 'EM') {
		php += '[i]' + html2php(ch, url) + '[/i]';
	  } else if (ch.nodeName == 'U') {
		php += '[u]' + html2php(ch, url) + '[/u]';
	  } else if (ch.nodeName == 'CODE') {
		php += '[pre]' + ch.textContent + '[/pre]';
	  } else if (ch.nodeName == 'OL') {
		ch.querySelectorAll(':scope > LI').forEach(li => { php += '[#] ' + html2php(li, url) + '\n' });
	  } else if (ch.nodeName == 'UL') {
		ch.querySelectorAll(':scope > LI').forEach(li => { php += '[*] ' + html2php(li, url) + '\n' });
	  } else if (ch.nodeName == 'A') {
		php += ch.childNodes.length > 0 ?
		  '[url=' + deAnonymize(ch.href) + ']' + html2php(ch, url) + '[/url]' :
			'[url]' + deAnonymize(ch.href) + '[/url]';
	  } else if (ch.nodeName == 'IMG') {
		php += '[img]' + (ch.dataset.src || ch.src) + '[/img]';
	  }
	});
	return php;
  }

  function deAnonymize(uri) {
	return typeof uri == 'string' ? uri.replace(/^https?:\/\/(?:www\.)?anonymz\.com\/\?/i, '') : null;
  }

  function writeDescription(desc) {
	if (typeof desc != 'string') return;
	if (elementWritable(ref = document.querySelector('textarea#desc')
		|| document.querySelector('textarea#description'))) ref.value = desc;
	if ((ref = document.getElementById('body')) != null && !ref.disabled) {
	  if (ref.textLength > 0) ref.value += '\n\n';
	  ref.value += desc;
	}
  }

  function setImage(url) {
	var image = document.getElementById('image') || document.querySelector('input[name="image"]');
	if (!elementWritable(image)) return false;
	image.value = url;

	if (prefs.auto_preview_cover && image.id) {
	  if ((child = document.getElementById('cover preview')) == null) {
		elem = document.createElement('div');
		elem.style.paddingTop = '10px';
		child = document.createElement('img');
		child.id = 'cover preview';
		child.style.width = '90%';
		elem.append(child);
		image.parentNode.previousElementSibling.append(elem);
	  }
	  child.src = url;
	}
	if (prefs.auto_rehost_cover) {
	  if (rehostItBtn != null) {
		rehostItBtn.click();
	  } else {
		rehost2PTPIMG([url]).then(urls => { if (urls.length > 0) image.value = urls[0] }).catch(e => { alert(e) });
	  }
	}
  }

  function elementWritable(elem) {
	return elem != null && !elem.disabled && (overwrite || !elem.value || isNWCD && elem.value == '---');
  }
}

function addArtistField() { exec(function() { AddArtistField() }) }
function removeArtistField() { exec(function() { RemoveArtistField() }) }

function array_homogenous(arr) { return arr.every(k => k === arr[0]) }

function titleCase(str) {
  return str.toLowerCase().split(' ').map(x => x[0].toUpperCase() + x.slice(1)).join(' ');
}

function exec(fn) {
  let script = document.createElement('script');
  script.type = 'application/javascript';
  script.textContent = '(' + fn + ')();';
  document.body.appendChild(script); // run the script
  document.body.removeChild(script); // clean up
}

function makeTimeString(duration) {
  let t = Math.abs(Math.round(duration));
  let H = Math.floor(t / 60 ** 2);
  let M = Math.floor(t / 60 % 60);
  let S = t % 60;
  return (duration < 0 ? '-' : '') + (H > 0 ? H + ':' + M.toString().padStart(2, '0') : M.toString()) +
	':' + S.toString().padStart(2, '0');
}

function timeStringToTime(str) {
  if (!/(-\s*)?\b(\d+(?::\d{2})*(?:\.\d+)?)\b/.test(str)) return null;
  var t = 0, a = RegExp.$2.split(':');
  while (a.length > 0) t = t * 60 + parseFloat(a.shift());
  return RegExp.$1 ? -t : t;
}

function extractYear(expr) {
  if (typeof expr == 'number') return Math.round(expr);
  if (typeof expr != 'string') return null;
  if (/\b(\d{4})\b/.test(expr)) return parseInt(RegExp.$1);
  var d = new Date(expr);
  return parseInt(isNaN(d) ? expr : d.getFullYear());
}

function reInParenthesis(expr) { return new RegExp('\\s+\\([^\\(\\)]*'.concat(expr, '[^\\(\\)]*\\)$'), 'i') }
function reInBrackets(expr) { return new RegExp('\\s+\\[[^\\[\\]]*'.concat(expr, '[^\\[\\]]*\\]$'), 'i') }

function addMessage(text, cls, html = false) {
  messages = document.getElementById('UA messages');
  if (messages == null) {
	var ua = document.getElementById('upload assistant');
	if (ua == null) return null;
	messages = document.createElement('TR');
	if (messages == null) return null;
	messages.id = 'UA messages';
	ua.children[0].append(messages);

	elem = document.createElement('TD');
	if (elem == null) return null;
	elem.colSpan = 2;
	elem.className = 'ua-messages-bg';
	messages.append(elem);
  } else {
	elem = messages.children[0]; // tbody
	if (elem == null) return null;
  }
  var div = document.createElement('DIV');
  div.classList.add('ua-messages', cls);
  div[html ? 'innerHTML' : 'textContent'] = text;
  return elem.appendChild(div);
}

function imageDropHandler(evt) {
  evt.preventDefault();
  if (evt.dataTransfer.files.length <= 0) return;
  var image = document.getElementById('image') || document.querySelector('input[name="image"]');
  if (image == null) return;
  evt.currentTarget.disabled = true;
  if (evt.currentTarget.hTimer) {
	clearTimeout(evt.currentTarget.hTimer);
	delete evt.currentTarget.hTimer;
  }
  var origlabel = evt.currentTarget.value;
  evt.currentTarget.value = 'Uploading...';
  evt.currentTarget.style.backgroundColor = '#A00000';
  var evtSrc = evt.currentTarget;
  upload2PTPIMG(evt.dataTransfer.files[0]).then(function(result) {
	if (result.length > 0) {
	  image.value = result[0];
	  evtSrc.style.backgroundColor = '#00A000';
	  evtSrc.hTimer = setTimeout(function() {
		evtSrc.style.backgroundColor = null;
		delete evtSrc.hTimer;
	  }, 10000);
	} else evtSrc.style.backgroundColor = null;
  }).catch(function(e) {
	alert(e);
	evtSrc.style.backgroundColor = null;
  }).then(function() {
	evtSrc.disabled = false;
	evtSrc.value = origlabel;
  });
}

function voidDragHandler(evt) { evt.preventDefault() }

function upload2PTPIMG(file, elem) {
  if (!(file instanceof File)) return Promise.reject('Bad parameter (file)');
  var config = JSON.parse(window.localStorage.ptpimg_it);
  if (!prefs.ptpimg_api_key && !config.api_key) return Promise.reject('API key not set');
  return new Promise(function(resolve, reject) {
	var reader = new FileReader();
	var fr = new Promise(function(resolve, reject) {
	  reader.onload = function() { resolve(reader.result) };
	  reader.readAsBinaryString/*readAsArrayBuffer*/(file);
	});
	fr.then(function(result) {
	  const boundary = '----NN-GGn-PTPIMG';
	  var data = '--' + boundary + '\r\n';
	  data += 'Content-Disposition: form-data; name="file-upload[0]"; filename="' + file.name.toASCII() + '"\r\n';
	  data += 'Content-Type: ' + file.type + '\r\n\r\n';
	  data += result + '\r\n';
	  data += '--' + boundary + '\r\n';
	  data += 'Content-Disposition: form-data; name="api_key"\r\n\r\n';
	  data += (prefs.ptpimg_api_key || config.api_key) + '\r\n';
	  data += '--' + boundary + '--\r\n';
	  GM_xmlhttpRequest({
		method: 'POST',
		url: 'https://ptpimg.me/upload.php',
		responseType: 'json',
		headers: {
		  'Content-Type': 'multipart/form-data; boundary=' + boundary,
		  'Content-Length': data.length,
		},
		data: data,
		binary: true,
		onload: function(response) {
		  if (response.readyState == 4 && response.status == 200) {
			resolve(response.response.map(item => 'https://ptpimg.me/' + item.code + '.' + item.ext));
		  } else reject('Response error ' + response.status + ' (' + response.statusText + ')');
		},
		onprogress: elem instanceof HTMLInputElement ?
			arg => { elem.value = 'Uploading... (' + arg.position + '%)' } : undefined,
		onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
		ontimeout: function() { reject('Timeout') },
	  });
	});
  });
}

function rehost2PTPIMG(urls) {
  if (!Array.isArray(urls)) return Promise.reject('Bad parameter (urls)');
  var config = JSON.parse(window.localStorage.ptpimg_it);
  if (!prefs.ptpimg_api_key && !config.api_key) return Promise.reject('API key not set');
  return new Promise(function(resolve, reject) {
	const boundary = 'NN-GGn-PTPIMG';
	const dcTest = /^https?:\/\/(?:\w+\.)?discogs\.com\//i;
	var data = '--' + boundary + "\n";
	data += 'Content-Disposition: form-data; name="link-upload"\n\n';
	data += urls.map(url => dcTest.test(url) ? 'https://reho.st/' + url : url).join('\n') + '\n';
	data += '--' + boundary + '\n';
	data += 'Content-Disposition: form-data; name="api_key"\n\n';
	data += (prefs.ptpimg_api_key || config.api_key) + '\n';
	data += '--' + boundary + '--';
	GM_xmlhttpRequest({
	  method: 'POST',
	  url: 'https://ptpimg.me/upload.php',
	  responseType: 'json',
	  headers: {
		'Content-type': 'multipart/form-data; boundary=' + boundary,
		'Content-Length': data.length,
	  },
	  data: data,
	  onload: function(response) {
		if (response.readyState == 4 && response.status == 200) {
		  resolve(response.response.map(item => 'https://ptpimg.me/' + item.code + '.' + item.ext));
		} else reject('Response error ' + response.status + ' (' + response.statusText + ')');
	  },
	  onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
	  ontimeout: function() { reject('Timeout') },
	});
  });
}

function qobuzTranslations() {
  return [
	// fr-fr
	['Accordéon', 'Accordion'],
	//['Acid jazz', 'Acid Jazz'],
	['Afrique', 'Africa'],
	['Allemagne', 'Germany'],
	['Allemand', 'German'],
	['Alternatif et Indé', 'Alternative & Indie'],
	['Ambiance', 'Ambient/New Age'],
	['Amérique du Nord', 'North America'],
	['Amérique latine', 'Latin America'],
	['Anglais', 'English'],
	['Asie', 'Asia'],
	['Bandes originales de films', 'Film Soundtracks'],
	['Be Bop', 'Bebop'],
	['Bossa Nova & Brésil', 'Bossa Nova & Brazil'],
	['Cantates (profanes)', 'Cantatas (secular)'],
	['Cantates sacrées', 'Cantatas (sacred)'],
	['Celtique', 'Celtic'],
	['Chanson française rétro', 'Retro French Music'],
	['Chanson française', 'French Music'],
	['Chansons paillardes', 'Bawdy songs'],
	['Chœurs sacrés', 'Choirs (sacred)'],
	['Classique', 'Classical'],
	['Comédies musicales', 'Musical Theatre'],
	['Concertos pour clavier', 'Keyboard Concertos'],
	['Concertos pour instruments à vent', 'Concertos for wind instruments'],
	['Concertos pour trompette', 'Concertos for trumpet'],
	['Concertos pour violon', 'Violin Concertos'],
	['Concertos pour violoncelle', 'Cello Concertos'],
	['Contes et comptines', 'Stories and Nursery Rhymes'],
	//['Cool jazz', 'Cool Jazz'],
	['Diction', 'Comedy/Other'],
	['Documents historiques', 'Historical Documents'],
	//['Downtempo', 'Chill-out'],
	['Duos', 'Duets'],
	['Ecosse', 'Scottish'],
	['Educatif', 'Educational'],
	['Électronique ou concrète', 'Experimental'],
	['Électronique', 'Electronic/Dance'],
	['Enfants', 'Children'],
	['Espagne', 'Spain'],
	['Europe de l\'Est', 'Eastern Europe'],
	['Extraits d\'opéra', 'Opera Extracts'],
	['Film', 'Soundtracks'],
	//['Folk', 'Folk/Americana'],
	['Français', 'French'],
	//['Free jazz & Avant-garde', 'Free Jazz & Avant-Garde'],
	['Gipsy', 'Gypsy'],
	['Grèce', 'Greece'],
	['Interprètes de chanson française', 'French Artists'],
	['Intégrales d\'opéra', 'Full Operas'],
	['Irish celtic', 'Irish Celtic'],
	['Irish popmusic', 'Irish Pop Music'],
	['Irlande', 'Ireland'],
	['Italie', 'Italy'],
	['Jazz contemporain', 'Contemporary Jazz'],
	['Jazz fusion & Jazz rock', 'Jazz Fusion & Jazz Rock'],
	['Jazz manouche', 'Gypsy Jazz'],
	['Jazz traditionnel & New Orleans', 'Traditional Jazz & New Orleans'],
	['Jazz vocal', 'Vocal Jazz'],
	['Jeux vidéo', 'Video Games'],
	['Karaoké', 'Karaoke'],
	//['Latin jazz', 'Latin Jazz'],
	['Lieder (Allemagne)', 'Lieder (German)'],
	['Lieder', 'Art Songs'],
	['Littérature', 'Literature'],
	['Messes, Passions, Requiems', 'Masses, Passions, Requiems'],
	['Musique chorale (pour chœur)', 'Choral Music (Choirs)'],
	['Musique concertante', 'Concertos'],
	['Musique concrète', 'Musique Concrète'],
	['Musique de chambre', 'Chamber Music'],
	['Musique de scène', 'Theatre Music'],
	['Musique folklorique Suisse', 'Swiss Folk Music'],
	['Musique indienne', 'Indian Music'],
	['Musique militaire', 'Military Music'],
	['Musique minimaliste', 'Minimal Music'],
	['Musique pour ensembles vocaux', 'Music by vocal ensembles'],
	['Musique symphonique', 'Symphonic Music'],
	['Musique vocale (profane et sacrée)', 'Vocal Music (Secular and Sacred)'],
	['Musique vocale profane', 'Secular Vocal Music'],
	['Musique vocale sacrée', 'Sacred Vocal Music'],
	['Musique électronique', 'Electronic'],
	['Musiques de Noël', 'Christmas Music'],
	['Musiques du monde', 'World'],
	['Musiques pour le cinéma', 'Cinema Music'],
	['Mélodies & Lieder', 'Art Songs, Mélodies & Lieder'],
	['Mélodies (Angleterre)', 'Mélodies (England)'],
	['Mélodies (Europe du Nord)', 'Mélodies (Northern Europe)'],
	['Mélodies (France)', 'Mélodies (French)'],
	['Néerlandais', 'Dutch'],
	['Opéra', 'Opera'],
	['Opérette', 'Operettas'],
	['Oratorios profanes', 'Oratorios (secular)'],
	['Oratorios sacrés', 'Sacred Oratorios'],
	['Orient', 'Oriental Music'],
	['Ouvertures', 'Overtures'],
	['Piano solo', 'Solo Piano'],
	['Pop indé', 'Indie Pop'],
	['Poèmes symphoniques', 'Symphonic Poems'],
	['Punk - New Wave', 'Punk / New Wave'],
	['Pédagogie', 'Educational'],
	['Quatuors', 'Quartets'],
	['Quintettes', 'Quintets'],
	//['Rap', 'Rap/Hip-Hop'],
	['Rock français', 'French Rock'],
	['Rock progressif', 'Progressive Rock'],
	['Russie', 'Russia'],
	['Récitals vocaux', 'Vocal Recitals'],
	//['Soul/Funk/R&B', 'R&B, Soul'],
	['Séries TV', 'TV Series'],
	['Turquie', 'Turkey'],
	['Variété internationale', 'International Pop'],
	['Violon solo', 'Violin Solos'],
	['Violoncelle solo', 'Cello Solos'],
	// de-de
	['Afrika', 'Africa'],
	['Akkordeon', 'Accordion'],
	['Alternativ und Indie', 'Alternative & Indie'],
	['Asien', 'Asia'],
	['Aus aller Welt', 'World'],
	['Ballett', 'Ballets'],
	['Bildung', 'Educational'],
	['BläserKonzerte', 'Concertos for wind instruments'],
	['Bossa Nova & brasilianische Musik', 'Bossa Nova & Brazil'],
	['Cellokonzerte', 'Cello Concertos'],
	['Cellosolo', 'Cello Solos'],
	['Chorwerk (für den Chor)', 'Choral Music (Choirs)'],
	['Computerspiele', 'Video Games'],
	['Crooner', 'Crooners'],
	['Deutsch', 'German'],
	['Deutsche Musik', 'Germany'],
	['Duette', 'Duets'],
	['Electronic', 'Electronic/Dance'],
	['Elektronische Musik oder Musique concrète', 'Experimental'],
	['Elektronische Musik', 'Electronic'],
	['Englisch', 'English'],
	['Entspannung', 'Relaxation'],
	['Europa', 'Europe'],
	['Filmmusik', 'Cinema Music'],
	['Französisch', 'French'],
	['Französische Chanson-Sänger', 'French Artists'],
	['Französische Mélodies (Frankreich)', 'Mélodies (French)'],
	['Französische Mélodies und Kunstlieder', 'Art Songs, Mélodies & Lieder'],
	['Französischer Chanson', 'French Music'],
	['Französischer Rock', 'French Rock'],
	['Französisches Retro-Chanson', 'Retro French Music'],
	['Free Jazz & Avantgarde', 'Free Jazz & Avant-Garde'],
	['Geistliche Chormusik', 'Choirs (sacred)'],
	['Geistliche Kantaten', 'Cantatas (sacred)'],
	['Geistliche Oratorien', 'Sacred Oratorios'],
	['Geistliche Vokalmusik', 'Sacred Vocal Music'],
	['Gesamtaufnahmen von Opern', 'Full Operas'],
	['Gesangsrezitale', 'Vocal Recitals'],
	['Griechenland', 'Greece'],
	['Gypsy-Jazz', 'Gypsy Jazz'],
	['Historische Dokumente', 'Historical Documents'],
	['Humor', 'Humour'],
	['Hörbücher', 'Comedy/Other'],
	['Indie-Pop', 'Indie Pop'],
	['Indische Musik', 'Indian Music'],
	['Instrumentalmusik', 'Concertos'],
	['Intermezzi', 'Theatre Music'],
	['Internationaler Pop', 'International Pop'],
	['Irisch-keltische Musik', 'Irish Celtic'],
	['Irische Popmusik', 'Irish Pop Music'],
	['Irland', 'Ireland'],
	['Italien', 'Italy'],
	['Jazz Fusion & Jazzrock', 'Jazz Fusion & Jazz Rock'],
	['Jazzgesang', 'Vocal Jazz'],
	['Jiddische Musik & Klezmer', 'Yiddish & Klezmer'],
	['Kammermusik', 'Chamber Music'],
	['Kantaten (weltlich)', 'Cantatas (secular)'],
	['Keltische Musik', 'Celtic'],
	['Kinder', 'Children'],
	['Klassik', 'Classical'],
	['Klassischer Jazz & New-Orleans-Jazz', 'Traditional Jazz & New Orleans'],
	['Klavierkonzerte', 'Keyboard Concertos'],
	['Klaviersolo', 'Solo Piano'],
	['Kunstlieder (Deutschland)', 'Lieder (German)'],
	['Kunstlieder', 'Art Songs'],
	['Lateinamerika', 'Latin America'],
	['Literatur', 'Literature'],
	['Messen, Passionen, Requiems', 'Masses, Passions, Requiems'],
	['Militärmusik', 'Military Music'],
	//['Modern Jazz', 'Contemporary Jazz'],
	['Musical', 'Musical Theatre'],
	['Musik der Roma', 'Gypsy'],
	['Musik für Vokalensembles', 'Music by vocal ensembles'],
	['Märchen und Kinderreime', 'Stories and Nursery Rhymes'],
	['Mélodies (Nordeuropa)', 'Mélodies (Northern Europe)'],
	['Niederländisch', 'Dutch'],
	['Nordamerika', 'North America'],
	['Oper', 'Opera'],
	['Operette', 'Operettas'],
	['Opernauszüge', 'Opera Extracts'],
	['Original Soundtrack', 'Film Soundtracks'],
	['Osteuropa', 'Eastern Europe'],
	['Ouvertüren', 'Overtures'],
	['Punk – New Wave', 'Punk / New Wave'],
	['Pädagogik', 'Educational'],
	['Quartette', 'Quartets'],
	['Quintette', 'Quintets'],
	['Russland', 'Russia'],
	['Schottland', 'Scottish'],
	['Schweizer Volksmusik', 'Swiss Folk Music'],
	['Spanien', 'Spain'],
	['Stimmungsmusik', 'Stimmungsmusik '],
	['Symphonien', 'Symphonies'],
	['Symphonieorchester', 'Symphonic Music'],
	['Symphonische Dichtung', 'Symphonic Poems'],
	['Trompetenkonzerte', 'Concertos for trumpet'],
	['TV-Serien', 'TV Series'],
	['Türkei', 'Turkey'],
	['Violinensolo', 'Violin Solos'],
	['Violinkonzerte', 'Violin Concertos'],
	['Vokalmusik (weltlich und geistlich)', 'Vocal Music (Secular and Sacred)'],
	['Weihnachtsmusik', 'Christmas Music'],
	['Weltliche Oratorien', 'Oratorios (secular)'],
	['Weltliche Vokalmusik', 'Secular Vocal Music'],
	['Zouk & Musik von den Antillen', 'Zouk & Antilles'],
	// es-es
	['Acordeón', 'Accordion'],
	['África', 'Africa'],
	['Alemania', 'Germany'],
	['Alemán', 'German'],
	['Alternativa & Indie', 'Alternative & Indie'],
	['Ambientes', 'Ambient'],
	['Audiolibros', 'Comedy/Other'],
	['Bandas sonoras de cine', 'Film Soundtracks'],
	['Bandas sonoras', 'Cinema Music'],
	['Bossa nova & Brasil', 'Bossa Nova & Brazil'],
	['Canciones gamberras', 'Bawdy songs'],
	['Cantatas (profanas)', 'Cantatas (secular)'],
	['Cantatas sacras', 'Cantatas (sacred)'],
	['Celta', 'Celtic'],
	['Chanson francesa retro', 'Retro French Music'],
	['Chanson francesa', 'French Music'],
	['Cine', 'Soundtracks'],
	['Clásica', 'Classical'],
	['Comedias musicales', 'Musical Theatre'],
	['Conciertos para instrumentos de viento', 'Concertos for wind instruments'],
	['Conciertos para tecla', 'Keyboard Concertos'],
	['Conciertos para trompeta', 'Concertos for trumpet'],
	['Conciertos para violonchelo', 'Cello Concertos'],
	['Conciertos para violín', 'Violin Concertos'],
	['Coros sacros', 'Choirs (sacred)'],
	['Cuartetos', 'Quartets'],
	['Cuentos & canciones infantiles', 'Stories and Nursery Rhymes'],
	['Dixie', 'Dixieland'],
	['Documentos históricos', 'Historical Documents'],
	['Dúos', 'Duets'],
	['Educativa', 'Educational'],
	['Electrónica o musique concrète', 'Experimental'],
	['Electrónica', 'Electronic/Dance'],
	['Escocia', 'Scottish'],
	['España', 'Spain'],
	['Europa del Este', 'Eastern Europe'],
	['Fragmentos de ópera', 'Opera Extracts'],
	['Francés', 'French'],
	['Free jazz & Vanguardia', 'Free Jazz & Avant-Garde'],
	['Gitano', 'Gypsy'],
	['Grecia', 'Greece'],
	['Infantil', 'Children'],
	['Inglés', 'English'],
	['Integrales de ópera', 'Full Operas'],
	['Intérpretes de chanson francesa', 'French Artists'],
	['Irlanda', 'Ireland'],
	['Italia', 'Italy'],
	['Jazz contemporáneo', 'Contemporary Jazz'],
	['Jazz fusión & Jazz rock', 'Jazz Fusion & Jazz Rock'],
	['Jazz tradicional & Nueva Orleans', 'Traditional Jazz & New Orleans'],
	['Latinoamérica', 'Latin America'],
	['Lieder (Alemania)', 'Lieder (German)'],
	['Literatura', 'Literature'],
	['Magreb', 'Maghreb'],
	['Misas, Pasiones, Réquiems', 'Masses, Passions, Requiems'],
	['Musique concréte', 'Musique Concrète'],
	['Mélodies (Europa del Norte)', 'Mélodies (Northern Europe)'],
	['Mélodies (Francia)', 'Mélodies (French)'],
	['Mélodies (Inglaterra)', 'Mélodies (England)'],
	['Música celta irlandesa', 'Irish Celtic'],
	['Música concertante', 'Concertos'],
	['Música coral (para coro)', 'Choral Music (Choirs)'],
	['Música de cámara', 'Chamber Music'],
	['Música electrónica', 'Electronic'],
	['Música escénica', 'Theatre Music'],
	['Música folclórica suiza', 'Swiss Folk Music'],
	['Música india', 'Indian Music'],
	['Música militar', 'Military Music'],
	['Música minimalista', 'Minimal Music'],
	['Música para conjuntos vocales', 'Music by vocal ensembles'],
	['Música pop irlandesa', 'Irish Pop Music'],
	['Música sinfónica', 'Symphonic Music'],
	['Música vocal (profana y sacra)', 'Vocal Music (Secular and Sacred)'],
	['Música vocal profana', 'Secular Vocal Music'],
	['Música vocal sacra', 'Sacred Vocal Music'],
	['Músicas navideñas', 'Christmas Music'],
	['Neerlandés', 'Dutch'],
	['Norteamérica', 'North America'],
	['Oberturas', 'Overtures'],
	['Ópera', 'Opera'],
	['Opereta', 'Operettas'],
	['Oratorios profanos', 'Oratorios (secular)'],
	['Oratorios sacros', 'Sacred Oratorios'],
	['Oriente', 'Oriental Music'],
	['Pedagogía', 'Educational'],
	['Poemas sinfónicos', 'Symphonic Poems'],
	['Pop indie', 'Indie Pop'],
	['Quintetos', 'Quintets'],
	['Recitales vocales', 'Vocal Recitals'],
	['Relajación', 'Relaxation'],
	['Rock francés', 'French Rock'],
	['Rock progresivo', 'Progressive Rock'],
	['Rusia', 'Russia'],
	['Series de televisión', 'TV Series'],
	['Sinfonías', 'Symphonies'],
	['Tríos', 'Trios'],
	['Turquía', 'Turkey'],
	['Variété internacional', 'International Pop'],
	['Violonchelo solo', 'Cello Solos'],
	['Violín solo', 'Violin Solos'],
	['Vídeojuegos', 'Video Games'],
	['Zouk & Antillas', 'Zouk & Antilles'],
	// it-it
	['Amercia del nord', 'North America'],
	['America latina', 'Latin America'],
	['Artisti francesi', 'French Artists'],
	['Assoli per pianoforte', 'Solo Piano'],
	['Assoli per violino', 'Violin Solos'],
	['Assoli per violoncello', 'Cello Solos'],
	['Balletti', 'Ballets'],
	['Bossa nova e musica brasiliana ', 'Bossa Nova & Brazil'],
	['Cantate (profane)', 'Cantatas (secular)'],
	['Cantate sacre', 'Cantatas (sacred)'],
	['Canzoni di Natale', 'Christmas Music'],
	['Canzoni licenziose', 'Bawdy songs'],
	['Cinema', 'Soundtracks'],
	['Classica', 'Classical'],
	['Colonne sonore', 'Film Soundtracks'],
	['Concerti per strumenti a fiato', 'Concertos for wind instruments'],
	['Concerti per tastiera', 'Keyboard Concertos'],
	['Concerti per tromba', 'Concertos for trumpet'],
	['Concerti per violino', 'Violin Concertos'],
	['Concerti per violoncello', 'Cello Concertos'],
	['Cori sacri', 'Choirs (sacred)'],
	['Documenti storici', 'Historical Documents'],
	['Duetti', 'Duets'],
	['Elettronica', 'Electronic/Dance'],
	['Estratti d\'opera', 'Opera Extracts'],
	['Europa dell\'est', 'Eastern Europe'],
	['Fisarmonica', 'Accordion'],
	['Francese', 'French'],
	['Free jazz et jazz d\'avanguardia', 'Free Jazz & Avant-Garde'],
	['Fusion & Jazz rock', 'Jazz Fusion & Jazz Rock'],
	['Germania', 'Germany'],
	//['Hard rock', 'Hard Rock'],
	//['Indie pop', 'Indie Pop'],
	['Infanzia', 'Children'],
	['Inglese', 'English'],
	['Jazz contemporaneo', 'Contemporary Jazz'],
	['Jazz tradizionale & New Orleans', 'Traditional Jazz & New Orleans'],
	['Letteratura', 'Literature'],
	['Lieder (Germania)', 'Lieder (German)'],
	['Messe, Passioni, Requiem', 'Masses, Passions, Requiems'],
	['Musica alternativa e indie', 'Alternative & Indie'],
	['Musica celtica irlandese', 'Irish Celtic'],
	['Musica celtica', 'Celtic'],
	['Musica concertante', 'Concertos'],
	['Musica concreta', 'Musique Concrète'],
	['Musica corale', 'Choral Music (Choirs)'],
	['Musica crooner', 'Crooners'],
	['Musica da camera', 'Chamber Music'],
	['Musica d\'ambiente/New Age', 'Ambient/New Age'],
	['Musica educativa', 'Educational'],
	['Musica elettronica', 'Electronic'],
	['Musica elettronica/concreta', 'Experimental'],
	['Musica folclorica svizzera', 'Swiss Folk Music'],
	['Musica francese retrò', 'Retro French Music'],
	['Musica francese', 'French Music'],
	['Musica indiana', 'Indian Music'],
	['Musica militare', 'Military Music'],
	['Musica minimalista', 'Minimal Music'],
	['Musica new Age', 'New Age'],
	['Musica orientale', 'Oriental Music'],
	['Musica per insiemi vocali', 'Music by vocal ensembles'],
	['Musica pop irlandese', 'Irish Pop Music'],
	['Musica rilassante', 'Relaxation'],
	['Musica sinfonica', 'Symphonic Music'],
	['Musica vocale (sacra e profana)', 'Vocal Music (Secular and Sacred)'],
	['Musica vocale profana', 'Secular Vocal Music'],
	['Musica vocale sacra', 'Sacred Vocal Music'],
	['Musica yiddish e klezmer', 'Yiddish & Klezmer'],
	['Musica zouk e Antille', 'Zouk & Antilles'],
	['Musiche di scena', 'Theatre Music'],
	['Musiche per il cinema', 'Cinema Music'],
	['Mélodies (Europa del nord)', 'Mélodies (Northern Europe)'],
	['Mélodies (Inghilterra)', 'Mélodies (England)'],
	['Olandese', 'Dutch'],
	['Opere integrali', 'Full Operas'],
	['Operetta', 'Operettas'],
	['Oratori profani', 'Oratorios (secular)'],
	['Oratori sacri', 'Sacred Oratorios'],
	['Overture', 'Overtures'],
	['Poemi sinfonici', 'Symphonic Poems'],
	['Pop internazionale', 'International Pop'],
	['Portogallo', 'Portugal'],
	['Punk/New wave', 'Punk / New Wave'],
	['Quartetti', 'Quartets'],
	['Quintetti', 'Quintets'],
	['Racconti e filastrocche', 'Stories and Nursery Rhymes'],
	['Recital vocali', 'Vocal Recitals'],
	['Rock francese', 'French Rock'],
	['Rock progressivo', 'Progressive Rock'],
	['Scozia', 'Scottish'],
	['Serie TV', 'TV Series'],
	['Sinfonie', 'Symphonies'],
	['Ska e rocksteady', 'Ska & Rocksteady'],
	['Spagna', 'Spain'],
	//['Spoken Word', 'Comedy/Other'],
	['Tedesco', 'German'],
	['Trii', 'Trios'],
	['Turchia', 'Turkey'],
	['Umorismo', 'Humour'],
	['Video Giochi', 'Video Games'],
	//['Vocal jazz', 'Vocal Jazz'],
	//['World music', 'World'],
	// nl-nl
	['Accordeon', 'Accordion'],
	['Alternative en Indie', 'Alternative & Indie'],
	['Ambient / New Age / Easy Listening', 'Ambient/New Age'],
	['Azië', 'Asia'],
	['Balletten', 'Ballets'],
	//['Blues/country/folk', 'Blues/Country/Folk'],
	['Bossanova en Brazilië', 'Bossa Nova & Brazil'],
	['Cabaret/ Komedie / Luisterboek', 'Comedy/Other'],
	['Cantates (wereldlijk)', 'Cantatas (secular)'],
	['Cello solo', 'Cello Solos'],
	['Concerten voor blaasinstrumenten', 'Concertos for wind instruments'],
	['Concerten voor cello', 'Cello Concertos'],
	['Concerten voor klavier', 'Keyboard Concertos'],
	['Concerten voor trompet', 'Concertos for trumpet'],
	['Concerten voor viool', 'Violin Concertos'],
	['Concertmuziek', 'Concertos'],
	['Cooljazz', 'Cool Jazz'],
	['Drum \'n\' bass', 'Drum & Bass'],
	['Duits', 'German'],
	['Duitsland', 'Germany'],
	['Duo´s', 'Duets'],
	['Educatief', 'Educational'],
	['Elektronische muziek of Musique Concrète', 'Experimental'],
	['Elektronische muziek', 'Electronic'],
	['Engels', 'English'],
	['Frans', 'French'],
	['Franse chansons', 'French Music'],
	['Franse rock', 'French Rock'],
	['Free jazz & Avant-garde jazz', 'Free Jazz & Avant-Garde'],
	['Gipsy jazz', 'Gypsy Jazz'],
	['Griekenland', 'Greece'],
	['Hardrock', 'Hard Rock'],
	['Historische documenten', 'Historical Documents'],
	['Ierland', 'Ireland'],
	['Iers Keltisch', 'Irish Celtic'],
	['Ierse popmuziek', 'Irish Pop Music'],
	['Indiase muziek', 'Indian Music'],
	['Indiepop', 'Indie Pop'],
	['Internationaal variété', 'International Pop'],
	['Italië', 'Italy'],
	['Jazz fusion en jazz rock', 'Jazz Fusion & Jazz Rock'],
	['Jiddisch en klezmer', 'Yiddish & Klezmer'],
	['Kamermuziek', 'Chamber Music'],
	['Keltisch', 'Celtic'],
	['Kerstmuziek', 'Christmas Music'],
	['Kinderen', 'Children'],
	['Klassiek', 'Classical'],
	['Koormuziek', 'Choral Music (Choirs)'],
	['Kwartetten', 'Quartets'],
	['Kwintetten', 'Quintets'],
	['Latijns-Amerika', 'Latin America'],
	['Liederen (Duitsland)', 'Lieder (German)'],
	['Liederen (Engeland)', 'Mélodies (England)'],
	['Liederen (Frankrijk)', 'Mélodies (French)'],
	['Liederen (Noord-Europa)', 'Mélodies (Northern Europe)'],
	['Liederen', 'Art Songs, Mélodies & Lieder'],
	['Literatuur', 'Literature'],
	['Militaire muziek', 'Military Music'],
	['Minimalistische muziek', 'Minimal Music'],
	['Missen, passies, requiems', 'Masses, Passions, Requiems'],
	['Moderne jazz', 'Contemporary Jazz'],
	['Musicals', 'Musical Theatre'],
	['Muziek voor vocale ensembles', 'Music by vocal ensembles'],
	['Nederlands', 'Dutch'],
	//['New age', 'New Age'],
	['Noord-Afrika', 'Maghreb'],
	['Noord-Amerika', 'North America'],
	['Ontspanning', 'Relaxation'],
	['Oost-Europa', 'Eastern Europe'],
	['Oosters', 'Oriental Music'],
	['Operafragmenten', 'Opera Extracts'],
	['Originele soundtracks', 'Film Soundtracks'],
	['Oude Franse chansons', 'Retro French Music'],
	['Pedagogiek', 'Educational'],
	['Progressieve rock', 'Progressive Rock'],
	['Punk en New Wave', 'Punk / New Wave'],
	['Religieuze cantates', 'Cantatas (sacred)'],
	['Religieuze koormuziek', 'Choirs (sacred)'],
	['Religieuze vocale muziek', 'Sacred Vocal Music'],
	['Rusland', 'Russia'],
	['Schotland', 'Scottish'],
	['Schuine liedjes', 'Bawdy songs'],
	['Ska en Rocksteady', 'Ska & Rocksteady'],
	['Soundtrack', 'Soundtracks'],
	['Spanje', 'Spain'],
	['Sprookjes en vertellingen', 'Stories and Nursery Rhymes'],
	['Symfonieën', 'Symphonies'],
	['Symfonische gedichten', 'Symphonic Poems'],
	['Symfonische muziek', 'Symphonic Music'],
	['Toneelmuziek', 'Theatre Music'],
	['Traditionele jazz en dixieland', 'Traditional Jazz & New Orleans'],
	['Trio´s', 'Trios'],
	['Triphop', 'Trip Hop'],
	['Turkije', 'Turkey'],
	['Tv-series', 'TV Series'],
	['Videogames', 'Video Games'],
	['Viool solo', 'Violin Solos'],
	['Vocale jazz', 'Vocal Jazz'],
	['Vocale muziek (wereldlijk en religieus)', 'Vocal Music (Secular and Sacred)'],
	['Vocale recitals', 'Vocal Recitals'],
	['Volledige opera\'s', 'Full Operas'],
	['Wereldlijke oratoria', 'Oratorios (secular)'],
	['Wereldlijke vocale muziek', 'Secular Vocal Music'],
	['Wereldmuziek', 'World'],
	['Zangers van Franse chansons', 'French Artists'],
	['Zigeunermuziek', 'Gypsy'],
	['Zouk en Antilliaans', 'Zouk & Antilles'],
	['Zwitserse volksmuziek', 'Swiss Folk Music'],
	// be-fr
	['Variété francophone', 'French Music'],
  ];
}