puremotion 0.0.1 → 0.1.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/.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
|