puremotion 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +1 -0
- data/README.md +69 -0
- data/Rakefile +23 -11
- data/examples/progress_reporting.rb +26 -0
- data/examples/simple.rb +22 -0
- data/ext/puremotion/audio.c +38 -0
- data/ext/puremotion/extconf.rb +34 -0
- data/ext/puremotion/frame.c +176 -0
- data/ext/puremotion/media.c +175 -0
- data/ext/puremotion/puremotion.c +26 -0
- data/ext/puremotion/puremotion.h +38 -0
- data/ext/puremotion/stream.c +128 -0
- data/ext/puremotion/stream_collection.c +44 -0
- data/ext/puremotion/utils.c +81 -0
- data/ext/puremotion/utils.h +6 -0
- data/ext/puremotion/video.c +141 -0
- data/lib/{puremotion/events → events}/event.rb +0 -0
- data/lib/{puremotion/events → events}/generator.rb +0 -0
- data/lib/media.rb +89 -0
- data/lib/preset/audio/audio.rb +42 -0
- data/lib/preset/file.rb +19 -0
- data/lib/preset/general.rb +28 -0
- data/lib/preset/metadata.rb +41 -0
- data/lib/preset/preset.rb +120 -0
- data/lib/preset/video/crop.rb +29 -0
- data/lib/preset/video/pad.rb +31 -0
- data/lib/preset/video/video.rb +130 -0
- data/lib/puremotion.rb +20 -12
- data/lib/puremotion_native.so +0 -0
- data/lib/threading.rb +54 -0
- data/lib/{puremotion/tools → tools}/ffmpeg.rb +12 -50
- data/lib/transcode/transcode.rb +142 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/units/media_spec.rb +13 -0
- data/spec/units/preset_spec.rb +91 -0
- metadata +52 -44
- data/.document +0 -5
- data/.gitignore +0 -21
- data/README.rdoc +0 -18
- data/VERSION +0 -1
- data/lib/puremotion/codecs.rb +0 -59
- data/lib/puremotion/media.rb +0 -490
- data/lib/puremotion/media/stream.rb +0 -4
- data/lib/puremotion/media/stream/audio.rb +0 -0
- data/lib/puremotion/media/stream/base.rb +0 -7
- data/lib/puremotion/media/stream/collection.rb +0 -5
- data/lib/puremotion/media/stream/video.rb +0 -61
- data/lib/puremotion/recipes/ipod.yml +0 -12
- data/lib/puremotion/thread.rb +0 -153
- data/lib/puremotion/transcode/recipe.rb +0 -250
- data/lib/puremotion/transcode/transcode.rb +0 -153
- data/puremotion.gemspec +0 -68
- data/test/helper.rb +0 -10
- data/test/test_puremotion.rb +0 -7
File without changes
|
@@ -1,61 +0,0 @@
|
|
1
|
-
class PureMotion::Stream::Video
|
2
|
-
|
3
|
-
@raw = ''
|
4
|
-
@media = nil
|
5
|
-
|
6
|
-
def initialize raw
|
7
|
-
@raw = raw
|
8
|
-
|
9
|
-
@matches = {
|
10
|
-
:stream => /Stream\s*(.*?)[,|:|\(|\[].*?\s*Video:\s*(.*?),\s*(.*?),\s*(\d*)x(\d*)/,
|
11
|
-
:bitrate => /Stream\s*.*?[,|:|\(|\[].*?\s*Video:\s*.*?,\s*.*?,\s*\d*x\d*[^,]*,\s*([0-9\.]+)\s*(.*\/.),/,
|
12
|
-
:frame_rate => /Stream\s*.*?[,|:|\(|\[].*?\s*Video:\s\S*,\s\S*,\s\d+x\d+[^,]+,\s\S*\s\S*,\s([^,\s]*)/,
|
13
|
-
:raw_duration => /Duration:\s*([0-9\:\.]+),/
|
14
|
-
}
|
15
|
-
end
|
16
|
-
|
17
|
-
def valid?
|
18
|
-
return (@raw =~ match(:stream))
|
19
|
-
end
|
20
|
-
|
21
|
-
def fps
|
22
|
-
fps = test(:frame_rate, 1)
|
23
|
-
|
24
|
-
return fps.to_f unless fps.to_f == 0.0
|
25
|
-
return nil
|
26
|
-
end
|
27
|
-
|
28
|
-
def resolution
|
29
|
-
res = {
|
30
|
-
:width => test(:stream, 4).to_i,
|
31
|
-
:height => test(:stream, 5).to_i
|
32
|
-
}
|
33
|
-
|
34
|
-
res.each_pair { |k, v| if v == 0 then res[k] = nil end }
|
35
|
-
|
36
|
-
Struct.new("Resolution", :width, :height)
|
37
|
-
Struct::Resolution.new(
|
38
|
-
res[:width],
|
39
|
-
res[:height]
|
40
|
-
)
|
41
|
-
end
|
42
|
-
|
43
|
-
#def detail
|
44
|
-
#
|
45
|
-
#end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def match name
|
50
|
-
return @matches[name]
|
51
|
-
end
|
52
|
-
|
53
|
-
def test(name = nil, n = -1)
|
54
|
-
match = @matches[name]
|
55
|
-
return nil if match.nil?
|
56
|
-
|
57
|
-
return match.match(@raw) unless n >= 0
|
58
|
-
return match.match(@raw)[n]
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
data/lib/puremotion/thread.rb
DELETED
@@ -1,153 +0,0 @@
|
|
1
|
-
# Author:: Jared Kuolt (mailto:me@superjared.com)
|
2
|
-
# Copyright:: Copyright (c) 2009 Jared Kuolt
|
3
|
-
# License:: MIT License
|
4
|
-
#
|
5
|
-
# See README.rdoc[link:files/README_rdoc.html] for usage
|
6
|
-
#
|
7
|
-
require 'logger'
|
8
|
-
require 'timeout'
|
9
|
-
|
10
|
-
module PureMotion
|
11
|
-
class Thread
|
12
|
-
# The Thread object, brah
|
13
|
-
attr_reader :thread
|
14
|
-
# If the Thread takes a poopie...
|
15
|
-
attr_reader :exception
|
16
|
-
# An identifier
|
17
|
-
attr_accessor :label
|
18
|
-
|
19
|
-
# Create a new RobustThread (see README)
|
20
|
-
def initialize(opts={}, &block)
|
21
|
-
self.class.send :init_exit_handler
|
22
|
-
args = (opts[:args] or [])
|
23
|
-
self.class.send :do_before_init
|
24
|
-
@thread = ::Thread.new(*args) do |*targs|
|
25
|
-
begin
|
26
|
-
self.class.send :do_before_yield
|
27
|
-
block.call(*targs)
|
28
|
-
self.class.send :do_after_yield
|
29
|
-
rescue Exception => e
|
30
|
-
@exception = e
|
31
|
-
self.class.send :handle_exception, e
|
32
|
-
end
|
33
|
-
self.class.log "#{self.label.inspect} exited cleanly"
|
34
|
-
end
|
35
|
-
self.label = opts[:label] || @thread.inspect
|
36
|
-
self.class.group << self
|
37
|
-
end
|
38
|
-
|
39
|
-
## Class methods and attributes
|
40
|
-
class << self
|
41
|
-
attr_accessor :logger, :say_goodnight, :exit_handler_initialized, :callbacks
|
42
|
-
VALID_CALLBACKS = [:before_init, :before_yield, :after_yield, :after_join, :before_exit]
|
43
|
-
|
44
|
-
# Logger object (see README)
|
45
|
-
def logger
|
46
|
-
@logger ||= Logger.new(STDOUT)
|
47
|
-
end
|
48
|
-
|
49
|
-
# Simple log interface
|
50
|
-
def log(msg, level=:info)
|
51
|
-
#self.logger.send level, "#{self}: " + msg
|
52
|
-
end
|
53
|
-
|
54
|
-
# The collection of RobustThread objects
|
55
|
-
def group
|
56
|
-
@group ||= []
|
57
|
-
end
|
58
|
-
|
59
|
-
# Loop an activity and exit it cleanly (see README)
|
60
|
-
def loop(opts={}, &block)
|
61
|
-
sleep_seconds = opts.delete(:seconds) || 2
|
62
|
-
self.new(opts) do |*args|
|
63
|
-
Kernel.loop do
|
64
|
-
break if self.say_goodnight
|
65
|
-
block.call(*args)
|
66
|
-
# We want to sleep for the right amount of time, but we also don't
|
67
|
-
# want to wait until the sleep is done if our exit handler has been
|
68
|
-
# called so we iterate over a loop, sleeping only 0.1 and checking
|
69
|
-
# each iteration whether we need to die, and the timeout is a noop
|
70
|
-
# indicating we need to continue.
|
71
|
-
begin
|
72
|
-
Timeout.timeout(sleep_seconds) do
|
73
|
-
Kernel.loop do
|
74
|
-
break if self.say_goodnight
|
75
|
-
sleep 0.1
|
76
|
-
end
|
77
|
-
end
|
78
|
-
rescue Timeout::Error
|
79
|
-
# OK
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# Set exception handler
|
86
|
-
def exception_handler(&block)
|
87
|
-
unless block.arity == 1
|
88
|
-
raise ArgumentError, "Bad arity for exception handler. It may only accept a single argument"
|
89
|
-
end
|
90
|
-
@exception_handler = block
|
91
|
-
end
|
92
|
-
|
93
|
-
# Add a callback
|
94
|
-
public
|
95
|
-
def add_callback(sym, &block)
|
96
|
-
sym = sym.to_sym
|
97
|
-
raise ArgumentError, "Invalid callback #{sym.inspect}" unless VALID_CALLBACKS.include? sym
|
98
|
-
self.callbacks ||= {}
|
99
|
-
self.callbacks[sym] ||= []
|
100
|
-
self.callbacks[sym] << block
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
# Calls exception handler if set (see RobustThread.exception_handler)
|
105
|
-
def handle_exception(exc)
|
106
|
-
if @exception_handler.is_a? Proc
|
107
|
-
@exception_handler.call(exc)
|
108
|
-
else
|
109
|
-
log("Unhandled exception:\n#{exc.message} " \
|
110
|
-
"(#{exc.class}): \n\t#{exc.backtrace.join("\n\t")}", :error)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# Sets up the exit_handler unless exit_handler_initialized
|
115
|
-
def init_exit_handler
|
116
|
-
return if self.exit_handler_initialized
|
117
|
-
self.say_goodnight = false
|
118
|
-
at_exit do
|
119
|
-
self.say_goodnight = true
|
120
|
-
begin
|
121
|
-
self.group.each do |rt|
|
122
|
-
log "waiting on #{rt.label.inspect}" if rt.thread.alive?
|
123
|
-
rt.thread.join
|
124
|
-
rt.class.send :do_after_join
|
125
|
-
end
|
126
|
-
self.send :do_before_exit
|
127
|
-
log "exited cleanly"
|
128
|
-
rescue Interrupt
|
129
|
-
log "prematurely killed by interrupt!", :error
|
130
|
-
end
|
131
|
-
end
|
132
|
-
self.exit_handler_initialized = true
|
133
|
-
end
|
134
|
-
|
135
|
-
def perform_callback(sym)
|
136
|
-
raise ArgumentError, "Cannot perform invalid callback #{sym.inspect}" unless VALID_CALLBACKS.include? sym
|
137
|
-
return unless self.callbacks and self.callbacks[sym]
|
138
|
-
self.callbacks[sym].reverse.each do |callback|
|
139
|
-
callback.call
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
# Performs callback, if possible
|
144
|
-
def method_missing(sym, *args)
|
145
|
-
if sym.to_s =~ /^do_(.*)$/
|
146
|
-
perform_callback($1.to_sym)
|
147
|
-
else
|
148
|
-
raise NoMethodError, "RobustThread method_missing: #{sym.inspect}"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
@@ -1,250 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
module PureMotion::Transcode
|
4
|
-
|
5
|
-
class Recipe
|
6
|
-
|
7
|
-
def self.from_file(file)
|
8
|
-
|
9
|
-
file.sub!(/\.yml/, '') if file =~ /\.yml/
|
10
|
-
|
11
|
-
built_in_path = File.join( File.expand_path(File.dirname(__FILE__)), '../recipes', file + '.yml')
|
12
|
-
|
13
|
-
where = nil
|
14
|
-
path = nil
|
15
|
-
|
16
|
-
if File.exists? built_in_path then where = :internal end
|
17
|
-
if File.exists? file then where = :external end
|
18
|
-
|
19
|
-
raise(ArgumentError, "Recipe file '#{file}' not found") if where.nil?
|
20
|
-
|
21
|
-
case where
|
22
|
-
when :internal
|
23
|
-
path = built_in_path
|
24
|
-
when :external
|
25
|
-
path = file
|
26
|
-
end
|
27
|
-
|
28
|
-
return self.new({ :source => :file, :recipe => path })
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.from_name(name)
|
32
|
-
|
33
|
-
n = name.to_s
|
34
|
-
|
35
|
-
raise(ArgumentError, 'Invalid recipe name') if not n.is_a?(String)
|
36
|
-
|
37
|
-
return from_file(n)
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.build(hash)
|
42
|
-
return self.new({ :source => :hash, :recipe => hash})
|
43
|
-
end
|
44
|
-
|
45
|
-
@recipe = {
|
46
|
-
'format' => :skip
|
47
|
-
}
|
48
|
-
@video = {}
|
49
|
-
@audio = {}
|
50
|
-
@container = nil
|
51
|
-
|
52
|
-
attr_accessor :audio, :video, :arg_string, :args
|
53
|
-
|
54
|
-
def initialize(opts)
|
55
|
-
|
56
|
-
if not opts.is_a? Hash then raise(ArgumentError, "opts should be a hash") end
|
57
|
-
|
58
|
-
defaults = {
|
59
|
-
:source => :hash,
|
60
|
-
:recipe => nil
|
61
|
-
}
|
62
|
-
|
63
|
-
@opts = defaults.merge!(opts)
|
64
|
-
|
65
|
-
@recipe = @opts[:recipe]
|
66
|
-
|
67
|
-
if @recipe.nil? then raise(ArgumentError, "No recipe given") end
|
68
|
-
|
69
|
-
case @opts[:source]
|
70
|
-
when :hash
|
71
|
-
hash_build
|
72
|
-
when :file
|
73
|
-
file_build @recipe
|
74
|
-
when :name
|
75
|
-
from_name @recipe
|
76
|
-
else
|
77
|
-
raise(ArgumentError, 'Invalid recipe source')
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
|
82
|
-
def parse o
|
83
|
-
|
84
|
-
recipe = nil
|
85
|
-
|
86
|
-
recipe = o['recipe'] if o.has_key? 'recipe'
|
87
|
-
recipe = o if o.has_key? 'video' or o.has_key? 'audio'
|
88
|
-
|
89
|
-
raise ArgumentError, 'Bad recipe' if recipe.nil?
|
90
|
-
|
91
|
-
video = recipe['video']
|
92
|
-
audio = recipe['audio']
|
93
|
-
|
94
|
-
parse_video = lambda do |source_video|
|
95
|
-
|
96
|
-
@video = {}
|
97
|
-
@video.merge! source_video
|
98
|
-
|
99
|
-
# Video codec
|
100
|
-
|
101
|
-
# End video codec
|
102
|
-
|
103
|
-
# Video bitrate
|
104
|
-
if @video['bitrate'].nil? then @video['bitrate'] = :skip else
|
105
|
-
begin
|
106
|
-
@video['bitrate'] = @video['bitrate'].to_i if not @video['bitrate'].is_a? Integer
|
107
|
-
rescue
|
108
|
-
raise ArgumentError, 'Video bitrate must be a number'
|
109
|
-
end
|
110
|
-
|
111
|
-
@video['bitrate'] *= 1024
|
112
|
-
raise ArgumentError, 'Recipe: video bitrate too low' if @video['bitrate'] < 1024
|
113
|
-
end
|
114
|
-
# End video
|
115
|
-
|
116
|
-
# Video resolution
|
117
|
-
if @video['resolution'].nil? then @video['resolution'] = :skip else
|
118
|
-
if not @video['resolution'] =~ /\d*x\d*/ then
|
119
|
-
raise ArgumentError, "Invalid resolution"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
# End video resolution
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
127
|
-
|
128
|
-
parse_audio = lambda do |source_audio|
|
129
|
-
|
130
|
-
@audio = {
|
131
|
-
'codec' => nil,
|
132
|
-
'bitrate' => nil,
|
133
|
-
}
|
134
|
-
|
135
|
-
@audio.merge! source_audio
|
136
|
-
|
137
|
-
# Audio codec
|
138
|
-
if @audio['codec'].nil? then @audio['codec'] = :skip else
|
139
|
-
raise ArgumentError, 'Recipe: Invalid Audio Codec' unless PureMotion::Codecs.valid? @audio['codec'], :audio
|
140
|
-
@audio['codec'] = PureMotion::Codecs.find(@audio['codec']).ffmpeg_name
|
141
|
-
end
|
142
|
-
# End Audio codec
|
143
|
-
|
144
|
-
# Audio bitrate
|
145
|
-
if @audio['bitrate'].nil? then @audio['bitrate'] = :skip else
|
146
|
-
begin
|
147
|
-
@audio['bitrate'] = @audio['bitrate'].to_i if not @audio['bitrate'].is_a? Integer
|
148
|
-
rescue
|
149
|
-
raise ArgumentError, 'Audio bitrate must be a number'
|
150
|
-
end
|
151
|
-
|
152
|
-
@audio['bitrate'] *= 1024
|
153
|
-
raise ArgumentError, 'Recipe: Audio bitrate too low' if @audio['bitrate'] < 1024
|
154
|
-
end
|
155
|
-
# End Audio Bitrate
|
156
|
-
|
157
|
-
# Audio channels
|
158
|
-
if @audio['channels'].nil? then @audio['channels'] = :skip else
|
159
|
-
channel_types = { 'mono' => 1, 'stereo' => 2 }
|
160
|
-
if @audio['channels'].is_a? String then
|
161
|
-
@audio['channels'].downcase!
|
162
|
-
if channel_types[@audio['channels']].nil? then
|
163
|
-
@audio['channels'] = :skip
|
164
|
-
else
|
165
|
-
@audio['channels'] = channel_types[@audio['channels']]
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
# End audio channels
|
170
|
-
|
171
|
-
end
|
172
|
-
|
173
|
-
parse_video.call video unless video.nil?
|
174
|
-
parse_audio.call audio unless audio.nil?
|
175
|
-
|
176
|
-
@recipe = {}
|
177
|
-
|
178
|
-
if recipe['format'].nil? then recipe['format'] = :skip else
|
179
|
-
@recipe.store('format', recipe['format'])
|
180
|
-
end
|
181
|
-
|
182
|
-
end
|
183
|
-
|
184
|
-
def valid?
|
185
|
-
valid = true
|
186
|
-
if audio? and !valid_audio? then valid = false end
|
187
|
-
if video? and !valid_video? then valid = false end
|
188
|
-
valid
|
189
|
-
end
|
190
|
-
|
191
|
-
def valid_video?
|
192
|
-
|
193
|
-
end
|
194
|
-
|
195
|
-
def valid_audio?
|
196
|
-
|
197
|
-
end
|
198
|
-
|
199
|
-
def to_args
|
200
|
-
|
201
|
-
args = []
|
202
|
-
arg_strings = []
|
203
|
-
|
204
|
-
args[0] = ['f', @recipe['format'] ]
|
205
|
-
args[1] = ['acodec', @audio['codec'] ]
|
206
|
-
args[2] = ['vcodec', @video['codec'] ]
|
207
|
-
args[3] = ['vb', @video['bitrate'] ]
|
208
|
-
args[4] = ['s', @video['resolution']]
|
209
|
-
args[5] = ['r', @video['fps'] ]
|
210
|
-
args[6] = ['ab', @audio['bitrate'] ]
|
211
|
-
args[7] = ['ac', @audio['channels'] ]
|
212
|
-
|
213
|
-
required_value_args = ['f', 'vcodec', 'acodec', 'vb', 's', 'r', 'ab', 'ac']
|
214
|
-
|
215
|
-
args.each do |arg|
|
216
|
-
next if required_value_args.include? arg[0] and arg[1].nil?
|
217
|
-
arg_strings << format_arg(arg[0], arg[1])
|
218
|
-
end
|
219
|
-
|
220
|
-
@args = arg_strings.join(' ')
|
221
|
-
|
222
|
-
@args
|
223
|
-
|
224
|
-
end
|
225
|
-
|
226
|
-
private
|
227
|
-
|
228
|
-
def format_arg name, value
|
229
|
-
name = '' if name.nil?
|
230
|
-
value = '' if value.nil?
|
231
|
-
value = value.to_s
|
232
|
-
value.gsub! '"', ''
|
233
|
-
value = '"' + value + '"' if value =~ /\s/
|
234
|
-
return value if name == :non
|
235
|
-
return name if value == :non
|
236
|
-
'-' + name.to_s + ' ' + value.to_s
|
237
|
-
end
|
238
|
-
|
239
|
-
def hash_build
|
240
|
-
|
241
|
-
end
|
242
|
-
|
243
|
-
def file_build(file)
|
244
|
-
@yaml = ::YAML.load( File.read(file) )
|
245
|
-
parse @yaml
|
246
|
-
end
|
247
|
-
|
248
|
-
end
|
249
|
-
|
250
|
-
end
|