class MediaFile::MediaFile
Attributes
base_dir[R]
disposition[R]
name[R]
source[R]
type[R]
Public Class Methods
new(full_path, base_dir: '.', force_album_artist: nil, verbose: false, debug: false)
click to toggle source
# File lib/mediafile/mediafile.rb, line 11 def initialize(full_path, base_dir: '.', force_album_artist: nil, verbose: false, debug: false) @base_dir = base_dir @destinations = Hash.new{ |k,v| k[v] = {} } @force_album_artist = force_album_artist @name = File.basename( full_path, File.extname( full_path ) ) @source = full_path @type = full_path[/(\w+)$/].downcase.to_sym @verbose = verbose @debug = debug @noop = false @cover = File.join( File.dirname(full_path), 'cover.jpg') @cover = nil unless File.exist?(@cover) @disposition = 'pending' end
Public Instance Methods
copy(dest: @base_dir, transcode_table: {})
click to toggle source
# File lib/mediafile/mediafile.rb, line 44 def copy(dest: @base_dir, transcode_table: {}) destination = out_path base_dir: dest, transcode_table: transcode_table debug "destination is '#{destination}'" if destination =~ /\/\.[a-zA-Z0-9]+$/ raise "This is the error you've been looking for!\n '#{@source}' => '#{destination}'" end temp_dest = tmp_path base_dir: dest, typ: transcode_table[@type] debug "temp dest is '#{temp_dest}'" lock { if File.exist?(destination) || File.exist?(temp_dest) @noop = true info("File already exists at destination: #{@source} => #{destination}") end } return if @noop unless check_transfer(temp_dest) error "Another file is already getting " + (transcode_table.has_key?(@type) ? 'transcoded' : 'copied') + "to the same destination path!" error "This file: #{@source} => #{temp_dest}" return end really_copy(temp_dest, destination, transcode_table) end
out_path(base_dir: @base_dir, transcode_table: {})
click to toggle source
# File lib/mediafile/mediafile.rb, line 36 def out_path(base_dir: @base_dir, transcode_table: {}) @destinations[base_dir][transcode_table] ||= File.join( base_dir, relative_path, new_file_name, ) << ".#{transcode_table[@type] || @type}" end
really_copy(temp_dest, destination, transcode_table)
click to toggle source
# File lib/mediafile/mediafile.rb, line 71 def really_copy(temp_dest, destination, transcode_table) debug("Create parent directories at '#{File.dirname destination}'.") FileUtils.mkdir_p File.dirname destination FileUtils.touch temp_dest if transcode_table.has_key?(@type) transcode(transcode_table, temp_dest) @disposition = 'transcoded' else FileUtils.cp(@source, temp_dest) @disposition = 'copied' end set_album_artist(temp_dest) set_comment_and_title(temp_dest) set_cover_art(temp_dest) FileUtils.mv temp_dest, destination ensure FileUtils.rm temp_dest if File.exist? temp_dest end
source_md5()
click to toggle source
# File lib/mediafile/mediafile.rb, line 32 def source_md5 @source_md5 ||= Digest::MD5.hexdigest( File.read(@source) ) end
to_s()
click to toggle source
# File lib/mediafile/mediafile.rb, line 90 def to_s "#{@source}" end
Private Instance Methods
clean_string(my_string)
click to toggle source
# File lib/mediafile/mediafile.rb, line 250 def clean_string(my_string) my_string ||= "" t = my_string.gsub( /\.+|\.+$/,"" ).gsub( /\/+|\\|\||\s+/, '_' ).gsub( /[,:;)\]\[('"@$^*<>?!=]/,"" ).gsub( /_?[&]_?/,"_and_" ).split('_').map{ |c| c.split('-').map{ |d| next if d[/_/] debug("capitalize: '#{d}'") d.capitalize }.join('-') }.join('_').gsub( /^[.]/, '' ).gsub( /_+/, '_' ) t == "" ? "UNKNOWN" : t debug("clean_string: '#{my_string} => '#{t}'") t end
get_cover_data()
click to toggle source
# File lib/mediafile/mediafile.rb, line 325 def get_cover_data info("Getting cover art from #{@source}.") # This is bad maybe @cover ? File.open(@cover, 'rb') { |c| c.read } : case @type when :m4a TagLib::MP4::File.open(@source) do |f| p = f.tag.item_map['covr'].to_cover_art_list.first p.data if p end when :flac TagLib::FLAC::File.open(@source) do |f| info("Geting cover art from #{@source}.") p = f.picture_list.find { |i| i.type == TagLib::FLAC::Picture::FrontCover } p.data if p end when :mp3 TagLib::MPEG::File.open(@source) do |f| tag = f.id3v2_tag if tag p = tag.frame_list('APIC').first p.picture if p end end else error "Unsupported file type '#{@type}'. Not adding cover art from '#{@cover}' for '#{@source}'." false end end
has_cover_art?(file)
click to toggle source
# File lib/mediafile/mediafile.rb, line 289 def has_cover_art?(file) typ = file[/(\w+)$/].downcase.to_sym debug("Checking if #{file} has clover art. (#{typ})") case typ when :m4a TagLib::MP4::File.open(file) do |f| return true if f.tag.item_map['covr'].to_cover_art_list.find do |p| p.format == TagLib::MP4::CoverArt::JPEG end end when :flac debug("It does.") TagLib::FLAC::File.open(file) do |f| return true if f.picture_list.find do |p| p.type == TagLib::FLAC::Picture::FrontCover end end when :mp3 TagLib::MPEG::File.open(file) do |f| tag = f.id3v2_tag # Don't overwrite an existing album cover. debug("Checking if the target mp3 file already has a cover.") return true if tag && tag.frame_list('APIC').find do |p| p.type == TagLib::ID3v2::AttachedPictureFrame::FrontCover end end end false end
new_file_name()
click to toggle source
# File lib/mediafile/mediafile.rb, line 233 def new_file_name # this doesn't include the extension. @newname ||= ( read_tags case when (@disc_number && (@track > 0) && @title) && !(@disc_total && @disc_total == 1) "%1d_%02d-" % [@disc_number, @track] + clean_string(@title) when (@track > 0 && @title) "%02d-" % @track + clean_string(@title) when @title && @title != "" clean_string(@title) else clean_string(@name) end ) end
relative_path()
click to toggle source
directory names cannot end with a ‘.’ it breaks windows (really!)
# File lib/mediafile/mediafile.rb, line 221 def relative_path @relpath ||= ( read_tags File.join( [@album_artist, @album].map { |word| debug word clean_string word } ) ) end
set_album_artist(file)
click to toggle source
# File lib/mediafile/mediafile.rb, line 398 def set_album_artist(file) return unless @force_album_artist typ = file[/(\w+)$/].downcase.to_sym case typ when :m4a TagLib::MP4::File.open(file) do |f| f.tag.item_map.insert("aART", TagLib::MP4::Item.from_string_list([@force_album_artist])) f.save end when :flac TagLib::FLAC::File.open(file) do |f| tag = f.xiph_comment ['ALBUMARTIST', 'ALBUM ARTIST', 'ALBUM_ARTIST'].select do |t| tag.add_field(t, @force_album_artist) end f.save end when :mp3 TagLib::MPEG::File.open(file) do |f| tag = f.id3v2_tag if tag frame = TagLib::ID3v2::TextIdentificationFrame.new("TPE2", TagLib::String::UTF8) frame.text = @force_album_artist tag.add_frame(frame) f.save else error("##########\nNo tag returned for #{@name}: #{@source}\n#############\n\n") end end end end
set_comment_and_title(file)
click to toggle source
# File lib/mediafile/mediafile.rb, line 431 def set_comment_and_title(file) debug "file is #{file}" typ = file[/(\w+)$/].downcase.to_sym klass,method = (typ == :mp3) ? [TagLib::MPEG::File, :id3v2_tag] : [TagLib::FileRef, :tag] klass.send(:open, file) do |f| tag = if (typ == :mp3) f.send(method, true) else f.send(method) end return false unless tag tag.comment = "#{@comment}" tag.title = (@title || @name.tr('_',' ')) unless tag.title && tag.title != "" if (typ == :mp3) debug("saving tags as id3v2") f.save(TagLib::MPEG::File::ID3v2) else debug("NOT saving tages as id3v2") f.save end end end
set_cover_art(file)
click to toggle source
# File lib/mediafile/mediafile.rb, line 319 def set_cover_art(file) debug("Checking for cover to apply to #{file}") return if has_cover_art?(file) write_cover_data(file, get_cover_data) end
set_decoder()
click to toggle source
# File lib/mediafile/mediafile.rb, line 110 def set_decoder() case @type when :flac %W{flac -c -s -d #{@source}} when :mp3 #%W{lame --decode #{@source} -} %W{sox #{@source} -t wav -} when :m4a %W{ffmpeg -i #{@source} -f wav -} when :wav %W{cat #{@source}} else raise "Unknown type '#{@type}'. Cannot set decoder" end end
set_encoder(to,destination)
click to toggle source
# File lib/mediafile/mediafile.rb, line 126 def set_encoder(to,destination) @comment += "\nTranscoded by MediaFile on #{Time.now}" encoder = case to when :flac %W{flac -7 -V -s -o #{destination}} + (@artist ? ["-T", "artist=#{@artist}"] : [] ) + (@title ? ["-T", "title=#{@title}"] : [] ) + (@album ? ["-T", "album=#{@album}"] : [] ) + (@track > 0 ? ["-T", "tracknumber=#{@track}"] : [] ) + (@year ? ["-T", "date=#{@year}"] : [] ) + (@genre ? ["-T", "genre=#{@genre}"] : [] ) + ["-T", "comment=#{@comment}"] + (@album_artist ? ["-T", "albumartist=#{@album_artist}"] : [] ) + (@disc_number ? ["-T", "discnumber=#{@disc_number}"] : [] ) + ["-"] #raise "Please don't transcode to flac. It is broken right now" when :mp3 %W{lame --quiet --preset extreme -h --add-id3v2 --id3v2-only} + (@title ? ["--tt", @title] : [] ) + (@artist ? ["--ta", @artist]: [] ) + (@album ? ["--tl", @album] : [] ) + (@track > 0 ? ["--tn", @track.to_s]: [] ) + (@year ? ["--ty", @year.to_s ] : [] ) + (@genre ? ["--tg", @genre ]: [] ) + ["--tc", @comment] + (@album_artist ? ["--tv", "TPE2=#{@album_artist}"] : [] ) + (@disc_number ? ["--tv", "TPOS=#{@disc_number}"] : [] ) + ["-", destination] when :wav %W{dd of=#{destination}} else raise "Unknown target '#{to}'. Cannot set encoder." end debug "Encoder set to '#{encoder}'" encoder end
tmp_file_name()
click to toggle source
# File lib/mediafile/mediafile.rb, line 276 def tmp_file_name "." + new_file_name end
tmp_path(base_dir: @base_dir, typ: nil)
click to toggle source
# File lib/mediafile/mediafile.rb, line 280 def tmp_path(base_dir: @base_dir, typ: nil) typ ||= @type File.join( base_dir, relative_path, tmp_file_name, ) << ".#{typ}" end
transcode(trans , destination)
click to toggle source
# File lib/mediafile/mediafile.rb, line 163 def transcode(trans , destination) to = trans[@type] if to == @type error "Attempting to transcode to the same format #{@source} from #{@type} to #{to}" end FileUtils.mkdir_p File.dirname destination decoder = set_decoder encoder = set_encoder(to, destination) info "Decoder: '#{decoder.join(' ')}'\nEncoder: '#{encoder.join(' ')}'" pipes = Hash[[:encoder,:decoder].zip IO.pipe] #readable, writeable = IO.pipe pids = { spawn(*decoder, :out=>pipes[:decoder], :err=>"/tmp/decoder.err") => :decoder, spawn(*encoder, :in =>pipes[:encoder], :err=>"/tmp/encoder.err") => :encoder, } tpids = pids.keys err = [] begin Timeout::timeout(60 * ( File.size(@source) / 1024 / 1024 /2 ) ) do while tpids.any? do sleep 0.2 tpids.delete_if do |pid| ret = false _p, stat = Process.wait2 pid, Process::WNOHANG if stat pipes[pids[pid]].close unless pipes[pids[pid]].closed? ret = true end if stat and stat.exitstatus and stat.exitstatus != 0 err << [ pids[pid], stat ] end ret end end end rescue Timeout::Error error "Timeout exceeded!\n" << tpids.map { |p| Process.kill 15, p Process.kill 9, p "#{p} #{Process.wait2( p )[1]}" }.join(", ") FileUtils.rm [destination] raise end if err.any? error "###\nError transcoding #{@source}: #{err.map{ |it,stat| "#{it} EOT:#{stat.exitstatus} #{stat}" }.join(" and ") }\n###\n" raise end end
write_cover_data(file, cover_art)
click to toggle source
# File lib/mediafile/mediafile.rb, line 355 def write_cover_data(file, cover_art) return unless cover_art typ = file[/(\w+)$/].downcase.to_sym case typ when :m4a TagLib::MP4::File.open(file) do c = TagLib::MP4::CoverArt.new(TagLib::MP4::CoverArt::JPEG, cover_art) item = TagLib::MP4::Item.from_cover_art_list([c]) file.tag.item_map.insert('covr', item) file.save end when :flac TagLib::FLAC::File.open(file) do |f| pic = TagLib::FLAC::Picture.new pic.type = TagLib::FLAC::Picture::FrontCover pic.mime_type = 'image/jpeg' pic.description = 'Cover' pic.width = 90 pic.height = 90 pic.data = cover_art info("Adding cover art tag to #{file}.") f.add_picture(pic) f.save end when :mp3 TagLib::MPEG::File.open(file) do |f| tag = f.id3v2_tag if tag apic = TagLib::ID3v2::AttachedPictureFrame.new apic.mime_type = 'image/jpeg' apic.description = 'Cover' apic.type = TagLib::ID3v2::AttachedPictureFrame::FrontCover apic.picture = cover_art tag.add_frame(apic) f.save end end else error "Unsupported file type '#{typ}'. Not adding cover art from '#{@cover}'." false end end