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.
Files changed (54) hide show
  1. data/.yardopts +1 -0
  2. data/README.md +69 -0
  3. data/Rakefile +23 -11
  4. data/examples/progress_reporting.rb +26 -0
  5. data/examples/simple.rb +22 -0
  6. data/ext/puremotion/audio.c +38 -0
  7. data/ext/puremotion/extconf.rb +34 -0
  8. data/ext/puremotion/frame.c +176 -0
  9. data/ext/puremotion/media.c +175 -0
  10. data/ext/puremotion/puremotion.c +26 -0
  11. data/ext/puremotion/puremotion.h +38 -0
  12. data/ext/puremotion/stream.c +128 -0
  13. data/ext/puremotion/stream_collection.c +44 -0
  14. data/ext/puremotion/utils.c +81 -0
  15. data/ext/puremotion/utils.h +6 -0
  16. data/ext/puremotion/video.c +141 -0
  17. data/lib/{puremotion/events → events}/event.rb +0 -0
  18. data/lib/{puremotion/events → events}/generator.rb +0 -0
  19. data/lib/media.rb +89 -0
  20. data/lib/preset/audio/audio.rb +42 -0
  21. data/lib/preset/file.rb +19 -0
  22. data/lib/preset/general.rb +28 -0
  23. data/lib/preset/metadata.rb +41 -0
  24. data/lib/preset/preset.rb +120 -0
  25. data/lib/preset/video/crop.rb +29 -0
  26. data/lib/preset/video/pad.rb +31 -0
  27. data/lib/preset/video/video.rb +130 -0
  28. data/lib/puremotion.rb +20 -12
  29. data/lib/puremotion_native.so +0 -0
  30. data/lib/threading.rb +54 -0
  31. data/lib/{puremotion/tools → tools}/ffmpeg.rb +12 -50
  32. data/lib/transcode/transcode.rb +142 -0
  33. data/spec/spec_helper.rb +7 -0
  34. data/spec/units/media_spec.rb +13 -0
  35. data/spec/units/preset_spec.rb +91 -0
  36. metadata +52 -44
  37. data/.document +0 -5
  38. data/.gitignore +0 -21
  39. data/README.rdoc +0 -18
  40. data/VERSION +0 -1
  41. data/lib/puremotion/codecs.rb +0 -59
  42. data/lib/puremotion/media.rb +0 -490
  43. data/lib/puremotion/media/stream.rb +0 -4
  44. data/lib/puremotion/media/stream/audio.rb +0 -0
  45. data/lib/puremotion/media/stream/base.rb +0 -7
  46. data/lib/puremotion/media/stream/collection.rb +0 -5
  47. data/lib/puremotion/media/stream/video.rb +0 -61
  48. data/lib/puremotion/recipes/ipod.yml +0 -12
  49. data/lib/puremotion/thread.rb +0 -153
  50. data/lib/puremotion/transcode/recipe.rb +0 -250
  51. data/lib/puremotion/transcode/transcode.rb +0 -153
  52. data/puremotion.gemspec +0 -68
  53. data/test/helper.rb +0 -10
  54. data/test/test_puremotion.rb +0 -7
@@ -1,4 +0,0 @@
1
- module PureMotion
2
-
3
-
4
- end
File without changes
@@ -1,7 +0,0 @@
1
- class PureMotion::Stream::Base
2
-
3
- def initialize
4
-
5
- end
6
-
7
- end
@@ -1,5 +0,0 @@
1
- class PureMotion::Stream::Collection < Array
2
-
3
-
4
-
5
- end
@@ -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
@@ -1,12 +0,0 @@
1
- name: iPod
2
- desc: Encodes a video suitable for iPod
3
- recipe:
4
- video:
5
- codec: libx264
6
- bitrate: 512
7
- resolution: 320x240
8
- fps: 20
9
- audio:
10
- codec: aac
11
- bitrate: 128
12
- channels: mono
@@ -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