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 +4 -4
- data/.circleci/config.yml +29 -21
- data/Gemfile +2 -0
- data/active_encode.gemspec +1 -0
- data/lib/active_encode/engine.rb +12 -0
- data/lib/active_encode/engine_adapters/ffmpeg_adapter/cleaner.rb +5 -1
- data/lib/active_encode/engine_adapters/ffmpeg_adapter.rb +54 -5
- data/lib/active_encode/filename_sanitizer.rb +3 -2
- data/lib/active_encode/technical_metadata.rb +7 -1
- data/lib/active_encode/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a55a4e3f05065b6f69afc4a7d097f9c5c15926aada0fdb5915c3b19e09d024a2
|
4
|
+
data.tar.gz: 6c3e3b9f78fb430ed77f658a6f2be986dee227b642ce92a0c156998a1d35663c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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-
|
69
|
-
ruby_version: "3.
|
70
|
-
rails_version: "
|
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-
|
73
|
-
ruby_version: "3.1
|
74
|
-
rails_version: "7.
|
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-
|
77
|
-
ruby_version: "3.
|
78
|
-
rails_version: "7.
|
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-
|
81
|
-
ruby_version: "3.
|
82
|
-
rails_version: "
|
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-
|
85
|
-
ruby_version: "3.
|
86
|
-
rails_version: "
|
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: "
|
89
|
-
ruby_version: "
|
90
|
-
rails_version: "
|
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: "
|
93
|
-
ruby_version: "2.7
|
94
|
-
rails_version: "
|
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
data/active_encode.gemspec
CHANGED
@@ -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"
|
data/lib/active_encode/engine.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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).
|
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
|
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
|
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.
|
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:
|
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.
|
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
|