active_encode 1.0.0 → 1.1.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.
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