Class: Aspera::Preview::Utils
- Inherits:
-
Object
- Object
- Aspera::Preview::Utils
- Defined in:
- lib/aspera/preview/utils.rb
Class Attribute Summary collapse
Class Method Summary collapse
-
.available_h264_encoder ⇒ String
Return the first H.264 encoder available in the local ffmpeg installation.
-
.check_tools(skip_types = []) ⇒ nil
Check that external tools can be executed.
-
.execute(*args, **kwargs) ⇒ Array<String>
Execute external command, verify it is in the supported list.
-
.ffmpeg(in:, out:, global: FFMPEG_DEFAULT_PARAMS) ⇒ nil
Execute
ffmpeg, capturing output. -
.ffmpeg_fmt(temp_folder) ⇒ String
File output pattern for ffmpeg, including temp folder.
-
.get_tmp_num_filepath(temp_folder, file_number) ⇒ String
Get numbered temporary file path.
-
.parse_magick_fonts(output) ⇒ Hash
Parse the output of
magick identify -list fontcommand. -
.silent_execute(*args) ⇒ nil
Execute external command, capturing and discarding output unless it fails.
-
.video_blend_frames(temp_folder, index_begin, index_end) ⇒ nil
Blend transition frames between two keyframes using ImageMagick.
-
.video_dump_frame(input_file, offset_seconds, scale, output_file) ⇒ nil
Dump a frame from a video file.
-
.video_dupe_frame(temp_folder, index, count) ⇒ nil
Duplicate a video frame by creating symlinks.
-
.video_get_duration(input_file) ⇒ Float
Get duration of a video file using
ffprobe.
Class Attribute Details
.office_tool ⇒ Object
29 30 31 |
# File 'lib/aspera/preview/utils.rb', line 29 def office_tool @office_tool end |
Class Method Details
.available_h264_encoder ⇒ String
Return the first H.264 encoder available in the local ffmpeg installation. Result is memoized after the first call.
35 36 37 38 39 40 41 42 |
# File 'lib/aspera/preview/utils.rb', line 35 def available_h264_encoder return @available_h264_encoder if defined?(@available_h264_encoder) stdout, = execute(:ffmpeg, '-encoders', mode: :capture, exception: false) available = stdout.lines.grep(/h264/i).map { |l| l.split[1] } @available_h264_encoder = H264_ENCODER_PREFERENCE.find { |enc| available.include?(enc) } Aspera.assert(@available_h264_encoder) { "No supported H.264 encoder found in ffmpeg. Available: #{available.join(', ')}" } @available_h264_encoder end |
.check_tools(skip_types = []) ⇒ nil
Check that external tools can be executed.
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/aspera/preview/utils.rb', line 48 def check_tools(skip_types = []) tools_to_check = EXTERNAL_TOOLS.dup tools_to_check.delete(:unoconv) if skip_types.include?(:office) || office_tool.eql?(:soffice) tools_to_check.delete(:soffice) if skip_types.include?(:office) || office_tool.eql?(:unoconv) # Check for binaries tools_to_check.each do |command_sym| silent_execute(command_sym, '-h') rescue Errno::ENOENT => e raise "missing #{command_sym} binary: #{e}" rescue nil end end |
.execute(*args, **kwargs) ⇒ Array<String>
Execute external command, verify it is in the supported list.
67 68 69 70 |
# File 'lib/aspera/preview/utils.rb', line 67 def execute(*args, **kwargs) Aspera.assert_values(args.first, EXTERNAL_TOOLS) { 'command' } Environment.secure_execute(*args, **kwargs) end |
.ffmpeg(in:, out:, global: FFMPEG_DEFAULT_PARAMS) ⇒ nil
Execute ffmpeg, capturing output.
On failure, the ffmpeg stderr is logged at debug level and re-raised.
87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/aspera/preview/utils.rb', line 87 def ffmpeg(in:, out:, global: FFMPEG_DEFAULT_PARAMS) Aspera.assert_type(global, Array) # NOTE: cannot use just "in", as it is a reserved word in ruby in_args = binding.local_variable_get(:in).dup out_args = out.dup Aspera.assert_type(in_args, Array) Aspera.assert_type(out_args, Array) in_file = in_args.shift out_file = out_args.shift execute(:ffmpeg, *global, *in_args, '-i', in_file, *out_args, out_file, mode: :capture) nil end |
.ffmpeg_fmt(temp_folder) ⇒ String
File output pattern for ffmpeg, including temp folder.
117 118 119 |
# File 'lib/aspera/preview/utils.rb', line 117 def ffmpeg_fmt(temp_folder) return File.join(temp_folder, TEMP_FORMAT) end |
.get_tmp_num_filepath(temp_folder, file_number) ⇒ String
Get numbered temporary file path.
125 126 127 |
# File 'lib/aspera/preview/utils.rb', line 125 def get_tmp_num_filepath(temp_folder, file_number) return File.join(temp_folder, format(TEMP_FORMAT, file_number)) end |
.parse_magick_fonts(output) ⇒ Hash
Parse the output of magick identify -list font command
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/aspera/preview/utils.rb', line 178 def parse_magick_fonts(output) result = {path: nil, fonts: []} current_font = nil output.each_line do |line| line = line.strip # Parse the Path line if line.start_with?('Path:') result[:path] = line.sub(/^Path:\s*/, '') # Parse Font name elsif line.start_with?('Font:') # Save previous font if exists result[:fonts] << current_font if current_font # Start new font current_font = {name: line.sub(/^Font:\s*/, '')} # Parse font properties elsif current_font && line.include?(':') key, value = line.split(':', 2) key = key.strip.gsub(/\s+/, '_').to_sym value = value.strip # Convert numeric values value = value.to_i if key == :weight || key == :index current_font[key] = value end end # Don't forget the last font result[:fonts] << current_font if current_font result end |
.silent_execute(*args) ⇒ nil
Execute external command, capturing and discarding output unless it fails. On failure, the captured stderr is included in the raised exception message.
76 77 78 79 |
# File 'lib/aspera/preview/utils.rb', line 76 def silent_execute(*args) execute(*args, mode: :capture) nil end |
.video_blend_frames(temp_folder, index_begin, index_end) ⇒ nil
Blend transition frames between two keyframes using ImageMagick.
147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/aspera/preview/utils.rb', line 147 def video_blend_frames(temp_folder, index_begin, index_end) img1 = get_tmp_num_filepath(temp_folder, index_begin) img2 = get_tmp_num_filepath(temp_folder, index_end) count = index_end - index_begin - 1 1.upto(count) do |i| percent = i * 100 / (count + 1) filename = get_tmp_num_filepath(temp_folder, index_begin + i) silent_execute(:magick, 'composite', '-blend', percent, img2, img1, filename) end nil end |
.video_dump_frame(input_file, offset_seconds, scale, output_file) ⇒ nil
Dump a frame from a video file
165 166 167 168 169 170 |
# File 'lib/aspera/preview/utils.rb', line 165 def video_dump_frame(input_file, offset_seconds, scale, output_file) ffmpeg( in: [input_file, '-ss', offset_seconds], out: [output_file, '-frames:v', 1, '-filter:v', "scale='#{scale}'"] ) end |
.video_dupe_frame(temp_folder, index, count) ⇒ nil
Duplicate a video frame by creating symlinks.
134 135 136 137 138 139 140 |
# File 'lib/aspera/preview/utils.rb', line 134 def video_dupe_frame(temp_folder, index, count) input_file = get_tmp_num_filepath(temp_folder, index) 1.upto(count) do |i| FileUtils.ln_s(input_file, get_tmp_num_filepath(temp_folder, index + i)) end nil end |
.video_get_duration(input_file) ⇒ Float
Get duration of a video file using ffprobe.
103 104 105 106 107 108 109 110 111 112 |
# File 'lib/aspera/preview/utils.rb', line 103 def video_get_duration(input_file) return execute( :ffprobe, '-loglevel', 'error', '-show_entries', 'format=duration', '-print_format', 'default=noprint_wrappers=1:nokey=1', # cspell:disable-line input_file, mode: :capture ).first.to_f end |