I’ve been doing random MP3 metadata work lately. Here’s some code which others might find useful.
Extracting MP3 tags from mp3 file hosted on server using HTTP Range queries.
So I was using Apache Tika for various metadata stuff. I wanted to get the song title for a file hosted on a server, but Tika only supports MP3 ID3v1 metadata, which exists at the end of a file. Downloading an entire MP3 just for the title is wasteful, but fortunatly HTTP Range queries can help us out.
HttpClient httpClient = new HttpClient(); httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(10000); httpClient.getHttpConnectionManager().getParams().setSoTimeout(10000); String address = "http://address of mp3 file here"; HttpMethod method = new HeadMethod(); method.setURI(new URI(address,true)); Header contentLengthHeader = null; Header acceptHeader = null; httpClient.executeMethod(method); try { //System.out.println(Arrays.toString(method.getResponseHeaders())); contentLengthHeader = method.getResponseHeader("Content-Length"); acceptHeader = method.getResponseHeader("Accept-Ranges"); } finally { method.releaseConnection(); } if ((contentLengthHeader != null) && (acceptHeader != null) && "bytes".equals(acceptHeader.getValue())) { long contentLength = Long.parseLong(contentLengthHeader.getValue()); long metaDataStartRange = contentLength - 128; if (metaDataStartRange > 0) { method = new GetMethod(); method.setURI(new URI(address,true)); method.addRequestHeader("Range", "bytes=" + metaDataStartRange + "-" + contentLength); System.out.println(Arrays.toString(method.getRequestHeaders())); httpClient.executeMethod(method); try { Parser parser = new AutoDetectParser(); Metadata metadata = new Metadata(); metadata.set(Metadata.RESOURCE_NAME_KEY, address); InputStream stream = method.getResponseBodyAsStream(); try { parser.parse(stream, new DefaultHandler(), metadata); } catch (Exception e) { e.printStackTrace(); } finally { stream.close(); } System.out.println(Arrays.toString(metadata.names())); System.out.println("Title: " + metadata.get("title")); System.out.println("Author: " + metadata.get("Author")); } finally { method.releaseConnection(); } } } else { System.err.println("Range not supported. Headers were: "); System.err.println(Arrays.toString(method.getResponseHeaders())); }
The next thing I needed to do was extract song titles from a shoutcast stream. Shoutcast streams are kinda-but-not-quite http. Metadata is embedded in the stream (not as part of the MP3). That makes the code pretty ugly, but whatever… This code will open a connection, read the metadata and close, so you don’t need to keep downloading gigs of data.
URL url = new URL("http://scfire-ntc-aa01.stream.aol.com:80/stream/1074"); URLConnection con = url.openConnection(); con.setRequestProperty("Icy-MetaData", "1"); InputStream stream = con.getInputStream(); try { BufferedReader in = new BufferedReader(new InputStreamReader(stream)); String metaIntervalString = null; // get the headers StringBuilder headers = new StringBuilder(); char c; while ((c = (char)in.read()) != -1) { headers.append(c); if (headers.length() > 5 && (headers.substring((headers.length() - 4), headers.length()).equals("\r\n\r\n"))) { // end of headers break; } } //System.out.println(headers); // headers look like this: // ICY 200 OK // icy-notice1:
This stream requires Winamp
// icy-notice2: Firehose Ultravox/SHOUTcast Relay Server/Linux v2.6.0
// icy-name: .977 The 80s Channel // icy-genre: 80s Pop Rock // icy-url: http://www.977music.com // content-type: audio/mpeg // icy-pub: 1 // icy-metaint: 16384 // icy-br: 128 Pattern p = Pattern.compile("\\r\\n(icy-metaint):\\s*(.*)\\r\\n"); Matcher m = p.matcher(headers.toString()); if (m.find()) { metaIntervalString = m.group(2); } if (metaIntervalString != null) { int metaInterval = Integer.parseInt(metaIntervalString.trim()); if (metaInterval > 0) { int b; int count = 0; int metaDataLength = 4080; // 4080 is the max length boolean inData = false; StringBuilder metaData = new StringBuilder(); while ((b = stream.read()) != -1) { count++; if (count == metaInterval + 1) { metaDataLength = b * 16; } if (count > metaInterval + 1 && count < (metaInterval + metaDataLength)) { inData = true; } else { inData = false; } if (inData) { if (b != 0) { metaData.append((char)b); } } if (count > (metaInterval + metaDataLength)) { break; } } String metaDataString = metaData.toString(); System.out.println(metaDataString); } } } finally { stream.close(); }
Wow, exactly what i searched for !
I have some question about the integration into a web page, could you help me ?
Thanks
ok, let it be known, i’m new to java. i think the code above does exactly what i’ve been trying to implement: grabbing metadata info off an online mp3 stream (song name, station, etc). problem is, i’m not too sure how to actually use the code above… assuming that is straight java, does it require some external class file or similar? if so, can i get a copy of that from you?
thanx
Thank you for your article, was very util for me !!
I did changed this for get the icy-metaint header.
final static public String BITERATE_HEADER = “icy-metaint”;
String metaInterval = con.getHeaderField(BITERATE_HEADER);
instead
StringBuilder headers = new StringBuilder();
char c;
while ((c = (char)in.read()) != -1) {
headers.append(c);
if (headers.length() > 5 && (headers.substring((headers.length() – 4), headers.length()).equals(“\r\n\r\n”))) {
// end of headers
break;
}
}
//System.out.println(headers);
// headers look like this:
// ICY 200 OK
// icy-notice1:
This stream requires Winamp
// icy-notice2: Firehose Ultravox/SHOUTcast Relay Server/Linux v2.6.0
// icy-name: .977 The 80s Channel
// icy-genre: 80s Pop Rock
// icy-url: http://www.977music.com
// content-type: audio/mpeg
// icy-pub: 1
// icy-metaint: 16384
// icy-br: 128
Pattern p = Pattern.compile(“\\r\\n(icy-metaint):\\s*(.*)\\r\\n”);
Matcher m = p.matcher(headers.toString());
if (m.find()) {
metaIntervalString = m.group(2);
}
Thanks a Lot !!