├── LICENSE
├── README.md
└── Stigs_Art_Grabr.user.js
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Stig Nygaard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Stig's Art Grabr
2 | Stig's Art Grabr is a userscript for grabbing high resolution album cover-art from various sites.
3 | Can also be used as a bookmarklet/favelet.
4 |
5 | For more information, see https://greasyfork.org/scripts/20771-stig-s-art-grabr
6 |
7 | I don't use this tool every day myself. Or every week. Not even every month. And some of the supported sites I even more seldom visit myself. So DO give me a tip if it stops working on some of the sites which is supposed to be supported. I might be slow discovering it myself if nobody tells me...
8 |
--------------------------------------------------------------------------------
/Stigs_Art_Grabr.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Stig's Art Grabr
3 | // @namespace dk.rockland.userscript.misc.artgrab
4 | // @description Grabbing big high resolution album cover-art from various sites
5 | // @version 2025.04.17.0
6 | // @author Stig Nygaard, https://www.rockland.dk
7 | // @homepageURL https://www.rockland.dk/userscript/misc/artgrab/
8 | // @supportURL https://www.rockland.dk/userscript/misc/artgrab/
9 | // @match *://*.allmusic.com/*
10 | // @match *://*.bandcamp.com/*
11 | // @match *://*.music.apple.com/*
12 | // @match *://*.itunes.apple.com/*
13 | // @match *://*.musicdiner.com/*
14 | // @match *://*.fnd.io/*
15 | // @match *://labs.stephenou.com/itunes/*
16 | // @match *://*.last.fm/*
17 | // @match *://*.lastfm.de/*
18 | // @match *://*.lastfm.es/*
19 | // @match *://*.lastfm.fr/*
20 | // @match *://*.lastfm.it/*
21 | // @match *://*.lastfm.ja/*
22 | // @match *://*.lastfm.pl/*
23 | // @match *://*.lastfm.pt/*
24 | // @match *://*.lastfm.ru/*
25 | // @match *://*.lastfm.sv/*
26 | // @match *://*.lastfm.tr/*
27 | // @match *://*.lastfm.zh/*
28 | // @match *://*.musicbrainz.org/*
29 | // @match *://*.soundcloud.com/*
30 | // @match *://play.google.com/*
31 | // @match *://*.deezer.com/*
32 | // @match *://*.qobuz.com/*
33 | // @match *://*.trackitdown.net/*
34 | // @match *://*.45cat.com/*
35 | // @match *://*.amazon.com/*
36 | // @match *://*.amazon.co.uk/*
37 | // @match *://*.amazon.ca/*
38 | // @match *://*.amazon.co.jp/*
39 | // @match *://*.amazon.com.au/*
40 | // @match *://*.amazon.com.br/*
41 | // @match *://*.amazon.com.mx/*
42 | // @match *://*.amazon.com.sa/*
43 | // @match *://*.amazon.cn/*
44 | // @match *://*.amazon.de/*
45 | // @match *://*.amazon.es/*
46 | // @match *://*.amazon.fr/*
47 | // @match *://*.amazon.in/*
48 | // @match *://*.amazon.it/*
49 | // @match *://*.amazon.nl/*
50 | // @match *://*.amazon.pk/*
51 | // @match *://*.cdbaby.com/*
52 | // @match *://*.jamendo.com/*
53 | // @match *://*.magnatune.com/*
54 | // @match *://open.spotify.com/*
55 | // @grant GM_registerMenuCommand
56 | // @grant GM.registerMenuCommand
57 | // @require https://greasyfork.org/scripts/34527/code/GMCommonAPI.js?version=237846
58 | // @noframes
59 | // ==/UserScript==
60 |
61 |
62 | /*
63 | * Stig's Art Grabr is an userscript and/or bookmarklet for grabbing big high resolution
64 | * album cover-art from various sites.
65 | *
66 | * https://greasyfork.org/scripts/20771-stig-s-art-grabr
67 | * https://github.com/StigNygaard/Stigs_Art_Grabr
68 | *
69 | * Partly based on tips at http://wiki.musicbrainz.org/User:Nikki/CAA and on itunes tip
70 | * from MusicBrainz/GitHub/GreasyFork user jesus2099 who has made a lot of userscripts
71 | * (especially for MusicBrainz users): https://greasyfork.org/users/2206-jesus2099
72 | *
73 | * To run this script as a bookmarklet (running latest GreasyFork hosted version), use:
74 | * javascript:(function(){document.body.appendChild(document.createElement("script")).src="https://greasyfork.org/scripts/20771/code/StigsArtGrabr.js?t="+Date.now();}())
75 | *
76 | * NOTICE: On Apple Music (iTunes), with most browsers Stig's Art Grabr will only work when
77 | * used as a userscript, and NOT when used as a bookmarklet (CSP restriction).
78 | */
79 |
80 | // CHANGELOG - The most important updates/versions:
81 | let changelog = [
82 | {version: '2025.04.17.0', description: 'Bigger artwork sizes from open.spotify.com. Thanks kopytko95.'},
83 | {version: '2024.01.07.0', description: 'Fix for yesterdays updated apple music support - now handling more original image formats.'},
84 | {version: '2024.01.06.0', description: 'Should get the actual original upload now on Apple Music/iTunes? Thanks for the help and insisting at https://github.com/StigNygaard/Stigs_Art_Grabr/issues/9 ;-)'},
85 | {version: '2021.05.09.0', description: 'Generally substitute found ".webp" files with similar named ".jpg" files (assumed available as "fallback"). This currently affects and works for Apple Music/iTunes and Last.FM.'},
86 | {version: '2021.01.29.0', description: 'Support for the new native menus (GM.registerMenuCommand) in Greasemonkey 4.11.'},
87 | {version: '2020.12.28.0', description: 'Yet another iTunes/Apple Music fix. Musicbrainz changelog appended to pages fix.'},
88 | {version: '2020.07.02.0', description: 'Another iTunes/Apple Music fix.'},
89 | {version: '2020.05.30.1', description: 'Adding partial support for open.spotify.com. On album-pages (might not work on all playlists) it can typically replace 232X232 or 464x464 with 640x640pixels cover art. Thanks to kopytko95 for tip making this possible.'},
90 | {version: '2020.04.25.0', description: 'iTunes / Apple Music fix for updated site.'},
91 | {version: '2019.11.03.0', description: 'Last.FM fix. Mouseover should work again now.'},
92 | {version: '2019.10.26.0', description: 'Last.FM partial fix. Now again able to find fullsize images. But mouseover with dimensions might not show and sometimes image is "protected" behind a layer.'},
93 | {version: '2018.02.10.0', description: 'Adding support for Deezer, Qobuz and Trackitdown (All tested on public pages only). Big thanks to Anton Fedorov for tips making this possible.'},
94 | {version: '2016.06.20.0', description: '1st official release version.'}
95 | ];
96 |
97 | function runGrabr() {
98 | let DEBUG = false;
99 | let log = function(s) {
100 | if (DEBUG && window.console) {
101 | window.console.log(s);
102 | }
103 | };
104 | // [ page pattern, search for img patterns, replace this, with this ]
105 | let a = [[/45cat\./, /-s\.jpg/i, /-s\.jpg/gi, ".jpg"],
106 | [/45cat\./, /-s\.png/i, /-s\.png/gi, ".png"],
107 | [/allmusic\./, /\/JPG_\d{3}\//i, /\/JPG_\d{3}\//gi, "/JPG_1080/"],
108 | [/amazon\./, /\._[A-Z]{2}\d{3}_[\w_,-]*\.jpg/i, /\._[A-Z]{2}\d{3}_[\w_,-]*\.jpg/gi, ".jpg"],
109 | [/amazon\./, /\._[A-Z]{2}\d{3}_[\w_,-]*\.png/i, /\._[A-Z]{2}\d{3}_[\w_,-]*\.png/gi, ".png"],
110 | [/bandcamp\./, /_\d{1,2}\.jpg/i, /_\d{1,2}\.jpg/gi, "_0.jpg"],
111 | [/bandcamp\./, /_\d{1,2}\.png/i, /_\d{1,2}\.png/gi, "_0.png"],
112 | [/cdbaby\./, /cdbaby\.name\/.*_small\.[jpgn]{3}/i, /_small\./gi, "."],
113 | [/cdbaby\./, /cdbaby\.name\/.*\.jpg/i, /\.jpg/gi, "_large.jpg"],
114 | [/cdbaby\./, /cdbaby\.name\/.*\.png/i, /\.png/gi, "_large.png"],
115 | [/deezer\./, /images\/\w{5,9}\/.*\.[jpng]{3}/i, /\/\d{2,3}x\d{2,3}-0{6}-\d{1,2}-0-0\.[jpng]{3}/gi, "/1400x1400-000000-0-0-0.png"],
116 | [/fnd\.io/, /\/\d{2,}x\d{2,}bb/i, /\/\d{2,}x\d{2,}bb/gi, "/999999x999999bb-100"],
117 | [/(music|itunes)\.apple\./, /^https:\/\/[a-z0-9/.-]+\/image\/thumb\/[a-z0-9/.-]+\.(jpg|jpeg|webp|png|avif|jxl|heif|heic)\/[a-z0-9-]+\.[jpegwbavifxl]{3,4}$/i, /^https:\/\/[a-z0-9/.-]+\/image\/thumb\/([a-z0-9/.-]+\.[jpgwebnavifxlhc]{3,4})\/[a-z0-9-]+\.[jpegwbavifxl]{3,4}$/gi, "https://a1.mzstatic.com/us/r1000/0/$1"], // fix 2024
118 | [/jamendo\./, /1\.\d00\.jpg/i, /1\.\d00\.jpg/gi, "1.0.jpg"],
119 | [/jamendo\./, /1\.\d00\.png/i, /1\.\d00\.png/gi, "1.0.png"],
120 | [/labs\.stephenou\.com/, /\/\d{2,3}x\d{2,3}bb/i, /\/\d{2,3}x\d{2,3}bb/gi, "/999999x999999bb-100"],
121 | [/last(fm)?\.[a-z]{2,3}/, /\.net\/i\/u\/[a-zA-Z]*\d{2,}\w*\//i, /\.net\/i\/u\/[a-z]*\d{2,}\w*\//gi, ".net/i/u/"], // fix 2019-10-26
122 | [/magnatune\./, /cover_\d{2,3}\./i, /cover_\d{2,3}\./gi, "cover."],
123 | [/musicbrainz\.org/, /_thumb\d{3}\./i, /_thumb\d{3}\./gi, "."],
124 | [/musicbrainz\.org/, /-\d{3}\.jpg/i, /-\d{3}\.jpg/gi, ".jpg"],
125 | [/musicbrainz\.org/, /-\d{3}\.png/i, /-\d{3}\.png/gi, ".png"],
126 | [/musicdiner\./, /\/\d{2,3}x\d{2,3}bb/i, /\/\d{2,3}x\d{2,3}bb/gi, "/999999x999999bb-100"],
127 | [/play\.google\.com/, /googleusercontent\.com.*\=w\d{3}/i, /\=w\d{3}$/gi, "=w1200"],
128 | [/qobuz\.com/, /static\.qobuz\.com\/images\/covers\//i, /_\d{2,3}\.jpg/gi, "_max.jpg"],
129 | [/trackitdown\.net/, /\.cloudfront.net\/graphics\//i, /__\w+\.png/gi, "_original.jpg"],
130 | [/soundcloud\./, /t\d\d0x\d\d0\./i, /t\d\d0x\d\d0\./gi, "original."],
131 | [/open\.spotify\.com/, /i\.scdn\.co\/image\/ab67616d0000/i, /ab67616d00001e02/gi, "ab67616d000082c1"]];
132 | /* https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/ */
133 | let aEv = function(e,ev,f,c) {
134 | c = c || false;
135 | if (e.addEventListener) {
136 | e.addEventListener(ev, f, c);
137 | } else if (e.attachEvent) {
138 | e.attachEvent("on" + ev, f);
139 | } else {
140 | e["on" + ev] = f;
141 | }
142 | };
143 | let w = null, n = 0, m = 20, d = document, i = 0;
144 |
145 | // General preburner
146 | let pictures = document.querySelectorAll("picture");
147 | const PURL = /^[^\s,]+/im;
148 | pictures.forEach(
149 | function(picture, idx) {
150 | let img = picture.querySelector("img");
151 | let source = picture.querySelector("source");
152 | if (img) {
153 | // img.removeAttribute("loading");
154 | let currentSrc = img.currentSrc;
155 | if (source?.srcset) {
156 | if (!currentSrc || currentSrc.endsWith("1x1.gif") || currentSrc.endsWith("MissingArtworkMusic.svg")) {
157 | let url = source.srcset.match(PURL);
158 | if (url) {
159 | currentSrc = url[0];
160 | }
161 | }
162 | }
163 | picture.replaceWith(img);
164 | if (currentSrc && !(currentSrc.endsWith("1x1.gif") || currentSrc.endsWith("MissingArtworkMusic.svg"))) {
165 | img.src = currentSrc;
166 | img.removeAttribute("loading"); // I don't really understand, but removing loading-attribut at this point seems to work better than doing it earlier?
167 | }
168 | }
169 | }
170 | );
171 |
172 | // soundcloud pre-burner
173 | if (d.location.hostname.search(/soundcloud\./) > -1) {
174 | let spans = document.querySelectorAll("span[style*=background-image]");
175 | for (const span of spans) {
176 | if (span.style.backgroundImage) {
177 | let imgsrc = span.style.backgroundImage.match(/url[\(\"\u0027]+([^\"\u0027\)]*)[\)\"\u0027]+/)[1];
178 | if ((span.getElementsByTagName("img").length === 0) && (imgsrc.match(/t[\d]{3}x[\d]{3}\./) !== null)) {
179 | span.innerHTML = "";
180 | }
181 | }
182 | }
183 | }
184 | // itunes/apple music pre-burner
185 | if (d.location.hostname.search(/\.apple\./) > -1) {
186 | let overlays = document.querySelectorAll(".artwork-overlay, .lockup__controls, .lockup__contextual-menu-trigger");
187 | for (const overlay of overlays) {
188 | overlay.parentNode.removeChild(overlay);
189 | }
190 | }
191 | // deezer pre-burner
192 | if (d.location.hostname.search(/deezer\.com/) > -1) {
193 | let pics = document.querySelectorAll("figure.thumbnail>div.picture");
194 | for (const pic of pics) {
195 | pic.classList.remove('picture');
196 | }
197 | }
198 | // last.fm pre-burner
199 | if (d.location.hostname.search(/last(fm)?\.[a-z]{2,3}/) > -1) {
200 | let elms = document.querySelectorAll(('.album-overview-cover-art-actions'));
201 | for (const elm of elms) {
202 | elm.parentNode.removeChild(elm);
203 | }
204 | let imgs = document.querySelectorAll(('a.cover-art img, a.image-list-item img'));
205 | for (const img of imgs) {
206 | img.style.maxWidth = "370px";
207 | img.style.maxHeight = "370px";
208 | img.parentNode.parentNode.replaceChild(img, img.parentNode); // (newchild, oldchild)
209 | }
210 | }
211 |
212 | log('Activated while on ' + d.location.hostname);
213 | o:
214 | for (const patterns of a) {
215 | if (d.location.hostname.search(patterns[0]) > -1) {
216 | log('Running on ' + d.location.hostname);
217 | w = patterns;
218 | let l = d.getElementsByTagName("img");
219 | if (l) {
220 | log('Found ' + l.length + ' image tags');
221 | for (const e of l) {
222 | // log(' - ' + e.currentSrc + ' . Includes ' + w[1] + '?: ' + ((e.currentSrc).search(w[1]) > -1) );
223 | if ((e.currentSrc).search(w[1]) > -1) {
224 | e.style.border = "1px #FB0 solid";
225 | if (e.naturalWidth) {
226 | // e.title = "just testing"; // adding dimemsions later on onload image?
227 | // e.parentNode.title = "just testing parent"; // adding dimemsions later on onload image?
228 | e.onmouseover = function () { // onmouseover via w3 metode. Eller på niveauet over og tage dimension på første img child???
229 | this.setAttribute("title", String(this.naturalWidth) + "x" + this.naturalHeight);
230 | this.setAttribute("data-title", String(this.naturalWidth) + "x" + this.naturalHeight);
231 | this.setAttribute("data-tooltip", String(this.naturalWidth) + "x" + this.naturalHeight);
232 | };
233 | }
234 | aEv(e, "load", function () {
235 | if (this.style) {
236 | this.style.borderColor = "#F00";
237 | if (this.naturalWidth && this.naturalWidth > 999) {
238 | this.style.borderWidth = "2px";
239 | }
240 | }
241 | // Sæt titles HER istedet !!!
242 | // this.title = "Done loading: " + String(this.naturalWidth) + "x" + this.naturalHeight;
243 | // this.parentNode.title = "Done loading: " + String(this.naturalWidth) + "x" + this.naturalHeight;
244 | });
245 | aEv(e, "click", function () {
246 | if (this.currentSrc) {
247 | window.location = this.currentSrc;
248 | }
249 | });
250 | // Lets take the chance and do a general substitution of .webp for .jpg on all sites supported!
251 | e.src = e.currentSrc.replace(w[2], w[3]).replace(/\.webp$/i, ".jpg");
252 | if (e.srcset) {
253 | e.removeAttribute('srcset')
254 | }
255 | n++;
256 | if (n === m) {
257 | if (confirm(String(n) + " images requested. Continue?")) {
258 | m = m + 20;
259 | } else {
260 | break o;
261 | }
262 | }
263 | }
264 | }
265 | }
266 | }
267 | }
268 | if (w === null) {
269 | log('No page pattern hits found...');
270 | }
271 | return void(0);
272 | }
273 |
274 | function createRichElement(tagName, attributes, ...content) {
275 | let element = document.createElement(tagName);
276 | if (attributes) {
277 | for (const [attr, value] of Object.entries(attributes)) {
278 | element.setAttribute(attr, value);
279 | }
280 | }
281 | if (content?.length) {
282 | element.append(...content);
283 | }
284 | return element;
285 | }
286 |
287 | function showGrabrLog() {
288 | document.getElementById('grabrlog').style.display = 'block';
289 | }
290 |
291 | if (typeof GM_info === 'object' || (typeof GM === 'object' && typeof GM.info === 'object')) {
292 | // Running as a userscript - setting up menu items...
293 | if (!document.getElementById('grabrlog')) {
294 | let gmwe = createRichElement("div", {id: "grabrlog"}, createRichElement("b", {}, "Stig's Art Grabr changelog"), document.createElement("ul"));
295 | gmwe.style.position = "fixed";
296 | gmwe.style.left = "0";
297 | gmwe.style.right = "0";
298 | gmwe.style.top = "10em";
299 | gmwe.style.zIndex = "3000009";
300 | gmwe.style.marginLeft = "auto";
301 | gmwe.style.marginRight = "auto";
302 | gmwe.style.minHeight = "8em";
303 | gmwe.style.width = "50%";
304 | gmwe.style.backgroundColor = "#eee";
305 | gmwe.style.color = "#111";
306 | gmwe.style.borderRadius = "5px";
307 | gmwe.style.display = "none";
308 | gmwe.style.padding = "1em";
309 | document.body.insertAdjacentElement("beforeend", gmwe);
310 | document.getElementById('grabrlog').addEventListener('click', function() {
311 | this.style.display = 'none';
312 | return false;
313 | }, false);
314 | let list = document.querySelector('div#grabrlog ul');
315 | let lcontent = '';
316 | for (let i = 0; i < Math.min(8, changelog.length); i++) {
317 | lcontent += '