Skip to content
This repository has been archived by the owner on Jul 3, 2019. It is now read-only.

Commit

Permalink
Creation and playback of compressed SWFM files.
Browse files Browse the repository at this point in the history
  • Loading branch information
yurydelendik committed Oct 15, 2015
1 parent 5d58064 commit f7e1fea
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 10 deletions.
8 changes: 6 additions & 2 deletions src/base/dataBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,12 @@ module Shumway.ArrayUtilities {
* will be set to 0.
*/
compact(): void {
this._u8.set(this._u8.subarray(this._position, this._length), 0);
this._length -= this._position;
var position = this._position;
if (position === 0) {
return; // nothing to compact
}
this._u8.set(this._u8.subarray(position, this._length), 0);
this._length -= position;
this._position = 0;
}

Expand Down
74 changes: 66 additions & 8 deletions src/gfx/test/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
module Shumway.GFX.Test {
import DataBuffer = Shumway.ArrayUtilities.DataBuffer;
import PlainObjectDataBuffer = Shumway.ArrayUtilities.PlainObjectDataBuffer;
import IDataDecoder = Shumway.ArrayUtilities.IDataDecoder;
import Inflate = Shumway.ArrayUtilities.Inflate;
import LzmaDecoder = Shumway.ArrayUtilities.LzmaDecoder;

enum MovieRecordObjectType {
Undefined = 0,
Expand Down Expand Up @@ -300,11 +303,19 @@ module Shumway.GFX.Test {
var MovieHeaderSize = 4;
var MovieRecordHeaderSize = 12;

class IdentityDataTransform implements IDataDecoder {
onData: (data: Uint8Array) => void = null;
onError: (e) => void = null;
push(data: Uint8Array) { this.onData(data); }
close() {}
}

export class MovieRecordParser {
private _buffer: DataBuffer;
private _state: MovieRecordParserState;
private _comressionType: MovieCompressionType;
private _closed: boolean;
private _transform: IDataDecoder;

public currentTimestamp: number;
public currentType: MovieRecordType;
Expand All @@ -316,7 +327,46 @@ module Shumway.GFX.Test {
this._closed = false;
}

public push(data: Uint8Array){
public push(data: Uint8Array): void {
if (this._state === MovieRecordParserState.Initial) {
// SWFM file starts from 4 bytes header: "MSWF", "MSWC", or "MSWZ"
var needToRead = MovieHeaderSize - this._buffer.length;
this._buffer.writeRawBytes(data.subarray(0, needToRead));
if (MovieHeaderSize > this._buffer.length) {
return;
}
this._buffer.position = 0;
var headerBytes = this._buffer.readRawBytes(MovieHeaderSize);
if (headerBytes[0] !== 0x4D || headerBytes[1] !== 0x53 || headerBytes[2] !== 0x57 ||
(headerBytes[3] !== 0x46 && headerBytes[3] !== 0x43 && headerBytes[3] !== 0x5A)) {
console.warn('Invalid SWFM header, stopping parsing');
return;
}
switch (headerBytes[3]) {
case 0x46: // 'F'
this._comressionType = MovieCompressionType.None;
this._transform = new IdentityDataTransform();
break;
case 0x43: // 'C'
this._comressionType = MovieCompressionType.ZLib;
this._transform = Inflate.create(true);
break;
case 0x5A: // 'Z'
this._comressionType = MovieCompressionType.Lzma;
this._transform = new LzmaDecoder(false);
break;
}

this._transform.onData = this._pushTransformed.bind(this);
this._transform.onError = this._errorTransformed.bind(this);
this._state = MovieRecordParserState.Parsing;
this._buffer.clear();
data = data.subarray(needToRead);
}
this._transform.push(data);
}

private _pushTransformed(data: Uint8Array): void {
this._buffer.compact();

var savedPosition = this._buffer.position;
Expand All @@ -326,21 +376,29 @@ module Shumway.GFX.Test {
}

public close() {
this._transform.close();
this._closed = true;
}

private _errorTransformed() {
console.warn('Error in SWFM stream');
this.close();
}

public readNextRecord(): MovieRecordType {
if (this._state === MovieRecordParserState.Initial) {
if (this._buffer.position + MovieHeaderSize > this._buffer.length) {
return MovieRecordType.Incomplete;
}
this._buffer.position += MovieHeaderSize;
this._comressionType = MovieCompressionType.None;
this._state = MovieRecordParserState.Parsing;
return MovieRecordType.Incomplete;
}
if (this._state === MovieRecordParserState.Ended) {
return MovieRecordType.None;
}

if (this._buffer.position >= this._buffer.length) {
return this._closed ? MovieRecordType.None : MovieRecordType.Incomplete;
if (this._closed) {
this._state = MovieRecordParserState.Ended;
return MovieRecordType.None;
}
return MovieRecordType.Incomplete;
}

if (this._buffer.position + MovieRecordHeaderSize > this._buffer.length) {
Expand Down
162 changes: 162 additions & 0 deletions utils/compressswfm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright 2015 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

var fs = require('fs');
var path = require('path');
var temp = require('temp');
var spawn = require('child_process').spawn;

// simple args parsing
var compressMethod = null;
var outputPath = null;
var inputPath = null;
for (var i = 2; i < process.argv.length;) {
var cmd = process.argv[i++];
switch (cmd) {
case '--lzma':
case '-z':
compressMethod = 'lzma';
break;
case '--zlib':
case '-c':
compressMethod = 'zlib';
break;
case '--out':
case '-o':
outputPath = process.argv[i++];
break;
default:
inputPath = cmd; // .swfm is expected
break;
}
}

function createRawSWFM(callback) {
// Create a temp file without 'MSWF' header.
fs.readFile(inputPath, function (err, data) {
if (err) return callback(err);
var header = data.slice(0, 4).toString();
if (header !== 'MSWF') {
return callback(new Error('swfm file header is not found: found \"' + header + '\"'));
}
temp.open('swfm', function (err, info) {
if (err) return callback(err);
fs.write(info.fd, data, 4, data.length - 4, function (err) {
if (err) return callback(err);
fs.close(info.fd, function (err) {
if (err) return callback(err);
callback(null, info.path);
});
});
});
});
}

function compressViaGZip(rawDataPath, compressedDataPath, callback) {
// Running gzip.
var proc = spawn('gzip', ['-k9n', rawDataPath]);
var resultPath = rawDataPath + '.gz';
proc.on('close', function (code) {
if (code !== 0 || !fs.existsSync(resultPath)) {
callback(new Error('Unable to run gzip'));
return;
}
// Prepending MSWC and zlib header before compressed data.
fs.writeFile(compressedDataPath, new Buffer("MSWC\u0078\u00DA", 'ascii'), function (err) {
if (err) return callback(err);
fs.readFile(resultPath, function (err, data) {
if (data[0] !== 0x1F || data[1] !== 0x8B || data[2] !== 0x08 || data[3] !== 0) {
return callback(new Error('Invalid gzip result'));
}
var dataStart = 10;
var dataEnd = data.length - 8;

// Appending adler32 at the end of data.
var a = 1, b = 0;
for (var i = dataStart; i < dataEnd; ++i) {
a = (a + (data[i] & 0xff)) % 65521;
b = (b + a) % 65521;
}
var adler32 = (b << 16) | a;
data[dataEnd] = (adler32 >> 24) & 255;
data[dataEnd + 1] = (adler32 >> 16) & 255;
data[dataEnd + 2] = (adler32 >> 8) & 255;
data[dataEnd + 3] = adler32 & 255;


if (err) return callback(err);
fs.appendFile(compressedDataPath, data.slice(dataStart, dataEnd + 4), function (err) {
fs.unlink(resultPath, callback);
});
});
});
});
}

function compressViaLzma(rawDataPath, compressedDataPath, callback) {
// Running lzma.
var proc = spawn('lzma', ['-zk9e', rawDataPath]);
var resultPath = rawDataPath + '.lzma';
proc.on('close', function (code) {
if (code !== 0 || !fs.existsSync(resultPath)) {
callback(new Error('Unable to run lzma'));
return;
}
// Prepending MSWZ before lzma compressed data.
fs.writeFile(compressedDataPath, "MSWZ", function (err) {
if (err) return callback(err);
fs.readFile(resultPath, function (err, data) {
if (err) return callback(err);
fs.appendFile(compressedDataPath, data, function (err) {
fs.unlink(resultPath, callback);
});
});
});
});
}

function printUsage() {
console.info('Usage: node compressswfm.js [-c|-z] inputFile [-o outputFile]');
}

if (!inputPath || !compressMethod) {
printUsage();
process.exit(1);
}

if (!outputPath) {
var ext = path.extname(inputPath);
outputPath = inputPath.slice(0, -ext.length) + '.' + compressMethod + ext;
console.log('Compressed results at ' + outputPath);
}

createRawSWFM(function (err, path) {
if (err) throw err;
switch (compressMethod) {
case 'zlib':
compressViaGZip(path, outputPath, function (err) {
if (err) throw err;
console.log('Done. Compressed using ZLib.');
});
break;
case 'lzma':
compressViaLzma(path, outputPath, function (err) {
if (err) throw err;
console.log('Done. Compressed using LZMA.');
});
break;
}
});

0 comments on commit f7e1fea

Please sign in to comment.