active_encode 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb +5 -3
  3. data/lib/active_encode/engine_adapters/ffmpeg_adapter.rb +9 -14
  4. data/lib/active_encode/engine_adapters/media_convert_adapter.rb +9 -3
  5. data/lib/active_encode/engine_adapters/pass_through_adapter.rb +1 -5
  6. data/lib/active_encode/filename_sanitizer.rb +22 -0
  7. data/lib/active_encode/version.rb +1 -1
  8. data/lib/active_encode.rb +5 -0
  9. data/spec/fixtures//"file_with_double_quote/".low.mp4 +0 -0
  10. data/spec/fixtures//"file_with_double_quote/".mp4 +0 -0
  11. data/spec/fixtures/'file_with_single_quote'.low.mp4 +0 -0
  12. data/spec/fixtures/'file_with_single_quote'.mp4 +0 -0
  13. data/spec/fixtures/ffmpeg/incomplete-id/error.log +0 -0
  14. data/spec/fixtures/ffmpeg/incomplete-id/exit_status.code +1 -0
  15. data/spec/fixtures/ffmpeg/incomplete-id/input_metadata +102 -0
  16. data/spec/fixtures/ffmpeg/incomplete-id/output_metadata-high +90 -0
  17. data/spec/fixtures/ffmpeg/incomplete-id/output_metadata-low +90 -0
  18. data/spec/fixtures/ffmpeg/incomplete-id/pid +1 -0
  19. data/spec/fixtures/ffmpeg/incomplete-id/progress +11 -0
  20. data/spec/fixtures/ffmpeg/incomplete-id/video-high.mp4 +0 -0
  21. data/spec/fixtures/ffmpeg/incomplete-id/video-low.mp4 +0 -0
  22. data/spec/fixtures/file.with :=+%sp3c!l-ch4cts().mp4 +0 -0
  23. data/spec/fixtures/file.with...periods.mp4 +0 -0
  24. data/spec/integration/elastic_transcoder_adapter_spec.rb +48 -0
  25. data/spec/integration/ffmpeg_adapter_spec.rb +124 -0
  26. data/spec/integration/media_convert_adapter_spec.rb +51 -0
  27. data/spec/integration/pass_through_adapter_spec.rb +84 -0
  28. metadata +33 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cc3915da436ad8220ab0a23f27280ad490dd31b1250f56957ca2667ecd6666d
4
- data.tar.gz: 6a43e177fbe445be244a80ee44f503324d0e34f98fc4ac90e9b20f5110c120c4
3
+ metadata.gz: 165b612fc067a4f2513bb6090876f015216dcb69986416f580256b58ee5979d0
4
+ data.tar.gz: 8730c65eb2b3f38a22f245992c4885a494f83bcff169af6746b35f2d2d75a5bc
5
5
  SHA512:
6
- metadata.gz: 7936993f21839c01bab5f3dd6cb7614bb837244c2ab9de2fa5953154d9f0a5c3460a485a2da1748f74c545b76d555c35c18decf697791002783d3607efaaed57
7
- data.tar.gz: '069ba0aaec4e5e0a31661018a560cfb73d9d39092076c0f3c9fdcc7ee7afea9ad1a23b5114fca4bd1aadd16f85b7c1d0f59a8206ef89b20efc1ccf7a2bd6a93c'
6
+ metadata.gz: '094791342ae22c10e682762e61a8f83934a742fa326746a5733a304cf14a657dc048560e251734b06a9833d7fe7b978fed0196361e8cca199e017b195d8e239f'
7
+ data.tar.gz: '087bff74b89364c3e2795a104038324846d7bf10e33e3e239a901db189445273cd7dc40f252a555cdb800d9adf82457ccb055fc3d2bcf0184ff250c891b7ff02'
@@ -140,9 +140,10 @@ module ActiveEncode
140
140
  # logger.info("Already in bucket `#{source_bucket}'")
141
141
  s3_object.key
142
142
  else
143
- s3_key = File.join(SecureRandom.uuid, s3_object.key)
143
+ cleaned_url = ActiveEncode.sanitize_filename input_url
144
+ s3_key = File.join(SecureRandom.uuid, File.basename(cleaned_url))
144
145
  # logger.info("Copying to `#{source_bucket}/#{input_url}'")
145
- target = Aws::S3::Object.new(bucket_name: source_bucket, key: input_url)
146
+ target = Aws::S3::Object.new(bucket_name: source_bucket, key: s3_key)
146
147
  target.copy_from(s3_object, multipart_copy: s3_object.size > 15_728_640) # 15.megabytes
147
148
  s3_key
148
149
  end
@@ -152,7 +153,8 @@ module ActiveEncode
152
153
  # original_input = input_url
153
154
  bucket = Aws::S3::Resource.new(client: s3client).bucket(source_bucket)
154
155
  filename = FileLocator.new(input_url).location
155
- s3_key = File.join(SecureRandom.uuid, File.basename(filename))
156
+ cleaned_url = ActiveEncode.sanitize_filename input_url
157
+ s3_key = File.join(SecureRandom.uuid, File.basename(cleaned_url))
156
158
  # logger.info("Copying `#{original_input}' to `#{source_bucket}/#{input_url}'")
157
159
  obj = bucket.object(s3_key)
158
160
  obj.upload_file filename
@@ -11,11 +11,14 @@ module ActiveEncode
11
11
  MEDIAINFO_PATH = ENV["MEDIAINFO_PATH"] || "mediainfo"
12
12
  FFMPEG_PATH = ENV["FFMPEG_PATH"] || "ffmpeg"
13
13
 
14
+ class_attribute :completeness_threshold
15
+
14
16
  def create(input_url, options = {})
15
17
  # Decode file uris for ffmpeg (mediainfo works either way)
16
18
  case input_url
17
19
  when /^file\:\/\/\//
18
20
  input_url = Addressable::URI.unencode(input_url)
21
+ input_url = ActiveEncode.sanitize_input(input_url)
19
22
  when /^s3\:\/\//
20
23
  require 'file_locator'
21
24
 
@@ -110,8 +113,8 @@ module ActiveEncode
110
113
  encode.state = :completed
111
114
  elsif cancelled? encode.id
112
115
  encode.state = :cancelled
113
- elsif encode.percent_complete < 100
114
- encode.errors << "Encoding has completed but the output duration is shorter than the input"
116
+ elsif encode.percent_complete < (completeness_threshold || 100)
117
+ encode.errors.prepend("Encoding has completed but the output duration is shorter than the input")
115
118
  encode.state = :failed
116
119
  end
117
120
 
@@ -170,7 +173,7 @@ module ActiveEncode
170
173
  err_path = working_path("error.log", id)
171
174
  error = File.read(err_path) if File.file? err_path
172
175
  if error.present?
173
- [error]
176
+ ["Error encountered during encoding. Check ffmpeg log for more details.", error]
174
177
  else
175
178
  []
176
179
  end
@@ -200,7 +203,7 @@ module ActiveEncode
200
203
  Dir["#{File.absolute_path(working_path('outputs', id))}/*"].each do |file_path|
201
204
  output = ActiveEncode::Output.new
202
205
  output.url = "file://#{file_path}"
203
- sanitized_filename = sanitize_base encode.input.url
206
+ sanitized_filename = ActiveEncode.sanitize_base encode.input.url
204
207
  output.label = file_path[/#{Regexp.quote(sanitized_filename)}\-(.*?)#{Regexp.quote(File.extname(file_path))}$/, 1]
205
208
  output.id = "#{encode.input.id}-#{output.label}"
206
209
  output.created_at = encode.created_at
@@ -219,7 +222,7 @@ module ActiveEncode
219
222
 
220
223
  def ffmpeg_command(input_url, id, opts)
221
224
  output_opt = opts[:outputs].collect do |output|
222
- sanitized_filename = sanitize_base input_url
225
+ sanitized_filename = ActiveEncode.sanitize_base input_url
223
226
  file_name = "outputs/#{sanitized_filename}-#{output[:label]}.#{output[:extension]}"
224
227
  " #{output[:ffmpeg_opt]} #{working_path(file_name, id)}"
225
228
  end.join(" ")
@@ -227,15 +230,7 @@ module ActiveEncode
227
230
  "#{k}: #{v}\r\n"
228
231
  end.join
229
232
  header_opt = "-headers '#{header_opt}'" if header_opt.present?
230
- "#{FFMPEG_PATH} #{header_opt} -y -loglevel error -progress #{working_path('progress', id)} -i \"#{input_url}\" #{output_opt}"
231
- end
232
-
233
- def sanitize_base(input_url)
234
- if input_url.is_a? URI::HTTP
235
- File.basename(input_url.path, File.extname(input_url.path))
236
- else
237
- File.basename(input_url, File.extname(input_url)).gsub(/[^0-9A-Za-z.\-]/, '_')
238
- end
233
+ "#{FFMPEG_PATH} #{header_opt} -y -loglevel level+fatal -progress #{working_path('progress', id)} -i \"#{input_url}\" #{output_opt}"
239
234
  end
240
235
 
241
236
  def get_pid(id)
@@ -482,6 +482,10 @@ module ActiveEncode
482
482
  end
483
483
  end
484
484
 
485
+ def s3client
486
+ Aws::S3::Client.new
487
+ end
488
+
485
489
  def s3_uri(url, options = {})
486
490
  bucket = options[:masterfile_bucket]
487
491
 
@@ -503,9 +507,10 @@ module ActiveEncode
503
507
  # logger.info("Already in bucket `#{source_bucket}'")
504
508
  s3_object.key
505
509
  else
506
- s3_key = File.join(SecureRandom.uuid, s3_object.key)
510
+ cleaned_url = ActiveEncode.sanitize_filename input_url
511
+ s3_key = File.join(SecureRandom.uuid, File.basename(cleaned_url))
507
512
  # logger.info("Copying to `#{source_bucket}/#{input_url}'")
508
- target = Aws::S3::Object.new(bucket_name: source_bucket, key: input_url)
513
+ target = Aws::S3::Object.new(bucket_name: source_bucket, key: s3_key)
509
514
  target.copy_from(s3_object, multipart_copy: s3_object.size > 15_728_640) # 15.megabytes
510
515
  s3_key
511
516
  end
@@ -515,7 +520,8 @@ module ActiveEncode
515
520
  # original_input = input_url
516
521
  bucket = Aws::S3::Resource.new(client: s3client).bucket(source_bucket)
517
522
  filename = FileLocator.new(input_url).location
518
- s3_key = File.join(SecureRandom.uuid, File.basename(filename))
523
+ cleaned_url = ActiveEncode.sanitize_filename input_url
524
+ s3_key = File.join(SecureRandom.uuid, File.basename(cleaned_url))
519
525
  # logger.info("Copying `#{original_input}' to `#{source_bucket}/#{input_url}'")
520
526
  obj = bucket.object(s3_key)
521
527
  obj.upload_file filename
@@ -55,7 +55,7 @@ module ActiveEncode
55
55
  # Copy derivatives to work directory
56
56
  options[:outputs].each do |opt|
57
57
  url = opt[:url]
58
- output_path = working_path("outputs/#{sanitize_base opt[:url]}#{File.extname opt[:url]}", new_encode.id)
58
+ output_path = working_path("outputs/#{ActiveEncode.sanitize_base opt[:url]}#{File.extname opt[:url]}", new_encode.id)
59
59
  FileUtils.cp FileLocator.new(url).location, output_path
60
60
  filename_label_hash[output_path] = opt[:label]
61
61
  end
@@ -206,10 +206,6 @@ module ActiveEncode
206
206
  outputs
207
207
  end
208
208
 
209
- def sanitize_base(input_url)
210
- File.basename(input_url, File.extname(input_url)).gsub(/[^0-9A-Za-z.\-]/, '_')
211
- end
212
-
213
209
  def working_path(path, id)
214
210
  File.join(WORK_DIR, id, path)
215
211
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module ActiveEncode
3
+ module FilenameSanitizer
4
+ # ffmpeg has trouble with double quotes in file names. Escape them to get ffmpeg to find the file.
5
+ def sanitize_input(input_url)
6
+ input_url.gsub(/["]/, '\\\\\0')
7
+ end
8
+
9
+ def sanitize_base(input_url)
10
+ filepath = input_url.is_a?(URI::HTTP) ? input_url.path : input_url
11
+ # Replace special characters with underscores and remove excess periods.
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('.')
14
+ end
15
+
16
+ def sanitize_filename(input_url)
17
+ filepath = input_url.is_a?(URI::HTTP) ? input_url.path : input_url
18
+ # Replace special characters with underscores and remove excess periods.
19
+ File.basename(filepath).gsub(/[^0-9A-Za-z.\-\/]/, '_').gsub(/\.(?=.*\.)/, '')
20
+ end
21
+ end
22
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ActiveEncode
3
- VERSION = '1.0.0'
3
+ VERSION = '1.1.0'
4
4
  end
data/lib/active_encode.rb CHANGED
@@ -2,3 +2,8 @@
2
2
  require 'active_encode/version'
3
3
  require 'active_encode/base'
4
4
  require 'active_encode/engine'
5
+ require 'active_encode/filename_sanitizer'
6
+
7
+ module ActiveEncode
8
+ extend ActiveEncode::FilenameSanitizer
9
+ end
File without changes
@@ -0,0 +1,102 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <MediaInfo
3
+ xmlns="https://mediaarea.net/mediainfo"
4
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5
+ xsi:schemaLocation="https://mediaarea.net/mediainfo https://mediaarea.net/mediainfo/mediainfo_2_0.xsd"
6
+ version="2.0">
7
+ <creatingLibrary version="18.05" url="https://mediaarea.net/MediaInfo">MediaInfoLib</creatingLibrary>
8
+ <media ref="/home/pdinh/Downloads/videoshort.mp4">
9
+ <track type="General">
10
+ <VideoCount>1</VideoCount>
11
+ <AudioCount>1</AudioCount>
12
+ <FileExtension>mp4</FileExtension>
13
+ <Format>MPEG-4</Format>
14
+ <Format_Profile>Base Media</Format_Profile>
15
+ <CodecID>mp42</CodecID>
16
+ <FileSize>199160</FileSize>
17
+ <Duration>6.315</Duration>
18
+ <OverallBitRate_Mode>VBR</OverallBitRate_Mode>
19
+ <OverallBitRate>252301</OverallBitRate>
20
+ <FrameRate>23.719</FrameRate>
21
+ <FrameCount>149</FrameCount>
22
+ <StreamSize>5679</StreamSize>
23
+ <HeaderSize>160</HeaderSize>
24
+ <DataSize>193489</DataSize>
25
+ <FooterSize>5511</FooterSize>
26
+ <IsStreamable>No</IsStreamable>
27
+ <Encoded_Date>UTC 2010-09-23 00:37:25</Encoded_Date>
28
+ <Tagged_Date>UTC 2010-09-23 00:37:27</Tagged_Date>
29
+ <File_Modified_Date>UTC 2017-12-14 19:29:35</File_Modified_Date>
30
+ <File_Modified_Date_Local>2017-12-14 14:29:35</File_Modified_Date_Local>
31
+ <Encoded_Application>HandBrake 0.9.4 2009112300</Encoded_Application>
32
+ </track>
33
+ <track type="Video">
34
+ <StreamOrder>0</StreamOrder>
35
+ <ID>1</ID>
36
+ <Format>AVC</Format>
37
+ <Format_Profile>Main</Format_Profile>
38
+ <Format_Level>1.1</Format_Level>
39
+ <Format_Settings_CABAC>Yes</Format_Settings_CABAC>
40
+ <Format_Settings_RefFrames>2</Format_Settings_RefFrames>
41
+ <CodecID>avc1</CodecID>
42
+ <Duration>6.282</Duration>
43
+ <BitRate>74477</BitRate>
44
+ <Width>200</Width>
45
+ <Height>110</Height>
46
+ <Stored_Width>208</Stored_Width>
47
+ <Stored_Height>112</Stored_Height>
48
+ <Sampled_Width>200</Sampled_Width>
49
+ <Sampled_Height>110</Sampled_Height>
50
+ <PixelAspectRatio>1.000</PixelAspectRatio>
51
+ <DisplayAspectRatio>1.818</DisplayAspectRatio>
52
+ <Rotation>0.000</Rotation>
53
+ <FrameRate_Mode>VFR</FrameRate_Mode>
54
+ <FrameRate>23.719</FrameRate>
55
+ <FrameRate_Minimum>12.500</FrameRate_Minimum>
56
+ <FrameRate_Maximum>24.390</FrameRate_Maximum>
57
+ <FrameRate_Original>24.000</FrameRate_Original>
58
+ <FrameCount>149</FrameCount>
59
+ <ColorSpace>YUV</ColorSpace>
60
+ <ChromaSubsampling>4:2:0</ChromaSubsampling>
61
+ <BitDepth>8</BitDepth>
62
+ <ScanType>Progressive</ScanType>
63
+ <StreamSize>58482</StreamSize>
64
+ <Encoded_Library>x264 - core 79 r1347 5ddd61b</Encoded_Library>
65
+ <Encoded_Library_Name>x264</Encoded_Library_Name>
66
+ <Encoded_Library_Version>core 79 r1347 5ddd61b</Encoded_Library_Version>
67
+ <Encoded_Library_Settings>cabac=1 / ref=2 / deblock=1:0:0 / analyse=0x1:0x111 / me=hex / subme=6 / psy=1 / psy_rd=1.0:0.0 / mixed_ref=0 / me_range=16 / chroma_me=1 / trellis=0 / 8x8dct=0 / cqm=0 / deadzone=21,11 / chroma_qp_offset=-2 / threads=3 / nr=0 / decimate=1 / mbaff=0 / constrained_intra=0 / bframes=2 / b_pyramid=0 / b_adapt=1 / b_bias=0 / direct=1 / wpredb=0 / wpredp=2 / keyint=240 / keyint_min=24 / scenecut=40 / rc_lookahead=40 / rc=crf / mbtree=1 / crf=25.5 / qcomp=0.60 / qpmin=10 / qpmax=51 / qpstep=4 / ip_ratio=1.40 / aq=1:1.00</Encoded_Library_Settings>
68
+ <Encoded_Date>UTC 2010-09-23 00:37:25</Encoded_Date>
69
+ <Tagged_Date>UTC 2010-09-23 00:37:27</Tagged_Date>
70
+ <colour_range>Limited</colour_range>
71
+ <colour_description_present>Yes</colour_description_present>
72
+ <colour_primaries>BT.601 NTSC</colour_primaries>
73
+ <transfer_characteristics>BT.709</transfer_characteristics>
74
+ <matrix_coefficients>BT.601</matrix_coefficients>
75
+ </track>
76
+ <track type="Audio">
77
+ <StreamOrder>1</StreamOrder>
78
+ <ID>2</ID>
79
+ <Format>AAC</Format>
80
+ <Format_Profile>LC</Format_Profile>
81
+ <CodecID>mp4a-40-2</CodecID>
82
+ <Duration>6.315</Duration>
83
+ <BitRate_Mode>VBR</BitRate_Mode>
84
+ <BitRate>171030</BitRate>
85
+ <BitRate_Maximum>201736</BitRate_Maximum>
86
+ <Channels>1</Channels>
87
+ <ChannelPositions>Front: C</ChannelPositions>
88
+ <ChannelLayout>C</ChannelLayout>
89
+ <SamplesPerFrame>1024</SamplesPerFrame>
90
+ <SamplingRate>48000</SamplingRate>
91
+ <SamplingCount>303120</SamplingCount>
92
+ <FrameRate>46.875</FrameRate>
93
+ <FrameCount>296</FrameCount>
94
+ <Compression_Mode>Lossy</Compression_Mode>
95
+ <StreamSize>134999</StreamSize>
96
+ <StreamSize_Proportion>0.67784</StreamSize_Proportion>
97
+ <Title>Stereo</Title>
98
+ <Encoded_Date>UTC 2010-09-23 00:37:25</Encoded_Date>
99
+ <Tagged_Date>UTC 2010-09-23 00:37:27</Tagged_Date>
100
+ </track>
101
+ </media>
102
+ </MediaInfo>
@@ -0,0 +1,90 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <MediaInfo
3
+ xmlns="https://mediaarea.net/mediainfo"
4
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5
+ xsi:schemaLocation="https://mediaarea.net/mediainfo https://mediaarea.net/mediainfo/mediainfo_2_0.xsd"
6
+ version="2.0">
7
+ <creatingLibrary version="18.05" url="https://mediaarea.net/MediaInfo">MediaInfoLib</creatingLibrary>
8
+ <media ref="spec/fixtures/ffmpeg/completed-id/low.mp4">
9
+ <track type="General">
10
+ <VideoCount>1</VideoCount>
11
+ <AudioCount>1</AudioCount>
12
+ <FileExtension>mp4</FileExtension>
13
+ <Format>MPEG-4</Format>
14
+ <Format_Profile>Base Media</Format_Profile>
15
+ <CodecID>isom</CodecID>
16
+ <FileSize>125403</FileSize>
17
+ <Duration>6.336</Duration>
18
+ <OverallBitRate>158337</OverallBitRate>
19
+ <FrameRate>24.000</FrameRate>
20
+ <FrameCount>150</FrameCount>
21
+ <StreamSize>5648</StreamSize>
22
+ <HeaderSize>40</HeaderSize>
23
+ <DataSize>119763</DataSize>
24
+ <FooterSize>5600</FooterSize>
25
+ <IsStreamable>No</IsStreamable>
26
+ <File_Modified_Date>UTC 2018-09-07 17:36:26</File_Modified_Date>
27
+ <File_Modified_Date_Local>2018-09-07 13:36:26</File_Modified_Date_Local>
28
+ <Encoded_Application>Lavf58.12.100</Encoded_Application>
29
+ </track>
30
+ <track type="Video">
31
+ <StreamOrder>0</StreamOrder>
32
+ <ID>1</ID>
33
+ <Format>AVC</Format>
34
+ <Format_Profile>High</Format_Profile>
35
+ <Format_Level>1.1</Format_Level>
36
+ <Format_Settings_CABAC>Yes</Format_Settings_CABAC>
37
+ <Format_Settings_RefFrames>4</Format_Settings_RefFrames>
38
+ <CodecID>avc1</CodecID>
39
+ <Duration>6.250</Duration>
40
+ <BitRate>79302</BitRate>
41
+ <Width>200</Width>
42
+ <Height>110</Height>
43
+ <Stored_Width>208</Stored_Width>
44
+ <Stored_Height>112</Stored_Height>
45
+ <Sampled_Width>200</Sampled_Width>
46
+ <Sampled_Height>110</Sampled_Height>
47
+ <PixelAspectRatio>1.000</PixelAspectRatio>
48
+ <DisplayAspectRatio>1.818</DisplayAspectRatio>
49
+ <Rotation>0.000</Rotation>
50
+ <FrameRate_Mode>CFR</FrameRate_Mode>
51
+ <FrameRate_Mode_Original>VFR</FrameRate_Mode_Original>
52
+ <FrameRate>24.000</FrameRate>
53
+ <FrameCount>150</FrameCount>
54
+ <ColorSpace>YUV</ColorSpace>
55
+ <ChromaSubsampling>4:2:0</ChromaSubsampling>
56
+ <BitDepth>8</BitDepth>
57
+ <ScanType>Progressive</ScanType>
58
+ <StreamSize>61955</StreamSize>
59
+ <Encoded_Library>x264 - core 152 r2854 e9a5903</Encoded_Library>
60
+ <Encoded_Library_Name>x264</Encoded_Library_Name>
61
+ <Encoded_Library_Version>core 152 r2854 e9a5903</Encoded_Library_Version>
62
+ <Encoded_Library_Settings>cabac=1 / ref=3 / deblock=1:0:0 / analyse=0x3:0x113 / me=hex / subme=7 / psy=1 / psy_rd=1.00:0.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=1 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=3 / lookahead_threads=1 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=2 / keyint=250 / keyint_min=24 / scenecut=40 / intra_refresh=0 / rc_lookahead=40 / rc=crf / mbtree=1 / crf=23.0 / qcomp=0.60 / qpmin=0 / qpmax=69 / qpstep=4 / ip_ratio=1.40 / aq=1:1.00</Encoded_Library_Settings>
63
+ </track>
64
+ <track type="Audio">
65
+ <StreamOrder>1</StreamOrder>
66
+ <ID>2</ID>
67
+ <Format>AAC</Format>
68
+ <Format_Profile>LC</Format_Profile>
69
+ <Format_Settings_SBR>No (Explicit)</Format_Settings_SBR>
70
+ <CodecID>mp4a-40-2</CodecID>
71
+ <Duration>6.336</Duration>
72
+ <BitRate_Mode>CBR</BitRate_Mode>
73
+ <BitRate>72000</BitRate>
74
+ <Channels>2</Channels>
75
+ <Channels_Original>1</Channels_Original>
76
+ <ChannelPositions>Front: C</ChannelPositions>
77
+ <ChannelLayout>C</ChannelLayout>
78
+ <SamplesPerFrame>1024</SamplesPerFrame>
79
+ <SamplingRate>48000</SamplingRate>
80
+ <SamplingCount>304128</SamplingCount>
81
+ <FrameRate>46.875</FrameRate>
82
+ <FrameCount>297</FrameCount>
83
+ <Compression_Mode>Lossy</Compression_Mode>
84
+ <StreamSize>57800</StreamSize>
85
+ <StreamSize_Proportion>0.46091</StreamSize_Proportion>
86
+ <Default>Yes</Default>
87
+ <AlternateGroup>1</AlternateGroup>
88
+ </track>
89
+ </media>
90
+ </MediaInfo>
@@ -0,0 +1,90 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <MediaInfo
3
+ xmlns="https://mediaarea.net/mediainfo"
4
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5
+ xsi:schemaLocation="https://mediaarea.net/mediainfo https://mediaarea.net/mediainfo/mediainfo_2_0.xsd"
6
+ version="2.0">
7
+ <creatingLibrary version="18.05" url="https://mediaarea.net/MediaInfo">MediaInfoLib</creatingLibrary>
8
+ <media ref="spec/fixtures/ffmpeg/completed-id/low.mp4">
9
+ <track type="General">
10
+ <VideoCount>1</VideoCount>
11
+ <AudioCount>1</AudioCount>
12
+ <FileExtension>mp4</FileExtension>
13
+ <Format>MPEG-4</Format>
14
+ <Format_Profile>Base Media</Format_Profile>
15
+ <CodecID>isom</CodecID>
16
+ <FileSize>125403</FileSize>
17
+ <Duration>6.336</Duration>
18
+ <OverallBitRate>158337</OverallBitRate>
19
+ <FrameRate>24.000</FrameRate>
20
+ <FrameCount>150</FrameCount>
21
+ <StreamSize>5648</StreamSize>
22
+ <HeaderSize>40</HeaderSize>
23
+ <DataSize>119763</DataSize>
24
+ <FooterSize>5600</FooterSize>
25
+ <IsStreamable>No</IsStreamable>
26
+ <File_Modified_Date>UTC 2018-09-07 17:36:26</File_Modified_Date>
27
+ <File_Modified_Date_Local>2018-09-07 13:36:26</File_Modified_Date_Local>
28
+ <Encoded_Application>Lavf58.12.100</Encoded_Application>
29
+ </track>
30
+ <track type="Video">
31
+ <StreamOrder>0</StreamOrder>
32
+ <ID>1</ID>
33
+ <Format>AVC</Format>
34
+ <Format_Profile>High</Format_Profile>
35
+ <Format_Level>1.1</Format_Level>
36
+ <Format_Settings_CABAC>Yes</Format_Settings_CABAC>
37
+ <Format_Settings_RefFrames>4</Format_Settings_RefFrames>
38
+ <CodecID>avc1</CodecID>
39
+ <Duration>6.250</Duration>
40
+ <BitRate>79302</BitRate>
41
+ <Width>200</Width>
42
+ <Height>110</Height>
43
+ <Stored_Width>208</Stored_Width>
44
+ <Stored_Height>112</Stored_Height>
45
+ <Sampled_Width>200</Sampled_Width>
46
+ <Sampled_Height>110</Sampled_Height>
47
+ <PixelAspectRatio>1.000</PixelAspectRatio>
48
+ <DisplayAspectRatio>1.818</DisplayAspectRatio>
49
+ <Rotation>0.000</Rotation>
50
+ <FrameRate_Mode>CFR</FrameRate_Mode>
51
+ <FrameRate_Mode_Original>VFR</FrameRate_Mode_Original>
52
+ <FrameRate>24.000</FrameRate>
53
+ <FrameCount>150</FrameCount>
54
+ <ColorSpace>YUV</ColorSpace>
55
+ <ChromaSubsampling>4:2:0</ChromaSubsampling>
56
+ <BitDepth>8</BitDepth>
57
+ <ScanType>Progressive</ScanType>
58
+ <StreamSize>61955</StreamSize>
59
+ <Encoded_Library>x264 - core 152 r2854 e9a5903</Encoded_Library>
60
+ <Encoded_Library_Name>x264</Encoded_Library_Name>
61
+ <Encoded_Library_Version>core 152 r2854 e9a5903</Encoded_Library_Version>
62
+ <Encoded_Library_Settings>cabac=1 / ref=3 / deblock=1:0:0 / analyse=0x3:0x113 / me=hex / subme=7 / psy=1 / psy_rd=1.00:0.00 / mixed_ref=1 / me_range=16 / chroma_me=1 / trellis=1 / 8x8dct=1 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=-2 / threads=3 / lookahead_threads=1 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=2 / keyint=250 / keyint_min=24 / scenecut=40 / intra_refresh=0 / rc_lookahead=40 / rc=crf / mbtree=1 / crf=23.0 / qcomp=0.60 / qpmin=0 / qpmax=69 / qpstep=4 / ip_ratio=1.40 / aq=1:1.00</Encoded_Library_Settings>
63
+ </track>
64
+ <track type="Audio">
65
+ <StreamOrder>1</StreamOrder>
66
+ <ID>2</ID>
67
+ <Format>AAC</Format>
68
+ <Format_Profile>LC</Format_Profile>
69
+ <Format_Settings_SBR>No (Explicit)</Format_Settings_SBR>
70
+ <CodecID>mp4a-40-2</CodecID>
71
+ <Duration>6.336</Duration>
72
+ <BitRate_Mode>CBR</BitRate_Mode>
73
+ <BitRate>72000</BitRate>
74
+ <Channels>2</Channels>
75
+ <Channels_Original>1</Channels_Original>
76
+ <ChannelPositions>Front: C</ChannelPositions>
77
+ <ChannelLayout>C</ChannelLayout>
78
+ <SamplesPerFrame>1024</SamplesPerFrame>
79
+ <SamplingRate>48000</SamplingRate>
80
+ <SamplingCount>304128</SamplingCount>
81
+ <FrameRate>46.875</FrameRate>
82
+ <FrameCount>297</FrameCount>
83
+ <Compression_Mode>Lossy</Compression_Mode>
84
+ <StreamSize>57800</StreamSize>
85
+ <StreamSize_Proportion>0.46091</StreamSize_Proportion>
86
+ <Default>Yes</Default>
87
+ <AlternateGroup>1</AlternateGroup>
88
+ </track>
89
+ </media>
90
+ </MediaInfo>
@@ -0,0 +1 @@
1
+ 99999
@@ -0,0 +1,11 @@
1
+ frame=150
2
+ fps=0.0
3
+ stream_0_0_q=-1.0
4
+ bitrate= 158.9kbits/s
5
+ total_size=125403
6
+ out_time_ms=6000000
7
+ out_time=00:00:06.000000
8
+ dup_frames=1
9
+ drop_frames=0
10
+ speed=43.1x
11
+ progress=end
@@ -137,6 +137,54 @@ describe ActiveEncode::EngineAdapters::ElasticTranscoderAdapter do
137
137
  end
138
138
  end
139
139
 
140
+ describe "#copy_to_input_bucket" do
141
+ context "when filename has no special characters" do
142
+ context "non-s3 file" do
143
+ let(:input_url) { "spec/fixtures/fireworks.mp4" }
144
+ let(:source_bucket) { "bucket1" }
145
+
146
+ it "calls the #upload_to_s3 method" do
147
+ allow(SecureRandom).to receive(:uuid).and_return("randomstring")
148
+ expect(described_class.new.send(:copy_to_input_bucket, input_url, source_bucket)).to eq "randomstring/fireworks.mp4"
149
+ end
150
+ end
151
+ context "s3 file" do
152
+ let(:input_url) { "s3://bucket1/file.mp4" }
153
+ let(:source_bucket) { "bucket1" }
154
+
155
+ it "calls the #check_s3_bucket method" do
156
+ expect(described_class.new.send(:copy_to_input_bucket, input_url, source_bucket)).to eq "file.mp4"
157
+ end
158
+ end
159
+ end
160
+ context "when filename has special characters" do
161
+ context "non-s3 file" do
162
+ let(:input) { ["'file_with_single_quote'.mp4", '"file_with_double_quote".mp4', "file with space.mp4", "file.with...periods.mp4", "file.with :=+%sp3c!l-ch4cts().mp4"] }
163
+ let(:clean) { ["_file_with_single_quote_.mp4", "_file_with_double_quote_.mp4", "file_with_space.mp4", "filewithperiods.mp4", "filewith_____sp3c_l-ch4cts__.mp4"] }
164
+ let(:source_bucket) { "bucket1" }
165
+
166
+ it "calls the #upload_to_s3 method" do
167
+ allow(SecureRandom).to receive(:uuid).and_return("randomstring")
168
+ input.each_with_index do |url, index|
169
+ expect(described_class.new.send(:copy_to_input_bucket, "spec/fixtures/#{url}", source_bucket)).to eq "randomstring/#{clean[index]}"
170
+ end
171
+ end
172
+ end
173
+ context "s3 file" do
174
+ let(:input_urls) { ["s3://bucket1/'file_with_single_quote'.mp4", 's3://bucket1/"file_with_double_quote".mp4', "s3://bucket1/file with space.mp4", "s3://bucket1/file.with...periods.mp4", "s3://bucket1/file.with :=+%sp3c!l-ch4cts().mp4"] }
175
+ let(:clean) { ["_file_with_single_quote_.mp4", "_file_with_double_quote_.mp4", "file_with_space.mp4", "filewithperiods.mp4", "filewith_____sp3c_l-ch4cts__.mp4"] }
176
+ let(:source_bucket) { "bucket2" }
177
+
178
+ it "calls the #check_s3_bucket method" do
179
+ allow(SecureRandom).to receive(:uuid).and_return("randomstring")
180
+ input_urls.each_with_index do |url, index|
181
+ expect(described_class.new.send(:copy_to_input_bucket, url, source_bucket)).to eq "randomstring/#{clean[index]}"
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+
140
188
  describe "#check_s3_bucket" do
141
189
  context "when file exists in masterfile_bucket" do
142
190
  let(:input_url) { "s3://bucket1/file.mp4" }
@@ -38,6 +38,7 @@ describe ActiveEncode::EngineAdapters::FfmpegAdapter do
38
38
  end
39
39
  let(:completed_job) { find_encode "completed-id" }
40
40
  let(:completed_with_warnings_job) { find_encode "completed-with-warnings-id" }
41
+ let(:incomplete_job) { find_encode "incomplete-id" }
41
42
  let(:failed_job) { find_encode 'failed-id' }
42
43
  let(:completed_tech_metadata) do
43
44
  {
@@ -147,6 +148,114 @@ describe ActiveEncode::EngineAdapters::FfmpegAdapter do
147
148
  end
148
149
  end
149
150
 
151
+ context "input filename with single quotes" do
152
+ let(:file_with_single_quote) { "file://" + Rails.root.join('..', 'spec', 'fixtures', "'file_with_single_quote'.mp4").to_s }
153
+ let!(:create_single_quote_job) { ActiveEncode::Base.create(file_with_single_quote, outputs: [{ label: "low", ffmpeg_opt: "-s 640x480", extension: 'mp4' }]) }
154
+ let(:find_single_quote_job) { ActiveEncode::Base.find create_single_quote_job.id }
155
+
156
+ it "does not have errors" do
157
+ sleep 2
158
+ expect(find_single_quote_job.errors).to be_empty
159
+ end
160
+
161
+ it "has the input technical metadata in a file" do
162
+ expect(File.read("#{work_dir}/#{create_single_quote_job.id}/input_metadata")).not_to be_empty
163
+ end
164
+
165
+ it "has the pid in a file" do
166
+ expect(File.read("#{work_dir}/#{create_single_quote_job.id}/pid")).not_to be_empty
167
+ end
168
+
169
+ context 'when uri encoded' do
170
+ let(:file_with_single_quote) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', "'file_with_single_quote'.mp4").to_s) }
171
+
172
+ it "does not have errors" do
173
+ sleep 2
174
+ expect(find_single_quote_job.errors).to be_empty
175
+ end
176
+
177
+ it "has the input technical metadata in a file" do
178
+ expect(File.read("#{work_dir}/#{create_single_quote_job.id}/input_metadata")).not_to be_empty
179
+ end
180
+
181
+ it "has the pid in a file" do
182
+ expect(File.read("#{work_dir}/#{create_single_quote_job.id}/pid")).not_to be_empty
183
+ end
184
+ end
185
+ end
186
+
187
+ context "input filename with double quotes" do
188
+ let(:file_with_double_quote) { "file://" + Rails.root.join('..', 'spec', 'fixtures', '"file_with_double_quote".mp4').to_s }
189
+ let!(:create_double_quote_job) { ActiveEncode::Base.create(file_with_double_quote, outputs: [{ label: "low", ffmpeg_opt: "-s 640x480", extension: 'mp4' }]) }
190
+ let(:find_double_quote_job) { ActiveEncode::Base.find create_double_quote_job.id }
191
+
192
+ it "does not have errors" do
193
+ sleep 2
194
+ expect(find_double_quote_job.errors).to be_empty
195
+ end
196
+
197
+ it "has the input technical metadata in a file" do
198
+ expect(File.read("#{work_dir}/#{create_double_quote_job.id}/input_metadata")).not_to be_empty
199
+ end
200
+
201
+ it "has the pid in a file" do
202
+ expect(File.read("#{work_dir}/#{create_double_quote_job.id}/pid")).not_to be_empty
203
+ end
204
+
205
+ context 'when uri encoded' do
206
+ let(:file_with_double_quote) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', '"file_with_double_quote".mp4').to_s) }
207
+
208
+ it "does not have errors" do
209
+ sleep 2
210
+ expect(find_double_quote_job.errors).to be_empty
211
+ end
212
+
213
+ it "has the input technical metadata in a file" do
214
+ expect(File.read("#{work_dir}/#{create_double_quote_job.id}/input_metadata")).not_to be_empty
215
+ end
216
+
217
+ it "has the pid in a file" do
218
+ expect(File.read("#{work_dir}/#{create_double_quote_job.id}/pid")).not_to be_empty
219
+ end
220
+ end
221
+ end
222
+
223
+ context "input filename with other special characters" do
224
+ let(:file_with_special_characters) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file.with :=+%sp3c!l-ch4cts().mp4').to_s }
225
+ let!(:create_special_characters_job) { ActiveEncode::Base.create(file_with_special_characters, outputs: [{ label: "low", ffmpeg_opt: "-s 640x480", extension: 'mp4' }]) }
226
+ let(:find_special_characters_job) { ActiveEncode::Base.find create_special_characters_job.id }
227
+
228
+ it "does not have errors" do
229
+ sleep 2
230
+ expect(find_special_characters_job.errors).to be_empty
231
+ end
232
+
233
+ it "has the input technical metadata in a file" do
234
+ expect(File.read("#{work_dir}/#{create_special_characters_job.id}/input_metadata")).not_to be_empty
235
+ end
236
+
237
+ it "has the pid in a file" do
238
+ expect(File.read("#{work_dir}/#{create_special_characters_job.id}/pid")).not_to be_empty
239
+ end
240
+
241
+ context 'when uri encoded' do
242
+ let(:file_with_special_characters) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', 'file.with :=+%sp3c!l-ch4cts().mp4').to_s) }
243
+
244
+ it "does not have errors" do
245
+ sleep 2
246
+ expect(find_special_characters_job.errors).to be_empty
247
+ end
248
+
249
+ it "has the input technical metadata in a file" do
250
+ expect(File.read("#{work_dir}/#{create_special_characters_job.id}/input_metadata")).not_to be_empty
251
+ end
252
+
253
+ it "has the pid in a file" do
254
+ expect(File.read("#{work_dir}/#{create_special_characters_job.id}/pid")).not_to be_empty
255
+ end
256
+ end
257
+ end
258
+
150
259
  context 'when failed' do
151
260
  subject { created_job }
152
261
 
@@ -212,6 +321,21 @@ describe ActiveEncode::EngineAdapters::FfmpegAdapter do
212
321
  expect(File).to exist("#{work_dir}/#{subject.id}/exit_status.code")
213
322
  expect(File.read("#{work_dir}/#{subject.id}/exit_status.code").to_i).to eq(-22)
214
323
  end
324
+
325
+ context 'with less than 100 percent completeness' do
326
+ subject { incomplete_job }
327
+
328
+ it { is_expected.to be_failed }
329
+ it 'has an error' do
330
+ expect(incomplete_job.errors).to include "Encoding has completed but the output duration is shorter than the input"
331
+ end
332
+
333
+ it 'succeeds with a configured completeness threshold' do
334
+ allow(ActiveEncode::EngineAdapters::FfmpegAdapter).to receive(:completeness_threshold).and_return(95)
335
+ expect(incomplete_job).not_to be_failed
336
+ expect(incomplete_job.errors).to be_empty
337
+ end
338
+ end
215
339
  end
216
340
  end
217
341
 
@@ -40,12 +40,15 @@ describe ActiveEncode::EngineAdapters::MediaConvertAdapter do
40
40
  let(:cloudwatch_events) { Aws::CloudWatchEvents::Client.new(stub_responses: true) }
41
41
  let(:cloudwatch_logs) { Aws::CloudWatchLogs::Client.new(stub_responses: true) }
42
42
 
43
+ let(:s3client) { Aws::S3::Client.new(stub_responses: true) }
44
+
43
45
  before do
44
46
  mediaconvert.stub_responses(:describe_endpoints, reconstitute_response("media_convert/endpoints.json"))
45
47
 
46
48
  allow(Aws::MediaConvert::Client).to receive(:new).and_return(mediaconvert)
47
49
  allow(Aws::CloudWatchEvents::Client).to receive(:new).and_return(cloudwatch_events)
48
50
  allow(Aws::CloudWatchLogs::Client).to receive(:new).and_return(cloudwatch_logs)
51
+ allow(Aws::S3::Client).to receive(:new).and_return(s3client)
49
52
  end
50
53
 
51
54
  let(:created_job) do
@@ -267,4 +270,52 @@ describe ActiveEncode::EngineAdapters::MediaConvertAdapter do
267
270
  completed_job
268
271
  end
269
272
  end
273
+
274
+ describe "#s3_uri" do
275
+ context "when filename has no special characters" do
276
+ context "non-s3 file" do
277
+ let(:input_url) { "spec/fixtures/fireworks.mp4" }
278
+ let(:source_bucket) { "bucket1" }
279
+
280
+ it "calls the #upload_to_s3 method" do
281
+ allow(SecureRandom).to receive(:uuid).and_return("randomstring")
282
+ expect(described_class.new.send(:s3_uri, input_url, { masterfile_bucket: source_bucket })).to eq "randomstring/fireworks.mp4"
283
+ end
284
+ end
285
+ context "s3 file" do
286
+ let(:input_url) { "s3://bucket1/file.mp4" }
287
+ let(:source_bucket) { "bucket1" }
288
+
289
+ it "calls the #check_s3_bucket method" do
290
+ expect(described_class.new.send(:s3_uri, input_url, { masterfile_bucket: source_bucket })).to eq "file.mp4"
291
+ end
292
+ end
293
+ end
294
+ context "when filename has special characters" do
295
+ context "non-s3 file" do
296
+ let(:input) { ["'file_with_single_quote'.mp4", '"file_with_double_quote".mp4', "file with space.mp4", "file.with...periods.mp4", "file.with :=+%sp3c!l-ch4cts().mp4"] }
297
+ let(:clean) { ["_file_with_single_quote_.mp4", "_file_with_double_quote_.mp4", "file_with_space.mp4", "filewithperiods.mp4", "filewith_____sp3c_l-ch4cts__.mp4"] }
298
+ let(:source_bucket) { "bucket1" }
299
+
300
+ it "calls the #upload_to_s3 method" do
301
+ allow(SecureRandom).to receive(:uuid).and_return("randomstring")
302
+ input.each_with_index do |url, index|
303
+ expect(described_class.new.send(:s3_uri, "spec/fixtures/#{url}", { masterfile_bucket: source_bucket })).to eq "randomstring/#{clean[index]}"
304
+ end
305
+ end
306
+ end
307
+ context "s3 file" do
308
+ let(:input_urls) { ["s3://bucket1/'file_with_single_quote'.mp4", 's3://bucket1/"file_with_double_quote".mp4', "s3://bucket1/file with space.mp4", "s3://bucket1/file.with...periods.mp4", "s3://bucket1/file.with :=+%sp3c!l-ch4cts().mp4"] }
309
+ let(:clean) { ["_file_with_single_quote_.mp4", "_file_with_double_quote_.mp4", "file_with_space.mp4", "filewithperiods.mp4", "filewith_____sp3c_l-ch4cts__.mp4"] }
310
+ let(:source_bucket) { "bucket2" }
311
+
312
+ it "calls the #check_s3_bucket method" do
313
+ allow(SecureRandom).to receive(:uuid).and_return("randomstring")
314
+ input_urls.each_with_index do |url, index|
315
+ expect(described_class.new.send(:s3_uri, url, { masterfile_bucket: source_bucket })).to eq "randomstring/#{clean[index]}"
316
+ end
317
+ end
318
+ end
319
+ end
320
+ end
270
321
  end
@@ -137,6 +137,90 @@ describe ActiveEncode::EngineAdapters::PassThroughAdapter do
137
137
  end
138
138
  end
139
139
 
140
+ context "input filename with single quotes" do
141
+ let(:file_with_single_quote) { "file://" + Rails.root.join('..', 'spec', 'fixtures', "'file_with_single_quote'.mp4").to_s }
142
+ let(:file_with_single_quote_derivative) { "file://" + Rails.root.join('..', 'spec', 'fixtures', "'file_with_single_quote'.low.mp4").to_s }
143
+ let!(:create_single_quote_job) { ActiveEncode::Base.create(file_with_single_quote, outputs: [{ label: "low", url: file_with_single_quote_derivative }]) }
144
+ let(:find_single_quote_job) { ActiveEncode::Base.find create_single_quote_job.id }
145
+
146
+ it "does not have errors" do
147
+ expect(find_single_quote_job.errors).to be_empty
148
+ end
149
+
150
+ it "has the input technical metadata in a file" do
151
+ expect(File.read("#{work_dir}/#{create_single_quote_job.id}/input_metadata")).not_to be_empty
152
+ end
153
+
154
+ context 'when uri encoded' do
155
+ let(:file_with_single_quote) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', "'file_with_single_quote'.mp4").to_s) }
156
+ let(:file_with_single_quote_derivative) { "file://" + Rails.root.join('..', 'spec', 'fixtures', "'file_with_single_quote'.low.mp4").to_s }
157
+
158
+ it "does not have errors" do
159
+ expect(find_single_quote_job.errors).to be_empty
160
+ end
161
+
162
+ it "has the input technical metadata in a file" do
163
+ expect(File.read("#{work_dir}/#{create_single_quote_job.id}/input_metadata")).not_to be_empty
164
+ end
165
+ end
166
+ end
167
+
168
+ context "input filename with double quotes" do
169
+ let(:file_with_double_quote) { "file://" + Rails.root.join('..', 'spec', 'fixtures', '"file_with_double_quote".mp4').to_s }
170
+ let(:file_with_double_quote_derivative) { "file://" + Rails.root.join('..', 'spec', 'fixtures', '"file_with_double_quote".mp4').to_s }
171
+ let!(:create_double_quote_job) { ActiveEncode::Base.create(file_with_double_quote, outputs: [{ label: "low", url: file_with_double_quote_derivative }]) }
172
+ let(:find_double_quote_job) { ActiveEncode::Base.find create_double_quote_job.id }
173
+
174
+ it "does not have errors" do
175
+ expect(find_double_quote_job.errors).to be_empty
176
+ end
177
+
178
+ it "has the input technical metadata in a file" do
179
+ expect(File.read("#{work_dir}/#{create_double_quote_job.id}/input_metadata")).not_to be_empty
180
+ end
181
+
182
+ context 'when uri encoded' do
183
+ let(:file_with_double_quote) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', '"file_with_double_quote".mp4').to_s) }
184
+ let(:file_with_double_quote_derivative) { "file://" + Rails.root.join('..', 'spec', 'fixtures', '"file_with_double_quote".mp4').to_s }
185
+
186
+ it "does not have errors" do
187
+ expect(find_double_quote_job.errors).to be_empty
188
+ end
189
+
190
+ it "has the input technical metadata in a file" do
191
+ expect(File.read("#{work_dir}/#{create_double_quote_job.id}/input_metadata")).not_to be_empty
192
+ end
193
+ end
194
+ end
195
+
196
+ context "input filename with other special characters" do
197
+ let(:file_with_special_characters) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file.with :=+%sp3c!l-ch4cts().mp4').to_s }
198
+ let(:file_with_special_characters_derivative) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file.with :=+%sp3c!l-ch4cts().mp4').to_s }
199
+ let!(:create_special_characters_job) { ActiveEncode::Base.create(file_with_special_characters, outputs: [{ label: "low", url: file_with_special_characters_derivative }]) }
200
+ let(:find_special_characters_job) { ActiveEncode::Base.find create_special_characters_job.id }
201
+
202
+ it "does not have errors" do
203
+ expect(find_special_characters_job.errors).to be_empty
204
+ end
205
+
206
+ it "has the input technical metadata in a file" do
207
+ expect(File.read("#{work_dir}/#{create_special_characters_job.id}/input_metadata")).not_to be_empty
208
+ end
209
+
210
+ context 'when uri encoded' do
211
+ let(:file_with_special_characters) { Addressable::URI.encode("file://" + Rails.root.join('..', 'spec', 'fixtures', 'file.with :=+%sp3c!l-ch4cts().mp4').to_s) }
212
+ let(:file_with_special_characters_derivative) { "file://" + Rails.root.join('..', 'spec', 'fixtures', 'file.with :=+%sp3c!l-ch4cts().mp4').to_s }
213
+
214
+ it "does not have errors" do
215
+ expect(find_special_characters_job.errors).to be_empty
216
+ end
217
+
218
+ it "has the input technical metadata in a file" do
219
+ expect(File.read("#{work_dir}/#{create_special_characters_job.id}/input_metadata")).not_to be_empty
220
+ end
221
+ end
222
+ end
223
+
140
224
  context 'when failed' do
141
225
  subject { created_job }
142
226
 
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.0.0
4
+ version: 1.1.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: 2022-07-13 00:00:00.000000000 Z
11
+ date: 2022-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -285,6 +285,7 @@ files:
285
285
  - lib/active_encode/engine_adapters/test_adapter.rb
286
286
  - lib/active_encode/engine_adapters/zencoder_adapter.rb
287
287
  - lib/active_encode/errors.rb
288
+ - lib/active_encode/filename_sanitizer.rb
288
289
  - lib/active_encode/global_id.rb
289
290
  - lib/active_encode/input.rb
290
291
  - lib/active_encode/output.rb
@@ -297,6 +298,10 @@ files:
297
298
  - lib/active_encode/version.rb
298
299
  - lib/file_locator.rb
299
300
  - spec/controllers/encode_record_controller_spec.rb
301
+ - spec/fixtures/"file_with_double_quote".low.mp4
302
+ - spec/fixtures/"file_with_double_quote".mp4
303
+ - spec/fixtures/'file_with_single_quote'.low.mp4
304
+ - spec/fixtures/'file_with_single_quote'.mp4
300
305
  - spec/fixtures/Bars_512kb.mp4
301
306
  - spec/fixtures/elastic_transcoder/input_completed.json
302
307
  - spec/fixtures/elastic_transcoder/input_generic.json
@@ -340,12 +345,23 @@ files:
340
345
  - spec/fixtures/ffmpeg/failed-id/input_metadata
341
346
  - spec/fixtures/ffmpeg/failed-id/pid
342
347
  - spec/fixtures/ffmpeg/failed-id/progress
348
+ - spec/fixtures/ffmpeg/incomplete-id/error.log
349
+ - spec/fixtures/ffmpeg/incomplete-id/exit_status.code
350
+ - spec/fixtures/ffmpeg/incomplete-id/input_metadata
351
+ - spec/fixtures/ffmpeg/incomplete-id/output_metadata-high
352
+ - spec/fixtures/ffmpeg/incomplete-id/output_metadata-low
353
+ - spec/fixtures/ffmpeg/incomplete-id/pid
354
+ - spec/fixtures/ffmpeg/incomplete-id/progress
355
+ - spec/fixtures/ffmpeg/incomplete-id/video-high.mp4
356
+ - spec/fixtures/ffmpeg/incomplete-id/video-low.mp4
343
357
  - spec/fixtures/ffmpeg/running-id/error.log
344
358
  - spec/fixtures/ffmpeg/running-id/input_metadata
345
359
  - spec/fixtures/ffmpeg/running-id/pid
346
360
  - spec/fixtures/ffmpeg/running-id/progress
347
361
  - spec/fixtures/file with space.low.mp4
348
362
  - spec/fixtures/file with space.mp4
363
+ - spec/fixtures/file.with :=+%sp3c!l-ch4cts().mp4
364
+ - spec/fixtures/file.with...periods.mp4
349
365
  - spec/fixtures/fireworks.low.mp4
350
366
  - spec/fixtures/fireworks.mp4
351
367
  - spec/fixtures/matterhorn/cancelled_response.xml
@@ -435,6 +451,10 @@ specification_version: 4
435
451
  summary: Declare encode job classes that can be run by a variety of encoding services
436
452
  test_files:
437
453
  - spec/controllers/encode_record_controller_spec.rb
454
+ - spec/fixtures/"file_with_double_quote".low.mp4
455
+ - spec/fixtures/"file_with_double_quote".mp4
456
+ - spec/fixtures/'file_with_single_quote'.low.mp4
457
+ - spec/fixtures/'file_with_single_quote'.mp4
438
458
  - spec/fixtures/Bars_512kb.mp4
439
459
  - spec/fixtures/elastic_transcoder/input_completed.json
440
460
  - spec/fixtures/elastic_transcoder/input_generic.json
@@ -478,12 +498,23 @@ test_files:
478
498
  - spec/fixtures/ffmpeg/failed-id/input_metadata
479
499
  - spec/fixtures/ffmpeg/failed-id/pid
480
500
  - spec/fixtures/ffmpeg/failed-id/progress
501
+ - spec/fixtures/ffmpeg/incomplete-id/error.log
502
+ - spec/fixtures/ffmpeg/incomplete-id/exit_status.code
503
+ - spec/fixtures/ffmpeg/incomplete-id/input_metadata
504
+ - spec/fixtures/ffmpeg/incomplete-id/output_metadata-high
505
+ - spec/fixtures/ffmpeg/incomplete-id/output_metadata-low
506
+ - spec/fixtures/ffmpeg/incomplete-id/pid
507
+ - spec/fixtures/ffmpeg/incomplete-id/progress
508
+ - spec/fixtures/ffmpeg/incomplete-id/video-high.mp4
509
+ - spec/fixtures/ffmpeg/incomplete-id/video-low.mp4
481
510
  - spec/fixtures/ffmpeg/running-id/error.log
482
511
  - spec/fixtures/ffmpeg/running-id/input_metadata
483
512
  - spec/fixtures/ffmpeg/running-id/pid
484
513
  - spec/fixtures/ffmpeg/running-id/progress
485
514
  - spec/fixtures/file with space.low.mp4
486
515
  - spec/fixtures/file with space.mp4
516
+ - spec/fixtures/file.with :=+%sp3c!l-ch4cts().mp4
517
+ - spec/fixtures/file.with...periods.mp4
487
518
  - spec/fixtures/fireworks.low.mp4
488
519
  - spec/fixtures/fireworks.mp4
489
520
  - spec/fixtures/matterhorn/cancelled_response.xml