NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name MusicBrainz Import from Music Forest // @namespace https://github.com/y-young // @version 2024.5.5 // @description Import releases from Music Forest into MusicBrainz. // @author y-young // @licence MIT; https://opensource.org/licenses/MIT // @supportURL https://github.com/y-young/userscripts/labels/mb-import-from-music-forest // @downloadURL https://github.com/y-young/userscripts/raw/master/musicbrainz-import-from-music-forest.user.js // @match https://www.minc.or.jp/music/list* // @match https://www.minc.or.jp/product/list* // @icon https://www.minc.or.jp/favicon.ico // @require https://cdn.jsdelivr.net/gh/murdos/musicbrainz-userscripts@e84565918e728252753a6e24d350b995dfae2953/lib/mbimport.js // @grant GM_registerMenuCommand // ==/UserScript== /* * Usage: * Open a modal that contains release information, * find "MusicBrainz Import from Music Forest" in right-click menu * and click "Import into MusicBrainz". * * Things to check before submission: * - Album language and script type, default to "Japanese" * - Release country, default to "Japan" */ "use strict"; /* * Parse Catalog No * "ABCD-12345" -> [{ catno: "ABCD-12345" }] * "ABCD-12345/6" -> [{ catno: "ABCD-12345" }, { catno: "ABCD-12346" }] * "ABCD-59/60" -> [{ catno: "ABCD-59" }, { catno: "ABCD-60" }] */ function parseCatNo(catNoStr) { const parts = catNoStr.split("/"); const first = parts[0]; const catNos = [{ catno: first }]; const endStr = parts[1]; if (endStr) { const end = parseInt(endStr); const start = parseInt(first.slice(0 - endStr.length)); for (let i = start + 1; i <= end; ++i) { catNos.push({ catno: first.slice(0, 0 - i.toString().length) + i.toString(), }); } } return catNos; } /* * Parse Release Date * "2021/1/1" -> { year: "2021", month: "1", day: "1" } */ function parseDate(date) { const data = date.split("/"); if (data.length !== 3) { return { year: "", month: "", day: "", }; } return { year: data[0], month: data[1], day: data[2], }; } function parseDiscFormat(discFormat) { if (!discFormat) { return ""; } const match = discFormat.innerText.match(/\b(CD|DVD)/); return match ? match[1] : ""; } /* * Parse track duration * `1:33:33` -> "1:33:33" * `3'33"` -> "3:33" * `33"` -> "0:33" */ function parseDuration(duration) { let data = duration.split("'"); let hours = ""; let minutes = ""; if (data.length > 2) { hours = data[0]; data = data.slice(1); } if (data.length > 1) { minutes = data[0]; data = data.slice(1); } else { minutes = "0"; } const seconds = data[0].slice(0, -1); return (hours ? hours + ":" : "") + minutes + ":" + seconds; } function parseArtistCredit(artists) { return artists.split("<br>").reduce((result, artist, index, array) => { result.push({ artist_name: artist, joinphrase: index < array.length - 1 ? " & " : "", }); return result; }, []); } function parseISRC(isrc) { isrc = isrc.trim(); return isrc === "-" ? "" : isrc; } function parseTrackList(trackList) { const rows = trackList.querySelectorAll("tr:not(.header)"); const tracks = []; rows.forEach((row) => { const cols = Array.from(row.children).map((col, index) => index === 5 ? col.innerHTML : col.innerText ); const medly = cols[1]; // Skip parts of a medly, e.g: JECN-358/9 if (medly !== "0") { return; } const title = cols[2]; const duration = parseDuration(cols[4]); const artist_credit = parseArtistCredit(cols[5]); const isrc = parseISRC(cols[6]); tracks.push({ title, duration, artist_credit, isrc, }); }); return tracks; } function resolveReleaseArtist(catno) { const rows = Array.from( document.querySelectorAll("table#cd-list tr:not(.header)") ); const row = rows.find((row) => row.children[1].innerText.trim() === catno); return parseArtistCredit(row.children[4].innerText); } function parseModalContent() { const modal = document.querySelector("div#cd_detail"); if (!modal.classList.contains("in")) { alert("Please open a modal first."); return; } const title = modal.querySelector("h4.modal-title").innerText; const metaItems = Array.from( modal.querySelectorAll("div.detail_data div.col-sm-3") ).map((item) => item.innerText); const catno = metaItems[0].substr(3); const labels = parseCatNo(catno); const date = parseDate(metaItems[1].substr(4)); const barcode = metaItems[4].substr(4); const artist_credit = resolveReleaseArtist(catno); const discFormats = modal.querySelectorAll( "div.disk_data div.col-sm-2:first-child" ); const trackLists = modal.querySelectorAll("table.cd-detail2-track-list"); const discs = []; trackLists.forEach((trackList, index) => { const format = parseDiscFormat(discFormats[index]); const tracks = parseTrackList(trackList); discs.push({ format, tracks, }); }); const release = { title, artist_credit, type: "album", status: "official", language: "jpn", script: "Jpan", ...date, country: "JP", barcode, labels, discs, urls: [], }; return release; } function importToMB() { const release = parseModalContent(); if (!release) { return; } const note = ` --------------- ${location.href} Imported from Music Forest using https://github.com/y-young/userscripts#import-from-music-forest`; const parameters = MBImport.buildFormParameters(release, note); const formHtml = MBImport.buildFormHTML(parameters); const form = document.createElement("div"); form.className = "clearfix"; form.style.display = "none"; form.innerHTML = formHtml; const modal = document.querySelector("div#cd_detail"); modal.querySelector("div.modal-body").prepend(form); form.querySelector("button[type='submit']").click(); } function submitISRCs() { const release = parseModalContent(); if (!release) { return; } const params = new URLSearchParams(); release.discs.forEach((disc, discIndex) => { disc.tracks.forEach((track, trackIndex) => { params.append(`isrc${discIndex + 1}-${trackIndex + 1}`, track.isrc); }); }); window.open("https://magicisrc.kepstin.ca/?" + params.toString()); } GM_registerMenuCommand("Import into MusicBrainz", importToMB); GM_registerMenuCommand("Submit ISRCs", submitISRCs);