├── 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 = "\u0027\u0027"; 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 += '
  • ' + changelog[i].version + ' - ' + changelog[i].description + '
  • '; 318 | } 319 | list.insertAdjacentHTML('beforeend', lcontent); 320 | } 321 | GMC.registerMenuCommand("Search big size cover art", runGrabr, "a"); 322 | GMC.registerMenuCommand("Changelog", showGrabrLog, "l"); 323 | } else { 324 | // Started from bookmarklet! 325 | runGrabr(); 326 | } 327 | --------------------------------------------------------------------------------