active_encode 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +1 -0
- data/lib/active_encode/engine_adapters/ffmpeg_adapter/cleaner.rb +71 -0
- data/lib/active_encode/engine_adapters/ffmpeg_adapter.rb +53 -10
- data/lib/active_encode/engine_adapters/pass_through_adapter.rb +51 -11
- data/lib/active_encode/filename_sanitizer.rb +3 -1
- data/lib/active_encode/version.rb +1 -1
- data/spec/fixtures/file_without_metadata.low.webm +0 -0
- data/spec/fixtures/file_without_metadata.mp4 +0 -0
- data/spec/fixtures/file_without_metadata.webm +0 -0
- data/spec/integration/ffmpeg_adapter_spec.rb +160 -0
- data/spec/integration/pass_through_adapter_spec.rb +48 -0
- data/spec/rails_helper.rb +2 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/units/filename_sanitizer_spec.rb +20 -0
- metadata +15 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8c41200059443abc28d64ef9748086b36a4a1c97537138da24bd1b5016e902d
|
4
|
+
data.tar.gz: 3fec8ac243d8e7ff3c96abaa26b08c23e5c409337c2f63463f465096e29e7cba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cae3d56d8c068e3cb73e01b4d15d7ba99d305f6c7bd09e7de51def230d247fa3b423d8b32674cadbf66ad7845ce235399b6b53fa068ef305b65bfad1c829f85
|
7
|
+
data.tar.gz: 7265200be814de1969887dd1275a8e89da3e0befe6a92542800c42119f93307f0bf7584a70dcfe8127ef11c7a7a76f5f14fab7b9a5f47ef9408cb474f64d2039
|
data/.rubocop_todo.yml
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ActiveEncode
|
3
|
+
module EngineAdapters
|
4
|
+
class FfmpegAdapter
|
5
|
+
module Cleaner
|
6
|
+
# This method is to clean up files leftover from the ffmpeg encode process.
|
7
|
+
# File names for the pass_through adapter are the same, so this will clean up
|
8
|
+
# pass_through encodes as well.
|
9
|
+
def remove_old_files!(options = {})
|
10
|
+
default_options = {
|
11
|
+
older_than: 2.weeks,
|
12
|
+
no_outputs: ['input_metadata', 'duration_input_metadata', 'error.log', 'exit_status.code', 'progress', 'completed', 'pid', 'output_metadata-*'],
|
13
|
+
outputs: false,
|
14
|
+
all: false
|
15
|
+
}
|
16
|
+
options.reverse_merge!(default_options)
|
17
|
+
|
18
|
+
if options[:all]
|
19
|
+
files = build_file_list(WORK_DIR, "*")
|
20
|
+
directories = remove_files(files, options[:older_than])
|
21
|
+
remove_empty_directories(directories)
|
22
|
+
elsif options[:outputs]
|
23
|
+
output_directories = build_file_list(WORK_DIR, "outputs")
|
24
|
+
remove_child_files(output_directories, options[:older_than])
|
25
|
+
remove_empty_directories(output_directories)
|
26
|
+
else
|
27
|
+
files = []
|
28
|
+
options[:no_outputs].each { |fn| files += build_file_list(WORK_DIR, fn) }
|
29
|
+
remove_files(files, options[:older_than])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_file_list(directory, filename)
|
34
|
+
file_path = File.join(directory, "**", filename)
|
35
|
+
# Some of the files generated by the ffmpeg encode seem to be hidden files.
|
36
|
+
# This uses File::FNM_DOTMATCH to include them in the results.
|
37
|
+
Dir.glob(file_path, File::FNM_DOTMATCH)
|
38
|
+
end
|
39
|
+
|
40
|
+
def file_check(path, older_than)
|
41
|
+
File.mtime(path) < DateTime.now - older_than && File.file?(path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove_files(files, older_than)
|
45
|
+
files_to_delete = files.select { |f| file_check(f, older_than) }
|
46
|
+
FileUtils.rm(files_to_delete) unless files_to_delete.empty?
|
47
|
+
|
48
|
+
# Return a list of any directories that were included in the files list for further processing.
|
49
|
+
# The files list can include directories such as "/tmp/.." which should not be included in directory list.
|
50
|
+
files.select { |f| File.directory?(f) unless f.end_with?(".") }
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove_empty_directories(directories)
|
54
|
+
directories_to_delete = directories.select { |d| Dir.empty?(d) }
|
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")) }
|
57
|
+
FileUtils.rmdir(directories_to_delete) unless directories_to_delete.empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
def remove_child_files(directories, older_than)
|
61
|
+
files_to_delete = []
|
62
|
+
directories.each do |d|
|
63
|
+
files = Dir.children(d).select { |ch| file_check(File.join(d, ch), older_than) }
|
64
|
+
files_to_delete += files.collect { |f| File.join(d, f) }
|
65
|
+
end
|
66
|
+
FileUtils.rm(files_to_delete) unless files_to_delete.empty?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,12 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'English'
|
2
3
|
require 'fileutils'
|
3
4
|
require 'nokogiri'
|
4
5
|
require 'shellwords'
|
5
6
|
require 'addressable/uri'
|
7
|
+
require 'active_encode/engine_adapters/ffmpeg_adapter/cleaner'
|
6
8
|
|
7
9
|
module ActiveEncode
|
8
10
|
module EngineAdapters
|
9
11
|
class FfmpegAdapter
|
12
|
+
extend ActiveEncode::EngineAdapters::FfmpegAdapter::Cleaner
|
13
|
+
|
10
14
|
WORK_DIR = ENV["ENCODE_WORK_DIR"] || "encodes" # Should read from config
|
11
15
|
MEDIAINFO_PATH = ENV["MEDIAINFO_PATH"] || "mediainfo"
|
12
16
|
FFMPEG_PATH = ENV["FFMPEG_PATH"] || "ffmpeg"
|
@@ -51,18 +55,38 @@ module ActiveEncode
|
|
51
55
|
|
52
56
|
new_encode.input = build_input new_encode
|
53
57
|
|
54
|
-
if
|
55
|
-
|
56
|
-
new_encode
|
58
|
+
# Log error if file is empty or inaccessible
|
59
|
+
if new_encode.input.duration.blank? && new_encode.input.file_size.blank?
|
60
|
+
file_error(new_encode, input_url)
|
61
|
+
return new_encode
|
62
|
+
# If file is not empty, try copying file to generate missing metadata
|
63
|
+
elsif new_encode.input.duration.blank? && new_encode.input.file_size.present?
|
64
|
+
|
65
|
+
# This regex removes the query string from URIs. This is necessary to
|
66
|
+
# properly process files originating from S3 or similar providers.
|
67
|
+
filepath = clean_url.to_s.gsub(/\?.*/, '')
|
68
|
+
copy_url = filepath.gsub(filepath, "#{File.basename(filepath, File.extname(filepath))}_temp#{File.extname(filepath)}")
|
69
|
+
copy_path = working_path(copy_url, new_encode.id)
|
70
|
+
|
71
|
+
# -map 0 sets ffmpeg to copy all available streams.
|
72
|
+
# -c copy sets ffmpeg to copy all codecs
|
73
|
+
# -y automatically overwrites the temp file if one already exists
|
74
|
+
`#{FFMPEG_PATH} -loglevel level+fatal -i \"#{input_url}\" -map 0 -c copy -y \"#{copy_path}\"`
|
75
|
+
|
76
|
+
# If ffmpeg copy fails, log error because file is either not a media file
|
77
|
+
# or the file extension does not match the codecs used to encode the file
|
78
|
+
unless $CHILD_STATUS.success?
|
79
|
+
file_error(new_encode, input_url)
|
80
|
+
return new_encode
|
81
|
+
end
|
57
82
|
|
58
|
-
|
59
|
-
|
60
|
-
else
|
61
|
-
["Error inspecting input: #{input_url}"]
|
62
|
-
end
|
83
|
+
# Write the mediainfo output to a separate file to preserve metadata from original file
|
84
|
+
`#{MEDIAINFO_PATH} #{curl_option} --Output=XML --LogFile=#{working_path("duration_input_metadata", new_encode.id)} "#{copy_path}"`
|
63
85
|
|
64
|
-
|
65
|
-
|
86
|
+
File.delete(copy_path)
|
87
|
+
|
88
|
+
# Assign duration to the encode created for the original file.
|
89
|
+
new_encode.input.duration = fixed_duration(working_path("duration_input_metadata", new_encode.id))
|
66
90
|
end
|
67
91
|
|
68
92
|
new_encode.state = :running
|
@@ -99,6 +123,7 @@ module ActiveEncode
|
|
99
123
|
encode.output = []
|
100
124
|
encode.created_at, encode.updated_at = get_times encode.id
|
101
125
|
encode.input = build_input encode
|
126
|
+
encode.input.duration ||= fixed_duration(working_path("duration_input_metadata", encode.id)) if File.exist?(working_path("duration_input_metadata", encode.id))
|
102
127
|
encode.percent_complete = calculate_percent_complete encode
|
103
128
|
|
104
129
|
pid = get_pid(id)
|
@@ -302,6 +327,24 @@ module ActiveEncode
|
|
302
327
|
def get_xpath_text(doc, xpath, cast_method)
|
303
328
|
doc.xpath(xpath).first&.text&.send(cast_method)
|
304
329
|
end
|
330
|
+
|
331
|
+
def fixed_duration(path)
|
332
|
+
get_tech_metadata(path)[:duration]
|
333
|
+
end
|
334
|
+
|
335
|
+
def file_error(new_encode, input_url)
|
336
|
+
new_encode.state = :failed
|
337
|
+
new_encode.percent_complete = 1
|
338
|
+
|
339
|
+
new_encode.errors = if new_encode.input.file_size.blank?
|
340
|
+
["#{input_url} does not exist or is not accessible"]
|
341
|
+
else
|
342
|
+
["Error inspecting input: #{input_url}"]
|
343
|
+
end
|
344
|
+
|
345
|
+
write_errors new_encode
|
346
|
+
new_encode
|
347
|
+
end
|
305
348
|
end
|
306
349
|
end
|
307
350
|
end
|
@@ -15,6 +15,7 @@ module ActiveEncode
|
|
15
15
|
class PassThroughAdapter
|
16
16
|
WORK_DIR = ENV["ENCODE_WORK_DIR"] || "encodes" # Should read from config
|
17
17
|
MEDIAINFO_PATH = ENV["MEDIAINFO_PATH"] || "mediainfo"
|
18
|
+
FFMPEG_PATH = ENV["FFMPEG_PATH"] || "ffmpeg"
|
18
19
|
|
19
20
|
def create(input_url, options = {})
|
20
21
|
# Decode file uris for ffmpeg (mediainfo works either way)
|
@@ -36,18 +37,38 @@ module ActiveEncode
|
|
36
37
|
new_encode.input.id = new_encode.id
|
37
38
|
new_encode.created_at, new_encode.updated_at = get_times new_encode.id
|
38
39
|
|
39
|
-
if
|
40
|
-
|
41
|
-
new_encode
|
42
|
-
|
43
|
-
new_encode.errors = if new_encode.input.file_size.blank?
|
44
|
-
["#{input_url} does not exist or is not accessible"]
|
45
|
-
else
|
46
|
-
["Error inspecting input: #{input_url}"]
|
47
|
-
end
|
48
|
-
|
49
|
-
write_errors new_encode
|
40
|
+
# Log error if file is empty or inaccessible
|
41
|
+
if new_encode.input.duration.blank? && new_encode.input.file_size.blank?
|
42
|
+
file_error(new_encode, input_url)
|
50
43
|
return new_encode
|
44
|
+
# If file is not empty, try copying file to generate missing metadata
|
45
|
+
elsif new_encode.input.duration.blank? && new_encode.input.file_size.present?
|
46
|
+
|
47
|
+
# This regex removes the query string from URIs. This is necessary to
|
48
|
+
# properly process files originating from S3 or similar providers.
|
49
|
+
filepath = input_url.to_s.gsub(/\?.*/, '')
|
50
|
+
copy_url = filepath.gsub(filepath, "#{File.basename(filepath, File.extname(filepath))}_temp#{File.extname(input_url)}")
|
51
|
+
copy_path = working_path(copy_url, new_encode.id)
|
52
|
+
|
53
|
+
# -map 0 sets ffmpeg to copy all available streams.
|
54
|
+
# -c copy sets ffmpeg to copy all codecs
|
55
|
+
# -y automatically overwrites the temp file if one already exists
|
56
|
+
`#{FFMPEG_PATH} -loglevel level+fatal -i \"#{input_url}\" -map 0 -c copy -y \"#{copy_path}\"`
|
57
|
+
|
58
|
+
# If ffmpeg copy fails, log error because file is either not a media file
|
59
|
+
# or the file extension does not match the codecs used to encode the file
|
60
|
+
unless $CHILD_STATUS.success?
|
61
|
+
file_error(new_encode, input_url)
|
62
|
+
return new_encode
|
63
|
+
end
|
64
|
+
|
65
|
+
# Write the mediainfo output to a separate file to preserve metadata from original file
|
66
|
+
`#{MEDIAINFO_PATH} --Output=XML --LogFile=#{working_path("duration_input_metadata", new_encode.id)} \"#{copy_path}\"`
|
67
|
+
|
68
|
+
File.delete(copy_path)
|
69
|
+
|
70
|
+
# Assign duration to the encode created for the original file.
|
71
|
+
new_encode.input.duration = fixed_duration(working_path("duration_input_metadata", new_encode.id))
|
51
72
|
end
|
52
73
|
|
53
74
|
# For saving filename to label map used to find the label when building outputs
|
@@ -86,6 +107,7 @@ module ActiveEncode
|
|
86
107
|
encode.created_at, encode.updated_at = get_times encode.id
|
87
108
|
encode.input = build_input encode
|
88
109
|
encode.input.id = encode.id
|
110
|
+
encode.input.duration ||= fixed_duration(working_path("duration_input_metadata", encode.id)) if File.exist?(working_path("duration_input_metadata", encode.id))
|
89
111
|
encode.output = []
|
90
112
|
encode.current_operations = []
|
91
113
|
|
@@ -231,6 +253,24 @@ module ActiveEncode
|
|
231
253
|
def get_xpath_text(doc, xpath, cast_method)
|
232
254
|
doc.xpath(xpath).first&.text&.send(cast_method)
|
233
255
|
end
|
256
|
+
|
257
|
+
def fixed_duration(path)
|
258
|
+
get_tech_metadata(path)[:duration]
|
259
|
+
end
|
260
|
+
|
261
|
+
def file_error(new_encode, input_url)
|
262
|
+
new_encode.state = :failed
|
263
|
+
new_encode.percent_complete = 1
|
264
|
+
|
265
|
+
new_encode.errors = if new_encode.input.file_size.blank?
|
266
|
+
["#{input_url} does not exist or is not accessible"]
|
267
|
+
else
|
268
|
+
["Error inspecting input: #{input_url}"]
|
269
|
+
end
|
270
|
+
|
271
|
+
write_errors new_encode
|
272
|
+
new_encode
|
273
|
+
end
|
234
274
|
end
|
235
275
|
end
|
236
276
|
end
|
@@ -24,7 +24,9 @@ module ActiveEncode
|
|
24
24
|
when /^file\:\/\/\//
|
25
25
|
input_url.to_s.gsub(/file:\/\//, '')
|
26
26
|
when /^s3\:\/\//
|
27
|
-
input_url.to_s.gsub(/#{input_url.normalized_site}/, '')
|
27
|
+
input_url.to_s.gsub(/#{Addressable::URI.parse(input_url).normalized_site}/, '')
|
28
|
+
when /^https?:\/\//
|
29
|
+
input_url
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -112,6 +112,62 @@ describe ActiveEncode::EngineAdapters::FfmpegAdapter do
|
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
115
|
+
context "input file format does not match extension" do
|
116
|
+
let(:improper_format_file) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file_without_metadata.mp4').to_s }
|
117
|
+
let(:improper_format_job) { ActiveEncode::Base.create(improper_format_file, outputs: [{ label: "low", ffmpeg_opt: "-s 640x480", extension: 'mp4' }]) }
|
118
|
+
|
119
|
+
it "returns the encode with correct error" do
|
120
|
+
expect(improper_format_job.errors).to include("Error inspecting input: #{improper_format_file}")
|
121
|
+
expect(improper_format_job.percent_complete).to be 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "input file with missing metadata" do
|
126
|
+
let(:file_without_metadata) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file_without_metadata.webm').to_s }
|
127
|
+
let!(:create_without_metadata_job) { ActiveEncode::Base.create(file_without_metadata, outputs: [{ label: "low", ffmpeg_opt: "-s 640x480", extension: 'mp4' }]) }
|
128
|
+
let(:find_without_metadata_job) { ActiveEncode::Base.find create_without_metadata_job.id }
|
129
|
+
|
130
|
+
it "does not have errors" do
|
131
|
+
sleep 2
|
132
|
+
expect(find_without_metadata_job.errors).to be_empty
|
133
|
+
end
|
134
|
+
|
135
|
+
it "has the input technical metadata in a file" do
|
136
|
+
expect(File.read("#{work_dir}/#{create_without_metadata_job.id}/input_metadata")).not_to be_empty
|
137
|
+
end
|
138
|
+
|
139
|
+
it "has the pid in a file" do
|
140
|
+
expect(File.read("#{work_dir}/#{create_without_metadata_job.id}/pid")).not_to be_empty
|
141
|
+
end
|
142
|
+
|
143
|
+
it "assigns the correct duration to the encode" do
|
144
|
+
expect(create_without_metadata_job.input.duration).to eq 68_653
|
145
|
+
expect(find_without_metadata_job.input.duration).to eq 68_653
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when uri encoded' do
|
149
|
+
let(:file_without_metadata) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', 'file_without_metadata.webm').to_s) }
|
150
|
+
|
151
|
+
it "does not have errors" do
|
152
|
+
sleep 2
|
153
|
+
expect(find_without_metadata_job.errors).to be_empty
|
154
|
+
end
|
155
|
+
|
156
|
+
it "has the input technical metadata in a file" do
|
157
|
+
expect(File.read("#{work_dir}/#{create_without_metadata_job.id}/input_metadata")).not_to be_empty
|
158
|
+
end
|
159
|
+
|
160
|
+
it "has the pid in a file" do
|
161
|
+
expect(File.read("#{work_dir}/#{create_without_metadata_job.id}/pid")).not_to be_empty
|
162
|
+
end
|
163
|
+
|
164
|
+
it "assigns the correct duration to the encode" do
|
165
|
+
expect(create_without_metadata_job.input.duration).to eq 68_653
|
166
|
+
expect(find_without_metadata_job.input.duration).to eq 68_653
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
115
171
|
context "input filename with spaces" do
|
116
172
|
let(:file_with_space) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file with space.mp4').to_s }
|
117
173
|
let!(:create_space_job) { ActiveEncode::Base.create(file_with_space, outputs: [{ label: "low", ffmpeg_opt: "-s 640x480", extension: 'mp4' }]) }
|
@@ -372,4 +428,108 @@ describe ActiveEncode::EngineAdapters::FfmpegAdapter do
|
|
372
428
|
expect { running_job.cancel! }.to raise_error(ActiveEncode::CancelError)
|
373
429
|
end
|
374
430
|
end
|
431
|
+
|
432
|
+
describe "#remove_old_files!" do
|
433
|
+
subject { created_job }
|
434
|
+
# 'exit_status.code' and 'progress' seem to be hidden files so rspec does not see them.
|
435
|
+
# That is why they are not explicitly included in the tests even though they are in the filenames list.
|
436
|
+
# If they were not being deleted they would cause other tests to fail.
|
437
|
+
let(:base_path) { "#{work_dir}/#{subject.id}" }
|
438
|
+
let(:input_metadata_file) { "#{base_path}/input_metadata" }
|
439
|
+
let(:error_log_file) { "#{base_path}/error.log" }
|
440
|
+
let(:pid_file) { "#{base_path}/pid" }
|
441
|
+
let(:exit_status_file) { "#{base_path}/exit_status.code" }
|
442
|
+
let(:progress_file) { "#{base_path}/progress" }
|
443
|
+
let(:pathnames) { [input_metadata_file, error_log_file, pid_file, exit_status_file, progress_file] }
|
444
|
+
|
445
|
+
# There was some flaky behavior with the file creation for created_job that
|
446
|
+
# would cause tests to fail. This ensures the files are created.
|
447
|
+
before :each do
|
448
|
+
FileUtils.touch(pathnames)
|
449
|
+
end
|
450
|
+
|
451
|
+
context ":no_outputs" do
|
452
|
+
it "deletes files created from encode process older than 2 weeks" do
|
453
|
+
# Another measure to give files time to be created.
|
454
|
+
sleep 1
|
455
|
+
travel 3.weeks do
|
456
|
+
expect { described_class.remove_old_files! }
|
457
|
+
.to change { File.exist?(input_metadata_file) }.from(true).to(false)
|
458
|
+
.and change { File.exist?(error_log_file) }.from(true).to(false)
|
459
|
+
.and change { File.exist?(pid_file) }.from(true).to(false)
|
460
|
+
.and not_change { Dir.children("#{work_dir}/#{subject.id}/outputs").count }.from(2)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
it "does not delete files younger than 2 weeks" do
|
465
|
+
sleep 1
|
466
|
+
expect { described_class.remove_old_files! }
|
467
|
+
.to not_change { File.exist?(input_metadata_file) }.from(true)
|
468
|
+
.and not_change { File.exist?(error_log_file) }.from(true)
|
469
|
+
.and not_change { File.exist?(pid_file) }.from(true)
|
470
|
+
.and not_change { Dir.children("#{work_dir}/#{subject.id}/outputs").count }.from(2)
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
context ":outputs" do
|
475
|
+
it "deletes outputs created from encode process older than 2 weeks" do
|
476
|
+
sleep 1
|
477
|
+
travel 3.weeks do
|
478
|
+
expect { described_class.remove_old_files!(outputs: true) }
|
479
|
+
.to not_change { File.exist?(input_metadata_file) }.from(true)
|
480
|
+
.and not_change { File.exist?(error_log_file) }.from(true)
|
481
|
+
.and not_change { File.exist?(pid_file) }.from(true)
|
482
|
+
.and change { Dir.exist?("#{work_dir}/#{subject.id}/outputs") }.from(true).to(false)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
it "does not delete outputs younger than 2 weeks" do
|
487
|
+
sleep 1
|
488
|
+
expect { described_class.remove_old_files!(outputs: true) }
|
489
|
+
.to not_change { File.exist?(input_metadata_file) }.from(true)
|
490
|
+
.and not_change { File.exist?(error_log_file) }.from(true)
|
491
|
+
.and not_change { File.exist?(pid_file) }.from(true)
|
492
|
+
.and not_change { Dir.children("#{work_dir}/#{subject.id}/outputs").count }.from(2)
|
493
|
+
end
|
494
|
+
|
495
|
+
it "does not delete outputs directory containing files younger than 2 weeks" do
|
496
|
+
sleep 1
|
497
|
+
travel 3.weeks do
|
498
|
+
allow(File).to receive(:mtime).and_call_original
|
499
|
+
allow(File).to receive(:mtime).with("#{work_dir}/#{subject.id}/outputs/fireworks-low.mp4").and_return(DateTime.now)
|
500
|
+
expect { described_class.remove_old_files!(outputs: true) }
|
501
|
+
.to not_change { Dir.exist?("#{work_dir}/#{subject.id}/outputs") }.from(true)
|
502
|
+
expect(Dir.children("#{work_dir}/#{subject.id}/outputs")).to eq(["fireworks-low.mp4"])
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
context ":all" do
|
508
|
+
it "deletes all files and directories older than 2 weeks" do
|
509
|
+
sleep 1
|
510
|
+
travel 3.weeks do
|
511
|
+
expect { described_class.remove_old_files!(all: true) }
|
512
|
+
.to change { Dir.exist?("#{work_dir}/#{subject.id}") }.from(true).to(false)
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
it "does not delete files and directories younger than 2 weeks" do
|
517
|
+
sleep 1
|
518
|
+
expect { described_class.remove_old_files!(all: true) }
|
519
|
+
.to not_change { Dir.exist?("#{work_dir}/#{subject.id}") }.from(true)
|
520
|
+
.and not_change { Dir.children("#{work_dir}/#{subject.id}").count }
|
521
|
+
end
|
522
|
+
|
523
|
+
it "does not delete directories containing files younger than 2 weeks" do
|
524
|
+
sleep 1
|
525
|
+
travel 3.weeks do
|
526
|
+
allow(File).to receive(:mtime).and_call_original
|
527
|
+
allow(File).to receive(:mtime).with("#{work_dir}/#{subject.id}/input_metadata").and_return(DateTime.now)
|
528
|
+
expect { described_class.remove_old_files!(all: true) }
|
529
|
+
.to not_change { Dir.exist?("#{work_dir}/#{subject.id}") }.from(true)
|
530
|
+
expect(Dir.children("#{work_dir}/#{subject.id}")).to eq(["input_metadata"])
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
375
535
|
end
|
@@ -109,6 +109,54 @@ describe ActiveEncode::EngineAdapters::PassThroughAdapter do
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
+
context "input file format does not match extension" do
|
113
|
+
let(:improper_format_file) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file_without_metadata.mp4').to_s }
|
114
|
+
let(:improper_format_job) { ActiveEncode::Base.create(improper_format_file, outputs: [{ label: "low", url: improper_format_file }]) }
|
115
|
+
|
116
|
+
it "returns the encode with correct error" do
|
117
|
+
expect(improper_format_job.errors).to include("Error inspecting input: #{improper_format_file}")
|
118
|
+
expect(improper_format_job.percent_complete).to be 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "input file without metadata" do
|
123
|
+
let(:file_without_metadata) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file_without_metadata.webm').to_s }
|
124
|
+
let(:file_without_metadata_derivative) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file_without_metadata.low.webm').to_s }
|
125
|
+
let(:create_without_metadata_job) { ActiveEncode::Base.create(file_without_metadata, outputs: [{ label: "low", url: file_without_metadata_derivative }]) }
|
126
|
+
let(:find_without_metadata_job) { ActiveEncode::Base.find create_without_metadata_job.id }
|
127
|
+
|
128
|
+
it "does not have errors" do
|
129
|
+
expect(find_without_metadata_job.errors).to be_empty
|
130
|
+
end
|
131
|
+
|
132
|
+
it "has the input technical metadata in a file" do
|
133
|
+
expect(File.read("#{work_dir}/#{create_without_metadata_job.id}/input_metadata")).not_to be_empty
|
134
|
+
end
|
135
|
+
|
136
|
+
it "assigns the correct duration to the encode" do
|
137
|
+
expect(create_without_metadata_job.input.duration).to eq 68_653
|
138
|
+
expect(find_without_metadata_job.input.duration).to eq 68_653
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'when uri encoded' do
|
142
|
+
let(:file_without_metadata) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', 'file_without_metadata.webm').to_s) }
|
143
|
+
let(:file_without_metadata_derivative) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', 'file_without_metadata.low.webm').to_s) }
|
144
|
+
|
145
|
+
it "does not have errors" do
|
146
|
+
expect(find_without_metadata_job.errors).to be_empty
|
147
|
+
end
|
148
|
+
|
149
|
+
it "has the input technical metadata in a file" do
|
150
|
+
expect(File.read("#{work_dir}/#{create_without_metadata_job.id}/input_metadata")).not_to be_empty
|
151
|
+
end
|
152
|
+
|
153
|
+
it "assigns the correct duration to the encode" do
|
154
|
+
expect(create_without_metadata_job.input.duration).to eq 68_653
|
155
|
+
expect(find_without_metadata_job.input.duration).to eq 68_653
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
112
160
|
context "input filename with spaces" do
|
113
161
|
let(:file_with_space) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file with space.mp4').to_s }
|
114
162
|
let(:file_with_space_derivative) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file with space.low.mp4').to_s }
|
data/spec/rails_helper.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'addressable'
|
4
|
+
|
5
|
+
describe ActiveEncode::FilenameSanitizer do
|
6
|
+
describe '#sanitize_uri' do
|
7
|
+
it 'removes file scheme' do
|
8
|
+
uri = "file:///path/to/file"
|
9
|
+
expect(ActiveEncode.sanitize_uri(uri)).to eq "/path/to/file"
|
10
|
+
end
|
11
|
+
it 'relativizes s3 uris' do
|
12
|
+
uri = "s3://mybucket/guitar.mp4"
|
13
|
+
expect(ActiveEncode.sanitize_uri(uri)).to eq "/guitar.mp4"
|
14
|
+
end
|
15
|
+
it 'does nothing for http(s) uris' do
|
16
|
+
uri = "https://www.googleapis.com/drive/v3/files/1WkWJ12WecI9hX-PmEbuKDGLPK_mN3kYP?alt=media"
|
17
|
+
expect(ActiveEncode.sanitize_uri(uri)).to eq uri
|
18
|
+
end
|
19
|
+
end
|
20
|
+
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.2.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: 2023-
|
11
|
+
date: 2023-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -278,6 +278,7 @@ files:
|
|
278
278
|
- lib/active_encode/engine_adapters.rb
|
279
279
|
- lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb
|
280
280
|
- lib/active_encode/engine_adapters/ffmpeg_adapter.rb
|
281
|
+
- lib/active_encode/engine_adapters/ffmpeg_adapter/cleaner.rb
|
281
282
|
- lib/active_encode/engine_adapters/matterhorn_adapter.rb
|
282
283
|
- lib/active_encode/engine_adapters/media_convert_adapter.rb
|
283
284
|
- lib/active_encode/engine_adapters/media_convert_output.rb
|
@@ -364,6 +365,9 @@ files:
|
|
364
365
|
- spec/fixtures/file with space.mp4
|
365
366
|
- spec/fixtures/file.with :=+%sp3c!l-ch4cts().mp4
|
366
367
|
- spec/fixtures/file.with...periods.mp4
|
368
|
+
- spec/fixtures/file_without_metadata.low.webm
|
369
|
+
- spec/fixtures/file_without_metadata.mp4
|
370
|
+
- spec/fixtures/file_without_metadata.webm
|
367
371
|
- spec/fixtures/fireworks.low.mp4
|
368
372
|
- spec/fixtures/fireworks.mp4
|
369
373
|
- spec/fixtures/matterhorn/cancelled_response.xml
|
@@ -421,6 +425,7 @@ files:
|
|
421
425
|
- spec/units/core_spec.rb
|
422
426
|
- spec/units/engine_adapter_spec.rb
|
423
427
|
- spec/units/file_locator_spec.rb
|
428
|
+
- spec/units/filename_sanitizer_spec.rb
|
424
429
|
- spec/units/global_id_spec.rb
|
425
430
|
- spec/units/input_spec.rb
|
426
431
|
- spec/units/output_spec.rb
|
@@ -433,7 +438,7 @@ licenses:
|
|
433
438
|
- Apache-2.0
|
434
439
|
metadata:
|
435
440
|
rubygems_mfa_required: 'true'
|
436
|
-
post_install_message:
|
441
|
+
post_install_message:
|
437
442
|
rdoc_options: []
|
438
443
|
require_paths:
|
439
444
|
- lib
|
@@ -448,8 +453,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
448
453
|
- !ruby/object:Gem::Version
|
449
454
|
version: '0'
|
450
455
|
requirements: []
|
451
|
-
rubygems_version: 3.
|
452
|
-
signing_key:
|
456
|
+
rubygems_version: 3.1.6
|
457
|
+
signing_key:
|
453
458
|
specification_version: 4
|
454
459
|
summary: Declare encode job classes that can be run by a variety of encoding services
|
455
460
|
test_files:
|
@@ -520,6 +525,9 @@ test_files:
|
|
520
525
|
- spec/fixtures/file with space.mp4
|
521
526
|
- spec/fixtures/file.with :=+%sp3c!l-ch4cts().mp4
|
522
527
|
- spec/fixtures/file.with...periods.mp4
|
528
|
+
- spec/fixtures/file_without_metadata.low.webm
|
529
|
+
- spec/fixtures/file_without_metadata.mp4
|
530
|
+
- spec/fixtures/file_without_metadata.webm
|
523
531
|
- spec/fixtures/fireworks.low.mp4
|
524
532
|
- spec/fixtures/fireworks.mp4
|
525
533
|
- spec/fixtures/matterhorn/cancelled_response.xml
|
@@ -577,6 +585,7 @@ test_files:
|
|
577
585
|
- spec/units/core_spec.rb
|
578
586
|
- spec/units/engine_adapter_spec.rb
|
579
587
|
- spec/units/file_locator_spec.rb
|
588
|
+
- spec/units/filename_sanitizer_spec.rb
|
580
589
|
- spec/units/global_id_spec.rb
|
581
590
|
- spec/units/input_spec.rb
|
582
591
|
- spec/units/output_spec.rb
|