Update of /cvsroot/bitcollider/jbitcollider/plugins/org.bitpedia.collider.video/src/org/bitpedia/collider/video In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv20753/plugins/org.bitpedia.collider.video/src/org/bitpedia/collider/video Added Files: QuickTimeFormat.java VideoFormatHandler.java AviFormat.java VideoUtils.java MpegFormat.java Log Message: initial commit from code drop of april 12 --- NEW FILE: QuickTimeFormat.java --- package org.bitpedia.collider.video; import java.io.IOException; import java.io.RandomAccessFile; import org.bitpedia.collider.video.VideoFormatHandler.VideoData; public class QuickTimeFormat { /* QuickTime uses big-endian ordering, and block ("atom") lengths include the * entire atom, including the fourcc specifying atom type and the length * integer itself. */ public static void parseQuickTime(RandomAccessFile stm, VideoData data) throws IOException { int blockLen; byte[] fourcc = new byte[4]; stm.skipBytes(4); stm.read(fourcc); /* If data is first, header's at end of file, so skip to it */ if("mdat".equals(new String(fourcc))) { stm.seek(0); blockLen = VideoUtils.readBE(stm, 4); stm.seek(blockLen + 4); stm.read(fourcc); } if(!"moov".equals(new String(fourcc))) { return; } long blockStart = stm.getFilePointer(); blockLen = VideoUtils.readBE(stm, 4); /* mvhd length */ stm.read(fourcc); if(!"mvhd".equals(new String(fourcc))) { return; } /* Now we're at the start of the movie header */ /* 20: time scale (time units per second) (4 bytes) */ stm.seek(blockStart + 20); int timescale = VideoUtils.readBE(stm, 4); /* 24: duration in time units (4 bytes) */ data.duration = (int)VideoUtils.round((double)VideoUtils.readBE(stm, 4) / timescale * 1000); /* Skip the rest of the mvhd */ stm.seek(blockStart + blockLen); /* Find and parse trak atoms */ while(stm.getFilePointer() < stm.length()) { /* Find the next trak atom */ blockStart = stm.getFilePointer(); blockLen = VideoUtils.readBE(stm, 4); /* trak (or other atom) length */ stm.read(fourcc); if (!"trak".equals(new String(fourcc))) { /* If it's not a trak atom, skip it */ if (stm.getFilePointer() < stm.length()) { stm.seek(blockStart + blockLen); } continue; } long subBlockStart = stm.getFilePointer(); int subBlockLen = VideoUtils.readBE(stm, 4); /* tkhd length */ stm.read(fourcc); if (!"tkhd".equals(new String(fourcc))) { return; } /* Now in the track header */ /* 84: width (2 bytes) */ stm.seek(subBlockStart + 84); int width = VideoUtils.readBE(stm, 2); /* 88: height (2 bytes) */ stm.seek(subBlockStart + 88); int height = VideoUtils.readBE(stm, 2); /* Note on above: Apple's docs say that width/height are 4-byte integers, * but all files I've seen have the data stored in the high-order two * bytes, with the low-order two being 0x0000. Interpreting it the * "official" way would make width/height be thousands of pixels each. */ /* Skip rest of tkhd */ stm.seek(subBlockStart + subBlockLen); /* Find mdia atom for this trak */ subBlockStart = stm.getFilePointer(); subBlockLen = VideoUtils.readBE(stm, 4); stm.read(fourcc); while(!"mdia".equals(new String(fourcc))) { stm.seek(subBlockStart + subBlockLen); subBlockStart = stm.getFilePointer(); subBlockLen = VideoUtils.readBE(stm, 4); stm.read(fourcc); } /* Now we're in the mdia atom; first sub-atom should be mdhd */ long subSubBlockStart = stm.getFilePointer(); int subSubBlockLen = VideoUtils.readBE(stm, 4); stm.read(fourcc); if (!"mdia".equals(new String(fourcc))) { return; } stm.seek(subSubBlockStart + subSubBlockLen); subSubBlockStart = stm.getFilePointer(); subSubBlockLen = VideoUtils.readBE(stm, 4); stm.read(fourcc); if (!"hdlr".equals(new String(fourcc))) { return; } /* 12: Component type: "mhlr" or "dhlr"; we only care about mhlr, * which should (?) appear first */ stm.seek(subSubBlockStart + 12); stm.read(fourcc); if (!"mhlr".equals(new String(fourcc))) { return; } stm.read(fourcc); if ("vide".equals(new String(fourcc))) { /* This is a video trak */ data.height = height; data.width = width; } /* Skip rest of the trak */ stm.seek(blockStart + blockLen); } } } --- NEW FILE: VideoFormatHandler.java --- package org.bitpedia.collider.video; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.util.LinkedHashMap; import java.util.Map; import org.bitpedia.collider.core.FormatHandler; public class VideoFormatHandler implements FormatHandler { private static final int UNKNOWN_FMT = 0; private static final int AVI_FMT = 1; private static final int QUICK_TIME_FMT = 2; private static final int MPEG_FMT = 3; /* * We must be able to determine file format using this many bytes from the * beginning of the file */ private static final int HEAD_BUFFER = 12; public static class VideoData { int width; /* width in pixels */ int height; /* height in pixels */ int fps; /* frames per second */ int duration; /* duration in milliseconds */ int bitrate; /* bitrate in kbps */ String codec; /* video compression codec */ } private String errorString; public boolean supportsExtension(String ext) { return "avi".equalsIgnoreCase(ext) || "mov".equalsIgnoreCase(ext) || "qt".equalsIgnoreCase(ext) || "mpg".equalsIgnoreCase(ext) || "mpeg".equalsIgnoreCase(ext) || "m2v".equalsIgnoreCase(ext); } public boolean supportsMemAnalyze() { return false; } public boolean supportsFileAnalyze() { return true; } public void analyzeInit() { } public void analyzeUpdate(byte[] buf, int bufLen) { } public Map analyzeFinal() { return null; } private int findFormat(String fileName) { InputStream stm = null; try { stm = new FileInputStream(fileName); byte[] buffer = new byte[HEAD_BUFFER]; if (HEAD_BUFFER != stm.read(buffer)) { return UNKNOWN_FMT; } if (("RIFF".equals(new String(buffer, 0, 4))) && ("AVI ".equals(new String(buffer, 8, 4)))) { /* AVI signature: "RIFF____AVI " */ return AVI_FMT; } else if (("moov".equals(new String(buffer, 4, 4))) || ("mdat".equals(new String(buffer, 4, 4)))) { /* QuickTime signature: "____moov" or "____mdat" */ return QUICK_TIME_FMT; } else if (0 == buffer[0] && 0 == buffer[1] && 1 == buffer[2] && ((byte)0xB3 == buffer[3] || (byte)0xBA == buffer[3])) { /* MPEG signature: 0x000001B3 or 0x000001BA */ return MPEG_FMT; } else { return UNKNOWN_FMT; } } catch (FileNotFoundException e) { return UNKNOWN_FMT; } catch (IOException e) { return UNKNOWN_FMT; } finally { try { stm.close(); } catch (IOException e) { } } } public Map analyzeFile(String fileName) { File file = new File(fileName); int fmt = findFormat(fileName); String fmtStr = ""; VideoData data = new VideoData(); RandomAccessFile stm = null; try { stm = new RandomAccessFile(fileName, "r"); switch (fmt) { case AVI_FMT: fmtStr = "AVI"; AviFormat.parseAvi(stm, data); break; case QUICK_TIME_FMT: fmtStr = "QuickTime"; QuickTimeFormat.parseQuickTime(stm, data); break; case MPEG_FMT: int version = MpegFormat.parseMpeg(stm, data); if (1 == version) { fmtStr = "MPEG-1"; } else if (2 == version) { fmtStr = "MPEG-2"; } break; } } catch (FileNotFoundException e) { } catch (IOException e) { } finally { try { stm.close(); } catch (IOException e) { } } /* * If necessary, use filesize to estimate bitrate from duration or vice * versa */ if ((0 == data.bitrate) && (0 != data.duration)) { data.bitrate = (int) VideoUtils.round((double)file.length() / data.duration * 8); } else if ((0 == data.duration) && (0 != data.bitrate)) { data.duration = (int) VideoUtils.round((double)file.length() / data.bitrate * 8); } Map attrs = new LinkedHashMap(); if (!VideoUtils.isEmpty(fmtStr)) { attrs.put("tag.video.format", fmtStr); } if (0 != data.width) { attrs.put("tag.video.width", "" + data.width); } if (0 != data.height) { attrs.put("tag.video.height", "" + data.height); } if (0 != data.fps) { attrs.put("tag.video.fps", "" + data.fps); } if (0 != data.duration) { attrs.put("tag.video.duration", "" + data.duration); } if (0 != data.bitrate) { attrs.put("tag.video.bitrate", "" + data.bitrate); } if (!VideoUtils.isEmpty(data.codec)) { attrs.put("tag.video.codec", data.codec); } return attrs; } public String getError() { return errorString; } } --- NEW FILE: AviFormat.java --- package org.bitpedia.collider.video; import java.io.IOException; import java.io.RandomAccessFile; import org.bitpedia.collider.video.VideoFormatHandler.VideoData; public class AviFormat { public static void parseAvi(RandomAccessFile stm, VideoData data) throws IOException { byte[] fourcc = new byte[4]; stm.skipBytes(12); /* Verify existence of and read length of AVI header: * "LIST____hdrlavih____" * where the first ____ is the length of the LIST block */ stm.read(fourcc); if (!"LIST".equals(new String(fourcc))) { return; } stm.skipBytes(4); stm.read(fourcc); if (!"hdrl".equals(new String(fourcc))) { return; } stm.read(fourcc); if (!"avih".equals(new String(fourcc))) { return; } int blockLen = VideoUtils.readLE(stm, 4); /* Now we're at the start of the AVI header */ /* 0: microseconds per frame (4 bytes) */ data.fps = (int)VideoUtils.round(1e6 / VideoUtils.readLE(stm, 4)); stm.skipBytes(12); /* 16: total frames (4 bytes) */ data.duration = (int)VideoUtils.round((double)VideoUtils.readLE(stm, 4) * 1000 / data.fps); stm.skipBytes(12); /* 32: width (4 bytes) */ data.width = VideoUtils.readLE(stm, 4); /* 36: height (4 bytes) */ data.height = VideoUtils.readLE(stm, 4); /* Skip rest of avi header */ stm.skipBytes(blockLen - 40); /* Verify existence of and read length of video stream header: * "LIST____strlstrh____vids" */ stm.read(fourcc); if (!"LIST".equals(new String(fourcc))) { return; } blockLen = VideoUtils.readLE(stm, 4); stm.read(fourcc); if (!"strl".equals(new String(fourcc))) { return; } stm.read(fourcc); if (!"strh".equals(new String(fourcc))) { return; } stm.skipBytes(4); stm.read(fourcc); if (!"vids".equals(new String(fourcc))) { return; } /* Now we're in the video stream header */ /* 16: FOURCC of video codec (4 bytes)*/ stm.read(fourcc); data.codec = new String(fourcc); } } --- NEW FILE: VideoUtils.java --- package org.bitpedia.collider.video; import java.io.IOException; import java.io.RandomAccessFile; public class VideoUtils { public static double round(double value) { return Math.floor(value + 0.5); } /* * Read the specified number of bytes as a little-endian (least significant * byte first) integer. Note: bytes must be less than the byte width of * "unsigned long int" on your platform (e.g. 8 for 32-bit systems). */ public static int readLE(RandomAccessFile stm, int bytes) throws IOException { int x, res = 0; for (x = 0; x < bytes; x++) { res |= stm.read() << (x * 8); } return res; } /* Same as above, but big-endian (most significant byte first) ordering */ public static int readBE(RandomAccessFile stm, int bytes) throws IOException { int x, res = 0; for (x = bytes - 1; x >= 0; x--) { res |= stm.read() << (x * 8); } return res; } public static boolean isEmpty(String str) { return (null == str) || "".equals(str); } } --- NEW FILE: MpegFormat.java --- package org.bitpedia.collider.video; import java.io.IOException; import java.io.RandomAccessFile; import org.bitpedia.collider.video.VideoFormatHandler.VideoData; public class MpegFormat { /* * Returns 1 or 2 to indicate MPEG-1 or MPEG-2 * * Most MPEG data is stored in bits not necessarily aligned on byte * boundaries; bits are ordered most-significant first, so big-endian of a * sort. Block sizes only count bytes after the block size integer. */ public static int parseMpeg(RandomAccessFile stm, VideoData data) throws IOException { int version = 0; /* MPEG-1/2; our return value */ /* * First check if this is a Program stream (multiplexed audio/video), * and handle Pack header if so */ int temp = VideoUtils.readBE(stm, 4); if (0x000001BA == temp) { /* Figure out if this is an MPEG-1 or MPEG-2 program */ temp = stm.read(); if (0x20 == (temp & 0xF0)) { /* binary 0010 xxxx */ version = 1; } else if (0x40 == (temp & 0xC0)) { /* binary 01xx xxxx */ version = 2; } else { return 0; } if (1 == version) { stm.skipBytes(4); data.bitrate = (int) VideoUtils.round((double) ((VideoUtils .readBE(stm, 3) & 0x7FFFFE) >> 1) * 0.4); } else { stm.skipBytes(5); data.bitrate = (int) VideoUtils.round((double) ((VideoUtils .readBE(stm, 3) & 0xFFFFFC) >> 2) * 0.4); temp = stm.read() & 0x07; /* stuffing bytes */ if (0 != temp) { stm.skipBytes(temp); } } /* * Skip any other blocks we find until we get to a video stream, * which might be within a 2nd PACK */ temp = VideoUtils.readBE(stm, 4); while (0x000001BA != temp && 0x000001E0 != temp) { if (0 == temp) { /* Skip past zero padding */ int buf = 0; while (0x00000100 != (temp & 0xFFFFFF00)) { if (-1 == buf) { return version; /* shouldn't happen here either */ } temp <<= 8; buf = stm.read(); temp |= buf; } } else { temp = VideoUtils.readBE(stm, 2); stm.skipBytes(temp); temp = VideoUtils.readBE(stm, 4); } } /* * Now read byte by byte until we find the 0x000001B3 instead of * actually parsing (due to too many variations). Theoretically this * could mean we find 0x000001B3 as data inside another packet, but * that's extremely unlikely, especially since the sequence header * should not be far */ temp = VideoUtils.readBE(stm, 4); int buf = 0; while (0x000001B3 != temp) { if (-1 == buf) { return version; /* No seq. header; shouldn't happen */ } temp <<= 8; buf = stm.read(); temp |= buf; } } else { /* video stream only */ stm.seek(4); } /* Now we're just past the video sequence header start code */ temp = VideoUtils.readBE(stm, 3); data.width = (temp & 0xFFF000) >> 12; data.height = temp & 0x000FFF; switch (stm.read() & 0x0F) { case 1: /* 23.976 fps */ case 2: /* 24 fps */ data.fps = 24; break; case 3: /* 25 fps */ data.fps = 25; break; case 4: /* 29.97 fps */ case 5: /* 30 fps */ data.fps = 30; break; case 6: /* 50 fps */ data.fps = 50; break; case 7: /* 59.94 fps */ case 8: /* 60 fps */ data.fps = 60; break; } if (0 == data.bitrate) { /* if this is a video-only stream, */ /* get bitrate from here */ temp = (VideoUtils.readBE(stm, 3) & 0xFFFFC0) >> 6; if (0x3FFFF != temp) { /* variable bitrate */ data.bitrate = (int) VideoUtils.round((double) temp * 0.4); } } else { stm.skipBytes(3); } /* If MPEG-2 or don't know yet, look for the sequence header extension */ if (1 != version) { /* Skip past rest of sequence header and 64-byte matrices (if any) */ temp = stm.read(); if (0 != (temp & 0x02)) { stm.skipBytes(63); temp = stm.read(); } if (0 != (temp & 0x01)) { stm.skipBytes(64); } temp = VideoUtils.readBE(stm, 4); if (0x000001B5 == temp) { if (0 == version) { version = 2; } stm.skipBytes(1); /* extensions specify MSBs of width/height */ temp = VideoUtils.readBE(stm, 2); data.width |= (temp & 0x0180) << 5; data.height |= (temp & 0x0060) << 7; stm.skipBytes(2); /* and a numerator/denominator multiplier for fps */ temp = stm.read(); if ((0 != (temp & 0x60)) && (0 != (temp & 0x1F))) { data.fps = (int) VideoUtils.round((double) data.fps * (temp & 0x60) / (temp & 0x1F)); } } else if (version == 0) { version = 1; } } return version; } } |