PDA

View Full Version : For MP3 HU owners: Java program for parsing iTunes library



chylld
14-11-2006, 08:33 PM
Hi all

This is a little program i wrote that parses the iTunes library and copies all 4-star and 5-star songs out into another folder, sorted into further folders for each artist. It also renames the copied files so that they don't include characters which error (underscore) on our HU displays, like ?, *, & etc.

I wrote this because the default itunes way to burn mp3 cd's is either flat or double-nested by artist then album, which i felt didn't work well with my 06 civic HU which only has prev/next folder and prev/next track.

If you burn the resulting folder structure to cdr (or better, cdrw) you can (if ur HU operates similar to the one on my 06 civic) then use the "folder" knob to scroll through artists, and then the prev/next track buttons on the HU or on the steering wheel to scroll through songs for that artist. just seems to make better sense to me than going flat or scrolling to the "unknown album" folder all the time :)

feel free to use and/or modify the code at your own will; i trust if you know how to compile and run it, then you'll know how to change it! i've highlighted the 2 bits you'll probably need to change though.



package crumpet;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Crumpet {
public static void main(String[] args) throws Exception {
new Crumpet();
}

public Crumpet() throws Exception {
parseXML("C:\\Documents and Settings\\username\\My Documents\\"+
"My Music\\iTunes\\iTunes Music Library.xml");
}

private void parseXML(String xmlFile) throws Exception {
parseTracks(getElementNodeByKey(
DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse(new File(xmlFile))
.getDocumentElement()
.getElementsByTagName("dict")
.item(0),"Tracks"));
}

private void parseTracks(Node tracks) throws Exception {
NodeList children = tracks.getChildNodes();

for(int i=0;i<children.getLength();i++) {
Node n = children.item(i);
if(n.getNodeName().equals("dict")) {
parseTrack(n);
}
}
}

private void parseTrack(Node track) throws Exception {
String artist = getTextValue(getElementNodeByKey(track,"Artist"));
String name = getTextValue(getElementNodeByKey(track,"Name"));
String location = getTextValue(getElementNodeByKey(track,"Location"));
String rating = getTextValue(getElementNodeByKey(track,"Rating"));

if(rating.equals("80") || rating.equals("100")) {
String dest = "Y:\\songs\\"+artist+"\\"+name+".mp3";
fileCopy(URLDecoder.decode(location.substring(17),"UTF-8"),fileSafe(dest));
}
}

private Node getElementNodeByKey(Node node, String key) {
Node ret = null;
NodeList children = node.getChildNodes();

for(int i=0;i<children.getLength();i++) {
Node n = children.item(i);
if(n.getNodeName().equals("key") && getTextValue(n).equals(key)) {
ret = n.getNextSibling();
while(ret.getNodeType()!=Node.ELEMENT_NODE) {
ret = ret.getNextSibling();
}
}
}

return ret;
}

private String getTextValue(Node n) {
String ret = "";

if(n!=null) {
NodeList children = n.getChildNodes();
for(int i=0;i<children.getLength();i++) {
if(children.item(i).getNodeType()==Node.TEXT_NODE) {
ret += children.item(i).getNodeValue().trim();
}
}
}

return ret;
}

private void fileCopy(String src, String dest) throws Exception {
File fSrc = new File(src);
File fDest = new File(dest);

System.out.println(src+" => "+dest);

new File(dest.substring(0,dest.lastIndexOf("\\"))).mkdirs();

FileChannel in = null, out = null;

in = new FileInputStream(fSrc).getChannel();
out = new FileOutputStream(fDest).getChannel();

long size = in.size();
MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size);

out.write(buf);

in.close();
out.close();
}

private String fileSafe(String name) {
return name.replace("?","")
.replace("*","-")
.replace("(","")
.replace(")","")
.replace("'","")
.replace(",","")
.replace("&","-");
}
}

JaCe
15-11-2006, 07:48 PM
What do you mean iTunes burns double nested? For me.. I thought iTunes only burns it all under the one folder. It's important for me though because since I have alot of Chinese music using Unicode tags, iTunes automatically renames these so that it can be read in a standard MP3 reading cd player- and in order.

Btw, kudos for creating something useful though :)

chylld
15-11-2006, 07:56 PM
well for me, itunes either burns cd's like this:

<cd>
__song1.mp3
__song2.mp3
__song3.mp3

or this:

<cd>
__<artist 1>
____<album 1>
______song 1.mp3
____<unknown album>
______song 2.mp3

etc.

the problem with the first way is that the folder knob is useless as there are no folders... the second way it's hard/impossible to tell which artist you're scrolling to, since there are no songs directly under the <artist 1> folder so it skips over that and instead turning the folder knob just shows you "album 1" and "unknown album"

what the program does is copy your songs out to the following structure:

<cd>
__<artist 1>
____song 1.mp3
____song 2.mp3
__<artist 2>
____song 10.mp3

that way turning the folder knob shows you "artist 1" and "artist 2", and once you've found the artist you wanna listen to you can use the next/prev track buttons to navigate the songs from there.

JaCe
15-11-2006, 09:55 PM
Ahh what you're talking about is what I find myself doing manually- usually songs from the one artist, there won't be that many artists so I just make a folder manually and put 'em in there... then, I copy and paste (using iTunes, right click COPY) into another folder and then manually use Nero to burn it.

How did you get iTunes to burn like this:
<cd>
__<artist 1>
____<album 1>
______song 1.mp3
____<unknown album>
______song 2.mp3

etc.