vidibus-recording 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011 Andre Pankratz
1
+ Copyright (c) 2011-2013 Andre Pankratz
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # Vidibus::Recording
2
+
3
+ Allows recording of RTMP video streams. Uses RTMPdump. Requires at least Ruby 1.9.
4
+
5
+ This gem is part of [Vidibus](http://vidibus.org), an open source toolset for building distributed (video) applications.
6
+
7
+
8
+ ## Installation
9
+
10
+ Add `gem 'vidibus-recording'` to the Gemfile of your application. Then call `bundle install` on your console.
11
+
12
+
13
+ ## Usage
14
+
15
+ ### Available methods
16
+
17
+ To control a recording, you may use these methods:
18
+
19
+ ```ruby
20
+ recording.start # starts recording
21
+ recording.stop # stops recording
22
+ recording.resume # continues if recording has been started but is not running
23
+ recording.restart # erases recorded data and restarts recording
24
+ ```
25
+
26
+
27
+ ### Custom class names
28
+
29
+ This gem will set up a model `Recording` if Rails is around. If you want to use the recording logic inside of a custom model, you just have to include the module `Vidibus::Recording::Mongoid`:
30
+
31
+ ```ruby
32
+ class MyCustomRecording
33
+ include Mongoid::Document
34
+ include Vidibus::Recording::Mongoid
35
+ end
36
+ ```
37
+
38
+
39
+ ### Monitoring
40
+
41
+ If the worker process does not receive data, it will halt the recording. To monitor and restart a recording perform `Vidibus::Recording.monitor`. Beware, this method is blocking, so better spawn the daemon.
42
+
43
+
44
+ #### Monitoring daemon
45
+
46
+ To run the monitor as daemon, this gem provides a shell script. Install it with
47
+
48
+ ```
49
+ rails g vidibus:recording
50
+ ```
51
+
52
+ The daemon requires that `gem 'daemons'` is installed. To spawn him, enter
53
+
54
+ ```
55
+ script/recording start
56
+ ```
57
+
58
+ #### Possible caveat
59
+
60
+ To monitor your custom recording classes, `Vidibus::Recording.monitor` requires that all classes that include `Vidibus::Recording::Mongoid` have been loaded.
61
+
62
+ Because Rails is autoloading almost everything in development, this requirement is not met without the help of a little hack: To trigger autoloading, the monitor collects all aforementioned class names from the `app` directory and constantizes them.
63
+
64
+ **So here's the caveat:** If you define custom recording models outside of the `app` directory, you'll have to let the listener know. An initializer is perfect for that:
65
+
66
+ ```ruby
67
+ # Collect all recording models in lib, too
68
+ Vidibus::Recording.autoload_paths << '/lib/**/*.rb'
69
+ ```
70
+
71
+
72
+ ## Deployment
73
+
74
+ A Capistrano configuration is included. Require it in your Capistrano `config.rb`.
75
+
76
+ ```ruby
77
+ require 'vidibus/recording/capistrano'
78
+ ```
79
+
80
+ That will add a bunch of callback hooks.
81
+
82
+ ```ruby
83
+ after 'deploy:stop', 'vidibus:recording:stop'
84
+ after 'deploy:start', 'vidibus:recording:start'
85
+ after 'deploy:restart', 'vidibus:recording:restart'
86
+ ```
87
+
88
+ If you need more control over the callbacks, you may load just the recipes without the hooks.
89
+
90
+ ```ruby
91
+ require 'vidibus/recording/capistrano/recipes'
92
+ ```
93
+
94
+
95
+ ## Testing
96
+
97
+ To test this gem, call `bundle install` and `bundle exec rspec spec` on your console.
98
+
99
+
100
+ ## Copyright
101
+
102
+ &copy; 2011-2013 André Pankratz. See LICENSE for details.
@@ -2,7 +2,7 @@ require 'rails/generators'
2
2
  require 'rails/generators/named_base'
3
3
 
4
4
  module Vidibus
5
- class RecorderGenerator < Rails::Generators::Base
5
+ class RecordingGenerator < Rails::Generators::Base
6
6
 
7
7
  self.source_paths << File.join(File.dirname(__FILE__), 'templates')
8
8
 
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require File.expand_path('../../config/environment', __FILE__)
4
- require 'vidibus/recorder/daemon'
4
+ require 'vidibus/recording/daemon'
5
5
 
6
- Vidibus::WatchFolder::Daemon.new(ARGV).daemonize
6
+ Vidibus::Recording.monitoring_interval = 1 # second
7
+ Vidibus::Recording.autoload_paths << Rails.root.join('app/**/*.rb')
8
+ Vidibus::Recording::Daemon.new(ARGV).daemonize
@@ -79,7 +79,7 @@ module Vidibus::Recording::Backend
79
79
  end
80
80
  end
81
81
 
82
- # Extract metadata from stdout or stderr.
82
+ # Detect error from stdout or stderr.
83
83
  # Output delivered by rtmpdump looks like this:
84
84
  #
85
85
  # RTMPDump v2.4
@@ -88,9 +88,12 @@ module Vidibus::Recording::Backend
88
88
  # ERROR: Problem accessing the DNS. (addr: whatever.domain)
89
89
  #
90
90
  def detect_error(string)
91
- prefix = /(?:ERROR\:\ (.+))/ if string.match(/ERROR\:/)
92
- if string.match(/(?:ERROR\:\ (.+))/)
93
- raise RuntimeError.new($1)
91
+ if error = string[/(?:ERROR\:\ (.+))/,1]
92
+ case error
93
+ when 'rtmp server sent error'
94
+ else
95
+ raise RuntimeError.new($1)
96
+ end
94
97
  end
95
98
  end
96
99
  end
@@ -1,4 +1,4 @@
1
- # Capistrano Recipes for watching folders.
1
+ # Capistrano Recipes for monitoring recordings.
2
2
  #
3
3
  # Load this file from your Capistrano config.rb:
4
4
  # require 'vidibus/recording/capistrano/recipes'
@@ -1,12 +1,12 @@
1
1
  require 'vidibus/recording/capistrano/recipes'
2
2
 
3
- # Run Capistrano Recipes for watching folders.
3
+ # Run Capistrano Recipes for monitoring recordings.
4
4
  #
5
5
  # Load this file from your Capistrano config.rb:
6
- # require 'vidibus/watch_folder/capistrano'
6
+ # require 'vidibus/recording/capistrano'
7
7
  #
8
8
  Capistrano::Configuration.instance.load do
9
- after 'deploy:stop', 'vidibus:watch_folder:stop'
10
- after 'deploy:start', 'vidibus:watch_folder:start'
11
- after 'deploy:restart', 'vidibus:watch_folder:restart'
9
+ after 'deploy:stop', 'vidibus:recording:stop'
10
+ after 'deploy:start', 'vidibus:recording:start'
11
+ after 'deploy:restart', 'vidibus:recording:restart'
12
12
  end
@@ -6,7 +6,7 @@ end
6
6
  require 'optparse'
7
7
 
8
8
  module Vidibus
9
- module Recorder
9
+ module Recording
10
10
  class Daemon
11
11
 
12
12
  def initialize(args)
@@ -34,10 +34,10 @@ module Vidibus
34
34
  def run
35
35
  Dir.chdir(Rails.root)
36
36
  log = File.join(Rails.root, 'log', 'recording.log')
37
- Vidibus::Recorder.logger = ActiveSupport::BufferedLogger.new(log)
38
- Vidibus::Recorder.monitor
37
+ Vidibus::Recording.logger = ActiveSupport::BufferedLogger.new(log)
38
+ Vidibus::Recording.monitor
39
39
  rescue => e
40
- Vidibus::Recorder.logger.fatal(e)
40
+ Vidibus::Recording.logger.fatal(e)
41
41
  STDERR.puts(e.message)
42
42
  exit 1
43
43
  end
@@ -1,3 +1,8 @@
1
+ require 'mongoid'
2
+ require 'vidibus-uuid'
3
+ require 'active_support/core_ext'
4
+ require 'delayed_job_mongoid'
5
+
1
6
  module Vidibus::Recording
2
7
  module Mongoid
3
8
  extend ActiveSupport::Concern
@@ -10,7 +15,6 @@ module Vidibus::Recording
10
15
 
11
16
  field :name
12
17
  field :stream
13
- # field :live, :type => Boolean
14
18
  field :pid, :type => Integer
15
19
  field :info, :type => Hash
16
20
  field :size, :type => Integer
@@ -20,13 +24,17 @@ module Vidibus::Recording
20
24
  field :started_at, :type => DateTime
21
25
  field :stopped_at, :type => DateTime
22
26
  field :failed_at, :type => DateTime
27
+ field :active, :type => Boolean, :default => false
23
28
  field :running, :type => Boolean, :default => false
24
- field :monitoring_job_identifier, :type => String
29
+
30
+ index :active
25
31
 
26
32
  validates :name, :presence => true
27
33
  validates :stream, :format => {:with => /^rtmp.*?:\/\/.+$/}
28
34
 
29
35
  before_destroy :cleanup
36
+
37
+ scope :active, where(active: true)
30
38
  end
31
39
 
32
40
  # Starts a recording worker now, unless it has been done already.
@@ -35,8 +43,8 @@ module Vidibus::Recording
35
43
  return false if done? || started?
36
44
  if time == :now
37
45
  self.started_at = Time.now
46
+ self.active = true
38
47
  start_worker
39
- start_monitoring_job
40
48
  save!
41
49
  else
42
50
  schedule(time)
@@ -48,8 +56,8 @@ module Vidibus::Recording
48
56
  return false if running? || !started?
49
57
  self.stopped_at = nil
50
58
  self.failed_at = nil
59
+ self.active = true
51
60
  start_worker
52
- start_monitoring_job
53
61
  save!
54
62
  end
55
63
 
@@ -67,7 +75,7 @@ module Vidibus::Recording
67
75
  self.pid = nil
68
76
  self.stopped_at = Time.now
69
77
  self.running = false
70
- self.monitoring_job_identifier = nil
78
+ self.active = false
71
79
  postprocess
72
80
  end
73
81
 
@@ -89,6 +97,7 @@ module Vidibus::Recording
89
97
  self.error = msg
90
98
  self.failed_at = Time.now
91
99
  self.running = false
100
+ self.active = false
92
101
  postprocess
93
102
  end
94
103
 
@@ -104,8 +113,7 @@ module Vidibus::Recording
104
113
  :info,
105
114
  :error,
106
115
  :size,
107
- :duration,
108
- :monitoring_job_identifier
116
+ :duration
109
117
  ].map {|a| blank[a] = nil }
110
118
  update_attributes!(blank)
111
119
  destroy_all_parts
@@ -222,16 +230,6 @@ module Vidibus::Recording
222
230
  self.pid = worker.pid
223
231
  end
224
232
 
225
- # Start a new monitoring job
226
- def start_monitoring_job
227
- self.monitoring_job_identifier = Vidibus::Uuid.generate
228
- Vidibus::Recording::MonitoringJob.create({
229
- :class_name => self.class.to_s,
230
- :uuid => uuid,
231
- :identifier => monitoring_job_identifier
232
- })
233
- end
234
-
235
233
  def setup_next_part
236
234
  number = nil
237
235
  if current_part
@@ -1,3 +1,7 @@
1
+ require 'yaml'
2
+ require 'mongoid'
3
+ require 'vidibus-uuid'
4
+
1
5
  module Vidibus::Recording
2
6
  class Part
3
7
  include Mongoid::Document
@@ -1 +1,9 @@
1
- railtie.rb
1
+ require 'rails'
2
+
3
+ module Vidibus
4
+ module Recording
5
+ if defined?(Rails)
6
+ class Engine < ::Rails::Engine; end
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  module Vidibus
2
2
  module Recording
3
- VERSION = '1.0.0'
3
+ VERSION = '2.0.0'
4
4
  end
5
5
  end
@@ -1,11 +1,12 @@
1
+ require 'open3'
1
2
  require 'timeout'
2
3
 
3
4
  module Vidibus::Recording
4
5
  class Worker
5
6
  class ProcessError < StandardError; end
6
7
 
7
- # START_TIMEOUT = 20
8
- STOP_TIMEOUT = 10
8
+ START_TIMEOUT = 20
9
+ STOP_TIMEOUT = 2
9
10
 
10
11
  attr_accessor :recording, :pid, :metadata
11
12
 
@@ -33,8 +34,7 @@ module Vidibus::Recording
33
34
  Timeout::timeout(STOP_TIMEOUT) do
34
35
  begin
35
36
  log("Stopping process #{pid}...")
36
- # Use SIGQUIT to terminate because DelayedJob traps INT and TERM
37
- Process.kill('SIGQUIT', pid)
37
+ Process.kill('SIGTERM', pid)
38
38
  Process.wait(pid)
39
39
  log('STOPPED')
40
40
  rescue Errno::ECHILD
@@ -73,8 +73,8 @@ module Vidibus::Recording
73
73
  def record
74
74
  cmd = recording.backend.command
75
75
  log("START: #{recording.stream}", true)
76
+ timeout = Time.now + START_TIMEOUT
76
77
  Open3::popen3(cmd) do |stdin, stdout, stderr|
77
- maxloops = 10
78
78
  loop do
79
79
  begin
80
80
  string = stdout.read_nonblock(1024).force_encoding('UTF-8')
@@ -90,12 +90,11 @@ module Vidibus::Recording
90
90
  fail(e.message) && break
91
91
  end
92
92
  unless metadata
93
- maxloops -= 1
94
- if maxloops == 0
95
- halt('No Metadata has been received so far.') && break
93
+ if Time.now > timeout
94
+ halt('No Metadata has been received so far, exiting.') && break
96
95
  end
97
96
  end
98
- sleep 2
97
+ sleep(2)
99
98
  end
100
99
  end
101
100
  end
@@ -1,6 +1,65 @@
1
1
  require 'vidibus/recording/worker'
2
- require 'vidibus/recording/monitoring_job'
3
2
  require 'vidibus/recording/backend'
4
3
  require 'vidibus/recording/helpers'
5
4
  require 'vidibus/recording/part'
6
5
  require 'vidibus/recording/mongoid'
6
+ require 'vidibus/recording/railtie' if defined?(Rails::Railtie)
7
+
8
+ module Vidibus
9
+ module Recording
10
+ extend self
11
+
12
+ class Error < StandardError; end
13
+
14
+ INTERVAL = 1
15
+
16
+ attr_accessor :logger, :autoload_paths, :classes, :monitoring_interval
17
+ @logger = Logger.new(STDOUT)
18
+ @autoload_paths = []
19
+ @classes = []
20
+ @monitoring_interval = 1
21
+
22
+ # Monitor all started recordings
23
+ def monitor
24
+ autoload
25
+ unless classes.any?
26
+ logger.error("[#{Time.now.utc}] - No recording classes given")
27
+ else
28
+ logger.info("[#{Time.now.utc}] - Watching recordings")
29
+ run
30
+ end
31
+ end
32
+
33
+ # Obtain all classes that include the Mongoid module
34
+ def autoload
35
+ return [] unless autoload_paths.any?
36
+ regexp = /class ([^<\n]+).+include Vidibus::Recording::Mongoid/m
37
+ names = Dir[*autoload_paths].map do |f|
38
+ File.read(f)[regexp, 1]
39
+ end.compact
40
+ self.classes = names.map { |k| k.constantize }
41
+ end
42
+
43
+ private
44
+
45
+ def run
46
+ loop do
47
+ classes.each do |klass|
48
+ klass.active.each do |recording|
49
+ begin
50
+ if recording.worker_running?
51
+ recording.track_progress
52
+ else
53
+ logger.info("[#{Time.now.utc}] - Resuming #{recording.class.name} #{recording.uuid}")
54
+ recording.resume
55
+ end
56
+ rescue => e
57
+ logger.error("[#{Time.now.utc}] - ERROR:\n#{e.inspect}\n---\n#{e.backtrace.join("\n")}")
58
+ end
59
+ end
60
+ end
61
+ sleep(monitoring_interval)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,15 +1 @@
1
- require 'open3'
2
- require 'yaml'
3
- require 'delayed_job_mongoid'
4
- require 'active_support/core_ext'
5
- require 'vidibus-uuid'
6
-
7
- module Vidibus
8
- module Recording
9
- if defined?(Rails)
10
- class Engine < ::Rails::Engine; end
11
- end
12
- end
13
- end
14
-
15
1
  require 'vidibus/recording'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vidibus-recording
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-30 00:00:00.000000000 Z
12
+ date: 2013-08-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: '2'
37
+ version: '2.5'
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,7 +42,7 @@ dependencies:
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: '2'
45
+ version: '2.5'
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: delayed_job_mongoid
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -179,7 +179,6 @@ extra_rdoc_files: []
179
179
  files:
180
180
  - lib/generators/vidibus/recording_generator.rb
181
181
  - lib/generators/vidibus/templates/script
182
- - lib/vidibus/recording/backend/railtie.rb
183
182
  - lib/vidibus/recording/backend/rtmpdump.rb
184
183
  - lib/vidibus/recording/backend.rb
185
184
  - lib/vidibus/recording/capistrano/recipes.rb
@@ -187,7 +186,6 @@ files:
187
186
  - lib/vidibus/recording/daemon.rb
188
187
  - lib/vidibus/recording/helpers.rb
189
188
  - lib/vidibus/recording/mongoid.rb
190
- - lib/vidibus/recording/monitoring_job.rb
191
189
  - lib/vidibus/recording/part.rb
192
190
  - lib/vidibus/recording/railtie.rb
193
191
  - lib/vidibus/recording/version.rb
@@ -196,7 +194,7 @@ files:
196
194
  - lib/vidibus-recording.rb
197
195
  - app/models/recording.rb
198
196
  - LICENSE
199
- - README.rdoc
197
+ - README.md
200
198
  - Rakefile
201
199
  homepage: https://github.com/vidibus/vidibus-recording
202
200
  licenses: []
@@ -212,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
212
210
  version: '0'
213
211
  segments:
214
212
  - 0
215
- hash: 766807753862642645
213
+ hash: 507116490268825761
216
214
  required_rubygems_version: !ruby/object:Gem::Requirement
217
215
  none: false
218
216
  requirements:
data/README.rdoc DELETED
@@ -1,11 +0,0 @@
1
- = Vidibus::Recording
2
-
3
- Allows recording of RTMP video streams. Uses RTMPdump.
4
- Further description goes here.
5
-
6
- Requires Ruby 1.9
7
-
8
-
9
- == Copyright
10
-
11
- Copyright (c) 2013 Andre Pankratz. See LICENSE for details.
@@ -1 +0,0 @@
1
- railtie.rb
@@ -1,52 +0,0 @@
1
- module Vidibus::Recording
2
- class MonitoringJob
3
- INTERVAL = 10.seconds
4
-
5
- def initialize(args)
6
- unless @uuid = args[:uuid]
7
- raise(ArgumentError, 'No recording UUID given')
8
- end
9
- unless @class_name = args[:class_name]
10
- raise(ArgumentError, 'Must provide class name of recording')
11
- end
12
- unless @identifier = args[:identifier]
13
- raise(ArgumentError, 'Must provide identifier of monitoring job')
14
- end
15
- ensure_recording
16
- end
17
-
18
- def perform
19
- r = recording.reload
20
- return unless r.monitoring_job_identifier == @identifier
21
- if r.worker_running?
22
- r.track_progress
23
- run_again
24
- elsif !r.stopped?
25
- r.resume
26
- end
27
- end
28
-
29
- # Returns job
30
- def self.create(args)
31
- job = new(args)
32
- Delayed::Job.enqueue(job)
33
- end
34
-
35
- private
36
-
37
- def recording
38
- @class_name.constantize.where(:uuid => @uuid).first
39
- end
40
-
41
- def ensure_recording
42
- recording || raise(ArgumentError, 'No valid recording UUID given')
43
- end
44
-
45
- def run_again
46
- obj = self.class.new({
47
- :uuid => @uuid, :class_name => @class_name, :identifier => @identifier
48
- })
49
- Delayed::Job.enqueue(obj, 0, INTERVAL.from_now)
50
- end
51
- end
52
- end