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.

Fra og med 26.11.2019. Se Den nyeste version.

// ==UserScript==
// @name         RED/NWCD Upload Assistant
// @namespace    https://greasyfork.org/users/321857-anakunda
// @version      1.186
// @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
//
// 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('remap_texttools_newlines', 0); // convert underscores to linebreaks (ambiguous)
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 emphasis works
prefs.set('always_hide_dnu_list', 0); // risky!
prefs.set('single_threshold', 8 * 60); // Max length of single in s
prefs.set('EP_threshold', 29 * 60); // Max time of EP in s
prefs.set('autfill_delay', 1000); // delay in ms to autofill for after pasting text into box, 0 to disable
prefs.set('auto_preview_cover', 1);
prefs.set('auto_rehost_cover', 1);
prefs.set('fetch_tags_from_artist', 0); // add n most used tags from release artist (if one) - experimental
prefs.set('always_request_perfect_flac', 0);
prefs.set('request_default_bounty', 0);
prefs.set('ptpimg_api_key');
prefs.set('spotify_clientid');
prefs.set('spotify_clientsecret');
prefs.set('soundcloud_clientid');
prefs.set('catbox_userhash');
prefs.set('dragdrop_patch_to_ptpimgit', 1);
prefs.set('upcoming_tags', ''); // add this tag(s) to upcoming releases (requests); empty to disable
prefs.set('estimate_decade_tag', 1); // deduce decade tag (1980s, etc.) from album year for regular albums
prefs.set('sacd_decoder', 'foobar2000\'s SACD decoder (direct-fp64)');
// tracklist specific
prefs.set('tracklist_style', 1); // 1: classical, 2: propertional right aligned
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_artist_color', '#889B2F');
prefs.set('tracklist_composer_color', '#556B2F');
prefs.set('tracklist_duration_color', '#8bb500');

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 (prefs.always_hide_dnu_list && (ref = document.querySelector('div#content > div:first-of-type')) != null) {
//   ref.style.display = 'none'; // Hide DNU list (warning - risky!)
// }

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;
  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á)$/,
	  /^(?:[Aa]nonym)/,
	  /^(?:[Ll]iturgical\b|[Ll]iturgick[áý])/,
	  /^(?:auditorium|[Oo]becenstvo|[Pp]ublikum)$/,
	  /^(?:Various\s+Composers)$/i,
	];
	const noAKAs = /\s+(?:aka|AKA)\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))
		|| totalTime > 0 && totalTime <= prefs.single_threshold) {
	  releaseType = getReleaseIndex('Single');
	} else if (tracks.every(it => it.identifiers.RELEASETYPE == 'EP')
		|| totalTime > 0 && totalTime <= prefs.EP_threshold) {
	  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 ? artist.replace(noAKAs, '') : 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 name.replace(noAKAs, '');
	}
	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 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 = artist.replace(noAKAs, '');
		  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 (tracks.every(function(it) {
		const p = /^([^\(\)\[\]]+?)(?:\s+(?:\([^\(\)]*\)|\[[^\[\]]*\]))?$/;
		try { return p.exec(it.title)[1] == p.exec(tracks[0].title)[1] } catch(e) { return false }
	  })) {
		releaseType = getReleaseIndex('Single');
	  } 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 ((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);
	  });
	  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 {
		if (/^https?:\/\/(\w+\.)?discogs\.com\/release\/[\w\-]+\/?$/i.test(url)) url += '/images';
		getCoverOnline(url);
	  }
	}
	// 	} else if (elementWritable(document.getElementById('image') || document.querySelector('input[name="image"]'))
	// 	  && ((ref = document.getElementById('album_desc')) != null || (ref = document.getElementById('body')) != null)
	// 		&& ref.textLength > 0 && (matches = ref.value.matchAll(/\b(https?\/\/[\w\-\&\_\?\=]+)/i)) != null) {

	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) {
	  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');
		  }
		  if (prefs.tracklist_style == 1) { // STYLE 1 ----------------------------------------
			prologue('[size=2]', '[/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]';
		  } else if (prefs.tracklist_style == 2) { // STYLE 2 ----------------------------------------
			prologue('[size=2][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();
			}
		  }
		}
		if (prefs.tracklist_style == 1 && totalTime > 0) {
		  playlist += '\n\n' + divs[0].repeat(10) + '\n[color=' + prefs.tracklist_duration_color +
			']Total time: [i]' + makeTimeString(totalTime) + '[/i][/color][/size]';
		} else if (prefs.tracklist_style == 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]';
		}

		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) return;
		  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__tracks > 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;
			track = [
			  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,
			];
			tracks.push(track.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) return;
		  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;
			track = [
			  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,
			];
			tracks.push(track.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) return;
		  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('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(k => { tags.add(k.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;
			track = [
			  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,
			];
			tracks.push(track.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) return;
		  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 = [];
			track = [
			  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,
			];
			tracks.push(track.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) return;
			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))
				  });
				track = [
				  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(' '),
				];
				tracks.push(track.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('Response error ' + response.status + ' (' + response.statusText + ')');
		  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] = [];
				track = [
				  isVA ? 'Various Artists' : 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,
				];
				tracks.push(track.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'],
			  ['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) return;
		  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;
			track = [
			  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,
			];
			tracks.push(track.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) return;
		  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);
			track = [
			  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, ''),
			];
			tracks.push(track.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) return;

		  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;
			track = [
			  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(),
			];
			tracks.push(track.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) return;
		  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);
			track = [
			  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,
			];
			tracks.push(track.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('Ready state ' + response.readyState + ', Response error ' + response.status + ' (' + response.statusText + ')');
			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;
			  track = [
				isVA ? 'Various Artists' : 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,
			  ];
			  tracks.push(track.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 = [];
			track = [
			  isVA ? 'Various Artists' : 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,
			];
			tracks.push(track.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('Response error ' + response.status + ' (' + response.statusText + ')');
				},
				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) return;
		  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;
			}
			track = [
			  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(),
			];
			tracks.push(track.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;
			track = [
			  isVA ? 'Various Artists' : 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,
			];
			tracks.push(track.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 = 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/');
	}

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