Finally cracked the nut on the FLV metadata processing.
This commit is contained in:
parent
86a09acc4c
commit
e039870adc
6 changed files with 453 additions and 107 deletions
|
|
@ -1,5 +1,6 @@
|
|||
package org.forkalsrud.album.video;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
|
@ -14,7 +15,7 @@ public class FlvFilter extends OutputStream {
|
|||
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FlvFilter.class);
|
||||
|
||||
public interface FlvReceiver {
|
||||
public void writeHeader(byte[]... data);
|
||||
public void writeHeader(byte[] data);
|
||||
public void writeBody(byte[] data);
|
||||
}
|
||||
|
||||
|
|
@ -50,8 +51,8 @@ public class FlvFilter extends OutputStream {
|
|||
|
||||
public FlvFilter(FlvReceiver receiver, FlvMetadata metadata) {
|
||||
this.receiver = receiver;
|
||||
this.metadata = metadata;
|
||||
this.extraMetadata = null;
|
||||
this.metadata = new FlvMetadata();
|
||||
this.extraMetadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,22 +142,21 @@ public class FlvFilter extends OutputStream {
|
|||
// Then only audio and video
|
||||
if (currentTagType == FLV_TAG_SCRIPTDATA) {
|
||||
if (!headerWritten) {
|
||||
incomingMetadataLength = currentTagSize;
|
||||
/*
|
||||
TODO (knut 09 JUL 2011) generate (extra-)metadata here to enable user injection of metadata
|
||||
metadata.read(currentBox, FLV_SIZE_TAGHEADER, currentTagSize - FLV_SIZE_TAGFOOTER);
|
||||
incomingMetadataLength = currentTagSize - (FLV_SIZE_TAGHEADER + FLV_SIZE_TAGFOOTER);
|
||||
ByteArrayInputStream scriptData = new ByteArrayInputStream(currentBox, FLV_SIZE_TAGHEADER, incomingMetadataLength);
|
||||
metadata.read(scriptData);
|
||||
metadata.merge(extraMetadata);
|
||||
generateHeader();
|
||||
*/
|
||||
receiver.writeHeader(fileHeader, currentBox);
|
||||
generateHeaderChunk();
|
||||
// receiver.writeHeader(fileHeader, currentBox); // copy the input directly
|
||||
headerWritten = true;
|
||||
metadata.clearKeyframes();
|
||||
} else {
|
||||
log.warn("SCRIPTDATA out of order");
|
||||
writeBody();
|
||||
}
|
||||
} else if (currentTagType == FLV_TAG_VIDEO || currentTagType == FLV_TAG_AUDIO) {
|
||||
if (!headerWritten) {
|
||||
log.error("SCRIPTDATA out of order");
|
||||
log.error("Missing SCRIPTDATA");
|
||||
receiver.writeHeader(fileHeader);
|
||||
headerWritten = true;
|
||||
}
|
||||
|
|
@ -190,16 +190,29 @@ public class FlvFilter extends OutputStream {
|
|||
}
|
||||
}
|
||||
|
||||
public void generateHeader() throws IOException {
|
||||
/**
|
||||
* Writes out the header chunk which consists of a file header and a script data tag.
|
||||
* The tag body comes from the metadata object. The file header is a copy of what we read in at the start.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void generateHeaderChunk() throws IOException {
|
||||
|
||||
metadata.setFileSize(fileHeader.length + incomingMetadataLength + bodyLen);
|
||||
metadata.setFileSize(fileHeader.length + FLV_SIZE_TAGHEADER + incomingMetadataLength + FLV_SIZE_TAGFOOTER + bodyLen);
|
||||
int metadataLength = metadata.calculateLength();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(fileHeader.length + metadataLength);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(fileHeader.length + FLV_SIZE_TAGHEADER + metadataLength + FLV_SIZE_TAGFOOTER);
|
||||
// file header
|
||||
out.write(fileHeader);
|
||||
metadata.setFileSize(fileHeader.length + metadataLength + bodyLen);
|
||||
metadata.setFileSize(fileHeader.length + FLV_SIZE_TAGHEADER + metadataLength + FLV_SIZE_TAGFOOTER + bodyLen);
|
||||
metadata.setFileOffsetDelta(metadataLength - incomingMetadataLength);
|
||||
metadata.writeOnMetadata(out, metadataLength);
|
||||
|
||||
// tag header
|
||||
out.write(FLV_TAG_SCRIPTDATA); // Script data tag 1 byte
|
||||
metadata.writeUint24(out, metadataLength); //data size 3 bytes
|
||||
metadata.writeTimestamp(out, 0); // timestamp 4 bytes
|
||||
metadata.writeUint24(out, 0); // Stream ID 3 bytes
|
||||
// body
|
||||
metadata.writeScriptDataObject(out);
|
||||
// tag footer
|
||||
metadata.writeUint32(out, FLV_SIZE_TAGHEADER + metadataLength + FLV_SIZE_TAGFOOTER); // size of tag
|
||||
receiver.writeHeader(out.toByteArray());
|
||||
}
|
||||
|
||||
|
|
@ -219,6 +232,12 @@ public class FlvFilter extends OutputStream {
|
|||
return ((ch1 << 16) + (ch2 << 8) + (ch3 << 0));
|
||||
}
|
||||
|
||||
int decodeUint16(byte[] buf, int offset) {
|
||||
int ch1 = buf[offset + 0] & 0xff;
|
||||
int ch2 = buf[offset + 1] & 0xff;
|
||||
return ((ch1 << 8) + (ch2 << 0));
|
||||
}
|
||||
|
||||
int decodeUint8(byte[] buf, int offset) {
|
||||
return buf[offset] & 0xff;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package org.forkalsrud.album.video;
|
||||
|
||||
public class FlvFormatException extends Exception {
|
||||
|
||||
public FlvFormatException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public FlvFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FlvFormatException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public FlvFormatException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,12 +2,14 @@ package org.forkalsrud.album.video;
|
|||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.forkalsrud.album.exif.Dimension;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FlvMetadata {
|
||||
|
||||
|
|
@ -27,15 +29,13 @@ public class FlvMetadata {
|
|||
this.value = value;
|
||||
}
|
||||
|
||||
public void write(OutputStream out) throws IOException {
|
||||
if (name != null) {
|
||||
writeFlvString(out, name);
|
||||
}
|
||||
}
|
||||
public abstract void write(OutputStream out) throws IOException;
|
||||
|
||||
public boolean isPresent() {
|
||||
return present;
|
||||
}
|
||||
|
||||
public abstract void read(InputStream in) throws FlvFormatException, IOException;
|
||||
}
|
||||
|
||||
private class StringAttr extends Attr<String> {
|
||||
|
|
@ -46,10 +46,18 @@ public class FlvMetadata {
|
|||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
super.write(out);
|
||||
out.write(2); // DataString
|
||||
writeFlvString(out, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws FlvFormatException, IOException {
|
||||
int type = in.read();
|
||||
if (type != 2) {
|
||||
throw new FlvFormatException("Not a string: " + type);
|
||||
}
|
||||
set(readString(in));
|
||||
}
|
||||
}
|
||||
|
||||
private class BooleanAttr extends Attr<Boolean> {
|
||||
|
|
@ -60,10 +68,18 @@ public class FlvMetadata {
|
|||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
super.write(out);
|
||||
out.write(1); // Bool
|
||||
out.write(value ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws FlvFormatException, IOException {
|
||||
int type = in.read();
|
||||
if (type != 1) {
|
||||
throw new FlvFormatException("Not a boolean: " + type);
|
||||
}
|
||||
set(readBoolean(in));
|
||||
}
|
||||
}
|
||||
|
||||
private class DoubleAttr extends Attr<Double> {
|
||||
|
|
@ -74,10 +90,17 @@ public class FlvMetadata {
|
|||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
super.write(out);
|
||||
out.write(0); // NUMBER
|
||||
long x = Double.doubleToLongBits(value);
|
||||
writeUint64(out, x);
|
||||
writeDouble(out, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws FlvFormatException, IOException {
|
||||
int type = in.read();
|
||||
if (type != 0) {
|
||||
throw new FlvFormatException("Not a boolean: " + type);
|
||||
}
|
||||
set(readDouble(in));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,10 +112,17 @@ public class FlvMetadata {
|
|||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
super.write(out);
|
||||
out.write(0); // NUMBER
|
||||
long x = Double.doubleToLongBits(value + filePositionDelta);
|
||||
writeUint64(out, x);
|
||||
writeDouble(out, value + filePositionDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws FlvFormatException, IOException {
|
||||
int type = in.read();
|
||||
if (type != 0) {
|
||||
throw new FlvFormatException("Not a boolean: " + type);
|
||||
}
|
||||
set((int)readDouble(in) - filePositionDelta);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +139,6 @@ public class FlvMetadata {
|
|||
|
||||
FileOffsetAttr offset = new FileOffsetAttr(null);
|
||||
DoubleAttr timestamp = new DoubleAttr(null);
|
||||
super.write(out);
|
||||
|
||||
out.write(3); // Variable Array
|
||||
|
||||
|
|
@ -127,6 +156,43 @@ public class FlvMetadata {
|
|||
writeFlvVariableArrayEnd(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws FlvFormatException, IOException {
|
||||
|
||||
FileOffsetAttr offset = new FileOffsetAttr(null);
|
||||
DoubleAttr timestamp = new DoubleAttr(null);
|
||||
|
||||
int type = in.read();
|
||||
if (type != 3) {
|
||||
throw new FlvFormatException("Not an object: " + type);
|
||||
}
|
||||
if (!in.markSupported()) {
|
||||
throw new FlvFormatException("Need redahead");
|
||||
}
|
||||
List<Integer> filePositions = null;
|
||||
List<Double> times = null;
|
||||
while (!isLookingAtEnd(in)) {
|
||||
String propName = readString(in);
|
||||
if ("filepositions".equals(propName)) {
|
||||
filePositions = readValueArray(in, offset);
|
||||
} else if ("times".equals(propName)) {
|
||||
times = readValueArray(in, timestamp);
|
||||
}
|
||||
}
|
||||
readObjectEnd(in);
|
||||
if (times != null && filePositions != null) {
|
||||
if (times.size() != filePositions.size()) {
|
||||
throw new FlvFormatException("positions and times need to match up " + times.size() + " v.s. " + filePositions.size());
|
||||
}
|
||||
keyframes.clear();
|
||||
for (int i = 0; i < times.size(); i++) {
|
||||
addVideoFrame(filePositions.get(i), Math.round(times.get(i).floatValue()), true);
|
||||
}
|
||||
} else {
|
||||
System.out.println("Missing filepositions (" + filePositions + ") or times (" + times + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresent() {
|
||||
return !value.isEmpty();
|
||||
|
|
@ -157,6 +223,7 @@ public class FlvMetadata {
|
|||
|
||||
private DoubleAttr audiosize = new DoubleAttr("audiosize");
|
||||
private DoubleAttr audiodatarate = new DoubleAttr("audiodatarate");
|
||||
private DoubleAttr audiodelay = new DoubleAttr("audiodelay");
|
||||
|
||||
private DoubleAttr audiocodecid = new DoubleAttr("audiocodecid");
|
||||
private DoubleAttr audiosamplerate = new DoubleAttr("audiosamplerate");
|
||||
|
|
@ -198,6 +265,7 @@ public class FlvMetadata {
|
|||
|
||||
attrs.add(audiosize);
|
||||
attrs.add(audiodatarate);
|
||||
attrs.add(audiodelay);
|
||||
|
||||
attrs.add(audiocodecid);
|
||||
attrs.add(audiosamplerate);
|
||||
|
|
@ -224,6 +292,10 @@ public class FlvMetadata {
|
|||
return i;
|
||||
}
|
||||
|
||||
public void clearKeyframes() {
|
||||
keyframes.clear();
|
||||
}
|
||||
|
||||
public void addVideoFrame(int filePos, int timestamp, boolean isKeyframe) {
|
||||
|
||||
hasVideo.set(true);
|
||||
|
|
@ -243,8 +315,9 @@ public class FlvMetadata {
|
|||
}
|
||||
|
||||
public int calculateLength() throws IOException {
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
writeOnMetadata(baos, 0);
|
||||
writeScriptDataObject(baos);
|
||||
return baos.size();
|
||||
}
|
||||
|
||||
|
|
@ -253,47 +326,28 @@ public class FlvMetadata {
|
|||
this.filePositionDelta = fileOffsetDelta;
|
||||
}
|
||||
|
||||
public void writeOnMetadata(OutputStream out, int size) throws IOException {
|
||||
|
||||
out.write(18); // Script data tag
|
||||
writeUint24(out, size - 15); //data size
|
||||
writeUint32(out, 0); // timestamp
|
||||
|
||||
/**
|
||||
* @param out
|
||||
* @throws IOException
|
||||
*/
|
||||
public void writeScriptDataObject(OutputStream out) throws IOException {
|
||||
// ScriptDataObject
|
||||
out.write(2);
|
||||
writeFlvEcmaArray(out, "onMetaData", length());
|
||||
out.write(2); // string
|
||||
writeFlvString(out, "onMetaData");
|
||||
writeFlvEcmaArray(out, length());
|
||||
for (Attr a : attrs) {
|
||||
if (a.isPresent()) {
|
||||
if (a.name != null) {
|
||||
writeFlvString(out, a.name);
|
||||
}
|
||||
a.write(out);
|
||||
}
|
||||
}
|
||||
writeFlvVariableArrayEnd(out);
|
||||
}
|
||||
|
||||
void writeStringAttr(OutputStream out, String name, String value) throws IOException {
|
||||
void writeDouble(OutputStream out, double value) throws IOException {
|
||||
|
||||
if (name != null) {
|
||||
writeFlvString(out, name);
|
||||
}
|
||||
out.write(2); // DataString
|
||||
writeFlvString(out, value);
|
||||
}
|
||||
|
||||
void writeBoolAttr(OutputStream out, String name, boolean value) throws IOException {
|
||||
|
||||
if (name != null) {
|
||||
writeFlvString(out, name);
|
||||
}
|
||||
out.write(1); // Bool
|
||||
out.write(value ? 1 : 0);
|
||||
}
|
||||
|
||||
void writeDoubleAttr(OutputStream out, String name, double value) throws IOException {
|
||||
|
||||
if (name != null) {
|
||||
writeFlvString(out, name);
|
||||
}
|
||||
out.write(0); // NUMBER
|
||||
long x = Double.doubleToLongBits(value);
|
||||
writeUint64(out, x);
|
||||
}
|
||||
|
|
@ -305,19 +359,11 @@ public class FlvMetadata {
|
|||
out.write(bytes);
|
||||
}
|
||||
|
||||
private void writeFlvEcmaArray(OutputStream out, String name, int len) throws IOException {
|
||||
|
||||
writeFlvString(out, name);
|
||||
private void writeFlvEcmaArray(OutputStream out, int len) throws IOException {
|
||||
out.write(8); // ECMAArray
|
||||
writeUint32(out, len);
|
||||
}
|
||||
/*
|
||||
private void writeFlvVariableArrayStart(OutputStream out, String name) throws IOException {
|
||||
|
||||
writeFlvString(out, name);
|
||||
out.write(3); // Variable Array
|
||||
}
|
||||
*/
|
||||
private void writeFlvVariableArrayEnd(OutputStream out) throws IOException {
|
||||
out.write(0);
|
||||
out.write(0);
|
||||
|
|
@ -364,6 +410,14 @@ public class FlvMetadata {
|
|||
out.write((value >> 0) & 0xff);
|
||||
}
|
||||
|
||||
void writeTimestamp(OutputStream out, int value) throws IOException {
|
||||
|
||||
out.write((value >> 16) & 0xff);
|
||||
out.write((value >> 8) & 0xff);
|
||||
out.write((value >> 0) & 0xff);
|
||||
out.write((value >> 24) & 0xff);
|
||||
}
|
||||
|
||||
private static class Keyframe {
|
||||
|
||||
private int fileOffset;
|
||||
|
|
@ -393,6 +447,14 @@ public class FlvMetadata {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes all the values from the parameter "theirs".
|
||||
* Corresponding values in this are overwritten.
|
||||
* Other values in this are kept.
|
||||
* The typical purpose is to fill in some missing metadata.
|
||||
*
|
||||
* @param other
|
||||
*/
|
||||
public void merge(FlvMetadata other) {
|
||||
|
||||
for (int i = 0; i < attrs.size(); i++) {
|
||||
|
|
@ -423,10 +485,145 @@ public class FlvMetadata {
|
|||
}
|
||||
|
||||
|
||||
public void read(byte[] buf, int offset, int len) {
|
||||
if (buf[offset] != 2) { //not string
|
||||
return;
|
||||
public void read(InputStream in) throws IOException {
|
||||
|
||||
try {
|
||||
int type = in.read();
|
||||
if (type != 2) {
|
||||
throw new FlvFormatException("Not a string: " + type);
|
||||
}
|
||||
String name = readString(in);
|
||||
if (!"onMetaData".equalsIgnoreCase(name)) {
|
||||
throw new FlvFormatException("Not \"onMetaData\": " + name);
|
||||
}
|
||||
type = in.read();
|
||||
if (type != 8) {
|
||||
throw new FlvFormatException("Not an ECMA array: " + type);
|
||||
}
|
||||
int arrayLen = readEcmaArrayHeader(in);
|
||||
for (int i = 0; i < arrayLen; i++) {
|
||||
readProperty(in);
|
||||
}
|
||||
readEcmaArrayFooter(in);
|
||||
} catch (FlvFormatException e) {
|
||||
LoggerFactory.getLogger(getClass()).error("invalid", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String readString(InputStream in) throws IOException {
|
||||
int len = readUint16(in);
|
||||
byte[] s = new byte[len];
|
||||
in.read(s);
|
||||
return new String(s, "UTF-8");
|
||||
}
|
||||
|
||||
protected boolean readBoolean(InputStream in) throws IOException {
|
||||
int val = in.read();
|
||||
return val != 0;
|
||||
}
|
||||
|
||||
protected double readDouble(InputStream in) throws IOException {
|
||||
long val = readUint64(in);
|
||||
return Double.longBitsToDouble(val);
|
||||
}
|
||||
|
||||
protected long readUint64(InputStream in) throws IOException {
|
||||
long ch1 = in.read();
|
||||
long ch2 = in.read();
|
||||
long ch3 = in.read();
|
||||
long ch4 = in.read();
|
||||
long ch5 = in.read();
|
||||
long ch6 = in.read();
|
||||
long ch7 = in.read();
|
||||
long ch8 = in.read();
|
||||
return ((ch1 << 56) + (ch2 << 48) + (ch3 << 40) + (ch4 << 32) + (ch5 << 24) + (ch6 << 16) + (ch7 << 8) + (ch8 << 0));
|
||||
}
|
||||
|
||||
protected int readUint32(InputStream in) throws IOException {
|
||||
int ch1 = in.read();
|
||||
int ch2 = in.read();
|
||||
int ch3 = in.read();
|
||||
int ch4 = in.read();
|
||||
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
|
||||
}
|
||||
|
||||
protected int readUint24(InputStream in) throws IOException {
|
||||
int ch1 = in.read();
|
||||
int ch2 = in.read();
|
||||
int ch3 = in.read();
|
||||
return ((ch1 << 16) + (ch2 << 8) + (ch3 << 0));
|
||||
}
|
||||
|
||||
protected int readUint16(InputStream in) throws IOException {
|
||||
int ch1 = in.read();
|
||||
int ch2 = in.read();
|
||||
return ((ch1 << 8) + (ch2 << 0));
|
||||
}
|
||||
|
||||
protected int readTimestamp(InputStream in) throws IOException {
|
||||
int ch1 = in.read();
|
||||
int ch2 = in.read();
|
||||
int ch3 = in.read();
|
||||
int ch4 = in.read();
|
||||
return ((ch4 << 24) + (ch1 << 16) + (ch2 << 8) + (ch3 << 0));
|
||||
}
|
||||
|
||||
protected int readEcmaArrayHeader(InputStream in) throws IOException {
|
||||
int len = readUint32(in);
|
||||
return len;
|
||||
}
|
||||
|
||||
protected void readEcmaArrayFooter(InputStream in) throws FlvFormatException, IOException {
|
||||
readObjectEnd(in);
|
||||
}
|
||||
|
||||
protected void readObjectEnd(InputStream in) throws FlvFormatException, IOException {
|
||||
byte[] word = new byte[3];
|
||||
in.read(word);
|
||||
if (word[0] != 0 || word[1] != 0 || word[2] != 9) {
|
||||
throw new FlvFormatException("Not at end: " + word[0] + ", " + word[1] + ", " + word[2]);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isLookingAtEnd(InputStream in) throws IOException {
|
||||
byte[] word = new byte[3];
|
||||
in.mark(word.length);
|
||||
in.read(word);
|
||||
in.reset();
|
||||
return word[0] == 0 && word[1] == 0 && word[2] == 9;
|
||||
}
|
||||
|
||||
protected void readProperty(InputStream in) throws FlvFormatException, IOException {
|
||||
|
||||
String name = readString(in);
|
||||
Attr attr = findAttrByName(name);
|
||||
if (attr == null) {
|
||||
throw new FlvFormatException("Unknown attribute: " + name);
|
||||
}
|
||||
attr.read(in);
|
||||
}
|
||||
|
||||
private Attr findAttrByName(String name) {
|
||||
for (Attr a : this.attrs) {
|
||||
if (name.equals(a.name)) {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected <T> List<T> readValueArray(InputStream in, Attr<T> elementAttr) throws FlvFormatException, IOException {
|
||||
int type = in.read();
|
||||
if (type != 10) {
|
||||
throw new FlvFormatException("Not a value array: " + type);
|
||||
}
|
||||
int len = readUint32(in);
|
||||
ArrayList<T> list = new ArrayList<T>(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
elementAttr.read(in);
|
||||
list.add(elementAttr.value);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -445,4 +642,45 @@ public class FlvMetadata {
|
|||
}
|
||||
|
||||
|
||||
|
||||
public int getWidth() {
|
||||
return width.isPresent() ? width.value.intValue() : 0;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height.isPresent() ? height.value.intValue() : 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || !this.getClass().equals(o.getClass())) {
|
||||
return false;
|
||||
}
|
||||
FlvMetadata other = (FlvMetadata)o;
|
||||
for (int i = 0; i < attrs.size(); i++) {
|
||||
Attr their = other.attrs.get(i);
|
||||
Attr our = this.attrs.get(i);
|
||||
if (our.isPresent() && their.isPresent()) {
|
||||
if (!our.value.equals(their.value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (our.isPresent() != their.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int x = 0;
|
||||
for (Attr a : attrs) {
|
||||
x *= 13;
|
||||
x += a.present ? a.value.hashCode() : 7;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,6 @@ public class MovieCoder {
|
|||
|
||||
private final int chunkSize = 4 * 65536;
|
||||
private File file;
|
||||
private Thumbnail thumbnail;
|
||||
private Dimension targetSize;
|
||||
private ArrayList<EncodingProcessListener> listeners = new ArrayList<EncodingProcessListener>();
|
||||
private Chunk currentChunk = null;
|
||||
|
|
@ -163,15 +162,11 @@ public class MovieCoder {
|
|||
|
||||
public EncodingProcess(File file, Thumbnail thumbnail, Dimension size) {
|
||||
this.file = file;
|
||||
this.thumbnail = thumbnail;
|
||||
this.targetSize = size;
|
||||
this.dbKey = key(file, targetSize);
|
||||
FlvMetadata meta = new FlvMetadata();
|
||||
meta.setDuration(thumbnail.getDuration());
|
||||
meta.setVideoCodecId(2);
|
||||
meta.setAudioCodecId(2);
|
||||
meta.setFramerate(15); // TODO (knut 10 JUL 2011) Need to make sure the ffmpeg args match
|
||||
this.filter = new FlvFilter(this, meta);
|
||||
FlvMetadata extraMeta = new FlvMetadata();
|
||||
extraMeta.setDuration(thumbnail.getDuration());
|
||||
this.filter = new FlvFilter(this, extraMeta);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -233,7 +228,10 @@ public class MovieCoder {
|
|||
endLastChunk();
|
||||
// Generate header again to get updated metadata
|
||||
// TODO (knut 09 JUL 2011) Figure out why the generated header doesn't work
|
||||
filter.generateHeader();
|
||||
// TODO (knut 30 JUL 2011) Add some metadata about the file in the first chunk of the file
|
||||
// size of first chunk, size of last chunk, number number of chunks between first-last, size of chunks between first-last
|
||||
// This will be used to determine the right chunk number from a given byte offset in a range oritented HTTP request
|
||||
filter.generateHeaderChunk();
|
||||
} catch (Exception e) {
|
||||
log.error("uh?", e);
|
||||
movieDb.delete(dbKey);
|
||||
|
|
@ -246,17 +244,10 @@ public class MovieCoder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void writeHeader(byte[]... data) {
|
||||
int len = 0;
|
||||
for (byte[] ba : data) {
|
||||
len += ba.length;
|
||||
}
|
||||
public void writeHeader(byte[] data) {
|
||||
int len = data.length;
|
||||
Chunk chunk0 = new Chunk(len, 0);
|
||||
int i = 0;
|
||||
for (byte[] ba : data) {
|
||||
System.arraycopy(ba, 0, chunk0.bits, i, ba.length);
|
||||
i += ba.length;
|
||||
}
|
||||
chunk0.bits = data;
|
||||
movieDb.store(dbKey, 0, chunk0);
|
||||
notifyListeners(0, false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,8 @@ public class FlvFilterTest extends TestCase {
|
|||
FlvFilter.FlvReceiver receiver = new FlvFilter.FlvReceiver() {
|
||||
|
||||
@Override
|
||||
public void writeHeader(byte[]... data) {
|
||||
int len = 0;
|
||||
for (byte[] ba : data) {
|
||||
len += ba.length;
|
||||
}
|
||||
buf.append("HEADER[" + len + "]BODY[" + bodyLen + "]");
|
||||
public void writeHeader(byte[] data) {
|
||||
buf.append("HEADER[" + data.length + "]BODY[" + bodyLen + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -33,8 +29,8 @@ public class FlvFilterTest extends TestCase {
|
|||
};
|
||||
FlvFilter os = new FlvFilter(receiver, new FlvMetadata());
|
||||
IOUtils.copy(is, os);
|
||||
os.generateHeader();
|
||||
assertEquals("HEADER[252]BODY[0]HEADER[579]BODY[911684]", buf.toString());
|
||||
os.generateHeaderChunk();
|
||||
assertEquals("HEADER[320]BODY[0]HEADER[797]BODY[911684]", buf.toString());
|
||||
}
|
||||
|
||||
public void testFoo() {
|
||||
|
|
@ -44,8 +40,4 @@ public class FlvFilterTest extends TestCase {
|
|||
System.out.println(Long.toHexString(Double.doubleToLongBits(150d)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
package org.forkalsrud.album.video;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.forkalsrud.album.exif.Dimension;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class FlvMetadataTest extends TestCase {
|
||||
|
||||
private ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
private ByteArrayInputStream in;
|
||||
FlvMetadata md0 = new FlvMetadata();
|
||||
|
||||
private void flip() {
|
||||
in = new ByteArrayInputStream(out.toByteArray());
|
||||
out.reset();
|
||||
}
|
||||
|
||||
public void testMerge() throws Exception {
|
||||
|
||||
FlvMetadata md1 = new FlvMetadata();
|
||||
|
|
@ -19,4 +32,76 @@ public class FlvMetadataTest extends TestCase {
|
|||
assertEquals(Double.valueOf("6"), md2.get("height"));
|
||||
}
|
||||
|
||||
public void testReadWriteDouble() throws Exception {
|
||||
|
||||
double value = 3.14;
|
||||
md0.writeDouble(out, value);
|
||||
flip();
|
||||
assertEquals(value, md0.readDouble(in));
|
||||
}
|
||||
|
||||
public void testReadWriteInt() throws Exception {
|
||||
int value = 45;
|
||||
// uint16
|
||||
md0.writeUint16(out, value);
|
||||
flip();
|
||||
assertEquals(value, md0.readUint16(in));
|
||||
|
||||
// uint24
|
||||
md0.writeUint24(out, value);
|
||||
flip();
|
||||
assertEquals(value, md0.readUint24(in));
|
||||
|
||||
// uint32
|
||||
md0.writeUint32(out, value);
|
||||
flip();
|
||||
assertEquals(value, md0.readUint32(in));
|
||||
|
||||
// timestamp
|
||||
md0.writeTimestamp(out, value);
|
||||
flip();
|
||||
assertEquals(value, md0.readTimestamp(in));
|
||||
}
|
||||
|
||||
public void testReadWriteString() throws Exception {
|
||||
String value = "awsome";
|
||||
md0.writeFlvString(out, value);
|
||||
flip();
|
||||
assertEquals(value, md0.readString(in));
|
||||
}
|
||||
|
||||
public void testWriteReadMetadata() throws Exception {
|
||||
|
||||
FlvMetadata md1 = new FlvMetadata();
|
||||
md1.setDuration("3.14");
|
||||
md1.setDimension(new Dimension(5, 6));
|
||||
|
||||
md1.writeScriptDataObject(out);
|
||||
flip();
|
||||
|
||||
FlvMetadata md2 = new FlvMetadata();
|
||||
md2.read(in);
|
||||
|
||||
assertEquals("3.14", md2.get("duration").toString());
|
||||
assertEquals(5, md2.getWidth());
|
||||
assertEquals(6, md2.getHeight());
|
||||
}
|
||||
|
||||
|
||||
public void testReadWriteMetadata() throws Exception {
|
||||
|
||||
InputStream is = getClass().getResourceAsStream("/VideoAd.flv");
|
||||
is.read(new byte[24]); // offset to start of scriptdata
|
||||
|
||||
FlvMetadata md1 = new FlvMetadata();
|
||||
md1.read(is);
|
||||
|
||||
md1.writeScriptDataObject(out);
|
||||
flip();
|
||||
|
||||
FlvMetadata md2 = new FlvMetadata();
|
||||
md2.read(in);
|
||||
|
||||
assertEquals(md1, md2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue