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(); }