aspera-cli 4.9.0 → 4.11.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 +0 -0
- data/BUGS.md +20 -0
- data/CHANGELOG.md +509 -0
- data/CONTRIBUTING.md +118 -0
- data/README.md +1241 -916
- data/bin/ascli +4 -4
- data/bin/asession +11 -11
- data/docs/test_env.conf +32 -21
- data/examples/aoc.rb +4 -4
- data/examples/dascli +16 -9
- data/examples/faspex4.rb +8 -8
- data/examples/node.rb +12 -12
- data/examples/server.rb +10 -10
- data/lib/aspera/aoc.rb +273 -266
- data/lib/aspera/ascmd.rb +56 -54
- data/lib/aspera/ats_api.rb +4 -4
- data/lib/aspera/cli/basic_auth_plugin.rb +15 -12
- data/lib/aspera/cli/extended_value.rb +5 -5
- data/lib/aspera/cli/formater.rb +64 -64
- data/lib/aspera/cli/info.rb +2 -2
- data/lib/aspera/cli/listener/line_dump.rb +1 -1
- data/lib/aspera/cli/listener/logger.rb +1 -1
- data/lib/aspera/cli/listener/progress.rb +5 -6
- data/lib/aspera/cli/listener/progress_multi.rb +14 -19
- data/lib/aspera/cli/main.rb +66 -67
- data/lib/aspera/cli/manager.rb +112 -110
- data/lib/aspera/cli/plugin.rb +57 -36
- data/lib/aspera/cli/plugins/alee.rb +4 -4
- data/lib/aspera/cli/plugins/aoc.rb +309 -670
- data/lib/aspera/cli/plugins/ats.rb +44 -46
- data/lib/aspera/cli/plugins/bss.rb +10 -10
- data/lib/aspera/cli/plugins/config.rb +497 -378
- data/lib/aspera/cli/plugins/console.rb +12 -12
- data/lib/aspera/cli/plugins/cos.rb +18 -20
- data/lib/aspera/cli/plugins/faspex.rb +112 -114
- data/lib/aspera/cli/plugins/faspex5.rb +71 -46
- data/lib/aspera/cli/plugins/node.rb +379 -283
- data/lib/aspera/cli/plugins/orchestrator.rb +46 -46
- data/lib/aspera/cli/plugins/preview.rb +122 -114
- data/lib/aspera/cli/plugins/server.rb +137 -83
- data/lib/aspera/cli/plugins/shares.rb +30 -29
- data/lib/aspera/cli/plugins/sync.rb +13 -33
- data/lib/aspera/cli/transfer_agent.rb +60 -59
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +27 -27
- data/lib/aspera/cos_node.rb +22 -20
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +35 -15
- data/lib/aspera/fasp/agent_base.rb +15 -15
- data/lib/aspera/fasp/agent_connect.rb +23 -21
- data/lib/aspera/fasp/agent_direct.rb +66 -64
- data/lib/aspera/fasp/agent_httpgw.rb +141 -78
- data/lib/aspera/fasp/agent_node.rb +23 -21
- data/lib/aspera/fasp/agent_trsdk.rb +20 -20
- data/lib/aspera/fasp/error.rb +3 -2
- data/lib/aspera/fasp/error_info.rb +11 -8
- data/lib/aspera/fasp/installation.rb +79 -79
- data/lib/aspera/fasp/listener.rb +1 -1
- data/lib/aspera/fasp/parameters.rb +86 -71
- data/lib/aspera/fasp/parameters.yaml +7 -4
- data/lib/aspera/fasp/resume_policy.rb +8 -8
- data/lib/aspera/fasp/transfer_spec.rb +35 -2
- data/lib/aspera/fasp/uri.rb +7 -7
- data/lib/aspera/faspex_gw.rb +7 -5
- data/lib/aspera/hash_ext.rb +3 -3
- data/lib/aspera/id_generator.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +38 -105
- data/lib/aspera/keychain/macos_security.rb +128 -57
- data/lib/aspera/log.rb +7 -7
- data/lib/aspera/nagios.rb +19 -18
- data/lib/aspera/node.rb +209 -35
- data/lib/aspera/oauth.rb +37 -36
- data/lib/aspera/open_application.rb +19 -11
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +16 -15
- data/lib/aspera/preview/file_types.rb +8 -8
- data/lib/aspera/preview/generator.rb +67 -67
- data/lib/aspera/preview/utils.rb +27 -27
- data/lib/aspera/proxy_auto_config.js +41 -41
- data/lib/aspera/proxy_auto_config.rb +21 -14
- data/lib/aspera/rest.rb +72 -67
- data/lib/aspera/rest_call_error.rb +2 -1
- data/lib/aspera/rest_error_analyzer.rb +18 -17
- data/lib/aspera/rest_errors_aspera.rb +16 -16
- data/lib/aspera/secret_hider.rb +15 -13
- data/lib/aspera/ssh.rb +11 -10
- data/lib/aspera/sync.rb +158 -44
- data/lib/aspera/temp_file_manager.rb +2 -2
- data/lib/aspera/uri_reader.rb +4 -4
- data/lib/aspera/web_auth.rb +14 -13
- data.tar.gz.sig +0 -0
- metadata +11 -36
- metadata.gz.sig +0 -0
@@ -14,7 +14,7 @@ module Aspera
|
|
14
14
|
# @param :format Optional dump method (default to JSON)
|
15
15
|
# @param :merge Optional merge data from file to current data
|
16
16
|
def initialize(options)
|
17
|
-
Log.log.debug
|
17
|
+
Log.log.debug{"persistency: #{options}"}
|
18
18
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
19
19
|
raise 'mandatory :manager' if options[:manager].nil?
|
20
20
|
raise 'mandatory :data' if options[:data].nil?
|
@@ -27,16 +27,16 @@ module Aspera
|
|
27
27
|
@delete_condition = options[:delete] || lambda{|d|d.empty?}
|
28
28
|
@persist_format = options[:format] || lambda {|h| JSON.generate(h)}
|
29
29
|
persist_parse = options[:parse] || lambda {|t| JSON.parse(t)}
|
30
|
-
persist_merge = options[:merge] || lambda {|current,file| current.concat(file).uniq rescue current}
|
30
|
+
persist_merge = options[:merge] || lambda {|current, file| current.concat(file).uniq rescue current}
|
31
31
|
value = @manager.get(@object_id)
|
32
|
-
persist_merge.call(@persisted_object,persist_parse.call(value)) unless value.nil?
|
32
|
+
persist_merge.call(@persisted_object, persist_parse.call(value)) unless value.nil?
|
33
33
|
end
|
34
34
|
|
35
35
|
def save
|
36
36
|
if @delete_condition.call(@persisted_object)
|
37
37
|
@manager.delete(@object_id)
|
38
38
|
else
|
39
|
-
@manager.put(@object_id
|
39
|
+
@manager.put(@object_id, @persist_format.call(@persisted_object))
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'aspera/log'
|
5
|
+
require 'aspera/environment'
|
5
6
|
|
6
7
|
# search: persistency_folder PersistencyFolder
|
7
8
|
|
@@ -13,17 +14,17 @@ module Aspera
|
|
13
14
|
def initialize(folder)
|
14
15
|
@cache = {}
|
15
16
|
@folder = folder
|
16
|
-
Log.log.debug
|
17
|
+
Log.log.debug{"persistency folder: #{@folder}"}
|
17
18
|
end
|
18
19
|
|
19
20
|
# @return String or nil string on existing persist, else nil
|
20
21
|
def get(object_id)
|
21
|
-
Log.log.debug
|
22
|
-
if @cache.
|
22
|
+
Log.log.debug{"persistency get: #{object_id}"}
|
23
|
+
if @cache.key?(object_id)
|
23
24
|
Log.log.debug('got from memory cache')
|
24
25
|
else
|
25
26
|
persist_filepath = id_to_filepath(object_id)
|
26
|
-
Log.log.debug
|
27
|
+
Log.log.debug{"persistency = #{persist_filepath}"}
|
27
28
|
if File.exist?(persist_filepath)
|
28
29
|
Log.log.debug('got from file cache')
|
29
30
|
@cache[object_id] = File.read(persist_filepath)
|
@@ -32,32 +33,32 @@ module Aspera
|
|
32
33
|
return @cache[object_id]
|
33
34
|
end
|
34
35
|
|
35
|
-
def put(object_id,value)
|
36
|
+
def put(object_id, value)
|
36
37
|
raise 'value: only String supported' unless value.is_a?(String)
|
37
38
|
persist_filepath = id_to_filepath(object_id)
|
38
|
-
Log.log.debug
|
39
|
+
Log.log.debug{"persistency saving: #{persist_filepath}"}
|
39
40
|
File.delete(persist_filepath) if File.exist?(persist_filepath)
|
40
|
-
File.write(persist_filepath,value)
|
41
|
-
|
41
|
+
File.write(persist_filepath, value)
|
42
|
+
Environment.restrict_file_access(persist_filepath)
|
42
43
|
@cache[object_id] = value
|
43
44
|
end
|
44
45
|
|
45
46
|
def delete(object_id)
|
46
47
|
persist_filepath = id_to_filepath(object_id)
|
47
|
-
Log.log.debug
|
48
|
+
Log.log.debug{"persistency deleting: #{persist_filepath}"}
|
48
49
|
File.delete(persist_filepath) if File.exist?(persist_filepath)
|
49
50
|
@cache.delete(object_id)
|
50
51
|
end
|
51
52
|
|
52
|
-
def garbage_collect(persist_category,max_age_seconds=nil)
|
53
|
-
garbage_files = Dir[File.join(@folder,persist_category + '*' + FILE_SUFFIX)]
|
53
|
+
def garbage_collect(persist_category, max_age_seconds=nil)
|
54
|
+
garbage_files = Dir[File.join(@folder, persist_category + '*' + FILE_SUFFIX)]
|
54
55
|
if !max_age_seconds.nil?
|
55
56
|
current_time = Time.now
|
56
57
|
garbage_files.select! { |filepath| (current_time - File.stat(filepath).mtime).to_i > max_age_seconds}
|
57
58
|
end
|
58
59
|
garbage_files.each do |filepath|
|
59
60
|
File.delete(filepath)
|
60
|
-
Log.log.debug
|
61
|
+
Log.log.debug{"persistency deleted expired: #{filepath}"}
|
61
62
|
end
|
62
63
|
return garbage_files
|
63
64
|
end
|
@@ -68,9 +69,9 @@ module Aspera
|
|
68
69
|
def id_to_filepath(object_id)
|
69
70
|
raise 'object_id: only String supported' unless object_id.is_a?(String)
|
70
71
|
FileUtils.mkdir_p(@folder)
|
71
|
-
|
72
|
-
return File.join(@folder,"#{object_id}#{FILE_SUFFIX}")
|
73
|
-
|
72
|
+
Environment.restrict_file_access(@folder)
|
73
|
+
return File.join(@folder, "#{object_id}#{FILE_SUFFIX}")
|
74
|
+
# .gsub(/[^a-z]+/,FILE_FIELD_SEPARATOR)
|
74
75
|
end
|
75
76
|
end # PersistencyFolder
|
76
77
|
end # Aspera
|
@@ -286,20 +286,20 @@ module Aspera
|
|
286
286
|
# check magic number inside file (empty string if not found)
|
287
287
|
detected_mime = MimeMagic.by_magic(File.open(filepath)).to_s
|
288
288
|
# check extension only
|
289
|
-
if !SUPPORTED_MIME_TYPES.
|
290
|
-
Log.log.debug
|
289
|
+
if !SUPPORTED_MIME_TYPES.key?(detected_mime)
|
290
|
+
Log.log.debug{"no conversion for #{detected_mime}, trying extension"}
|
291
291
|
detected_mime = MimeMagic.by_extension(File.extname(filepath)).to_s
|
292
292
|
end
|
293
293
|
detected_mime = nil if detected_mime.empty?
|
294
|
-
Log.log.debug
|
294
|
+
Log.log.debug{"mimemagic: #{detected_mime.class.name} [#{detected_mime}]"}
|
295
295
|
return detected_mime
|
296
296
|
end
|
297
297
|
|
298
298
|
# return file type, one of enum CONVERSION_TYPES
|
299
299
|
# @param filepath [String] full path to file
|
300
300
|
# @param mimetype [String] provided by node api
|
301
|
-
def conversion_type(filepath,mimetype)
|
302
|
-
Log.log.debug
|
301
|
+
def conversion_type(filepath, mimetype)
|
302
|
+
Log.log.debug{"conversion_type(#{filepath},m=#{mimetype},t=#{@use_mimemagic})"}
|
303
303
|
# 1- get type from provided mime type, using local mapping
|
304
304
|
conv_type = SUPPORTED_MIME_TYPES[mimetype] if !mimetype.nil?
|
305
305
|
# 2- else, from computed mime type (if available)
|
@@ -311,8 +311,8 @@ module Aspera
|
|
311
311
|
if mimetype.eql?(detected_mime)
|
312
312
|
Log.log.debug('matching mime type per magic number')
|
313
313
|
else
|
314
|
-
#
|
315
|
-
Log.log.debug
|
314
|
+
# NOTE: detected can be nil
|
315
|
+
Log.log.debug{"non matching mime types: node=[#{mimetype}], magic=[#{detected_mime}]"}
|
316
316
|
end
|
317
317
|
end
|
318
318
|
end
|
@@ -320,7 +320,7 @@ module Aspera
|
|
320
320
|
# 3- else, from extensions, using local mapping
|
321
321
|
extension = File.extname(filepath.downcase)[1..-1]
|
322
322
|
conv_type = SUPPORTED_EXTENSIONS[extension] if conv_type.nil?
|
323
|
-
Log.log.debug
|
323
|
+
Log.log.debug{"conversion_type(#{extension}): #{conv_type.class.name} [#{conv_type}]"}
|
324
324
|
return conv_type
|
325
325
|
end
|
326
326
|
end
|
@@ -27,14 +27,14 @@ module Aspera
|
|
27
27
|
# -> preview_format is one of Generator::PREVIEW_FORMATS
|
28
28
|
# the conversion video->mp4 is implemented in methods: convert_video_to_mp4_using_<video_conversion>
|
29
29
|
# -> conversion method is one of Generator::VIDEO_CONVERSION_METHODS
|
30
|
-
def initialize(options,src,dst,main_temp_dir,api_mime_type)
|
30
|
+
def initialize(options, src, dst, main_temp_dir, api_mime_type)
|
31
31
|
@options = options
|
32
32
|
@source_file_path = src
|
33
33
|
@destination_file_path = dst
|
34
|
-
@temp_folder = File.join(main_temp_dir
|
34
|
+
@temp_folder = File.join(main_temp_dir, @source_file_path.split('/').last.gsub(/\s/, '_').gsub(/\W/, ''))
|
35
35
|
# extract preview format from extension of target file
|
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
|
+
@preview_format_symb = File.extname(@destination_file_path).gsub(/^\./, '').to_sym
|
37
|
+
@conversion_type = FileTypes.instance.conversion_type(@source_file_path, api_mime_type)
|
38
38
|
end
|
39
39
|
|
40
40
|
# name of processing method in this object
|
@@ -49,7 +49,7 @@ module Aspera
|
|
49
49
|
name = "#{name}_using_#{@options.video_png_conv}"
|
50
50
|
end
|
51
51
|
end
|
52
|
-
Log.log.debug
|
52
|
+
Log.log.debug{"method: #{name}"}
|
53
53
|
return name.to_sym
|
54
54
|
end
|
55
55
|
|
@@ -57,25 +57,25 @@ module Aspera
|
|
57
57
|
# for instance, plaintext to mp4 is not supported
|
58
58
|
def supported?
|
59
59
|
return false if @conversion_type.nil?
|
60
|
-
return respond_to?(processing_method_symb,true)
|
60
|
+
return respond_to?(processing_method_symb, true)
|
61
61
|
end
|
62
62
|
|
63
63
|
# create preview as specified in constructor
|
64
64
|
def generate
|
65
65
|
raise 'could not detect type of file' if @conversion_type.nil?
|
66
66
|
method_symb = processing_method_symb
|
67
|
-
Log.log.info
|
67
|
+
Log.log.info{"#{@source_file_path}->#{@destination_file_path} (#{method_symb})"}
|
68
68
|
begin
|
69
69
|
send(method_symb)
|
70
70
|
# check that generated size does not exceed maximum
|
71
71
|
result_size = File.size(@destination_file_path)
|
72
72
|
if result_size > @options.max_size
|
73
|
-
Log.log.warn
|
73
|
+
Log.log.warn{"preview size exceeds maximum #{result_size} > #{@options.max_size}"}
|
74
74
|
end
|
75
75
|
rescue StandardError => e
|
76
|
-
Log.log.error
|
76
|
+
Log.log.error{"Ignoging: #{e.message}"}
|
77
77
|
Log.log.debug(e.backtrace.join("\n").red)
|
78
|
-
FileUtils.cp(File.expand_path(@preview_format_symb.eql?(:mp4) ? 'video_error.png' : 'image_error.png',File.dirname(__FILE__))
|
78
|
+
FileUtils.cp(File.expand_path(@preview_format_symb.eql?(:mp4) ? 'video_error.png' : 'image_error.png', File.dirname(__FILE__)), @destination_file_path)
|
79
79
|
ensure
|
80
80
|
FileUtils.rm_rf(@temp_folder)
|
81
81
|
end
|
@@ -106,10 +106,10 @@ module Aspera
|
|
106
106
|
last_keyframe = nil
|
107
107
|
current_index = 1
|
108
108
|
1.upto(p_keyframecount) do |i|
|
109
|
-
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)
|
110
110
|
Utils.video_dump_frame(@source_file_path, offset_seconds, @options.video_scale, this_tmpdir, current_index)
|
111
111
|
Utils.video_dupe_frame(this_tmpdir, current_index, @options.blend_pauseframes)
|
112
|
-
Utils.video_blend_frames(this_tmpdir,last_keyframe, current_index) unless last_keyframe.nil?
|
112
|
+
Utils.video_blend_frames(this_tmpdir, last_keyframe, current_index) unless last_keyframe.nil?
|
113
113
|
# go to last dupe frame
|
114
114
|
last_keyframe = current_index + @options.blend_pauseframes
|
115
115
|
# go after last dupe frame and keep space to blend
|
@@ -117,41 +117,41 @@ module Aspera
|
|
117
117
|
end
|
118
118
|
Utils.ffmpeg(
|
119
119
|
in_f: Utils.ffmpeg_fmt(this_tmpdir),
|
120
|
-
in_p: ['-framerate'
|
120
|
+
in_p: ['-framerate', @options.blend_fps],
|
121
121
|
out_f: @destination_file_path,
|
122
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'])
|
123
|
+
'-filter:v', "scale='trunc(iw/2)*2:trunc(ih/2)*2'",
|
124
|
+
'-codec:v', 'libx264',
|
125
|
+
'-r', 30,
|
126
|
+
'-pix_fmt', 'yuv420p'])
|
127
127
|
end
|
128
128
|
|
129
129
|
# generate n clips starting at offset
|
130
130
|
def convert_video_to_mp4_using_clips
|
131
131
|
p_duration = Utils.video_get_duration(@source_file_path)
|
132
|
-
filelist = File.join(this_tmpdir,'clip_files.txt')
|
132
|
+
filelist = File.join(this_tmpdir, 'clip_files.txt')
|
133
133
|
File.open(filelist, 'w+') do |f|
|
134
134
|
1.upto(@options.clips_count.to_i) do |i|
|
135
|
-
offset_seconds = get_offset(p_duration
|
136
|
-
tmpfilename = format('clip%04d.mp4',i)
|
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)
|
137
137
|
Utils.ffmpeg(
|
138
138
|
in_f: @source_file_path,
|
139
|
-
in_p: ['-ss',0.9
|
140
|
-
out_f: File.join(this_tmpdir,tmpfilename),
|
139
|
+
in_p: ['-ss', offset_seconds * 0.9],
|
140
|
+
out_f: File.join(this_tmpdir, tmpfilename),
|
141
141
|
out_p: [
|
142
|
-
'-ss',0.1
|
143
|
-
'-t'
|
144
|
-
'-filter:v',"scale=#{@options.video_scale}",
|
145
|
-
'-codec:a','libmp3lame'])
|
142
|
+
'-ss', offset_seconds * 0.1,
|
143
|
+
'-t', @options.clips_length,
|
144
|
+
'-filter:v', "scale=#{@options.video_scale}",
|
145
|
+
'-codec:a', 'libmp3lame'])
|
146
146
|
f.puts("file '#{tmpfilename}'")
|
147
147
|
end
|
148
148
|
end
|
149
149
|
# concat clips
|
150
150
|
Utils.ffmpeg(
|
151
151
|
in_f: filelist,
|
152
|
-
in_p: ['-f','concat'],
|
152
|
+
in_p: ['-f', 'concat'],
|
153
153
|
out_f: @destination_file_path,
|
154
|
-
out_p: ['-codec','copy'])
|
154
|
+
out_p: ['-codec', 'copy'])
|
155
155
|
end
|
156
156
|
|
157
157
|
# do a simple reencoding
|
@@ -160,20 +160,20 @@ module Aspera
|
|
160
160
|
in_f: @source_file_path,
|
161
161
|
out_f: @destination_file_path,
|
162
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'])
|
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'])
|
177
177
|
end
|
178
178
|
|
179
179
|
def convert_video_to_png_using_fixed
|
@@ -192,62 +192,62 @@ module Aspera
|
|
192
192
|
Utils.ffmpeg(
|
193
193
|
in_f: @source_file_path,
|
194
194
|
in_p: [
|
195
|
-
'-ss',10, # seek to input position
|
196
|
-
'-t',20 # max seconds
|
195
|
+
'-ss', 10, # seek to input position
|
196
|
+
'-t', 20 # max seconds
|
197
197
|
],
|
198
198
|
out_f: @destination_file_path,
|
199
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'
|
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
203
|
])
|
204
204
|
end
|
205
205
|
|
206
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')
|
208
|
-
Utils.external_command(:unoconv,[
|
209
|
-
'-f','pdf',
|
210
|
-
'-o',tmp_pdf_file,
|
207
|
+
tmp_pdf_file = File.join(this_tmpdir, File.basename(@source_file_path, File.extname(@source_file_path)) + '.pdf')
|
208
|
+
Utils.external_command(:unoconv, [
|
209
|
+
'-f', 'pdf',
|
210
|
+
'-o', tmp_pdf_file,
|
211
211
|
@source_file_path])
|
212
212
|
convert_pdf_to_png(tmp_pdf_file)
|
213
213
|
end
|
214
214
|
|
215
215
|
def convert_pdf_to_png(source_file_path=nil)
|
216
216
|
source_file_path ||= @source_file_path
|
217
|
-
Utils.external_command(:convert,[
|
218
|
-
'-size',"x#{@options.thumb_img_size}",
|
219
|
-
'-background','white',
|
217
|
+
Utils.external_command(:convert, [
|
218
|
+
'-size', "x#{@options.thumb_img_size}",
|
219
|
+
'-background', 'white',
|
220
220
|
'-flatten',
|
221
221
|
"#{source_file_path}[0]",
|
222
222
|
@destination_file_path])
|
223
223
|
end
|
224
224
|
|
225
225
|
def convert_image_to_png
|
226
|
-
Utils.external_command(:convert,[
|
226
|
+
Utils.external_command(:convert, [
|
227
227
|
'-auto-orient',
|
228
|
-
'-thumbnail',"#{@options.thumb_img_size}x#{@options.thumb_img_size}>",
|
229
|
-
'-quality',95,
|
228
|
+
'-thumbnail', "#{@options.thumb_img_size}x#{@options.thumb_img_size}>",
|
229
|
+
'-quality', 95,
|
230
230
|
'+dither',
|
231
|
-
'-posterize',40,
|
231
|
+
'-posterize', 40,
|
232
232
|
"#{@source_file_path}[0]",
|
233
233
|
@destination_file_path])
|
234
|
-
Utils.external_command(:optipng,[@destination_file_path])
|
234
|
+
Utils.external_command(:optipng, [@destination_file_path])
|
235
235
|
end
|
236
236
|
|
237
237
|
# text to png
|
238
238
|
def convert_plaintext_to_png
|
239
239
|
# get 100 first lines of text file
|
240
240
|
first_lines = File.open(@source_file_path){|f|Array.new(100){f.readline rescue ''}.join}
|
241
|
-
Utils.external_command(:convert,[
|
242
|
-
'-size',"#{@options.thumb_img_size}x#{@options.thumb_img_size}",
|
241
|
+
Utils.external_command(:convert, [
|
242
|
+
'-size', "#{@options.thumb_img_size}x#{@options.thumb_img_size}",
|
243
243
|
'xc:white', # define canvas with background color (xc, or canvas) of preceding size
|
244
|
-
'-font'
|
245
|
-
'-pointsize',12,
|
246
|
-
'-fill','black', # font color
|
247
|
-
'-annotate','+0+0',first_lines,
|
244
|
+
'-font', @options.thumb_text_font,
|
245
|
+
'-pointsize', 12,
|
246
|
+
'-fill', 'black', # font color
|
247
|
+
'-annotate', '+0+0', first_lines,
|
248
248
|
'-trim', # avoid large blank regions
|
249
|
-
'-bordercolor','white',
|
250
|
-
'-border',8,
|
249
|
+
'-bordercolor', 'white',
|
250
|
+
'-border', 8,
|
251
251
|
'+repage',
|
252
252
|
@destination_file_path])
|
253
253
|
end
|
data/lib/aspera/preview/utils.rb
CHANGED
@@ -16,7 +16,7 @@ module Aspera
|
|
16
16
|
# external binaries used
|
17
17
|
EXPERNAL_TOOLS = %i[ffmpeg ffprobe convert composite optipng unoconv].freeze
|
18
18
|
TMPFMT = 'img%04d.jpg'
|
19
|
-
private_constant :BASH_SPECIAL_CHARACTERS
|
19
|
+
private_constant :BASH_SPECIAL_CHARACTERS, :BASH_EXIT_NOT_FOUND, :EXPERNAL_TOOLS, :TMPFMT
|
20
20
|
|
21
21
|
class << self
|
22
22
|
# returns string with single quotes suitable for bash if there is any bash metacharacter
|
@@ -27,22 +27,22 @@ module Aspera
|
|
27
27
|
|
28
28
|
# check that external tools can be executed
|
29
29
|
def check_tools(skip_types=[])
|
30
|
-
tools_to_check=EXPERNAL_TOOLS.dup
|
30
|
+
tools_to_check = EXPERNAL_TOOLS.dup
|
31
31
|
tools_to_check.delete(:unoconv) if skip_types.include?(:office)
|
32
32
|
# Check for binaries
|
33
33
|
tools_to_check.each do |command_symb|
|
34
|
-
external_command(command_symb,['-h'])
|
34
|
+
external_command(command_symb, ['-h'])
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
# execute external command
|
39
39
|
# one could use "system", but we would need to redirect stdout/err
|
40
40
|
# @return true if su
|
41
|
-
def external_command(command_symb,command_args)
|
41
|
+
def external_command(command_symb, command_args)
|
42
42
|
raise "unexpected command #{command_symb}" unless EXPERNAL_TOOLS.include?(command_symb)
|
43
43
|
# build command line, and quote special characters
|
44
44
|
command = command_args.clone.unshift(command_symb).map{|i| shell_quote(i.to_s)}.join(' ')
|
45
|
-
Log.log.debug
|
45
|
+
Log.log.debug{"cmd=#{command}".blue}
|
46
46
|
# capture3: only in ruby2+
|
47
47
|
if Open3.respond_to?(:capture3)
|
48
48
|
stdout, stderr, exit_status = Open3.capture3(command)
|
@@ -55,10 +55,10 @@ module Aspera
|
|
55
55
|
raise "Error: #{command_symb} is not in the PATH"
|
56
56
|
end
|
57
57
|
unless exit_status.success?
|
58
|
-
Log.log.error
|
59
|
-
Log.log.error
|
60
|
-
Log.log.error
|
61
|
-
Log.log.error
|
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
62
|
raise "#{command_symb} error #{exit_status}"
|
63
63
|
end
|
64
64
|
return {status: exit_status, stdout: stdout}
|
@@ -66,60 +66,60 @@ module Aspera
|
|
66
66
|
|
67
67
|
def ffmpeg(a)
|
68
68
|
raise 'error: hash expected' unless a.is_a?(Hash)
|
69
|
-
#input_file,input_args,output_file,output_args
|
69
|
+
# input_file,input_args,output_file,output_args
|
70
70
|
a[:gl_p] ||= [
|
71
71
|
'-y', # overwrite output without asking
|
72
|
-
'-loglevel','error' # show only errors and up]
|
72
|
+
'-loglevel', 'error' # show only errors and up]
|
73
73
|
]
|
74
74
|
a[:in_p] ||= []
|
75
75
|
a[:out_p] ||= []
|
76
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)
|
77
|
+
external_command(:ffmpeg, [a[:gl_p], a[:in_p], '-i', a[:in_f], a[:out_p], a[:out_f]].flatten)
|
78
78
|
end
|
79
79
|
|
80
80
|
# @return Float in seconds
|
81
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',
|
82
|
+
result = external_command(:ffprobe, [
|
83
|
+
'-loglevel', 'error',
|
84
|
+
'-show_entries', 'format=duration',
|
85
|
+
'-print_format', 'default=noprint_wrappers=1:nokey=1',
|
86
86
|
input_file])
|
87
87
|
return result[:stdout].to_f
|
88
88
|
end
|
89
89
|
|
90
90
|
def ffmpeg_fmt(temp_folder)
|
91
|
-
return File.join(temp_folder,TMPFMT)
|
91
|
+
return File.join(temp_folder, TMPFMT)
|
92
92
|
end
|
93
93
|
|
94
94
|
def get_tmp_num_filepath(temp_folder, file_number)
|
95
|
-
return File.join(temp_folder,format(TMPFMT,file_number))
|
95
|
+
return File.join(temp_folder, format(TMPFMT, file_number))
|
96
96
|
end
|
97
97
|
|
98
98
|
def video_dupe_frame(temp_folder, index, count)
|
99
|
-
input_file = get_tmp_num_filepath(temp_folder,index)
|
99
|
+
input_file = get_tmp_num_filepath(temp_folder, index)
|
100
100
|
1.upto(count) do |i|
|
101
|
-
FileUtils.ln_s(input_file,get_tmp_num_filepath(temp_folder,index + i))
|
101
|
+
FileUtils.ln_s(input_file, get_tmp_num_filepath(temp_folder, index + i))
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
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)
|
106
|
+
img1 = get_tmp_num_filepath(temp_folder, index1)
|
107
|
+
img2 = get_tmp_num_filepath(temp_folder, index2)
|
108
108
|
count = index2 - index1 - 1
|
109
109
|
1.upto(count) do |i|
|
110
|
-
percent =
|
110
|
+
percent = i * 100 / (count + 1)
|
111
111
|
filename = get_tmp_num_filepath(temp_folder, index1 + i)
|
112
|
-
external_command(:composite,['-blend',percent,img2,img1,filename])
|
112
|
+
external_command(:composite, ['-blend', percent, img2, img1, filename])
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
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?
|
117
|
+
output_file = get_tmp_num_filepath(output_file, index) unless index.nil?
|
118
118
|
ffmpeg(
|
119
119
|
in_f: input_file,
|
120
|
-
in_p: ['-ss',offset_seconds],
|
120
|
+
in_p: ['-ss', offset_seconds],
|
121
121
|
out_f: output_file,
|
122
|
-
out_p: ['-frames:v',1,'-filter:v',"scale=#{scale}"])
|
122
|
+
out_p: ['-frames:v', 1, '-filter:v', "scale=#{scale}"])
|
123
123
|
return output_file
|
124
124
|
end
|
125
125
|
end
|
@@ -207,65 +207,65 @@ function timeRange() {
|
|
207
207
|
var arg = arguments[i];
|
208
208
|
if (gmt) {
|
209
209
|
switch (i) {
|
210
|
-
case 0:
|
211
|
-
date1.setUTCHours(arg);
|
212
|
-
date2.setUTCHours(arg);
|
213
|
-
break;
|
214
|
-
case 1:
|
215
|
-
date1.setUTCMinutes(arg);
|
216
|
-
date2.setUTCMinutes(arg);
|
217
|
-
break;
|
218
|
-
case 2:
|
219
|
-
date1.setUTCSeconds(arg);
|
220
|
-
date2.setUTCSeconds(arg);
|
221
|
-
break;
|
222
|
-
}
|
223
|
-
} else {
|
224
|
-
switch (i) {
|
225
|
-
case 0:
|
226
|
-
date1.setHours(arg);
|
227
|
-
date2.setHours(arg);
|
228
|
-
break;
|
229
|
-
case 1:
|
230
|
-
date1.setMinutes(arg);
|
231
|
-
date2.setMinutes(arg);
|
232
|
-
break;
|
233
|
-
case 2:
|
234
|
-
date1.setSeconds(arg);
|
235
|
-
date2.setSeconds(arg);
|
236
|
-
break;
|
237
|
-
}
|
238
|
-
}
|
239
|
-
}
|
240
|
-
if (num != 1) {
|
241
|
-
date2.setMinutes(0);
|
242
|
-
date2.setSeconds(0);
|
243
|
-
date2.setMilliseconds(0);
|
244
|
-
for (i = 0; i < (num / 2); i++) {
|
245
|
-
var arg = arguments[(num / 2) + i];
|
246
|
-
if (gmt) {
|
247
|
-
switch (i) {
|
248
210
|
case 0:
|
211
|
+
date1.setUTCHours(arg);
|
249
212
|
date2.setUTCHours(arg);
|
250
213
|
break;
|
251
214
|
case 1:
|
215
|
+
date1.setUTCMinutes(arg);
|
252
216
|
date2.setUTCMinutes(arg);
|
253
217
|
break;
|
254
218
|
case 2:
|
219
|
+
date1.setUTCSeconds(arg);
|
255
220
|
date2.setUTCSeconds(arg);
|
256
221
|
break;
|
257
|
-
|
258
|
-
|
259
|
-
|
222
|
+
}
|
223
|
+
} else {
|
224
|
+
switch (i) {
|
260
225
|
case 0:
|
226
|
+
date1.setHours(arg);
|
261
227
|
date2.setHours(arg);
|
262
228
|
break;
|
263
229
|
case 1:
|
230
|
+
date1.setMinutes(arg);
|
264
231
|
date2.setMinutes(arg);
|
265
232
|
break;
|
266
233
|
case 2:
|
234
|
+
date1.setSeconds(arg);
|
267
235
|
date2.setSeconds(arg);
|
268
236
|
break;
|
237
|
+
}
|
238
|
+
}
|
239
|
+
}
|
240
|
+
if (num != 1) {
|
241
|
+
date2.setMinutes(0);
|
242
|
+
date2.setSeconds(0);
|
243
|
+
date2.setMilliseconds(0);
|
244
|
+
for (i = 0; i < (num / 2); i++) {
|
245
|
+
var arg = arguments[(num / 2) + i];
|
246
|
+
if (gmt) {
|
247
|
+
switch (i) {
|
248
|
+
case 0:
|
249
|
+
date2.setUTCHours(arg);
|
250
|
+
break;
|
251
|
+
case 1:
|
252
|
+
date2.setUTCMinutes(arg);
|
253
|
+
break;
|
254
|
+
case 2:
|
255
|
+
date2.setUTCSeconds(arg);
|
256
|
+
break;
|
257
|
+
}
|
258
|
+
} else {
|
259
|
+
switch (i) {
|
260
|
+
case 0:
|
261
|
+
date2.setHours(arg);
|
262
|
+
break;
|
263
|
+
case 1:
|
264
|
+
date2.setMinutes(arg);
|
265
|
+
break;
|
266
|
+
case 2:
|
267
|
+
date2.setSeconds(arg);
|
268
|
+
break;
|
269
269
|
}
|
270
270
|
}
|
271
271
|
}
|