Supraphonline: kopírovat metadata do schránkuy

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

当前为 2020-08-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Supraphonline: kopírovat metadata do schránkuy
// @name-cz      Supraphonline: kopírovat metadata do schránkuy
// @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ě
// @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());
}