swf_recompress 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/swf_recompress +69 -0
- data/lib/kzip +0 -0
- data/lib/swf_recompress.rb +208 -0
- data/src/SWFExtract.class +0 -0
- data/src/SWFExtract.java +61 -0
- data/src/SWFInject.class +0 -0
- data/src/SWFInject.java +55 -0
- metadata +73 -0
data/bin/swf_recompress
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require File.join(File.dirname(__FILE__), '../lib/swf_recompress')
|
5
|
+
|
6
|
+
ACQUIRE_KZIP_TEXT = "Run with --acquire-kzip to download the kzip tool"
|
7
|
+
SWF_RECOMPRESS_BANNER = "swf_recompress version #{SWFRecompress::VERSION}#{SWFRecompress.kzip_available? ? '' : ', but no kzip installed'}"
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "#{SWF_RECOMPRESS_BANNER}\nRecompress a swf file with more aggressive DEFLATE settings\nUsage: swf_recompress [options] swf_filename [output_swf_filename]"
|
12
|
+
opts.on('-i', '--in-place', 'Compress the swf in-place, replacing the original file') do
|
13
|
+
options[:use_input_filename] = true
|
14
|
+
end
|
15
|
+
opts.on('-a', '--acquire-kzip', 'Download kzip tool') do
|
16
|
+
options[:acquire_kzip] = true
|
17
|
+
end
|
18
|
+
opts.on_tail('-v', '--version', 'Show version & contributors') do
|
19
|
+
puts SWF_RECOMPRESS_BANNER
|
20
|
+
puts " Based on Jos-Iven Hirth's \"Improving SWF Compression\": http://kaioa.com/node/87"
|
21
|
+
puts SWFRecompress::KZIP_ABOUT
|
22
|
+
if !SWFRecompress.kzip_available?
|
23
|
+
puts SWFRecompress::KZIP_INSTALL_TEXT
|
24
|
+
end
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
28
|
+
puts opts
|
29
|
+
puts "If only one filename is given and the -i flag is not supplied, a new swf"
|
30
|
+
puts "with the suffix '_compressed' will be created alongside the original file."
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end.parse!(ARGV)
|
34
|
+
|
35
|
+
options[:input_filename] = ARGV[0]
|
36
|
+
options[:output_filename] = ARGV[0] if options[:use_input_filename]
|
37
|
+
|
38
|
+
def require_kzip!
|
39
|
+
if !SWFRecompress.kzip_available?
|
40
|
+
$stderr.puts "No kzip installed"
|
41
|
+
$stderr.puts "#{ACQUIRE_KZIP_TEXT}\n#{SWFRecompress::KZIP_ABOUT}"
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
if options[:acquire_kzip]
|
48
|
+
if SWFRecompress.kzip_available?
|
49
|
+
puts "kzip already available"
|
50
|
+
else
|
51
|
+
SWFRecompress.acquire_kzip
|
52
|
+
puts "Installed lib/kzip"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if options[:output_filename]
|
57
|
+
require_kzip!
|
58
|
+
SWFRecompress.recompress_to(options[:input_filename], options[:output_filename])
|
59
|
+
elsif options[:input_filename]
|
60
|
+
require_kzip!
|
61
|
+
SWFRecompress.recompress(options[:input_filename])
|
62
|
+
elsif !options[:acquire_kzip]
|
63
|
+
$stderr.puts "Please specify a swf file to recompress"
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
rescue => e
|
67
|
+
$stderr.puts "There was an error recompressing #{options[:input_filename].inspect}\n#{e}"
|
68
|
+
exit 1
|
69
|
+
end
|
data/lib/kzip
ADDED
Binary file
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module SWFRecompress
|
2
|
+
VERSION = "0.0.6"
|
3
|
+
|
4
|
+
require 'fileutils'
|
5
|
+
require 'pathname'
|
6
|
+
require 'tempfile'
|
7
|
+
|
8
|
+
ROOT = File.join(File.dirname(__FILE__), '..')
|
9
|
+
TMP_DIR = Dir::tmpdir
|
10
|
+
|
11
|
+
KZIP_HOST = 'static.jonof.id.au'
|
12
|
+
KZIP_MD5 = 'fdbf05e2bd12b16e899df0f3b6a3e87d'
|
13
|
+
KZIP_PATH = '/dl/kenutils/kzipmix-20091108-darwin.tar.gz'
|
14
|
+
KZIP_ABOUT = <<-END_KZIP_ABOUT
|
15
|
+
kzip by Ken Silverman: http://advsys.net/ken/utils.htm
|
16
|
+
Mac OS X and Linux binaries maintained by Jonathan Fowler: http://www.jonof.id.au/
|
17
|
+
END_KZIP_ABOUT
|
18
|
+
KZIP_INSTALL_TEXT = " Install kzip binary to #{File.expand_path(File.join(ROOT, 'lib/kzip'))}"
|
19
|
+
|
20
|
+
class Tempfile
|
21
|
+
def self.open(temp_stem, write_mode = nil)
|
22
|
+
begin
|
23
|
+
instance_tmp_dir = nil
|
24
|
+
begin
|
25
|
+
instance_tmp_dir = File.join(TMP_DIR, rand(1_000_000).to_s)
|
26
|
+
end until !File.exists?(instance_tmp_dir)
|
27
|
+
FileUtils.mkdir_p(instance_tmp_dir)
|
28
|
+
ext = File.extname(temp_stem)
|
29
|
+
temp_filename = Pathname.new(
|
30
|
+
File.expand_path(File.join(instance_tmp_dir, '%s%s' % [ File.basename(temp_stem, ext), ext ]))
|
31
|
+
).relative_path_from(Pathname.new(Dir.pwd))
|
32
|
+
File.open(temp_filename, write_mode || 'w') do |f|
|
33
|
+
yield(f)
|
34
|
+
end
|
35
|
+
rescue => e
|
36
|
+
raise "Error during Tempfile open #{e.message}"
|
37
|
+
ensure
|
38
|
+
FileUtils.rm(temp_filename)
|
39
|
+
FileUtils.rm_rf(instance_tmp_dir)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class SWFRecompressor
|
45
|
+
attr_reader :data_filename, :data_zip_filename, :info_filename, :swf_filename, :output_filename
|
46
|
+
def initialize(swf_filename, output_filename)
|
47
|
+
if !File.exists?(swf_filename)
|
48
|
+
raise "The file #{swf_filename.inspect} does not exist"
|
49
|
+
end
|
50
|
+
@swf_filename = swf_filename
|
51
|
+
@output_filename = output_filename
|
52
|
+
end
|
53
|
+
|
54
|
+
def recompress!
|
55
|
+
with_tempfiles do
|
56
|
+
SWFRecompress.execute(swf_extract, kzip_data, swf_inject)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def swf_extract
|
62
|
+
java('SWFExtract', swf_filename, data_filename, info_filename)
|
63
|
+
end
|
64
|
+
|
65
|
+
def swf_inject
|
66
|
+
java('SWFInject', data_zip_filename, info_filename, output_filename)
|
67
|
+
end
|
68
|
+
|
69
|
+
def kzip_data
|
70
|
+
kzip('-y', '-k0', data_zip_filename, data_filename)
|
71
|
+
end
|
72
|
+
|
73
|
+
def with_tempfiles
|
74
|
+
Dir.chdir(ROOT) do
|
75
|
+
with_temp_info_file do
|
76
|
+
with_temp_data_file do
|
77
|
+
with_temp_data_zip_file do
|
78
|
+
yield
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def with_temp_info_file
|
86
|
+
Tempfile.open('INFO') do |f|
|
87
|
+
f.close
|
88
|
+
@info_filename = f.path
|
89
|
+
yield
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def with_temp_data_file
|
94
|
+
Tempfile.open('SWF_DATA') do |f|
|
95
|
+
f.close
|
96
|
+
@data_filename = f.path
|
97
|
+
yield
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def with_temp_data_zip_file
|
102
|
+
Tempfile.open('SWF_DATA.zip') do |f|
|
103
|
+
f.close
|
104
|
+
@data_zip_filename = f.path
|
105
|
+
yield
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def java(*args)
|
110
|
+
command('java', '-classpath', 'src', *args)
|
111
|
+
end
|
112
|
+
|
113
|
+
def kzip(*args)
|
114
|
+
command('lib/kzip', *args)
|
115
|
+
end
|
116
|
+
|
117
|
+
def zip(*args)
|
118
|
+
command('zip', *args)
|
119
|
+
end
|
120
|
+
|
121
|
+
def command(*args)
|
122
|
+
SWFRecompress.command(*args)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
module ClassMethods
|
127
|
+
def recompress(filename)
|
128
|
+
expanded_filename = File.expand_path(filename)
|
129
|
+
ext = File.extname(expanded_filename)
|
130
|
+
dirname = File.dirname(expanded_filename)
|
131
|
+
new_filename = File.join(dirname, '%s%s%s' % [ File.basename(expanded_filename, ext), '_compressed', ext ])
|
132
|
+
compressor = SWFRecompressor.new(expanded_filename, new_filename)
|
133
|
+
compressor.recompress!
|
134
|
+
end
|
135
|
+
|
136
|
+
def recompress_to(filename, new_filename)
|
137
|
+
expanded_filename = File.expand_path(filename)
|
138
|
+
compressor = SWFRecompressor.new(
|
139
|
+
File.expand_path(filename),
|
140
|
+
File.expand_path(new_filename))
|
141
|
+
compressor.recompress!
|
142
|
+
end
|
143
|
+
|
144
|
+
def execute(*ramulons)
|
145
|
+
execution = ramulons.join(' && ')
|
146
|
+
results = ramulons.map { |command| `#{command} 2&> /dev/null` }
|
147
|
+
puts results.join("\n")
|
148
|
+
results
|
149
|
+
end
|
150
|
+
|
151
|
+
def command(*args)
|
152
|
+
args.map { |arg| '"%s"' % arg }.join(' ')
|
153
|
+
end
|
154
|
+
|
155
|
+
def kzip_available?
|
156
|
+
@kzip_available ||= File.exists?('lib/kzip') && KZIP_MD5 == kzip_md5('lib/kzip')
|
157
|
+
end
|
158
|
+
|
159
|
+
def kzip_md5(kzip_filename)
|
160
|
+
require 'digest/md5'
|
161
|
+
Digest::MD5.hexdigest(File.read(kzip_filename))
|
162
|
+
end
|
163
|
+
|
164
|
+
def acquire_kzip
|
165
|
+
begin
|
166
|
+
Tempfile.open('kzipmix.tar.gz', 'wb') do |f|
|
167
|
+
begin
|
168
|
+
download_kzipmix(f)
|
169
|
+
rescue => e
|
170
|
+
raise "There was an error downloading kzipmix"
|
171
|
+
end
|
172
|
+
extracted_kzip_filename = extract_kzipmix(f)
|
173
|
+
if File.exists?(extracted_kzip_filename)
|
174
|
+
extracted_kzip_md5 = kzip_md5(extracted_zip_filename)
|
175
|
+
if KZIP_MD5 == extracted_kzip_md5
|
176
|
+
FileUtils.cp(extracted_kzip_filename, File.expand_path(File.join(ROOT, 'lib/kzip')))
|
177
|
+
else
|
178
|
+
raise "The MD5 of the downloaded kzip #{extracted_kzip_md5} did not match the expected MD5 #{KZIP_MD5}"
|
179
|
+
end
|
180
|
+
else
|
181
|
+
raise "Failed to extract kzip from the downloaded kzipmix archive"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
rescue => e
|
185
|
+
raise "Unable to acquire kzip utility: #{e.message}\n#{KZIP_ABOUT}#{KZIP_INSTALL_TEXT}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
def download_kzipmix(f)
|
191
|
+
require 'net/http'
|
192
|
+
Net::HTTP.start(KZIP_HOST) do |http|
|
193
|
+
f.write(http.get(KZIP_PATH).body)
|
194
|
+
f.close
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def extract_kzipmix(f)
|
199
|
+
fdir = Pathname.new(File.dirname(f.path))
|
200
|
+
Dir.chdir(fdir) do
|
201
|
+
execute(command('tar', 'xvzf', Pathname.new(f.path).relative_path_from(fdir), '--include', '*/kzip', '-s', '/.*kzip$/kzip/'))
|
202
|
+
end
|
203
|
+
File.join(File.dirname(f.path), 'kzip')
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
extend ClassMethods
|
208
|
+
end
|
Binary file
|
data/src/SWFExtract.java
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
import java.io.*;
|
2
|
+
import java.util.zip.*;
|
3
|
+
public class SWFExtract{
|
4
|
+
public static void main(String[]args)throws Exception{
|
5
|
+
String name = args[0];
|
6
|
+
String dataOutputFileName = args[1];
|
7
|
+
String infoOutputFileName = args[2];
|
8
|
+
File file = new File(name);
|
9
|
+
DataInputStream in = new DataInputStream(new FileInputStream(file));
|
10
|
+
|
11
|
+
boolean compressed = true;
|
12
|
+
byte first = in.readByte();
|
13
|
+
|
14
|
+
if (first == 0x46) //'F'
|
15
|
+
compressed = false;
|
16
|
+
else if (first != 0x43) //'C'
|
17
|
+
noSWF(name, "Not a swf file");
|
18
|
+
if (in.readByte() != 0x57) //'W'
|
19
|
+
noSWF(name, "Not a swf file");
|
20
|
+
if (in.readByte() != 0x53) //'S'
|
21
|
+
noSWF(name, "Not a swf file");
|
22
|
+
|
23
|
+
byte version = in.readByte();
|
24
|
+
int reportedFileSize = Integer.reverseBytes(in.readInt());
|
25
|
+
int actualFileSize = (int)file.length();
|
26
|
+
if (compressed) {
|
27
|
+
System.out.println("Initially compressed");
|
28
|
+
} else {
|
29
|
+
System.out.println("Initially uncompressed");
|
30
|
+
}
|
31
|
+
|
32
|
+
DataOutputStream out=new DataOutputStream(new FileOutputStream(dataOutputFileName));
|
33
|
+
InputStream in2;
|
34
|
+
if(compressed)
|
35
|
+
in2=new InflaterInputStream(in);
|
36
|
+
else
|
37
|
+
in2=new DataInputStream(in);
|
38
|
+
byte[]data=new byte[4096];
|
39
|
+
int r;
|
40
|
+
Adler32 a32=new Adler32();
|
41
|
+
while((r=in2.read(data))!=-1){
|
42
|
+
out.write(data,0,r);
|
43
|
+
a32.update(data,0,r);
|
44
|
+
}
|
45
|
+
|
46
|
+
in2.close();
|
47
|
+
out.flush();
|
48
|
+
out.close();
|
49
|
+
|
50
|
+
DataOutputStream iout=new DataOutputStream(new FileOutputStream(infoOutputFileName));
|
51
|
+
iout.write(version);
|
52
|
+
iout.writeInt((int)a32.getValue());
|
53
|
+
iout.writeLong(file.length());
|
54
|
+
iout.flush();
|
55
|
+
iout.close();
|
56
|
+
}
|
57
|
+
private static void noSWF(String filename, String message){
|
58
|
+
System.err.printf("Could not compress swf [%s]: %s", filename, message);
|
59
|
+
System.exit(1);
|
60
|
+
}
|
61
|
+
}
|
data/src/SWFInject.class
ADDED
Binary file
|
data/src/SWFInject.java
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
import java.io.*;
|
2
|
+
import java.util.zip.*;
|
3
|
+
import java.text.*;
|
4
|
+
public class SWFInject{
|
5
|
+
public static void main(String[]args)throws Exception{
|
6
|
+
String zipInputFileName = args[0];
|
7
|
+
String infoInputFileName = args[1];
|
8
|
+
String outputFileName = args[2];
|
9
|
+
|
10
|
+
DataInputStream iin=new DataInputStream(new FileInputStream(infoInputFileName));
|
11
|
+
byte version = iin.readByte();
|
12
|
+
int a32 = iin.readInt();
|
13
|
+
long oldLength = iin.readLong();
|
14
|
+
iin.close();
|
15
|
+
|
16
|
+
File outFile = new File(outputFileName);
|
17
|
+
DataInputStream in = new DataInputStream(new FileInputStream(zipInputFileName));
|
18
|
+
DataOutputStream out = new DataOutputStream(new FileOutputStream(outFile));
|
19
|
+
|
20
|
+
// Skip zipfile header
|
21
|
+
in.skipBytes(4 + 2 + 2 + 2 + 2 + 2 + 4);
|
22
|
+
int compressedSize = Integer.reverseBytes(in.readInt());
|
23
|
+
int uncompressedSize = Integer.reverseBytes(in.readInt());
|
24
|
+
// Skip some more
|
25
|
+
in.skipBytes(2 + 2 + 8);
|
26
|
+
|
27
|
+
byte[]data = new byte[compressedSize];
|
28
|
+
in.readFully(data);
|
29
|
+
in.close();
|
30
|
+
int newSize = 1 + 1 + 1 // Header fields CWS
|
31
|
+
+ 1 // SWF version
|
32
|
+
+ 4 // File length
|
33
|
+
+ 2 // For DEFLATE - maximum compression sigil
|
34
|
+
+ uncompressedSize
|
35
|
+
+ 4; // Adler32 checksum
|
36
|
+
|
37
|
+
out.write(0x43); //'C'
|
38
|
+
out.write(0x57); //'W'
|
39
|
+
out.write(0x53); //'S'
|
40
|
+
out.write(version < 6 ? 6 : version);
|
41
|
+
out.writeInt(Integer.reverseBytes(newSize));
|
42
|
+
|
43
|
+
out.writeShort(0x78DA);
|
44
|
+
out.write(data, 0, data.length);
|
45
|
+
out.writeInt(a32);
|
46
|
+
out.flush();
|
47
|
+
out.close();
|
48
|
+
|
49
|
+
DecimalFormat byteFormat = new DecimalFormat("###,###");
|
50
|
+
long newLength = outFile.length();
|
51
|
+
System.out.printf("old Length: %10s\n",byteFormat.format(oldLength));
|
52
|
+
System.out.printf("new Length: %10s\n",byteFormat.format(newLength));
|
53
|
+
System.out.printf("saved : %10s (%.2f%%)\n",byteFormat.format(oldLength-newLength),100-(100.0/oldLength*newLength));
|
54
|
+
}
|
55
|
+
}
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: swf_recompress
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 6
|
10
|
+
version: 0.0.6
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Duncan Beevers
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-05-21 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Recompress a swf file with more aggressive DEFLATE settings
|
23
|
+
email: duncan@dweebd.com
|
24
|
+
executables:
|
25
|
+
- swf_recompress
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/kzip
|
32
|
+
- lib/swf_recompress.rb
|
33
|
+
- src/SWFExtract.class
|
34
|
+
- src/SWFExtract.java
|
35
|
+
- src/SWFInject.class
|
36
|
+
- src/SWFInject.java
|
37
|
+
- bin/swf_recompress
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/duncanbeevers/swf_recompress
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
hash: 3
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
requirements:
|
66
|
+
- none
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.3.7
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Recompress a swf file with more aggressive DEFLATE settings
|
72
|
+
test_files: []
|
73
|
+
|