Created
March 13, 2013 20:43
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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