puremotion 0.1.0 → 0.1.1

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