Added Movie FLV export - writing simple onMetadata tag

This commit is contained in:
Jindra Petřík
2022-12-26 13:09:56 +01:00
parent fa659f336c
commit c18a603ed4
5 changed files with 259 additions and 67 deletions

View File

@@ -27,6 +27,7 @@ import com.jpexs.decompiler.flash.exporters.modes.MovieExportMode;
import com.jpexs.decompiler.flash.exporters.settings.MovieExportSettings;
import com.jpexs.decompiler.flash.flv.FLVOutputStream;
import com.jpexs.decompiler.flash.flv.FLVTAG;
import com.jpexs.decompiler.flash.flv.SCRIPTDATA;
import com.jpexs.decompiler.flash.flv.VIDEODATA;
import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag;
import com.jpexs.decompiler.flash.tags.Tag;
@@ -114,20 +115,24 @@ public class MovieExporter {
return SWFInputStream.BYTE_ARRAY_EMPTY;
}
//double ms = 1000.0f / ((float) frameRate);
ByteArrayOutputStream fos = new ByteArrayOutputStream();
//CopyOutputStream cos = new CopyOutputStream(fos, new FileInputStream("f:\\trunk\\testdata\\xfl\\xfl\\_obj\\streamvideo 7.flv"));
OutputStream tos = fos;
FLVOutputStream flv = new FLVOutputStream(tos);
flv.writeHeader(false, true);
//flv.writeTag(new FLVTAG(0, SCRIPTDATA.onMetaData(ms * frames.size() / 1000.0, videoStream.width, videoStream.height, 0, frameRate, videoStream.codecID, 0, 0, false, 0, fileSize)));
int numFrames = videoStream.numFrames;
if (ffdecInternal) {
numFrames += 2;
}
int internalFrameDelaySec = 5;
flv.writeTag(new FLVTAG(0, SCRIPTDATA.simpleVideOnMetadata(ffdecInternal ? numFrames * internalFrameDelaySec : numFrames / swf.frameRate, videoStream.width, videoStream.height,ffdecInternal? internalFrameDelaySec : swf.frameRate ,videoStream.codecID)));
int horizontalAdjustment = 0;
int verticalAdjustment = 0;
int[] frameNumArray = Helper.toIntArray(frames.keySet());
Arrays.sort(frameNumArray);
FLVTAG lastTag = null;
int frameNum = 0;
int internalFrameDelay = 5 * 1000;
int internalFrameDelay = internalFrameDelaySec * 1000;
for (int i = 0; i < frameNumArray.length; i++) {
VideoFrameTag tag = frames.get(frameNumArray[i]);
ByteArrayOutputStream baos = new ByteArrayOutputStream();

View File

@@ -17,9 +17,14 @@
package com.jpexs.decompiler.flash.flv;
import com.jpexs.decompiler.flash.EndOfStreamException;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Reference;
import com.jpexs.helpers.utf8.Utf8Helper;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -30,52 +35,53 @@ import java.util.List;
* @author JPEXS
*/
public class FLVInputStream {
private final DataInputStream is;
private int bitPos = 0;
private int tempByte = 0;
private long pos = 0;
public FLVInputStream(InputStream is) {
this.is = new DataInputStream(is);
}
public void readHeader(Reference<Boolean> audioPresent, Reference<Boolean> videoPresent) throws IOException {
byte signature[] = new byte[3];
is.readFully(signature);
if (signature[0] != 'F' || signature[1] != 'L' || signature[2] != 'V') {
throw new IOException("Invalid FLV file - invalid signature");
}
pos+=3;
pos += 3;
int version = read();
if (version != 1) {
throw new IOException("Unsupported FLV version: "+version+", only 1 is supported");
}
int reserved = (int)readUB(5);
audioPresent.setVal(readUB(1)==1L);
throw new IOException("Unsupported FLV version: " + version + ", only 1 is supported");
}
int reserved = (int) readUB(5);
audioPresent.setVal(readUB(1) == 1L);
readUB(1);
videoPresent.setVal(readUB(1) == 1L);
long headerSize = readUI32();
if (headerSize != 9) {
throw new IOException("Invalid header size: "+headerSize);
throw new IOException("Invalid header size: " + headerSize);
}
readUI32(); //should be 0
}
public List<FLVTAG> readTags() throws IOException {
List<FLVTAG> ret = new ArrayList<>();
while(available() > 0) {
while (available() > 0) {
ret.add(readTag());
}
return ret;
}
private long readUI32() throws IOException {
return ((readEx() << 24) + (readEx() << 16) + (readEx() << 8) + (readEx())) & 0xffffffffL;
}
/**
* Reads one byte from the stream
*
@@ -86,7 +92,7 @@ public class FLVInputStream {
bitPos = 0;
return readNoBitReset();
}
/**
* Reads UB[nBits] (Unsigned-bit value) value from the stream
*
@@ -101,7 +107,7 @@ public class FLVInputStream {
long ret = readUBInternal(nBits);
return ret;
}
private int readNoBitReset() throws IOException, EndOfStreamException {
int r = is.read();
if (r == -1) {
@@ -140,65 +146,60 @@ public class FLVInputStream {
}
return ret;
}
private void alignByte() {
bitPos = 0;
}
public int read() throws IOException {
alignByte();
pos++;
return is.read();
}
public int readUI8() throws IOException {
return read();
}
public int readUI24() throws IOException {
int ret = (readEx() << 16) + (readEx() << 8) + (readEx());
return ret;
}
public byte[] readBytes(int len) throws IOException {
byte[] data = new byte[len];
is.readFully(data);
pos += len;
return data;
}
public int available() throws IOException {
return is.available();
}
public FLVTAG readTag() throws IOException {
long posBefore = pos;
public FLVTAG readTag() throws IOException {
int tagType = readUI8();
int dataLen = readUI24();
//System.out.println("reading tag of type "+ tagType+" datalen="+dataLen);
int timeStamp = readUI24();
int timeStampExtended = readUI8();
int timeStampFull = (timeStampExtended << 24) + timeStamp;
//System.out.println("reding timestamp "+timeStampFull);
readUI24(); //streamId, always 0
byte data[] = new byte[dataLen];
is.readFully(data);
byte data[] = readBytes(dataLen);
readUI32(); //tag size
switch(tagType) {
FLVInputStream subStream = new FLVInputStream(new ByteArrayInputStream(data));
switch (tagType) {
case FLVTAG.DATATYPE_VIDEO:
FLVInputStream subStream = new FLVInputStream(new ByteArrayInputStream(data));
int frameType = (int)subStream.readUB(4);
int codecId = (int)subStream.readUB(4);
byte[] videoData = subStream.readBytes(subStream.available());
return new FLVTAG(timeStampFull, new VIDEODATA(frameType, codecId, videoData));
case FLVTAG.DATATYPE_AUDIO:
return new FLVTAG(timeStampFull, subStream.readVIDEODATA());
case FLVTAG.DATATYPE_SCRIPT_DATA:
return new FLVTAG(timeStampFull, subStream.readSCRIPTDATA());
case FLVTAG.DATATYPE_AUDIO:
return new FLVTAG(timeStampFull, subStream.readAUDIODATA());
default:
return new FLVTAG(timeStampFull, new UnparsedDATA(tagType, data));
}
}
}
/**
* Reads one UI16 (Unsigned 16bit integer) value from the stream
*
@@ -208,4 +209,170 @@ public class FLVInputStream {
public int readUI16() throws IOException {
return (readEx() << 8) + readEx();
}
public SCRIPTDATA readSCRIPTDATA() throws IOException {
SCRIPTDATAVALUE name = readSCRIPTDATAVALUE();
SCRIPTDATAVALUE value = readSCRIPTDATAVALUE();
return new SCRIPTDATA(name, value);
}
/**
* Reads one DOUBLE (double precision floating point value) value from the
* stream
*
* @return DOUBLE value
* @throws IOException
*/
public double readDOUBLE() throws IOException {
long el = readLong();
double ret = Double.longBitsToDouble(el);
return ret;
}
private long readLong() throws IOException {
byte[] readBuffer = readBytes(8);
return (((long) readBuffer[0] << 56)
+ ((long) (readBuffer[1] & 0xff) << 48)
+ ((long) (readBuffer[2] & 0xff) << 40)
+ ((long) (readBuffer[3] & 0xff) << 32)
+ ((long) (readBuffer[4] & 0xff) << 24)
+ ((readBuffer[5] & 0xff) << 16)
+ ((readBuffer[6] & 0xff) << 8)
+ ((readBuffer[7] & 0xff)));
}
public AUDIODATA readAUDIODATA() throws IOException {
int soundFormat = (int) readUB(4);
int soundRate = (int) readUB(2);
boolean soundSize = (int) readUB(1) == 1;
boolean soundType = (int) readUB(1) == 1;
byte[] soundData = readBytes(available());
return new AUDIODATA(soundFormat, soundRate, soundSize, soundType, soundData);
}
public SCRIPTDATAOBJECT readSCRIPTDATAOBJECT() throws IOException {
System.out.println("reading obj");
String objectName = readSCRIPTDATASTRING();
System.out.println("objectName "+objectName);
if (objectName.length() == 0) {
int endMarker = readUI8();
if (endMarker != 9) {
throw new IOException("Invalid SCRIPTOBJECT end marker - 9 expected but " + endMarker + " found");
}
return null;
}
SCRIPTDATAVALUE objectData = readSCRIPTDATAVALUE();
return new SCRIPTDATAOBJECT(objectName, objectData);
}
public VIDEODATA readVIDEODATA() throws IOException {
int frameType = (int) readUB(4);
int codecId = (int) readUB(4);
byte[] videoData = readBytes(available());
return new VIDEODATA(frameType, codecId, videoData);
}
public SCRIPTDATAVALUE readSCRIPTDATAVALUE() throws IOException {
int type = readUI8();
switch (type) {
//Number type
case 0:
return new SCRIPTDATAVALUE(readDOUBLE());
//Boolean type
case 1:
return new SCRIPTDATAVALUE(type, readUI8() == 1 ? Boolean.TRUE : Boolean.FALSE);
//String type
case 2:
return new SCRIPTDATAVALUE(type, readSCRIPTDATASTRING());
//Object type
case 3:
List<SCRIPTDATAOBJECT> objects = new ArrayList<>();
SCRIPTDATAOBJECT object;
while ((object = readSCRIPTDATAOBJECT()) != null) {
objects.add(object);
}
return new SCRIPTDATAVALUE(type, objects);
//MovieClip type
case 4:
return new SCRIPTDATAVALUE(type, readSCRIPTDATASTRING());
//Null type
case 5:
//Undefined type
case 6:
return new SCRIPTDATAVALUE(type, null);
//Reference type
case 7:
return new SCRIPTDATAVALUE(type, readUI16());
//ECMA array type
case 8:
int ecmaArrayLength = (int) readUI32();
List<SCRIPTDATAVARIABLE> variables2 = new ArrayList<>();
SCRIPTDATAVARIABLE variable2;
while ((variable2 = readSCRIPTDATAVARIABLE()) != null) {
variables2.add(variable2);
}
return new SCRIPTDATAVALUE(type, variables2);
//Strict array type
case 10:
int arrayLength = (int) readUI32();
List<SCRIPTDATAVARIABLE> variables = new ArrayList<>();
for (int i = 0; i < arrayLength; i++) {
variables.add(readSCRIPTDATAVARIABLE());
}
return new SCRIPTDATAVALUE(type, variables);
//Date type
case 11:
return new SCRIPTDATAVALUE(type, readSCRIPTDATADATE());
//Long string type
case 12:
return new SCRIPTDATAVALUE(type, readSCRIPTDATALONGSTRING());
default:
return null;
}
}
public String readSCRIPTDATALONGSTRING() throws IOException {
int len = (int) readUI32(); //? should be unsinged
return new String(readBytes(len), Utf8Helper.charset);
}
public SCRIPTDATADATE readSCRIPTDATADATE() throws IOException {
double time = readDOUBLE();
int localDateTimeOffset = readSI16();
return new SCRIPTDATADATE(time, localDateTimeOffset);
}
/**
* Reads one SI16 (Signed 16bit integer) value from the stream
*
* @return SI16 value
* @throws IOException
*/
public int readSI16() throws IOException {
int uval = (readEx() << 8) + readEx();
if (uval >= 0x8000) {
uval = -(((~uval) & 0xffff) + 1);
}
return uval;
}
public SCRIPTDATAVARIABLE readSCRIPTDATAVARIABLE() throws IOException {
String variableName = readSCRIPTDATASTRING();
if (variableName.length() == 0) {
int endMarker = readUI8();
if (endMarker != 9) {
throw new IOException("Invalid SCRIPTDATAVARIABLE end marker - 9 expected but " + endMarker + " found");
}
return null;
}
SCRIPTDATAVALUE variableValue = readSCRIPTDATAVALUE();
return new SCRIPTDATAVARIABLE(variableName, variableValue);
}
public String readSCRIPTDATASTRING() throws IOException {
int len = readUI16();
return new String(readBytes(len), Utf8Helper.charset);
}
}

View File

@@ -170,14 +170,14 @@ public class FLVOutputStream extends OutputStream {
private void writeLong(long value) throws IOException {
byte[] writeBuffer = new byte[8];
writeBuffer[3] = (byte) (value >>> 56);
writeBuffer[2] = (byte) (value >>> 48);
writeBuffer[1] = (byte) (value >>> 40);
writeBuffer[0] = (byte) (value >>> 32);
writeBuffer[7] = (byte) (value >>> 24);
writeBuffer[6] = (byte) (value >>> 16);
writeBuffer[5] = (byte) (value >>> 8);
writeBuffer[4] = (byte) (value);
writeBuffer[0] = (byte) (value >>> 56);
writeBuffer[1] = (byte) (value >>> 48);
writeBuffer[2] = (byte) (value >>> 40);
writeBuffer[3] = (byte) (value >>> 32);
writeBuffer[4] = (byte) (value >>> 24);
writeBuffer[5] = (byte) (value >>> 16);
writeBuffer[6] = (byte) (value >>> 8);
writeBuffer[7] = (byte) (value);
write(writeBuffer);
}
@@ -198,15 +198,19 @@ public class FLVOutputStream extends OutputStream {
public void writeSCRIPTDATAVALUE(SCRIPTDATAVALUE v) throws IOException {
writeUI8(v.type);
switch (v.type) {
//Number type
case 0:
writeDOUBLE((double) (Double) v.value);
break;
//Boolean type
case 1:
writeUI8((boolean) (Boolean) v.value ? 1 : 0);
break;
//String type
case 2:
writeSCRIPTDATASTRING((String) v.value);
break;
//Object type
case 3:
@SuppressWarnings("unchecked") List<SCRIPTDATAOBJECT> objects = (List<SCRIPTDATAOBJECT>) v.value;
for (SCRIPTDATAOBJECT o : objects) {
@@ -214,18 +218,21 @@ public class FLVOutputStream extends OutputStream {
}
writeUI24(9);//SCRIPTDATAOBJECTEND
break;
//MovieClip type
case 4:
writeSCRIPTDATASTRING((String) v.value);
break;
//Null type
case 5:
//null
break;
//Undefined type
case 6:
//undefined
break;
//Reference type
case 7:
writeUI16((int) (Integer) v.value);
break;
//ECMA array type
case 8:
@SuppressWarnings("unchecked") List<SCRIPTDATAVARIABLE> variables = (List<SCRIPTDATAVARIABLE>) v.value;
writeUI32(variables.size());
@@ -234,9 +241,10 @@ public class FLVOutputStream extends OutputStream {
}
writeUI24(9);//SCRIPTDATAVARIABLEEND
break;
//Reserved
case 9:
//reserved
break;
//Strict array type
case 10:
@SuppressWarnings("unchecked") List<SCRIPTDATAVARIABLE> stvariables = (List<SCRIPTDATAVARIABLE>) v.value;
writeUI32(stvariables.size());
@@ -244,9 +252,11 @@ public class FLVOutputStream extends OutputStream {
writeSCRIPTDATAVARIABLE(var);
}
break;
//Date type
case 11:
writeSCRIPTDATADATE((SCRIPTDATADATE) v.value);
break;
//Long string type
case 12:
writeSCRIPTDATALONGSTRING((String) v.value);
break;

View File

@@ -29,28 +29,39 @@ import java.util.logging.Logger;
*/
public class SCRIPTDATA extends DATA {
public List<SCRIPTDATAOBJECT> data;
public SCRIPTDATA(List<SCRIPTDATAOBJECT> data) {
this.data = data;
public SCRIPTDATAVALUE name;
public SCRIPTDATAVALUE value;
public SCRIPTDATA(SCRIPTDATAVALUE name, SCRIPTDATAVALUE value) {
this.name = name;
this.value = value;
}
@Override
public byte[] getBytes() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (FLVOutputStream fos = new FLVOutputStream(baos)) {
for (SCRIPTDATAOBJECT d : data) {
fos.writeSCRIPTDATAOBJECT(d);
}
fos.writeUI24(9); //SCRIPTDATAOBJECTEND
fos.writeSCRIPTDATAVALUE(name);
fos.writeSCRIPTDATAVALUE(value);
} catch (IOException ex) {
Logger.getLogger(SCRIPTDATA.class.getName()).log(Level.SEVERE, "i/o error", ex);
}
return baos.toByteArray();
}
public static SCRIPTDATA simpleVideOnMetadata(double duration, double width, double height, double framerate, double videocodecid) {
List<SCRIPTDATAVARIABLE> values = new ArrayList<>();
values.add(new SCRIPTDATAVARIABLE("duration", new SCRIPTDATAVALUE(duration)));
values.add(new SCRIPTDATAVARIABLE("width", new SCRIPTDATAVALUE(width)));
values.add(new SCRIPTDATAVARIABLE("height", new SCRIPTDATAVALUE(height)));
values.add(new SCRIPTDATAVARIABLE("framerate", new SCRIPTDATAVALUE(framerate)));
values.add(new SCRIPTDATAVARIABLE("videocodecid", new SCRIPTDATAVALUE(videocodecid)));
SCRIPTDATAVALUE valuesList = new SCRIPTDATAVALUE(8, values);
SCRIPTDATAVALUE onMetadataKey = new SCRIPTDATAVALUE(2,"onMetaData");
return new SCRIPTDATA(onMetadataKey, valuesList);
}
public static SCRIPTDATA onMetaData(double duration, double width, double height, double videodatarate, double framerate, double videocodecid, double audiosamplerate, double audiosamplesize, boolean stereo, double audiocodecid, double filesize) {
List<SCRIPTDATAOBJECT> list = new ArrayList<>();
List<SCRIPTDATAVARIABLE> values = new ArrayList<>();
values.add(new SCRIPTDATAVARIABLE("duration", new SCRIPTDATAVALUE(duration)));
values.add(new SCRIPTDATAVARIABLE("width", new SCRIPTDATAVALUE(width)));
@@ -64,9 +75,7 @@ public class SCRIPTDATA extends DATA {
values.add(new SCRIPTDATAVARIABLE("audiocodecid", new SCRIPTDATAVALUE(audiocodecid)));
values.add(new SCRIPTDATAVARIABLE("filesize", new SCRIPTDATAVALUE(filesize)));
SCRIPTDATAVALUE valuesList = new SCRIPTDATAVALUE(8, values);
SCRIPTDATAOBJECT metaData = new SCRIPTDATAOBJECT("onMetaData", valuesList);
list.add(metaData);
SCRIPTDATA ret = new SCRIPTDATA(list);
return ret;
SCRIPTDATAVALUE onMetadataKey = new SCRIPTDATAVALUE(2,"onMetaData");
return new SCRIPTDATA(onMetadataKey, valuesList);
}
}