puremotion 0.1.0 → 0.1.1

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,7 +1,8 @@
1
1
  PureMotion (0.1.0)
2
2
  ==================
3
3
 
4
- **Homepage**: [http://github.com/ominiom/puremotion](http://github.com/ominiom/puremotion)
4
+ * **Homepage**: [http://github.com/ominiom/puremotion](http://github.com/ominiom/puremotion)
5
+ * **Docs (Incomplete)**: [http://ominiom.net/projects/puremotion/doc/](http://ominiom.net/projects/puremotion/doc/)
5
6
 
6
7
  OVERVIEW
7
8
  --------
@@ -57,7 +58,7 @@ a video stream.
57
58
  Media 'sample.mp4' do
58
59
 
59
60
  if video? then
60
- seek(5).grab.resize(320, 240).save('thumb.png')
61
+ video.seek(5).grab.resize(320, 240).save('thumb.png')
61
62
  end
62
63
 
63
64
  end
@@ -66,4 +67,4 @@ BUILDING
66
67
  --------
67
68
 
68
69
  To build the PureMotion gem from source you will need to have libav* and libgd
69
- installed.
70
+ installed.
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ begin
12
12
  gem.authors = ["Ominiom"]
13
13
  gem.extensions = ["ext/puremotion/extconf.rb"]
14
14
  gem.files = [
15
- "README.md",
15
+ "README.markdown",
16
16
  "LICENSE",
17
17
  "Rakefile",
18
18
  ".yardopts",
@@ -63,3 +63,9 @@ rescue LoadError
63
63
  abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
64
64
  end
65
65
  end
66
+
67
+ require 'spec/rake/spectask'
68
+ Spec::Rake::SpecTask.new(:spec) do |spec|
69
+ spec.libs << 'lib' << 'spec'
70
+ spec.spec_files = FileList['spec/**/*_spec.rb']
71
+ end
File without changes
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'daemons'
5
+
6
+ server = File.join(File.dirname(__FILE__), %w(.. lib server server.rb))
7
+
8
+ options = {
9
+ :app_name => "pmts",
10
+ :dir_mode => :normal,
11
+ :dir => File.dirname(__FILE__),
12
+ :multiple => false,
13
+ :ontop => false,
14
+ :mode => :load,
15
+ :backtrace => true,
16
+ :monitor => true
17
+ }
18
+
19
+ Daemons.run(server, options)
@@ -0,0 +1,28 @@
1
+ Transcode '../spec/samples/sample.ogv' do
2
+
3
+ event :failure do |reason, message|
4
+ puts "Failure - #{message}"
5
+ end
6
+
7
+ event :complete do |output|
8
+ puts "Success! Output is #{output.video.duration}"
9
+ end
10
+
11
+ overwrite!
12
+
13
+ video do
14
+ codec :flv
15
+ bitrate '200k'
16
+ resolution 320, 176
17
+ end
18
+
19
+ audio do
20
+ codec :libmp3lame
21
+ bitrate '96k'
22
+ end
23
+
24
+ log 'failure_handling.log'
25
+
26
+ output '/totally/invalid/path/on/normal/systems.flv'
27
+
28
+ end
@@ -0,0 +1,7 @@
1
+ ff = PureMotion::Process.new('ffmpeg -i test.wmv')
2
+
3
+ ff.on(:output) do |output|
4
+ puts output
5
+ end
6
+
7
+ ff.run
@@ -17,7 +17,7 @@ Transcode do
17
17
 
18
18
  output 'test.flv'
19
19
 
20
- log 'simple.log'
20
+ log 'progress_reporting.log'
21
21
 
22
22
  event :progress do |transcode, progress|
23
23
  puts "#{progress[:percent]}%"
@@ -22,6 +22,13 @@ static AVFrame * alloc_picture(int pix_fmt, int width, int height) {
22
22
  return picture;
23
23
  }
24
24
 
25
+ /* Convert frame to RGB24
26
+ *
27
+ * Used internally to convert the pixel format when resizing and saving to PNG
28
+ *
29
+ * @return [PureMotion::Frame] Frame in RGB24 format
30
+ *
31
+ */
25
32
  static VALUE frame_to_rgb24(VALUE self) {
26
33
  int width = NUM2INT(rb_iv_get(self, "@width"));
27
34
  int height = NUM2INT(rb_iv_get(self, "@height"));
@@ -42,8 +49,10 @@ static VALUE frame_to_rgb24(VALUE self) {
42
49
  return build_frame_object(to, width, height, PIX_FMT_RGB24);
43
50
  }
44
51
 
45
-
46
-
52
+ /* Encodes the frame in PPM format
53
+ *
54
+ * @return [String] PPM encoded representation of the frame
55
+ */
47
56
  static VALUE frame_to_ppm(VALUE self) {
48
57
  VALUE rb_frame = frame_to_rgb24(self);
49
58
  AVFrame * frame = get_frame(rb_frame);
@@ -63,6 +72,13 @@ static VALUE frame_to_ppm(VALUE self) {
63
72
  return rb_str_new(data_string, size);
64
73
  }
65
74
 
75
+ /* Resizes the frame using libgd
76
+ *
77
+ * @param [Integer] width Target width
78
+ * @param [Integer] height Target height
79
+ *
80
+ * @return [PureMotion::Frame] New frame that's been resized
81
+ */
66
82
  static VALUE frame_resize(VALUE self, VALUE w, VALUE h) {
67
83
 
68
84
  int orig_width = NUM2INT(rb_iv_get(self, "@width"));
@@ -89,8 +105,28 @@ static VALUE frame_resize(VALUE self, VALUE w, VALUE h) {
89
105
 
90
106
  }
91
107
 
108
+ /* Saves the frame to PNG
109
+ *
110
+ * @param [String] path A writable path to save the frame to
111
+ *
112
+ * @return [PureMotion::Frame] self
113
+ */
92
114
  static VALUE frame_save(VALUE self, VALUE filename) {
93
115
 
116
+ VALUE directory = rb_funcall(rb_cFile, rb_intern("dirname"), 1, filename);
117
+ VALUE writable = rb_funcall(rb_cFile, rb_intern("writable?"), 1, directory);
118
+ VALUE exists = rb_funcall(rb_cFile, rb_intern("exists?"), 1, filename);
119
+
120
+ if( exists == Qtrue ) {
121
+ rb_raise(rb_eArgError, "File '%s' already exists.", STR2CSTR(filename));
122
+ return self;
123
+ }
124
+
125
+ if( writable == Qfalse ) {
126
+ rb_raise(rb_eArgError, "File '%s' is not writable.", STR2CSTR(filename));
127
+ return self;
128
+ }
129
+
94
130
  int w = NUM2INT(rb_iv_get(self, "@width"));
95
131
  int h = NUM2INT(rb_iv_get(self, "@height"));
96
132
 
@@ -115,7 +115,7 @@ static VALUE media_init(VALUE self, VALUE file) {
115
115
  if( error < 0 ) {
116
116
  rb_raise(rb_eUnsupportedFormat, "File '%s' unable to be opened. Unsupported format.", StringValuePtr(file));
117
117
  rb_iv_set(self, "@valid", Qfalse);
118
- return Qnil;
118
+ return self;
119
119
  }
120
120
 
121
121
  error = av_find_stream_info(fmt_ctx);
@@ -123,6 +123,7 @@ static VALUE media_init(VALUE self, VALUE file) {
123
123
  if( error < 0 ) {
124
124
  rb_raise(rb_eUnsupportedFormat, "File '%s': Streams are unreadable.", StringValuePtr(file));
125
125
  rb_iv_set(self, "@valid", Qfalse);
126
+ return self;
126
127
  }
127
128
 
128
129
  rb_iv_set(self, "@valid", Qtrue);
@@ -4,28 +4,6 @@
4
4
  VALUE rb_cStream;
5
5
  VALUE rb_mStreams;
6
6
 
7
- static int next_packet(AVFormatContext * format_context, AVPacket * packet)
8
- {
9
- if(packet->data != NULL)
10
- av_free_packet(packet);
11
-
12
- if(av_read_frame(format_context, packet) < 0) {
13
- return -1;
14
- }
15
-
16
- return 0;
17
- }
18
-
19
- static int next_packet_for_stream(AVFormatContext * format_context, int stream_index, AVPacket * packet)
20
- {
21
- int ret = 0;
22
- do {
23
- ret = next_packet(format_context, packet);
24
- } while(packet->stream_index != stream_index && ret == 0);
25
-
26
- return ret;
27
- }
28
-
29
7
  static VALUE stream_type( VALUE self ) {
30
8
  AVStream * stream = get_stream(self);
31
9
 
@@ -78,4 +78,4 @@ VALUE codec_type_id_to_sym(int codec_type)
78
78
  }
79
79
 
80
80
  return type_sym;
81
- }
81
+ }
@@ -5,6 +5,35 @@ VALUE rb_cStream;
5
5
  VALUE rb_cVideoStream;
6
6
  VALUE rb_mStreams;
7
7
 
8
+ static int next_packet(AVFormatContext * format_context, AVPacket * packet)
9
+ {
10
+ if(packet->data != NULL)
11
+ av_free_packet(packet);
12
+
13
+ if(av_read_frame(format_context, packet) < 0) {
14
+ return -1;
15
+ }
16
+
17
+ return 0;
18
+ }
19
+
20
+ static int next_packet_for_stream(AVFormatContext * format_context, int stream_index, AVPacket * packet)
21
+ {
22
+ int ret = 0;
23
+ do {
24
+ ret = next_packet(format_context, packet);
25
+ } while(packet->stream_index != stream_index && ret == 0);
26
+
27
+ return ret;
28
+ }
29
+
30
+ /*
31
+ * call-seq: frame_rate => Float
32
+ *
33
+ * Returns the video frame rate as a Float
34
+ *
35
+ * @return [Float] Framerate
36
+ */
8
37
  static VALUE stream_frame_rate(VALUE self) {
9
38
  AVStream * stream = get_stream(self);
10
39
  return(rb_float_new(av_q2d(stream->r_frame_rate)));
@@ -44,8 +73,23 @@ static int extract_next_frame(AVFormatContext * format_context, AVCodecContext *
44
73
  /*
45
74
  * call-seq: grab => PureMotion::Frame
46
75
  *
76
+ * Grabs a single frame from the current position in the stream.
77
+ *
78
+ * Example Usage:
79
+ *
80
+ * Media 'sample.wmv' do
81
+ *
82
+ * if video? then
83
+ *
84
+ * frame = video.seek(5).grab
85
+ *
86
+ * # Resize, save
47
87
  *
88
+ * end
48
89
  *
90
+ * end
91
+ *
92
+ * @return [PureMotion::Frame]
49
93
  */
50
94
 
51
95
  static VALUE stream_grab(VALUE self) {
@@ -98,6 +142,12 @@ static VALUE stream_grab(VALUE self) {
98
142
  return self;
99
143
  }
100
144
 
145
+ /* call-seq: resolution => Array<Integer width, Integer height>
146
+ *
147
+ * Returns video frame resolution as [width, height] array
148
+ *
149
+ * @return [Array<Integer width, Integer height>]
150
+ */
101
151
  static VALUE stream_resolution(VALUE self, VALUE media) {
102
152
  AVFormatContext * format_context = get_format_context(rb_iv_get(self, "@media"));
103
153
  AVStream * stream = get_stream(self);
@@ -138,4 +188,4 @@ void Init_video_stream() {
138
188
  rb_define_method(rb_cVideoStream, "resolution", stream_resolution, 0);
139
189
  rb_define_method(rb_cVideoStream, "frame_rate", stream_frame_rate, 0);
140
190
  rb_define_method(rb_cVideoStream, "grab", stream_grab, 0);
141
- }
191
+ }
@@ -43,17 +43,17 @@ module PureMotion::Events
43
43
 
44
44
  # Calls all of the handlers for the events, with the given sender and set
45
45
  # of arguments.
46
- def call(sender, args)
46
+ def call(*args)
47
47
  # Make sure we don't get surprised with new friends while we're calling
48
48
  # the handlers
49
49
  named_handlers = @named_handlers
50
50
  handlers = @handlers
51
51
 
52
52
  named_handlers.each do |name, handler|
53
- handler.call(sender, *args)
53
+ handler.call(*args)
54
54
  end
55
55
  handlers.each do |handler|
56
- handler.call(sender, *args)
56
+ handler.call(*args)
57
57
  end
58
58
  end
59
59
 
@@ -1,50 +1,31 @@
1
- # ruby-event - generator.rb
2
- # Author :: Stefan Nuxoll
3
- # License :: BSD License
4
- # Copyright :: Copyright (C) 2009 Stefan Nuxoll
1
+ module PureMotion
2
+ module Events
3
+ module Generator
5
4
 
6
- module PureMotion::Events
7
- module Generator
8
-
9
- # Constructs the methods and objects for an event automatically
10
- #
11
- # Example:
12
- # event :data_received
13
- def event(event_name)
14
- self.class_eval <<-EOT
15
-
16
- def #{event_name}(*args, &block)
17
- _ensure_events
18
- if not @_events["#{event_name}"]
19
- @_events["#{event_name}"] = PureMotion::Events::Event.new
20
- end
21
- if args != []
22
- @_events["#{event_name}"].call(self, args)
23
- else
24
- if block
25
- #{event_name} + block
26
- else
27
- @_events["#{event_name}"]
28
- end
29
- end
30
- end
31
-
32
- def #{event_name}=(event)
33
- _ensure_events
34
- if PureMotion::Events::Event === event
35
- @_events["#{event_name}"] = event
5
+ def event(event_name)
6
+ @@_events_built = false unless class_variable_defined?(:@@_events_built)
7
+ self._build_events unless @@_events_built
8
+ end
9
+
10
+ def _build_events
11
+ self.class_eval do
12
+
13
+ def on(event_name, &block)
14
+ @_events = {} unless instance_variable_defined?(:@_events)
15
+ @_events[event_name] = PureMotion::Events::Event.new unless @_events[event_name]
16
+ return @_events[event_name] unless block_given?
17
+ @_events[event_name] + block
36
18
  end
37
- end
38
-
39
- def _ensure_events
40
- if not @_events
41
- @_events = {}
19
+
20
+ def fire(event_name, *args)
21
+ @_events = {} unless instance_variable_defined?(:@_events)
22
+ @_events[event_name].call(*args) unless @_events[event_name].nil?
42
23
  end
24
+
43
25
  end
44
-
45
- private :_ensure_events
46
- EOT
26
+ @@_events_built = true
27
+ end
28
+
47
29
  end
48
-
49
30
  end
50
31
  end
@@ -4,7 +4,7 @@ module PureMotion
4
4
 
5
5
  def self.build(&block)
6
6
 
7
- preset = self.new(:dsl)
7
+ preset = self.new({ :from => :dsl })
8
8
  preset.instance_eval(&block) if block_given?
9
9
 
10
10
  preset
@@ -13,7 +13,16 @@ module PureMotion
13
13
 
14
14
  attr_accessor :event_handlers
15
15
 
16
- def initialize(form=nil)
16
+ def initialize(options = {})
17
+
18
+ defaults = {
19
+ :from => :dsl,
20
+ :input => nil,
21
+ :output => nil
22
+ }
23
+
24
+ options = defaults.merge(options)
25
+
17
26
  @input = nil
18
27
  @output = nil
19
28
  @log = nil
@@ -22,7 +31,11 @@ module PureMotion
22
31
  @commands = []
23
32
  @event_handlers = {}
24
33
  @dsls = [:general, :file, :video, :audio, :metadata ,:crop, :pad]
25
- extend_for(:general) if form == :dsl
34
+
35
+ input(options[:input]) unless options[:input].nil?
36
+ output(options[:output]) unless options[:output].nil?
37
+
38
+ extend_for(:general) if options[:from] == :dsl
26
39
  end
27
40
 
28
41
  def method_missing(name, *args, &block)
@@ -42,7 +55,7 @@ module PureMotion
42
55
  end
43
56
 
44
57
  def event_handler(name)
45
- @event_handlers[name]
58
+ @event_handlers[name] or nil
46
59
  end
47
60
 
48
61
  def log(log=nil)
@@ -65,15 +78,28 @@ module PureMotion
65
78
  return false unless @input.nil?
66
79
  if input.is_a?(String) then
67
80
  if ::File.exists?(input) then # Ruby's File class is hidden by Preset::File
68
- @input = PureMotion::Media.new(input)
81
+ begin
82
+ @input = PureMotion::Media.new(input)
83
+ rescue PureMotion::UnsupportedFormat
84
+ if event_handler(:failure).nil? then
85
+ raise ArgumentError, "Invalid input file '#{input}'" if event_handler(:failure).nil?
86
+ return false
87
+ else
88
+ event_handler(:failure).call(:invalid_input, "Invalid input file '#{input}'")
89
+ return false
90
+ end
91
+ end
69
92
  else
70
93
  raise ArgumentError, "Input file '#{input}' not found"
94
+ return false
71
95
  end
72
96
  else
73
97
  if input.is_a?(PureMotion::Media)
74
98
  @input = input
99
+ return true
75
100
  else
76
101
  raise ArgumentError, "Invalid input"
102
+ return false
77
103
  end
78
104
  end
79
105
  raise ArgumentError, "Invalid media input '#{@input.filename}'" unless @input.valid?
@@ -0,0 +1,88 @@
1
+ module PureMotion
2
+
3
+ class Process
4
+
5
+ event :start
6
+ event :exit
7
+ event :output
8
+
9
+ def initialize(options = {})
10
+
11
+ defaults = {
12
+ :command => nil,
13
+ :autostart => false
14
+ }
15
+
16
+ options = { :command => options } if options.is_a?(String)
17
+
18
+ options = defaults.merge(options)
19
+
20
+ @command = options[:command]
21
+
22
+ raise ArgumentError, "No command given" if @command.nil?
23
+
24
+ @status = :ready
25
+ @buffer = ''
26
+ @output = []
27
+ end
28
+
29
+ def run
30
+ @status = :running
31
+
32
+ @process = IO.popen("#{@command} 2>&1")
33
+ @id = @process.pid
34
+
35
+ runner = lambda {
36
+ @process.each_byte { |byte|
37
+ @process.eof? ? handle_exit : handle_output(byte)
38
+ }
39
+ }
40
+
41
+ if PureMotion::Settings[:threaded] then
42
+ PureMotion::Thread.new { runner.call }
43
+ else
44
+ runner.call
45
+ end
46
+
47
+ end
48
+
49
+ def kill!
50
+ return false unless @status == :running
51
+ ::Process.kill('TERM', @id)
52
+ end
53
+
54
+ def kill!
55
+ return false unless @status == :running
56
+ ::Process.kill('KILL', @id)
57
+ end
58
+
59
+ private
60
+
61
+ def find_in_output(regexp)
62
+ return '' if @status == :ready
63
+ m = regexp.match(@output.join('\n'))
64
+ return m[1] if m
65
+ nil
66
+ end
67
+
68
+ def handle_output(byte)
69
+ if [10, 13].include?(byte) then
70
+ @output << @buffer
71
+ fire(:output, @buffer)
72
+ @buffer = ''
73
+ else
74
+ byte = byte.chr.to_s
75
+ @buffer = @buffer + byte
76
+ end
77
+ end
78
+
79
+ def handle_exit
80
+ @status = :exited
81
+ @id = nil
82
+ @process.close
83
+ fire(:exit)
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -12,6 +12,7 @@ require req + 'events/generator'
12
12
 
13
13
  Object.extend(PureMotion::Events::Generator)
14
14
 
15
+ require req + 'settings'
15
16
  require req + 'preset/preset'
16
17
 
17
18
  require req + 'preset/general'
@@ -22,9 +23,8 @@ require req + 'preset/video/crop'
22
23
  require req + 'preset/video/pad'
23
24
  require req + 'preset/audio/audio'
24
25
 
25
-
26
+ require req + 'process'
26
27
  require req + 'threading'
27
28
  require req + 'media'
28
- require req + 'codecs'
29
29
  require req + 'transcode/transcode'
30
30
  require req + 'tools/ffmpeg'
Binary file
@@ -0,0 +1,27 @@
1
+ Models = PureMotion::Server::Models
2
+
3
+ class PureMotion::Server::App < Sinatra::Base
4
+
5
+ set :port, OPTIONS[:port]
6
+ set :public, File.dirname(__FILE__) + '/static'
7
+ set :views, File.dirname(__FILE__) + '/views'
8
+
9
+ use Rack::Auth::Basic do |username, password|
10
+ [username, password] == ['admin', 'admin']
11
+ end
12
+
13
+ get '/transcode/new' do
14
+ haml :'transcode/new'
15
+ end
16
+
17
+ post '/transcode/create' do
18
+ params[:file][:tempfile].methods.inspect
19
+ end
20
+
21
+ get '/transcode/:id/download' do
22
+ transcode = Models::Transcode.get(params[:id])
23
+ return error 404 unless transcode
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,19 @@
1
+ DataMapper::Logger.new($stdout, :debug)
2
+ DataMapper.setup(:default, 'sqlite3::memory:')
3
+
4
+ module PureMotion::Server::Models
5
+
6
+ class Transcode
7
+ include DataMapper::Resource
8
+
9
+ property :id, Serial
10
+ property :name, String
11
+ property :file, String
12
+ property :status, String
13
+ property :attempts, Integer
14
+ property :progress, String
15
+ end
16
+
17
+ DataMapper.auto_migrate!
18
+
19
+ end
@@ -0,0 +1,39 @@
1
+ LOAD_PATH = File.dirname(__FILE__)
2
+ require File.expand_path(File.join(LOAD_PATH, *%w(.. puremotion)))
3
+
4
+ require 'rubygems'
5
+ require 'dm-core'
6
+ require 'sinatra'
7
+ require 'json'
8
+ require 'log4r'
9
+ require 'optparse'
10
+ require 'yaml'
11
+
12
+ OPTIONS = {
13
+ :config => 'default.yml',
14
+ :port => 9005
15
+ }
16
+
17
+ OptionParser.new do |opts|
18
+
19
+ opts.on("-c", "--config PATH", "Path to config file") do |path|
20
+ OPTIONS[:config] = path
21
+ end
22
+
23
+ opts.on("-p", "--port N", "Listening port") do |port|
24
+ OPTIONS[:port] = port
25
+ end
26
+
27
+ end.parse!
28
+
29
+ CONFIG = YAML.load(OPTIONS[:config]) if File.exists?(OPTIONS[:config])
30
+
31
+ module PureMotion::Server
32
+
33
+ end
34
+
35
+ %w(database app).each do |file|
36
+ require File.join(LOAD_PATH, file)
37
+ end
38
+
39
+ PureMotion::Server::App.run!
@@ -0,0 +1,19 @@
1
+ module PureMotion
2
+
3
+ class Settings
4
+
5
+ @@settings = {
6
+ :threaded => true
7
+ }
8
+
9
+ def self.[]=(name, value)
10
+ @@settings[name] = value
11
+ end
12
+
13
+ def self.[](name)
14
+ @@settings[name]
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -1,101 +1,13 @@
1
1
  module PureMotion::Tools
2
2
 
3
- class FFmpeg
3
+ class FFmpeg < PureMotion::Process
4
4
 
5
- class Status
6
-
7
- NOT_STARTED = 1
8
- INITIALIZING = 2
9
- ANALYZING = 4
10
- PREPARING_OUTPUT = 8
11
- ENCODING = 16
12
- ERROR = 32
13
-
14
- end
15
-
16
- attr_reader :pid
17
- attr_reader :args
18
- attr_accessor :output
19
-
20
- event :line
21
- event :complete
22
- event :exited
23
- event :status_change
24
-
25
- @pid = nil
26
-
27
- def initialize(params = {})
28
- if params.class != Hash then raise ArgumentError, "Invalid parameters" end
29
-
30
- @defaults = {
31
- :ffmpeg => 'ffmpeg',
32
- :options => nil
33
- }
34
-
35
- @params = params
36
-
37
- @options = @params[:options]
38
-
39
- if @options.nil? then
40
- raise ArgumentError, "No options given"
41
- end
42
-
43
- @output = []
44
- end
45
-
46
- def run
47
-
48
- temp = ''
49
-
50
- @thread = PureMotion::Thread.new do
51
- @pio = IO.popen("ffmpeg #{@options} 2>&1")
52
- @pid = @pio.pid
53
- @pio.each_byte do |l|
54
- if l == 10 or l == 13 then
55
- @output.push temp
56
- line(temp)
57
- temp = ''
58
- l = ''
59
- else
60
- l = l.chr
61
- end
62
- temp = temp + l
63
- #@output << l.gsub("\r","\n")
64
- end
65
- if @pio.eof? then
66
- @done = true
67
- complete(true)
68
- @pid = nil
69
- @pio.close
70
- exited(true)
71
- end
72
- end
73
-
74
- end
75
-
76
- def ran?
77
- @pio.eof? unless @pio.nil?
78
- false
79
- end
80
-
81
- def ended?
82
- return false unless ran?
83
- @pio.eof?
5
+ def initialize(arguments)
6
+ super("ffmpeg #{arguments}")
84
7
  end
85
8
 
86
9
  def version
87
- return nil unless ran?
88
- find(/^FFmpeg version (\S+)/)
89
- end
90
-
91
- def find(regexp)
92
- m = regexp.match(@output)
93
- return m[1] if m
94
- nil
95
- end
96
-
97
- def status
98
- @status
10
+ find_in_ouput(/^FFmpeg version (\S+)/)
99
11
  end
100
12
 
101
13
  end
@@ -2,38 +2,46 @@ def Transcode(*args, &block)
2
2
 
3
3
  raise ArgumentError, "No block given for transcode preset" unless block_given?
4
4
 
5
- preset = PureMotion::Preset.new(:dsl)
5
+ preset_options = {
6
+ :from => :dsl
7
+ }
8
+
9
+ preset_options[:input] = args[0] || nil
10
+
11
+ preset = PureMotion::Preset.new(preset_options)
6
12
  preset.instance_eval(&block)
7
13
 
8
- options = {
9
- :input => preset.input,
10
- :output => preset.output,
14
+ transcode_options = {
11
15
  :preset => preset
12
16
  }
13
17
 
14
- options[:log] = preset.log unless preset.log.nil?
18
+ transcode_options[:log] = preset.log unless preset.log.nil?
15
19
 
16
- transcode = PureMotion::Transcode::Transcode.new(options)
20
+ transcode = PureMotion::Transcode::Transcode.new(transcode_options)
17
21
 
18
- transcode.run
19
-
20
- return transcode
22
+ if transcode.valid? then
23
+ transcode.run
24
+ return transcode
25
+ else
26
+ return false
27
+ end
21
28
 
22
29
  end
23
30
 
24
31
  module PureMotion::Transcode
25
32
 
26
- class Transcode < Object
33
+ class Transcode
27
34
 
28
35
  event :progress
29
36
  event :complete
30
37
  event :output
31
-
32
- @ffmpeg = nil
33
- @input = nil
34
-
38
+ event :failure
39
+
35
40
  def initialize(options)
36
-
41
+
42
+ @valid = true # Until it all goes wrong
43
+
44
+ # Default everything to nil
37
45
  default_options = {
38
46
  :output => nil,
39
47
  :preset => nil,
@@ -43,46 +51,45 @@ module PureMotion::Transcode
43
51
  raise ArgumentError, "Invalid transcode parameters given" unless options.is_a?(Hash)
44
52
 
45
53
  options = default_options.merge(options)
46
-
47
- raise ArgumentError, "No output file given" if options[:output].nil?
48
54
 
49
- if options[:input].is_a?(String) then
50
- @input = PureMotion::Media.new(options[:input])
51
- else
52
- @input = options[:input]
53
- end
54
55
  @preset = options[:preset]
55
- @output = options[:output]
56
-
57
- validate
58
56
 
59
57
  wire_events
60
58
 
59
+ @input = (@preset.input || options[:input])
60
+ validate_input
61
+
62
+ @output = File.expand_path(@preset.output || options[:output])
63
+ validate_output
64
+
65
+ @ffmpeg = PureMotion::Tools::FFmpeg.new(@preset.arguments)
66
+
61
67
  if options[:log] then
62
68
  @log = File.new(options[:log], 'w')
63
- self.output + lambda do |t, line|
69
+ on(:output) do |line|
64
70
  @log.puts line
65
71
  end
66
- self.complete + lambda do |t, complete|
72
+ @ffmpeg.on(:exit) do
67
73
  @log.close
68
74
  end
69
75
  end
70
-
71
- # Raise error if output file exists and overwrite not set
72
-
73
- @ffmpeg = PureMotion::Tools::FFmpeg.new(:options => @preset.arguments)
74
-
75
- @ffmpeg.line + lambda do |ffmpeg, line|
76
+
77
+ @ffmpeg.on(:output) do |line|
76
78
  handle_output line
77
79
  end
78
80
 
79
- @ffmpeg.exited + lambda do |ffmpeg, exited|
80
- complete(true)
81
+ @ffmpeg.on(:exit) do
82
+ handle_exit
81
83
  end
82
-
84
+
85
+ end
86
+
87
+ def valid?
88
+ return @valid
83
89
  end
84
90
 
85
91
  def run
92
+ raise ArgumentError, "Transcode is invalid" unless valid?
86
93
  @ffmpeg.run
87
94
  end
88
95
 
@@ -95,9 +102,27 @@ module PureMotion::Transcode
95
102
  end
96
103
 
97
104
  private
98
-
105
+
106
+ def invalidate!
107
+ @valid = false
108
+ end
109
+
110
+ def handle_exit
111
+ if !File.exists?(@output) then
112
+ fire(:failure, :output_missing)
113
+ return
114
+ end
115
+ begin
116
+ output_media = Media(@output)
117
+ fire(:complete, output_media) if output_media.valid?
118
+ rescue PureMotion::UnsupportedFormat
119
+ fire(:failure, :transcode_unreadable)
120
+ return
121
+ end
122
+ end
123
+
99
124
  def handle_output line
100
- output line
125
+ fire(:output, line)
101
126
  # /^frame=\s*(?<frame>\d*)\s*fps=\s*(?<fps>\d*)\s*q=(?<q>\S*)\s*size=\s*(?<size>[\d]*)(?<size_unit>[kmg]B)\s*time=\s*(?<time>[\d.]*)\s*bitrate=\s*(?<bitrate>[\d.]*)(?<bitrate_unit>[km]b).*$/
102
127
  progress_line = /frame=\s*(\d*)\s*fps=\s*(\d*)\s*q=(\S*)\s*[L]?size=\s*([\d]*)([kmg]B)\s*time=\s*([\d.]*)\s*bitrate=\s*([\d.]*)([km]b)/
103
128
  # The regex is documentated above and the matches it should give below
@@ -113,8 +138,7 @@ module PureMotion::Transcode
113
138
  end
114
139
 
115
140
  def handle_progress line
116
- if line[4].to_i == 0 then return false end
117
- p = {
141
+ progress = {
118
142
  :frame => line[1].to_i,
119
143
  :fps => line[2].to_i,
120
144
  :q => line[3].to_f,
@@ -123,17 +147,42 @@ module PureMotion::Transcode
123
147
  :bitrate => line[7].to_f,
124
148
  :percent => ((100.00 / @input.duration) * line[6].to_f).to_i
125
149
  }
126
- p[:percent] = 100 if p[:percent] > 100 unless p[:percent].nil?
127
- progress(p)
150
+ progress[:percent] = 100 if progress[:percent] > 100 unless progress[:percent].nil?
151
+ fire(:progress, self, progress)
128
152
  end
129
153
 
130
- def validate
131
-
154
+ def validate_input
155
+ if @input.is_a?(String) then
156
+ begin
157
+ @input = Media @input
158
+ rescue PureMotion::UnsupportedFormat
159
+ make_fail(:invalid_input, "Invalid input '#{@input}'")
160
+ end
161
+ end
162
+ if @input.is_a?(PureMotion::Media) then
163
+ make_fail(:invalid_input, "Invalid input '#{@input}'") unless @input.valid?
164
+ end
165
+ end
166
+
167
+ def validate_output
168
+ make_fail(:invalid_output, "No output file given") if @output.nil?
169
+ # TODO : Check if output file exists and overwite not set
170
+ make_fail(:invalid_output, "Output file '#{@output}' not writable") unless File.writable?(File.dirname(@output))
171
+ end
172
+
173
+ def make_fail(reason=:unknown,message="Unknown reason")
174
+ #invalidate!
175
+ if @preset.event_handler(:failure).nil? then
176
+ raise ArgumentError, message
177
+ else
178
+ fire(:failure, reason, message)
179
+ end
132
180
  end
133
181
 
134
182
  def wire_events
135
- self.complete += @preset.event_handlers[:complete] unless @preset.event_handlers[:complete].nil?
136
- self.progress += @preset.event_handlers[:progress] unless @preset.event_handlers[:progress].nil?
183
+ @preset.event_handlers.each_pair do |event, handler|
184
+ on(event) + handler
185
+ end
137
186
  end
138
187
 
139
188
  end
@@ -4,4 +4,8 @@ require 'puremotion'
4
4
 
5
5
  def sample_path
6
6
  ::File.expand_path(::File.join(::File.dirname(__FILE__), "samples", "sample.ogv"))
7
+ end
8
+
9
+ def invalid_file
10
+ ::File.expand_path(::File.join(::File.dirname(__FILE__), "samples", "invalid.txt"))
7
11
  end
@@ -1,13 +1,77 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper.rb'
2
2
 
3
- describe PureMotion::Media do
3
+ describe "media" do
4
+
5
+ context "opening media" do
6
+
7
+ it "should read supported formats" do
8
+ lambda do
9
+ PureMotion::Media.new(sample_path)
10
+ end.should_not raise_error
11
+ end
12
+
13
+ it "should handle file not found" do
14
+ lambda {
15
+ Media('a_file_that_does_not_exist')
16
+ }.should raise_exception(ArgumentError)
17
+ end
18
+
19
+ it "should handle invalid files" do
20
+ lambda {
21
+ Media(invalid_file)
22
+ }.should raise_exception(PureMotion::UnsupportedFormat)
23
+ end
24
+
25
+ end
26
+
27
+ context "reading media" do
28
+
29
+ before(:all) do
30
+ @media = Media(sample_path)
31
+ end
32
+
33
+ it "can check for streams" do
34
+ @media.video?.should be_true and @media.audio?.should be_true
35
+ end
36
+
37
+ it "can read video resolution" do
38
+ @media.video.resolution.should == [320, 176]
39
+ end
40
+
41
+ it "can detect stream types" do
42
+ @media.video.type.should == :video and
43
+ @media.audio.type.should == :audio
44
+ end
45
+
46
+ it "can read the duration" do
47
+ @media.video.duration.should be_close(55, 3)
48
+ end
49
+
50
+ it "can seek reasonably accurately" do
51
+ @media.video.seek 5
52
+ @media.video.position.should be_close(5, 1)
53
+ end
54
+
55
+ after(:all) do
56
+ @media = nil
57
+ end
4
58
 
5
- it "should read supported formats" do
6
- lambda do
7
- PureMotion::Media.new(sample_path)
8
- end.should_not raise_error
9
59
  end
10
60
 
61
+ context "grabbing" do
11
62
 
63
+ before(:all) do
64
+ @media = Media(sample_path)
65
+ end
66
+
67
+ it "can grab a video frame" do
68
+ @media.video.grab.should be_an_instance_of(PureMotion::Frame)
69
+ end
70
+
71
+ after(:all) do
72
+ @media = nil
73
+ end
74
+
75
+ end
12
76
 
13
77
  end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe PureMotion::Transcode::Transcode do
4
+
5
+ it "should detect missing files" do
6
+ lambda {
7
+
8
+ Transcode '/gibberish/path' do
9
+
10
+ end
11
+
12
+ }.should raise_error ArgumentError
13
+ end
14
+
15
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 1
9
+ version: 0.1.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ominiom
@@ -14,23 +14,24 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-25 00:00:00 +00:00
17
+ date: 2010-04-03 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
21
21
  description: A Ruby wrapper for FFmpeg
22
22
  email: iain@ominiom.com
23
- executables: []
24
-
23
+ executables:
24
+ - default.yaml
25
+ - pmts
25
26
  extensions:
26
27
  - ext/puremotion/extconf.rb
27
28
  extra_rdoc_files:
28
29
  - LICENSE
29
- - README.md
30
+ - README.markdown
30
31
  files:
31
32
  - .yardopts
32
33
  - LICENSE
33
- - README.md
34
+ - README.markdown
34
35
  - Rakefile
35
36
  - ext/puremotion/audio.c
36
37
  - ext/puremotion/extconf.rb
@@ -54,8 +55,13 @@ files:
54
55
  - lib/preset/video/crop.rb
55
56
  - lib/preset/video/pad.rb
56
57
  - lib/preset/video/video.rb
58
+ - lib/process.rb
57
59
  - lib/puremotion.rb
58
60
  - lib/puremotion_native.so
61
+ - lib/server/app.rb
62
+ - lib/server/database.rb
63
+ - lib/server/server.rb
64
+ - lib/settings.rb
59
65
  - lib/threading.rb
60
66
  - lib/tools/ffmpeg.rb
61
67
  - lib/transcode/transcode.rb
@@ -91,7 +97,10 @@ specification_version: 3
91
97
  summary: PureMotion
92
98
  test_files:
93
99
  - spec/units/media_spec.rb
100
+ - spec/units/transcode_spec.rb
94
101
  - spec/units/preset_spec.rb
95
102
  - spec/spec_helper.rb
96
103
  - examples/progress_reporting.rb
104
+ - examples/failure_handling.rb
97
105
  - examples/simple.rb
106
+ - examples/process.rb