rest-ftp-daemon 0.400.0 → 0.410.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +26 -6
  3. data/Rakefile +5 -3
  4. data/bin/rest-ftp-daemon +2 -1
  5. data/config.ru +8 -2
  6. data/lib/rest-ftp-daemon.rb +10 -2
  7. data/lib/rest-ftp-daemon/api/debug.rb +17 -5
  8. data/lib/rest-ftp-daemon/api/{job_presenter.rb → entities/job.rb} +25 -2
  9. data/lib/rest-ftp-daemon/api/entities/options.rb +15 -0
  10. data/lib/rest-ftp-daemon/api/jobs.rb +46 -13
  11. data/lib/rest-ftp-daemon/api/root-real.rb +80 -0
  12. data/lib/rest-ftp-daemon/api/root-test.rb +69 -0
  13. data/lib/rest-ftp-daemon/api/root.rb +21 -5
  14. data/lib/rest-ftp-daemon/constants.rb +18 -1
  15. data/lib/rest-ftp-daemon/counters.rb +0 -3
  16. data/lib/rest-ftp-daemon/exceptions.rb +16 -12
  17. data/lib/rest-ftp-daemon/helpers/api.rb +3 -3
  18. data/lib/rest-ftp-daemon/helpers/common.rb +12 -5
  19. data/lib/rest-ftp-daemon/helpers/views.rb +41 -11
  20. data/lib/rest-ftp-daemon/job.rb +118 -420
  21. data/lib/rest-ftp-daemon/job_queue.rb +34 -5
  22. data/lib/rest-ftp-daemon/jobs/dummy.rb +20 -0
  23. data/lib/rest-ftp-daemon/jobs/transfer.rb +319 -0
  24. data/lib/rest-ftp-daemon/jobs/video.rb +107 -0
  25. data/lib/rest-ftp-daemon/location.rb +125 -0
  26. data/lib/rest-ftp-daemon/path.rb +3 -0
  27. data/lib/rest-ftp-daemon/remote_ftp.rb +2 -2
  28. data/lib/rest-ftp-daemon/remote_sftp.rb +2 -2
  29. data/lib/rest-ftp-daemon/static/config.json +411 -0
  30. data/lib/rest-ftp-daemon/static/css/bootstrap.min.css +14 -0
  31. data/lib/rest-ftp-daemon/static/css/{bootstrap.css → bootstrap.old.css} +0 -0
  32. data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.eot +0 -0
  33. data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.svg +288 -0
  34. data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.ttf +0 -0
  35. data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.woff +0 -0
  36. data/lib/rest-ftp-daemon/static/fonts/glyphicons-halflings-regular.woff2 +0 -0
  37. data/lib/rest-ftp-daemon/static/swagger.html +83 -0
  38. data/lib/rest-ftp-daemon/static/swagger/css/print.css +1362 -0
  39. data/lib/rest-ftp-daemon/static/swagger/css/reset.css +125 -0
  40. data/lib/rest-ftp-daemon/static/swagger/css/screen.css +1489 -0
  41. data/lib/rest-ftp-daemon/static/swagger/css/style.css +250 -0
  42. data/lib/rest-ftp-daemon/static/swagger/css/typography.css +14 -0
  43. data/lib/rest-ftp-daemon/static/swagger/fonts/DroidSans-Bold.ttf +0 -0
  44. data/lib/rest-ftp-daemon/static/swagger/fonts/DroidSans.ttf +0 -0
  45. data/lib/rest-ftp-daemon/static/swagger/images/collapse.gif +0 -0
  46. data/lib/rest-ftp-daemon/static/swagger/images/expand.gif +0 -0
  47. data/lib/rest-ftp-daemon/static/swagger/images/explorer_icons.png +0 -0
  48. data/lib/rest-ftp-daemon/static/swagger/images/favicon-16x16.png +0 -0
  49. data/lib/rest-ftp-daemon/static/swagger/images/favicon-32x32.png +0 -0
  50. data/lib/rest-ftp-daemon/static/swagger/images/favicon.ico +0 -0
  51. data/lib/rest-ftp-daemon/static/swagger/images/logo_small.png +0 -0
  52. data/lib/rest-ftp-daemon/static/swagger/images/pet_store_api.png +0 -0
  53. data/lib/rest-ftp-daemon/static/swagger/images/throbber.gif +0 -0
  54. data/lib/rest-ftp-daemon/static/swagger/images/wordnik_api.png +0 -0
  55. data/lib/rest-ftp-daemon/static/swagger/lib/backbone-min.js +15 -0
  56. data/lib/rest-ftp-daemon/static/swagger/lib/es5-shim.js +2065 -0
  57. data/lib/rest-ftp-daemon/static/swagger/lib/handlebars-4.0.5.js +4608 -0
  58. data/lib/rest-ftp-daemon/static/swagger/lib/highlight.9.1.0.pack.js +2 -0
  59. data/lib/rest-ftp-daemon/static/swagger/lib/highlight.9.1.0.pack_extended.js +34 -0
  60. data/lib/rest-ftp-daemon/static/swagger/lib/jquery-1.8.0.min.js +2 -0
  61. data/lib/rest-ftp-daemon/static/swagger/lib/jquery.ba-bbq.min.js +18 -0
  62. data/lib/rest-ftp-daemon/static/swagger/lib/jquery.slideto.min.js +1 -0
  63. data/lib/rest-ftp-daemon/static/swagger/lib/jquery.wiggle.min.js +8 -0
  64. data/lib/rest-ftp-daemon/static/swagger/lib/js-yaml.min.js +3 -0
  65. data/lib/rest-ftp-daemon/static/swagger/lib/jsoneditor.min.js +11 -0
  66. data/lib/rest-ftp-daemon/static/swagger/lib/lodash.min.js +102 -0
  67. data/lib/rest-ftp-daemon/static/swagger/lib/marked.js +1272 -0
  68. data/lib/rest-ftp-daemon/static/swagger/lib/object-assign-pollyfill.js +23 -0
  69. data/lib/rest-ftp-daemon/static/swagger/lib/swagger-oauth.js +347 -0
  70. data/lib/rest-ftp-daemon/static/swagger/lib/swagger-ui.min.js +10 -0
  71. data/lib/rest-ftp-daemon/uri.rb +9 -1
  72. data/lib/rest-ftp-daemon/views/dashboard.haml +1 -1
  73. data/lib/rest-ftp-daemon/views/dashboard_footer.haml +6 -3
  74. data/lib/rest-ftp-daemon/views/dashboard_jobs.haml +3 -3
  75. data/lib/rest-ftp-daemon/views/dashboard_table.haml +25 -20
  76. data/lib/rest-ftp-daemon/workers/transfer.rb +14 -0
  77. data/rest-ftp-daemon.gemspec +14 -4
  78. data/spec/rest-ftp-daemon/features/swagger_spec.rb +24 -0
  79. data/spec/spec_helper.rb +4 -4
  80. metadata +119 -14
@@ -35,6 +35,35 @@ module RestFtpDaemon
35
35
  log_info "JobQueue initialized (prefix: #{@prefix})"
36
36
  end
37
37
 
38
+ def create_job params
39
+ # Build class name and chock if it exists
40
+ # klass = Kernel.const_get("Job#{params[:type].to_s.capitalize}") rescue nil
41
+ klass_name = "Job#{params[:type].to_s.capitalize}"
42
+ klass = Kernel.const_get(klass_name) rescue nil
43
+
44
+ # If object not found, don't create a job !
45
+ unless klass && klass < Job
46
+ message = "can't create [#{klass_name}] for type [#{params[:type]}]"
47
+ log_error "JobQueue.create_job: #{message}"
48
+ raise QueueCantCreateJob, message
49
+ end
50
+
51
+ # Generate an ID and stack it
52
+ @mutex.synchronize do
53
+ @last_id += 1
54
+ end
55
+ job_id = prefixed_id(@last_id)
56
+
57
+ # Instantiate it and return the now object
58
+ log_info "JobQueue.create_job: creating [#{klass.name}] with ID [#{job_id}]"
59
+ job = klass.new(job_id, params)
60
+
61
+ # Push it on the queue
62
+ push job
63
+
64
+ return job
65
+ end
66
+
38
67
  def generate_id
39
68
  @mutex.synchronize do
40
69
  @last_id += 1
@@ -121,13 +150,13 @@ module RestFtpDaemon
121
150
 
122
151
  def push job
123
152
  # Check that item responds to "priorty" method
124
- raise "JobQueue.push: job should respond to priority method" unless job.respond_to? :priority
125
- raise "JobQueue.push: job should respond to id method" unless job.respond_to? :id
126
- raise "JobQueue.push: job should respond to pool method" unless job.respond_to? :pool
127
- raise "JobQueue.push: job should respond to reset" unless job.respond_to? :reset
153
+ raise "JobQueue.push: job should respond to: priority" unless job.respond_to? :priority
154
+ raise "JobQueue.push: job should respond to: id" unless job.respond_to? :id
155
+ raise "JobQueue.push: job should respond to: pool" unless job.respond_to? :pool
156
+ raise "JobQueue.push: job should respond to: reset" unless job.respond_to? :reset
128
157
 
129
158
  @mutex.synchronize do
130
- # Get this job's pool @ prepare queue of this pool
159
+ # Get this job's pool & prepare queue of this pool
131
160
  pool = job.pool
132
161
  myqueue = (@queues[pool] ||= [])
133
162
 
@@ -0,0 +1,20 @@
1
+ module RestFtpDaemon
2
+ class JobDummy < Job
3
+
4
+ # def initialize job_id, params = {}
5
+ # super
6
+ # end
7
+
8
+ def before
9
+ end
10
+
11
+ def work
12
+ log_info "JobDummy.work YEAH WE'RE PROCESSING, man !"
13
+ sleep 5
14
+ end
15
+
16
+ def after
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,319 @@
1
+ #FIXME: move progress from Job/infos/transfer to Job/progress
2
+
3
+ module RestFtpDaemon
4
+ class JobTransfer < Job
5
+
6
+ protected
7
+
8
+ def before
9
+ # Prepare flags
10
+ flag_prepare :mkdir, false
11
+ flag_prepare :overwrite, false
12
+ flag_prepare :tempfile, true
13
+
14
+ # Some init
15
+ @transfer_sent = 0
16
+ set_info :source, :processed, 0
17
+
18
+ # Ensure source is FILE
19
+ raise RestFtpDaemon::SourceNotSupported, @source_loc.scheme unless source_uri.is_a? URI::FILE
20
+
21
+ # Prepare remote (case would be preferable but too hard to use,
22
+ # as target could be of a descendent class of URI:XXX and not matching directly)
23
+ case target_uri
24
+ when URI::FTP
25
+ log_info "JobTransfer.before target_method FTP"
26
+ @remote = RemoteFTP.new @target_loc.uri, log_prefix, debug: @config[:debug_ftp]
27
+ when URI::FTPES, URI::FTPS
28
+ log_info "JobTransfer.before target_method FTPES/FTPS"
29
+ @remote = RemoteFTP.new @target_loc.uri, log_prefix, debug: @config[:debug_ftps], ftpes: true
30
+ when URI::SFTP
31
+ log_info "JobTransfer.before target_method SFTP"
32
+ @remote = RemoteSFTP.new @target_loc.uri, log_prefix, debug: @config[:debug_sftp]
33
+ else
34
+ log_info "JobTransfer.before unknown scheme [#{@target_loc.scheme}]"
35
+ raise RestFtpDaemon::TargetNotSupported, @target_loc.scheme
36
+ end
37
+
38
+ rescue RestFtpDaemon::AssertionFailed => exception
39
+ return oops :started, exception
40
+
41
+ # rescue URI::InvalidURIError => exception
42
+ # return oops :started, exception, "target_invalid"
43
+ end
44
+
45
+ def work
46
+ # Scan local source files from disk
47
+ set_status JOB_STATUS_CHECKING_SRC
48
+ sources = scan_local_paths @source_loc.path
49
+ set_info :source, :count, sources.count
50
+ set_info :source, :files, sources.collect(&:full)
51
+ log_info "JobTransfer.work sources #{sources.collect(&:name)}"
52
+ raise RestFtpDaemon::SourceNotFound if sources.empty?
53
+
54
+ # Guess target file name, and fail if present while we matched multiple sources
55
+ raise RestFtpDaemon::TargetDirectoryError if @target_loc.name && sources.count>1
56
+
57
+ # Connect to remote server and login
58
+ set_status JOB_STATUS_CONNECTING
59
+ @remote.connect
60
+
61
+ # Prepare target path or build it if asked
62
+ set_status JOB_STATUS_CHDIR
63
+ @remote.chdir_or_create @target_loc.dir, @mkdir
64
+
65
+ # Compute total files size
66
+ @transfer_total = sources.collect(&:size).sum
67
+ set_info :work, :total, @transfer_total
68
+
69
+ # Reset counters
70
+ @last_data = 0
71
+ @last_time = Time.now
72
+
73
+ # Handle each source file matched, and start a transfer
74
+ source_processed = 0
75
+ targets = []
76
+ sources.each do |source|
77
+ # Compute target filename
78
+ target_final = @target_loc.clone
79
+
80
+ # Add the source file name if none found in the target path
81
+ unless full_target.name
82
+ target_final.name = source.name
83
+ end
84
+
85
+ # Do the transfer, for each file
86
+ remote_push source, target_final
87
+
88
+ # Add it to transferred target names
89
+ targets << target_final.path
90
+ set_info :target, :files, targets
91
+
92
+ # Update counters
93
+ set_info :source, :processed, source_processed += 1
94
+ end
95
+
96
+ rescue SocketError => exception
97
+ return oops :ended, exception, "conn_socket_error"
98
+
99
+ rescue EOFError => exception
100
+ return oops :ended, exception, "conn_eof"
101
+
102
+ rescue Errno::EHOSTDOWN => exception
103
+ return oops :ended, exception, "conn_host_is_down"
104
+
105
+ rescue Errno::EPIPE=> exception
106
+ return oops :ended, exception, "conn_broken_pipe"
107
+
108
+ rescue Errno::ENETUNREACH => exception
109
+ return oops :ended, exception, "conn_unreachable"
110
+
111
+ rescue Errno::ECONNRESET => exception
112
+ return oops :ended, exception, "conn_reset_by_peer"
113
+
114
+ rescue Errno::ENOTCONN => exception
115
+ return oops :ended, exception, "conn_failed"
116
+
117
+ rescue Errno::ECONNREFUSED => exception
118
+ return oops :ended, exception, "conn_refused"
119
+
120
+ rescue Timeout::Error, Errno::ETIMEDOUT, Net::ReadTimeout => exception
121
+ return oops :ended, exception, "conn_timed_out"
122
+
123
+ rescue OpenSSL::SSL::SSLError => exception
124
+ return oops :ended, exception, "conn_openssl_error"
125
+
126
+ rescue Net::FTPReplyError => exception
127
+ return oops :ended, exception, "ftp_reply_error"
128
+
129
+ rescue Net::FTPTempError => exception
130
+ return oops :ended, exception, "ftp_temp_error"
131
+
132
+ rescue Net::FTPPermError => exception
133
+ return oops :ended, exception, "ftp_perm_error"
134
+
135
+ rescue Net::FTPProtoError => exception
136
+ return oops :ended, exception, "ftp_proto_error"
137
+
138
+ rescue Net::FTPError => exception
139
+ return oops :ended, exception, "ftp_error"
140
+
141
+ rescue Net::SFTP::StatusException => exception
142
+ return oops :ended, exception, "sftp_exception"
143
+
144
+ rescue Net::SSH::HostKeyMismatch => exception
145
+ return oops :ended, exception, "sftp_key_mismatch"
146
+
147
+ rescue Net::SSH::AuthenticationFailed => exception
148
+ return oops :ended, exception, "sftp_auth_failed"
149
+
150
+ rescue Errno::EMFILE => exception
151
+ return oops :ended, exception, "too_many_open_files"
152
+
153
+ rescue Errno::EINVAL => exception
154
+ return oops :ended, exception, "invalid_argument", true
155
+
156
+ # rescue Encoding::UndefinedConversionError => exception
157
+ # return oops :ended, exception, "encoding_error", true
158
+
159
+ rescue RestFtpDaemon::SourceNotFound => exception
160
+ return oops :ended, exception
161
+
162
+ rescue RestFtpDaemon::TargetFileExists => exception
163
+ return oops :ended, exception
164
+
165
+ rescue RestFtpDaemon::TargetDirectoryError => exception
166
+ return oops :ended, exception
167
+
168
+ rescue RestFtpDaemon::TargetPermissionError => exception
169
+ return oops :ended, exception
170
+
171
+ rescue RestFtpDaemon::AssertionFailed => exception
172
+ return oops :ended, exception
173
+ end
174
+
175
+ def after
176
+ # Close FTP connexion and free up memory
177
+ log_info "JobTransfer.after"
178
+ @remote.close
179
+
180
+ # Free-up remote object
181
+ @remote = nil
182
+
183
+ # Update job status
184
+ set_status JOB_STATUS_DISCONNECTING
185
+ @finished_at = Time.now
186
+
187
+ # Update counters
188
+ RestFtpDaemon::Counters.instance.increment :jobs, :finished
189
+ RestFtpDaemon::Counters.instance.add :data, :transferred, @transfer_total
190
+ end
191
+
192
+ def remote_push source, target
193
+ # Method assertions
194
+ raise RestFtpDaemon::AssertionFailed, "remote_push/remote" if @remote.nil?
195
+ raise RestFtpDaemon::AssertionFailed, "remote_push/source" if source.nil?
196
+ raise RestFtpDaemon::AssertionFailed, "remote_push/target" if target.nil?
197
+
198
+ # Use source filename if target path provided none (typically with multiple sources)
199
+ log_info "JobTransfer.remote_push [#{source.name}]: [#{source.full}] > [#{target.full}]"
200
+ set_info :source, :current, source.name
201
+
202
+ # Compute temp target name
203
+ tempname = nil
204
+ if @tempfile
205
+ tempname = "#{target.name}.temp-#{identifier(JOB_TEMPFILE_LEN)}"
206
+ log_debug "JobTransfer.remote_push tempname [#{tempname}]"
207
+ end
208
+
209
+ # Remove any existing version if expected, or test its presence
210
+ if @overwrite
211
+ @remote.remove! target
212
+ elsif size = @remote.present?(target)
213
+ log_debug "JobTransfer.remote_push existing (#{format_bytes size, 'B'})"
214
+ raise RestFtpDaemon::TargetFileExists
215
+ end
216
+
217
+ # Start transfer
218
+ transfer_started_at = Time.now
219
+ @progress_at = 0
220
+ @notified_at = transfer_started_at
221
+
222
+ # Start the transfer, update job status after each block transfer
223
+ set_status JOB_STATUS_UPLOADING
224
+ @remote.push source, target, tempname do |transferred, name|
225
+ # Update transfer statistics
226
+ progress transferred, name
227
+
228
+ # Touch my worker status
229
+ touch_job
230
+ end
231
+
232
+ # Compute final bitrate
233
+ global_transfer_bitrate = get_bitrate @transfer_total, (Time.now - transfer_started_at)
234
+ set_info :work, :bitrate, global_transfer_bitrate.round(0)
235
+
236
+ # Done
237
+ set_info :source, :current, nil
238
+ end
239
+
240
+ def progress transferred, name = ""
241
+
242
+
243
+ # What's current time ?
244
+ now = Time.now
245
+ notify_after = @config[:notify_after]
246
+
247
+ # Update counters
248
+ @transfer_sent += transferred
249
+ set_info :work, :sent, @transfer_sent
250
+
251
+ # Update job info
252
+ percent0 = (100.0 * @transfer_sent / @transfer_total).round(0)
253
+ set_info :work, :progress, percent0
254
+
255
+ # Update job status after each NOTIFY_UPADE_STATUS
256
+ progressed_ago = (now.to_f - @progress_at.to_f)
257
+ if (!JOB_UPDATE_INTERVAL.to_f.zero?) && (progressed_ago > JOB_UPDATE_INTERVAL.to_f)
258
+ @current_bitrate = running_bitrate @transfer_sent
259
+ set_info :work, :bitrate, @current_bitrate.round(0)
260
+
261
+ # Log progress
262
+ stack = []
263
+ stack << "#{percent0} %"
264
+ stack << (format_bytes @transfer_sent, "B")
265
+ stack << (format_bytes @transfer_total, "B")
266
+ stack << (format_bytes @current_bitrate.round(0), "bps")
267
+ stack2 = stack.map { |txt| ("%#{LOG_PIPE_LEN.to_i}s" % txt) }.join("\t")
268
+ log_debug "progress #{stack2} \t#{name}"
269
+
270
+ # Remember when we last did it
271
+ @progress_at = now
272
+ end
273
+
274
+ # Notify if requested
275
+ notified_ago = (now.to_f - @notified_at.to_f)
276
+ if (!notify_after.nil?) && (notified_ago > notify_after)
277
+ # Prepare and send notification
278
+ notif_status = {
279
+ progress: percent0,
280
+ transfer_sent: @transfer_sent,
281
+ transfer_total: @transfer_total,
282
+ transfer_bitrate: @current_bitrate.round(0),
283
+ }
284
+ client_notify :progress, status: notif_status
285
+
286
+ # Remember when we last did it
287
+ @notified_at = now
288
+ end
289
+ end
290
+
291
+ private
292
+
293
+ def get_bitrate delta_data, delta_time
294
+ return nil if delta_time.nil? || delta_time.zero?
295
+ 8 * delta_data.to_f.to_f / delta_time
296
+ end
297
+
298
+ def running_bitrate current_data
299
+ return if @last_time.nil?
300
+
301
+ # Compute deltas
302
+ @last_data ||= 0
303
+ delta_data = current_data - @last_data
304
+ delta_time = Time.now - @last_time
305
+
306
+ # Update counters
307
+ @last_time = Time.now
308
+ @last_data = current_data
309
+
310
+ # Return bitrate
311
+ get_bitrate delta_data, delta_time
312
+ end
313
+ end
314
+
315
+ # NewRelic instrumentation
316
+ # add_transaction_tracer :prepare, category: :task
317
+ # add_transaction_tracer :run, category: :task
318
+
319
+ end
@@ -0,0 +1,107 @@
1
+ # FIXME: handle overwrite
2
+ # FIXME: progress over multiple files
3
+ # FIXME: open movie files to guess total runtime
4
+ # FIXME: analyze media files at prepare
5
+
6
+ require 'streamio-ffmpeg'
7
+
8
+ module RestFtpDaemon
9
+ class JobVideo < Job
10
+
11
+ # Process job
12
+ def before
13
+ log_info "JobVideo.before source_loc.path: #{@source_loc.path}"
14
+ log_info "JobVideo.before target_loc.path: #{@target_loc.path}"
15
+
16
+ # Ensure source and target are FILE
17
+ raise RestFtpDaemon::SourceNotSupported, @source_loc.scheme unless source_uri.is_a? URI::FILE
18
+ raise RestFtpDaemon::TargetNotSupported, @target.scheme unless target_uri.is_a? URI::FILE
19
+ end
20
+
21
+ def work
22
+ # Guess source files from disk
23
+ set_status JOB_STATUS_TRANSFORMING
24
+ sources = scan_local_paths @source_loc.path
25
+ raise RestFtpDaemon::SourceNotFound if sources.empty?
26
+
27
+ # Add the source file name if none found in the target path
28
+ target_final = @target_loc.clone
29
+ target_final.name = @source_loc.name unless target_final.name
30
+ log_info "JobVideo.work target_final.path [#{target_final.path}]"
31
+
32
+ # Ensure target directory exists
33
+ log_info "JobVideo.work mkdir_p [#{@target_loc.dir}]"
34
+ FileUtils.mkdir_p @target_loc.dir
35
+
36
+ # Do the work, for each file
37
+ set_info :source, :current, @source_loc.name
38
+ video_command @source_loc, target_final
39
+
40
+ # Done
41
+ set_info :source, :current, nil
42
+
43
+ rescue FFMPEG::Error => exception
44
+ return oops :ended, exception, "ffmpeg_error"
45
+ end
46
+
47
+ def after
48
+ # Done
49
+ set_info :source, :current, nil
50
+ end
51
+
52
+ protected
53
+
54
+ def video_command source, target
55
+ log_info "JobVideo.video_command [#{source.name}]: [#{source.path}] > [#{target.path}]"
56
+ set_info :source, :current, source.name
57
+
58
+ # Read info about source file
59
+ movie = FFMPEG::Movie.new(source.path)
60
+
61
+ # Build options
62
+ ffmpeg_custom_options = {
63
+ audio_codec: @video_ac,
64
+ video_codec: @video_vc,
65
+ custom: ffmpeg_custom_option_array,
66
+ }
67
+ set_info :work, :ffmpeg_custom_options, ffmpeg_custom_options
68
+
69
+ # Build command
70
+ movie.transcode(target.path, ffmpeg_custom_options) do |ffmpeg_progress|
71
+ set_info :work, :ffmpeg_progress, ffmpeg_progress
72
+
73
+ percent0 = (100.0 * ffmpeg_progress).round(0)
74
+ set_info :work, :progress, percent0
75
+
76
+ log_debug "progress #{ffmpeg_progress}"
77
+ end
78
+ end
79
+
80
+ def ffmpeg_custom_option_array
81
+ # Ensure options ar in the correct format
82
+ return [] unless @video_custom.is_a? Hash
83
+ # video_custom_parts = @video_custom.to_s.scan(/(?:\w|"[^"]*")+/)
84
+
85
+ # Build the final array
86
+ custom_parts = []
87
+ @video_custom.each do |name, value|
88
+ custom_parts << "-#{name}"
89
+ custom_parts << value.to_s
90
+ end
91
+
92
+ # Return this
93
+ return custom_parts
94
+ end
95
+
96
+ end
97
+ end
98
+
99
+
100
+ # require "stringio"
101
+ # def capture_stderr
102
+ # real_stderr, $stderr = $stderr, StringIO.new
103
+ # yield
104
+ # $stderr.string
105
+ # ensure
106
+ # $stderr = real_stderr
107
+ # end