diff --git a/projects/robodj/src/java/robodj/convert/CDParanoiaRipper.java b/projects/robodj/src/java/robodj/convert/CDParanoiaRipper.java new file mode 100644 index 00000000..eade5e1e --- /dev/null +++ b/projects/robodj/src/java/robodj/convert/CDParanoiaRipper.java @@ -0,0 +1,107 @@ +// +// $Id: CDParanoiaRipper.java,v 1.1 2000/10/30 21:08:53 mdb Exp $ + +package robodj.rip; + +import java.io.*; +import java.util.ArrayList; + +import gnu.regexp.*; + +/** + * A ripper implementation that uses cdparanoia to do it's job. + */ +public class CDParanoiaRipper implements Ripper +{ + public TrackInfo[] getTrackInfo () throws RipException + { + try { + // fork off a cdparanoia process to read the TOC + Runtime rt = Runtime.getRuntime(); + Process tocproc = rt.exec("cdparanoia -Q"); + + InputStream in = tocproc.getErrorStream(); + BufferedInputStream bin = new BufferedInputStream(in); + DataInputStream din = new DataInputStream(bin); + + String inline; + StringBuffer input = new StringBuffer(); + ArrayList flist = new ArrayList(); + + // an input line that we're interested in looks something like + // this: + // + // 1. 17980 [03:59.55] 0 [00:00.00] no no 2 + RE regex = + new RE("^\\s*\\d+\\.\\s+(\\d+)\\s\\[\\S*\\]\\s+(\\d+)"); + + while ((inline = din.readLine()) != null) { + // skip blank lines and lines that are in the header + if (inline.trim().length() == 0 || + inline.indexOf("mit.edu") != -1 || + inline.indexOf("cdparanoia") == 0) { + continue; + } + + // System.out.println("Matching: " + inline); + + // keep track of all of the input in case we need to + // report an error later + input.append(inline).append("\n"); + + // see if we match our regular expression + REMatch match = regex.getMatch(inline); + if (match != null) { + flist.add(match.toString(1)); + flist.add(match.toString(2)); + } + } + + // check the return value of the process + try { + int retval = tocproc.waitFor(); + if (retval != 0) { + // ship off the error output from cdparanoia + throw new RipException(input.toString()); + } + + } catch (InterruptedException ie) { + // why we were interrupted I can only speculate, but we'll + // go ahead and freak out anyway + throw new RipException("Interrupted while waiting for " + + "cdparanoia process to exit."); + } + + // parse the frame offsets and stick them in an array + TrackInfo[] frames = new TrackInfo[flist.size()/2]; + for (int i = 0; i < frames.length; i++) { + try { + frames[i] = new TrackInfo(); + frames[i].length = + Integer.parseInt((String)flist.get(2*i)); + frames[i].offset = + Integer.parseInt((String)flist.get(2*i+1)); + + // for some reason, cdparanoia reports track offsets + // starting from zero but CDDB assumes they start at 2 + // seconds, so we have to adjust... sigh. + frames[i].offset += 2 * RipUtil.FRAMES_PER_SECOND; + + } catch (NumberFormatException nfe) { + throw new RipException("Bogus frame value for track " + + (i+1) + ": " + nfe.getMessage() + + "\n\n" + input); + } + } + + return frames; + + } catch (REException ree) { + throw new RipException("Can't compile regular expression?! " + + ree); + + } catch (IOException ioe) { + throw new RipException("Error talking to rip process: " + ioe); + } + } +} diff --git a/projects/robodj/src/java/robodj/convert/Makefile b/projects/robodj/src/java/robodj/convert/Makefile new file mode 100644 index 00000000..4cb6a541 --- /dev/null +++ b/projects/robodj/src/java/robodj/convert/Makefile @@ -0,0 +1,13 @@ +# +# $Id: Makefile,v 1.1 2000/10/30 21:08:53 mdb Exp $ + +ROOT = ../.. + +SRCS = \ + CDParanoiaRipper.java \ + RipException.java \ + RipTest.java \ + RipUtil.java \ + Ripper.java \ + +include $(ROOT)/build/Makefile.java diff --git a/projects/robodj/src/java/robodj/convert/RipException.java b/projects/robodj/src/java/robodj/convert/RipException.java new file mode 100644 index 00000000..aa4a0fdd --- /dev/null +++ b/projects/robodj/src/java/robodj/convert/RipException.java @@ -0,0 +1,16 @@ +// +// $Id: RipException.java,v 1.1 2000/10/30 21:08:53 mdb Exp $ + +package robodj.rip; + +/** + * A rip exception can be thrown to indicate that some problem was + * encountered during some part of the ripping process. + */ +public class RipException extends Exception +{ + public RipException (String message) + { + super(message); + } +} diff --git a/projects/robodj/src/java/robodj/convert/RipTest.java b/projects/robodj/src/java/robodj/convert/RipTest.java new file mode 100644 index 00000000..30d08b3f --- /dev/null +++ b/projects/robodj/src/java/robodj/convert/RipTest.java @@ -0,0 +1,90 @@ +// +// $Id: RipTest.java,v 1.1 2000/10/30 21:08:53 mdb Exp $ + +package robodj.rip; + +import com.samskivert.net.cddb.*; + +public class RipTest +{ + public static void lookupCDDB (String hostname, Ripper.TrackInfo[] info) + throws Exception + { + CDDB cddb = new CDDB(); + + long discid = RipUtil.computeDiscId(info); + String cdid = Long.toString(discid, 0x10); + + // create an array with the track offsets + int[] offsets = new int[info.length]; + for (int i = 0; i < info.length; i++) { + offsets[i] = info[i].offset; + } + + int length = RipUtil.computeDiscLength(info); + + try { + String rsp = cddb.connect(hostname); + + // set the timeout to 30 seconds + cddb.setTimeout(30*1000); + + // try a test query + CDDB.Entry[] entries = cddb.query(cdid, offsets, length); + + if (entries == null || entries.length == 0) { + System.out.println("No match for " + cdid + "."); + + } else { + for (int i = 0; i < entries.length; i++) { + System.out.println("Match " + entries[i].category + "/" + + entries[i].cdid + "/" + + entries[i].title); + } + + CDDB.Detail detail = cddb.read(entries[0].category, + entries[0].cdid); + System.out.println("Title: " + detail.title); + for (int i = 0; i < detail.trackNames.length; i++) { + System.out.println(pad(i) + ": " + detail.trackNames[i]); + } + System.out.println("Extended data: " + detail.extendedData); + for (int i = 0; i < detail.extendedTrackData.length; i++) { + System.out.println(pad(i) + ": " + + detail.extendedTrackData[i]); + } + } + + } finally { + cddb.close(); + } + } + + protected static String pad (int value) + { + return ((value > 9) ? "" : " ") + value; + } + + public static void main (String[] args) + { + try { + Ripper ripper = new CDParanoiaRipper(); + Ripper.TrackInfo[] info = ripper.getTrackInfo(); + for (int i = 0; i < info.length; i++) { + System.out.println((i+1) + ": " + info[i].offset + ", " + + info[i].length); + } + + if (info.length == 0) { + System.out.println("Matched no tracks?!"); + + } else { + // finally try the CDDB lookup + lookupCDDB("us.cddb.com", info); + } + + } catch (Exception e) { + System.err.println("Error: " + e); + } + } +} diff --git a/projects/robodj/src/java/robodj/convert/RipUtil.java b/projects/robodj/src/java/robodj/convert/RipUtil.java new file mode 100644 index 00000000..fe98d4e6 --- /dev/null +++ b/projects/robodj/src/java/robodj/convert/RipUtil.java @@ -0,0 +1,65 @@ +// +// $Id: RipUtil.java,v 1.1 2000/10/30 21:08:53 mdb Exp $ + +package robodj.rip; + +/** + * This class contains utility functions related to ripping. + */ +public class RipUtil +{ + /** + * Compact discs contain this many frames per second. + */ + public static int FRAMES_PER_SECOND = 75; + + /** + * Computes the CDDB disc id for the specified CD using the supplied + * track info. + */ + public static long computeDiscId (Ripper.TrackInfo[] info) + { + // first we sum all the digits in the numbers that represent the + // number of seconds for each track + long digits = 0; + for (int i = 0; i < info.length; i++) { + int secs = info[i].offset/FRAMES_PER_SECOND; + digits += addDigits(secs); + } + + // determine the total number of seconds of all the tracks on the + // CD (doing so by subtracting the offset of the end of the disc + // from the offset of the beginning which is how the CDDB + // algorithm does it) + long totsecs = (long)computeDiscLength(info); + + // finally combine these two values with the number of tracks on + // the disc into the actual disc id + return ((digits & 0xFF) << 24) | (totsecs << 8) | info.length; + } + + /** + * @return the length of the CD (in seconds) as computed from the + * supplied frame information. + */ + public static int computeDiscLength (Ripper.TrackInfo[] info) + { + return (info[info.length-1].offset + + info[info.length-1].length - + info[0].offset)/FRAMES_PER_SECOND; + } + + /** + * @return the sum of the digits in the supplied number (eg. 6 for 15, + * 8 for 251, etc.). + */ + protected static int addDigits (int value) + { + int rv = 0; + while (value > 0) { + rv += value%10; + value /= 10; + } + return rv; + } +} diff --git a/projects/robodj/src/java/robodj/convert/Ripper.java b/projects/robodj/src/java/robodj/convert/Ripper.java new file mode 100644 index 00000000..ab2abfc8 --- /dev/null +++ b/projects/robodj/src/java/robodj/convert/Ripper.java @@ -0,0 +1,41 @@ +// +// $Id: Ripper.java,v 1.1 2000/10/30 21:08:53 mdb Exp $ + +package robodj.rip; + +/** + * The ripper interface is used to manipulate a particular piece of CD + * ripping software in the ways needed by the RoboDJ system. + */ +public interface Ripper +{ + /** + * Instances of this object are used to communicate the track + * information of a CD back to the caller. + */ + public class TrackInfo + { + /** + * The offset of the start of this track (in frames). + */ + public int offset; + + /** + * The length of this track (in frames). + */ + public int length; + } + + /** + * This function should return an array with an entry for each track + * on the CD (in order) containing the frame offset and length of that + * track. This function will be called in another thread and has 30 + * seconds to do its job or it will be declared delinquent and + * ignored. + * + * @exception RipException can be thrown if some problem occurs trying + * to read the CD table of contents (like lack of access to the CDROM + * device or any other errors). + */ + public TrackInfo[] getTrackInfo () throws RipException; +}