bulldog 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +7 -0
- data/lib/bulldog/attachment/base.rb +3 -6
- data/lib/bulldog/attachment/has_dimensions.rb +43 -34
- data/lib/bulldog/attachment/image.rb +5 -26
- data/lib/bulldog/attachment/pdf.rb +3 -28
- data/lib/bulldog/attachment/video.rb +48 -45
- data/lib/bulldog/style.rb +28 -1
- data/lib/bulldog/version.rb +1 -1
- data/spec/data/3-bytes.txt +1 -0
- data/spec/data/4-bytes.txt +1 -0
- data/spec/data/5-bytes.txt +1 -0
- data/spec/data/6-bytes.txt +1 -0
- data/spec/data/test-20x10.jpg +0 -0
- data/spec/data/test-20x10.pdf +0 -0
- data/spec/data/test-20x10x1.mov +0 -0
- data/spec/data/test-40x30.jpg +0 -0
- data/spec/data/test-40x30.pdf +0 -0
- data/spec/data/test-40x30x1.mov +0 -0
- data/spec/helpers/files.rb +123 -0
- data/spec/integration/lifecycle_hooks_spec.rb +1 -1
- data/spec/integration/processing_image_attachments.rb +1 -13
- data/spec/macros/attachment/has_dimensions_spec.rb +313 -0
- data/spec/spec_helper.rb +3 -4
- data/spec/unit/attachment/base_spec.rb +25 -45
- data/spec/unit/attachment/image_spec.rb +48 -171
- data/spec/unit/attachment/maybe_spec.rb +4 -12
- data/spec/unit/attachment/pdf_spec.rb +18 -136
- data/spec/unit/attachment/video_spec.rb +98 -170
- data/spec/unit/attachment_spec.rb +1 -1
- data/spec/unit/has_attachment_spec.rb +29 -26
- data/spec/unit/interpolation_spec.rb +2 -2
- data/spec/unit/processor/ffmpeg_spec.rb +3 -3
- data/spec/unit/processor/image_magick_spec.rb +1 -1
- data/spec/unit/processor/one_shot_spec.rb +1 -1
- data/spec/unit/stream_spec.rb +3 -3
- data/spec/unit/style_spec.rb +40 -0
- data/spec/unit/validations_spec.rb +33 -33
- metadata +28 -8
- data/spec/helpers/temporary_directory.rb +0 -25
- data/spec/helpers/test_upload_files.rb +0 -108
data/CHANGELOG
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== 0.2.0 2010-07-14
|
2
|
+
|
3
|
+
* Remove dimensions and aspect ratio as a storable attributes. Store
|
4
|
+
width and height instead.
|
5
|
+
* Videos return 0 tracks if the file is missing (but still dimensions
|
6
|
+
of 2x2 so thumbnail sizes can be calculated).
|
7
|
+
|
1
8
|
== 0.1.1 2010-06-30
|
2
9
|
|
3
10
|
* Create output directory when recording frames if necessary.
|
@@ -143,13 +143,10 @@ module Bulldog
|
|
143
143
|
end
|
144
144
|
|
145
145
|
#
|
146
|
-
# Return the value of the given
|
147
|
-
#
|
146
|
+
# Return the value of the given instance variable, running a
|
147
|
+
# file examination first if necessary.
|
148
148
|
#
|
149
|
-
|
150
|
-
#
|
151
|
-
def from_examination(name)
|
152
|
-
ivar = :"@#{name}"
|
149
|
+
def from_examination(ivar)
|
153
150
|
value = instance_variable_get(ivar) and
|
154
151
|
return value
|
155
152
|
examine
|
@@ -10,10 +10,8 @@ module Bulldog
|
|
10
10
|
def self.included(base)
|
11
11
|
super
|
12
12
|
base.class_eval do
|
13
|
-
storable_attribute :width
|
14
|
-
storable_attribute :height
|
15
|
-
storable_attribute :aspect_ratio, :per_style => true, :memoize => true
|
16
|
-
storable_attribute :dimensions , :per_style => true, :memoize => true, :cast => true
|
13
|
+
storable_attribute :width , :per_style => true, :memoize => true
|
14
|
+
storable_attribute :height, :per_style => true, :memoize => true
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
@@ -22,8 +20,13 @@ module Bulldog
|
|
22
20
|
#
|
23
21
|
# +style_name+ defaults to the attribute's #default_style.
|
24
22
|
#
|
25
|
-
def width(style_name)
|
26
|
-
|
23
|
+
def width(style_name=nil)
|
24
|
+
style_name ||= reflection.default_style
|
25
|
+
if style_name.equal?(:original)
|
26
|
+
from_examination :@original_width
|
27
|
+
else
|
28
|
+
dimensions(style_name).at(0)
|
29
|
+
end
|
27
30
|
end
|
28
31
|
|
29
32
|
#
|
@@ -31,8 +34,13 @@ module Bulldog
|
|
31
34
|
#
|
32
35
|
# +style_name+ defaults to the attribute's #default_style.
|
33
36
|
#
|
34
|
-
def height(style_name)
|
35
|
-
|
37
|
+
def height(style_name=nil)
|
38
|
+
style_name ||= reflection.default_style
|
39
|
+
if style_name.equal?(:original)
|
40
|
+
from_examination :@original_height
|
41
|
+
else
|
42
|
+
dimensions(style_name).at(1)
|
43
|
+
end
|
36
44
|
end
|
37
45
|
|
38
46
|
#
|
@@ -40,55 +48,56 @@ module Bulldog
|
|
40
48
|
#
|
41
49
|
# +style_name+ defaults to the attribute's #default_style.
|
42
50
|
#
|
43
|
-
def aspect_ratio(style_name)
|
44
|
-
|
51
|
+
def aspect_ratio(style_name=nil)
|
52
|
+
style_name ||= reflection.default_style
|
53
|
+
if style_name.equal?(:original)
|
54
|
+
original_width = from_examination(:@original_width)
|
55
|
+
original_height = from_examination(:@original_height)
|
56
|
+
original_width.to_f / original_height
|
57
|
+
else
|
58
|
+
w, h = *dimensions(style_name)
|
59
|
+
w.to_f / h
|
60
|
+
end
|
45
61
|
end
|
46
62
|
|
47
63
|
#
|
48
64
|
# Return the width and height of the named style, as a 2-element
|
49
65
|
# array.
|
50
66
|
#
|
51
|
-
def dimensions
|
52
|
-
|
67
|
+
def dimensions(style_name=nil)
|
68
|
+
style_name ||= reflection.default_style
|
69
|
+
if style_name.equal?(:original)
|
70
|
+
original_width = from_examination(:@original_width)
|
71
|
+
original_height = from_examination(:@original_height)
|
72
|
+
[original_width, original_height]
|
73
|
+
else
|
74
|
+
resize_dimensions(dimensions(:original), reflection.styles[style_name])
|
75
|
+
end
|
53
76
|
end
|
54
77
|
|
55
78
|
protected # -----------------------------------------------------
|
56
79
|
|
57
80
|
#
|
58
81
|
# Return the dimensions, as an array [width, height], that
|
59
|
-
# result from resizing +original_dimensions+
|
60
|
-
# +
|
61
|
-
# will fill the target box. Otherwise the aspect ratio will be
|
62
|
-
# maintained.
|
82
|
+
# result from resizing +original_dimensions+ for the given
|
83
|
+
# +style+.
|
63
84
|
#
|
64
|
-
def
|
65
|
-
if
|
66
|
-
|
85
|
+
def resize_dimensions(original_dimensions, style)
|
86
|
+
if style.filled?
|
87
|
+
style.dimensions
|
67
88
|
else
|
68
89
|
original_aspect_ratio = original_dimensions[0].to_f / original_dimensions[1]
|
69
|
-
target_aspect_ratio =
|
90
|
+
target_aspect_ratio = style.dimensions[0].to_f / style.dimensions[1]
|
70
91
|
if original_aspect_ratio > target_aspect_ratio
|
71
|
-
width =
|
92
|
+
width = style.dimensions[0]
|
72
93
|
height = (width / original_aspect_ratio).round
|
73
94
|
else
|
74
|
-
height =
|
95
|
+
height = style.dimensions[1]
|
75
96
|
width = (height * original_aspect_ratio).round
|
76
97
|
end
|
77
98
|
[width, height]
|
78
99
|
end
|
79
100
|
end
|
80
|
-
|
81
|
-
private # -----------------------------------------------------
|
82
|
-
|
83
|
-
def serialize_dimensions(dimensions)
|
84
|
-
return nil if dimensions.blank?
|
85
|
-
dimensions.join('x')
|
86
|
-
end
|
87
|
-
|
88
|
-
def deserialize_dimensions(string)
|
89
|
-
return nil if string.blank?
|
90
|
-
string.scan(/\d+/).map{|s| s.to_i}
|
91
|
-
end
|
92
101
|
end
|
93
102
|
end
|
94
103
|
end
|
@@ -2,33 +2,11 @@ module Bulldog
|
|
2
2
|
module Attachment
|
3
3
|
class Image < Base
|
4
4
|
handle :image
|
5
|
-
|
6
|
-
#
|
7
|
-
# Return the width and height of the named style, as a 2-element
|
8
|
-
# array.
|
9
|
-
#
|
10
|
-
# For :original, this is based on the output of ImageMagick's
|
11
|
-
# <tt>identify</tt> command. Other styles are calculated from
|
12
|
-
# the original style's dimensions, plus the style's :size and
|
13
|
-
# :filled attributes.
|
14
|
-
#
|
15
|
-
# +style_name+ defaults to the attribute's #default_style.
|
16
|
-
#
|
17
|
-
def dimensions(style_name)
|
18
|
-
if style_name.equal?(:original)
|
19
|
-
from_examination :original_dimensions
|
20
|
-
else
|
21
|
-
style = reflection.styles[style_name]
|
22
|
-
target_dimensions = style[:size].split(/x/).map{|s| s.to_i}
|
23
|
-
resized_dimensions(dimensions(:original), target_dimensions, style[:filled])
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
5
|
include HasDimensions
|
28
6
|
|
29
7
|
def unload
|
30
8
|
super
|
31
|
-
@
|
9
|
+
@original_width = @original_height = nil
|
32
10
|
end
|
33
11
|
|
34
12
|
protected # ---------------------------------------------------
|
@@ -46,18 +24,19 @@ module Bulldog
|
|
46
24
|
#
|
47
25
|
def run_examination
|
48
26
|
if stream.missing?
|
49
|
-
@
|
27
|
+
@original_width, @original_height = 1, 1
|
50
28
|
false
|
51
29
|
else
|
52
30
|
output = `identify -format "%w %h %[exif:Orientation]" #{stream.path} 2> /dev/null`
|
53
31
|
if $?.success? && output.present?
|
54
32
|
width, height, orientation = *output.scan(/(\d+) (\d+) (\d?)/).first.map{|s| s.to_i}
|
55
33
|
rotated = (5..8).include?(orientation)
|
56
|
-
@
|
34
|
+
@original_width = rotated ? height : width
|
35
|
+
@original_height = rotated ? width : height
|
57
36
|
true
|
58
37
|
else
|
59
38
|
Bulldog.logger.warn "command failed (#{$?.exitstatus})"
|
60
|
-
@
|
39
|
+
@original_width, @original_height = 1, 1
|
61
40
|
false
|
62
41
|
end
|
63
42
|
end
|
@@ -2,28 +2,6 @@ module Bulldog
|
|
2
2
|
module Attachment
|
3
3
|
class Pdf < Base
|
4
4
|
handle :pdf
|
5
|
-
|
6
|
-
#
|
7
|
-
# Return the width and height of the named style, as a 2-element
|
8
|
-
# array.
|
9
|
-
#
|
10
|
-
# For :original, this is based on the output of ImageMagick's
|
11
|
-
# <tt>identify</tt> command. Other styles are calculated from
|
12
|
-
# the original style's dimensions, plus the style's :size and
|
13
|
-
# :filled attributes.
|
14
|
-
#
|
15
|
-
# +style_name+ defaults to the attribute's #default_style.
|
16
|
-
#
|
17
|
-
def dimensions(style_name)
|
18
|
-
if style_name.equal?(:original)
|
19
|
-
from_examination :original_dimensions
|
20
|
-
else
|
21
|
-
style = reflection.styles[style_name]
|
22
|
-
target_dimensions = style[:size].split(/x/).map{|s| s.to_i}
|
23
|
-
resized_dimensions(dimensions(:original), target_dimensions, style[:filled])
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
5
|
include HasDimensions
|
28
6
|
|
29
7
|
protected # ---------------------------------------------------
|
@@ -41,18 +19,15 @@ module Bulldog
|
|
41
19
|
#
|
42
20
|
def run_examination
|
43
21
|
if stream.missing?
|
44
|
-
@
|
22
|
+
@original_width, @original_height = 1, 1
|
45
23
|
false
|
46
24
|
else
|
47
|
-
output = `identify -format "%w %h
|
25
|
+
output = `identify -format "%w %h" #{stream.path}[0] 2> /dev/null`
|
48
26
|
if $?.success? && output.present?
|
49
|
-
|
50
|
-
rotated = (orientation & 0x4).nonzero?
|
51
|
-
@original_dimensions ||= rotated ? [height, width] : [width, height]
|
27
|
+
@original_width, @original_height = *output.scan(/(\d+) (\d+)/).first.map{|s| s.to_i}
|
52
28
|
true
|
53
29
|
else
|
54
30
|
Bulldog.logger.warn "command failed (#{$?.exitstatus})"
|
55
|
-
@original_dimensions = [1, 1]
|
56
31
|
false
|
57
32
|
end
|
58
33
|
end
|
@@ -2,19 +2,6 @@ module Bulldog
|
|
2
2
|
module Attachment
|
3
3
|
class Video < Base
|
4
4
|
handle :video
|
5
|
-
|
6
|
-
#
|
7
|
-
# Return the width and height of the named style, as a 2-element
|
8
|
-
# array.
|
9
|
-
#
|
10
|
-
# This runs ffmpeg for, and only for, the original style.
|
11
|
-
#
|
12
|
-
# +style_name+ defaults to the attribute's #default_style.
|
13
|
-
#
|
14
|
-
def dimensions(style_name)
|
15
|
-
video_tracks(style_name).first.dimensions
|
16
|
-
end
|
17
|
-
|
18
5
|
include HasDimensions
|
19
6
|
|
20
7
|
#
|
@@ -26,12 +13,7 @@ module Bulldog
|
|
26
13
|
# +style_name+ defaults to the attribute's #default_style.
|
27
14
|
#
|
28
15
|
def duration(style_name)
|
29
|
-
|
30
|
-
if stream.missing?
|
31
|
-
0
|
32
|
-
else
|
33
|
-
from_examination :original_duration
|
34
|
-
end
|
16
|
+
from_examination :@original_duration
|
35
17
|
end
|
36
18
|
|
37
19
|
#
|
@@ -40,27 +22,18 @@ module Bulldog
|
|
40
22
|
#
|
41
23
|
# Each VideoTrack has:
|
42
24
|
#
|
43
|
-
# * <tt>#
|
44
|
-
#
|
25
|
+
# * <tt>#duration</tt> - the duration of the video track.
|
26
|
+
# * <tt>#dimensions</tt> - the [width, height] of the video
|
27
|
+
# track.
|
45
28
|
#
|
46
29
|
def video_tracks(style_name=nil)
|
47
30
|
style_name ||= reflection.default_style
|
48
|
-
if style_name
|
49
|
-
|
50
|
-
[VideoTrack.new(:dimensions => [2, 2])]
|
51
|
-
else
|
52
|
-
examine unless @original_video_tracks
|
53
|
-
if @original_video_tracks.empty?
|
54
|
-
@original_video_tracks << VideoTrack.new(:dimensions => [2, 2])
|
55
|
-
end
|
56
|
-
@original_video_tracks
|
57
|
-
end
|
31
|
+
if style_name.equal?(:original)
|
32
|
+
from_examination :@original_video_tracks
|
58
33
|
else
|
59
34
|
style = reflection.styles[style_name]
|
60
|
-
target_dimensions = style[:size].split(/x/).map{|s| s.to_i}
|
61
35
|
video_tracks(:original).map do |video_track|
|
62
|
-
dimensions =
|
63
|
-
dimensions.map!{|i| i &= -2} # some codecs require multiples of 2
|
36
|
+
dimensions = resize_dimensions(dimensions(:original), style)
|
64
37
|
VideoTrack.new(:dimensions => dimensions)
|
65
38
|
end
|
66
39
|
end
|
@@ -72,12 +45,12 @@ module Bulldog
|
|
72
45
|
#
|
73
46
|
# AudioTrack objects do not yet have any useful methods.
|
74
47
|
#
|
75
|
-
def audio_tracks(style_name)
|
48
|
+
def audio_tracks(style_name=nil)
|
76
49
|
examine
|
77
50
|
@original_audio_tracks
|
78
51
|
end
|
79
52
|
|
80
|
-
storable_attribute :duration
|
53
|
+
storable_attribute :duration, :per_style => true, :memoize => true
|
81
54
|
|
82
55
|
def unload
|
83
56
|
super
|
@@ -95,38 +68,66 @@ module Bulldog
|
|
95
68
|
:ffmpeg
|
96
69
|
end
|
97
70
|
|
71
|
+
#
|
72
|
+
# Overridden to round down to multiples of 2, as required by
|
73
|
+
# some codecs.
|
74
|
+
#
|
75
|
+
def resize_dimensions(original_dimensions, style)
|
76
|
+
dimensions = super
|
77
|
+
dimensions.map!{|i| i &= -2}
|
78
|
+
end
|
79
|
+
|
98
80
|
private # -----------------------------------------------------
|
99
81
|
|
100
82
|
#
|
101
83
|
# Read the original image metadata with ffmpeg.
|
102
84
|
#
|
103
85
|
def run_examination
|
104
|
-
|
105
|
-
|
106
|
-
|
86
|
+
if stream.missing?
|
87
|
+
set_defaults
|
88
|
+
false
|
89
|
+
else
|
90
|
+
output = `ffmpeg -i #{stream.path} 2>&1`
|
91
|
+
# ffmpeg exits nonzero - don't bother checking status.
|
92
|
+
parse_output(output)
|
93
|
+
true
|
94
|
+
end
|
107
95
|
end
|
108
96
|
|
109
|
-
def
|
110
|
-
result = false
|
97
|
+
def set_defaults
|
111
98
|
@original_duration = 0
|
112
|
-
@
|
99
|
+
@original_width = 2
|
100
|
+
@original_height = 2
|
113
101
|
@original_audio_tracks = []
|
102
|
+
@original_video_tracks = []
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_output(output)
|
106
|
+
result = false
|
107
|
+
set_defaults
|
114
108
|
io = StringIO.new(output)
|
115
109
|
while (line = io.gets)
|
116
110
|
case line
|
117
111
|
when /^Input #0, (.*?), from '(?:.*)':$/
|
118
112
|
result = true
|
113
|
+
duration = nil
|
119
114
|
when /^ Duration: (\d+):(\d+):(\d+)\.(\d+)/
|
120
|
-
|
115
|
+
duration = $1.to_i.hours + $2.to_i.minutes + $3.to_i.seconds
|
121
116
|
when /Stream #(?:.*?): Video: /
|
122
117
|
if $' =~ /(\d+)x(\d+)/
|
123
118
|
dimensions = [$1.to_i, $2.to_i]
|
124
119
|
end
|
125
|
-
@original_video_tracks << VideoTrack.new(:dimensions => dimensions)
|
120
|
+
@original_video_tracks << VideoTrack.new(:dimensions => dimensions, :duration => duration)
|
126
121
|
when /Stream #(?:.*?): Audio: (.*?)/
|
127
|
-
@original_audio_tracks << AudioTrack.new
|
122
|
+
@original_audio_tracks << AudioTrack.new(:duration => duration)
|
128
123
|
end
|
129
124
|
end
|
125
|
+
if (track = @original_video_tracks.first)
|
126
|
+
@original_width, @original_height = *track.dimensions
|
127
|
+
end
|
128
|
+
if (track = @original_video_tracks.first || @original_audio_tracks.first)
|
129
|
+
@original_duration = track.duration
|
130
|
+
end
|
130
131
|
result
|
131
132
|
end
|
132
133
|
|
@@ -136,6 +137,8 @@ module Bulldog
|
|
136
137
|
send("#{name}=", value)
|
137
138
|
end
|
138
139
|
end
|
140
|
+
|
141
|
+
attr_accessor :duration
|
139
142
|
end
|
140
143
|
|
141
144
|
class VideoTrack < Track
|
data/lib/bulldog/style.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
module Bulldog
|
2
|
+
#
|
3
|
+
# Represents a style to generate.
|
4
|
+
#
|
2
5
|
class Style
|
3
6
|
def initialize(name, attributes={})
|
4
7
|
@name = name
|
5
8
|
@attributes = attributes
|
9
|
+
set_dimensions(attributes[:size])
|
6
10
|
end
|
7
11
|
|
8
12
|
attr_reader :name, :attributes
|
@@ -15,7 +19,12 @@ module Bulldog
|
|
15
19
|
#
|
16
20
|
# Set the value of the given style attribute.
|
17
21
|
#
|
18
|
-
|
22
|
+
def []=(name, value)
|
23
|
+
if name == :size
|
24
|
+
set_dimensions(value)
|
25
|
+
end
|
26
|
+
attributes[name] = value
|
27
|
+
end
|
19
28
|
|
20
29
|
#
|
21
30
|
# Return true if the argument is a Style with the same name and
|
@@ -33,6 +42,24 @@ module Bulldog
|
|
33
42
|
|
34
43
|
delegate :hash, :eql?, :to => :name
|
35
44
|
|
45
|
+
#
|
46
|
+
# The [width, height] specified by :size, or nil if there is no :size.
|
47
|
+
#
|
48
|
+
attr_reader :dimensions
|
49
|
+
|
50
|
+
#
|
51
|
+
# Return true if :filled is true, false otherwise.
|
52
|
+
#
|
53
|
+
def filled?
|
54
|
+
!!self[:filled]
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def set_dimensions(value)
|
60
|
+
@dimensions = value ? value.scan(/\A(\d+)x(\d+)\z/).first.map{|s| s.to_i} : nil
|
61
|
+
end
|
62
|
+
|
36
63
|
ORIGINAL = new(:original, {})
|
37
64
|
end
|
38
65
|
end
|