aspera-cli 4.27.0 → 4.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +46 -1
- data/CONTRIBUTING.md +1 -7
- data/TODO.md +50 -0
- data/docs/README.md +709 -489
- data/lib/aspera/agent/base.rb +10 -2
- data/lib/aspera/agent/connect.rb +7 -7
- data/lib/aspera/agent/desktop.rb +3 -3
- data/lib/aspera/agent/direct.rb +48 -19
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +2 -2
- data/lib/aspera/agent/transferd.rb +12 -12
- data/lib/aspera/api/aoc.rb +28 -28
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +2 -2
- data/lib/aspera/api/httpgw.rb +20 -20
- data/lib/aspera/api/node.rb +31 -53
- data/lib/aspera/ascmd.rb +13 -13
- data/lib/aspera/ascp/installation.rb +43 -36
- data/lib/aspera/ascp/management.rb +6 -6
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +7 -7
- data/lib/aspera/cli/async_transfer_store.rb +49 -5
- data/lib/aspera/cli/bootstrapper.rb +9 -9
- data/lib/aspera/cli/command_registry.rb +1 -1
- data/lib/aspera/cli/command_spec.rb +6 -3
- data/lib/aspera/cli/context.rb +3 -3
- data/lib/aspera/cli/extended_value.rb +26 -26
- data/lib/aspera/cli/formatter.rb +76 -54
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +22 -29
- data/lib/aspera/cli/mailer.rb +7 -7
- data/lib/aspera/cli/mcp_tool.rb +31 -4
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +273 -3
- data/lib/aspera/cli/parser.rb +539 -280
- data/lib/aspera/cli/plugins/alee.rb +1 -1
- data/lib/aspera/cli/plugins/aoc.rb +303 -314
- data/lib/aspera/cli/plugins/ats.rb +25 -21
- data/lib/aspera/cli/plugins/base.rb +357 -186
- data/lib/aspera/cli/plugins/basic_auth.rb +0 -12
- data/lib/aspera/cli/plugins/config.rb +76 -73
- data/lib/aspera/cli/plugins/console.rb +8 -8
- data/lib/aspera/cli/plugins/cos.rb +4 -3
- data/lib/aspera/cli/plugins/factory.rb +2 -2
- data/lib/aspera/cli/plugins/faspex5.rb +259 -248
- data/lib/aspera/cli/plugins/faspio.rb +7 -2
- data/lib/aspera/cli/plugins/httpgw.rb +1 -1
- data/lib/aspera/cli/plugins/mcp.rb +11 -11
- data/lib/aspera/cli/plugins/node.rb +227 -283
- data/lib/aspera/cli/plugins/orchestrator.rb +23 -23
- data/lib/aspera/cli/plugins/preview.rb +61 -64
- data/lib/aspera/cli/plugins/server.rb +36 -31
- data/lib/aspera/cli/plugins/shares.rb +174 -135
- data/lib/aspera/cli/preset_actions.rb +39 -19
- data/lib/aspera/cli/preset_manager.rb +74 -32
- data/lib/aspera/cli/result.rb +18 -16
- data/lib/aspera/cli/runner.rb +75 -39
- data/lib/aspera/cli/secret_finder.rb +1 -1
- data/lib/aspera/cli/sync_actions.rb +36 -35
- data/lib/aspera/cli/terminal_formatter.rb +2 -2
- data/lib/aspera/cli/transfer_actions.rb +17 -10
- data/lib/aspera/cli/transfer_agent.rb +44 -44
- data/lib/aspera/cli/transfer_progress.rb +7 -7
- data/lib/aspera/cli/vault_manager.rb +34 -12
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +17 -20
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +17 -16
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/dot_container.rb +8 -8
- data/lib/aspera/environment.rb +13 -13
- data/lib/aspera/faspex_gw.rb +4 -4
- data/lib/aspera/faspex_postproc.rb +14 -9
- data/lib/aspera/graphql.rb +4 -2
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/json_rpc/client.rb +1 -1
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +17 -5
- data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +23 -25
- data/lib/aspera/keychain/one_password_api.rb +86 -0
- data/lib/aspera/keychain/one_password_base.rb +34 -0
- data/lib/aspera/keychain/one_password_cli.rb +98 -0
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +13 -13
- data/lib/aspera/markdown.rb +5 -5
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +33 -27
- data/lib/aspera/oauth/base.rb +4 -5
- data/lib/aspera/oauth/boot.rb +3 -3
- data/lib/aspera/oauth/factory.rb +5 -5
- data/lib/aspera/oauth/jwt.rb +5 -7
- data/lib/aspera/oauth/web.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +8 -8
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +49 -46
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +65 -18
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +10 -10
- data/lib/aspera/rest.rb +149 -115
- data/lib/aspera/rest_error_analyzer.rb +3 -3
- data/lib/aspera/rest_list.rb +10 -9
- data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
- data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +39 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +17 -11
- data/lib/aspera/schema/reader.rb +78 -4
- data/lib/aspera/schema/registry.rb +26 -7
- data/lib/aspera/secret_hider.rb +5 -5
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +3 -3
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +13 -13
- data/lib/aspera/temp_file_manager.rb +24 -4
- data/lib/aspera/transfer/faux_file.rb +2 -2
- data/lib/aspera/transfer/parameters.rb +15 -15
- data/lib/aspera/transfer/resumer.rb +9 -9
- data/lib/aspera/transfer/spec.rb +1 -1
- data/lib/aspera/transfer/spec.schema.yaml +1 -0
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +6 -5
- data/lib/aspera/web_server_simple.rb +9 -9
- data/lib/aspera/yaml.rb +4 -4
- data.tar.gz.sig +0 -0
- metadata +10 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/plugins/faspex.rb +0 -576
- data/lib/aspera/transfer/uri.rb +0 -56
|
@@ -57,21 +57,21 @@ module Aspera
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
@processing_method = @processing_method.to_sym
|
|
60
|
-
Log.log.debug{"method: #{@processing_method}"}
|
|
61
|
-
Aspera.assert(respond_to?(@processing_method, true)){"no processing known for #{conversion_type} -> #{@preview_format_sym}"}
|
|
60
|
+
Log.log.debug { "method: #{@processing_method}" }
|
|
61
|
+
Aspera.assert(respond_to?(@processing_method, true)) { "no processing known for #{conversion_type} -> #{@preview_format_sym}" }
|
|
62
62
|
command = [:magick] + %w[identify -list font]
|
|
63
63
|
magick_fonts = Utils.parse_magick_fonts(Utils.execute(*command, mode: :capture).first)
|
|
64
|
-
Aspera.assert(magick_fonts[:fonts].any?{ |f| f[:name].eql?(@options.thumb_text_font)}){"Missing font #{@options.thumb_text_font} in #{command}"}
|
|
64
|
+
Aspera.assert(magick_fonts[:fonts].any? { |f| f[:name].eql?(@options.thumb_text_font) }) { "Missing font #{@options.thumb_text_font} in #{command}" }
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Creates preview as specified in constructor.
|
|
68
68
|
def generate
|
|
69
|
-
Log.log.debug{"#{@source}->#{@destination} (#{@processing_method})"}
|
|
69
|
+
Log.log.debug { "#{@source}->#{@destination} (#{@processing_method})" }
|
|
70
70
|
begin
|
|
71
71
|
send(@processing_method)
|
|
72
72
|
# Check that generated size does not exceed maximum.
|
|
73
73
|
result_size = File.size(@destination)
|
|
74
|
-
Log.log.warn{"preview size exceeds maximum allowed #{result_size} > #{@options.max_size}"} if result_size > @options.max_size
|
|
74
|
+
Log.log.warn { "preview size exceeds maximum allowed #{result_size} > #{@options.max_size}" } if result_size > @options.max_size
|
|
75
75
|
ensure
|
|
76
76
|
FileUtils.rm_rf(@temp_folder)
|
|
77
77
|
end
|
|
@@ -99,7 +99,7 @@ module Aspera
|
|
|
99
99
|
# @param index [Integer] Index of part (starts at 1).
|
|
100
100
|
# @return [Float] Offset in seconds suitable for ffmpeg -ss option.
|
|
101
101
|
def get_offset(duration, start_offset, total_count, index)
|
|
102
|
-
Aspera.assert_type(duration, Float){'duration'}
|
|
102
|
+
Aspera.assert_type(duration, Float) { 'duration' }
|
|
103
103
|
return start_offset + ((index - 1) * (duration - start_offset) / total_count)
|
|
104
104
|
end
|
|
105
105
|
|
|
@@ -127,10 +127,12 @@ module Aspera
|
|
|
127
127
|
current_index = last_keyframe + 1 + @options.blend_transframes
|
|
128
128
|
end
|
|
129
129
|
Utils.ffmpeg(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
in: [
|
|
131
|
+
Utils.ffmpeg_fmt(this_tmpdir),
|
|
132
|
+
'-framerate', @options.blend_fps
|
|
133
|
+
],
|
|
134
|
+
out: [
|
|
135
|
+
@destination,
|
|
134
136
|
'-filter:v', "scale='trunc(iw/2)*2:trunc(ih/2)*2'",
|
|
135
137
|
'-codec:v', 'libx264',
|
|
136
138
|
'-r', frame_rate_hz,
|
|
@@ -149,13 +151,15 @@ module Aspera
|
|
|
149
151
|
offset_seconds = get_offset(p_duration, @options.video_start_sec.to_i, @options.clips_count.to_i, i)
|
|
150
152
|
tmp_file_name = format('clip%04d.mp4', i)
|
|
151
153
|
Utils.ffmpeg(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
in: [
|
|
155
|
+
@source,
|
|
156
|
+
'-ss', offset_seconds * 0.9
|
|
157
|
+
],
|
|
158
|
+
out: [
|
|
159
|
+
File.join(this_tmpdir, tmp_file_name),
|
|
156
160
|
'-ss', offset_seconds * 0.1,
|
|
157
161
|
'-t', @options.clips_length,
|
|
158
|
-
'-filter:v', "scale
|
|
162
|
+
'-filter:v', "scale='#{@options.video_scale}'",
|
|
159
163
|
'-codec:a', 'libmp3lame'
|
|
160
164
|
]
|
|
161
165
|
)
|
|
@@ -164,10 +168,8 @@ module Aspera
|
|
|
164
168
|
end
|
|
165
169
|
# Concat clips.
|
|
166
170
|
Utils.ffmpeg(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
out_f: @destination,
|
|
170
|
-
out_p: ['-codec', 'copy']
|
|
171
|
+
in: [file_list_file, '-f', 'concat'],
|
|
172
|
+
out: [@destination, '-codec', 'copy']
|
|
171
173
|
)
|
|
172
174
|
File.delete(file_list_file)
|
|
173
175
|
end
|
|
@@ -176,31 +178,32 @@ module Aspera
|
|
|
176
178
|
# Performs a simple re-encoding with configurable ffmpeg options.
|
|
177
179
|
def convert_video_to_mp4_using_reencode
|
|
178
180
|
options = @options.reencode_ffmpeg
|
|
179
|
-
Aspera.assert_type(options, Hash){'reencode_ffmpeg'}
|
|
181
|
+
Aspera.assert_type(options, Hash) { 'reencode_ffmpeg' }
|
|
180
182
|
options.each do |k, v|
|
|
181
|
-
Aspera.assert_values(k, FFMPEG_OPTIONS_LIST){'key'}
|
|
182
|
-
Aspera.assert_type(v, Array){k}
|
|
183
|
+
Aspera.assert_values(k, FFMPEG_OPTIONS_LIST) { 'key' }
|
|
184
|
+
Aspera.assert_type(v, Array) { k }
|
|
183
185
|
end
|
|
186
|
+
codec = @options.video_codec || Utils.available_h264_encoder
|
|
187
|
+
in_opts = options['in'] || ['-ss', @options.video_start_sec.to_i * 0.9]
|
|
188
|
+
out_opts = options['out'] || [
|
|
189
|
+
'-t', 60,
|
|
190
|
+
'-codec:v', codec,
|
|
191
|
+
'-profile:v', 'high',
|
|
192
|
+
'-pix_fmt', 'yuv420p',
|
|
193
|
+
'-preset', 'slow',
|
|
194
|
+
'-b:v', '500k',
|
|
195
|
+
'-maxrate', '500k',
|
|
196
|
+
'-bufsize', '1000k',
|
|
197
|
+
'-filter:v', "scale='#{@options.video_scale}'",
|
|
198
|
+
'-threads', '0',
|
|
199
|
+
'-codec:a', 'libmp3lame',
|
|
200
|
+
'-ac', '2',
|
|
201
|
+
'-b:a', '128k',
|
|
202
|
+
'-movflags', 'faststart'
|
|
203
|
+
]
|
|
184
204
|
Utils.ffmpeg(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
out_f: @destination,
|
|
188
|
-
out_p: options['out'] || [
|
|
189
|
-
'-t', 60,
|
|
190
|
-
'-codec:v', 'libx264',
|
|
191
|
-
'-profile:v', 'high',
|
|
192
|
-
'-pix_fmt', 'yuv420p',
|
|
193
|
-
'-preset', 'slow',
|
|
194
|
-
'-b:v', '500k',
|
|
195
|
-
'-maxrate', '500k',
|
|
196
|
-
'-bufsize', '1000k',
|
|
197
|
-
'-filter:v', "scale=#{@options.video_scale}",
|
|
198
|
-
'-threads', '0',
|
|
199
|
-
'-codec:a', 'libmp3lame',
|
|
200
|
-
'-ac', '2',
|
|
201
|
-
'-b:a', '128k',
|
|
202
|
-
'-movflags', 'faststart'
|
|
203
|
-
]
|
|
205
|
+
in: [@source, *in_opts],
|
|
206
|
+
out: [@destination, *out_opts]
|
|
204
207
|
)
|
|
205
208
|
end
|
|
206
209
|
|
|
@@ -228,13 +231,13 @@ module Aspera
|
|
|
228
231
|
p_max_duration = p_duration
|
|
229
232
|
end
|
|
230
233
|
Utils.ffmpeg(
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
in: [
|
|
235
|
+
@source,
|
|
233
236
|
'-ss', p_start_offset,
|
|
234
237
|
'-t', p_max_duration
|
|
235
238
|
],
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
out: [
|
|
240
|
+
@destination,
|
|
238
241
|
'-vf', 'fps=5,scale=120:-1:flags=lanczos',
|
|
239
242
|
'-plays', 0, # Loop forever (0 = infinite loop for APNG).
|
|
240
243
|
'-f', 'apng'
|
|
@@ -265,7 +268,7 @@ module Aspera
|
|
|
265
268
|
# soffice creates the file with the source name, so we need to rename it if needed.
|
|
266
269
|
generated_pdf = File.join(File.dirname(tmp_pdf_file), "#{File.basename(@source, File.extname(@source))}.pdf")
|
|
267
270
|
FileUtils.mv(generated_pdf, tmp_pdf_file) if generated_pdf != tmp_pdf_file
|
|
268
|
-
else Aspera.error_unexpected_value(@options.office_conversion){'office_conversion'}
|
|
271
|
+
else Aspera.error_unexpected_value(@options.office_conversion) { 'office_conversion' }
|
|
269
272
|
end
|
|
270
273
|
convert_pdf_to_png(tmp_pdf_file)
|
|
271
274
|
end
|
|
@@ -18,15 +18,16 @@ module Aspera
|
|
|
18
18
|
# -x : keep aspect ratio, having value a multiple of x
|
|
19
19
|
DESCRIPTIONS = [
|
|
20
20
|
{name: :max_size, default: 1 << 24, description: 'maximum size (in bytes) of preview file'},
|
|
21
|
-
{name: :thumb_vid_scale, default:
|
|
21
|
+
{name: :thumb_vid_scale, default: '-1:min(ih,100)', description: 'png: video: size (ffmpeg scale argument)'},
|
|
22
22
|
{name: :thumb_vid_fraction, default: 0.1, description: 'png: video: time percent position of snapshot'},
|
|
23
23
|
{name: :thumb_img_size, default: 800, description: 'png: non-video: height (and width)'},
|
|
24
24
|
{name: :thumb_text_font, default: 'Courier', description: 'png: plaintext: font for text rendering: `magick identify -list font`'},
|
|
25
25
|
{name: :office_conversion, default: :soffice, description: 'office: method for office document conversion', values: OFFICE_CONVERSION_METHODS},
|
|
26
26
|
{name: :video_conversion, default: :reencode, description: 'mp4: method for preview generation', values: VIDEO_CONVERSION_METHODS},
|
|
27
27
|
{name: :video_png_conv, default: :fixed, description: 'mp4: method for thumbnail generation', values: VIDEO_THUMBNAIL_METHODS},
|
|
28
|
-
{name: :video_scale, default:
|
|
28
|
+
{name: :video_scale, default: 'min(iw,360):-2', description: 'mp4: all: video scale (ffmpeg scale argument)'},
|
|
29
29
|
{name: :video_start_sec, default: 10, description: 'mp4: all: start offset (seconds) of video preview'},
|
|
30
|
+
{name: :video_codec, default: nil, description: 'mp4: reencode: video codec for ffmpeg (e.g. libx264, h264_videotoolbox, h264_nvenc), default: auto-detect'},
|
|
30
31
|
{name: :reencode_ffmpeg, default: {}, description: 'mp4: reencode: options to ffmpeg, keys: `in`, `out`'},
|
|
31
32
|
{name: :blend_keyframes, default: 30, description: 'mp4: blend: # key frames'},
|
|
32
33
|
{name: :blend_pauseframes, default: 3, description: 'mp4: blend: # pause frames'},
|
|
@@ -58,7 +58,7 @@ module Aspera
|
|
|
58
58
|
pixel_colors = []
|
|
59
59
|
@image.scale(*terminal_scaling(@image.rows, @image.columns)).each_pixel do |pixel, col, row|
|
|
60
60
|
pixel_rgb = [pixel.red, pixel.green, pixel.blue]
|
|
61
|
-
pixel_rgb = pixel_rgb.map{ |color| color >> shift_for_8_bit} unless shift_for_8_bit.eql?(0)
|
|
61
|
+
pixel_rgb = pixel_rgb.map { |color| color >> shift_for_8_bit } unless shift_for_8_bit.eql?(0)
|
|
62
62
|
# Initialize the destination 2D pixel matrix row by row.
|
|
63
63
|
pixel_colors[row] ||= []
|
|
64
64
|
pixel_colors[row][col] = pixel_rgb
|
|
@@ -87,7 +87,7 @@ module Aspera
|
|
|
87
87
|
dst_w, dst_h = terminal_scaling(src_h, src_w)
|
|
88
88
|
dst_w = [dst_w, 1].max
|
|
89
89
|
dst_h = [dst_h, 1].max
|
|
90
|
-
pixel_colors = Array.new(dst_h){Array.new(dst_w)}
|
|
90
|
+
pixel_colors = Array.new(dst_h) { Array.new(dst_w) }
|
|
91
91
|
x_ratio = src_w.to_f / dst_w
|
|
92
92
|
y_ratio = src_h.to_f / dst_h
|
|
93
93
|
dst_h.times do |dy|
|
|
@@ -98,7 +98,7 @@ module Aspera
|
|
|
98
98
|
sx = src_w - 1 if sx >= src_w
|
|
99
99
|
rgba = @png.get_pixel(sx, sy)
|
|
100
100
|
# ChunkyPNG stores pixels as 0xRRGGBBAA; extract 8-bit RGB channels.
|
|
101
|
-
pixel_colors[dy][dx] = %i[r g b].map{ |i| ::ChunkyPNG::Color.send(i, rgba)}
|
|
101
|
+
pixel_colors[dy][dx] = %i[r g b].map { |i| ::ChunkyPNG::Color.send(i, rgba) }
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
pixel_colors
|
|
@@ -178,7 +178,7 @@ module Aspera
|
|
|
178
178
|
size: blob.length
|
|
179
179
|
# width: image.columns,
|
|
180
180
|
# height: image.rows
|
|
181
|
-
}.map{ |k, v| "#{k}=#{v}"}.join(';')
|
|
181
|
+
}.map { |k, v| "#{k}=#{v}" }.join(';')
|
|
182
182
|
# `\a` is BEL and `\e` is ESC.
|
|
183
183
|
# See: https://github.com/ruby/ruby/blob/master/doc/syntax/literals.rdoc#label-Strings
|
|
184
184
|
# Return the full escape sequence expected by iTerm2-compatible terminals.
|
|
@@ -190,7 +190,7 @@ module Aspera
|
|
|
190
190
|
# @return [Boolean] `true` when the current terminal advertises iTerm2 image support
|
|
191
191
|
def iterm_supported?
|
|
192
192
|
TERM_ENV_VARS.each do |env_var|
|
|
193
|
-
return true if ITERM_NAMES.any?{ |term| ENV[env_var]&.include?(term)}
|
|
193
|
+
return true if ITERM_NAMES.any? { |term| ENV[env_var]&.include?(term) }
|
|
194
194
|
end
|
|
195
195
|
false
|
|
196
196
|
end
|
data/lib/aspera/preview/utils.rb
CHANGED
|
@@ -20,11 +20,25 @@ module Aspera
|
|
|
20
20
|
'-y', # overwrite output without asking
|
|
21
21
|
'-loglevel', 'error' # show only errors and up
|
|
22
22
|
].freeze
|
|
23
|
-
|
|
23
|
+
# Preferred H.264 encoders in order: best quality/compatibility first.
|
|
24
|
+
H264_ENCODER_PREFERENCE = %w[libx264 libopenh264 h264_nvenc h264_amf h264_qsv h264_vaapi h264_v4l2m2m].freeze
|
|
25
|
+
private_constant :EXTERNAL_TOOLS, :TEMP_FORMAT, :FFMPEG_DEFAULT_PARAMS, :H264_ENCODER_PREFERENCE
|
|
24
26
|
|
|
25
27
|
class << self
|
|
26
|
-
#
|
|
28
|
+
# Return the first H.264 encoder available in the local ffmpeg installation.
|
|
29
|
+
# Result is memoized after the first call.
|
|
30
|
+
# @return [String] encoder name (e.g. 'libx264', 'libopenh264')
|
|
31
|
+
# @raise [RuntimeError] if no supported H.264 encoder is found
|
|
32
|
+
def available_h264_encoder
|
|
33
|
+
return @available_h264_encoder if defined?(@available_h264_encoder)
|
|
34
|
+
stdout, = execute(:ffmpeg, '-encoders', mode: :capture, exception: false)
|
|
35
|
+
@available_h264_encoder = H264_ENCODER_PREFERENCE.find { |enc| stdout.include?(enc) } ||
|
|
36
|
+
raise("No supported H.264 encoder found in ffmpeg. Available: #{stdout.lines.grep(/h264/i).map(&:strip).join(', ')}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check that external tools can be executed.
|
|
27
40
|
# @param skip_types [Array<Symbol>] list of tools to skip
|
|
41
|
+
# @raise [RuntimeError] if a required tool binary is missing
|
|
28
42
|
# @return [nil]
|
|
29
43
|
def check_tools(skip_types = [])
|
|
30
44
|
tools_to_check = EXTERNAL_TOOLS.dup
|
|
@@ -42,28 +56,46 @@ module Aspera
|
|
|
42
56
|
end
|
|
43
57
|
end
|
|
44
58
|
|
|
45
|
-
# Execute external command, verify it is in the supported list
|
|
59
|
+
# Execute external command, verify it is in the supported list.
|
|
60
|
+
# @param args [Array] command name followed by CLI arguments
|
|
61
|
+
# @param kwargs [Hash] execution options passed to {Environment.secure_execute}
|
|
62
|
+
# @raise [Aspera::AssertError] if the command is not in {EXTERNAL_TOOLS}
|
|
63
|
+
# @return [Array<String>] captured stdout and stderr lines depending on mode
|
|
46
64
|
def execute(*args, **kwargs)
|
|
47
|
-
Aspera.assert_values(args.first, EXTERNAL_TOOLS){'command'}
|
|
65
|
+
Aspera.assert_values(args.first, EXTERNAL_TOOLS) { 'command' }
|
|
48
66
|
Environment.secure_execute(*args, **kwargs)
|
|
49
67
|
end
|
|
50
68
|
|
|
51
|
-
# Execute external command
|
|
69
|
+
# Execute external command, capturing and discarding output unless it fails.
|
|
70
|
+
# On failure, the captured stderr is included in the raised exception message.
|
|
71
|
+
# @param args [Array] command name followed by CLI arguments
|
|
52
72
|
# @return [nil]
|
|
53
73
|
def silent_execute(*args)
|
|
54
|
-
execute(*args,
|
|
74
|
+
execute(*args, mode: :capture)
|
|
55
75
|
nil
|
|
56
76
|
end
|
|
57
77
|
|
|
58
|
-
# Execute `ffmpeg
|
|
78
|
+
# Execute `ffmpeg`, capturing output.
|
|
79
|
+
# On failure, the ffmpeg stderr is logged at debug level and re-raised.
|
|
80
|
+
# @param in [Array] input file path followed by input options
|
|
81
|
+
# @param out [Array] output file path followed by output options
|
|
82
|
+
# @param global [Array<String>] global options for ffmpeg
|
|
59
83
|
# @return [nil]
|
|
60
|
-
def ffmpeg(
|
|
61
|
-
Aspera.assert_type(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
def ffmpeg(in:, out:, global: FFMPEG_DEFAULT_PARAMS)
|
|
85
|
+
Aspera.assert_type(global, Array)
|
|
86
|
+
# NOTE: cannot use just "in", as it is a reserved word in ruby
|
|
87
|
+
in_args = binding.local_variable_get(:in).dup
|
|
88
|
+
out_args = out.dup
|
|
89
|
+
Aspera.assert_type(in_args, Array)
|
|
90
|
+
Aspera.assert_type(out_args, Array)
|
|
91
|
+
in_file = in_args.shift
|
|
92
|
+
out_file = out_args.shift
|
|
93
|
+
execute(:ffmpeg, *global, *in_args, '-i', in_file, *out_args, out_file, mode: :capture)
|
|
94
|
+
nil
|
|
65
95
|
end
|
|
66
96
|
|
|
97
|
+
# Get duration of a video file using `ffprobe`.
|
|
98
|
+
# @param input_file [String] path to video file
|
|
67
99
|
# @return [Float] duration in seconds
|
|
68
100
|
def video_get_duration(input_file)
|
|
69
101
|
return execute(
|
|
@@ -76,23 +108,39 @@ module Aspera
|
|
|
76
108
|
).first.to_f
|
|
77
109
|
end
|
|
78
110
|
|
|
79
|
-
# File output
|
|
111
|
+
# File output pattern for ffmpeg, including temp folder.
|
|
112
|
+
# @param temp_folder [String] path to temp folder
|
|
113
|
+
# @return [String] file path pattern
|
|
80
114
|
def ffmpeg_fmt(temp_folder)
|
|
81
115
|
return File.join(temp_folder, TEMP_FORMAT)
|
|
82
116
|
end
|
|
83
117
|
|
|
118
|
+
# Get numbered temporary file path.
|
|
119
|
+
# @param temp_folder [String] path to temp folder
|
|
120
|
+
# @param file_number [Integer] frame index
|
|
121
|
+
# @return [String] file path
|
|
84
122
|
def get_tmp_num_filepath(temp_folder, file_number)
|
|
85
|
-
# Format using {Kernel.format}
|
|
86
123
|
return File.join(temp_folder, format(TEMP_FORMAT, file_number))
|
|
87
124
|
end
|
|
88
125
|
|
|
126
|
+
# Duplicate a video frame by creating symlinks.
|
|
127
|
+
# @param temp_folder [String] path to temp folder
|
|
128
|
+
# @param index [Integer] frame index to duplicate
|
|
129
|
+
# @param count [Integer] number of duplicate frames to create
|
|
130
|
+
# @return [nil]
|
|
89
131
|
def video_dupe_frame(temp_folder, index, count)
|
|
90
132
|
input_file = get_tmp_num_filepath(temp_folder, index)
|
|
91
133
|
1.upto(count) do |i|
|
|
92
134
|
FileUtils.ln_s(input_file, get_tmp_num_filepath(temp_folder, index + i))
|
|
93
135
|
end
|
|
136
|
+
nil
|
|
94
137
|
end
|
|
95
138
|
|
|
139
|
+
# Blend transition frames between two keyframes using ImageMagick.
|
|
140
|
+
# @param temp_folder [String] path to temp folder
|
|
141
|
+
# @param index_begin [Integer] starting frame index
|
|
142
|
+
# @param index_end [Integer] ending frame index
|
|
143
|
+
# @return [nil]
|
|
96
144
|
def video_blend_frames(temp_folder, index_begin, index_end)
|
|
97
145
|
img1 = get_tmp_num_filepath(temp_folder, index_begin)
|
|
98
146
|
img2 = get_tmp_num_filepath(temp_folder, index_end)
|
|
@@ -102,6 +150,7 @@ module Aspera
|
|
|
102
150
|
filename = get_tmp_num_filepath(temp_folder, index_begin + i)
|
|
103
151
|
silent_execute(:magick, 'composite', '-blend', percent, img2, img1, filename)
|
|
104
152
|
end
|
|
153
|
+
nil
|
|
105
154
|
end
|
|
106
155
|
|
|
107
156
|
# Dump a frame from a video file
|
|
@@ -112,10 +161,8 @@ module Aspera
|
|
|
112
161
|
# @return [nil]
|
|
113
162
|
def video_dump_frame(input_file, offset_seconds, scale, output_file)
|
|
114
163
|
ffmpeg(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
out_f: output_file,
|
|
118
|
-
out_p: ['-frames:v', 1, '-filter:v', "scale=#{scale}"]
|
|
164
|
+
in: [input_file, '-ss', offset_seconds],
|
|
165
|
+
out: [output_file, '-frames:v', 1, '-filter:v', "scale='#{scale}'"]
|
|
119
166
|
)
|
|
120
167
|
end
|
|
121
168
|
|
|
@@ -47,7 +47,7 @@ module Aspera
|
|
|
47
47
|
app_root: File.join(Dir.home, '.aspera', 'connect'),
|
|
48
48
|
run_root: File.join(Dir.home, '.aspera', 'connect')
|
|
49
49
|
}]
|
|
50
|
-
end.map{ |i| i.merge({expected: APP_NAME})}
|
|
50
|
+
end.map { |i| i.merge({expected: APP_NAME}) }
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -65,7 +65,7 @@ module Aspera
|
|
|
65
65
|
Log.dump(:javascript, connect_versions_javascript)
|
|
66
66
|
# get javascript object only
|
|
67
67
|
found = connect_versions_javascript.match(/^.*? = (.*);/)
|
|
68
|
-
Aspera.assert(!found.nil?, type: Cli::Error){'Problem when getting connect versions from internet'}
|
|
68
|
+
Aspera.assert(!found.nil?, type: Cli::Error) { 'Problem when getting connect versions from internet' }
|
|
69
69
|
all_data = JSON.parse(found[1])
|
|
70
70
|
@connect_versions = all_data['entries']
|
|
71
71
|
end
|
|
@@ -63,9 +63,9 @@ module Aspera
|
|
|
63
63
|
found = scan_locations.select do |item|
|
|
64
64
|
product_names.push(item[:expected]) unless product_names.include?(item[:expected])
|
|
65
65
|
# skip if not main folder
|
|
66
|
-
Log.log.trace1{"Checking #{item[:app_root]}"}
|
|
66
|
+
Log.log.trace1 { "Checking #{item[:app_root]}" }
|
|
67
67
|
next false unless Dir.exist?(item[:app_root])
|
|
68
|
-
Log.log.debug{"Found #{item[:expected]}"}
|
|
68
|
+
Log.log.debug { "Found #{item[:expected]}" }
|
|
69
69
|
sub_bin = item[:sub_bin] || 'bin'
|
|
70
70
|
item[:ascp_path] = File.join(item[:app_root], sub_bin, Environment.instance.exe_file('ascp'))
|
|
71
71
|
# skip if no ascp
|
|
@@ -17,12 +17,12 @@ module Aspera
|
|
|
17
17
|
[{
|
|
18
18
|
app_root: sdk_directory,
|
|
19
19
|
sub_bin: ''
|
|
20
|
-
}].map{ |i| i.merge({expected: APP_NAME})}
|
|
20
|
+
}].map { |i| i.merge({expected: APP_NAME}) }
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# location of SDK files
|
|
24
24
|
def sdk_directory=(folder)
|
|
25
|
-
Log.log.debug{"sdk_directory=#{folder}"}
|
|
25
|
+
Log.log.debug { "sdk_directory=#{folder}" }
|
|
26
26
|
@sdk_dir = folder
|
|
27
27
|
sdk_directory
|
|
28
28
|
end
|
|
@@ -53,7 +53,7 @@ module Aspera
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
Aspera.assert(!result.nil?, 'Port not found in daemon logs')
|
|
56
|
-
Log.log.debug{"Got port #{result} from log"}
|
|
56
|
+
Log.log.debug { "Got port #{result} from log" }
|
|
57
57
|
return result
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -16,7 +16,7 @@ module URI
|
|
|
16
16
|
def register_proxy_finder
|
|
17
17
|
Aspera.assert(block_given?, 'block required for register_proxy_finder')
|
|
18
18
|
# overload the method in URI : call user's provided block and fallback to original method
|
|
19
|
-
define_method(:find_proxy){ |env_vars = ENV| yield(to_s) || find_proxy_orig(env_vars)}
|
|
19
|
+
define_method(:find_proxy) { |env_vars = ENV| yield(to_s) || find_proxy_orig(env_vars) }
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -44,7 +44,7 @@ module Aspera
|
|
|
44
44
|
context_ip = r_addr.to_s if r_addr.is_a?(Resolv::IPv4)
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
|
-
Aspera.assert(!context_ip.nil?){"DNS name not found: #{context_host}"}
|
|
47
|
+
Aspera.assert(!context_ip.nil?) { "DNS name not found: #{context_host}" }
|
|
48
48
|
# NOTE: Javascript code here with string inclusions
|
|
49
49
|
javascript = <<END_OF_JAVASCRIPT
|
|
50
50
|
function dnsResolve(host) {
|
|
@@ -73,7 +73,7 @@ END_OF_JAVASCRIPT
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def register_uri_generic
|
|
76
|
-
URI::Generic.register_proxy_finder{ |url_str| get_proxies(url_str).first}
|
|
76
|
+
URI::Generic.register_proxy_finder { |url_str| get_proxies(url_str).first }
|
|
77
77
|
# allow chaining
|
|
78
78
|
return self
|
|
79
79
|
end
|
|
@@ -84,7 +84,7 @@ END_OF_JAVASCRIPT
|
|
|
84
84
|
uri = URI.parse(service_url)
|
|
85
85
|
simple_url = "#{uri.scheme}://#{uri.host}"
|
|
86
86
|
if !@cache.key?(simple_url)
|
|
87
|
-
Log.log.debug{"PAC: starting javascript for #{service_url}"}
|
|
87
|
+
Log.log.debug { "PAC: starting javascript for #{service_url}" }
|
|
88
88
|
# require at runtime, in case there is no js engine
|
|
89
89
|
require 'execjs'
|
|
90
90
|
# read template lib
|
|
@@ -93,7 +93,7 @@ END_OF_JAVASCRIPT
|
|
|
93
93
|
js_to_execute = "#{pac_dns_functions(uri.host)}#{@pac_functions}#{@proxy_auto_config}"
|
|
94
94
|
executable_js = ExecJS.compile(js_to_execute)
|
|
95
95
|
@cache[simple_url] = executable_js.call(PAC_MAIN_FUNCTION, simple_url, uri.host)
|
|
96
|
-
Log.log.debug{"PAC: result: #{@cache[simple_url]}"}
|
|
96
|
+
Log.log.debug { "PAC: result: #{@cache[simple_url]}" }
|
|
97
97
|
end
|
|
98
98
|
return @cache[simple_url]
|
|
99
99
|
end
|
|
@@ -106,7 +106,7 @@ END_OF_JAVASCRIPT
|
|
|
106
106
|
# execute PAC script
|
|
107
107
|
proxy_list_str = find_proxy_for_url(service_url)
|
|
108
108
|
if !proxy_list_str.is_a?(String)
|
|
109
|
-
Log.log.warn{"PAC: did not return a String, returned #{proxy_list_str.class}"}
|
|
109
|
+
Log.log.warn { "PAC: did not return a String, returned #{proxy_list_str.class}" }
|
|
110
110
|
return uri_list
|
|
111
111
|
end
|
|
112
112
|
proxy_list_str.strip!
|
|
@@ -132,15 +132,15 @@ END_OF_JAVASCRIPT
|
|
|
132
132
|
uri.password = @proxy_pass
|
|
133
133
|
uri_list.push(uri)
|
|
134
134
|
else
|
|
135
|
-
Log.log.warn{"PAC: PROXY must be <address>:<port>, ignoring #{addr_port}"}
|
|
135
|
+
Log.log.warn { "PAC: PROXY must be <address>:<port>, ignoring #{addr_port}" }
|
|
136
136
|
end
|
|
137
137
|
rescue StandardError => e
|
|
138
|
-
Log.log.warn{"PAC: cannot parse #{addr_port} #{e}"}
|
|
138
|
+
Log.log.warn { "PAC: cannot parse #{addr_port} #{e}" }
|
|
139
139
|
end
|
|
140
|
-
else Log.log.warn{"PAC: ignoring proxy type #{proxy_type}: not supported"}
|
|
140
|
+
else Log.log.warn { "PAC: ignoring proxy type #{proxy_type}: not supported" }
|
|
141
141
|
end
|
|
142
142
|
end
|
|
143
|
-
Log.log.debug{"Proxies: #{uri_list}"}
|
|
143
|
+
Log.log.debug { "Proxies: #{uri_list}" }
|
|
144
144
|
return uri_list
|
|
145
145
|
end
|
|
146
146
|
end
|