aspera-cli 4.5.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +1 -0
- data/README.md +1894 -1574
- data/bin/ascli +21 -1
- data/bin/asession +38 -34
- data/docs/test_env.conf +14 -3
- data/examples/aoc.rb +17 -15
- data/examples/dascli +26 -0
- data/examples/faspex4.rb +42 -35
- data/examples/proxy.pac +1 -1
- data/examples/transfer.rb +38 -37
- data/lib/aspera/aoc.rb +245 -205
- data/lib/aspera/ascmd.rb +111 -90
- data/lib/aspera/ats_api.rb +16 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +19 -18
- data/lib/aspera/cli/extended_value.rb +50 -39
- data/lib/aspera/cli/formater.rb +161 -135
- data/lib/aspera/cli/info.rb +18 -0
- data/lib/aspera/cli/listener/line_dump.rb +4 -2
- data/lib/aspera/cli/listener/logger.rb +3 -1
- data/lib/aspera/cli/listener/progress.rb +20 -21
- data/lib/aspera/cli/listener/progress_multi.rb +29 -31
- data/lib/aspera/cli/main.rb +194 -183
- data/lib/aspera/cli/manager.rb +213 -206
- data/lib/aspera/cli/plugin.rb +71 -49
- data/lib/aspera/cli/plugins/alee.rb +8 -7
- data/lib/aspera/cli/plugins/aoc.rb +675 -558
- data/lib/aspera/cli/plugins/ats.rb +116 -109
- data/lib/aspera/cli/plugins/bss.rb +35 -34
- data/lib/aspera/cli/plugins/config.rb +722 -542
- data/lib/aspera/cli/plugins/console.rb +28 -22
- data/lib/aspera/cli/plugins/cos.rb +28 -37
- data/lib/aspera/cli/plugins/faspex.rb +281 -227
- data/lib/aspera/cli/plugins/faspex5.rb +129 -84
- data/lib/aspera/cli/plugins/node.rb +426 -232
- data/lib/aspera/cli/plugins/orchestrator.rb +106 -98
- data/lib/aspera/cli/plugins/preview.rb +196 -191
- data/lib/aspera/cli/plugins/server.rb +131 -126
- data/lib/aspera/cli/plugins/shares.rb +49 -36
- data/lib/aspera/cli/plugins/sync.rb +27 -28
- data/lib/aspera/cli/transfer_agent.rb +84 -79
- data/lib/aspera/cli/version.rb +3 -1
- data/lib/aspera/colors.rb +37 -28
- data/lib/aspera/command_line_builder.rb +84 -63
- data/lib/aspera/cos_node.rb +68 -34
- data/lib/aspera/data_repository.rb +4 -2
- data/lib/aspera/environment.rb +61 -46
- data/lib/aspera/fasp/agent_base.rb +36 -31
- data/lib/aspera/fasp/agent_connect.rb +44 -37
- data/lib/aspera/fasp/agent_direct.rb +101 -104
- data/lib/aspera/fasp/agent_httpgw.rb +91 -90
- data/lib/aspera/fasp/agent_node.rb +36 -33
- data/lib/aspera/fasp/agent_trsdk.rb +28 -31
- data/lib/aspera/fasp/error.rb +3 -1
- data/lib/aspera/fasp/error_info.rb +81 -54
- data/lib/aspera/fasp/installation.rb +171 -151
- data/lib/aspera/fasp/listener.rb +2 -0
- data/lib/aspera/fasp/parameters.rb +105 -111
- data/lib/aspera/fasp/parameters.yaml +305 -249
- data/lib/aspera/fasp/resume_policy.rb +20 -20
- data/lib/aspera/fasp/transfer_spec.rb +27 -0
- data/lib/aspera/fasp/uri.rb +31 -29
- data/lib/aspera/faspex_gw.rb +95 -118
- data/lib/aspera/hash_ext.rb +12 -13
- data/lib/aspera/id_generator.rb +11 -9
- data/lib/aspera/keychain/encrypted_hash.rb +73 -57
- data/lib/aspera/keychain/macos_security.rb +27 -29
- data/lib/aspera/log.rb +40 -39
- data/lib/aspera/nagios.rb +24 -22
- data/lib/aspera/node.rb +38 -30
- data/lib/aspera/oauth.rb +217 -248
- data/lib/aspera/open_application.rb +9 -7
- data/lib/aspera/persistency_action_once.rb +15 -14
- data/lib/aspera/persistency_folder.rb +15 -18
- data/lib/aspera/preview/file_types.rb +266 -270
- data/lib/aspera/preview/generator.rb +94 -92
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +20 -17
- data/lib/aspera/preview/utils.rb +99 -102
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
- data/lib/aspera/proxy_auto_config.rb +114 -21
- data/lib/aspera/rest.rb +144 -142
- data/lib/aspera/rest_call_error.rb +3 -2
- data/lib/aspera/rest_error_analyzer.rb +31 -31
- data/lib/aspera/rest_errors_aspera.rb +18 -16
- data/lib/aspera/secret_hider.rb +68 -0
- data/lib/aspera/ssh.rb +20 -16
- data/lib/aspera/sync.rb +57 -54
- data/lib/aspera/temp_file_manager.rb +20 -14
- data/lib/aspera/timer_limiter.rb +10 -8
- data/lib/aspera/uri_reader.rb +14 -15
- data/lib/aspera/web_auth.rb +85 -80
- data.tar.gz.sig +0 -0
- metadata +169 -40
- metadata.gz.sig +2 -0
- data/bin/dascli +0 -13
- data/docs/Makefile +0 -63
- data/docs/README.erb.md +0 -4221
- data/docs/README.md +0 -13
- data/docs/diagrams.txt +0 -49
- data/docs/doc_tools.rb +0 -58
- data/lib/aspera/cli/plugins/shares2.rb +0 -114
- data/lib/aspera/fasp/default.rb +0 -17
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'open3'
|
|
2
4
|
require 'aspera/preview/options'
|
|
3
5
|
require 'aspera/preview/utils'
|
|
@@ -8,7 +10,7 @@ module Aspera
|
|
|
8
10
|
# generate one preview file for one format for one file at a time
|
|
9
11
|
class Generator
|
|
10
12
|
# values for preview_format : output format
|
|
11
|
-
PREVIEW_FORMATS=[
|
|
13
|
+
PREVIEW_FORMATS = %i[png mp4].freeze
|
|
12
14
|
|
|
13
15
|
# CLI needs to know conversion type to know if need skip it
|
|
14
16
|
attr_reader :conversion_type
|
|
@@ -26,25 +28,25 @@ module Aspera
|
|
|
26
28
|
# the conversion video->mp4 is implemented in methods: convert_video_to_mp4_using_<video_conversion>
|
|
27
29
|
# -> conversion method is one of Generator::VIDEO_CONVERSION_METHODS
|
|
28
30
|
def initialize(options,src,dst,main_temp_dir,api_mime_type)
|
|
29
|
-
@options=options
|
|
30
|
-
@source_file_path=src
|
|
31
|
-
@destination_file_path=dst
|
|
32
|
-
@temp_folder=File.join(main_temp_dir,@source_file_path.split('/').last.gsub(/\s/, '_').gsub(/\W/, ''))
|
|
31
|
+
@options = options
|
|
32
|
+
@source_file_path = src
|
|
33
|
+
@destination_file_path = dst
|
|
34
|
+
@temp_folder = File.join(main_temp_dir,@source_file_path.split('/').last.gsub(/\s/, '_').gsub(/\W/, ''))
|
|
33
35
|
# extract preview format from extension of target file
|
|
34
|
-
@preview_format_symb=File.extname(@destination_file_path).gsub(/^\./,'').to_sym
|
|
35
|
-
@conversion_type=FileTypes.instance.conversion_type(@source_file_path,api_mime_type)
|
|
36
|
+
@preview_format_symb = File.extname(@destination_file_path).gsub(/^\./,'').to_sym
|
|
37
|
+
@conversion_type = FileTypes.instance.conversion_type(@source_file_path,api_mime_type)
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
# name of processing method in this object
|
|
39
41
|
# combination of: conversion type and output format (and video_conversion for video)
|
|
40
42
|
def processing_method_symb
|
|
41
|
-
name="convert_#{@conversion_type}_to_#{@preview_format_symb}"
|
|
43
|
+
name = "convert_#{@conversion_type}_to_#{@preview_format_symb}"
|
|
42
44
|
if @conversion_type.eql?(:video)
|
|
43
45
|
case @preview_format_symb
|
|
44
46
|
when :mp4
|
|
45
|
-
name="#{name}_using_#{@options.video_conversion}"
|
|
47
|
+
name = "#{name}_using_#{@options.video_conversion}"
|
|
46
48
|
when :png
|
|
47
|
-
name="#{name}_using_#{@options.video_png_conv}"
|
|
49
|
+
name = "#{name}_using_#{@options.video_png_conv}"
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
Log.log.debug("method: #{name}")
|
|
@@ -60,20 +62,20 @@ module Aspera
|
|
|
60
62
|
|
|
61
63
|
# create preview as specified in constructor
|
|
62
64
|
def generate
|
|
63
|
-
raise
|
|
64
|
-
method_symb=processing_method_symb
|
|
65
|
+
raise 'could not detect type of file' if @conversion_type.nil?
|
|
66
|
+
method_symb = processing_method_symb
|
|
65
67
|
Log.log.info("#{@source_file_path}->#{@destination_file_path} (#{method_symb})")
|
|
66
68
|
begin
|
|
67
|
-
|
|
69
|
+
send(method_symb)
|
|
68
70
|
# check that generated size does not exceed maximum
|
|
69
|
-
result_size=File.size(@destination_file_path)
|
|
71
|
+
result_size = File.size(@destination_file_path)
|
|
70
72
|
if result_size > @options.max_size
|
|
71
73
|
Log.log.warn("preview size exceeds maximum #{result_size} > #{@options.max_size}")
|
|
72
74
|
end
|
|
73
|
-
rescue => e
|
|
74
|
-
Log.log.error("#{e.message}")
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
Log.log.error("Ignoging: #{e.message}")
|
|
75
77
|
Log.log.debug(e.backtrace.join("\n").red)
|
|
76
|
-
FileUtils.cp(File.expand_path(@preview_format_symb.eql?(:mp4)?'video_error.png':'image_error.png',File.dirname(__FILE__)),@destination_file_path)
|
|
78
|
+
FileUtils.cp(File.expand_path(@preview_format_symb.eql?(:mp4) ? 'video_error.png' : 'image_error.png',File.dirname(__FILE__)),@destination_file_path)
|
|
77
79
|
ensure
|
|
78
80
|
FileUtils.rm_rf(@temp_folder)
|
|
79
81
|
end
|
|
@@ -93,17 +95,18 @@ module Aspera
|
|
|
93
95
|
# @param total_count of parts
|
|
94
96
|
# @param index of part (start at 1)
|
|
95
97
|
def get_offset(duration, start_offset, total_count, index)
|
|
96
|
-
|
|
98
|
+
raise 'duration must be Float' unless duration.is_a?(Float)
|
|
99
|
+
return start_offset + ((index - 1) * (duration - start_offset) / total_count)
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
def convert_video_to_mp4_using_blend
|
|
102
|
+
def convert_video_to_mp4_using_blend
|
|
100
103
|
p_duration = Utils.video_get_duration(@source_file_path)
|
|
101
104
|
p_start_offset = @options.video_start_sec.to_i
|
|
102
105
|
p_keyframecount = @options.blend_keyframes.to_i
|
|
103
106
|
last_keyframe = nil
|
|
104
107
|
current_index = 1
|
|
105
108
|
1.upto(p_keyframecount) do |i|
|
|
106
|
-
offset_seconds=get_offset(p_duration,p_start_offset,p_keyframecount,i)
|
|
109
|
+
offset_seconds = get_offset(p_duration,p_start_offset,p_keyframecount,i)
|
|
107
110
|
Utils.video_dump_frame(@source_file_path, offset_seconds, @options.video_scale, this_tmpdir, current_index)
|
|
108
111
|
Utils.video_dupe_frame(this_tmpdir, current_index, @options.blend_pauseframes)
|
|
109
112
|
Utils.video_blend_frames(this_tmpdir,last_keyframe, current_index) unless last_keyframe.nil?
|
|
@@ -113,95 +116,95 @@ module Aspera
|
|
|
113
116
|
current_index = last_keyframe + 1 + @options.blend_transframes
|
|
114
117
|
end
|
|
115
118
|
Utils.ffmpeg(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
in_f: Utils.ffmpeg_fmt(this_tmpdir),
|
|
120
|
+
in_p: ['-framerate',@options.blend_fps],
|
|
121
|
+
out_f: @destination_file_path,
|
|
122
|
+
out_p: [
|
|
123
|
+
'-filter:v',"scale='trunc(iw/2)*2:trunc(ih/2)*2'",
|
|
124
|
+
'-codec:v','libx264',
|
|
125
|
+
'-r',30,
|
|
126
|
+
'-pix_fmt','yuv420p'])
|
|
124
127
|
end
|
|
125
128
|
|
|
126
129
|
# generate n clips starting at offset
|
|
127
|
-
def convert_video_to_mp4_using_clips
|
|
130
|
+
def convert_video_to_mp4_using_clips
|
|
128
131
|
p_duration = Utils.video_get_duration(@source_file_path)
|
|
129
132
|
filelist = File.join(this_tmpdir,'clip_files.txt')
|
|
130
133
|
File.open(filelist, 'w+') do |f|
|
|
131
134
|
1.upto(@options.clips_count.to_i) do |i|
|
|
132
|
-
offset_seconds=get_offset(p_duration,@options.video_start_sec.to_i,@options.clips_count.to_i,i)
|
|
133
|
-
tmpfilename=
|
|
135
|
+
offset_seconds = get_offset(p_duration,@options.video_start_sec.to_i,@options.clips_count.to_i,i)
|
|
136
|
+
tmpfilename = format('clip%04d.mp4',i)
|
|
134
137
|
Utils.ffmpeg(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
in_f: @source_file_path,
|
|
139
|
+
in_p: ['-ss',0.9 * offset_seconds],
|
|
140
|
+
out_f: File.join(this_tmpdir,tmpfilename),
|
|
141
|
+
out_p: [
|
|
142
|
+
'-ss',0.1 * offset_seconds,
|
|
143
|
+
'-t',@options.clips_length,
|
|
144
|
+
'-filter:v',"scale=#{@options.video_scale}",
|
|
145
|
+
'-codec:a','libmp3lame'])
|
|
143
146
|
f.puts("file '#{tmpfilename}'")
|
|
144
147
|
end
|
|
145
148
|
end
|
|
146
149
|
# concat clips
|
|
147
150
|
Utils.ffmpeg(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
in_f: filelist,
|
|
152
|
+
in_p: ['-f','concat'],
|
|
153
|
+
out_f: @destination_file_path,
|
|
154
|
+
out_p: ['-codec','copy'])
|
|
152
155
|
end
|
|
153
156
|
|
|
154
157
|
# do a simple reencoding
|
|
155
|
-
def convert_video_to_mp4_using_reencode
|
|
158
|
+
def convert_video_to_mp4_using_reencode
|
|
156
159
|
Utils.ffmpeg(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
160
|
+
in_f: @source_file_path,
|
|
161
|
+
out_f: @destination_file_path,
|
|
162
|
+
out_p: [
|
|
163
|
+
'-t','60',
|
|
164
|
+
'-codec:v','libx264',
|
|
165
|
+
'-profile:v','high',
|
|
166
|
+
'-pix_fmt','yuv420p',
|
|
167
|
+
'-preset','slow',
|
|
168
|
+
'-b:v','500k',
|
|
169
|
+
'-maxrate','500k',
|
|
170
|
+
'-bufsize','1000k',
|
|
171
|
+
'-filter:v',"scale=#{@options.video_scale}",
|
|
172
|
+
'-threads','0',
|
|
173
|
+
'-codec:a','libmp3lame',
|
|
174
|
+
'-ac','2',
|
|
175
|
+
'-b:a','128k',
|
|
176
|
+
'-movflags','faststart'])
|
|
174
177
|
end
|
|
175
178
|
|
|
176
|
-
def convert_video_to_png_using_fixed
|
|
179
|
+
def convert_video_to_png_using_fixed
|
|
177
180
|
Utils.video_dump_frame(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
@source_file_path,
|
|
182
|
+
Utils.video_get_duration(@source_file_path) * @options.thumb_vid_fraction,
|
|
183
|
+
@options.thumb_vid_scale,
|
|
184
|
+
@destination_file_path)
|
|
182
185
|
end
|
|
183
186
|
|
|
184
187
|
# https://trac.ffmpeg.org/wiki/SponsoringPrograms/GSoC/2015#AnimatedPortableNetworkGraphicsAPNG
|
|
185
188
|
# ffmpeg -h muxer=apng
|
|
186
189
|
# thumb is 32x32
|
|
187
190
|
# ffmpeg output.png
|
|
188
|
-
def convert_video_to_png_using_animated
|
|
191
|
+
def convert_video_to_png_using_animated
|
|
189
192
|
Utils.ffmpeg(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
193
|
+
in_f: @source_file_path,
|
|
194
|
+
in_p: [
|
|
195
|
+
'-ss',10, # seek to input position
|
|
196
|
+
'-t',20 # max seconds
|
|
197
|
+
],
|
|
198
|
+
out_f: @destination_file_path,
|
|
199
|
+
out_p: [
|
|
200
|
+
'-vf','fps=5,scale=120:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse',
|
|
201
|
+
'-loop',0,
|
|
202
|
+
'-f','gif'
|
|
203
|
+
])
|
|
201
204
|
end
|
|
202
205
|
|
|
203
|
-
def convert_office_to_png
|
|
204
|
-
tmp_pdf_file=File.join(this_tmpdir,File.basename(@source_file_path,File.extname(@source_file_path))+'.pdf')
|
|
206
|
+
def convert_office_to_png
|
|
207
|
+
tmp_pdf_file = File.join(this_tmpdir,File.basename(@source_file_path,File.extname(@source_file_path)) + '.pdf')
|
|
205
208
|
Utils.external_command(:unoconv,[
|
|
206
209
|
'-f','pdf',
|
|
207
210
|
'-o',tmp_pdf_file,
|
|
@@ -210,7 +213,7 @@ module Aspera
|
|
|
210
213
|
end
|
|
211
214
|
|
|
212
215
|
def convert_pdf_to_png(source_file_path=nil)
|
|
213
|
-
source_file_path
|
|
216
|
+
source_file_path ||= @source_file_path
|
|
214
217
|
Utils.external_command(:convert,[
|
|
215
218
|
'-size',"x#{@options.thumb_img_size}",
|
|
216
219
|
'-background','white',
|
|
@@ -219,7 +222,7 @@ module Aspera
|
|
|
219
222
|
@destination_file_path])
|
|
220
223
|
end
|
|
221
224
|
|
|
222
|
-
def convert_image_to_png
|
|
225
|
+
def convert_image_to_png
|
|
223
226
|
Utils.external_command(:convert,[
|
|
224
227
|
'-auto-orient',
|
|
225
228
|
'-thumbnail',"#{@options.thumb_img_size}x#{@options.thumb_img_size}>",
|
|
@@ -232,23 +235,22 @@ module Aspera
|
|
|
232
235
|
end
|
|
233
236
|
|
|
234
237
|
# text to png
|
|
235
|
-
def convert_plaintext_to_png
|
|
238
|
+
def convert_plaintext_to_png
|
|
236
239
|
# get 100 first lines of text file
|
|
237
|
-
first_lines=File.open(@source_file_path){|f|100
|
|
240
|
+
first_lines = File.open(@source_file_path){|f|Array.new(100){f.readline rescue ''}.join}
|
|
238
241
|
Utils.external_command(:convert,[
|
|
239
242
|
'-size',"#{@options.thumb_img_size}x#{@options.thumb_img_size}",
|
|
240
|
-
'xc:white',
|
|
241
|
-
'-font',
|
|
243
|
+
'xc:white', # define canvas with background color (xc, or canvas) of preceding size
|
|
244
|
+
'-font',@options.thumb_text_font,
|
|
242
245
|
'-pointsize',12,
|
|
243
|
-
'-fill','black',
|
|
244
|
-
'-annotate','+
|
|
245
|
-
'-trim',
|
|
246
|
-
'-bordercolor','
|
|
247
|
-
'-border',
|
|
246
|
+
'-fill','black', # font color
|
|
247
|
+
'-annotate','+0+0',first_lines,
|
|
248
|
+
'-trim', # avoid large blank regions
|
|
249
|
+
'-bordercolor','white',
|
|
250
|
+
'-border',8,
|
|
248
251
|
'+repage',
|
|
249
252
|
@destination_file_path])
|
|
250
253
|
end
|
|
251
|
-
|
|
252
254
|
end # Generator
|
|
253
255
|
end # Preview
|
|
254
256
|
end # Aspera
|
|
Binary file
|
|
@@ -1,31 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Aspera
|
|
2
4
|
module Preview
|
|
3
5
|
# generator options. Used as parameter to preview generator object.
|
|
4
6
|
# also settable by command line.
|
|
5
7
|
class Options
|
|
6
8
|
# types of generation for video files
|
|
7
|
-
VIDEO_CONVERSION_METHODS=[
|
|
8
|
-
VIDEO_THUMBNAIL_METHODS=[
|
|
9
|
+
VIDEO_CONVERSION_METHODS = %i[reencode blend clips].freeze
|
|
10
|
+
VIDEO_THUMBNAIL_METHODS = %i[fixed animated].freeze
|
|
9
11
|
# options used in generator
|
|
10
12
|
# for scaling see: https://trac.ffmpeg.org/wiki/Scaling
|
|
11
13
|
# iw/ih : input width or height
|
|
12
14
|
# -x : keep aspect ratio, having value a multiple of x
|
|
13
15
|
DESCRIPTIONS = [
|
|
14
|
-
{ name: :max_size, default: 1<<24,
|
|
15
|
-
{ name: :thumb_vid_scale, default: "-1:'min(ih,100)'", description:
|
|
16
|
-
{ name: :thumb_vid_fraction, default: 0.1, description:
|
|
17
|
-
{ name: :thumb_img_size, default: 800, description:
|
|
18
|
-
{ name: :
|
|
19
|
-
{ name: :
|
|
20
|
-
{ name: :
|
|
21
|
-
{ name: :
|
|
22
|
-
{ name: :
|
|
23
|
-
{ name: :
|
|
24
|
-
{ name: :
|
|
25
|
-
{ name: :
|
|
26
|
-
{ name: :
|
|
27
|
-
{ name: :
|
|
28
|
-
|
|
16
|
+
{ name: :max_size, default: 1 << 24, description: 'maximum size (in bytes) of preview file' },
|
|
17
|
+
{ name: :thumb_vid_scale, default: "-1:'min(ih,100)'", description: 'png: video: size (ffmpeg scale argument)' },
|
|
18
|
+
{ name: :thumb_vid_fraction, default: 0.1, description: 'png: video: time percent position of snapshot' },
|
|
19
|
+
{ name: :thumb_img_size, default: 800, description: 'png: non-video: height (and width)' },
|
|
20
|
+
{ name: :thumb_text_font, default: 'Courier', description: 'png: plaintext: font to render text with imagemagick convert (identify -list font)'},
|
|
21
|
+
{ name: :video_conversion, default: :reencode, description: 'mp4: method for preview generation', values: VIDEO_CONVERSION_METHODS },
|
|
22
|
+
{ name: :video_png_conv, default: :fixed, description: 'mp4: method for thumbnail generation', values: VIDEO_THUMBNAIL_METHODS },
|
|
23
|
+
{ name: :video_start_sec, default: 10, description: 'mp4: start offset (seconds) of video preview' },
|
|
24
|
+
{ name: :video_scale, default: "'min(iw,360)':-2", description: 'mp4: video scale (ffmpeg)' },
|
|
25
|
+
{ name: :blend_keyframes, default: 30, description: 'mp4: blend: # key frames' },
|
|
26
|
+
{ name: :blend_pauseframes, default: 3, description: 'mp4: blend: # pause frames' },
|
|
27
|
+
{ name: :blend_transframes, default: 5, description: 'mp4: blend: # transition blend frames' },
|
|
28
|
+
{ name: :blend_fps, default: 15, description: 'mp4: blend: frame per second' },
|
|
29
|
+
{ name: :clips_count, default: 5, description: 'mp4: clips: number of clips' },
|
|
30
|
+
{ name: :clips_length, default: 5, description: 'mp4: clips: length in seconds of each clips' }
|
|
31
|
+
].freeze
|
|
29
32
|
# add accessors
|
|
30
33
|
DESCRIPTIONS.each do |opt|
|
|
31
34
|
attr_accessor opt[:name]
|
data/lib/aspera/preview/utils.rb
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
1
4
|
require 'tmpdir'
|
|
2
5
|
require 'fileutils'
|
|
3
6
|
require 'aspera/log'
|
|
@@ -7,124 +10,118 @@ module Aspera
|
|
|
7
10
|
module Preview
|
|
8
11
|
class Utils
|
|
9
12
|
# from bash manual: meta-character need to be escaped
|
|
10
|
-
BASH_SPECIAL_CHARACTERS="|&;()<> \t#\n"
|
|
13
|
+
BASH_SPECIAL_CHARACTERS = "|&;()<> \t#\n"
|
|
11
14
|
# shell exit code when command is not found
|
|
12
|
-
BASH_EXIT_NOT_FOUND=127
|
|
15
|
+
BASH_EXIT_NOT_FOUND = 127
|
|
13
16
|
# external binaries used
|
|
14
|
-
EXPERNAL_TOOLS=[
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# returns string with single quotes suitable for bash if there is any bash metacharacter
|
|
18
|
-
def self.shell_quote(argument)
|
|
19
|
-
return argument unless argument.split('').any?{|c|BASH_SPECIAL_CHARACTERS.include?(c)}
|
|
20
|
-
return "'"+argument.gsub(/'/){|s| "'\"'\"'"}+"'"
|
|
21
|
-
end
|
|
17
|
+
EXPERNAL_TOOLS = %i[ffmpeg ffprobe convert composite optipng unoconv].freeze
|
|
18
|
+
TMPFMT = 'img%04d.jpg'
|
|
19
|
+
private_constant :BASH_SPECIAL_CHARACTERS,:BASH_EXIT_NOT_FOUND,:EXPERNAL_TOOLS,:TMPFMT
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`#{bin} -h 2>&1`
|
|
29
|
-
raise "Error: #{bin} is not in the PATH" if $?.exitstatus.eql?(BASH_EXIT_NOT_FOUND)
|
|
21
|
+
class << self
|
|
22
|
+
# returns string with single quotes suitable for bash if there is any bash metacharacter
|
|
23
|
+
def shell_quote(argument)
|
|
24
|
+
return argument unless argument.chars.any?{|c|BASH_SPECIAL_CHARACTERS.include?(c)}
|
|
25
|
+
return "'" + argument.gsub(/'/){|_s| "'\"'\"'"} + "'"
|
|
30
26
|
end
|
|
31
|
-
end
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# capture3: only in ruby2+
|
|
42
|
-
if Open3.respond_to?('capture3') then
|
|
43
|
-
stdout, stderr, exit_status = Open3.capture3(command)
|
|
44
|
-
else
|
|
45
|
-
stderr='<merged with stdout>'
|
|
46
|
-
stdout=%x[#{command} 2>&1]
|
|
47
|
-
exit_status=$?
|
|
48
|
-
end
|
|
49
|
-
if BASH_EXIT_NOT_FOUND.eql?(exit_status)
|
|
50
|
-
raise "Error: #{bin} is not in the PATH"
|
|
51
|
-
end
|
|
52
|
-
unless exit_status.success?
|
|
53
|
-
Log.log.error("commandline: #{command}")
|
|
54
|
-
Log.log.error("Error code: #{exit_status}")
|
|
55
|
-
Log.log.error("stdout: #{stdout}")
|
|
56
|
-
Log.log.error("stderr: #{stderr}")
|
|
57
|
-
raise "command returned error"
|
|
28
|
+
# check that external tools can be executed
|
|
29
|
+
def check_tools(skip_types=[])
|
|
30
|
+
tools_to_check=EXPERNAL_TOOLS.dup
|
|
31
|
+
tools_to_check.delete(:unoconv) if skip_types.include?(:office)
|
|
32
|
+
# Check for binaries
|
|
33
|
+
tools_to_check.each do |command_symb|
|
|
34
|
+
external_command(command_symb,['-h'])
|
|
35
|
+
end
|
|
58
36
|
end
|
|
59
|
-
stdout_return.replace(stdout) unless stdout_return.nil?
|
|
60
|
-
return exit_status.success?
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def self.ffmpeg(a)
|
|
64
|
-
raise "error: hash expected" unless a.is_a?(Hash)
|
|
65
|
-
#input_file,input_args,output_file,output_args
|
|
66
|
-
a[:gl_p]||=[
|
|
67
|
-
'-y', # overwrite output without asking
|
|
68
|
-
'-loglevel','error', # show only errors and up]
|
|
69
|
-
]
|
|
70
|
-
a[:in_p]||=[]
|
|
71
|
-
a[:out_p]||=[]
|
|
72
|
-
raise "wrong params (#{a.keys.sort})" unless [:gl_p, :in_f, :in_p, :out_f, :out_p].eql?(a.keys.sort)
|
|
73
|
-
external_command(:ffmpeg,[a[:gl_p],a[:in_p],'-i',a[:in_f],a[:out_p],a[:out_f]].flatten)
|
|
74
|
-
end
|
|
75
37
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
38
|
+
# execute external command
|
|
39
|
+
# one could use "system", but we would need to redirect stdout/err
|
|
40
|
+
# @return true if su
|
|
41
|
+
def external_command(command_symb,command_args)
|
|
42
|
+
raise "unexpected command #{command_symb}" unless EXPERNAL_TOOLS.include?(command_symb)
|
|
43
|
+
# build command line, and quote special characters
|
|
44
|
+
command = command_args.clone.unshift(command_symb).map{|i| shell_quote(i.to_s)}.join(' ')
|
|
45
|
+
Log.log.debug("cmd=#{command}".blue)
|
|
46
|
+
# capture3: only in ruby2+
|
|
47
|
+
if Open3.respond_to?(:capture3)
|
|
48
|
+
stdout, stderr, exit_status = Open3.capture3(command)
|
|
49
|
+
else
|
|
50
|
+
stderr = '<merged with stdout>'
|
|
51
|
+
stdout = %x(#{command} 2>&1)
|
|
52
|
+
exit_status = $CHILD_STATUS
|
|
53
|
+
end
|
|
54
|
+
if BASH_EXIT_NOT_FOUND.eql?(exit_status)
|
|
55
|
+
raise "Error: #{command_symb} is not in the PATH"
|
|
56
|
+
end
|
|
57
|
+
unless exit_status.success?
|
|
58
|
+
Log.log.error("commandline: #{command}")
|
|
59
|
+
Log.log.error("Error code: #{exit_status}")
|
|
60
|
+
Log.log.error("stdout: #{stdout}")
|
|
61
|
+
Log.log.error("stderr: #{stderr}")
|
|
62
|
+
raise "#{command_symb} error #{exit_status}"
|
|
63
|
+
end
|
|
64
|
+
return {status: exit_status, stdout: stdout}
|
|
65
|
+
end
|
|
85
66
|
|
|
86
|
-
|
|
67
|
+
def ffmpeg(a)
|
|
68
|
+
raise 'error: hash expected' unless a.is_a?(Hash)
|
|
69
|
+
#input_file,input_args,output_file,output_args
|
|
70
|
+
a[:gl_p] ||= [
|
|
71
|
+
'-y', # overwrite output without asking
|
|
72
|
+
'-loglevel','error' # show only errors and up]
|
|
73
|
+
]
|
|
74
|
+
a[:in_p] ||= []
|
|
75
|
+
a[:out_p] ||= []
|
|
76
|
+
raise "wrong params (#{a.keys.sort})" unless %i[gl_p in_f in_p out_f out_p].eql?(a.keys.sort)
|
|
77
|
+
external_command(:ffmpeg,[a[:gl_p],a[:in_p],'-i',a[:in_f],a[:out_p],a[:out_f]].flatten)
|
|
78
|
+
end
|
|
87
79
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
# @return Float in seconds
|
|
81
|
+
def video_get_duration(input_file)
|
|
82
|
+
result = external_command(:ffprobe,[
|
|
83
|
+
'-loglevel','error',
|
|
84
|
+
'-show_entries','format=duration',
|
|
85
|
+
'-print_format','default=noprint_wrappers=1:nokey=1',
|
|
86
|
+
input_file])
|
|
87
|
+
return result[:stdout].to_f
|
|
88
|
+
end
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
def ffmpeg_fmt(temp_folder)
|
|
91
|
+
return File.join(temp_folder,TMPFMT)
|
|
92
|
+
end
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
1.upto(count) do |i|
|
|
99
|
-
FileUtils.ln_s(input_file,get_tmp_num_filepath(temp_folder,index+i))
|
|
94
|
+
def get_tmp_num_filepath(temp_folder, file_number)
|
|
95
|
+
return File.join(temp_folder,format(TMPFMT,file_number))
|
|
100
96
|
end
|
|
101
|
-
end
|
|
102
97
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
percent = 100 * i / (count + 1)
|
|
109
|
-
filename = get_tmp_num_filepath(temp_folder, index1 + i)
|
|
110
|
-
external_command(:composite,['-blend',percent,img2,img1,filename])
|
|
98
|
+
def video_dupe_frame(temp_folder, index, count)
|
|
99
|
+
input_file = get_tmp_num_filepath(temp_folder,index)
|
|
100
|
+
1.upto(count) do |i|
|
|
101
|
+
FileUtils.ln_s(input_file,get_tmp_num_filepath(temp_folder,index + i))
|
|
102
|
+
end
|
|
111
103
|
end
|
|
112
|
-
end
|
|
113
104
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
105
|
+
def video_blend_frames(temp_folder, index1, index2)
|
|
106
|
+
img1 = get_tmp_num_filepath(temp_folder,index1)
|
|
107
|
+
img2 = get_tmp_num_filepath(temp_folder,index2)
|
|
108
|
+
count = index2 - index1 - 1
|
|
109
|
+
1.upto(count) do |i|
|
|
110
|
+
percent = 100 * i / (count + 1)
|
|
111
|
+
filename = get_tmp_num_filepath(temp_folder, index1 + i)
|
|
112
|
+
external_command(:composite,['-blend',percent,img2,img1,filename])
|
|
113
|
+
end
|
|
114
|
+
end
|
|
123
115
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
116
|
+
def video_dump_frame(input_file, offset_seconds, scale, output_file, index=nil)
|
|
117
|
+
output_file = get_tmp_num_filepath(output_file,index) unless index.nil?
|
|
118
|
+
ffmpeg(
|
|
119
|
+
in_f: input_file,
|
|
120
|
+
in_p: ['-ss',offset_seconds],
|
|
121
|
+
out_f: output_file,
|
|
122
|
+
out_p: ['-frames:v',1,'-filter:v',"scale=#{scale}"])
|
|
123
|
+
return output_file
|
|
124
|
+
end
|
|
128
125
|
end
|
|
129
126
|
end # Options
|
|
130
127
|
end # Preview
|
|
Binary file
|