active_encode 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9104a0bde11fcf84284f375301d9c4b2de4a25010e8834a363323b7e8e0bb994
4
- data.tar.gz: d03aa952ac5d8cee3b8668950563e3b4ce4c2fb4cfade4562f69b48918b60e37
3
+ metadata.gz: a55a4e3f05065b6f69afc4a7d097f9c5c15926aada0fdb5915c3b19e09d024a2
4
+ data.tar.gz: 6c3e3b9f78fb430ed77f658a6f2be986dee227b642ce92a0c156998a1d35663c
5
5
  SHA512:
6
- metadata.gz: 6e3280b175e3943a28094f5298fe5c9674317b3d8d4147d83a9d5f215edd9dd422f452f7181fb57f28ebbc7632775c9bc6b91bdd7b72b5abe239c56edc7c3caf
7
- data.tar.gz: '01062903b0008ae7299d1b97c91f26d8a1b8b6f4b69fb14a5722a577046da6b6357b0f8970b150c25735471d23166d1a25e78f216228cfcc87ad4ca257eecf98'
6
+ metadata.gz: 1124cbecacf61ec7fa05a5eaf109599d4957e6865c0bc7e8938dc0a5d09f1912c5c93d49764c60301b7d1de0917b6d1af923b3e322812a7cc7f4b1dacf571257
7
+ data.tar.gz: 69695a33b2af42d9b19f101e2030209c2483d8bed919fc76542593f599a603bcba720341f0826fdd350154ac4feb62733a7a8453e11b935a28016889dc41e79a
data/.circleci/config.yml CHANGED
@@ -65,30 +65,38 @@ workflows:
65
65
  ci:
66
66
  jobs:
67
67
  - bundle_and_test:
68
- name: "ruby3-2_rails7-0"
69
- ruby_version: "3.2.0"
70
- rails_version: "7.0.4.2"
68
+ name: "ruby3-4_rails8-0"
69
+ ruby_version: "3.4.1"
70
+ rails_version: "8.0.1"
71
71
  - bundle_and_test:
72
- name: "ruby3-1_rails7-0"
73
- ruby_version: "3.1.3"
74
- rails_version: "7.0.4.2"
72
+ name: "ruby3-4_rails7-2"
73
+ ruby_version: "3.4.1"
74
+ rails_version: "7.2.2.1"
75
75
  - bundle_and_test:
76
- name: "ruby3-0_rails7-0"
77
- ruby_version: "3.0.5"
78
- rails_version: "7.0.4.2"
76
+ name: "ruby3-4_rails7-1"
77
+ ruby_version: "3.4.1"
78
+ rails_version: "7.1.5.1"
79
79
  - bundle_and_test:
80
- name: "ruby3-0_rails6-1"
81
- ruby_version: "3.0.5"
82
- rails_version: "6.1.7.2"
80
+ name: "ruby3-3_rails8-0"
81
+ ruby_version: "3.3.7"
82
+ rails_version: "8.0.1"
83
83
  - bundle_and_test:
84
- name: "ruby3-0_rails6-0"
85
- ruby_version: "3.0.5"
86
- rails_version: "6.0.6.1"
84
+ name: "ruby3-3_rails7-2"
85
+ ruby_version: "3.3.7"
86
+ rails_version: "7.2.2.1"
87
87
  - bundle_and_test:
88
- name: "ruby2-7_rails6-0"
89
- ruby_version: "2.7.7"
90
- rails_version: "6.0.6.1"
88
+ name: "ruby3-3_rails7-1"
89
+ ruby_version: "3.3.7"
90
+ rails_version: "7.1.5.1"
91
91
  - bundle_and_test:
92
- name: "ruby2-7_rails5-2"
93
- ruby_version: "2.7.7"
94
- rails_version: "5.2.8.1"
92
+ name: "ruby3-2_rails8-0"
93
+ ruby_version: "3.2.7"
94
+ rails_version: "8.0.1"
95
+ - bundle_and_test:
96
+ name: "ruby3-2_rails7-2"
97
+ ruby_version: "3.2.7"
98
+ rails_version: "7.2.2.1"
99
+ - bundle_and_test:
100
+ name: "ruby3-2_rails7-1"
101
+ ruby_version: "3.2.7"
102
+ rails_version: "7.1.5.1"
data/Gemfile CHANGED
@@ -35,6 +35,8 @@ else
35
35
  end
36
36
 
37
37
  case ENV['RAILS_VERSION']
38
+ when /^[56]/, /^7.0/
39
+ gem 'concurrent-ruby', '1.3.4'
38
40
  when /^6.0/
39
41
  gem 'sass-rails', '>= 6'
40
42
  gem 'webpacker', '~> 4.0'
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.add_development_dependency "aws-sdk-cloudwatchevents"
27
27
  spec.add_development_dependency "aws-sdk-cloudwatchlogs"
28
+ spec.add_development_dependency "aws-sdk-core", "<= 3.220.0"
28
29
  spec.add_development_dependency "aws-sdk-elastictranscoder"
29
30
  spec.add_development_dependency "aws-sdk-mediaconvert"
30
31
  spec.add_development_dependency "aws-sdk-s3"
@@ -4,5 +4,17 @@ require 'rails'
4
4
  module ActiveEncode
5
5
  class Engine < ::Rails::Engine
6
6
  isolate_namespace ActiveEncode
7
+
8
+ config.before_configuration do
9
+ # rubocop:disable Style/IfUnlessModifier
10
+ # see https://github.com/fxn/zeitwerk#for_gem
11
+ # Blacklight puts a generator into LOCAL APP lib/generators, so tell
12
+ # zeitwerk to ignore the whole directory? If we're using zeitwerk
13
+ #
14
+ # See: https://github.com/cbeer/engine_cart/issues/117
15
+ if ::Rails.try(:autoloaders).try(:main).respond_to?(:ignore)
16
+ ::Rails.autoloaders.main.ignore(::Rails.root.join('lib', 'generators'))
17
+ end
18
+ end
7
19
  end
8
20
  end
@@ -53,7 +53,11 @@ module ActiveEncode
53
53
  def remove_empty_directories(directories)
54
54
  directories_to_delete = directories.select { |d| Dir.empty?(d) }
55
55
  non_empty_directories = directories - directories_to_delete
56
- directories_to_delete += non_empty_directories.select { |ned| Dir.children(ned) == ["outputs"] && directories_to_delete.include?(File.join(ned, "outputs")) }
56
+ directories_to_delete += non_empty_directories.select do |ned|
57
+ Dir.children(ned).sort == ["outputs", "supplemental_files"] &&
58
+ directories_to_delete.include?(File.join(ned, "outputs")) &&
59
+ directories_to_delete.include?(File.join(ned, "supplemental_files"))
60
+ end
57
61
  FileUtils.rmdir(directories_to_delete) unless directories_to_delete.empty?
58
62
  end
59
63
 
@@ -28,6 +28,8 @@ module ActiveEncode
28
28
 
29
29
  s3_object = FileLocator::S3File.new(input_url).object
30
30
  input_url = URI.parse(s3_object.presigned_url(:get))
31
+ when /^https?\:\/\//
32
+ input_url = URI.parse(input_url)
31
33
  end
32
34
 
33
35
  new_encode = ActiveEncode::Base.new(input_url, options)
@@ -40,6 +42,7 @@ module ActiveEncode
40
42
  # Create a working directory that holds all output files related to the encode
41
43
  FileUtils.mkdir_p working_path("", new_encode.id)
42
44
  FileUtils.mkdir_p working_path("outputs", new_encode.id)
45
+ FileUtils.mkdir_p working_path("supplemental_files", new_encode.id)
43
46
 
44
47
  # Extract technical metadata from input file
45
48
  curl_option = if options && options[:headers]
@@ -89,12 +92,14 @@ module ActiveEncode
89
92
  new_encode.input.duration = fixed_duration(working_path("duration_input_metadata", new_encode.id))
90
93
  end
91
94
 
95
+ subtitle_count = new_encode.input.subtitles.length if new_encode.input.subtitles.present?
96
+
92
97
  new_encode.state = :running
93
98
  new_encode.percent_complete = 1
94
99
  new_encode.errors = []
95
100
 
96
101
  # Run the ffmpeg command and save its pid
97
- command = ffmpeg_command(input_url, new_encode.id, options)
102
+ command = ffmpeg_command(input_url, new_encode.id, options, subtitle_count)
98
103
  # Capture the exit status in a file in order to differentiate warning output in stderr between real process failure
99
104
  exit_status_file = working_path("exit_status.code", new_encode.id)
100
105
  command = "#{command}; echo $? > #{exit_status_file}"
@@ -148,6 +153,7 @@ module ActiveEncode
148
153
  end
149
154
 
150
155
  encode.output = build_outputs encode if encode.completed?
156
+ encode.output += build_supplemental_outputs encode if encode.completed?
151
157
 
152
158
  encode
153
159
  end
@@ -232,7 +238,7 @@ module ActiveEncode
232
238
  Dir["#{File.absolute_path(working_path('outputs', id))}/*"].each do |file_path|
233
239
  output = ActiveEncode::Output.new
234
240
  output.url = "file://#{file_path}"
235
- sanitized_filename = ActiveEncode.sanitize_base encode.input.url
241
+ sanitized_filename = encode.input.url.match?(/^https?\:\/\//) ? ActiveEncode.sanitize_base(URI.parse(encode.input.url)) : ActiveEncode.sanitize_base(encode.input.url)
236
242
  output.label = file_path[/#{Regexp.quote(sanitized_filename)}.*\-(.*?)#{Regexp.quote(File.extname(file_path))}$/, 1]
237
243
  output.id = "#{encode.input.id}-#{output.label}"
238
244
  output.created_at = encode.created_at
@@ -249,17 +255,51 @@ module ActiveEncode
249
255
  outputs
250
256
  end
251
257
 
252
- def ffmpeg_command(input_url, id, opts)
258
+ def build_supplemental_outputs(encode)
259
+ id = encode.id
260
+ files = []
261
+ Dir["#{File.absolute_path(working_path('supplemental_files', id))}/*"].each_with_index do |file_path, index|
262
+ file = ActiveEncode::Output.new
263
+ file.url = "file://#{file_path}"
264
+ file.id = "#{encode.input.id}-#{File.basename(file_path)}"
265
+ file.created_at = encode.created_at
266
+ file.updated_at = File.mtime file_path
267
+ file.label = encode.input.subtitles[index][:label]
268
+ file.language = encode.input.subtitles[index][:language]
269
+ file.format = 'vtt'
270
+
271
+ files << file
272
+ end
273
+
274
+ files
275
+ end
276
+
277
+ def ffmpeg_command(input_url, id, opts, subtitle_count = nil)
278
+ sanitized_filename = ActiveEncode.sanitize_base input_url
253
279
  output_opt = opts[:outputs].collect do |output|
254
- sanitized_filename = ActiveEncode.sanitize_base input_url
255
280
  file_name = "outputs/#{sanitized_filename}-#{output[:label]}.#{output[:extension]}"
256
281
  " #{output[:ffmpeg_opt]} #{working_path(file_name, id)}"
257
282
  end.join(" ")
283
+
284
+ supplemental_file_opt = if opts[:extract_subtitles] && subtitle_count.present?
285
+ caption_extraction_options(sanitized_filename, subtitle_count, id)
286
+ end
287
+
258
288
  header_opt = Array(opts[:headers]).map do |k, v|
259
289
  "#{k}: #{v}\r\n"
260
290
  end.join
261
291
  header_opt = "-headers '#{header_opt}'" if header_opt.present?
262
- "#{FFMPEG_PATH} #{header_opt} -y -loglevel level+fatal -progress #{working_path('progress', id)} -i \"#{input_url}\" #{output_opt}"
292
+ "#{FFMPEG_PATH} #{header_opt} -y -loglevel level+fatal -progress #{working_path('progress', id)} -i \"#{input_url}\" #{supplemental_file_opt} #{output_opt}"
293
+ end
294
+
295
+ def caption_extraction_options(filename, count, id)
296
+ opts = ""
297
+ (0..count - 1).each do |i|
298
+ subtitle_filename = "supplemental_files/#{filename}-caption#{i}.vtt"
299
+ opts += " -map 0:s:#{i} -c:s webvtt #{working_path(subtitle_filename, id)}"
300
+ end
301
+
302
+ opts
263
303
  end
264
304
 
265
305
  def get_pid(id)
@@ -321,13 +361,24 @@ module ActiveEncode
321
361
  audio_codec: get_xpath_text(doc, '//track[@type="Audio"]/CodecID/text()', :to_s),
322
362
  audio_bitrate: get_xpath_text(doc, '//track[@type="Audio"]/BitRate/text()', :to_i),
323
363
  video_codec: get_xpath_text(doc, '//track[@type="Video"]/CodecID/text()', :to_s),
324
- video_bitrate: get_xpath_text(doc, '//track[@type="Video"]/BitRate/text()', :to_i) }
364
+ video_bitrate: get_xpath_text(doc, '//track[@type="Video"]/BitRate/text()', :to_i),
365
+ subtitles: get_subtitle_tech_metadata(doc).compact }
325
366
  end
326
367
 
327
368
  def get_xpath_text(doc, xpath, cast_method)
328
369
  doc.xpath(xpath).first&.text&.send(cast_method)
329
370
  end
330
371
 
372
+ def get_subtitle_tech_metadata(doc)
373
+ doc.xpath("//track[@type='Text' and Format='Timed Text']").collect do |track|
374
+ {
375
+ format: get_xpath_text(track, "./CodecID/text()", :to_s),
376
+ label: get_xpath_text(track, "./Title/text()", :to_s),
377
+ language: get_xpath_text(track, "./Language/text()", :to_s)
378
+ }
379
+ end
380
+ end
381
+
331
382
  def fixed_duration(path)
332
383
  get_tech_metadata(path)[:duration]
333
384
  end
@@ -10,7 +10,8 @@ module ActiveEncode
10
10
  filepath = input_url.is_a?(URI::HTTP) ? input_url.path : input_url
11
11
  # Replace special characters with underscores and remove excess periods.
12
12
  # This removes the extension before processing so it is safe to delete all detected periods.
13
- File.basename(filepath, File.extname(filepath)).gsub(/[^0-9A-Za-z.\-\/]/, '_').delete('.')
13
+ # This explicitly handles percent encoded spaces.
14
+ File.basename(filepath, File.extname(filepath)).gsub(/%20/, "_").gsub(/[^0-9A-Za-z.\-\/]/, '_').delete('.')
14
15
  end
15
16
 
16
17
  def sanitize_filename(input_url)
@@ -24,7 +25,7 @@ module ActiveEncode
24
25
  when /^file\:\/\/\//
25
26
  input_url.to_s.gsub(/file:\/\//, '')
26
27
  when /^s3\:\/\//
27
- input_url.to_s.gsub(/#{Addressable::URI.parse(input_url).normalized_site}/, '')
28
+ input_url.to_s.gsub(/#{Addressable::URI.parse(input_url).site}/, '')
28
29
  when /^https?:\/\//
29
30
  input_url
30
31
  end
@@ -22,11 +22,17 @@ module ActiveEncode
22
22
  attr_accessor :video_codec
23
23
  attr_accessor :audio_bitrate
24
24
  attr_accessor :video_bitrate
25
+
26
+ # Array of hashes
27
+ attr_accessor :subtitles
28
+ attr_accessor :format
29
+ attr_accessor :language
25
30
  end
26
31
 
27
32
  def assign_tech_metadata(metadata)
28
33
  [:width, :height, :frame_rate, :duration, :file_size, :checksum,
29
- :audio_codec, :video_codec, :audio_bitrate, :video_bitrate].each do |field|
34
+ :audio_codec, :video_codec, :audio_bitrate, :video_bitrate, :subtitles,
35
+ :format, :language].each do |field|
30
36
  send("#{field}=", metadata[field]) if metadata.key?(field)
31
37
  end
32
38
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ActiveEncode
3
- VERSION = '1.2.2'
3
+ VERSION = '1.3.0'
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_encode
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Klein, Chris Colvard, Phuong Dinh
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-14 00:00:00.000000000 Z
11
+ date: 2025-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: aws-sdk-core
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "<="
74
+ - !ruby/object:Gem::Version
75
+ version: 3.220.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "<="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.220.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: aws-sdk-elastictranscoder
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -303,7 +317,7 @@ licenses:
303
317
  - Apache-2.0
304
318
  metadata:
305
319
  rubygems_mfa_required: 'true'
306
- post_install_message:
320
+ post_install_message:
307
321
  rdoc_options: []
308
322
  require_paths:
309
323
  - lib
@@ -318,8 +332,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
318
332
  - !ruby/object:Gem::Version
319
333
  version: '0'
320
334
  requirements: []
321
- rubygems_version: 3.5.6
322
- signing_key:
335
+ rubygems_version: 3.5.3
336
+ signing_key:
323
337
  specification_version: 4
324
338
  summary: Declare encode job classes that can be run by a variety of encoding services
325
339
  test_files: []