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 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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