Greasy Fork is available in English.

RED (+ NWCD, Orpheus) Upload Assistant

Script fills in as much accurately the upload and group edit forms based on foobar2000's playlist selection via pasted output of copy command, release consistency check, two tracklist layouts, basic colours customization, featured artists extraction, image URl fetching from store and more...

As of 31/08/2019. See the latest version.

// ==UserScript==
// @name         RED (+ NWCD, Orpheus) Upload Assistant
// @namespace    https://greasyfork.org/cs/users/321857-anakunda
// @version      1.721
// @description  Script fills in as much accurately the upload and group edit forms based on foobar2000's playlist selection via pasted output of copy command, release consistency check, two tracklist layouts, basic colours customization, featured artists extraction, image URl fetching from store and more...
// @author       Anakunda
// @iconURL      https://redacted.ch/favicon.ico
// @match        https://redacted.ch/upload.php*
// @match        https://redacted.ch/torrents.php?action=editgroup*
// @match        https://notwhat.cd/upload.php*
// @match        https://notwhat.cd/torrents.php?action=editgroup*
// @match        https://orpheus.network/upload.php*
// @match        https://orpheus.network/torrents.php?action=editgroup*
// @connect      file://*
// @connect      *
// @grant        RegExp
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_log
// ==/UserScript==

// The pattern for built-in copy command or custom Text Tools quick copy command, which is handled by this helper is:
//   $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($if2(%label%,%publisher%),)]$char(30)[$fix_eol($if3(%catalog%,%CATALOGNUMBER%,%catalog #%,%barcode%,%UPC%,%EAN%),)]$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%,%discogs_format%,%source%)]$char(30)[$fix_eol(%genre%,)]['; '$fix_eol(%style%,)]$char(30)[$num(%discnumber%,0)]$char(30)[$num(%totaldiscs%,0)]$char(30)[$fix_eol(%discsubtitle%,)]$char(30)[%track number%]$char(30)[$num(%totaltracks%,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(%composer%,)]$char(30)[$fix_eol(%conductor%,)]$char(30)[$fix_eol(%remixer%,)]$char(30)[$fix_eol(%compiler%,)]$char(30)[$fix_eol($if2(%producer%,%producedby%),)]$char(30)%length_seconds_fp%$char(30)[%replaygain_album_gain%]$char(30)[%album dynamic range%]$char(30)[%__tool%][ | %ENCODER%][ | %ENCODER_OPTIONS%]$char(30)[$fix_eol($if2(%url%,'https://www.discogs.com/release/'%discogs_release_id%),)]$char(30)$directory_path(%path%)$char(30)[$replace($replace(%comment%,$char(13),$char(29)),$char(10),$char(28))]

'use strict';

var prefs = {
  set: function(prop, def) { this[prop] = GM_getValue(prop, def) },
  save: function() {
	for (var iter in this) { if (typeof this[iter] != 'function') 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', 28 * 60); // Max time of EP in s
// 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('tracklist_head_color', '#4682B4');
prefs.set('tracklist_single_color', '#708080');
// classical tracklist only components colouring
prefs.set('tracklist_discsubtitle_color', '#008B8B');
prefs.set('tracklist_classicalblock_color', 'Olive');
prefs.set('tracklist_tracknumber_color', '#8899AA');
prefs.set('tracklist_artist_color', '#889B2F');
prefs.set('tracklist_composer_color', '#556B2F');
prefs.set('tracklist_duration_color', '#4682B4');

var iter, rows = [], ref, tbl, elem, child, tb, warnings;
if (document.URL.search(/\/upload\.php\b/) >= 0) {
  ref = document.querySelector('form#upload_table > div#dynamic_form');
  if (ref == null) return;
  common1();
  let x = [];
  x.push(document.createElement('tr'));
  x[0].style.verticalAlign = 'middle';
  child = document.createElement('input');
  child.id = 'fill-from-text';
  child.value = 'Fill from text (overwrite)';
  child.type = 'button';
  child.style.width = '13em';
  child.addEventListener("click", fill_from_text, false);
  x[0].append(child);
  elem.append(x[0]);
  x.push(document.createElement('tr'));
  x[1].style.verticalAlign = 'middle';
  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.addEventListener("click", fill_from_text, false);
  x[1].append(child);
  elem.append(x[1]);
  common2();
} else if (document.URL.indexOf('/torrents.php?action=editgroup') >= 0) {
  ref = document.querySelector('form.edit_form > div > div > input[type="submit"]');
  if (ref == null) return;
  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.addEventListener("click", fill_from_text, false);
  elem.append(child);
  common2();
}

function common1() {
  tbl = document.createElement('tr');
  elem = document.createElement('td');
  child = document.createElement('textarea');
  child.id = 'import_data';
  child.name = 'import_data';
  child.cols = 50;
  child.rows = 3;
  child.style.width = '610px';
  child.style.height = '3em';
  child.className = ' wbbarea';
  child.setAttribute('data-wbb', '');
  elem.append(child);
  tbl.append(elem);
  elem = document.createElement('td');
  elem.align = 'right';
}
function common2() {
  tbl.append(elem);
  tb = document.createElement('tbody');
  tb.append(tbl);
  tbl = document.createElement('table');
  tbl.id = 'upload assistant';
  tbl.cellPadding = 3;
  tbl.cellSpacing = '';
  tbl.className = 'layout border';
  tbl.border = 0;
  tbl.width = '100%';
  tbl.append(tb);
  ref.parentNode.insertBefore(tbl, ref);
}

// Hide DNU list (warning - risky!)
//if ((ref = document.querySelector('div#content > div:first-of-type')) != null) ref.style.display = 'none';

class TagManager extends Array {
  constructor() {
	super();
	this.substitutions = [
	  [/^Alternative\s*&\s*Indie$/i, 'alternative', 'indie'],
	  [/^Pop and Rock$/i, 'pop', 'rock'],
	  [/^Rock and Pop$/i, 'pop', 'rock'],
	  [/^World\s*&\s*Country$/i, 'world.music', 'country'],
	  [/^Jazz Fusion\s*&\s*Jazz Rock$/i, 'jazz.fusion', 'jazz rock'],
	  [/^(?:Singer\s*&\s*)?Songwriter$/i, 'singer.songwriter'],
	  [/^Singer and Songwriter$/i, 'singer.songwriter'],
	  [/^R\s*&\s*B$/i, 'rhytm.and.blues'],
	  [/^Rock\s*(?:[\-\/]\s*)?Pop$/i, 'pop.rock'],
	  [/^(?:film )?soundtracks?$/i, 'score'],
	  [/^electro$/i, 'electronic'],
	  [/^metal$/i, 'heavy.metal'],
	  [/^nonfiction$/i, 'non.fiction'],
	];
  }

  add(...tags) {
	var added = 0;
	for (var tag of tags) {
	  if (typeof tag != 'string') continue;
	  tag.split(/\s*[\,\/\;\>\|]+\s*/).forEach(function(tag) {
		tag = tag.normalize("NFD").
		  replace(/[\u0300-\u036f]/g, '').
		  replace(/\(.*?\)|\[.*?\]|\{.*?\}/g, '').
		  trim();
		if (tag.length <= 0 || tag == '?') return null;
		for (var k of this.substitutions) {
		  if (k[0].test(tag)) { added += this.add(...k.slice(1)); return; }
		}
		tag = tag.
		  replace(/\s*[\'\’\`][Nn](?:\s+|[\'\’\`]\s*)/, ' and ').
		  replace(/\s*[&\+]\s*/g, ' and ').
		  replace(/[\!\@\#\$\%\^\*\?\<\"\[\{\]\}\=]+/g, '').
		  replace(/[\s\-\_\.\'\`\~]+/g, '.').
		  toLowerCase();
		if (tag.length >= 2 && !this.includes(tag)) {
		  this.push(tag);
		  ++added;
		}
	  }.bind(this));
	}
	return added;
  }
  toString() {
	return this.length > 0 ? this.sort().join(', ') : '';
  }
};

function fill_from_text() {
  var overwrite = this.id != 'fill-from-text-weak';
  var clipBoard = document.getElementById('import_data');
  if (clipBoard == null) return false;
  //let promise = clientInformation.clipboard.readText().then(text => clipBoard = text);
  //if (typeof clipBoard != 'string') return false;
  var category = document.getElementById('categories');
  var ref, iter, i, matches, rx;
  if ((warnings = document.getElementById('UA_warnings')) != null) warnings.parentNode.removeChild(warnings);
  if (category == null && document.getElementById('releasetype') != null
	  || category != null && category.value == 0) return fill_from_text_music();
  if (category != null && category.value == 1) return fill_from_text_apps();
  if (category != null && (category.value == 2 || category.value == 3)) return fill_from_text_books();
  return category == null ? fill_from_text_apps() || fill_from_text_books() : false;

  function fill_from_text_music() {
	const div = ['—', '⸺', '⸻'];
	var lines = clipBoard.value.split(/[\r\n]+/);
	var track, tracks = [];

	for (iter of lines) {
	  let metaData = iter.split('\x1E');
	  track = {
		artist: metaData.shift().trim() || null,
		album: metaData.shift().trim() || null,
		album_year: extract_year(metaData.shift().trim()),
		release_year: extract_year(metaData.shift().trim()),
		label: metaData.shift().trim() || null,
		catalog: metaData.shift().trim() || null,
		encoding: metaData.shift().trim() || null,
		codec: metaData.shift().trim() || null,
		codec_profile: metaData.shift().trim() || null,
		bitrate: parseFloat(metaData.shift().trim()) || null,
		bd: parseInt(metaData.shift().trim()) || null,
		sr: parseInt(metaData.shift().trim()) || null,
		channels: parseInt(metaData.shift().trim()) || null,
		media: metaData.shift().trim() || null,
		genre: metaData.shift().trim() || null,
		discnumber: parseInt(metaData.shift().trim()) || null,
		totaldiscs: parseInt(metaData.shift().trim()) || null,
		discsubtitle: metaData.shift().trim() || null,
		tracknumber: metaData.shift().trim() || null,
		totaltracks: parseInt(metaData.shift().trim()) || null,
		title: metaData.shift().trim() || null,
		track_artist: metaData.shift().trim() || null,
		performer: metaData.shift().trim() || null,
		composer: metaData.shift().trim() || null,
		conductor: metaData.shift().trim() || null,
		remixer: metaData.shift().trim() || null,
		compiler: metaData.shift().trim() || null,
		producer: metaData.shift().trim() || null,
		duration: parseFloat(metaData.shift().trim()) || null,
		rg: metaData.shift().trim() || null,
		dr: metaData.shift().trim() || null,
		vendor: metaData.shift().trim() || null,
		url: metaData.shift().trim() || null,
		dirpath: metaData.shift() || null,
		comment: metaData.shift().trim() || null,
	  };
	  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
	  }
	  if (track.dr != null) track.dr = parseInt(track.dr); // DR0
	  tracks.push(track);
	}
	var album_artists = [], albums = [], album_years = [], release_years = [], labels = [], catalogs = [];
	var codecs = [], bds = [], medias = [], genres = [], srs = {}, urls = [], comments = [], track_artists = [];
	var encodings = [], bitrates = [], codec_profiles = [], drs = [], channels = [], rgs = [], dirpaths = [];
	var vendors = [];
	let is_va = false, composer_significant = false, is_from_dsd = false, is_classical = false;
	var total_time = 0, release_type = 1, album_bitrate = 0, totaldiscs = 1, artist_counter = 0;
	var edition_title, media, yadg_prefil = '';
	const featParser1 = /\(feat(?:\.|uring)\s+([^\(\)]+?)\s*\)$/i;
	const featParser2 = /\[feat(?:\.|uring)\s+([^\[\]]+?)\s*\]$/i;
	for (iter of tracks) {
	  push_unique(album_artists, 'artist');
	  push_unique(track_artists, 'track_artist');
	  push_unique(albums, 'album');
	  push_unique(album_years, 'album_year');
	  push_unique(release_years, 'release_year');
	  push_unique(labels, 'label');
	  push_unique(catalogs, 'catalog');
	  push_unique(encodings, 'encoding');
	  push_unique(codecs, 'codec');
	  push_unique(codec_profiles, 'codec_profile');
	  push_unique(bitrates, 'bitrate');
	  push_unique(bds, 'bd');
	  push_unique(channels, 'channels');
	  push_unique(medias, 'media');
	  if (iter.sr) {
		if (typeof srs[iter.sr] != 'number') {
		  srs[iter.sr] = iter.duration;
		} else {
		  srs[iter.sr] += iter.duration;
		}
	  }
	  push_unique(genres, 'genre');
	  push_unique(urls, 'url');
	  push_unique(comments, 'comment');
	  push_unique(rgs, 'rg');
	  push_unique(drs, 'dr');
	  push_unique(vendors, 'vendor');
	  push_unique(dirpaths, 'dirpath');

	  if (iter.discnumber > totaldiscs) totaldiscs = iter.discnumber;
	  total_time += iter.duration;
	  album_bitrate += iter.duration * iter.bitrate;
	}
	function push_unique(array, prop) {
	  if (iter[prop] !== undefined && iter[prop] !== null && (typeof iter[prop] != 'string' || iter[prop].length > 0)
		  && !array.includes(iter[prop])) array.push(iter[prop]);
	}
	// inconsistent releases not allowed - die
	if (encodings.length > 1) { addWarning('FATAL: fuzzy releases aren\'t allowed (encoding): ' + encodings); return false; }
	if (codecs.length > 1) { addWarning('FATAL: fuzzy releases aren\'t allowed (codec): ' + codecs); return false; }
	if (codec_profiles.length > 1) { addWarning('FATAL: fuzzy releases aren\'t allowed (codec profile): ' + codec_profiles); return false; }
	if (vendors.length > 1) { addWarning('FATAL: fuzzy releases aren\'t allowed (vendor): ' + vendors); return false; }
	if (medias.length > 1) { addWarning('FATAL: fuzzy releases aren\'t allowed (media): ' + medias); return false; }
	if (channels.length > 1) { addWarning('FATAL: fuzzy releases aren\'t allowed (channel): ' + channels); return false; }
	if (album_artists.length > 1) { addWarning('FATAL: fuzzy releases aren\'t allowed (album artists): ' + album_artists); return false; }
	if (albums.length > 1) { addWarning('FATAL: fuzzy releases aren\'t allowed (album): ' + albums); return false; }
	var tags = new TagManager();
	album_bitrate /= total_time;
	if (total_time <= prefs.single_threshold) {
	  release_type = 9; // single
	} else if (total_time <= prefs.EP_threshold) {
	  release_type = 5; // EP
	}
	if (album_artists.length == 1 && (ref = document.getElementById('artist')) != null) {
	  const artist_parser = /\s*(?:[\,\;\/\|]|(?:&)\s+(?!(?:The|His|Friends)\b))+\s*/i;
	  const weak_artist_parser = /\s*[\,\;\/\|]+\s*/;
	  const guest_parser = /^(.*?)(?:\s+(?:feat(?:\.|uring)|with)\s+(.*))?$/;
	  const invalid_artist = /^#?N\/?A$/i;
	  let main_artists = [], guests = [], composers = [], conductors = [];
	  var remixers = [], performers = [], compilers = [], producers = [];
	  function notInGuestSMain(k) { return !main_artists.includesCaseless(k) && !guests.includesCaseless(k) }
	  function twoOrMore(k) { return k.length >= 2 };
	  if (matches = album_artists[0].match(guest_parser)) {
		let j;
		if (matches[1].search(/^(?:Various(?: Artists?)?|VA)$/) >= 0) {
		  is_va = true;
		} else {
		  j = matches[1].split(artist_parser);
		  main_artists = j.every(twoOrMore) ? j : [ matches[0] ];
		  yadg_prefil = matches[1];
		}
		if (!is_va && matches[2]) {
		  guests = matches[2].split(weak_artist_parser);
		  if (!guests.every(twoOrMore)) guests = matches[2];
		}
		for (iter of tracks) {
		  add_track_artists('track_artist');
		  add_track_artists('performer');
		  add_other_artists(remixers, 'remixer');
		  add_other_artists(composers, 'composer');
		  add_other_artists(conductors, 'conductor');
		  add_other_artists(compilers, 'compiler');
		  add_other_artists(producers, 'producer');

		  if (iter.title
			  && ((matches = iter.title.match(/\(remix(?:ed)? by ([^\(\)]+)\)/i))
				  || (matches = iter.title.match(/\(([^\(\)]+?)(?:[\'\’\`]s)? remix\)/i))
				  || (matches = iter.title.match(/\[remix(?:ed)? by ([^\[\]]+)\]/i))
				  || (matches = iter.title.match(/\[([^\[\]]+?)(?:[\'\’\`]s)? remix\]/i)))) {
			j = matches[1].split(weak_artist_parser);
			for (i of j.every(twoOrMore) ? j : [ matches[0] ]) { if (!remixers.includesCaseless(i)) remixers.push(i); }
		  }
		  if (iter.title && ((matches = iter.title.match(featParser1)) || (matches = iter.title.match(featParser2)))) {
			j = matches[1].split(weak_artist_parser);
			(j.every(twoOrMore) ? j : [ matches[1] ]).forEach(k => { if (!notInGuestSMain(k)) guests.push(k) });
			addWarning('Warning: featured artist(s) in track name (#' + iter.tracknumber + ': ' +
				iter.title + ')', false, '#C00000');
		  }
		} // iterate tracks
		// split ampersands
		for (i = main_artists.length; i > 0; --i) {
		  j = main_artists[i - 1].split(' & ');
		  if (j.length >= 2 && j.every(twoOrMore) && !j.every(notInGuestSMain)) {
			main_artists.splice(i - 1, 1, ...j.filter(k => !main_artists.includesCaseless(k)));
		  }
		}
		for (i = guests.length; i > 0; --i) {
		  j = guests[i - 1].split(' & ');
		  if (j.length >= 2 && j.every(twoOrMore) && !j.every(notInGuestSMain)) {
			guests.splice(i - 1, 1, ...j.filter(notInGuestSMain));
		  }
		}
		function add_track_artists(prop) {
		  if (iter[prop] && (matches = iter[prop].match(guest_parser))) {
			j = matches[1].split(weak_artist_parser);
			for (i of j.every(twoOrMore) ? j : [ matches[1] ]) {
			  i = i.replace(/\s+aka\s+.*/, '');
			  if (!invalid_artist.test(i) && !main_artists.includesCaseless(i)
				  && (is_va || !guests.includesCaseless(i))) {
				(is_va ? main_artists : guests).push(i);
			  }
			}
			if (matches[2]) {
			  j = matches[2].split(weak_artist_parser);
			  for (i of j.every(twoOrMore) ? j : [ matches[2] ]) {
				i = i.replace(/\s+aka\s+.*/, '');
				if (!invalid_artist.test(i) && notInGuestSMain(i)) guests.push(i);
			  }
			}
		  }
		}
		function add_other_artists(list, prop) {
		  if (!iter[prop]) return;
		  j = iter[prop].split(weak_artist_parser);
		  for (i of j.every(twoOrMore) ? j : [ iter[prop] ]) {
			i = i.replace(/\s+aka\s+.*/, '');
			if (!invalid_artist.test(i) && !list.includesCaseless(i)) list.push(i)
		  }
		}
		if (!ref.disabled) {
		  let artist_index = 0;
		  feed_artist_category(main_artists.filter(k => !conductors.includesCaseless(k)), 1);
		  feed_artist_category(guests.filter(k => !main_artists.includesCaseless(k) && !conductors.includesCaseless(k)), 2);
		  feed_artist_category(remixers, 3);
		  feed_artist_category(composers, 4);
		  feed_artist_category(conductors, 5);
		  feed_artist_category(compilers, 6);
		  feed_artist_category(producers, 7);

		  function feed_artist_category(list, type) {
			for (iter of list.sort()) {
			  let id = 'artist';
			  if (artist_index > 0) {
				id += '_' + artist_index;
				if (document.getElementById(id) == null) add_artist();
			  }
			  ref = document.getElementById(id);
			  if (ref != null && (overwrite || !ref.value)) {
				ref.value = iter;
				ref.nextElementSibling.value = type;
			  }
			  ++artist_index;
			}
		  }
		}
	  }
	}
	if (is_va && release_type == 1) release_type = 7; // compilation
	if (albums.length == 1) {
	  let album = albums[0];
	  rx = /\s+(?:-\s+Single|\[Single\]|\(Single\))$/i;
	  if (rx.test(album)) {
		album = album.replace(rx, '');
		release_type = 9; // single
	  }
	  rx = /\s+(?:(?:-\s+)?EP|\[EP\]|\(EP\))$/;
	  if (rx.test(album)) {
		album = album.replace(rx, '')
		release_type = 5; // EP
	  }
	  rx = /\s+\((?:Live|En directo?|Ao Vivo)\b[^\(\)]*\)$/i;
	  if (rx.test(album)) {
		//album = album.replace(rx, '')
		if (release_type == 1 || release_type == 7) release_type = 11; // live album
	  }
	  rx = /\s+\[(?:Live|En directo?|Ao Vivo)\b[^\[\]]*\]$/i;
	  if (rx.test(album)) {
		//album = album.replace(rx, '')
		if (release_type == 1 || release_type == 7) release_type = 11; // live album
	  }
	  if (album.search(/(?:^Live [aA]t\b|^Directo? [Ee]n\b|\bUnplugged\b|\bAcoustic Stage\b|\s+Live$)/) >= 0
		  && (release_type == 1 || release_type == 7)) release_type = 11; // live album
	  rx = /\b(?:Best [Oo]f|Greatest Hits)\b/;
	  if (rx.test(album) && release_type == 1) release_type = 6; // Anthology
	  rx = '\\b(?:Soundtrack|Score|Motion Picture|Series|Television|Original(?: \w+)? Cast|Music from|Musique originale|Bande originale)\\b';
	  if (reInParenthesis(rx).test(album) || reInBrackets(rx).test(album)) {
		//album = album.replace(rx, '')
		release_type = 3; // soundtrack
		tags.add('score');
		composer_significant = true;
	  }
	  rx = /\s+(?:\([^\(\)]*\bRemix(?:e[ds])?\b[^\(\)]*\)|Remix(?:e[ds])?)$/i;
	  if (rx.test(album)) {
		//album = album.replace(rx, '')
		if (release_type == 1) release_type = 13; // remix
	  }
	  rx = /\s+\[[^\[\]]*\bRemix(?:e[ds])?\b[^\[\]]*\]$/i;
	  if (rx.test(album)) {
		//album = album.replace(rx, '')
		if (release_type == 1) release_type = 13; // remix
	  }
	  rx = /\s+\(([^\(\)]*\b(?:Remaster(?:ed)?\b[^\(\)]*|Reissue|Edition|Version))\)$/i;
	  if (matches = rx.exec(album)) {
		album = album.replace(rx, '');
		edition_title = matches[1];
	  }
	  rx = /\s+\[([^\[\]]*\b(?:Remaster(?:ed)?\b[^\[\]]*|Reissue|Edition|Version))\]$/i;
	  if (matches = rx.exec(album)) {
		album = album.replace(rx, '');
		edition_title = matches[1];
	  }
	  rx = /\s+-\s+([^\[\]\(\)\-]*\b(?:(?:Remaster(?:ed)?|Bonus Track)\b[^\[\]\(\)\-]*|Reissue|Edition|Version))$/i;
	  if (matches = rx.exec(album)) {
		album = album.replace(rx, '');
		edition_title = matches[1];
	  }
	  if (featParser1.test(album)) album = album.replace(featParser1, '');
	  if (featParser2.test(album)) album = album.replace(featParser1, '');
	  rx = /\s+(?:\[LP\]|\(LP\))$/;
	  if (matches = rx.exec(album)) { album = album.replace(rx, ''); media = 'Vinyl'; }
	  rx = /\s+(?:\[SACD\]|\(SACD\))$/;
	  if (matches = rx.exec(album)) { album = album.replace(rx, ''); media = 'SACD'; }
	  rx = /\s+(?:\[(?:Blu[\s\-]?Ray|B[DR])\]|\((?:Blu[\s\-]?Ray|B[DR])\))$/;
	  if (matches = rx.exec(album)) { album = album.replace(rx, ''); media = 'Blu-Ray'; }
	  rx = /\s+(?:\[DVD(?:-?A)?\]|\(DVD(?:-?A)?\))$/;
	  if (matches = rx.exec(album)) { album = album.replace(rx, ''); media = 'DVD'; }
	  if (element_writable(ref = document.getElementById('title'))) ref.value = album;
	  if (yadg_prefil) { yadg_prefil += ' - ' }
	  yadg_prefil += album;
	}
	if (yadg_prefil && (ref = document.getElementById('yadg_input')) != null) {
	  ref.value = yadg_prefil;
	  ref = document.getElementById('yadg_submit');
	  if (ref != null && !ref.disabled) ref.click();
	}
	if (album_years.length == 1) {
	  if (element_writable(ref = document.getElementById('year'))) ref.value = album_years[0];
	} else if (album_years.length > 1) {
	  addWarning('Warning: inconsistent album year accross album: ' + album_years, false, '#C00000');
	}
	if (release_years.length == 1) {
	  if (element_writable(ref = document.getElementById('remaster_year'))) ref.value = release_years[0];
	} else if (release_years.length > 1) {
	  addWarning('Warning: inconsistent release year accross album: ' + release_years, false, '#C00000');
	}
	if (edition_title) {
	  if (element_writable(ref = document.getElementById('remaster_title'))) ref.value = edition_title;
	}
	rx = /\s*[\,\;]\s*/g;
	if (labels.length == 1 && element_writable(ref = document.getElementById('remaster_record_label'))) {
	  ref.value = labels[0].replace(rx, ' / ');
	} else if (labels.length > 1) {
	  addWarning('Warning: inconsistent label accross album: ' + labels, false, '#C00000');
	}
	if (catalogs.length > 0 && element_writable(ref = document.getElementById('remaster_catalogue_number'))) {
	  ref.value = catalogs.map(k => k.replace(rx, ' / ')).join(' / ');
	}
	var br_isSet = (ref = document.getElementById('bitrate')) != null && ref.value;
	if (codecs.length > 0 && element_writable(ref = document.getElementById('format'))) {
	  ref.value = codecs[0];
	  exec(function() { Format() });
	}
	let sel;
	if (encodings[0] == 'lossless') {
	  if (!bds.every(k => [16, 24].includes(k))) {
		sel = null; // album containing disallowed bit depth
	  } else {
		sel = bds.includes(24) ? '24bit Lossless' : 'Lossless';
	  }
	} else if (bitrates.length > 0) {
	  let lame_version = vendors.length > 0 && (matches = vendors[0].match(/^LAME(\d+)\.(\d+)/i)) ?
		  parseInt(matches[1]) * 1000 + parseInt(matches[2]) : undefined;
	  if (codec_profiles.length == 1 && codec_profiles[0] == 'VBR V0') {
		sel = lame_version >= 3094 ? 'V0 (VBR)' : 'APX (VBR)'
	  } else if (codec_profiles.length == 1 && codec_profiles[0] == 'VBR V1') {
		sel = 'V1 (VBR)'
	  } else if (codec_profiles.length == 1 && codec_profiles[0] == 'VBR V2') {
		sel = lame_version >= 3094 ? sel = 'V2 (VBR)' : 'APS (VBR)'
	  } else if (bitrates.length == 1 && [192, 256, 320].includes(Math.round(bitrates[0]))) {
		sel = Math.round(bitrates[0]);
	  } else if (bitrates.length >= 1) {
		if (element_writable(ref = document.getElementById('bitrate')) && ref.value != 'Other') {
		  ref.value = 'Other';
		  exec(function() { Bitrate() });
		}
		if (element_writable(ref = document.getElementById('other_bitrate'))) {
		  ref.value = Math.round(bitrates.length == 1 ? bitrates[0] : album_bitrate);
		  if ((ref = document.getElementById('vbr')) != null) ref.checked = bitrates.length > 1;
		}
	  }
	}
	if (sel && (ref = document.getElementById('bitrate')) != null && !elem.disabled && (overwrite || !br_isSet)) {
	  ref.value = sel;
	}
	if (medias.length > 0) {
	  sel = undefined;
	  if (medias[0].search(/\b(?:WEB|File|Digital Download)\b/i) >= 0) sel = 'WEB';
	  if (medias[0].search(/\bCD\b/) >= 0) sel = 'CD';
	  if (medias[0].search(/\b(?:SACD|Hybrid)\b/) >= 0) sel = 'SACD';
	  if (medias[0].search(/\bBlu[-\s]?Ray\b/i) >= 0) sel = 'Blu-Ray';
	  if (medias[0].search(/\bDVD(?:-?A)?\b/) >= 0) sel = 'DVD';
	  if (medias[0].search(/\b(?:Vinyl\b|LP\b|12"|7")/) >= 0) sel = 'Vinyl';
	  if (sel) media = sel;
	  if (media && element_writable(ref = document.getElementById('media'))) ref.value = sel;
	}
	if (genres.length >= 1) {
	  genres.forEach(function(genre) {
		if (/\b(?:Classical|Symphony|Symphonic(?:al)?$|Chamber|Choral|Opera|Klassik|Duets)\b/i.test(genre)) {
		  composer_significant = true;
		  is_classical = true
		}
		if (/\b(?:Jazz|Vocal)\b/i.test(genre)) composer_significant = true;
		if (/\b(?:Soundtracks?|Score|Films?|Games?|Video|Series?|Theatre|Musical)\b/i.test(genre)) {
		  composer_significant = true;
		  if (release_type == 1) release_type = 3;
		}
	  	tags.add(genre);
	  });
	  if (genres.length > 1) addWarning('Warning: inconsistent genre accross album: ' + genres, false, '#C00000');
	}
	if (tags.length > 0 && element_writable(ref = document.getElementById('tags'))) ref.value = tags.toString();
	if (element_writable(ref = document.getElementById('releasetype'))) ref.value = release_type;

	if (!composer_significant && !prefs.keep_meaningles_composers) {
	  iter = document.querySelectorAll('input[name="artists[]"]');
	  if (iter != null) {
		iter.forEach(function(i) {
			if (['4', '5'].includes(i.nextElementSibling.value)) i.value = null;
	  	});
	  }
	}

	var description, ripinfo, dur, vinyl_test = /^(Vinyl rip by\s+)(.*)$/im;
	if (tracks.length > 1) {
	  gen_full_tracklist();
	} else { // single
	  description = '[align=center]';
	  description += isRED() ? '[pad=20|20|20|20]' : '';
	  description += '[size=4][b][color=' + prefs.tracklist_artist_color + ']' + album_artists[0] + '[/color][hr]';
	  //description += '[color=' + prefs.tracklist_single_color + ']';
	  description += tracks[0].title;
	  //description += '[/color]'
	  description += '[/b]';
	  if (tracks[0].composer) {
		description += '\n[i][color=' + prefs.tracklist_composer_color + '](' + tracks[0].composer + ')[/color][/i]';
	  }
	  description += '\n\n[color=' + prefs.tracklist_duration_color +'][' +
		make_time_string(tracks[0].duration) + '][/color][/size]';
	  if (isRED()) description += '[/pad]';
	  description += '[/align]';
	}

	if (comments.length == 1 && comments[0]) {
	  let cmt = comments[0];
	  if (matches = cmt.match(vinyl_test)) {
		ripinfo = cmt.slice(matches.index).trim().split(/[\r\n]+/);
		description = description.concat('\n\n', cmt.slice(0, matches.index).trim());
	  } else {
		description = description.concat('\n\n', cmt);
	  }
	}
	if (element_writable(ref = document.getElementById('album_desc'))) ref.value = description;
	if ((ref = document.getElementById('body')) != null && !ref.disabled) {
	  let editioninfo;
	  if (edition_title) {
		editioninfo = '[size=5][b]' + edition_title;
		if (release_years.length >= 1) { editioninfo = editioninfo.concat(' (', release_years[0] + ')') }
		editioninfo = editioninfo.concat('[/b][/size]\n\n');
	  } else { editioninfo = '' }
	  if (ref.textLength > 0) {
		ref.value = ref.value.concat('\n\n', editioninfo, description);
	  } else {
		ref.value = editioninfo + description;
	  }
	}
	let lineage = '', comment = '', drinfo, srcinfo;
	if (Object.keys(srs).length > 0) {
	  let kHz = Object.keys(srs).sort((a, b) => srs[b] - srs[a]).map(f => f / 1000).join('/').concat('kHz');
	  if (element_writable(ref = document.getElementById('release_samplerate'))) {
		ref.value = Object.keys(srs).length > 1 ? '999' : Math.floor(Object.keys(srs)[0] / 1000);
	  }
	  if (bds.includes(24)) {
		if (drs.length >= 1) drinfo = '[hide=DR' + (drs.length == 1 ? drs[0] : '') + '][pre][/pre]';
		if (media == 'Vinyl') {
		  let hassr = ref == null || Object.keys(srs).length > 1;
		  lineage = hassr ? kHz + ' ' : '';
		  if (ripinfo) {
			ripinfo[0] = ripinfo[0].replace(vinyl_test, '$1[color=blue]$2[/color]');
			if (hassr) { ripinfo[0] = ripinfo[0].replace(/^Vinyl\b/, 'vinyl') }
			lineage += ripinfo[0] + '\n\n[u]Lineage:[/u]' + ripinfo.slice(1).map(k => '\n' + k).join('');
		  } else {
			lineage += (hassr ? 'Vinyl' : ' vinyl') + ' rip by [color=blue][/color]\n\n[u]Lineage:[/u]';
		  }
		  if (drs.length >= 1) drinfo += '\n\n[img][/img]\n[img][/img]\n[img][/img][/hide]';
		} else if (['Blu-Ray', 'DVD', 'SACD'].includes(media)) {
		  lineage = ref ? '' : kHz;
		  if (channels.length == 1) add_channel_info();
		  if (media == 'SACD' || is_from_dsd) {
			lineage += ' from DSD64 using foobar2000\'s SACD decoder (direct-fp64)';
			lineage += '\nOutput gain +0dB';
		  }
		  drinfo += '[/hide]';
		  //add_rg_info();
		} else { // WEB Hi-Res
		  if (ref == null || Object.keys(srs).length > 1) lineage = kHz;
		  if (channels.length == 1 && channels[0] != 2) add_channel_info();
		  add_dr_info();
		  //if (lineage.length > 0) add_rg_info();
		  if (bds.length >= 2) {
			let hybrid_tracks = tracks.filter(k => k.bd < 24).map(k => k.tracknumber);
			if (hybrid_tracks) {
			  if (lineage) lineage += '\n';
			  lineage += 'Note: track';
			  if (hybrid_tracks.length > 1) lineage += 's';
			  lineage += ' #' + hybrid_tracks.sort().join(', ') +
				(hybrid_tracks.length > 1 ? ' are' : ' is') + ' 16bit lossless';
			}
		  }
		  drinfo = Object.keys(srs).includes(88200) ? drinfo.concat('[/hide]') : null;
		}
	  } else { // 16bit or lossy
		if (Object.keys(srs).some(f => f != 44100)) lineage = kHz;
		if (channels.length == 1 && channels[0] != 2) add_channel_info();
		//add_dr_info();
		//if (lineage.length > 0) add_rg_info();
		if (['AAC', 'Opus', 'Vorbis'].includes(codecs[0]) && vendors[0]) {
		  let _encoder_settings = vendors[0];
		  if (codecs[0] == 'AAC' && vendors[0].search(/^qaac\s+[\d\.]+/i) >= 0) {
			let enc = [];
			if (matches = vendors[0].match(/\bqaac\s+([\d\.]+)\b/i)) enc[0] = matches[1];
			if (matches = vendors[0].match(/\bCoreAudioToolbox\s+([\d\.]+)\b/i)) enc[1] = matches[1];
			if (matches = vendors[0].match(/\b(AAC-\S+)\s+Encoder\b/i)) enc[2] = matches[1];
			if (matches = vendors[0].match(/\b([TC]VBR|ABR|CBR)\s+(\S+)\b/)) { enc[3] = matches[1]; enc[4] = matches[2]; }
			if (matches = vendors[0].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 (drs.length != 1 || document.getElementById('release_dynamicrange') != null) return false;
	  if (lineage.length > 0) lineage += ' | ';
	  if (drs[0] < 4) lineage += '[color=red]';
	  lineage += 'DR' + drs[0];
	  if (drs[0] < 4) lineage += '[/color]';
	  return true;
	}
	function add_rg_info() {
	  if (rgs.length != 1) return false;
	  if (lineage.length > 0) lineage += ' | ';
	  lineage += 'RG'; //lineage += 'RG ' + rgs[0];
	  return true;
	}
	function add_channel_info() {
	  if (channels.length != 1) return false;
	  let chi = getChanString(channels[0]);
	  if (lineage.length > 0 && chi.length > 0) lineage += ', ';
	  lineage += chi;
	  return chi.length > 0;
	}
	if (urls.length == 1 && urls[0]) {
	  srcinfo = '[url]' + urls[0] + '[/url]';
	  if (element_writable(document.getElementById('image'))) {
		let u = urls[0];
		if (u.search(/^https?:\/\/(\w+\.)?discogs\.com\/release\/[\w\-]+\/?$/i) >= 0) u += '/images';
	  	GM_xmlhttpRequest({ method: 'GET', url: u, onload: fetch_image_from_store });
	  }
// 	} else if (element_writable(document.getElementById('image'))
// 	  && ((ref = document.getElementById('album_desc')) != null || (ref = document.getElementById('body')) != null)
// 		&& ref.textLength > 0 && (matches = ref.value.matchAll(/\b(https?\/\/[\w\-\&\_\?\=]+)/i)) != null) {
	}
	ref = document.getElementById('release_lineage');
	if (ref != null) {
	  if (element_writable(ref)) {
		if (drinfo) comment = drinfo;
		if (lineage && srcinfo) lineage += '\n\n';
		if (srcinfo) lineage += srcinfo;
		ref.value = lineage;
	  }
	} else {
	  comment = lineage;
	  if (comment && drinfo) comment += '\n\n';
	  if (drinfo) comment += drinfo;
	  if (comment && srcinfo) comment += '\n\n';
	  if (srcinfo) comment += srcinfo;
	}
	if (comment.length > 0) {
	  if (element_writable(ref = document.getElementById('release_desc'))) ref.value = comment;
	}
	if (encodings[0] == 'lossless' && codecs[0] == 'FLAC' && bds.includes(24) && dirpaths.length == 1) {
	  var uri = new URL(dirpaths[0] + '\\foo_dr.txt');
	  GM_xmlhttpRequest({
		method: 'GET',
		url: uri.href,
		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 (drs.length == 1) {
	  if (element_writable(ref = document.getElementById('release_dynamicrange'))) ref.value = drs[0];
	}
	if (prefs.clean_on_apply) document.getElementById('import_data').value = null;
	prefs.save();
	return true;

	function gen_full_tracklist() { // ========================= TACKLIST =========================
	  description = isRED() ? '[pad=5|0|0|0]' : '';
	  description += '[size=4][color=' + prefs.tracklist_head_color + '][b]Tracklisting[/b][/color][/size]';
	  if (isRED()) '[/pad]';
	  let classical_units = new Set();
	  if (is_classical) {
		for (track of tracks) {
		  if (matches = track.title.match(/^(.+?)\s*:\s+(.*)$/)) {
			classical_units.add(track.classical_unit_title = matches[1]);
			track.classical_title = matches[2];
		  } else {
			track.classical_unit_title = null;
		  }
		}
		for (let unit of classical_units.keys()) {
		  let group_performer = array_homogenous(tracks.filter(k => k.classical_unit_title === unit).map(k => k.track_artist));
		  let group_composer = array_homogenous(tracks.filter(k => k.classical_unit_title === unit).map(k => k.composer));
		  for (track of tracks) {
			if (track.classical_unit_title !== unit) continue;
			if (group_composer) track.classical_unit_composer = track.composer;
			if (group_performer) track.classical_unit_performer = track.track_artist;
		  }
		}
	  }
	  let block = 1, lastdisc, lastsubtitle, lastside, vinyl_trackwidth;
	  let lastwork = classical_units.size > 0 ? null : undefined;
	  description += '\n';
	  let volumes = new Map(tracks.map(k => [k.discnumber, undefined]));
	  volumes.forEach(function(val, key) {
		volumes.set(key, array_homogenous(tracks.filter(k => k.discnumber == key).map(k => k.discsubtitle)));
	  });
	  if (media == 'Vinyl') {
		let max_side_track = undefined;
		rx = /^([A-Z])(\d+)?(\.(\d+))?/i;
		for (iter of tracks) {
		  if (matches = iter.tracknumber.match(rx)) {
			max_side_track = Math.max(parseInt(matches[2]) || 1, max_side_track || 0);
		  }
		}
		if (typeof max_side_track == 'number') {
		  max_side_track = max_side_track.toString().length;
		  vinyl_trackwidth = 1 + max_side_track;
		  for (iter of tracks) {
			if (matches = iter.tracknumber.match(rx)) {
			  iter.tracknumber = matches[1].toUpperCase();
			  if (matches[2]) iter.tracknumber += matches[2].padStart(max_side_track, '0');
			}
		  }
		}
	  }
	  function prologue(prefix, postfix) {
		function block1() {
		  if (block == 3) description += postfix;
		  description += '\n';
		  block = 1;
		}
		function block2() {
		  if (block == 3) description += postfix;
		  description += '\n';
		  block = 2;
		}
		function block3() {
		  if (block == 2) { description += '[hr]' } else { description += '\n' }
		  if (block != 3) description += prefix;
		  block = 3;
		}
		if (totaldiscs > 1 && iter.discnumber != lastdisc) {
		  block1();
		  description += '[size=3][color=' + prefs.tracklist_discsubtitle_color + '][b]Disc ' + iter.discnumber;
		  if (iter.discsubtitle && (!volumes.has(iter.discnumber) || volumes.get(iter.discnumber))) {
			description += ' - ' + iter.discsubtitle;
			lastsubtitle = iter.discsubtitle;
		  }
		  description += '[/b][/color][/size]';
		  lastdisc = iter.discnumber;
		}
		if (iter.discsubtitle != lastsubtitle) {
		  block1();
		  if (iter.discsubtitle) {
			description += '[size=2][color=' + prefs.tracklist_discsubtitle_color + '][b]' +
			  iter.discsubtitle + '[/b][/color][/size]';
		  }
		  lastsubtitle = iter.discsubtitle;
		}
		if (iter.classical_unit_title !== lastwork) {
		  if (iter.classical_unit_composer || iter.classical_unit_title || iter.classical_unit_performer) {
			block2();
			description += '[size=2][color=' + prefs.tracklist_classicalblock_color + '][b]';
			if (iter.classical_unit_composer) description += iter.classical_unit_composer + ': ';
			if (iter.classical_unit_title) description += iter.classical_unit_title;
			description += '[/b]';
			if (iter.classical_unit_performer) description += ' (' + iter.classical_unit_performer + ')';
			description += '[/color][/size]';
		  } else {
			if (block != 2) block1();
		  }
		  lastwork = iter.classical_unit_title;
		}
		block3();
		if (media == 'Vinyl') {
		  let c = iter.tracknumber[0].toUpperCase();
		  if (lastside != undefined && c != lastside) description += '\n';
		  lastside = c;
		}
	  }
	  for (iter of tracks.sort(function(a, b) {
		var d = a.discnumber - b.discnumber;
		var t = a.tracknumber - b.tracknumber;
		return isNaN(d) || d == 0 ? isNaN(t) ? a.tracknumber.localeCompare(b.tracknumber) : t : d;
	  })) {
		let title = '';
		let ttwidth = vinyl_trackwidth || Math.max((iter.totaltracks || tracks.length).toString().length, 2);
		if (prefs.tracklist_style == 1) {
		  // STYLE 1 ----------------------------------------
		  prologue('[size=2]', '[/size]\n');
		  track = '[b][color=' + prefs.tracklist_tracknumber_color + ']';
		  track += isNaN(parseInt(iter.tracknumber)) ? iter.tracknumber : iter.tracknumber.padStart(ttwidth, '0');
		  track += '[/color][/b]' + prefs.title_separator;
		  if (iter.track_artist && !iter.classical_unit_performer) {
			title = '[color=' + prefs.tracklist_artist_color + ']' + iter.track_artist + '[/color] - ';
		  }
		  title += iter.classical_title || iter.title;
		  if (iter.composer && composer_significant && !iter.classical_unit_composer) {
			title = title.concat(' [color=', prefs.tracklist_composer_color, '](', iter.composer, ')[/color]');
		  }
		  description += track + title + ' [i][color=' + prefs.tracklist_duration_color +'][' +
			make_time_string(iter.duration) + '][/color][/i]';
		} else if (prefs.tracklist_style == 2) {
		  // STYLE 2 ----------------------------------------
		  prologue('[size=2][pre]', '[/pre][/size]');
		  track = isNaN(parseInt(iter.tracknumber)) ? iter.tracknumber : iter.tracknumber.padStart(ttwidth, '0');
		  track += prefs.title_separator;
		  if (iter.track_artist && !iter.classical_unit_performer) title = iter.track_artist + ' - ';
		  title += iter.classical_title || iter.title;
		  if (iter.composer && composer_significant && !iter.classical_unit_composer) {
			title = title.concat(' (', iter.composer, ')');
		  }
		  dur = '[' + make_time_string(iter.duration) + ']';
		  let l = 0, width = prefs.max_tracklist_width - track.length - dur.length - 1;
		  while (title.length > 0) {
			let j = width;
			if (title.length > width) {
			  while (j > 0 && title[j] != ' ') { --j }
			  if (j <= 0) { j = width }
			}
			let left = title.slice(0, j).trim();
			if (++l <= 1) {
			  description = description.concat(track, left.padEnd(width, ' '), ' ', dur);
			  width = prefs.max_tracklist_width - track.length - 2;
			} else {
			  description = description.concat('\n', ' '.repeat(track.length), left);
			}
			title = title.slice(j).trim();
		  }
		}
	  }
	  if (prefs.tracklist_style == 1) {
		description += '\n\n' + div[0].repeat(10) + '\n[color=' + prefs.tracklist_duration_color +
		  ']Total time: [i]' + make_time_string(total_time) + '[/i][/color][/size]';
	  } else if (prefs.tracklist_style == 2) {
		dur = '[' + make_time_string(total_time) + ']';
		description = description.concat('\n\n', div[0].repeat(32).padStart(prefs.max_tracklist_width));
		description = description.concat('\n', 'Total time:'.padEnd(prefs.max_tracklist_width - dur.length), dur);
		description = description.concat('[/pre][/size]');
	  }
	}

	function getChanString(n) {
	  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 fetch_image_from_store(response) {
	  if (response.readyState != 4 || !response.responseText) return;
	  ref = document.getElementById('image');
	  var parser = new DOMParser();
	  var html = parser.parseFromString(response.responseText, "text/html");
	  if (response.finalUrl.toLowerCase().indexOf('qobuz.com') >= 0
		  && (ref = html.querySelector('div.album-cover > img')) != null) set_image(ref.src);
	  if (response.finalUrl.toLowerCase().indexOf('7digital.com') >= 0
		  && (ref = html.querySelector('span.release-packshot-image > img[itemprop="image"]')) != null) {
		set_image(ref.src);
	  }
	  if (response.finalUrl.toLowerCase().indexOf('highresaudio.com') >= 0
		  && (ref = html.querySelector('div.albumbody > img.cover[data-pin-media]')) != null) {
		set_image(ref.dataset.pinMedia);
	  }
	  if (response.finalUrl.toLowerCase().indexOf('hdtracks.com') >= 0
		  && (ref = html.querySelector('p.product-image > img')) != null) set_image(ref.src);
	  if (response.finalUrl.toLowerCase().indexOf('bandcamp.com') >= 0
		  && (ref = html.querySelector('div#tralbumArt > a.popupImage')) != null) set_image(ref.href);
	  if (response.finalUrl.toLowerCase().indexOf('discogs.com') >= 0
		  && (ref = html.querySelector('div#view_images > p:first-of-type > span > img')) != null) set_image(ref.src);
	  if (response.finalUrl.toLowerCase().indexOf('junodownload.com') >= 0
		  && (ref = html.querySelector('a.productimage')) != null) set_image(ref.href);
	  if (response.finalUrl.toLowerCase().indexOf('supraphonline.cz') >= 0
		  && (ref = html.querySelector('div.sexycover > img')) != null) set_image(ref.src.replace(/\?\d+$/, ''));
	}
  }

  function fill_from_text_apps() {
	if (clipBoard.value.search(/^https?:\/\//i) < 0) return false;
	var parser, html, description, tags = new TagManager();
	if (clipBoard.value.toLowerCase().indexOf('//sanet') >= 0) {
	  GM_xmlhttpRequest({ method: 'GET', url: clipBoard.value, onload: function(response) {
		if (response.readyState != 4 || response.status != 200) return;
		parser = new DOMParser();
		html = parser.parseFromString(response.responseText, "text/html");

		i = html.querySelector('h1.item_title > span');
		if (i != null && element_writable(ref = document.getElementById('title'))) {
		  ref.value = i.textContent.replace(/\(x64\)$/i, '(64-bit)').replace(/\bBuild\s+(\d+)/, 'build $1').
		  replace(/\bMultilingual\b/, 'multilingual').replace(/\bMultilanguage\b/, 'multilanguage');
		}

		i = html.querySelector('section.descr');
		if (i != null) {
		  description = '';
		  ref = html.querySelector('section.descr > div.release-info');
		  if (ref != null) var releaseInfo = ref.textContent.trim();
		  desc_extract(i);
		  ref = html.querySelector('div.txtleft > a');
		  if (ref != null) description += '\n\n[b]Product page:[/b]\n[url]' + de_anonymize(ref.href) + '[/url]';
		  write_description(description);

		  function desc_extract(node) {
			for (var i of node.childNodes) {
			  if (i.nodeType == 3) {
				if (i.length < 5) continue;
				description += i.textContent.trim();
			  } else if (i.nodeName == 'BR' || i.nodeName == 'HR') {
				description += '\n';
			  } else if (i.nodeName == 'LABEL') {
				description += '\n\n[b]' + i.textContent.trim() + '[/b]\n';
			  } else if (i.nodeName == 'A') {
				if (i.classList.contains('mfp-image')) {
				  //rehost_imgs([de_anonymize(i.href)]).then(new_url => {
				  //  description += '\n\n[img]' + new_url + '[/img]'
				  //}).catch(function() {
				  //  description += '\n\n[img]' + de_anonymize(i.href) + '[/img]'
				  //});
				  description += '\n\n[img]' + de_anonymize(i.href) + '[/img]'
				} else {
				  description += '[url=' + de_anonymize(i.href) + ']' + i.textContent.trim() + '[/url]';
				}
			  } else if (i.nodeName == 'B' || i.nodeName == 'STRONG') {
				description += '[b]' + i.textContent + '[/b]';
			  } else if (i.nodeName == 'I') {
				description += '[i]' + i.textContent + '[/i]';
			  } else if (i.nodeName == 'DIV') {
				if (i.classList.contains('scrpad') || i.classList.contains('aleft')) {
				  desc_extract(i);
				  description += '\n';
				}
			  }
			}
		  }
		}

		i = html.querySelector('section.descr > div.center > a.mfp-image');
		if (i != null) {
		  set_image(i.href);
		} else {
		  i = html.querySelector('section.descr > div.center > img[data-src]');
		  if (i != null) set_image(i.dataset.src);
		}

		var cat = html.querySelector('a.cat:last-of-type > span');
		if (cat != null) {
		  if (cat.textContent.toLowerCase() == 'windows') {
			tags.add('apps.windows');
			if (releaseInfo && releaseInfo.search(/\bx64\b/i) >= 0) tags.add('win64');
			if (releaseInfo && releaseInfo.search(/\bx86\b/i) >= 0) 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 && element_writable(ref = document.getElementById('tags'))) {
		  ref.value = tags.toString();
		}
	  }, });
	  return true;
	}
	return false;

	function de_anonymize(uri) {
	  return uri ? uri.replace('http://anonymz.com/?', '').replace('https://anonymz.com/?', '') : null;
	}

	function html2php(str) {
	  return str ? str.replace(/\<b\>/ig, '[b]').replace(/\<\/b\>/ig, '[/b]').
	  	replace(/\<i\>/ig, '[i]').replace(/\<\/i\>/ig, '[/i]') : null;
	}
  }

  function fill_from_text_books() {
	if (clipBoard.value.search(/^https?:\/\//i) < 0) return false;
	var parser, html, description, tags = new TagManager();
	if (clipBoard.value.toLowerCase().indexOf('martinus.cz') >= 0 || clipBoard.value.toLowerCase().indexOf('martinus.sk') >= 0) {
	  GM_xmlhttpRequest({ method: 'GET', url: clipBoard.value, onload: function(response) {
		if (response.readyState != 4 || response.status != 200) return;
		parser = new DOMParser();
		html = parser.parseFromString(response.responseText, "text/html");

		function get_detail(x, y) {
		  var ref = html.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 = html.querySelectorAll('article > ul > li > a');
		if (i != null && element_writable(ref = document.getElementById('title'))) {
		  description = join_authors(i);
		  i = html.querySelector('article > h1');
		  if (i != null) description += ' - ' + i.textContent.trim();
		  i = html.querySelector('div.bar.mb-medium > div:nth-child(1) > dl > dd > span');
		  if (i != null && (matches = i.textContent.match(/\b(\d{4})\b/)) != null) description += ' (' + matches[1] + ')';
		  ref.value = description;
		}

		description = '[quote]';
		i = html.querySelector('section#description > div');
		if (i != null) {
		  desc_extract(i);

		  function desc_extract(node) {
			for (var i of node.childNodes) {
			  if (i.nodeType == 3 || i.nodeName == 'P') {
				//if (i.length < 10) continue;
				description += i.textContent;
			  } else if (i.nodeName == 'BR' || i.nodeName == 'HR') {
				description += '\n';
			  } else if (i.nodeName == 'B' || i.nodeName == 'STRONG') {
				description += '[b]' + i.textContent + '[/b]';
			  } else if (i.nodeName == 'I') {
				description += '[i]' + i.textContent + '[/i]';
			  } else if (i.nodeName == 'DIV') {
				//desc_extract(i);
				//description += '\n';
			  }
			}
		  }
		}

		description += '[/quote]';
		let details = html.querySelectorAll('section#details > div > div > div:first-of-type > div > dl');
		for (var detail of details) {
		  var lbl = detail.children[0].textContent.trim();
		  var val = detail.children[1].textContent.trim();
		  if (lbl.search(/\b(?:originál)/i) >= 0) lbl = 'Original title'
		  else if (lbl.search(/\b(?:rozm)/i) >= 0) continue
		  else if (lbl.search(/\b(?:datum|dátum|rok)\b/i) >= 0) lbl = 'Release date'
		  else if (lbl.search(/\b(?:katalog|katalóg)/i) >= 0) lbl = 'Catalogue #'
		  else if (lbl.search(/\b(?:stran|strán)\b/i) >= 0) lbl = 'Page count'
		  else if (lbl.search(/\bjazyk/i) >= 0) lbl = 'Language'
		  else if (lbl.search(/\b(?:nakladatel|vydavatel)/i) >= 0) lbl = 'Publisher'
		  else if (lbl.search(/\b(?:vazba|vázba)\b/i) >= 0) continue
		  else if (lbl.search(/\b(?:doporuč|ODPORÚČ)/i) >= 0) lbl = 'Age rating'
		  else if (lbl.search(/\b(?:ISBN)\b/i) >= 0) {
			val = '[url=https://www.worldcat.org/isbn/' + detail.children[1].textContent.trim() +
			  ']' + detail.children[1].textContent.trim() + '[/url]';
// 		  } else if (lbl.search(/\b(?:ISBN)\b/i) >= 0) {
// 			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;
		}
		description += '\n[b]More info:[/b] ' + response.finalUrl;
		write_description(description);

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

		var cat = html.querySelectorAll('dd > ul > li > a');
		if (cat != null) cat.forEach(x => { tags.add(x.textContent) });
		if (tags.length > 0 && element_writable(ref = document.getElementById('tags'))) {
		  ref.value = tags.toString();
		}
	  }, });
	  return true;
	} else if (clipBoard.value.toLowerCase().indexOf('goodreads.com') >= 0) {
	  GM_xmlhttpRequest({ method: 'GET', url: clipBoard.value, onload: function(response) {
		if (response.readyState != 4 || response.status != 200) return;
		parser = new DOMParser();
		html = parser.parseFromString(response.responseText, "text/html");

		i = html.querySelectorAll('a.authorName > span');
		if (i != null && element_writable(ref = document.getElementById('title'))) {
		  description = join_authors(i);
		  i = html.querySelector('h1#bookTitle');
		  if (i != null) description += ' - ' + i.textContent.trim();
		  i = html.querySelector('div#details > div#row:nth-child(2)');
		  if (i != null && (matches = i.textContent.match(/\b(\d{4})\b/)) != null) description += ' (' + matches[1] + ')';
		  ref.value = description;
		}

		description = '[quote]';
		i = html.querySelector('div#description > span:last-of-type');
		if (i != null) {
		  desc_extract(i);

		  function desc_extract(node) {
			for (var i of node.childNodes) {
			  if (i.nodeType == 3 || i.nodeName == 'P') {
				//if (i.length < 10) continue;
				description += i.textContent;
			  } else if (i.nodeName == 'BR' || i.nodeName == 'HR') {
				description += '\n';
			  } else if (i.nodeName == 'B' || i.nodeName == 'STRONG') {
				description += '[b]' + i.textContent + '[/b]';
			  } else if (i.nodeName == 'I') {
				description += '[i]' + i.textContent + '[/i]';
			  } else if (i.nodeName == 'DIV') {
				//desc_extract(i);
				//description += '\n';
			  }
			}
		  }
		}
		description += '[/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;
		}

		i = html.querySelectorAll('div#details > div.row');
		if (i != null) i.forEach(k => { description += '\n' + strip(k.innerText) });
		description += '\n';

		let details = html.querySelectorAll('div#bookDataBox > div.clearFloats');
		for (var detail of details) {
		  var lbl = detail.children[0].textContent.trim();
		  var val = strip(detail.children[1].textContent);
		  if (lbl.search(/\b(?:ISBN)\b/i) >= 0 && ((matches = val.match(/\b(\d{13})\b/)) != null
				|| (matches = val.match(/\b(\d{10})\b/)) != null)) {
			val = '[url=https://www.worldcat.org/isbn/' + matches[1] + ']' + strip(detail.children[1].textContent) + '[/url]';
		  }
		  description += '\n[b]' + lbl + ':[/b] ' + val;
		}
		description += '\n[b]More info:[/b] ' + response.finalUrl;
		write_description(description);

		if ((i = html.querySelector('div.editionCover > img')) != null) {
		  set_image(i.src.replace(/\?.*/, ''));
		}

		var cat = html.querySelectorAll('div.elementList > div.left');
		if (cat != null) cat.forEach(x => { tags.add(x.textContent.trim()) });
		if (tags.length > 0 && element_writable(ref = document.getElementById('tags'))) {
		  ref.value = tags.toString();
		}
	  }, });
	  return true;
	} else if (clipBoard.value.toLowerCase().indexOf('databazeknih.cz') >= 0) {
	  let url = clipBoard.value;
	  if (url.toLowerCase().indexOf('show=alldesc') < 0) {
		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) return;
		parser = new DOMParser();
		html = parser.parseFromString(response.responseText, "text/html");

		i = html.querySelectorAll('span[itemprop="author"] > a');
		if (i != null && element_writable(ref = document.getElementById('title'))) {
		  description = join_authors(i);
		  i = html.querySelector('h1[itemprop="name"]');
		  if (i != null) description += ' - ' + i.textContent.trim();
		  i = html.querySelector('span[itemprop="datePublished"]');
		  if (i != null && (matches = i.textContent.match(/\b(\d{4})\b/)) != null) description += ' (' + matches[1] + ')';
		  ref.value = description;
		}

		description = '[quote]';
		i = html.querySelector('p[itemprop="description"]');
		if (i != null) desc_extract(i);

		function desc_extract(node) {
		  for (var i of node.childNodes) {
			if (i.nodeType == 3 || i.nodeName == 'P') {
			  //if (i.length < 10) continue;
			  description += i.textContent.trim();
			} else if (i.nodeName == 'BR' || i.nodeName == 'HR') {
			  description += '\n';
			} else if (i.nodeName == 'B' || i.nodeName == 'STRONG') {
			  description += '[b]' + i.textContent + '[/b]';
			} else if (i.nodeName == 'I') {
			  description += '[i]' + i.textContent + '[/i]';
			} else if (i.nodeName == 'DIV') {
			  //desc_extract(i);
			  //description += '\n';
			}
		  }
		}
		description += '[/quote]';

		let details = html.querySelectorAll('table.bdetail tr');
		if (details != null) details.forEach(function(detail) {
		  var lbl = detail.children[0].textContent.trim();
		  var val = detail.children[1].textContent.trim();
		  if (lbl.search(/(?:žánr|\bvazba)\b/i) >= 0) return;
		  else if (lbl.search(/\b(?:orig)/i) >= 0) lbl = 'Original title';
		  else if (lbl.search(/\b(?:série)\b/i) >= 0) lbl = 'Series';
		  else if (lbl.search(/\b(?:vydáno)\b/i) >= 0) lbl = 'Released';
		  else if (lbl.search(/\b(?:stran)\b/i) >= 0) lbl = 'Page count';
		  else if (lbl.search(/\b(?:jazyk)\b/i) >= 0) lbl = 'Language';
		  else if (lbl.search(/\b(?:překlad)/i) >= 0) lbl = 'Translation';
		  else if (lbl.search(/\b(?:autor obálky)\b/i) >= 0) lbl = 'Cover author';
		  else if (lbl.search(/\b(?:ISBN)\b/i) >= 0 && (matches = val.match(/\b(\d+(?:-\d+)*)\b/)) != null) {
			val = '[url=https://www.worldcat.org/isbn/' + matches[1].replace(/-/g, '') +
			  ']' + detail.children[1].textContent.trim() + '[/url]';
		  }
		  description += '\n[b]' + lbl + '[/b] ' + val;
		});
		description += '\n[b]More info:[/b] ' + response.finalUrl.replace(/\?.*/, '');
		write_description(description);

		if ((i = html.querySelector('div#icover_mid > a')) != null) set_image(i.href.replace(/\?.*/, ''));
		if ((i = html.querySelector('div#lbImage')) != null
			&& (matches = i.style.backgroundImage.match(/\burl\("(.*)"\)/i)) != null) {
		  set_image(matches[1].replace(/\?.*/, ''));
		}

		var cat = html.querySelectorAll('h5[itemprop="genre"] > a');
		if (cat != null) cat.forEach(x => { tags.add(x.textContent.trim()) });
		cat = html.querySelectorAll('a.tag');
		if (cat != null) cat.forEach(x => { tags.add(x.textContent.trim()) });
		if (tags.length > 0 && element_writable(ref = document.getElementById('tags'))) {
		  ref.value = tags.toString();
		}
	  }, });
	  return true;
	}
	return false;

	function join_authors(nodeList) {
	  if (typeof nodeList != 'object') return null;
	  var authors = [];
	  nodeList.forEach(k => { authors.push(k.textContent.trim()) });
	  return authors.join(' & ');
	}
  }

  function write_description(desc) {
	if (typeof desc != 'string') return;
	if (element_writable(ref = document.getElementById('desc'))) ref.value = desc;
	if ((ref = document.getElementById('body')) != null && !ref.disabled) {
	  if (ref.textLength > 0) ref.value += '\n\n';
	  ref.value += desc;
	}
  }

  function set_image(url) {
	var image = document.getElementById('image');
	if (!element_writable(image)) return false;
	image.value = url;
	var rehost_btn = document.querySelector('input.rehost_it_cover[type="button"]');
	if (rehost_btn != null) {
	  rehost_btn.click();
	} else {
	  var pr = rehost_imgs([url]);
	  if (pr != null) pr.then(new_urls => { image.value = new_urls[0] });
	}
  }

  // PTPIMG rehoster taken from `PTH PTPImg It`
  function rehost_imgs(urls) {
	if (!Array.isArray(urls)) return null;;
	var config = JSON.parse(window.localStorage.ptpimg_it);
	return config.api_key ? new Promise(ptpimg_upload_urls).catch(m => { alert(m) }) : null;

	function ptpimg_upload_urls(resolve, reject) {
	  const boundary = 'NN-GGn-PTPIMG';
	  var data = '--' + boundary + "\n";
	  data += 'Content-Disposition: form-data; name="link-upload"\n\n';
	  data += urls.map(function(url) {
		return url.toLowerCase().indexOf('://reho.st/') < 0 && url.toLowerCase().indexOf('discogs.com') >= 0 ?
		  'https://reho.st/' + url : url;
	  }).join('\n') + '\n';
	  data += '--' + boundary + '\n';
	  data += 'Content-Disposition: form-data; name="api_key"\n\n';
	  data += 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,
		},
		data: data,
		onload: response => {
		  if (response.status != 200) reject('Response error ' + response.status);
		  resolve(response.response.map(item => 'https://ptpimg.me/' + item.code + '.' + item.ext));
		},
	  });
	}
  }

  function element_writable(elem) { return elem != null && !elem.disabled && (overwrite || !elem.value) }
}

function add_artist() { exec(function() { AddArtistField() }) }

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

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 make_time_string(duration) {
  let t = Math.round(duration);
  t = Math.abs(t);
  let x = Math.floor(t / 60 ** 2);
  let res;
  if (x > 0) {
	res = x + ':' + Math.floor(t / 60 % 60).toString().padStart(2, '0');
  } else {
	res = Math.floor(t / 60 % 60).toString();
  }
  return res + ':' + (t % 60).toString().padStart(2, '0');
}

function extract_year(expr) {
  if (typeof expr != 'string') return null;
  var year = parseInt(expr);
  if (year > 0) return year;
  var m = expr.match(/\b(\d{4})\b/);
  return m && (year = parseInt(m[1])) > 0 ? year : null;
}

function isRED() { return document.domain.toLowerCase().endsWith('redacted.ch') }
function isNWCD() { return document.domain.toLowerCase().endsWith('notwhat.cd') }
function isOrpheus() { return document.domain.toLowerCase().endsWith('orpheus.network') }

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

function matchCaseless(str) { return str.toLowerCase() == this.toLowerCase() }

Array.prototype.includesCaseless = function(str) { return this.find(matchCaseless, str) != undefined };

function addWarning(text, bold = true, color = 'red') {
  warnings = document.getElementById('UA_warnings');
  if (warnings == null) {
	var ua = document.getElementById('upload assistant');
	if (ua == null) return null;
	warnings = document.createElement('TR');
	if (warnings == null) return null;
	warnings.id = 'UA_warnings';
	ua.children[0].append(warnings);

	elem = document.createElement('TD');
	if (elem == null) return null;
	elem.colSpan = 2;
	elem.style.paddingLeft = '15px';
	elem.style.paddingRight = '15px';
	elem.style.textAlign = 'left';
	warnings.append(elem);
  } else {
	elem = warnings.children[0];
	if (elem == null) return null;
  }
  var div = document.createElement('DIV');
  if (color) div.style.color = color;
  if (bold) {
	div.appendChild(document.createElement('B')).textContent = text;
  } else {
	div.textContent = text;
  }
  return elem.appendChild(div);
}