Skip to content

Commit 73e461f

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 73e461f

1 file changed

Lines changed: 223 additions & 0 deletions

File tree

internetarchive_importer.user.js

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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.replace(`${artist} - `, '').replace(`[${releaseYear}]`, '').trim();
139+
if ('date' in metadata) {
140+
title = title.replace(`[${metadata.date}]`, '').trim();
141+
}
142+
143+
let disc = filesToDisc(files, artist);
144+
let artists = [];
145+
146+
if (isVariousDisc(disc)) {
147+
artists = [MBImport.specialArtist('various_artists')];
148+
} else {
149+
artists = getMultiArtist(artist);
150+
}
151+
152+
let release = {
153+
media_type: metadata.mediatype,
154+
title: title,
155+
artist_credit: artists,
156+
status: 'official',
157+
language: 'eng',
158+
script: 'Latn',
159+
packaging: 'None',
160+
country: 'XW',
161+
labels: [],
162+
barcode: '',
163+
annotation: createAnnotation(metadata),
164+
year: releaseYear,
165+
month: month,
166+
day: day,
167+
urls: [],
168+
discs: [disc],
169+
};
170+
171+
// Download & stream urls
172+
release.urls.push({
173+
url: release_url,
174+
link_type: MBImport.URL_TYPES.download_for_free,
175+
});
176+
release.urls.push({
177+
url: release_url,
178+
link_type: MBImport.URL_TYPES.stream_for_free,
179+
});
180+
// License url
181+
if ('licenseurl' in metadata) {
182+
release.urls.push({
183+
url: metadata.licenseurl,
184+
link_type: MBImport.URL_TYPES.license,
185+
});
186+
}
187+
// Labels
188+
metadata.collection.forEach(collection => {
189+
release.labels.push({
190+
name: document.querySelector(`a[data-event-click-tracking*="CollectionList|${collection}"]`).textContent.trim(),
191+
catno: metadata.identifier,
192+
});
193+
});
194+
195+
return release;
196+
}
197+
198+
// Insert button into page
199+
function insertLink(release, release_url) {
200+
if (release.media_type !== 'audio') {
201+
// only import audio
202+
return false;
203+
}
204+
205+
const edit_note = MBImport.makeEditNote(release_url, 'Internet Archive');
206+
const parameters = MBImport.buildFormParameters(release, edit_note);
207+
208+
const importButton = MBImport.buildFormHTML(parameters);
209+
const searchButton = MBImport.buildSearchButton(release);
210+
211+
const mbUI = document.createElement('div');
212+
mbUI.id = 'mb_buttons';
213+
mbUI.innerHTML = `${importButton}${searchButton}`;
214+
document.querySelector('h1.item-title').appendChild(mbUI);
215+
mbUI.style.float = 'right';
216+
mbUI.style.marginLeft = '1em';
217+
}
218+
219+
if (document.readyState !== 'loading') {
220+
onLoad();
221+
} else {
222+
document.addEventListener('DOMContentLoaded', onLoad);
223+
}

0 commit comments

Comments
 (0)