active_encode 1.2.3 → 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: aa6172de1470349b8dd8c2422c51049402168ca45eba77aa62ab6b7d9637f4ae
4
- data.tar.gz: 999ec2e97a500f3bb258db4d4fbfa6e5fa4132dfefb4a82da1667eefa6875cff
3
+ metadata.gz: a55a4e3f05065b6f69afc4a7d097f9c5c15926aada0fdb5915c3b19e09d024a2
4
+ data.tar.gz: 6c3e3b9f78fb430ed77f658a6f2be986dee227b642ce92a0c156998a1d35663c
5
5
  SHA512:
6
- metadata.gz: a78df8af781cf5f4f198a9b4d9e994108294fd0248725d9b6553a96fb829de41afc7a7ccc69cadacafe8d6cb4ac809cb4331c2331c968b7429b7306e19cf3f51
7
- data.tar.gz: 358557321351790685a68c4604d6ca43592c928523085e96f5353603ffe8ad0bfc70b6bcde119b2275cb3d3bbec7b6be78c74b84c3f65900dcd2f0e8f3c38421
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
 
@@ -42,6 +42,7 @@ module ActiveEncode
42
42
  # Create a working directory that holds all output files related to the encode
43
43
  FileUtils.mkdir_p working_path("", new_encode.id)
44
44
  FileUtils.mkdir_p working_path("outputs", new_encode.id)
45
+ FileUtils.mkdir_p working_path("supplemental_files", new_encode.id)
45
46
 
46
47
  # Extract technical metadata from input file
47
48
  curl_option = if options && options[:headers]
@@ -91,12 +92,14 @@ module ActiveEncode
91
92
  new_encode.input.duration = fixed_duration(working_path("duration_input_metadata", new_encode.id))
92
93
  end
93
94
 
95
+ subtitle_count = new_encode.input.subtitles.length if new_encode.input.subtitles.present?
96
+
94
97
  new_encode.state = :running
95
98
  new_encode.percent_complete = 1
96
99
  new_encode.errors = []
97
100
 
98
101
  # Run the ffmpeg command and save its pid
99
- command = ffmpeg_command(input_url, new_encode.id, options)
102
+ command = ffmpeg_command(input_url, new_encode.id, options, subtitle_count)
100
103
  # Capture the exit status in a file in order to differentiate warning output in stderr between real process failure
101
104
  exit_status_file = working_path("exit_status.code", new_encode.id)
102
105
  command = "#{command}; echo $? > #{exit_status_file}"
@@ -150,6 +153,7 @@ module ActiveEncode
150
153
  end
151
154
 
152
155
  encode.output = build_outputs encode if encode.completed?
156
+ encode.output += build_supplemental_outputs encode if encode.completed?
153
157
 
154
158
  encode
155
159
  end
@@ -251,17 +255,51 @@ module ActiveEncode
251
255
  outputs
252
256
  end
253
257
 
254
- 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
255
279
  output_opt = opts[:outputs].collect do |output|
256
- sanitized_filename = ActiveEncode.sanitize_base input_url
257
280
  file_name = "outputs/#{sanitized_filename}-#{output[:label]}.#{output[:extension]}"
258
281
  " #{output[:ffmpeg_opt]} #{working_path(file_name, id)}"
259
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
+
260
288
  header_opt = Array(opts[:headers]).map do |k, v|
261
289
  "#{k}: #{v}\r\n"
262
290
  end.join
263
291
  header_opt = "-headers '#{header_opt}'" if header_opt.present?
264
- "#{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
265
303
  end
266
304
 
267
305
  def get_pid(id)
@@ -323,13 +361,24 @@ module ActiveEncode
323
361
  audio_codec: get_xpath_text(doc, '//track[@type="Audio"]/CodecID/text()', :to_s),
324
362
  audio_bitrate: get_xpath_text(doc, '//track[@type="Audio"]/BitRate/text()', :to_i),
325
363
  video_codec: get_xpath_text(doc, '//track[@type="Video"]/CodecID/text()', :to_s),
326
- 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 }
327
366
  end
328
367
 
329
368
  def get_xpath_text(doc, xpath, cast_method)
330
369
  doc.xpath(xpath).first&.text&.send(cast_method)
331
370
  end
332
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
+
333
382
  def fixed_duration(path)
334
383
  get_tech_metadata(path)[:duration]
335
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.3'
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.3
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Klein, Chris Colvard, Phuong Dinh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-13 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
@@ -318,7 +332,7 @@ 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.16
335
+ rubygems_version: 3.5.3
322
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