falcon 0.1.3 → 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.
@@ -1,5 +0,0 @@
1
- module Falcon
2
- class Encoding < ::ActiveRecord::Base
3
- include Falcon::Encoder
4
- end
5
- end
data/lib/falcon/base.rb DELETED
@@ -1,98 +0,0 @@
1
- module Falcon
2
- module Base
3
- def self.included(base)
4
- base.extend SingletonMethods
5
- end
6
-
7
- module SingletonMethods
8
- #
9
- # falcon_encode 'media', :source => lambda { |file| file.data.path },
10
- # :profiles => ['web_mp4', 'web_ogg']
11
- #
12
- # falcon_encode 'media', :source => :method_return_path_to_file,
13
- # :profiles => ['web_mp4', 'web_ogg']
14
- # :metadata => :method_return_options_hash,
15
- # :encode => lambda { |encoding| encoding.delay.encode }
16
- #
17
- def falcon_encode(name, options = {})
18
- extend ClassMethods
19
- include InstanceMethods
20
-
21
- options.assert_valid_keys(:source, :profiles, :metadata, :encode)
22
-
23
- unless respond_to?(:falcon_encoding_definitions)
24
- class_attribute :falcon_encoding_definitions, :instance_writer => false
25
- self.falcon_encoding_definitions = {}
26
- end
27
-
28
- self.falcon_encoding_definitions[name] = options
29
-
30
- has_many :falcon_encodings,
31
- :class_name => 'Falcon::Encoding',
32
- :as => :videoable,
33
- :dependent => :destroy
34
-
35
- after_save :save_falcon_medias
36
-
37
- define_falcon_callbacks :encode, :"#{name}_encode"
38
-
39
- define_method name do |*args|
40
- a = falcon_media_for(name)
41
- (args.length > 0) ? a.to_s(args.first) : a
42
- end
43
-
44
- define_method "#{name}=" do |source_path|
45
- falcon_media_for(name).assign(source_path)
46
- end
47
-
48
- define_method "#{name}?" do
49
- falcon_media_for(name).exist?
50
- end
51
- end
52
- end
53
-
54
- module ClassMethods
55
- def define_falcon_callbacks(*callbacks)
56
- define_callbacks *[callbacks, {:terminator => "result == false"}].flatten
57
- callbacks.each do |callback|
58
- eval <<-end_callbacks
59
- def before_#{callback}(*args, &blk)
60
- set_callback(:#{callback}, :before, *args, &blk)
61
- end
62
- def after_#{callback}(*args, &blk)
63
- set_callback(:#{callback}, :after, *args, &blk)
64
- end
65
- end_callbacks
66
- end
67
- end
68
- end
69
-
70
- module InstanceMethods
71
-
72
- def falcon_media_for(name)
73
- @_falcon_medias ||= {}
74
- @_falcon_medias[name] ||= Media.new(name, self, self.class.falcon_encoding_definitions[name])
75
- end
76
-
77
- def each_falcon_medias
78
- self.class.falcon_encoding_definitions.each do |name, definition|
79
- yield(name, falcon_media_for(name))
80
- end
81
- end
82
-
83
- protected
84
-
85
- def save_falcon_medias
86
- each_falcon_medias do |name, media|
87
- media.send(:save)
88
- end
89
- end
90
-
91
- def destroy_falcon_medias
92
- each_falcon_medias do |name, media|
93
- media.send(:destroy)
94
- end
95
- end
96
- end
97
- end
98
- end
@@ -1,235 +0,0 @@
1
- require 'fileutils'
2
- require 'web_video'
3
-
4
- module Falcon
5
- module Encoder
6
- PROCESSING = 1
7
- SUCCESS = 2
8
- FAILURE = 3
9
-
10
- def self.included(base)
11
- base.send :include, InstanceMethods
12
- base.send :extend, ClassMethods
13
- end
14
-
15
- module ClassMethods
16
- def self.extended(base)
17
- base.class_eval do
18
- belongs_to :videoable, :polymorphic => true
19
-
20
- attr_accessor :ffmpeg_resolution, :ffmpeg_padding
21
-
22
- attr_accessible :name, :profile_name, :source_path
23
-
24
- validates_presence_of :name, :profile_name, :source_path
25
-
26
- before_validation :set_resolution
27
- before_destroy :remove_output
28
-
29
- scope :with_profile, lambda {|name| where(:profile_name => Falcon::Profile.detect(name).name) }
30
- scope :with_name, lambda {|name| where(:name => name) }
31
- scope :processing, where(:status => PROCESSING)
32
- scope :success, where(:status => SUCCESS)
33
- scope :failure, where(:status => FAILURE)
34
- end
35
- end
36
- end
37
-
38
- module InstanceMethods
39
-
40
- def profile
41
- @profile ||= Falcon::Profile.find(profile_name)
42
- end
43
-
44
- def resolution
45
- self.width ? "#{self.width}x#{self.height}" : nil
46
- end
47
-
48
- def transcoder
49
- @transcoder ||= ::WebVideo::Transcoder.new(source_path)
50
- end
51
-
52
- def output_path
53
- @output_path ||= self.profile.path(source_path, name).to_s
54
- end
55
-
56
- def output_directory
57
- @output_directory ||= File.dirname(self.output_path)
58
- end
59
-
60
- def profile_options(input_file, output_file)
61
- self.profile.encode_options.merge({
62
- :input_file => input_file,
63
- :output_file => output_file,
64
- :resolution => self.ffmpeg_resolution
65
- })
66
- end
67
-
68
- # A hash of metadatas for video:
69
- #
70
- # { :title => '', :author => '', :copyright => '',
71
- # :comment => '', :description => '', :language => ''}
72
- #
73
- def metadata_options
74
- videoable.send(name).metadata
75
- end
76
-
77
- def encode
78
- videoable.run_callbacks(:encode) do
79
- videoable.run_callbacks(:"#{name}_encode") do
80
- process_encoding
81
- end
82
- end
83
- end
84
-
85
- def processing?
86
- self.status == PROCESSING
87
- end
88
-
89
- def fail?
90
- self.status == FAILURE
91
- end
92
-
93
- def success?
94
- self.status == SUCCESS
95
- end
96
-
97
- def processing!
98
- self.status = PROCESSING
99
- save(:validate => false)
100
- end
101
-
102
- def fail!
103
- self.status = FAILURE
104
- save(:validate => false)
105
- end
106
-
107
- def success!
108
- self.status = SUCCESS
109
- self.encoded_at = Time.now
110
- save(:validate => false)
111
- end
112
-
113
- protected
114
-
115
- def process_encoding
116
- begun_encoding = Time.now
117
-
118
- processing!
119
-
120
- if encode_source && generate_screenshots
121
- self.encoding_time = (Time.now - begun_encoding).to_i
122
- success!
123
- else
124
- fail!
125
- end
126
- end
127
-
128
- def set_resolution
129
- unless profile.nil?
130
- self.width ||= profile.width
131
- self.height ||= profile.height
132
- end
133
- end
134
-
135
- # Calculate resolution and any padding
136
- def ffmpeg_resolution_and_padding_no_cropping(v_width, v_height)
137
- in_w = v_width.to_f
138
- in_h = v_height.to_f
139
- out_w = self.width.to_f
140
- out_h = self.height.to_f
141
-
142
- begin
143
- aspect = in_w / in_h
144
- aspect_inv = in_h / in_w
145
- rescue
146
- Rails.logger.error "Couldn't do w/h to caculate aspect. Just using the output resolution now."
147
- @ffmpeg_resolution = "#{self.width}x#{self.height}"
148
- return
149
- end
150
-
151
- height = (out_w / aspect.to_f).to_i
152
- height -= 1 if height % 2 == 1
153
-
154
- @ffmpeg_resolution = "#{self.width}x#{height}"
155
-
156
- # Keep the video's original width if the height
157
- if height > out_h
158
- width = (out_h / aspect_inv.to_f).to_i
159
- width -= 1 if width % 2 == 1
160
-
161
- @ffmpeg_resolution = "#{width}x#{self.height}"
162
- self.width = width
163
- self.save(:validate => false)
164
- # Otherwise letterbox it
165
- elsif height < out_h
166
- pad = ((out_h - height.to_f) / 2.0).to_i
167
- pad -= 1 if pad % 2 == 1
168
- @ffmpeg_padding = "-vf pad=#{self.width}:#{height + pad}:0:#{pad / 2}"
169
- end
170
- end
171
-
172
- def encode_source
173
- stream = transcoder.source.video_stream
174
- ffmpeg_resolution_and_padding_no_cropping(stream.width, stream.height)
175
- options = self.profile_options(self.source_path, output_path)
176
-
177
- begin
178
- transcoder.convert(output_path, options) do |command|
179
- # Audo
180
- command << "-ar $audio_sample_rate$"
181
- command << "-ab $audio_bitrate_in_bits$"
182
- command << "-acodec $audio_codec$"
183
- command << "-ac 1"
184
-
185
- # Video
186
- command << "-vcodec $video_codec$"
187
- command << "-b $video_bitrate_in_bits$"
188
- command << "-bt 240k"
189
- command << "-r $fps$"
190
- command << "-f $container$"
191
-
192
- # Profile additional arguments
193
- command << self.profile.command
194
-
195
- # Metadata options
196
- if metadata_options
197
- metadata_options.each do |key, value|
198
- command << "-metadata #{key}=\"#{value}\""
199
- end
200
- end
201
-
202
- command << self.ffmpeg_padding
203
- command << "-y"
204
- end
205
- rescue ::WebVideo::CommandLineError => e
206
- ::WebVideo.logger.error("Unable to transcode video #{self.id}: #{e.class} - #{e.message}")
207
- return false
208
- end
209
- end
210
-
211
- def generate_screenshots
212
- image_files = output_path.gsub(File.extname(output_path), '_%2d.jpg')
213
- options = {:resolution => self.resolution, :count => 1, :at => :center}
214
- image_transcoder = ::WebVideo::Transcoder.new(output_path)
215
-
216
- begin
217
- image_transcoder.screenshot(image_files, options) do |command|
218
- command << "-vcodec mjpeg"
219
-
220
- # The duration for which image extraction will take place
221
- #command << "-t 4"
222
- command << "-y"
223
- end
224
- rescue ::WebVideo::CommandLineError => e
225
- ::WebVideo.logger.error("Unable to generate screenshots for video #{self.id}: #{e.class} - #{e.message}")
226
- return false
227
- end
228
- end
229
-
230
- def remove_output
231
- FileUtils.rm(output_path, :force => true) if File.exists?(output_path)
232
- end
233
- end
234
- end
235
- end
data/lib/falcon/engine.rb DELETED
@@ -1,12 +0,0 @@
1
- require 'rails'
2
- require 'falcon'
3
-
4
- module Falcon
5
- class Engine < ::Rails::Engine
6
- config.before_initialize do
7
- ActiveSupport.on_load :active_record do
8
- ::ActiveRecord::Base.send :include, Falcon::Base
9
- end
10
- end
11
- end
12
- end
data/lib/falcon/media.rb DELETED
@@ -1,154 +0,0 @@
1
- require 'fileutils'
2
-
3
- module Falcon
4
- class Media
5
- def self.default_options
6
- @default_options ||= {
7
- :profiles => ['web_mp4', 'web_ogg'],
8
- :metadata => {},
9
- :source => nil,
10
- :encode => nil
11
- }
12
- end
13
-
14
- attr_reader :name, :instance, :options
15
-
16
- def initialize(name, instance, options = {})
17
- @name = name
18
- @instance = instance
19
- @options = self.class.default_options.merge(options)
20
- @profiles = @options[:profiles]
21
- @encode = @options[:encode]
22
- @dirty = false
23
- end
24
-
25
- # Array of processing profiles
26
- def profiles
27
- unless @normalized_profiles
28
- @normalized_profiles = {}
29
- (@profiles.respond_to?(:call) ? @profiles.call(self) : @profiles).each do |name|
30
- @normalized_profiles[name] = Falcon::Profile.find(name)
31
- end
32
- end
33
-
34
- @normalized_profiles
35
- end
36
-
37
- # List of generated video files
38
- def sources
39
- @sources ||= profiles.values.map{|profile| url(profile) }
40
- end
41
-
42
- # A hash of metadatas for video:
43
- #
44
- # { :title => '', :author => '', :copyright => '',
45
- # :comment => '', :description => '', :language => ''}
46
- #
47
- def metadata
48
- @metadata ||= begin
49
- method = @options[:metadata]
50
- method.respond_to?(:call) ? method.call(self) : instance.send(method)
51
- end
52
- end
53
-
54
- # Path for media source file
55
- def source_path
56
- @source_path ||= begin
57
- method = options[:source]
58
- method.respond_to?(:call) ? method.call(instance) : instance.send(method)
59
- end
60
- end
61
-
62
- def output_directory
63
- @output_directory ||= File.dirname(source_path)
64
- end
65
-
66
- # Returns true if there are changes that need to be saved.
67
- def dirty?
68
- @dirty
69
- end
70
-
71
- # Returns the path of the generated media file by profile object or profile name
72
- def path(profile)
73
- Falcon::Profile.detect(profile).path(source_path, name)
74
- end
75
-
76
- # Returns the public URL of the media, with a given profile
77
- def url(profile)
78
- "/" + path(profile).relative_path_from( Rails.root.join('public') )
79
- end
80
-
81
- def save
82
- flush_deletes
83
- create_encodings
84
- @dirty = false
85
- true
86
- end
87
-
88
- # Destroy files end encodings
89
- def destroy
90
- flush_deletes
91
- @dirty = false
92
- true
93
- end
94
-
95
- # Check if source file exists
96
- def exist?
97
- File.exist?(source_path)
98
- end
99
-
100
- # Check if source encoded by all profiles
101
- def all_ready?
102
- instance.falcon_encodings.success.count == profiles.keys.size
103
- end
104
-
105
- def ready?(profile)
106
- instance.falcon_encodings.with_profile(profile).success.exists?
107
- end
108
-
109
- def assign(source)
110
- if File.exist?(source)
111
- @source_path = source
112
- @dirty = true
113
- end
114
- end
115
-
116
- # Yield generated screenshots and remove them
117
- def screenshots(&block)
118
- Dir.glob(File.join(output_directory, '*.{jpg,JPG}').to_s).each do |filepath|
119
- yield filepath
120
- FileUtils.rm(filepath, :force => true)
121
- end
122
- end
123
-
124
- protected
125
-
126
- def create_encodings
127
- profiles.each do |profile_name, profile|
128
- encoding = create_encoding(profile_name)
129
- start_encoding(encoding)
130
- end
131
- end
132
-
133
- def create_encoding(profile_name)
134
- instance.falcon_encodings.create(
135
- :name => name,
136
- :profile_name => profile_name,
137
- :source_path => source_path)
138
- end
139
-
140
- # Start encoding direcly or send it into method if set
141
- def start_encoding(encoding)
142
- if @encode
143
- @encode.respond_to?(:call) ? @encode.call(encoding) : instance.send(@encode, encoding)
144
- else
145
- encoding.encode
146
- end
147
- end
148
-
149
- # Clear generated files and created encodings
150
- def flush_deletes
151
- instance.falcon_encodings.clear
152
- end
153
- end
154
- end