falcon 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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