Skip to content

Commit a944458

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 a944458

1 file changed

Lines changed: 221 additions & 0 deletions

File tree

internetarchive_importer.user.js

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

0 commit comments

Comments
 (0)