active_encode 1.1.3 → 1.2.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/.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/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
- metadata +13 -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
|
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
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
|
@@ -434,7 +438,7 @@ licenses:
|
|
434
438
|
- Apache-2.0
|
435
439
|
metadata:
|
436
440
|
rubygems_mfa_required: 'true'
|
437
|
-
post_install_message:
|
441
|
+
post_install_message:
|
438
442
|
rdoc_options: []
|
439
443
|
require_paths:
|
440
444
|
- lib
|
@@ -449,8 +453,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
449
453
|
- !ruby/object:Gem::Version
|
450
454
|
version: '0'
|
451
455
|
requirements: []
|
452
|
-
rubygems_version: 3.
|
453
|
-
signing_key:
|
456
|
+
rubygems_version: 3.1.6
|
457
|
+
signing_key:
|
454
458
|
specification_version: 4
|
455
459
|
summary: Declare encode job classes that can be run by a variety of encoding services
|
456
460
|
test_files:
|
@@ -521,6 +525,9 @@ test_files:
|
|
521
525
|
- spec/fixtures/file with space.mp4
|
522
526
|
- spec/fixtures/file.with :=+%sp3c!l-ch4cts().mp4
|
523
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
|
524
531
|
- spec/fixtures/fireworks.low.mp4
|
525
532
|
- spec/fixtures/fireworks.mp4
|
526
533
|
- spec/fixtures/matterhorn/cancelled_response.xml
|