bulldog 0.0.1
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/.gitignore +2 -0
- data/DESCRIPTION.txt +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/bulldog.gemspec +157 -0
- data/lib/bulldog.rb +95 -0
- data/lib/bulldog/attachment.rb +49 -0
- data/lib/bulldog/attachment/base.rb +167 -0
- data/lib/bulldog/attachment/has_dimensions.rb +94 -0
- data/lib/bulldog/attachment/image.rb +63 -0
- data/lib/bulldog/attachment/maybe.rb +229 -0
- data/lib/bulldog/attachment/none.rb +37 -0
- data/lib/bulldog/attachment/pdf.rb +63 -0
- data/lib/bulldog/attachment/unknown.rb +11 -0
- data/lib/bulldog/attachment/video.rb +143 -0
- data/lib/bulldog/error.rb +5 -0
- data/lib/bulldog/has_attachment.rb +214 -0
- data/lib/bulldog/interpolation.rb +73 -0
- data/lib/bulldog/missing_file.rb +12 -0
- data/lib/bulldog/processor.rb +5 -0
- data/lib/bulldog/processor/argument_tree.rb +116 -0
- data/lib/bulldog/processor/base.rb +124 -0
- data/lib/bulldog/processor/ffmpeg.rb +172 -0
- data/lib/bulldog/processor/image_magick.rb +134 -0
- data/lib/bulldog/processor/one_shot.rb +19 -0
- data/lib/bulldog/reflection.rb +234 -0
- data/lib/bulldog/saved_file.rb +19 -0
- data/lib/bulldog/stream.rb +186 -0
- data/lib/bulldog/style.rb +38 -0
- data/lib/bulldog/style_set.rb +101 -0
- data/lib/bulldog/tempfile.rb +28 -0
- data/lib/bulldog/util.rb +92 -0
- data/lib/bulldog/validations.rb +68 -0
- data/lib/bulldog/vector2.rb +18 -0
- data/rails/init.rb +9 -0
- data/script/console +8 -0
- data/spec/data/empty.txt +0 -0
- data/spec/data/test.jpg +0 -0
- data/spec/data/test.mov +0 -0
- data/spec/data/test.pdf +0 -0
- data/spec/data/test.png +0 -0
- data/spec/data/test2.jpg +0 -0
- data/spec/helpers/image_creation.rb +8 -0
- data/spec/helpers/temporary_directory.rb +25 -0
- data/spec/helpers/temporary_models.rb +76 -0
- data/spec/helpers/temporary_values.rb +102 -0
- data/spec/helpers/test_upload_files.rb +108 -0
- data/spec/helpers/time_travel.rb +20 -0
- data/spec/integration/data/test.jpg +0 -0
- data/spec/integration/lifecycle_hooks_spec.rb +213 -0
- data/spec/integration/processing_image_attachments.rb +72 -0
- data/spec/integration/processing_video_attachments_spec.rb +82 -0
- data/spec/integration/saving_an_attachment_spec.rb +31 -0
- data/spec/matchers/file_operations.rb +159 -0
- data/spec/spec_helper.rb +76 -0
- data/spec/unit/attachment/base_spec.rb +311 -0
- data/spec/unit/attachment/image_spec.rb +128 -0
- data/spec/unit/attachment/maybe_spec.rb +126 -0
- data/spec/unit/attachment/pdf_spec.rb +137 -0
- data/spec/unit/attachment/video_spec.rb +176 -0
- data/spec/unit/attachment_spec.rb +61 -0
- data/spec/unit/has_attachment_spec.rb +700 -0
- data/spec/unit/interpolation_spec.rb +108 -0
- data/spec/unit/processor/argument_tree_spec.rb +159 -0
- data/spec/unit/processor/ffmpeg_spec.rb +467 -0
- data/spec/unit/processor/image_magick_spec.rb +260 -0
- data/spec/unit/processor/one_shot_spec.rb +70 -0
- data/spec/unit/reflection_spec.rb +338 -0
- data/spec/unit/stream_spec.rb +234 -0
- data/spec/unit/style_set_spec.rb +44 -0
- data/spec/unit/style_spec.rb +51 -0
- data/spec/unit/validations_spec.rb +491 -0
- data/spec/unit/vector2_spec.rb +27 -0
- data/tasks/bulldog_tasks.rake +4 -0
- metadata +193 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
module Bulldog
|
2
|
+
class MissingFile
|
3
|
+
def initialize(options={})
|
4
|
+
@attachment_type = attachment_type
|
5
|
+
@file_name = options[:file_name] || 'missing-file'
|
6
|
+
@content_type = options[:content_type]
|
7
|
+
@path = options[:path] || '/dev/null'
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :attachment_type, :file_name, :content_type, :path
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Bulldog
|
2
|
+
module Processor
|
3
|
+
class ArgumentTree
|
4
|
+
def initialize(styles)
|
5
|
+
@styles = styles
|
6
|
+
@root = Node.new(styles)
|
7
|
+
@heads = {}
|
8
|
+
styles.each{|s| @heads[s] = @root}
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :styles, :root, :heads
|
12
|
+
|
13
|
+
def add(style, arguments, &callback)
|
14
|
+
# Assume that if the arguments are the same for a node, the
|
15
|
+
# callback will be identical.
|
16
|
+
child = heads[style].children.find do |node|
|
17
|
+
node.arguments == arguments
|
18
|
+
end
|
19
|
+
if child
|
20
|
+
child.styles << style
|
21
|
+
else
|
22
|
+
child = Node.new([style], arguments, &callback)
|
23
|
+
heads[style].children << child
|
24
|
+
end
|
25
|
+
heads[style] = child
|
26
|
+
end
|
27
|
+
|
28
|
+
def output(style, path)
|
29
|
+
heads[style].outputs << path
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
io = StringIO.new
|
34
|
+
inspect_node(io, @root)
|
35
|
+
io.string
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Return the list of arguments the tree represents.
|
40
|
+
#
|
41
|
+
def arguments
|
42
|
+
arguments = visit_node_for_arguments([], root, false)
|
43
|
+
# Don't specify -write for the last output file.
|
44
|
+
arguments[-2] == '-write' or
|
45
|
+
raise "[BULLDOG BUG]: expected second last argument to be -write in: #{arguments.inspect}"
|
46
|
+
arguments.delete_at(-2)
|
47
|
+
arguments
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Yield each callback, along with the styles they apply to, in
|
52
|
+
# the order they appear in the tree.
|
53
|
+
#
|
54
|
+
def each_callback(&block)
|
55
|
+
visit_node_for_callbacks(root, block)
|
56
|
+
end
|
57
|
+
|
58
|
+
private # ---------------------------------------------------
|
59
|
+
|
60
|
+
def inspect_node(io, node, margin='')
|
61
|
+
puts "#{margin}* #{node.styles.map(&:name).join(', ')}: #{node.arguments.join(' ')}"
|
62
|
+
node.children.each do |child|
|
63
|
+
inspect_node(io, child, margin + ' ')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def visit_node_for_arguments(arguments, node, clone)
|
68
|
+
if clone
|
69
|
+
arguments << '(' << '+clone'
|
70
|
+
visit_node_for_arguments(arguments, node, false)
|
71
|
+
arguments << '+delete' << ')'
|
72
|
+
else
|
73
|
+
arguments.concat(node.arguments)
|
74
|
+
node.outputs.each{|path| arguments << '-write' << path}
|
75
|
+
|
76
|
+
num_children = node.children.size
|
77
|
+
node.children.each_with_index do |child, i|
|
78
|
+
# No need to clone the image for the last child.
|
79
|
+
visit_node_for_arguments(arguments, child, i < num_children - 1)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
arguments
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_node_for_callbacks(node, block)
|
86
|
+
if node.callback
|
87
|
+
block.call(node.styles, node.callback)
|
88
|
+
end
|
89
|
+
node.children.each do |child|
|
90
|
+
visit_node_for_callbacks(child, block)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Node
|
95
|
+
def initialize(styles, arguments=[], &callback)
|
96
|
+
@styles = styles
|
97
|
+
@arguments = arguments
|
98
|
+
@callback = callback
|
99
|
+
@outputs = []
|
100
|
+
@children = []
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_child(child)
|
104
|
+
children << child
|
105
|
+
end
|
106
|
+
|
107
|
+
def remove_child(child)
|
108
|
+
children.delete(child)
|
109
|
+
end
|
110
|
+
|
111
|
+
attr_accessor :outputs
|
112
|
+
attr_reader :styles, :arguments, :callback, :children
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Bulldog
|
2
|
+
module Processor
|
3
|
+
class Base
|
4
|
+
def initialize(attachment, styles, input_file)
|
5
|
+
@attachment = attachment
|
6
|
+
@styles = styles
|
7
|
+
@input_file = input_file
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# The attachment object being processed.
|
12
|
+
#
|
13
|
+
attr_reader :attachment
|
14
|
+
|
15
|
+
#
|
16
|
+
# The styles to run this processor for.
|
17
|
+
#
|
18
|
+
attr_reader :styles
|
19
|
+
|
20
|
+
#
|
21
|
+
# The record being processed.
|
22
|
+
#
|
23
|
+
def record
|
24
|
+
attachment.record
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# The name of the attachment being processed.
|
29
|
+
#
|
30
|
+
def name
|
31
|
+
attachment.name
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# The name of the original file.
|
36
|
+
#
|
37
|
+
attr_reader :input_file
|
38
|
+
|
39
|
+
#
|
40
|
+
# The name of the output file for the given style.
|
41
|
+
#
|
42
|
+
def output_file(style_name)
|
43
|
+
overrides = {}
|
44
|
+
if (format = styles[style_name][:format])
|
45
|
+
overrides[:extension] = format
|
46
|
+
end
|
47
|
+
attachment.interpolate_path(style_name, overrides)
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Return the value of the attachment.
|
52
|
+
#
|
53
|
+
def value
|
54
|
+
record.send(name).value
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Run the given block in the context of this processor, once for
|
59
|
+
# each style.
|
60
|
+
#
|
61
|
+
# #style will be set to the current style each time the block is
|
62
|
+
# called.
|
63
|
+
#
|
64
|
+
def process(*args, &block)
|
65
|
+
return if styles.empty?
|
66
|
+
styles.each do |style|
|
67
|
+
@style = style
|
68
|
+
begin
|
69
|
+
process_style(*args, &block)
|
70
|
+
ensure
|
71
|
+
@style = nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# The current style being processed.
|
78
|
+
#
|
79
|
+
attr_reader :style
|
80
|
+
|
81
|
+
protected # ---------------------------------------------------
|
82
|
+
|
83
|
+
#
|
84
|
+
# Run the given block with #style set to one of the styles to
|
85
|
+
# process.
|
86
|
+
#
|
87
|
+
# This is called by #process for each output style.
|
88
|
+
#
|
89
|
+
def process_style(*args, &block)
|
90
|
+
# Avoid #instance_exec if possible for ruby 1.8.
|
91
|
+
evaluator = args.empty? ? :instance_eval : :instance_exec
|
92
|
+
send(evaluator, *args, &block) if block
|
93
|
+
end
|
94
|
+
|
95
|
+
def log(level, message)
|
96
|
+
logger = Bulldog.logger
|
97
|
+
logger.send(level, message) unless logger.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
def shell_escape(str)
|
101
|
+
if RUBY_VERSION >= '1.9'
|
102
|
+
Shellwords.shellescape(str)
|
103
|
+
else
|
104
|
+
# Taken from ruby 1.9.
|
105
|
+
|
106
|
+
# An empty argument will be skipped, so return empty quotes.
|
107
|
+
return "''" if str.empty?
|
108
|
+
|
109
|
+
str = str.dup
|
110
|
+
|
111
|
+
# Process as a single byte sequence because not all shell
|
112
|
+
# implementations are multibyte aware.
|
113
|
+
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
|
114
|
+
|
115
|
+
# A LF cannot be escaped with a backslash because a backslash + LF
|
116
|
+
# combo is regarded as line continuation and simply ignored.
|
117
|
+
str.gsub!(/\n/, "'\n'")
|
118
|
+
|
119
|
+
return str
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module Bulldog
|
2
|
+
module Processor
|
3
|
+
class Ffmpeg < Base
|
4
|
+
class << self
|
5
|
+
attr_accessor :ffmpeg_command
|
6
|
+
end
|
7
|
+
|
8
|
+
self.ffmpeg_command = Bulldog.find_in_path('ffmpeg')
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super
|
12
|
+
@operation = nil
|
13
|
+
@arguments = style_list_map
|
14
|
+
@still_frame_callbacks = style_list_map
|
15
|
+
end
|
16
|
+
|
17
|
+
def process(*args)
|
18
|
+
return if styles.empty?
|
19
|
+
super
|
20
|
+
run_ffmpeg
|
21
|
+
run_still_frame_callbacks
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_style(*args)
|
25
|
+
super
|
26
|
+
run_default_operation
|
27
|
+
end
|
28
|
+
|
29
|
+
def use_threads(num_threads)
|
30
|
+
operate '-threads', num_threads
|
31
|
+
end
|
32
|
+
|
33
|
+
def encode(params={})
|
34
|
+
@operation = :encode
|
35
|
+
params = style.attributes.merge(params)
|
36
|
+
parse_video_option(params)
|
37
|
+
parse_audio_option(params)
|
38
|
+
style_option '-vcodec', params[:video_codec]
|
39
|
+
style_option '-acodec', params[:audio_codec]
|
40
|
+
preset_option '-vpre', params[:video_preset]
|
41
|
+
preset_option '-apre', params[:audio_preset]
|
42
|
+
operate '-s', attachment.dimensions(style.name).join('x') if params[:size]
|
43
|
+
style_option '-r', params[:frame_rate]
|
44
|
+
style_option '-b', params[:video_bit_rate]
|
45
|
+
style_option '-ar', params[:sampling_rate]
|
46
|
+
style_option '-ab', params[:audio_bit_rate]
|
47
|
+
style_option '-ac', params[:channels]
|
48
|
+
operate '-deinterlace' if params[:deinterlaced]
|
49
|
+
style_option '-pix_fmt', params[:pixel_format]
|
50
|
+
style_option '-b_strategy', params[:b_strategy]
|
51
|
+
style_option '-bufsize', params[:buffer_size]
|
52
|
+
style_option '-coder', params[:coder]
|
53
|
+
style_option '-v', params[:verbosity]
|
54
|
+
style_option '-flags', params[:flags]
|
55
|
+
preset_option '-spre', params[:subtitle_preset]
|
56
|
+
style_option '-y', output_file(style.name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def record_frame(params={}, &block)
|
60
|
+
@operation = :record_frame
|
61
|
+
params = style.attributes.merge(params)
|
62
|
+
operate '-vframes', 1
|
63
|
+
operate '-ss', params[:position] || attachment.duration.to_i / 2
|
64
|
+
operate '-f', 'image2'
|
65
|
+
operate '-vcodec', params[:codec] || default_frame_codec(params)
|
66
|
+
|
67
|
+
if (attribute = params[:assign_to])
|
68
|
+
basename = "recorded_frame.#{params[:format]}"
|
69
|
+
output_path = record.send(attribute).interpolate_path(:original, :basename => basename)
|
70
|
+
@still_frame_callbacks[style] << lambda do
|
71
|
+
file = SavedFile.new(output_path, :file_name => basename)
|
72
|
+
record.update_attribute(attribute, file)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
output_path = output_file(style.name)
|
76
|
+
end
|
77
|
+
|
78
|
+
operate '-y', output_path
|
79
|
+
if block
|
80
|
+
@still_frame_callbacks[style] << lambda{instance_exec(output_path, &block)}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private # -----------------------------------------------------
|
85
|
+
|
86
|
+
def style_list_map
|
87
|
+
hash = {}
|
88
|
+
styles.each{|s| hash[s] = []}
|
89
|
+
hash
|
90
|
+
end
|
91
|
+
|
92
|
+
def operate(*args)
|
93
|
+
@arguments[style].concat args.map(&:to_s)
|
94
|
+
end
|
95
|
+
|
96
|
+
def run_default_operation
|
97
|
+
encode if @operation.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_video_option(params)
|
101
|
+
value = params.delete(:video) or
|
102
|
+
return
|
103
|
+
value.split.each do |word|
|
104
|
+
case word
|
105
|
+
when /fps\z/i
|
106
|
+
params[:frame_rate] = $`
|
107
|
+
when /bps\z/i
|
108
|
+
params[:video_bit_rate] = $`
|
109
|
+
else
|
110
|
+
params[:video_codec] = word
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_audio_option(params)
|
116
|
+
value = params.delete(:audio) or
|
117
|
+
return
|
118
|
+
value.split.each do |word|
|
119
|
+
case word
|
120
|
+
when /hz\z/i
|
121
|
+
params[:sampling_rate] = $`
|
122
|
+
when /bps\z/i
|
123
|
+
params[:audio_bit_rate] = $`
|
124
|
+
when 'mono'
|
125
|
+
params[:channels] = 1
|
126
|
+
when 'stereo'
|
127
|
+
params[:channels] = 2
|
128
|
+
else
|
129
|
+
params[:audio_codec] = word
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def style_option(option, *args)
|
135
|
+
operate(option, *args) if args.all?
|
136
|
+
end
|
137
|
+
|
138
|
+
def preset_option(option, value)
|
139
|
+
Array(value).each do |preset|
|
140
|
+
operate option, preset
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def default_frame_codec(params)
|
145
|
+
case params[:format].to_s
|
146
|
+
when /jpe?g/i
|
147
|
+
'mjpeg'
|
148
|
+
when /png/i
|
149
|
+
'png'
|
150
|
+
else
|
151
|
+
format = params[:format]
|
152
|
+
raise ProcessingError, "no default codec for '#{format}' - please use :codec to specify"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def run_ffmpeg
|
157
|
+
@arguments.each do |style, arguments|
|
158
|
+
command = [self.class.ffmpeg_command]
|
159
|
+
command << '-i' << input_file
|
160
|
+
command.concat(arguments)
|
161
|
+
Bulldog.run(*command)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def run_still_frame_callbacks
|
166
|
+
@still_frame_callbacks.each do |style, callbacks|
|
167
|
+
callbacks.each{|c| c.call}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Bulldog
|
4
|
+
module Processor
|
5
|
+
class ImageMagick < Base
|
6
|
+
class << self
|
7
|
+
attr_accessor :convert_command
|
8
|
+
attr_accessor :identify_command
|
9
|
+
end
|
10
|
+
|
11
|
+
self.convert_command = Bulldog.find_in_path('convert')
|
12
|
+
self.identify_command = Bulldog.find_in_path('identify')
|
13
|
+
|
14
|
+
def initialize(*args)
|
15
|
+
super
|
16
|
+
@tree = ArgumentTree.new(styles)
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(*args, &block)
|
20
|
+
return if styles.empty?
|
21
|
+
super
|
22
|
+
run_convert
|
23
|
+
end
|
24
|
+
|
25
|
+
# Input image attributes --------------------------------------
|
26
|
+
|
27
|
+
#
|
28
|
+
# Yield the dimensions of the generated file at this point in
|
29
|
+
# the pipeline.
|
30
|
+
#
|
31
|
+
# The block is called when `convert' is run after the processor
|
32
|
+
# block is evaluated for all styles. If you just need the
|
33
|
+
# dimensions of the input file, see Attribute::Photo#dimensions.
|
34
|
+
#
|
35
|
+
def dimensions(&block)
|
36
|
+
operate '-format', '%w %h'
|
37
|
+
operate '-identify' do |styles, output|
|
38
|
+
width, height = output.gets.split.map(&:to_i)
|
39
|
+
block.call(styles, width, height)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private # -----------------------------------------------------
|
44
|
+
|
45
|
+
# Image operations --------------------------------------------
|
46
|
+
|
47
|
+
def resize
|
48
|
+
operate '-resize', style[:size]
|
49
|
+
end
|
50
|
+
|
51
|
+
def auto_orient
|
52
|
+
operate '-auto-orient'
|
53
|
+
end
|
54
|
+
|
55
|
+
def strip
|
56
|
+
operate '-strip'
|
57
|
+
end
|
58
|
+
|
59
|
+
def flip
|
60
|
+
operate '-flip'
|
61
|
+
end
|
62
|
+
|
63
|
+
def flop
|
64
|
+
operate '-flop'
|
65
|
+
end
|
66
|
+
|
67
|
+
def rotate(angle)
|
68
|
+
unless angle.to_i.zero?
|
69
|
+
operate '-rotate', angle.to_s
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def crop(params)
|
74
|
+
operate '-crop', geometry(params[:size], params[:origin])
|
75
|
+
operate '+repage'
|
76
|
+
end
|
77
|
+
|
78
|
+
def thumbnail
|
79
|
+
if style[:filled]
|
80
|
+
operate '-resize', "#{style[:size]}^"
|
81
|
+
operate '-gravity', 'Center'
|
82
|
+
operate '-crop', "#{style[:size]}+0+0"
|
83
|
+
operate '+repage'
|
84
|
+
else
|
85
|
+
operate '-resize', "#{style[:size]}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private # -----------------------------------------------------
|
90
|
+
|
91
|
+
def geometry(size, origin=nil)
|
92
|
+
size = Vector2.new(size)
|
93
|
+
geometry = '%dx%d' % [size.x, size.y]
|
94
|
+
if origin
|
95
|
+
origin = Vector2.new(origin)
|
96
|
+
geometry << ('%+d%+d' % [origin.x, origin.y])
|
97
|
+
end
|
98
|
+
geometry
|
99
|
+
end
|
100
|
+
|
101
|
+
def operate(*arguments, &block)
|
102
|
+
@tree.add(style, arguments, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
def run_convert
|
106
|
+
add_final_style_arguments
|
107
|
+
output = run_convert_command and
|
108
|
+
run_convert_callbacks(output)
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_final_style_arguments
|
112
|
+
styles.each do |style|
|
113
|
+
@tree.add(style, ['-quality', style[:quality].to_s]) if style[:quality]
|
114
|
+
@tree.add(style, ['-colorspace', style[:colorspace]]) if style[:colorspace]
|
115
|
+
path = output_file(style.name)
|
116
|
+
FileUtils.mkdir_p(File.dirname(path))
|
117
|
+
@tree.output(style, path)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def run_convert_command
|
122
|
+
command = [self.class.convert_command, "#{input_file}[0]", *@tree.arguments].flatten
|
123
|
+
Bulldog.run(*command)
|
124
|
+
end
|
125
|
+
|
126
|
+
def run_convert_callbacks(output)
|
127
|
+
io = StringIO.new(output)
|
128
|
+
@tree.each_callback do |styles, callback|
|
129
|
+
callback.call(styles, io)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|