enigmamachine 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,4 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ %w{ .sqlite3 }.each {|exception| at.add_exception(exception)}
3
+ end
4
+
data/README.rdoc CHANGED
@@ -45,8 +45,8 @@ Then check it out in your browser, at http://localhost:2002. This web interface
45
45
  exists so that you can configure your enigmamachine easily, and check its status.
46
46
 
47
47
  Most of the time, though, your enigmamachine will be accessed not through a
48
- browser, but by your program's code. Let's say you want your website to be able to
49
- enocode video. You write the code for the video upload, so your web app can
48
+ browser, but by your program's code. Let's say you want your website to be able
49
+ to encode video. You write the code for the video upload, so your web app can
50
50
  receive the video. When the upload is complete, make an HTTP call something like this in your code:
51
51
 
52
52
  POST http://localhost:2002/videos
@@ -214,11 +214,10 @@ system).
214
214
 
215
215
  == Status
216
216
 
217
- This thing is still pre-release, but it is usable in a basic form now.
218
-
219
- I'm still working on getting the gem dependencies correct, so you may need to
220
- manually install a few things to get it to work. You'll also need a working
221
- copy of ffmpeg available on your path.
217
+ Enigmamachine should be considered beta quality code. At the same time,
218
+ crashes and bugs have been infrequent for us, and it's a simple system which
219
+ is working well. Please let us know of any bugs by filing an issue on the
220
+ GitHub tracker.
222
221
 
223
222
 
224
223
  == Note on Patches/Pull Requests
data/Rakefile CHANGED
@@ -10,15 +10,18 @@ begin
10
10
  gem.email = "dave@caprica"
11
11
  gem.homepage = "http://github.com/futurechimp/enigmamachine"
12
12
  gem.authors = ["Dave Hrycyszyn", "Dmitry Brazhkin"]
13
- gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
13
+ gem.add_development_dependency "thoughtbot-shoulda"
14
+ gem.add_development_dependency "rack-test"
14
15
  gem.add_dependency "data_mapper", ">=1.0.2"
15
16
  gem.add_dependency "dm-sqlite-adapter", ">=1.0.2"
17
+ gem.add_dependency "state_machine", ">=0.9.4"
16
18
  gem.add_dependency "eventmachine", ">=0.12.10"
17
19
  gem.add_dependency "rack-flash"
18
20
  gem.add_dependency "ruby-debug"
19
21
  gem.add_dependency "sinatra", ">=1.0.0"
20
22
  gem.add_dependency "streamio-ffmpeg", ">=0.7.3"
21
23
  gem.add_dependency "thin"
24
+ gem.add_dependency "em-http-request"
22
25
 
23
26
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
27
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{enigmamachine}
8
- s.version = "0.5.0"
8
+ s.version = "0.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Dave Hrycyszyn", "Dmitry Brazhkin"]
12
- s.date = %q{2010-10-04}
12
+ s.date = %q{2010-11-01}
13
13
  s.default_executable = %q{enigmamachine}
14
14
  s.description = %q{A RESTful video encoder which you can use as either a front-end to ffmpeg or headless on a server.}
15
15
  s.email = %q{dave@caprica}
@@ -19,7 +19,8 @@ Gem::Specification.new do |s|
19
19
  "README.rdoc"
20
20
  ]
21
21
  s.files = [
22
- ".document",
22
+ ".autotest",
23
+ ".document",
23
24
  ".gitignore",
24
25
  "LICENSE",
25
26
  "README.rdoc",
@@ -30,6 +31,7 @@ Gem::Specification.new do |s|
30
31
  "lib/enigmamachine.rb",
31
32
  "lib/enigmamachine.sqlite3",
32
33
  "lib/enigmamachine/config.ru",
34
+ "lib/enigmamachine/download_queue.rb",
33
35
  "lib/enigmamachine/encoding_queue.rb",
34
36
  "lib/enigmamachine/models/encoder.rb",
35
37
  "lib/enigmamachine/models/encoding_task.rb",
@@ -76,52 +78,61 @@ Gem::Specification.new do |s|
76
78
  s.homepage = %q{http://github.com/futurechimp/enigmamachine}
77
79
  s.rdoc_options = ["--charset=UTF-8"]
78
80
  s.require_paths = ["lib"]
79
- s.rubygems_version = %q{1.3.6}
81
+ s.rubygems_version = %q{1.3.7}
80
82
  s.summary = %q{A RESTful video encoder.}
81
83
  s.test_files = [
82
- "test/support/blueprints.rb",
83
- "test/helper.rb",
84
- "test/test_encoding_queue.rb",
85
- "test/test_video.rb",
84
+ "test/test_encoding_queue.rb",
86
85
  "test/test_encoder.rb",
87
- "test/test_enigmamachine.rb"
86
+ "test/test_video.rb",
87
+ "test/test_enigmamachine.rb",
88
+ "test/helper.rb",
89
+ "test/support/blueprints.rb"
88
90
  ]
89
91
 
90
92
  if s.respond_to? :specification_version then
91
93
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
92
94
  s.specification_version = 3
93
95
 
94
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
96
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
95
97
  s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
98
+ s.add_development_dependency(%q<rack-test>, [">= 0"])
96
99
  s.add_runtime_dependency(%q<data_mapper>, [">= 1.0.2"])
97
100
  s.add_runtime_dependency(%q<dm-sqlite-adapter>, [">= 1.0.2"])
101
+ s.add_runtime_dependency(%q<state_machine>, [">= 0.9.4"])
98
102
  s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.10"])
99
103
  s.add_runtime_dependency(%q<rack-flash>, [">= 0"])
100
104
  s.add_runtime_dependency(%q<ruby-debug>, [">= 0"])
101
105
  s.add_runtime_dependency(%q<sinatra>, [">= 1.0.0"])
102
106
  s.add_runtime_dependency(%q<streamio-ffmpeg>, [">= 0.7.3"])
103
107
  s.add_runtime_dependency(%q<thin>, [">= 0"])
108
+ s.add_runtime_dependency(%q<em-http-request>, [">= 0"])
104
109
  else
105
110
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
111
+ s.add_dependency(%q<rack-test>, [">= 0"])
106
112
  s.add_dependency(%q<data_mapper>, [">= 1.0.2"])
107
113
  s.add_dependency(%q<dm-sqlite-adapter>, [">= 1.0.2"])
114
+ s.add_dependency(%q<state_machine>, [">= 0.9.4"])
108
115
  s.add_dependency(%q<eventmachine>, [">= 0.12.10"])
109
116
  s.add_dependency(%q<rack-flash>, [">= 0"])
110
117
  s.add_dependency(%q<ruby-debug>, [">= 0"])
111
118
  s.add_dependency(%q<sinatra>, [">= 1.0.0"])
112
119
  s.add_dependency(%q<streamio-ffmpeg>, [">= 0.7.3"])
113
120
  s.add_dependency(%q<thin>, [">= 0"])
121
+ s.add_dependency(%q<em-http-request>, [">= 0"])
114
122
  end
115
123
  else
116
124
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
125
+ s.add_dependency(%q<rack-test>, [">= 0"])
117
126
  s.add_dependency(%q<data_mapper>, [">= 1.0.2"])
118
127
  s.add_dependency(%q<dm-sqlite-adapter>, [">= 1.0.2"])
128
+ s.add_dependency(%q<state_machine>, [">= 0.9.4"])
119
129
  s.add_dependency(%q<eventmachine>, [">= 0.12.10"])
120
130
  s.add_dependency(%q<rack-flash>, [">= 0"])
121
131
  s.add_dependency(%q<ruby-debug>, [">= 0"])
122
132
  s.add_dependency(%q<sinatra>, [">= 1.0.0"])
123
133
  s.add_dependency(%q<streamio-ffmpeg>, [">= 0.7.3"])
124
134
  s.add_dependency(%q<thin>, [">= 0"])
135
+ s.add_dependency(%q<em-http-request>, [">= 0"])
125
136
  end
126
137
  end
127
138
 
data/lib/enigmamachine.rb CHANGED
@@ -8,9 +8,10 @@ require 'eventmachine'
8
8
  require 'rack-flash'
9
9
  require 'dm-validations'
10
10
  require 'dm-migrations'
11
- require 'open3'
11
+ require 'state_machine'
12
12
  require 'logger'
13
13
  require 'streamio-ffmpeg'
14
+ require 'em-http-request'
14
15
 
15
16
  # Extensions to Sinatra
16
17
  #
@@ -24,6 +25,7 @@ require File.dirname(__FILE__) + '/enigmamachine/models/encoder'
24
25
  require File.dirname(__FILE__) + '/enigmamachine/models/encoding_task'
25
26
  require File.dirname(__FILE__) + '/enigmamachine/models/video'
26
27
  require File.dirname(__FILE__) + '/enigmamachine/encoding_queue'
28
+ require File.dirname(__FILE__) + '/enigmamachine/download_queue'
27
29
 
28
30
 
29
31
 
@@ -38,7 +40,6 @@ class EnigmaMachine < Sinatra::Base
38
40
 
39
41
  configure :development do
40
42
  db = "sqlite3:///#{Dir.pwd}/enigmamachine.sqlite3"
41
- DataMapper::Logger.new(STDOUT, :debug)
42
43
  DataMapper.setup(:default, db)
43
44
  end
44
45
 
@@ -60,9 +61,12 @@ class EnigmaMachine < Sinatra::Base
60
61
  unless File.exist? File.join(Dir.getwd, 'config.yml')
61
62
  FileUtils.cp(File.dirname(__FILE__) + '/generators/config.yml', Dir.getwd)
62
63
  end
63
- raw_config = File.read(Dir.getwd + "/config.yml")
64
- @@username = YAML.load(raw_config)['username']
65
- @@password = YAML.load(raw_config)['password']
64
+ config = YAML.load(File.read(Dir.getwd + "/config.yml"))
65
+ @@username = config['username']
66
+ @@password = config['password']
67
+ @@threads = config['threads']
68
+ @@enable_http_downloads = config['enable_http_downloads']
69
+ @@download_storage_path = config['download_storage_path']
66
70
  end
67
71
 
68
72
  # Set the views to the proper path inside the gem
@@ -93,12 +97,14 @@ class EnigmaMachine < Sinatra::Base
93
97
  # main Sinatra/thin thread once the periodic timer is added.
94
98
  #
95
99
  configure do
96
- Video.reset_encoding_videos
97
100
  Thread.new do
98
101
  until EM.reactor_running?
99
102
  sleep 1
100
103
  end
101
- queue = EncodingQueue.new
104
+ Video.reset_encoding_videos
105
+ Video.reset_downloading_videos
106
+ encode_queue = EncodingQueue.new
107
+ download_queue = DownloadQueue.new
102
108
  end
103
109
  end
104
110
 
@@ -110,6 +116,10 @@ class EnigmaMachine < Sinatra::Base
110
116
  [username, password] == [@@username, @@password]
111
117
  end
112
118
 
119
+ # Some accessors for config variables
120
+ #
121
+ cattr_accessor :download_storage_path, :enable_http_downloads, :threads
122
+
113
123
 
114
124
  # Shows the enigma status page.
115
125
  #
@@ -242,8 +252,10 @@ class EnigmaMachine < Sinatra::Base
242
252
  get '/videos' do
243
253
  @completed_videos = Video.complete
244
254
  @encoding_videos = Video.encoding
245
- @videos_with_errors = Video.with_errors
255
+ @videos_with_errors = Video.with_encode_errors
246
256
  @unencoded_videos = Video.unencoded
257
+ @downloading_videos = Video.downloading
258
+ @waiting_for_download_videos = Video.waiting_for_download
247
259
  erb :'videos/index'
248
260
  end
249
261
 
@@ -0,0 +1,31 @@
1
+ class DownloadQueue
2
+
3
+ # Adds a periodic timer to the Eventmachine reactor loop and immediately
4
+ # starts looking for videos to download.
5
+ #
6
+ def initialize
7
+ if EnigmaMachine.enable_http_downloads
8
+ EM.add_periodic_timer(5) do
9
+ download_next_video
10
+ end
11
+ end
12
+ end
13
+
14
+
15
+ # Gets the next waiting_for_download Video from the database and
16
+ # starts downloading it.
17
+ #
18
+ def download_next_video
19
+ if Video.waiting_for_download.count > 0 && Video.downloading.count == 0
20
+ video = Video.waiting_for_download.first
21
+ begin
22
+ puts "downloading video #{video.id}"
23
+ video.download!
24
+ rescue Exception => ex
25
+ # don't do anything just yet, until we set up logging properly.
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+
@@ -6,20 +6,19 @@ class EncodingQueue
6
6
  # starts looking for unencoded videos.
7
7
  #
8
8
  def initialize
9
- @threads = YAML.load_file(Dir.getwd + '/config.yml')['threads'] if @threads.nil?
10
9
  EM.add_periodic_timer(5) {
11
10
  encode_next_video
12
11
  }
13
12
  end
14
13
 
15
14
 
16
- # Gets the next unencoded Video from the database and starts encoding it.
15
+ # Gets the next unencoded Video from the database and starts encoding its file.
17
16
  #
18
17
  def encode_next_video
19
- if Video.unencoded.count > 0 && ::Video.encoding.count < @threads
18
+ if Video.unencoded.count > 0 && Video.encoding.count < EnigmaMachine.threads
20
19
  video = Video.unencoded.first
21
20
  begin
22
- video.encoder.encode(video)
21
+ video.encode!
23
22
  rescue Exception => ex
24
23
  # don't do anything just yet, until we set up logging properly.
25
24
  end
@@ -19,39 +19,5 @@ class Encoder
19
19
  #
20
20
  has n, :encoding_tasks
21
21
 
22
- # Kicks off an FFMpeg encode on a given video.
23
- #
24
- def encode(video)
25
- ffmpeg(encoding_tasks.first, video)
26
- end
27
-
28
- private
29
-
30
- # Shells out to ffmpeg and hits the given video with the parameters in the
31
- # given task. Will call itself recursively until all tasks in this encoder's
32
- # encoding_tasks are completed.
33
- #
34
- def ffmpeg(task, video)
35
- current_task_index = encoding_tasks.index(task)
36
- movie = FFMPEG::Movie.new(video.file)
37
- encoding_operation = proc {
38
- video.update(:state => 'encoding')
39
- movie.transcode(video.file + task.output_file_suffix, task.command) do |p|
40
- puts p*100
41
- end
42
- }
43
- completion_callback = proc {|result|
44
- if task == encoding_tasks.last
45
- video.update(:state => 'complete')
46
- video.notify_complete
47
- else
48
- next_task_index = current_task_index + 1
49
- next_task = encoding_tasks[next_task_index]
50
- ffmpeg(next_task, video)
51
- end
52
- }
53
- EventMachine.defer(encoding_operation, completion_callback)
54
- end
55
-
56
22
  end
57
23
 
@@ -1,4 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/encoder'
2
+ require 'net/http'
3
+ require 'uri'
2
4
 
3
5
  # A video which we want to encode.
4
6
  #
@@ -9,32 +11,74 @@ class Video
9
11
  #
10
12
  property :id, Serial
11
13
  property :file, String, :required => true, :length => (1..510)
12
- property :state, String, :required => true,
13
- :length => (1..10), :default => 'unencoded'
14
14
  property :created_at, DateTime
15
15
  property :updated_at, DateTime
16
16
  property :encoder_id, Integer, :required => true
17
17
  property :callback_url, String
18
+ property :state, String
18
19
 
19
- validates_with_method :file, :method => :check_file
20
+ # State machine transitions
21
+ #
22
+ state_machine do
23
+
24
+ # State transitions for HTTP-hosted videos
25
+ after_transition :on => :download, :do => :do_download
26
+
27
+ # States for videos on the local filesystem
28
+ after_transition :on => :encode, :do => :do_encode
29
+ after_transition :on => :complete, :do => :notify_complete
30
+
31
+ event :download do
32
+ transition :waiting_for_download => :downloading
33
+ end
34
+
35
+ event :download_complete do
36
+ transition :downloading => :unencoded
37
+ end
38
+
39
+ event :download_error do
40
+ transition :downloading => :download_error
41
+ end
42
+
43
+ event :reset_download do
44
+ transition :downloading => :waiting_for_download
45
+ end
46
+
47
+ event :encode do
48
+ transition :unencoded => :encoding
49
+ end
50
+
51
+ event :complete do
52
+ transition :encoding => :complete
53
+ end
54
+
55
+ event :reset do
56
+ transition :encoding => :unencoded
57
+ end
58
+
59
+ event :encode_error do
60
+ transition :encoding => :encode_error
61
+ end
62
+
63
+ end
64
+
65
+ # Validations
66
+ #
20
67
  validates_uniqueness_of :file, :scope => :encoder_id,
21
68
  :message => "Same file with same encoder already exists"
69
+ validates_with_method :file, :method => :check_file
70
+
71
+ # Associations
72
+ #
22
73
  belongs_to :encoder
23
74
 
75
+ # Filters
76
+ #
24
77
  before :destroy, :check_destroy
78
+ before :create, :set_initial_state
25
79
 
26
80
  default_scope(:default).update(:order => [:created_at.asc])
27
81
 
28
- # Notifies a calling application that processing has completed by sending
29
- # a GET request to the video's callback_url.
30
- #
31
- def notify_complete
32
- begin
33
- Net::HTTP.get(URI.parse(video.callback_url)) unless callback_url.nil?
34
- rescue
35
- end
36
- end
37
-
38
82
 
39
83
  # Named scope for all videos which are waiting to start encoding.
40
84
  #
@@ -51,8 +95,8 @@ class Video
51
95
 
52
96
  # Named scope giving back all videos with encoding errors.
53
97
  #
54
- def self.with_errors
55
- all(:state => 'error')
98
+ def self.with_encode_errors
99
+ all(:state => 'encode_error')
56
100
  end
57
101
 
58
102
  # Named scope giving back all videos which have completed encoding.
@@ -61,6 +105,18 @@ class Video
61
105
  all(:state => 'complete', :order => [:updated_at.desc])
62
106
  end
63
107
 
108
+ # Named scope returning all videos which are not yet downloaded.
109
+ #
110
+ def self.waiting_for_download
111
+ all(:state => 'waiting_for_download')
112
+ end
113
+
114
+ # Named scope returning all videos which currently downloading.
115
+ #
116
+ def self.downloading
117
+ all(:state => 'downloading')
118
+ end
119
+
64
120
  # Resets all videos currently marked as "encoding" to state "unencoded"
65
121
  # which is the initial state.
66
122
  #
@@ -70,34 +126,138 @@ class Video
70
126
  #
71
127
  def self.reset_encoding_videos
72
128
  Video.encoding.each do |video|
73
- video.state = "unencoded"
74
- video.save!
129
+ video.reset!
130
+ end
131
+ end
132
+
133
+ def self.reset_downloading_videos
134
+ Video.downloading.each do |video|
135
+ puts "resetting video #{video.id}"
136
+ video.reset_download!
75
137
  end
76
138
  end
77
139
 
78
140
  private
79
141
 
142
+ # Validation checks for files - we want to ensure that the video file exists,
143
+ # and that it can be encoded by ffmpeg.
144
+ #
80
145
  def check_file
81
- return [false, "Give a file name, not nil"] if self.file.nil?
82
- return [false, "Give a file name, not a blank string"] if self.file.to_s.empty?
83
- return [false, "#{self.file} does not exist"] unless File.exist? self.file
84
- return [false, "#{self.file} is a directory"] if File.directory? self.file
85
- movie = FFMPEG::Movie.new(self.file)
86
- return [false, "#{self.file} is not a media file"] unless movie.valid?
146
+ if local?
147
+ return [false, "Give a file name, not nil"] if self.file.nil?
148
+ return [false, "Give a file name, not a blank string"] if self.file.to_s.empty?
149
+ return [false, "#{self.file} does not exist"] unless File.exist? self.file
150
+ return [false, "#{self.file} is a directory"] if File.directory? self.file
151
+ movie = FFMPEG::Movie.new(self.file)
152
+ return [false, "#{self.file} is not a media file"] unless movie.valid?
153
+ end
87
154
  return true
88
155
  end
89
156
 
157
+ # Returns true unless the video's file starts with 'http://'
158
+ #
159
+ def set_initial_state
160
+ if local?
161
+ self.state = "unencoded"
162
+ else
163
+ self.state = "waiting_for_download"
164
+ end
165
+ end
166
+
167
+ # Checks whether a video object can be destroyed - videos cannot be destroyed
168
+ # if they are currently encoding.
169
+ #
90
170
  def check_destroy
91
171
  return true if (self.state != 'encoding')
92
- encoder = Encoder.get(self.encoder_id)
93
172
  return true if stop_encode
94
173
  throw :halt
95
174
  end
96
175
 
176
+ # Would stop the encoder process if it was implemented. Currently does nothing
177
+ # and returns false.
178
+ #
97
179
  def stop_encode
98
180
  return false
99
181
  #TODO Kill the encoder process
100
182
  end
101
183
 
184
+ # Tells this video's encoder to start encoding tasks.
185
+ #
186
+ def do_encode
187
+ ffmpeg(encoder.encoding_tasks.first)
188
+ end
189
+
190
+ # Shells out to ffmpeg and hits the given video with the parameters in the
191
+ # given task. Will call itself recursively until all tasks in the encoder's
192
+ # encoding_tasks are completed.
193
+ #
194
+ def ffmpeg(task)
195
+ current_task_index = encoder.encoding_tasks.index(task)
196
+ movie = FFMPEG::Movie.new(file_to_encode)
197
+ encoding_operation = proc do
198
+ movie.transcode(file_to_encode + task.output_file_suffix, task.command)
199
+ end
200
+
201
+ completion_callback = proc do |result|
202
+ if task == encoder.encoding_tasks.last
203
+ self.complete!
204
+ else
205
+ next_task_index = current_task_index + 1
206
+ next_task = encoder.encoding_tasks[next_task_index]
207
+ ffmpeg(next_task)
208
+ end
209
+ end
210
+
211
+ EventMachine.defer(encoding_operation, completion_callback)
212
+ end
213
+
214
+ # Notifies a calling application that processing has completed by sending
215
+ # a GET request to the video's callback_url.
216
+ #
217
+ def notify_complete
218
+ EventMachine::HttpRequest.new(callback_url).get :timeout => 10 unless callback_url.nil?
219
+ end
220
+
221
+ # Downloads a video from a remote location via HTTP
222
+ #
223
+ def do_download
224
+ FileUtils.rm(file_to_encode, :force => true)
225
+ FileUtils.mkdir_p(File.dirname(file_to_encode))
226
+ http = EventMachine::HttpRequest.new(file).get :timeout => 10
227
+
228
+ http.stream do |data|
229
+ puts "what is status?: " + http.response_header.status.to_s
230
+ File.open(file_to_encode, 'a') {|f| f.write(data) }
231
+ end
232
+
233
+ http.callback do
234
+ download_complete!
235
+ end
236
+
237
+ http.errback do
238
+ download_error!
239
+ end
240
+ end
241
+
242
+ # Returns false if the video is available via http
243
+ #
244
+ def local?
245
+ return false if self.file =~ /^http:\/\//
246
+ return true
247
+ end
248
+
249
+ # If the file is local, this just returns its location. If it's not local,
250
+ # it builds a path to the file based on your config.yml + the video's id and
251
+ # returns that.
252
+ #
253
+ def file_to_encode
254
+ if local?
255
+ return file
256
+ else
257
+ filename = File.basename(URI.parse(file).path)
258
+ return File.join(EnigmaMachine.download_storage_path, self.id.to_s, filename)
259
+ end
260
+ end
261
+
102
262
  end
103
263
 
@@ -34,6 +34,20 @@
34
34
  </ul>
35
35
  <% end %>
36
36
 
37
+ <% unless @downloading_videos.empty? %>
38
+ <h2>Failed to encode</h2>
39
+ <ul>
40
+ <%= partial :"videos/video", :collection => @downloading_videos %>
41
+ </ul>
42
+ <% end %>
43
+
44
+ <% unless @waiting_for_download_videos.empty? %>
45
+ <h2>Failed to encode</h2>
46
+ <ul>
47
+ <%= partial :"videos/video", :collection => @waiting_for_download_videos %>
48
+ </ul>
49
+ <% end %>
50
+
37
51
  </div>
38
52
  </div>
39
53
 
@@ -1,3 +1,6 @@
1
1
  username: admin
2
2
  password: admin
3
3
  threads: 1
4
+ enable_http_downloads: false
5
+ download_storage_path: /path/to/where/downloads/should/end/up
6
+
data/test/helper.rb CHANGED
@@ -42,3 +42,7 @@ def encode_credentials(username, password)
42
42
  "Basic " + Base64.encode64("#{username}:#{password}")
43
43
  end
44
44
 
45
+ def http_file_location
46
+ "http://foo.org/bar/blah.wmv"
47
+ end
48
+
@@ -2,22 +2,11 @@ require 'machinist/data_mapper'
2
2
  require 'faker'
3
3
  require 'sham'
4
4
 
5
+
5
6
  Encoder.blueprint do
6
7
  name { Faker::Name.name }
7
8
  end
8
9
 
9
- Video.blueprint do
10
- encoder
11
- file { File.dirname(__FILE__) + "/afile.mpg" }
12
- state { "unencoded" }
13
- created_at DateTime.now
14
- updated_at DateTime.now
15
- end
16
-
17
- Video.blueprint(:with_callback) do
18
- callback_url { "http://example.com/call/back/id" }
19
- end
20
-
21
10
  EncodingTask.blueprint do
22
11
  name { "320x240 flv"}
23
12
  output_file_suffix { ".foo.flv" }
@@ -31,3 +20,19 @@ EncodingTask.blueprint(:with_encoder) do
31
20
  encoder
32
21
  end
33
22
 
23
+ Video.blueprint do
24
+ encoder
25
+ file { File.dirname(__FILE__) + "/afile.mpg" }
26
+ state { "unencoded" }
27
+ created_at DateTime.now
28
+ updated_at DateTime.now
29
+ end
30
+
31
+ Video.blueprint(:http) do
32
+ file { http_file_location }
33
+ end
34
+
35
+ Video.blueprint(:with_callback) do
36
+ callback_url { "http://example.com/call/back/id" }
37
+ end
38
+
data/test/test_encoder.rb CHANGED
@@ -5,7 +5,7 @@ class TestEncoder < Test::Unit::TestCase
5
5
  context "An Encoder instance" do
6
6
 
7
7
  setup do
8
- @encoder = ::Encoder.make
8
+ @encoder = Encoder.make
9
9
  end
10
10
 
11
11
  should "be invalid without a name" do
@@ -28,18 +28,19 @@ class TestEncoder < Test::Unit::TestCase
28
28
  context "hitting the ffmpeg method" do
29
29
  context "for an encoder with 1 task" do
30
30
  setup do
31
- video = Video.make
31
+ @video = Video.make
32
32
  task = EncodingTask.make
33
- video.encoder.encoding_tasks << task
34
- video.encoder.save
35
- video.encoder.encode(video)
36
- @id = video.id
33
+ @video.encoder.encoding_tasks << task
34
+ @video.encoder.save
35
+ @video.encode!
36
+ @id = @video.id
37
37
  sleep 1
38
38
  end
39
39
 
40
- should "mark the video as encoding" do
41
- video = Video.get(@id)
42
- assert_equal "encoding", video.state
40
+ should "create the file specified in the encoder's first encoding task" do
41
+ suffix = @video.encoder.encoding_tasks.first.output_file_suffix
42
+ file = @video.file + suffix
43
+ assert File.exist? file
43
44
  end
44
45
 
45
46
  should_eventually "mark the video as complete" do
@@ -56,22 +57,15 @@ class TestEncoder < Test::Unit::TestCase
56
57
  task = EncodingTask.make
57
58
  2.times { video.encoder.encoding_tasks << EncodingTask.make }
58
59
  video.encoder.save
59
- video.encoder.encode(video)
60
+ video.encode!
60
61
  @id = video.id
61
62
  end
62
63
 
63
- should "mark the video as encoding" do
64
- sleep 1
65
- video = Video.get(@id)
66
- assert_equal "encoding", video.state
67
- end
68
-
69
64
  should_eventually "mark the video as complete" do
70
65
  sleep 1
71
66
  video = Video.get(@id)
72
67
  assert_equal "complete", video.state
73
68
  end
74
-
75
69
  end
76
70
 
77
71
  end
@@ -18,7 +18,7 @@ class TestEnigmamachine < Test::Unit::TestCase
18
18
  get '/', {}, basic_auth_creds
19
19
  end
20
20
 
21
- should "respond" do
21
+ should "work" do
22
22
  assert last_response.ok?
23
23
  end
24
24
 
data/test/test_video.rb CHANGED
@@ -6,36 +6,36 @@ class TestVideo < Test::Unit::TestCase
6
6
  context "A Video instance" do
7
7
 
8
8
  should "be invalid with a bad file path" do
9
- resource = ::Video.make
10
- resource.file = ""
11
- assert(!resource.valid?, "must not be empty")
12
- resource.file = nil
13
- assert(!resource.valid?, "must not be nil")
14
- resource.file = "/fdfdf/sfdsdfsd/fse.gfr"
15
- assert(!resource.valid?, "must be exist")
16
- resource.file = File.dirname(__FILE__)
17
- assert(!resource.valid?, "must not be a directory")
18
- resource.file = __FILE__
19
- assert(!resource.valid?, "must be media file")
9
+ video = Video.make
10
+ video.file = ""
11
+ assert(!video.valid?, "must not be empty")
12
+ video.file = nil
13
+ assert(!video.valid?, "must not be nil")
14
+ video.file = "/fdfdf/sfdsdfsd/fse.gfr"
15
+ assert(!video.valid?, "must exist")
16
+ video.file = File.dirname(__FILE__)
17
+ assert(!video.valid?, "must not be a directory")
18
+ video.file = __FILE__
19
+ assert(!video.valid?, "must be media file")
20
20
  end
21
21
 
22
22
  should "be valid without a callback_url" do
23
- resource = ::Video.make
24
- resource.callback_url = ""
25
- assert resource.valid?
26
- resource.callback_url = nil
27
- assert resource.valid?
23
+ video = Video.make
24
+ video.callback_url = ""
25
+ assert video.valid?
26
+ video.callback_url = nil
27
+ assert video.valid?
28
28
  end
29
29
 
30
30
  should "be valid with a callback_url" do
31
- resource = ::Video.make
32
- resource.callback_url = "blah"
33
- assert resource.valid?
31
+ video = Video.make
32
+ video.callback_url = "blah"
33
+ assert video.valid?
34
34
  end
35
35
 
36
36
  should "be valid with a correct file path" do
37
- resource = ::Video.make
38
- assert resource.valid?
37
+ video = ::Video.make
38
+ assert video.valid?
39
39
  end
40
40
 
41
41
  should "belong to an Encoder" do
@@ -51,6 +51,60 @@ class TestVideo < Test::Unit::TestCase
51
51
  end
52
52
  end
53
53
 
54
+ context "on a local filesystem" do
55
+ setup do
56
+ @video = Video.make
57
+ end
58
+
59
+ should "have an initial state of 'unencoded'" do
60
+ assert_equal("unencoded", @video.state)
61
+ end
62
+
63
+ should "transition to state 'encoding' on 'encode!' command" do
64
+ @video.encode!
65
+ assert_equal("encoding", @video.state)
66
+ end
67
+
68
+ should "transition to state 'unencoded' on 'reset!' command" do
69
+ @video.encode!
70
+ @video.reset!
71
+ assert_equal("unencoded", @video.state)
72
+ end
73
+
74
+ end
75
+
76
+ context "available via http" do
77
+ setup do
78
+ @video = Video.make(:http)
79
+ end
80
+
81
+ should "have an initial state of 'waiting_for_download'" do
82
+ assert_equal("waiting_for_download", @video.state)
83
+ end
84
+
85
+ context "for the download! event" do
86
+ setup do
87
+ EventMachine.run do
88
+ EventMachine::MockHttpRequest.use {
89
+ EventMachine::HttpRequest.register_file(http_file_location, :get, '/home/dave/workspace/enigmamachine/test/support/afile.mpg')
90
+ }
91
+ @video.download!
92
+ EventMachine.stop
93
+ end
94
+ end
95
+
96
+ should_eventually "transition to state 'downloading'" do
97
+ assert_equal("downloading", @video.state)
98
+ end
99
+
100
+ should_eventually "hit the download URL once" do
101
+ EventMachine::MockHttpRequest.activate!
102
+ assert_equal(1, EventMachine::HttpRequest.count(http_file_location, :get))
103
+ EventMachine::MockHttpRequest.deactivate!
104
+ end
105
+
106
+ end
107
+ end
54
108
  end
55
109
 
56
110
  context "The Video class" do
@@ -81,40 +135,40 @@ class TestVideo < Test::Unit::TestCase
81
135
  end
82
136
  end
83
137
 
84
- context "when try to delete any kind of videos from base" do
138
+ context "deleting videos" do
85
139
  setup do
86
140
  clear_videos
87
141
  5.times { Video.make }
88
142
  end
89
143
 
90
- should "be delete an unencoded videos" do
144
+ should "delete an unencoded video" do
91
145
  count = Video.unencoded.count
92
146
  2.times { Video.unencoded.first.destroy }
93
147
  assert_equal count - 2, Video.unencoded.count
94
148
  end
95
149
 
96
- should "be delete a completed videos" do
150
+ should "delete a completed video" do
97
151
  3.times { Video.unencoded.first.update(:state => "complete") }
98
152
  count = Video.complete.count
99
153
  2.times { Video.complete.first.destroy }
100
154
  assert_equal count - 2, Video.complete.count
101
155
  end
102
156
 
103
- should "be delete a videos with errors" do
104
- 3.times { Video.unencoded.first.update(:state => "error") }
105
- count = Video.with_errors.count
106
- 2.times { Video.with_errors.first.destroy }
107
- assert_equal count - 2, Video.with_errors.count
157
+ should "delete videos with errors" do
158
+ 3.times { Video.unencoded.first.update(:state => "encode_error") }
159
+ count = Video.with_encode_errors.count
160
+ 2.times { Video.with_encode_errors.first.destroy }
161
+ assert_equal count - 2, Video.with_encode_errors.count
108
162
  end
109
163
 
110
- should "not be delete an encoding videos" do
164
+ should "not delete an encoding video" do
111
165
  3.times { Video.unencoded.first.update(:state => "encoding") }
112
166
  count = Video.encoding.count
113
167
  2.times { Video.encoding.first.destroy }
114
168
  assert_equal count, Video.encoding.count
115
169
  end
116
170
 
117
- should "be hard delete an encoding videos" do
171
+ should "allow force destroy of an encoding video" do
118
172
  3.times { Video.unencoded.first.update(:state => "encoding") }
119
173
  count = Video.encoding.count
120
174
  2.times { Video.encoding.first.destroy! }
@@ -128,13 +182,21 @@ class TestVideo < Test::Unit::TestCase
128
182
  end
129
183
 
130
184
  should "be able to grab all videos with errors" do
131
- assert Video.respond_to? "with_errors"
185
+ assert Video.respond_to? "with_encode_errors"
132
186
  end
133
187
 
134
188
  should "be able to grab all videos that are encoding" do
135
189
  assert Video.respond_to? "encoding"
136
190
  end
137
191
 
192
+ should "be able to grab all videos that are not yet downloaded" do
193
+ assert Video.respond_to? "waiting_for_download"
194
+ end
195
+
196
+ should "be able to grab all videos that are downloading" do
197
+ assert Video.respond_to? "downloading"
198
+ end
199
+
138
200
  end
139
201
 
140
202
  def clear_videos
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enigmamachine
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 7
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
- - 5
8
+ - 6
8
9
  - 0
9
- version: 0.5.0
10
+ version: 0.6.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Dave Hrycyszyn
@@ -15,127 +16,189 @@ autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2010-10-04 00:00:00 +01:00
19
+ date: 2010-11-01 00:00:00 +00:00
19
20
  default_executable: enigmamachine
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
22
23
  name: thoughtbot-shoulda
23
24
  prerelease: false
24
25
  requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
25
27
  requirements:
26
28
  - - ">="
27
29
  - !ruby/object:Gem::Version
30
+ hash: 3
28
31
  segments:
29
32
  - 0
30
33
  version: "0"
31
34
  type: :development
32
35
  version_requirements: *id001
33
36
  - !ruby/object:Gem::Dependency
34
- name: data_mapper
37
+ name: rack-test
35
38
  prerelease: false
36
39
  requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: data_mapper
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
37
55
  requirements:
38
56
  - - ">="
39
57
  - !ruby/object:Gem::Version
58
+ hash: 19
40
59
  segments:
41
60
  - 1
42
61
  - 0
43
62
  - 2
44
63
  version: 1.0.2
45
64
  type: :runtime
46
- version_requirements: *id002
65
+ version_requirements: *id003
47
66
  - !ruby/object:Gem::Dependency
48
67
  name: dm-sqlite-adapter
49
68
  prerelease: false
50
- requirement: &id003 !ruby/object:Gem::Requirement
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
51
71
  requirements:
52
72
  - - ">="
53
73
  - !ruby/object:Gem::Version
74
+ hash: 19
54
75
  segments:
55
76
  - 1
56
77
  - 0
57
78
  - 2
58
79
  version: 1.0.2
59
80
  type: :runtime
60
- version_requirements: *id003
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
83
+ name: state_machine
84
+ prerelease: false
85
+ requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 51
91
+ segments:
92
+ - 0
93
+ - 9
94
+ - 4
95
+ version: 0.9.4
96
+ type: :runtime
97
+ version_requirements: *id005
61
98
  - !ruby/object:Gem::Dependency
62
99
  name: eventmachine
63
100
  prerelease: false
64
- requirement: &id004 !ruby/object:Gem::Requirement
101
+ requirement: &id006 !ruby/object:Gem::Requirement
102
+ none: false
65
103
  requirements:
66
104
  - - ">="
67
105
  - !ruby/object:Gem::Version
106
+ hash: 59
68
107
  segments:
69
108
  - 0
70
109
  - 12
71
110
  - 10
72
111
  version: 0.12.10
73
112
  type: :runtime
74
- version_requirements: *id004
113
+ version_requirements: *id006
75
114
  - !ruby/object:Gem::Dependency
76
115
  name: rack-flash
77
116
  prerelease: false
78
- requirement: &id005 !ruby/object:Gem::Requirement
117
+ requirement: &id007 !ruby/object:Gem::Requirement
118
+ none: false
79
119
  requirements:
80
120
  - - ">="
81
121
  - !ruby/object:Gem::Version
122
+ hash: 3
82
123
  segments:
83
124
  - 0
84
125
  version: "0"
85
126
  type: :runtime
86
- version_requirements: *id005
127
+ version_requirements: *id007
87
128
  - !ruby/object:Gem::Dependency
88
129
  name: ruby-debug
89
130
  prerelease: false
90
- requirement: &id006 !ruby/object:Gem::Requirement
131
+ requirement: &id008 !ruby/object:Gem::Requirement
132
+ none: false
91
133
  requirements:
92
134
  - - ">="
93
135
  - !ruby/object:Gem::Version
136
+ hash: 3
94
137
  segments:
95
138
  - 0
96
139
  version: "0"
97
140
  type: :runtime
98
- version_requirements: *id006
141
+ version_requirements: *id008
99
142
  - !ruby/object:Gem::Dependency
100
143
  name: sinatra
101
144
  prerelease: false
102
- requirement: &id007 !ruby/object:Gem::Requirement
145
+ requirement: &id009 !ruby/object:Gem::Requirement
146
+ none: false
103
147
  requirements:
104
148
  - - ">="
105
149
  - !ruby/object:Gem::Version
150
+ hash: 23
106
151
  segments:
107
152
  - 1
108
153
  - 0
109
154
  - 0
110
155
  version: 1.0.0
111
156
  type: :runtime
112
- version_requirements: *id007
157
+ version_requirements: *id009
113
158
  - !ruby/object:Gem::Dependency
114
159
  name: streamio-ffmpeg
115
160
  prerelease: false
116
- requirement: &id008 !ruby/object:Gem::Requirement
161
+ requirement: &id010 !ruby/object:Gem::Requirement
162
+ none: false
117
163
  requirements:
118
164
  - - ">="
119
165
  - !ruby/object:Gem::Version
166
+ hash: 5
120
167
  segments:
121
168
  - 0
122
169
  - 7
123
170
  - 3
124
171
  version: 0.7.3
125
172
  type: :runtime
126
- version_requirements: *id008
173
+ version_requirements: *id010
127
174
  - !ruby/object:Gem::Dependency
128
175
  name: thin
129
176
  prerelease: false
130
- requirement: &id009 !ruby/object:Gem::Requirement
177
+ requirement: &id011 !ruby/object:Gem::Requirement
178
+ none: false
131
179
  requirements:
132
180
  - - ">="
133
181
  - !ruby/object:Gem::Version
182
+ hash: 3
134
183
  segments:
135
184
  - 0
136
185
  version: "0"
137
186
  type: :runtime
138
- version_requirements: *id009
187
+ version_requirements: *id011
188
+ - !ruby/object:Gem::Dependency
189
+ name: em-http-request
190
+ prerelease: false
191
+ requirement: &id012 !ruby/object:Gem::Requirement
192
+ none: false
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ hash: 3
197
+ segments:
198
+ - 0
199
+ version: "0"
200
+ type: :runtime
201
+ version_requirements: *id012
139
202
  description: A RESTful video encoder which you can use as either a front-end to ffmpeg or headless on a server.
140
203
  email: dave@caprica
141
204
  executables:
@@ -146,6 +209,7 @@ extra_rdoc_files:
146
209
  - LICENSE
147
210
  - README.rdoc
148
211
  files:
212
+ - .autotest
149
213
  - .document
150
214
  - .gitignore
151
215
  - LICENSE
@@ -157,6 +221,7 @@ files:
157
221
  - lib/enigmamachine.rb
158
222
  - lib/enigmamachine.sqlite3
159
223
  - lib/enigmamachine/config.ru
224
+ - lib/enigmamachine/download_queue.rb
160
225
  - lib/enigmamachine/encoding_queue.rb
161
226
  - lib/enigmamachine/models/encoder.rb
162
227
  - lib/enigmamachine/models/encoding_task.rb
@@ -209,30 +274,34 @@ rdoc_options:
209
274
  require_paths:
210
275
  - lib
211
276
  required_ruby_version: !ruby/object:Gem::Requirement
277
+ none: false
212
278
  requirements:
213
279
  - - ">="
214
280
  - !ruby/object:Gem::Version
281
+ hash: 3
215
282
  segments:
216
283
  - 0
217
284
  version: "0"
218
285
  required_rubygems_version: !ruby/object:Gem::Requirement
286
+ none: false
219
287
  requirements:
220
288
  - - ">="
221
289
  - !ruby/object:Gem::Version
290
+ hash: 3
222
291
  segments:
223
292
  - 0
224
293
  version: "0"
225
294
  requirements: []
226
295
 
227
296
  rubyforge_project:
228
- rubygems_version: 1.3.6
297
+ rubygems_version: 1.3.7
229
298
  signing_key:
230
299
  specification_version: 3
231
300
  summary: A RESTful video encoder.
232
301
  test_files:
233
- - test/support/blueprints.rb
234
- - test/helper.rb
235
302
  - test/test_encoding_queue.rb
236
- - test/test_video.rb
237
303
  - test/test_encoder.rb
304
+ - test/test_video.rb
238
305
  - test/test_enigmamachine.rb
306
+ - test/helper.rb
307
+ - test/support/blueprints.rb