Last active
July 11, 2023 13:52
-
-
Save geekman/c0690693a85fdb20383edfa12394a880 to your computer and use it in GitHub Desktop.
WSH script to perform string replacement on iTunes Library media file paths
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// WSH script to replace all paths in iTunes after moving media files. | |
// iTunes will prompt you to locate missing files, but you need to do them | |
// one by one, which is not humanly feasible. | |
// | |
// run using: cscript.exe itunes-migrate-files.js | |
// | |
// 2022.04.23 darell tan | |
// | |
var iTunes = WScript.CreateObject("iTunes.Application"); | |
var wsh = WScript.CreateObject("WScript.Shell"); | |
function hexstr(n) { | |
var h = '00000000' + (n < 0 ? 0xFFFFFFFF + n + 1 : n).toString(16).toUpperCase(); | |
return h.substr(h.length - 8); | |
} | |
function getPersistentID(t) { | |
var h = iTunes.ITObjectPersistentIDHigh(t), | |
l = iTunes.ITObjectPersistentIDLow(t); | |
return hexstr(h) + hexstr(l); | |
} | |
function getDictValue(dictElem, key) { | |
var k = dictElem.selectSingleNode(".//key[.='" + key + "']"); | |
if (k) { | |
return k.nextSibling.text; | |
} | |
return false; | |
} | |
function getLibraryFiles(xmlFile) { | |
var doc = WScript.CreateObject("Msxml2.DOMDocument.6.0"); | |
doc.resolveExternals = false; | |
doc.setProperty("ProhibitDTD", false); | |
doc.setProperty("ValidateOnParse", false); | |
doc.load(xmlFile); | |
if (doc.parseError.errorCode != 0) { | |
WScript.Echo("Library XML parse error: " + doc.parseError.reason); | |
return false; | |
} | |
var library = {}; | |
var xmlTracks = doc.selectNodes("//dict[key='Location']"); | |
for (var i = 0; i < xmlTracks.length; i++) { | |
var t = xmlTracks.item(i); | |
// remove prefix from Location | |
var path = getDictValue(t, "Location"); | |
var prefix = "file://localhost/"; | |
if (path.indexOf(prefix) == 0) { | |
path = path.substr(prefix.length); | |
// if it starts with slash, likely a UNC path | |
if (path.substr(0, 1) == '/') path = '/' + path; | |
path = decodeURIComponent(path); | |
} | |
var id = getDictValue(t, "Persistent ID"); | |
if (id.length == 0) { | |
WScript.Echo("persistent ID not found"); | |
return false; | |
} | |
//WScript.Echo("id " + id + " " + path); | |
library[id] = path; | |
} | |
return library; | |
} | |
////////////////////////////////////////////////////////////////////// | |
// you need to export your iTunes Library into an XML first | |
// because iTunes COM will return an empty path if the file is not found | |
// it is still in the database however, and can be extracted from the XML file | |
var lib = getLibraryFiles(wsh.ExpandEnvironmentStrings("%USERPROFILE%\\Music\\iTunes\\Library.xml")); | |
if (lib === false) { | |
WScript.Echo("cannot load iTunes Library XML"); | |
WScript.Quit(); | |
} | |
var tracks = iTunes.LibraryPlaylist.Tracks; | |
WScript.Echo("total tracks: " + tracks.Count); | |
// modify the replacement regex and string here | |
var replaceRE = new RegExp('^//nas-1/', 'i'); | |
var replaceStr = '//nas-2/'; | |
for (var i = 1; i <= tracks.Count; i++) { | |
var t = tracks.Item(i); | |
if (t.Kind != 1) continue; // process only file tracks | |
// skip voice memos | |
if (t.Genre == "Voice Memo") continue; | |
var id = getPersistentID(t); | |
var filePath = t.Location || lib[id]; | |
// if track is not found | |
if (t.Location == "") { | |
WScript.Echo(t.Artist + ", " + t.Name + " " + getPersistentID(t) + " " + filePath); | |
// do the replacement here | |
var newPath = filePath.replace(replaceRE, replaceStr); | |
WScript.Echo(newPath); | |
// this will fail if the path doesn't exist | |
if (newPath != filePath) { | |
t.Location = newPath; | |
} | |
//break; | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// WSH script to remove iTunes tracks with paths that match a regexp. | |
// These are karaoke/instrumental tracks that should not be played (normally). | |
// | |
// run using: cscript.exe itunes-remove-instrumentals.js | |
// | |
// 2023.07.11 darell tan | |
// | |
var iTunes = WScript.CreateObject("iTunes.Application"); | |
var wsh = WScript.CreateObject("WScript.Shell"); | |
function hexstr(n) { | |
var h = '00000000' + (n < 0 ? 0xFFFFFFFF + n + 1 : n).toString(16).toUpperCase(); | |
return h.substr(h.length - 8); | |
} | |
function getPersistentID(t) { | |
var h = iTunes.ITObjectPersistentIDHigh(t), | |
l = iTunes.ITObjectPersistentIDLow(t); | |
return hexstr(h) + hexstr(l); | |
} | |
var tracks = iTunes.LibraryPlaylist.Tracks; | |
WScript.Echo("total tracks: " + tracks.Count); | |
// checks for "Instrumental" in path | |
var pathRE = new RegExp('[/\\\\]instrumental[/\\\\]', 'i'); | |
var matchingTracks = {}; | |
var count = 0; | |
for (var i = 1; i <= tracks.Count; i++) { | |
var t = tracks.Item(i); | |
if (t.Kind != 1) continue; // process only file tracks | |
// skip voice memos | |
if (t.Genre == "Voice Memo") continue; | |
var id = getPersistentID(t); | |
// if track is not found | |
if (t.Location != "" && t.Location.match(pathRE)) { | |
WScript.Echo(t.Artist + ", " + t.Name + " " + getPersistentID(t) + " " + t.Location); | |
// queue up for deletion later | |
// note that the track object can't be reliably operated on, so use the ID | |
matchingTracks[id] = 1; | |
count++; | |
} | |
} | |
WScript.Echo('found ' + count + ' matching tracks.'); | |
//WScript.Quit(0); | |
WScript.Echo('deleting now...'); | |
// second sweep to do the deletion | |
// it's not very convenient to retrieve by some kind of persistent ID | |
// and multiple rounds are required because the tracks will shift around | |
while (count > 0) { | |
for (var i = 1; i <= tracks.Count; i++) { | |
var t = tracks.Item(i); | |
if (t.Kind != 1) continue; // process only file tracks | |
var id = getPersistentID(t); | |
if (matchingTracks[id] === 1) { | |
//WScript.Echo(t.Artist + ", " + t.Name + " " + getPersistentID(t) + " " + t.Location); | |
delete(matchingTracks[id]); | |
t.Delete(); | |
count--; | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment