Supraphonline: kopírovat metadata do schránky

Zkopíruje playlist do schránky tak aby jej bylo možno v hudebním přehrávači aplikovat na tagy

目前为 2020-09-01 提交的版本。查看 最新版本

// ==UserScript==
// @name         Supraphonline: kopírovat metadata do schránky
// @name-cz      Supraphonline: kopírovat metadata do schránky
// @name-en      Supraphonline: copy metadata to clipboard
// @namespace    https://greasyfork.org/cs/users/321857-anakunda
// @version      1.2
// @description  Zkopíruje playlist do schránky tak aby jej bylo možno v hudebním přehrávači aplikovat na tagy
// @author       Já, Osobně
// @copyright    2020, Anakunda (https://greasyfork.org/cs/users/321857-anakunda)
// @license      GPL-3.0-or-later
// @iconURL      https://www.supraphonline.cz/favicon.ico
// @match        http*://*.supraphonline.cz/album/*
// @grant        GM_getValue
// @grant        GM_getValue
// @grant        GM_setClipboard
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest

// ==/UserScript==

// Výraz pro 'Automatically Fill Values' funkci ve foobaru2000:
//   %album artist%%album%%date%%releasedate%%genre%%label%%catalog%%discnumber%%totaldiscs%%discsubtitle%%tracknumber%%totaltracks%%artist%%title%%performer%%composer%%media%%comment%%url%

'use strict';

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;
};

var hTimer = setInterval(function() {
  var ref = document.querySelector('form.table-action');
  if (ref == null) return;
  clearInterval(hTimer);
  var child = document.createElement('button');
  child.id = 'copy-info-to-clipboard';
  child.textContent = 'Kopírovat do schránky';
  child.type = 'button';
  child.name = 'copy-info-to-clipboard';
  child.className = 'btn btn-danger topframe_login';
  child.style.marginRight = '10px';
  child.onclick = fetchAlbum;
  ref.prepend(child);
}, 1000);

function fetchAlbum(evt) {
  var original_text = evt.target.textContent;
  evt.target.disabled = true;
  evt.target.textContent = 'Pracuji...';
  var isVA = false, ndx, artists = [], tracks = [], totalTime, discNumber, discSubtitle;
  var domParser = new DOMParser();
  var release = {
	artists: [],
	composers: [],
	performers: [],
	totalDiscs: 1,
  };
  document.querySelectorAll('h2.album-artist > a').forEach(it => { release.artists.pushUnique(it.title) });
  if (release.artists.length == 0 && (ref = document.querySelector('h2.album-artist[title]')) != null) {
	if (/^(?:Různí(?:\s+interpreti)?|Various(?:\s+Artists)?|VA)$/i.test(ref.title)) isVA = true;
	if (isVA) release.artists = ['Various Artists']; //else albumArtist = [ref.title];
  }
  var ref = document.querySelector('span[itemprop="byArtist"] > meta[itemprop="name"]');
  if (ref != null && /^(?:Různí\s+interpreti)$/i.test(ref.content)) isVA = true;
  if ((ref = document.querySelector('h1[itemprop="name"]')) != null) release.album = ref.firstChild.data.trim();
  if ((ref = document.querySelector('meta[itemprop="numTracks"]')) != null) release.totalTracks = parseInt(ref.content);
  if ((ref = document.querySelector('meta[itemprop="genre"]')) != null) release.genre = ref.content;
  if ((ref = document.querySelector('li.album-version > div.selected > div')) != null) {
	if (/\b(?:CD)\b/.test(ref.textContent)) release.media = 'CD';
	if (/\b(?:LP)\b/.test(ref.textContent)) release.media = 'Vinyl';
	if (/\b(?:MP3)\b/.test(ref.textContent)) release.media = 'WEB-DL';
	if (/\b(?:FLAC)\b/.test(ref.textContent)) release.media = 'WEB-DL';
	if (/\b(?:Hi[\s\-]*Res)\b/.test(ref.textContent)) release.media = 'WEB-DL';
  }
  document.querySelectorAll('ul.summary > li').forEach(function(it) {
	if (it.children.length < 1) return;
	if (it.children[0].textContent.startsWith('Nosič')) release.media = it.lastChild.textContent.trim();
	if (it.children[0].textContent.startsWith('Datum vydání')) release.releaseDate = normalizeDate(it.lastChild.textContent);
	//if (it.children[0].textContent.startsWith('Žánr')) release.genre = it.lastChild.textContent.trim();
	if (it.children[0].textContent.startsWith('Vydavatel')) release.label = it.lastChild.textContent.trim();
	if (it.children[0].textContent.startsWith('Katalogové číslo')) release.catalogue = it.lastChild.textContent.trim();
	if (it.children[0].textContent.startsWith('Celková stopáž')) totalTime = timeStringToTime(it.lastChild.textContent.trim());
	if (/^(?:\([PC]\)|℗|©)$/i.test(it.children[0].textContent)) release.albumYear = extract_year(it.lastChild.data);
  });
  [
	[/^(?:Orchestrální\s+hudba)$/i, 'Orchestral Music'],
	[/^(?:Komorní\s+hudba)$/i, 'Chamber Music'],
	[/^(?:Vokální)$/i, 'Classical, Vocal'],
	[/^(?:Klasická\s+hudba)$/i, 'Classical'],
	[/^(?:Melodram)$/i, 'Classical, Melodram'],
	[/^(?:Symfonie)$/i, 'Symphony'],
	[/^(?:Vánoční\s+hudba)$/i, 'Christmas Music'],
	[/^(?:Alternativní)$/i, 'Alternative'],
	[/^(?:Dechová\s+hudba)$/i, 'Brass Music'],
	[/^(?:Elektronika)$/i, 'Electronic'],
	[/^(?:Folklor)$/i, 'Folclore, World Music'],
	[/^(?:Instrumentální\s+hudba)$/i, 'Instrumental'],
	[/^(?:Latinské\s+rytmy)$/i, 'Latin'],
	[/^(?:Meditační\s+hudba)$/i, 'Meditative'],
	[/^(?:Pro\s+děti)$/i, 'Children'],
  ].forEach(it => { if (it[0].test(release.genre)) release.genre = it[1] });
  const creators = [
	'autoři',
	'interpreti',
	'tělesa',
	'digitalizace',
  ];
  for (var i = 0; i < 4; ++i) artists[i] = {};
  document.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;
	  ref = it.querySelector('span');
	  let key = ref != null ? ref.textContent.replace(/\s*:.*$/, '').toLowerCase() : undefined;
// 	  [
// 		[/^(?:zpěv)$/, 'vocal'],
// 		[/^(?:hudba)$/, 'music'],
// 		[/^(?:původní\s+text)$/, 'original lyrics'],
// 		[/^(?:text)$/, 'lyrics'],
// 		[/^(?:autor)$/, 'author'],
// 		[/^(?:účinkuje)$/, 'participating'],
// 		[/^(?:nahrál)$/, 'recorded'],
// 		[/^(?:sbormistr)$/, 'choirmaster'],
// 		[/^(?:řídí|dirigent)$/, 'conductor'],
// 	  ].forEach(it => { if (it[0].test(key)) key = it[1] });
	  if (!(artists[ndx][key] instanceof Array)) artists[ndx][key] = [];
	  artists[ndx][key].pushUnique(it.querySelector('a').textContent.trim());
	}
  });
  release.description = Array.from(document.querySelectorAll('div[itemprop="description"] p'))
	.map(it => it.textContent.trim()).join(' ').replace(/[\r\n]+/g, ' ');
  for (i = 1; i < 3; ++i) Object.keys(artists[i]).forEach(it => { release.performers.pushUnique(...artists[i][it]) });
  Object.keys(artists[0]).forEach(it => { release.composers.pushUnique(...artists[0][it]) });
  if (release.artists.length == 0 && !isVA) release.artists = release.performers;
  var promises = [];
  document.querySelectorAll('table.table-tracklist > tbody > tr').forEach(function(it) {
	if (it.classList.contains('cd-header')) {
	  discNumber = /\b\d+\b/.test(it.querySelector(':scope h3').firstChild.data.trim()) ?
	  	parseInt(RegExp.lastMatch) : undefined;
	  if (discNumber > release.totalDiscs) release.totalDiscs = discNumber;
	}
	if (it.classList.contains('song-header')) discSubtitle = it.children[0].title.trim() || undefined;
	if (it.classList.contains('track') && it.id) {
	  let track = {
		id: parseInt(it.id.replace(/^track-/i, '')),
		artists: [],
		performers: [],
		composers: [],
		conductors: [],
		producers: [],
		discNumber: discNumber,
		discSubtitle: discSubtitle,
	  };
	  if (/^\s*(\d+)\.?\s*$/.test(it.children[0].firstChild.textContent)) track.trackNumber = parseInt(RegExp.$1);
	  ref = it.querySelectorAll('meta[itemprop="name"]');
	  if (ref.length > 0) track.title = ref[0].content;
	  if (/^PT(\d+)H(\d+)M(\d+)S$/i.test(it.querySelector('meta[itemprop="duration"]').content)) {
		track.duration = parseInt(RegExp.$1 || 0) * 60**2 + parseInt(RegExp.$2 || 0) * 60 + parseInt(RegExp.$3 || 0);
	  }
	  tracks.push(track);
	  ref = it.querySelector('td > a.trackdetail');
	  if (ref != null) promises.push(new Promise(function(resolve, reject) {
		GM_xmlhttpRequest({
		  method: 'GET',
		  url: ref.href,
		  responseType: 'text/html',
		  context: track,
		  onload: function(response) {
			if (response.readyState != 4 || response.status != 200) {
			  reject('Response error ' + response.status + ' (' + response.statusText + ')');
			  return;
			}
			let dom = domParser.parseFromString(response.responseText, "text/html");
			if (dom == null) reject('Parse error');
			var track = response.context;
			var tr_det = dom.querySelector('div[data-swap="trackdetail-' + track.id + '"] > div > div.row');
			if (tr_det == null) reject('No trackdetail for track-' + track.id);
			tr_det.querySelectorAll('div:nth-of-type(1) > ul > li > span').forEach(function(it) {
			  if (it.textContent.startsWith('Nahrávka dokončena')) track.recordDate = it.nextSibling.data.trim();
			  if (it.textContent.startsWith('Místo nahrání')) track.recordPlace = it.nextSibling.data.trim();
			  if (it.textContent.startsWith('Rok prvního vydání')) track.albumYear = parseInt(it.nextSibling.data);
			  if (it.textContent.startsWith('(P)')) track.copyright = it.nextSibling.data.trim();
			  if (it.textContent.startsWith('Žánr')) track.genre = it.nextSibling.data.trim();
			});
			tr_det.querySelectorAll('div:nth-of-type(2) > ul > li > span').forEach(function(it) {
			  function oneOf(arr) { return arr.some(k => elem[0].startsWith(k)) }
			  var elem = [it.textContent.trim().replace(/:.*/, ''), it.nextElementSibling.textContent.trim()];
			  if (oneOf(['hudba', 'text', 'hudba+text', 'původní text', 'český text', 'libreto'])) {
				track.composers.push(elem);
			  } else if (oneOf(['nahrál'])) {
			  } else if (oneOf(['dirigent', 'řídí'])) {
				track.conductors.push(elem);
			  } else if (oneOf(['produkce'])) {
				track.producers.push(elem[1]);
			  } else {
				track.performers.push(elem);
			  }
			});
			// TODO: vyfiltrovat track.artists z track.performers
			resolve(true);
		  },
		  onerror: response => { reject('Response error ' + response.status + ' (' + response.statusText + ')') },
		  ontimeout: function() { reject('Timeout') },
		});
	  }));
	}
  });
  Promise.all(promises).then(function() {
	var clipBoard = '';
	tracks.sort(function(a, b) {
	  var k = a.discNumber - b.discNumber;
	  return isNaN(k) || k == 0 ? a.trackNumber - b.trackNumber : k;
	}).forEach(function(track) {
	  clipBoard += [
		joinArtists(release.artists),
		release.album,
		release.albumYear || track.albumYear,
		release.releaseDate,
		track.genre || release.genre,
		release.label,
		release.catalogue,
		track.discNumber,
		release.totalDiscs > 1 ? release.totalDiscs : undefined,
		track.discSubtitle,
		track.trackNumber,
		release.totalTracks,
		joinArtists(track.artists.length > 0 ? track.artists : release.artists),
		track.title,
		track.performers.concat(track.conductors).map(desc).join(', ') || release.performers.join(', '),
		track.composers.map(desc).join(', ') || release.composers.join(', '),
		release.media,
		release.description,
		document.location.origin + document.location.pathname,
	  ].join('\x1E') + '\n';
	});
	GM_setClipboard(clipBoard, 'text');
  }).catch(e => { alert(e) }).then(function() {
	evt.target.disabled = false;
	evt.target.textContent = original_text;
  });
}

function desc(el) {
  var res = el[1];
  if (el[0] && el[0] != 'hudební tělesa') res += ' (' + el[0] + ')';
  return res;
}

function joinArtists(list) {
  return list.length < 3 ? list.join(' a ') : list.slice(0, -1).join(', ') + ' a ' + list[list.length - 1];
}

function normalizeDate(str) {
  if (typeof str != 'string') return null;
  if (/\b(d{4})-(\d+)-(\d+)\b/.test(str)) {
	var D = parseInt(RegExp.$3);
	var M = parseInt(RegExp.$2);
	var Y = parseInt(RegExp.$1);
  } else if (/\b(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})\b/.test(str)) {
	D = parseInt(RegExp.$2);
	M = parseInt(RegExp.$1);
	Y = parseInt(RegExp.$3);
  } else if (/\b(\d{1,2})\.\s?(\d{1,2})\.\s?(\d{2}|\d{4})\b/.test(str)) {
	D = parseInt(RegExp.$1);
	M = parseInt(RegExp.$2);
	Y = parseInt(RegExp.$3);
  }
  if (Y < 100) Y += 2000;
  return D > 0 && M > 0 && Y > 0 ?
	Y.toString().padStart(4, '0') + '-' + M.toString().padStart(2, '0') + '-' + D.toString().padStart(2, '0')
  		: extract_year(str);
}

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 extract_year(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());
}