ruby-imagespec 0.3.1 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/image_spec.rb +5 -1
- data/lib/image_spec/parser.rb +18 -0
- data/lib/image_spec/parser/bmp.rb +24 -0
- data/lib/image_spec/parser/gif.rb +24 -0
- data/lib/image_spec/parser/jpeg.rb +65 -0
- data/lib/image_spec/parser/png.rb +24 -0
- data/lib/image_spec/parser/swf.rb +76 -0
- metadata +43 -18
- data/lib/parser.rb +0 -21
- data/lib/parser/gif.rb +0 -29
- data/lib/parser/jpeg.rb +0 -69
- data/lib/parser/png.rb +0 -29
- data/lib/parser/swf.rb +0 -78
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b959efb6a395dbe1ba3d6ffc3716ebb40489839
|
4
|
+
data.tar.gz: 6c556dded2c7788fe947855eec6afc736c5f4008
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 21ae732a659fb3b433d84e466a3ce83cc565798251216363384f7ff61a43b17e8968fa9ccc59c39bf731c609ca0a9398e706c3c83e468dec25e6bcf787221e1d
|
7
|
+
data.tar.gz: 2add58ef7912adefbc39edbf06dc6b4b17628af5d00548e141f8f7fbf8aa4e8b8c71fc484fb17934b83542411a6b941fadec71c129803c152e924e58be7e960c
|
data/lib/image_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'open-uri'
|
2
|
-
require File.join(File.dirname(__FILE__), 'parser')
|
3
2
|
|
4
3
|
class ImageSpec
|
4
|
+
Error = Class.new(StandardError)
|
5
5
|
|
6
6
|
def initialize(file)
|
7
7
|
@attributes = Parser.parse(stream_for(file))
|
@@ -28,3 +28,7 @@ class ImageSpec
|
|
28
28
|
end
|
29
29
|
|
30
30
|
end
|
31
|
+
|
32
|
+
%w|parser|.each do |name|
|
33
|
+
require "image_spec/#{name}"
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ImageSpec::Parser
|
2
|
+
|
3
|
+
def self.formats
|
4
|
+
@@formats ||= constants.collect { |format| const_get(format) }
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.parse(stream)
|
8
|
+
formats.each do |format|
|
9
|
+
return format.attributes(stream) if format.detected?(stream)
|
10
|
+
end
|
11
|
+
raise ImageSpec::Error, "#{stream.inspect} is not a supported image format. Sorry bub :("
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
%w|gif jpeg png swf bmp|.each do |name|
|
17
|
+
require "image_spec/parser/#{name}"
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
class ImageSpec::Parser::BMP
|
3
|
+
CONTENT_TYPE = 'image/bmp'
|
4
|
+
|
5
|
+
def self.attributes(stream)
|
6
|
+
width, height = dimensions(stream)
|
7
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.detected?(stream)
|
11
|
+
stream.rewind
|
12
|
+
stream.read(2) == 'BM'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.dimensions(stream)
|
16
|
+
stream.seek(18)
|
17
|
+
stream.read(8).unpack('LL')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.size(stream)
|
21
|
+
stream.size
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
class ImageSpec::Parser::GIF
|
3
|
+
CONTENT_TYPE = 'image/gif'
|
4
|
+
|
5
|
+
def self.attributes(stream)
|
6
|
+
width, height = dimensions(stream)
|
7
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.detected?(stream)
|
11
|
+
stream.rewind
|
12
|
+
stream.read(4) == 'GIF8'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.dimensions(stream)
|
16
|
+
stream.seek(6)
|
17
|
+
stream.read(4).unpack('SS')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.size(stream)
|
21
|
+
stream.size
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
class ImageSpec::Parser::JPEG
|
3
|
+
|
4
|
+
CONTENT_TYPE = 'image/jpeg'
|
5
|
+
TYPE_JFIF = Regexp.new '^\xff\xd8\xff\xe0\x00\x10JFIF', nil, 'n'
|
6
|
+
TYPE_EXIF = Regexp.new '^\xff\xd8\xff\xe1(.*){2}Exif', nil, 'n'
|
7
|
+
|
8
|
+
def self.attributes(stream)
|
9
|
+
width, height = dimensions(stream)
|
10
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.detected?(stream)
|
14
|
+
stream.rewind
|
15
|
+
case stream.read(10)
|
16
|
+
when TYPE_JFIF, TYPE_EXIF then true
|
17
|
+
else false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.dimensions(stream)
|
22
|
+
stream.rewind
|
23
|
+
raise ImageSpec::Error, 'malformed JPEG' unless stream.readbyte.chr == "\xFF" && stream.readbyte.chr == "\xD8" # SOI
|
24
|
+
|
25
|
+
class << stream
|
26
|
+
def readint
|
27
|
+
(readbyte.ord << 8) + readbyte.ord
|
28
|
+
end
|
29
|
+
|
30
|
+
def readframe
|
31
|
+
read(readint - 2)
|
32
|
+
end
|
33
|
+
|
34
|
+
def readsof
|
35
|
+
[readint, readbyte.chr, readint, readint, readbyte.chr]
|
36
|
+
end
|
37
|
+
|
38
|
+
def next
|
39
|
+
c = readbyte.chr while c != "\xFF"
|
40
|
+
c = readbyte.chr while c == "\xFF"
|
41
|
+
c
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
while marker = stream.next
|
46
|
+
case marker
|
47
|
+
when "\xC0".."\xC3", "\xC5".."\xC7", "\xC9".."\xCB", "\xCD".."\xCF"
|
48
|
+
length, bits, height, width, components = stream.readsof
|
49
|
+
raise ImageSpec::Error, 'malformed JPEG' unless length == 8 + components[0].ord * 3
|
50
|
+
return [width, height]
|
51
|
+
when "\xD9", "\xDA"
|
52
|
+
break
|
53
|
+
when "\xFE"
|
54
|
+
@comment = stream.readframe
|
55
|
+
else
|
56
|
+
stream.readframe
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.size(stream)
|
62
|
+
stream.size
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
class ImageSpec::Parser::PNG
|
3
|
+
CONTENT_TYPE = 'image/png'
|
4
|
+
|
5
|
+
def self.attributes(stream)
|
6
|
+
width, height = dimensions(stream)
|
7
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.detected?(stream)
|
11
|
+
stream.rewind
|
12
|
+
stream.read(4) == "\x89PNG"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.dimensions(stream)
|
16
|
+
stream.seek(0x10)
|
17
|
+
stream.read(8).unpack('NN')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.size(stream)
|
21
|
+
stream.size
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
class ImageSpec::Parser::SWF
|
5
|
+
CONTENT_TYPE = 'application/x-shockwave-flash'
|
6
|
+
|
7
|
+
def self.attributes(stream)
|
8
|
+
width, height, version = dimensions(stream)
|
9
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :version => version, :dimensions => [width, height], :file_size => size(stream)}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.detected?(stream)
|
13
|
+
stream.rewind
|
14
|
+
stream.read(3) =~ /(F|C)WS/ ? true : false
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.dimensions(stream)
|
18
|
+
# Read the entire stream into memory because the
|
19
|
+
# dimensions aren't stored in a standard location
|
20
|
+
stream.rewind
|
21
|
+
contents = stream.read.force_encoding("ASCII-8BIT")
|
22
|
+
|
23
|
+
# Our 'signature' is the first 3 bytes
|
24
|
+
# Either FWS or CWS. CWS indicates compression
|
25
|
+
signature = contents[0..2]
|
26
|
+
|
27
|
+
# SWF version
|
28
|
+
version = contents[3].unpack('C').join.to_i
|
29
|
+
|
30
|
+
# Determine the length of the uncompressed stream
|
31
|
+
length = contents[4..7].unpack('V').join.to_i
|
32
|
+
|
33
|
+
# If we do, in fact, have compression
|
34
|
+
if signature == 'CWS'
|
35
|
+
# Decompress the body of the SWF
|
36
|
+
body = Zlib::Inflate.inflate( contents[8..length] )
|
37
|
+
|
38
|
+
# And reconstruct the stream contents to the first 8 bytes (header)
|
39
|
+
# Plus our decompressed body
|
40
|
+
contents = contents[0..7] + body
|
41
|
+
end
|
42
|
+
|
43
|
+
# Determine the nbits of our dimensions rectangle
|
44
|
+
nbits = contents.unpack('C'*contents.length)[8] >> 3
|
45
|
+
|
46
|
+
# Determine how many bits long this entire RECT structure is
|
47
|
+
rectbits = 5 + nbits * 4 # 5 bits for nbits, as well as nbits * number of fields (4)
|
48
|
+
|
49
|
+
# Determine how many bytes rectbits composes (ceil(rectbits/8))
|
50
|
+
rectbytes = (rectbits.to_f / 8).ceil
|
51
|
+
|
52
|
+
# Unpack the RECT structure from the stream in little-endian bit order, then join it into a string
|
53
|
+
rect = contents[8..(8 + rectbytes)].unpack("#{'B8' * rectbytes}").join()
|
54
|
+
|
55
|
+
# Read in nbits incremenets starting from 5
|
56
|
+
dimensions = Array.new
|
57
|
+
4.times do |n|
|
58
|
+
s = 5 + (n * nbits) # Calculate our start index
|
59
|
+
e = s + (nbits - 1) # Calculate our end index
|
60
|
+
dimensions[n] = rect[s..e].to_i(2) # Read that range (binary) and convert it to an integer
|
61
|
+
end
|
62
|
+
|
63
|
+
# The values we have here are in "twips"
|
64
|
+
# 20 twips to a pixel (that's why SWFs are fuzzy sometimes!)
|
65
|
+
width = (dimensions[1] - dimensions[0]) / 20
|
66
|
+
height = (dimensions[3] - dimensions[2]) / 20
|
67
|
+
|
68
|
+
# If you can't figure this one out, you probably shouldn't have read this far
|
69
|
+
return [width, height, version]
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.size(stream)
|
73
|
+
stream.size
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-imagespec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Brandon Anderson
|
@@ -12,22 +11,48 @@ authors:
|
|
12
11
|
autorequire:
|
13
12
|
bindir: bin
|
14
13
|
cert_chain: []
|
15
|
-
date:
|
14
|
+
date: 2015-06-23 00:00:00.000000000 Z
|
16
15
|
dependencies:
|
17
16
|
- !ruby/object:Gem::Dependency
|
18
17
|
name: rake
|
19
18
|
requirement: !ruby/object:Gem::Requirement
|
20
|
-
none: false
|
21
19
|
requirements:
|
22
|
-
- -
|
20
|
+
- - ">="
|
23
21
|
- !ruby/object:Gem::Version
|
24
22
|
version: '0'
|
25
23
|
type: :development
|
26
24
|
prerelease: false
|
27
25
|
version_requirements: !ruby/object:Gem::Requirement
|
28
|
-
none: false
|
29
26
|
requirements:
|
30
|
-
- -
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: test-unit
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
31
56
|
- !ruby/object:Gem::Version
|
32
57
|
version: '0'
|
33
58
|
description:
|
@@ -38,35 +63,35 @@ extra_rdoc_files: []
|
|
38
63
|
files:
|
39
64
|
- README
|
40
65
|
- init.rb
|
41
|
-
- lib/parser/gif.rb
|
42
|
-
- lib/parser/png.rb
|
43
|
-
- lib/parser/swf.rb
|
44
|
-
- lib/parser/jpeg.rb
|
45
66
|
- lib/image_spec.rb
|
46
|
-
- lib/parser.rb
|
67
|
+
- lib/image_spec/parser.rb
|
68
|
+
- lib/image_spec/parser/bmp.rb
|
69
|
+
- lib/image_spec/parser/gif.rb
|
70
|
+
- lib/image_spec/parser/jpeg.rb
|
71
|
+
- lib/image_spec/parser/png.rb
|
72
|
+
- lib/image_spec/parser/swf.rb
|
47
73
|
homepage: http://github.com/dim/ruby-imagespec
|
48
74
|
licenses: []
|
75
|
+
metadata: {}
|
49
76
|
post_install_message:
|
50
77
|
rdoc_options: []
|
51
78
|
require_paths:
|
52
79
|
- lib
|
53
80
|
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
-
none: false
|
55
81
|
requirements:
|
56
|
-
- -
|
82
|
+
- - ">="
|
57
83
|
- !ruby/object:Gem::Version
|
58
84
|
version: 1.9.1
|
59
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
-
none: false
|
61
86
|
requirements:
|
62
|
-
- -
|
87
|
+
- - ">="
|
63
88
|
- !ruby/object:Gem::Version
|
64
89
|
version: 1.3.6
|
65
90
|
requirements: []
|
66
91
|
rubyforge_project:
|
67
|
-
rubygems_version:
|
92
|
+
rubygems_version: 2.4.7
|
68
93
|
signing_key:
|
69
|
-
specification_version:
|
94
|
+
specification_version: 4
|
70
95
|
summary: Image/Flash extract width/height dimensions extractor
|
71
96
|
test_files: []
|
72
97
|
has_rdoc:
|
data/lib/parser.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
Dir[File.join(File.dirname(__FILE__), 'parser/*.rb')].each { |parser| require parser }
|
2
|
-
|
3
|
-
class ImageSpec
|
4
|
-
Error = Class.new(StandardError)
|
5
|
-
|
6
|
-
module Parser
|
7
|
-
|
8
|
-
def self.formats
|
9
|
-
@@formats ||= constants.collect { |format| const_get(format) }
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.parse(stream)
|
13
|
-
formats.each do |format|
|
14
|
-
return format.attributes(stream) if format.detected?(stream)
|
15
|
-
end
|
16
|
-
raise Error, "#{stream.inspect} is not a supported image format. Sorry bub :("
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
data/lib/parser/gif.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
class ImageSpec
|
3
|
-
module Parser
|
4
|
-
class GIF
|
5
|
-
|
6
|
-
CONTENT_TYPE = 'image/gif'
|
7
|
-
|
8
|
-
def self.attributes(stream)
|
9
|
-
width, height = dimensions(stream)
|
10
|
-
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.detected?(stream)
|
14
|
-
stream.rewind
|
15
|
-
stream.read(4) == 'GIF8'
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.dimensions(stream)
|
19
|
-
stream.seek(6)
|
20
|
-
stream.read(4).unpack('SS')
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.size(stream)
|
24
|
-
stream.size
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/parser/jpeg.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
class ImageSpec
|
3
|
-
module Parser
|
4
|
-
class JPEG
|
5
|
-
|
6
|
-
CONTENT_TYPE = 'image/jpeg'
|
7
|
-
TYPE_JFIF = Regexp.new '^\xff\xd8\xff\xe0\x00\x10JFIF', nil, 'n'
|
8
|
-
TYPE_EXIF = Regexp.new '^\xff\xd8\xff\xe1(.*){2}Exif', nil, 'n'
|
9
|
-
|
10
|
-
def self.attributes(stream)
|
11
|
-
width, height = dimensions(stream)
|
12
|
-
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.detected?(stream)
|
16
|
-
stream.rewind
|
17
|
-
case stream.read(10)
|
18
|
-
when TYPE_JFIF, TYPE_EXIF then true
|
19
|
-
else false
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.dimensions(stream)
|
24
|
-
stream.rewind
|
25
|
-
raise ImageSpec::Error, 'malformed JPEG' unless stream.readbyte.chr == "\xFF" && stream.readbyte.chr == "\xD8" # SOI
|
26
|
-
|
27
|
-
class << stream
|
28
|
-
def readint
|
29
|
-
(readbyte.ord << 8) + readbyte.ord
|
30
|
-
end
|
31
|
-
|
32
|
-
def readframe
|
33
|
-
read(readint - 2)
|
34
|
-
end
|
35
|
-
|
36
|
-
def readsof
|
37
|
-
[readint, readbyte.chr, readint, readint, readbyte.chr]
|
38
|
-
end
|
39
|
-
|
40
|
-
def next
|
41
|
-
c = readbyte.chr while c != "\xFF"
|
42
|
-
c = readbyte.chr while c == "\xFF"
|
43
|
-
c
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
while marker = stream.next
|
48
|
-
case marker
|
49
|
-
when "\xC0".."\xC3", "\xC5".."\xC7", "\xC9".."\xCB", "\xCD".."\xCF"
|
50
|
-
length, bits, height, width, components = stream.readsof
|
51
|
-
raise ImageSpec::Error, 'malformed JPEG' unless length == 8 + components[0].ord * 3
|
52
|
-
return [width, height]
|
53
|
-
when "\xD9", "\xDA"
|
54
|
-
break
|
55
|
-
when "\xFE"
|
56
|
-
@comment = stream.readframe
|
57
|
-
else
|
58
|
-
stream.readframe
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.size(stream)
|
64
|
-
stream.size
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/lib/parser/png.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
class ImageSpec
|
3
|
-
module Parser
|
4
|
-
class PNG
|
5
|
-
|
6
|
-
CONTENT_TYPE = 'image/png'
|
7
|
-
|
8
|
-
def self.attributes(stream)
|
9
|
-
width, height = dimensions(stream)
|
10
|
-
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.detected?(stream)
|
14
|
-
stream.rewind
|
15
|
-
stream.read(4) == "\x89PNG"
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.dimensions(stream)
|
19
|
-
stream.seek(0x10)
|
20
|
-
stream.read(8).unpack('NN')
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.size(stream)
|
24
|
-
stream.size
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/parser/swf.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
require 'zlib'
|
3
|
-
|
4
|
-
class ImageSpec
|
5
|
-
module Parser
|
6
|
-
class SWF
|
7
|
-
|
8
|
-
CONTENT_TYPE = 'application/x-shockwave-flash'
|
9
|
-
|
10
|
-
def self.attributes(stream)
|
11
|
-
width, height = dimensions(stream)
|
12
|
-
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.detected?(stream)
|
16
|
-
stream.rewind
|
17
|
-
stream.read(3) =~ /(F|C)WS/ ? true : false
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.dimensions(stream)
|
21
|
-
# Read the entire stream into memory because the
|
22
|
-
# dimensions aren't stored in a standard location
|
23
|
-
stream.rewind
|
24
|
-
contents = stream.read.force_encoding("ASCII-8BIT")
|
25
|
-
|
26
|
-
# Our 'signature' is the first 3 bytes
|
27
|
-
# Either FWS or CWS. CWS indicates compression
|
28
|
-
signature = contents[0..2]
|
29
|
-
|
30
|
-
# Determine the length of the uncompressed stream
|
31
|
-
length = contents[4..7].unpack('V').join.to_i
|
32
|
-
|
33
|
-
# If we do, in fact, have compression
|
34
|
-
if signature == 'CWS'
|
35
|
-
# Decompress the body of the SWF
|
36
|
-
body = Zlib::Inflate.inflate( contents[8..length] )
|
37
|
-
|
38
|
-
# And reconstruct the stream contents to the first 8 bytes (header)
|
39
|
-
# Plus our decompressed body
|
40
|
-
contents = contents[0..7] + body
|
41
|
-
end
|
42
|
-
|
43
|
-
# Determine the nbits of our dimensions rectangle
|
44
|
-
nbits =contents.unpack('c'*contents.length)[8] >> 3
|
45
|
-
|
46
|
-
# Determine how many bits long this entire RECT structure is
|
47
|
-
rectbits = 5 + nbits * 4 # 5 bits for nbits, as well as nbits * number of fields (4)
|
48
|
-
|
49
|
-
# Determine how many bytes rectbits composes (ceil(rectbits/8))
|
50
|
-
rectbytes = (rectbits.to_f / 8).ceil
|
51
|
-
|
52
|
-
# Unpack the RECT structure from the stream in little-endian bit order, then join it into a string
|
53
|
-
rect = contents[8..(8 + rectbytes)].unpack("#{'B8' * rectbytes}").join()
|
54
|
-
|
55
|
-
# Read in nbits incremenets starting from 5
|
56
|
-
dimensions = Array.new
|
57
|
-
4.times do |n|
|
58
|
-
s = 5 + (n * nbits) # Calculate our start index
|
59
|
-
e = s + (nbits - 1) # Calculate our end index
|
60
|
-
dimensions[n] = rect[s..e].to_i(2) # Read that range (binary) and convert it to an integer
|
61
|
-
end
|
62
|
-
|
63
|
-
# The values we have here are in "twips"
|
64
|
-
# 20 twips to a pixel (that's why SWFs are fuzzy sometimes!)
|
65
|
-
width = (dimensions[1] - dimensions[0]) / 20
|
66
|
-
height = (dimensions[3] - dimensions[2]) / 20
|
67
|
-
|
68
|
-
# If you can't figure this one out, you probably shouldn't have read this far
|
69
|
-
return [width, height]
|
70
|
-
end
|
71
|
-
|
72
|
-
def self.size(stream)
|
73
|
-
stream.size
|
74
|
-
end
|
75
|
-
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|