Module: Metaclean::Ffmpeg
- Defined in:
- lib/metaclean/ffmpeg.rb
Constant Summary collapse
- NESTED_IMAGE_CODECS =
%w[bmp gif mjpeg mjpegb png tiff webp].freeze
Class Method Summary collapse
- .append_safe_stream_languages(args, streams) ⇒ Object
- .available? ⇒ Boolean
- .ensure_structure_preserved!(before, after) ⇒ Object
- .file_url(path) ⇒ Object
- .probe_structure(path) ⇒ Object
- .reject_unverifiable_streams!(streams) ⇒ Object
- .stream_signatures(streams) ⇒ Object
- .strip!(path) ⇒ Object
- .version ⇒ Object
Class Method Details
.append_safe_stream_languages(args, streams) ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/metaclean/ffmpeg.rb', line 113 def append_safe_stream_languages(args, streams) streams.each do |stream| index = Integer(stream.fetch('index')) = stream['tags'].is_a?(Hash) ? stream['tags'] : {} language = ['language'].to_s.downcase if language.match?(/\A[a-z]{2,3}(?:-[a-z0-9]{2,8})*\z/) args.concat(["-metadata:s:#{index}", "language=#{language}"]) end end rescue KeyError, TypeError, ArgumentError => e raise Error, "Unexpected ffprobe stream data: #{e.}" end |
.available? ⇒ Boolean
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/metaclean/ffmpeg.rb', line 11 def available? return @available if defined?(@available) out, _err, status = Metaclean.capture3( 'ffmpeg', '-version', timeout: Metaclean::PROBE_TIMEOUT, max_output: Metaclean::PROBE_MAX_OUTPUT_BYTES ) _probe_out, _probe_err, probe_status = Metaclean.capture3( 'ffprobe', '-version', timeout: Metaclean::PROBE_TIMEOUT, max_output: Metaclean::PROBE_MAX_OUTPUT_BYTES ) @available = status.success? && probe_status.success? @version = @available ? out.lines.first.to_s.split[2] : nil @available rescue Errno::ENOENT, Error @version = nil @available = false end |
.ensure_structure_preserved!(before, after) ⇒ Object
101 102 103 104 105 106 107 |
# File 'lib/metaclean/ffmpeg.rb', line 101 def ensure_structure_preserved!(before, after) before_signatures = stream_signatures(before[:streams]) after_signatures = stream_signatures(after[:streams]) unless before_signatures == after_signatures && before[:chapters] == after[:chapters] raise Error, 'ffmpeg output changed the stream or chapter structure' end end |
.file_url(path) ⇒ Object
126 127 128 |
# File 'lib/metaclean/ffmpeg.rb', line 126 def file_url(path) "file:#{File.(path)}" end |
.probe_structure(path) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/metaclean/ffmpeg.rb', line 65 def probe_structure(path) out, err, status = Metaclean.capture3( 'ffprobe', '-v', 'error', '-show_streams', '-show_chapters', '-of', 'json', file_url(path) ) raise Error, "ffprobe failed: #{err.strip}" unless status.success? data = JSON.parse(out) streams = data.fetch('streams', []) chapters = data.fetch('chapters', []) raise TypeError unless streams.is_a?(Array) && chapters.is_a?(Array) { streams: streams, chapters: chapters.size } rescue JSON::ParserError, KeyError, TypeError => e raise Error, "Could not parse ffprobe output: #{e.}" end |
.reject_unverifiable_streams!(streams) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/metaclean/ffmpeg.rb', line 82 def reject_unverifiable_streams!(streams) video_streams = streams.count { |stream| stream['codec_type'] == 'video' } nested = streams.any? do |stream| = stream['tags'].is_a?(Hash) ? stream['tags'] : {} disposition = stream['disposition'].is_a?(Hash) ? stream['disposition'] : {} nested_image = stream['codec_type'] == 'video' && NESTED_IMAGE_CODECS.include?(stream['codec_name']) && (video_streams > 1 || streams.any? { |candidate| candidate['codec_type'] == 'audio' }) stream['codec_type'] == 'attachment' || nested_image || disposition['attached_pic'].to_i == 1 || !['filename'].to_s.empty? || !['mimetype'].to_s.empty? end return unless nested raise Error, 'Matroska contains attachments or cover art whose nested metadata cannot be verified' end |
.stream_signatures(streams) ⇒ Object
109 110 111 |
# File 'lib/metaclean/ffmpeg.rb', line 109 def stream_signatures(streams) streams.map { |stream| [stream['codec_type'], stream['codec_name']] } end |
.strip!(path) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/metaclean/ffmpeg.rb', line 36 def strip!(path) raise Error, 'ffmpeg not available' unless available? source = FileOps.regular_stat!(path) structure = probe_structure(path) reject_unverifiable_streams!(structure[:streams]) FileOps.with_private_workspace(path, 'ffmpeg') do |workspace| tmp = File.join(workspace, "output#{File.extname(path)}") args = [ 'ffmpeg', '-y', '-v', 'error', '-nostdin', '-i', file_url(path), '-map', '0', '-map_metadata', '-1', '-map_chapters', structure[:chapters].positive? ? '0' : '-1' ] args.concat(['-metadata:c', 'title=']) if structure[:chapters].positive? append_safe_stream_languages(args, structure[:streams]) args.concat(['-c', 'copy', file_url(tmp)]) _out, err, status = Metaclean.capture3(*args) raise Error, "ffmpeg failed: #{err.strip}" unless status.success? && File.exist?(tmp) FileOps.validate_output!(tmp) ensure_structure_preserved!(structure, probe_structure(tmp)) FileOps.ensure_same_file!(path, source) FileOps.(tmp, source) File.rename(tmp, path) true end end |
.version ⇒ Object
32 33 34 |
# File 'lib/metaclean/ffmpeg.rb', line 32 def version available? ? @version : nil end |