Skip to content

Instantly share code, notes, and snippets.

@Jessidhia
Created March 13, 2013 20:43
Show Gist options
  • Save Jessidhia/5155954 to your computer and use it in GitHub Desktop.
Save Jessidhia/5155954 to your computer and use it in GitHub Desktop.
Extracts album art embedded in audio files. If given a directory, will recursively extract the album art from the first file in each subdir that has embedded art.
#! /usr/bin/env ruby
require 'open3'
require 'json'
def ff_find_first(*binaries)
binaries.find do |cmd|
begin
pid = Process.spawn(cmd, '-version', [:in, :out, :err] => :close)
Process.wait2(pid)[1].success?
rescue Errno::ENOENT
false
end
end or raise SystemCallError.new("Couldn't find any of #{binaries.join ', '}", Errno::ENOENT::Errno)
end
class FFprobe
FFPROBE_BINARIES = %w{avprobe ffprobe}
def initialize
@@ffprobe ||= ff_find_first FFPROBE_BINARIES
end
def probe_files(*paths, &block)
each_path = paths.each
Enumerator.new do |y|
loop { y << probe_file(each_path.next, &block) }
end
end
def probe_file(path, &block)
stdout, stderr, status = Open3.capture3(@@ffprobe, *%w{-v 0 -of json -show_format -show_streams}, path)
return nil unless status.success?
parsed = JSON.parse(stdout, {symbolize_names: true})
if block
yield parsed
else
parsed
end
end
end
class FFconv
FFCONV_BINARIES = %w{avconv ffmpeg}
def initialize
@@ffconv ||= ff_find_first FFCONV_BINARIES
end
def extract_stream(file, stream_map, output)
# -v 23 is to disable non-error output (24 is warnings, 32 is regular output)
pid = Process.spawn(@@ffconv, *%w{-v 23 -y -i}, file, *build_map(stream_map),
*%w{-map_metadata -1 -c copy}, output, :in => :close)
Process.wait2(pid)[1].success?
end
private
def build_map(stream_map)
maps = []
stream_map.each do |arg|
maps << '-map'
maps << arg
end
maps
end
end
$probe = FFprobe.new
$conv = FFconv.new
def process_file(path, outfile_base = "folder")
$probe.probe_file path do |probe|
# Only extract from files that have an audio stream
return unless probe[:streams].find {|stream| stream[:codec_type] == 'audio'}
# Only extract from files that have a single frame of video (timebase = 0)
stream = probe[:streams].find {|stream| stream[:codec_type] == 'video' && stream[:codec_time_base] == '0/1'}
return unless stream
outfile = File.dirname(path) + File::SEPARATOR + outfile_base
case stream[:codec_name]
when 'mjpeg'
outfile << ".jpg"
when 'png'
outfile << ".png"
else
raise Exception.new("Unknown album art codec #{stream[:codec_name]}")
end
puts "Extracting: '#{path}' -> '#{outfile}'"
$conv.extract_stream(path, ["0:#{stream[:index]}"], outfile)
end
end
def process_dir(path, outfile_base = "folder")
seen = false
Dir.foreach(path) do |entry|
next if entry == '.' or entry == '..'
entry = path + File::SEPARATOR + entry
if File.directory? entry
process_dir entry, outfile_base
elsif ! seen
seen = process_file entry, outfile_base
end
end
end
ARGV.each do |arg|
if File.directory? arg
process_dir arg
elsif File.exists? arg
process_file arg
else
raise Exception.new("#{arg} is not a file or directory")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment