Skip to content

Commit a09897f

Browse files
committed
internetarchive_importer: add new importer
This importer adds releases from the Internet Archive. Currently supporting https://archive.org/details/*
1 parent c550e09 commit a09897f

1 file changed

Lines changed: 226 additions & 0 deletions

File tree

internetarchive_importer.user.js

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// ==UserScript==
2+
// @name Import Internet Archive releases to Musicbrainz
3+
// @description Add a button to Internet Archive pages to open MusicBrainz release editor with pre-filled data for the selected release
4+
// @version 2026.07.06.0
5+
// @downloadURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/internetarchive_importer.user.js
6+
// @updateURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/internetarchive_importer.user.js
7+
// @namespace https://github.com/murdos/musicbrainz-userscripts
8+
// @match https://archive.org/details/*
9+
// @require https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/mbimport.js
10+
// @require https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/logger.js
11+
// @require https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/mbimportstyle.js
12+
// @grant unsafeWindow
13+
// @run-at document-start
14+
// ==/UserScript==
15+
16+
// @ts-nocheck
17+
18+
if (!unsafeWindow) {
19+
/* oxlint-disable-next-line no-global-assign */
20+
unsafeWindow = window;
21+
}
22+
23+
async function onLoad() {
24+
MBImportStyle();
25+
26+
const release_url = window.location.href.replace('/?.*$/', '').replace(/#.*$/, '');
27+
let releaseInfo = getReleaseInfo(release_url);
28+
insertLink(releaseInfo, release_url);
29+
}
30+
31+
function isAudioFile(file) {
32+
if (file.match(/mp3|flac|wave|ogg|audio|aiff|shorten|weba/i)) {
33+
return true;
34+
}
35+
return false;
36+
}
37+
38+
function stripHTMLTags(textContent) {
39+
let tempDiv = document.createElement('div');
40+
tempDiv.innerHTML = textContent.replaceAll('<br />', '\n').replaceAll('<br>', '\n');
41+
return tempDiv.innerText.trim();
42+
}
43+
44+
function createAnnotation(metadata) {
45+
let annotation = '=== Credits from Internet Archive ===\n';
46+
annotation += stripHTMLTags(metadata.description);
47+
48+
let notes = '';
49+
if ('date' in metadata) notes += `\nPublication date: ${ metadata.date}`;
50+
if ('subject' in metadata) {
51+
let tags = metadata.subject;
52+
if (typeof tags == 'string') {
53+
tags = tags
54+
.replaceAll(';', ',')
55+
.split(',')
56+
.map(e => e.trim());
57+
}
58+
notes += `\nTags: ${ tags.join(', ')}`;
59+
}
60+
if (notes.length > 0) annotation += `\n==== metadata ====${ notes}`;
61+
return annotation;
62+
}
63+
64+
function getMultiArtist(artist) {
65+
let artists = [];
66+
// Split release style.
67+
if (artist.includes('/')) {
68+
artists = artist
69+
.split('/')
70+
.map(e => e.trim())
71+
.map(function (item) {
72+
return { artist_name: item, joinphrase: ' / ' };
73+
});
74+
artists[artists.length - 1].joinphrase = '';
75+
} else {
76+
// Removing trailing chars.
77+
// Splitting only with ';' as ',' is used in sortnames and '&' may belong to artist name.
78+
artists = MBImport.makeArtistCredits(
79+
artist
80+
.replace(/[,;&]*$/, '')
81+
.split(';')
82+
.map(e => e.trim()),
83+
);
84+
}
85+
// return various if over 5 artists
86+
if (artists.length > 5) artists = [MBImport.specialArtist('various_artists')];
87+
return artists;
88+
}
89+
90+
function filesToDisc(files, artist) {
91+
let disc = {
92+
tracks: [],
93+
format: 'Digital Media',
94+
};
95+
96+
files.forEach(file => {
97+
if (file.source == 'original' && isAudioFile(file.format)) {
98+
disc.tracks.push({
99+
title: file.title,
100+
duration: Math.round(file.length * 1000),
101+
artist_credit: MBImport.makeArtistCredits([file.artist ?? artist]),
102+
});
103+
}
104+
});
105+
106+
return disc;
107+
}
108+
109+
function isVariousDisc(disc) {
110+
if (disc.tracks.length <= 5) return false;
111+
let unique_artists = new Set();
112+
disc.tracks.map(function (item) {
113+
unique_artists.add(item.artist_credit.artist_name);
114+
});
115+
return unique_artists.size > 5;
116+
}
117+
118+
function getReleaseInfo(release_url) {
119+
const files = JSON.parse(document.querySelector('input[class="js-ia-metadata"]').value).files;
120+
const metadata = JSON.parse(document.querySelector('input[class="js-ia-metadata"]').value).metadata;
121+
122+
let artist = metadata.creator ?? metadata.md_music_artist;
123+
if (artist === undefined) {
124+
artist = 'artist key not found';
125+
LOGGER.info(
126+
'No known artist key found.\n' +
127+
'Please inspect the metadata in console and submit a bug report.\n' +
128+
'JSON.parse(document.querySelector(\'input[class="js-ia-metadata"]\').value).metadata',
129+
);
130+
}
131+
const identifier = metadata.identifier;
132+
133+
let releaseDate = new Date(`${metadata.publicdate }Z`);
134+
let month = releaseDate.getUTCMonth() + 1;
135+
let day = releaseDate.getUTCDate();
136+
let releaseYear = releaseDate.getUTCFullYear();
137+
138+
let title = metadata.title
139+
.replace(`${artist } - `, '')
140+
.replace(`[${ releaseYear }]`, '')
141+
.trim();
142+
if ('date' in metadata) {
143+
title = title.replace(`[${ metadata.date }]`, '').trim();
144+
}
145+
146+
let disc = filesToDisc(files, artist);
147+
let artists = [];
148+
149+
if (isVariousDisc(disc)) {
150+
artists = [MBImport.specialArtist('various_artists')];
151+
} else {
152+
artists = getMultiArtist(artist);
153+
}
154+
155+
let release = {
156+
media_type: metadata.mediatype,
157+
title: title,
158+
artist_credit: artists,
159+
status: 'official',
160+
language: 'eng',
161+
script: 'Latn',
162+
packaging: 'None',
163+
country: 'XW',
164+
labels: [],
165+
barcode: '',
166+
annotation: createAnnotation(metadata),
167+
year: releaseYear,
168+
month: month,
169+
day: day,
170+
urls: [],
171+
discs: [disc],
172+
};
173+
174+
// Download & stream urls
175+
release.urls.push({
176+
url: release_url,
177+
link_type: MBImport.URL_TYPES.download_for_free,
178+
});
179+
release.urls.push({
180+
url: release_url,
181+
link_type: MBImport.URL_TYPES.stream_for_free,
182+
});
183+
// License url
184+
if ('licenseurl' in metadata) {
185+
release.urls.push({
186+
url: metadata.licenseurl,
187+
link_type: MBImport.URL_TYPES.license,
188+
});
189+
}
190+
// Labels
191+
metadata.collection.forEach(collection => {
192+
release.labels.push({
193+
name: document.querySelector(`a[data-event-click-tracking*="CollectionList|${ collection }"]`).textContent.trim(),
194+
catno: metadata.identifier,
195+
});
196+
});
197+
198+
return release;
199+
}
200+
201+
// Insert button into page
202+
function insertLink(release, release_url) {
203+
if (release.media_type !== 'audio') {
204+
// only import audio
205+
return false;
206+
}
207+
208+
const edit_note = MBImport.makeEditNote(release_url, 'Internet Archive');
209+
const parameters = MBImport.buildFormParameters(release, edit_note);
210+
211+
const importButton = MBImport.buildFormHTML(parameters);
212+
const searchButton = MBImport.buildSearchButton(release);
213+
214+
const mbUI = document.createElement('div');
215+
mbUI.id = 'mb_buttons';
216+
mbUI.innerHTML = `${importButton}${searchButton}`;
217+
document.querySelector('h1.item-title').appendChild(mbUI);
218+
mbUI.style.float = 'right';
219+
mbUI.style.marginLeft = '1em';
220+
}
221+
222+
if (document.readyState !== 'loading') {
223+
onLoad();
224+
} else {
225+
document.addEventListener('DOMContentLoaded', onLoad);
226+
}

0 commit comments

Comments
 (0)