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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -0
  3. data/README.md +1894 -1574
  4. data/bin/ascli +21 -1
  5. data/bin/asession +38 -34
  6. data/docs/test_env.conf +14 -3
  7. data/examples/aoc.rb +17 -15
  8. data/examples/dascli +26 -0
  9. data/examples/faspex4.rb +42 -35
  10. data/examples/proxy.pac +1 -1
  11. data/examples/transfer.rb +38 -37
  12. data/lib/aspera/aoc.rb +245 -205
  13. data/lib/aspera/ascmd.rb +111 -90
  14. data/lib/aspera/ats_api.rb +16 -14
  15. data/lib/aspera/cli/basic_auth_plugin.rb +19 -18
  16. data/lib/aspera/cli/extended_value.rb +50 -39
  17. data/lib/aspera/cli/formater.rb +161 -135
  18. data/lib/aspera/cli/info.rb +18 -0
  19. data/lib/aspera/cli/listener/line_dump.rb +4 -2
  20. data/lib/aspera/cli/listener/logger.rb +3 -1
  21. data/lib/aspera/cli/listener/progress.rb +20 -21
  22. data/lib/aspera/cli/listener/progress_multi.rb +29 -31
  23. data/lib/aspera/cli/main.rb +194 -183
  24. data/lib/aspera/cli/manager.rb +213 -206
  25. data/lib/aspera/cli/plugin.rb +71 -49
  26. data/lib/aspera/cli/plugins/alee.rb +8 -7
  27. data/lib/aspera/cli/plugins/aoc.rb +675 -558
  28. data/lib/aspera/cli/plugins/ats.rb +116 -109
  29. data/lib/aspera/cli/plugins/bss.rb +35 -34
  30. data/lib/aspera/cli/plugins/config.rb +722 -542
  31. data/lib/aspera/cli/plugins/console.rb +28 -22
  32. data/lib/aspera/cli/plugins/cos.rb +28 -37
  33. data/lib/aspera/cli/plugins/faspex.rb +281 -227
  34. data/lib/aspera/cli/plugins/faspex5.rb +129 -84
  35. data/lib/aspera/cli/plugins/node.rb +426 -232
  36. data/lib/aspera/cli/plugins/orchestrator.rb +106 -98
  37. data/lib/aspera/cli/plugins/preview.rb +196 -191
  38. data/lib/aspera/cli/plugins/server.rb +131 -126
  39. data/lib/aspera/cli/plugins/shares.rb +49 -36
  40. data/lib/aspera/cli/plugins/sync.rb +27 -28
  41. data/lib/aspera/cli/transfer_agent.rb +84 -79
  42. data/lib/aspera/cli/version.rb +3 -1
  43. data/lib/aspera/colors.rb +37 -28
  44. data/lib/aspera/command_line_builder.rb +84 -63
  45. data/lib/aspera/cos_node.rb +68 -34
  46. data/lib/aspera/data_repository.rb +4 -2
  47. data/lib/aspera/environment.rb +61 -46
  48. data/lib/aspera/fasp/agent_base.rb +36 -31
  49. data/lib/aspera/fasp/agent_connect.rb +44 -37
  50. data/lib/aspera/fasp/agent_direct.rb +101 -104
  51. data/lib/aspera/fasp/agent_httpgw.rb +91 -90
  52. data/lib/aspera/fasp/agent_node.rb +36 -33
  53. data/lib/aspera/fasp/agent_trsdk.rb +28 -31
  54. data/lib/aspera/fasp/error.rb +3 -1
  55. data/lib/aspera/fasp/error_info.rb +81 -54
  56. data/lib/aspera/fasp/installation.rb +171 -151
  57. data/lib/aspera/fasp/listener.rb +2 -0
  58. data/lib/aspera/fasp/parameters.rb +105 -111
  59. data/lib/aspera/fasp/parameters.yaml +305 -249
  60. data/lib/aspera/fasp/resume_policy.rb +20 -20
  61. data/lib/aspera/fasp/transfer_spec.rb +27 -0
  62. data/lib/aspera/fasp/uri.rb +31 -29
  63. data/lib/aspera/faspex_gw.rb +95 -118
  64. data/lib/aspera/hash_ext.rb +12 -13
  65. data/lib/aspera/id_generator.rb +11 -9
  66. data/lib/aspera/keychain/encrypted_hash.rb +73 -57
  67. data/lib/aspera/keychain/macos_security.rb +27 -29
  68. data/lib/aspera/log.rb +40 -39
  69. data/lib/aspera/nagios.rb +24 -22
  70. data/lib/aspera/node.rb +38 -30
  71. data/lib/aspera/oauth.rb +217 -248
  72. data/lib/aspera/open_application.rb +9 -7
  73. data/lib/aspera/persistency_action_once.rb +15 -14
  74. data/lib/aspera/persistency_folder.rb +15 -18
  75. data/lib/aspera/preview/file_types.rb +266 -270
  76. data/lib/aspera/preview/generator.rb +94 -92
  77. data/lib/aspera/preview/image_error.png +0 -0
  78. data/lib/aspera/preview/options.rb +20 -17
  79. data/lib/aspera/preview/utils.rb +99 -102
  80. data/lib/aspera/preview/video_error.png +0 -0
  81. data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
  82. data/lib/aspera/proxy_auto_config.rb +114 -21
  83. data/lib/aspera/rest.rb +144 -142
  84. data/lib/aspera/rest_call_error.rb +3 -2
  85. data/lib/aspera/rest_error_analyzer.rb +31 -31
  86. data/lib/aspera/rest_errors_aspera.rb +18 -16
  87. data/lib/aspera/secret_hider.rb +68 -0
  88. data/lib/aspera/ssh.rb +20 -16
  89. data/lib/aspera/sync.rb +57 -54
  90. data/lib/aspera/temp_file_manager.rb +20 -14
  91. data/lib/aspera/timer_limiter.rb +10 -8
  92. data/lib/aspera/uri_reader.rb +14 -15
  93. data/lib/aspera/web_auth.rb +85 -80
  94. data.tar.gz.sig +0 -0
  95. metadata +169 -40
  96. metadata.gz.sig +2 -0
  97. data/bin/dascli +0 -13
  98. data/docs/Makefile +0 -63
  99. data/docs/README.erb.md +0 -4221
  100. data/docs/README.md +0 -13
  101. data/docs/diagrams.txt +0 -49
  102. data/docs/doc_tools.rb +0 -58
  103. data/lib/aspera/cli/plugins/shares2.rb +0 -114
  104. 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=[:png,:mp4]
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 "could not detect type of file" if @conversion_type.nil?
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
- self.send(method_symb)
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
- return start_offset + (index-1)*(1.0*duration - start_offset) / total_count
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
- in_f: Utils.ffmpeg_fmt(this_tmpdir),
117
- in_p: ['-framerate',@options.blend_fps],
118
- out_f: @destination_file_path,
119
- out_p: [
120
- '-filter:v',"scale='trunc(iw/2)*2:trunc(ih/2)*2'",
121
- '-codec:v','libx264',
122
- '-r',30,
123
- '-pix_fmt','yuv420p'])
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=sprintf("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)
134
137
  Utils.ffmpeg(
135
- in_f: @source_file_path,
136
- in_p: ['-ss',0.9*offset_seconds],
137
- out_f: File.join(this_tmpdir,tmpfilename),
138
- out_p: [
139
- '-ss',0.1*offset_seconds,
140
- '-t',@options.clips_length,
141
- '-filter:v',"scale=#{@options.video_scale}",
142
- '-codec:a','libmp3lame'])
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
- in_f: filelist,
149
- in_p: ['-f','concat'],
150
- out_f: @destination_file_path,
151
- out_p: ['-codec','copy'])
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
- in_f: @source_file_path,
158
- out_f: @destination_file_path,
159
- out_p: [
160
- '-t','60',
161
- '-codec:v','libx264',
162
- '-profile:v','high',
163
- '-pix_fmt','yuv420p',
164
- '-preset','slow',
165
- '-b:v','500k',
166
- '-maxrate','500k',
167
- '-bufsize','1000k',
168
- '-filter:v',"scale=#{@options.video_scale}",
169
- '-threads','0',
170
- '-codec:a','libmp3lame',
171
- '-ac','2',
172
- '-b:a','128k',
173
- '-movflags','faststart'])
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
- @source_file_path,
179
- Utils.video_get_duration(@source_file_path)*@options.thumb_vid_fraction,
180
- @options.thumb_vid_scale,
181
- @destination_file_path)
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
- in_f: @source_file_path,
191
- in_p: [
192
- '-ss',10, # seek to input position
193
- '-t',20, # max seconds
194
- ],
195
- out_f: @destination_file_path,
196
- out_p: [
197
- '-vf','fps=5,scale=120:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse',
198
- '-loop',0,
199
- '-f','gif'
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||=@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.times.map{f.readline rescue ''}}.join
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','Courier',
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','+15+15',first_lines,
245
- '-trim',
246
- '-bordercolor','#FFF',
247
- '-border',10,
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=[:reencode,:blend,:clips]
8
- VIDEO_THUMBNAIL_METHODS=[:fixed,:animated]
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, description: "maximum size (in bytes) of preview file" },
15
- { name: :thumb_vid_scale, default: "-1:'min(ih,100)'", description: "png: video: size (ffmpeg scale argument)" },
16
- { name: :thumb_vid_fraction, default: 0.1, description: "png: video: position of snapshot" },
17
- { name: :thumb_img_size, default: 800, description: "png: non-video: height (and width)" },
18
- { name: :video_conversion, default: :reencode, description: "mp4: method for preview generation", :values => VIDEO_CONVERSION_METHODS },
19
- { name: :video_png_conv, default: :fixed, description: "mp4: method for thumbnail generation", :values => VIDEO_THUMBNAIL_METHODS },
20
- { name: :video_start_sec, default: 10, description: "mp4: start offset (seconds) of video preview" },
21
- { name: :video_scale, default: "'min(iw,360)':-2", description: "mp4: video scale (ffmpeg)" },
22
- { name: :blend_keyframes, default: 30, description: "mp4: blend: # key frames" },
23
- { name: :blend_pauseframes, default: 3, description: "mp4: blend: # pause frames" },
24
- { name: :blend_transframes, default: 5, description: "mp4: blend: # transition blend frames" },
25
- { name: :blend_fps, default: 15, description: "mp4: blend: frame per second" },
26
- { name: :clips_count, default: 5, description: "mp4: clips: number of clips" },
27
- { name: :clips_length, default: 5, description: "mp4: clips: length in seconds of each clips" },
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]
@@ -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=[:ffmpeg,:ffprobe,:convert,:composite,:optipng,:unoconv]
15
- private_constant :BASH_SPECIAL_CHARACTERS,:BASH_EXIT_NOT_FOUND,:EXPERNAL_TOOLS
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
- # check that external tools can be executed
24
- def self.check_tools(skip_types=[])
25
- EXPERNAL_TOOLS.delete(:unoconv) if skip_types.include?(:office)
26
- # Check for binaries
27
- EXPERNAL_TOOLS.map{|i|i.to_s}.each do |bin|
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
- # execute external command
34
- # one could use "system", but we would need to redirect stdout/err
35
- # @return true if su
36
- def self.external_command(command_symb,command_args,stdout_return=nil)
37
- raise "unexpected command #{command_symb}" unless EXPERNAL_TOOLS.include?(command_symb)
38
- # build command line, and quote special characters
39
- command=command_args.clone.unshift(command_symb).map{|i| shell_quote(i.to_s)}.join(' ')
40
- Log.log.debug("cmd=#{command}".blue)
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
- def self.video_get_duration(input_file)
77
- result = String.new
78
- external_command(:ffprobe,[
79
- '-loglevel','error',
80
- '-show_entries','format=duration',
81
- '-print_format','default=noprint_wrappers=1:nokey=1',
82
- input_file],result)
83
- result.to_f
84
- end
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
- TMPFMT='img%04d.jpg'
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
- def self.ffmpeg_fmt(temp_folder)
89
- return File.join(temp_folder,TMPFMT)
90
- end
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
- def self.get_tmp_num_filepath(temp_folder, file_number)
93
- return File.join(temp_folder,sprintf(TMPFMT,file_number))
94
- end
90
+ def ffmpeg_fmt(temp_folder)
91
+ return File.join(temp_folder,TMPFMT)
92
+ end
95
93
 
96
- def self.video_dupe_frame(temp_folder, index, count)
97
- input_file=get_tmp_num_filepath(temp_folder,index)
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
- def self.video_blend_frames(temp_folder, index1, index2)
104
- img1=get_tmp_num_filepath(temp_folder,index1)
105
- img2=get_tmp_num_filepath(temp_folder,index2)
106
- count=index2-index1-1
107
- 1.upto(count) do |i|
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
- def self.video_dump_frame(input_file, offset_seconds, scale, output_file, index=nil)
115
- output_file=get_tmp_num_filepath(output_file,index) unless index.nil?
116
- ffmpeg(
117
- in_f: input_file,
118
- in_p: ['-ss',offset_seconds],
119
- out_f: output_file,
120
- out_p: ['-frames:v',1,'-filter:v',"scale=#{scale}"])
121
- return output_file
122
- end
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
- def message_to_png(message)
125
- # convert -size 400x -background '#666666' -fill '#ffffff' -interword-spacing 10 -kerning 4 -pointsize 10 -gravity West -size x22 label:"Lorem dolor sit amet" -flatten xxx.png
126
- external_command(:convert,[
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