class MiniExiftool
Simple OO access to the ExifTool command-line application.
Constants
- VERSION
Attributes
Public Class Methods
Source
# File lib/mini_exiftool.rb, line 314 def self.command @@cmd end
Returns the command name of the called ExifTool application.
Source
# File lib/mini_exiftool.rb, line 319 def self.command= cmd @@cmd = cmd end
Setting the command name of the called ExifTool application.
Source
# File lib/mini_exiftool.rb, line 63 def self.encoding_opt enc_type (enc_type.to_s + '_encoding').to_sym end
Source
# File lib/mini_exiftool.rb, line 353 def self.exiftool_version Open3.popen3 "#{MiniExiftool.command} -ver" do |_inp, out, _err, _thr| out.read.chomp! end rescue SystemCallError raise MiniExiftool::Error.new("Command '#{MiniExiftool.command}' not found") end
Returns the version of the ExifTool command-line application.
Source
# File lib/mini_exiftool.rb, line 293 def self.from_hash hash, opts={} instance = MiniExiftool.new nil, opts instance.initialize_from_hash hash instance end
Create a MiniExiftool
instance from a hash. Default value conversions will be applied if neccesary.
Source
# File lib/mini_exiftool.rb, line 301 def self.from_json json, opts={} instance = MiniExiftool.new nil, opts instance.initialize_from_json json instance end
Create a MiniExiftool
instance from JSON data. Default value conversions will be applied if neccesary.
Source
# File lib/mini_exiftool.rb, line 309 def self.from_yaml yaml, opts={} MiniExiftool.from_hash YAML.unsafe_load(yaml), opts end
Create a MiniExiftool
instance from YAML data created with MiniExiftool#to_yaml
Source
# File lib/mini_exiftool.rb, line 517 def self.load_or_create_pstore FileUtils.mkdir_p(pstore_dir) pstore_filename = File.join(pstore_dir, 'exiftool_tags_' << exiftool_version.gsub('.', '_') << '.pstore') @@pstore = PStore.new(pstore_filename, _threadsafe = true) if !File.exist?(pstore_filename) || File.size(pstore_filename) == 0 $stderr.puts 'Generating cache file for ExifTool tag names. This takes a few seconds but is only needed once...' @@pstore.transaction do |ps| ps[:all_tags] = all_tags = determine_tags('list') ps[:writable_tags] = determine_tags('listw') map = {} all_tags.each { |k| map[unify(k)] = k } ps[:all_tags_map] = map end $stderr.puts 'Cache file generated.' end end
Source
# File lib/mini_exiftool.rb, line 107 def initialize filename_or_io=nil, opts={} @opts = @@opts.merge opts if @opts[:convert_encoding] warn 'Option :convert_encoding is not longer supported!' warn 'Please use the String#encod* methods.' end @filename = nil @io = nil @values = TagHash.new @changed_values = TagHash.new @errors = TagHash.new load filename_or_io unless filename_or_io.nil? end
filename_or_io
The kind of the parameter is determined via duck typing: if the argument responds to to_str
it is interpreted as filename, if it responds to read
it is interpreted es IO instance.
ATTENTION: If using an IO instance writing of meta data is not supported!
opts
support at the moment
-
:numerical
for numerical values, default isfalse
-
:composite
for including composite tags while loading, default istrue
-
:ignore_minor_errors
ignore minor errors (See -m-option of the exiftool command-line application, default isfalse
) -
:coord_format
set format for GPS coordinates (See -c-option of the exiftool command-line application, default isnil
that means exiftool standard) -
:fast
useful when reading JPEGs over a slow network connection (See -fast-option of the exiftool command-line application, default isfalse
) -
:fast2
useful when reading JPEGs over a slow network connection (See -fast2-option of the exiftool command-line application, default isfalse
) -
:replace_invalid_chars
replace string for invalid UTF-8 characters orfalse
if no replacing should be done, default isfalse
-
:timestamps
generating DateTime objects instead of Time objects if set toDateTime
, default isTime
ATTENTION: Time objects are created using
Time.local
therefore they use your local timezone, DateTime objects instead are created without timezone! -
:exif_encoding
,:iptc_encoding
,:xmp_encoding
,:png_encoding
,:id3_encoding
,:pdf_encoding
,:photoshop_encoding
,:quicktime_encoding
,:aiff_encoding
,:mie_encoding
,:vorbis_encoding
to set this specific encoding (see -charset option of the exiftool command-line application, default isnil
: no encoding specified)
Source
# File lib/mini_exiftool.rb, line 324 def self.opts @@opts end
Returns the options hash.
Source
# File lib/mini_exiftool.rb, line 45 def self.opts_accessor *attrs attrs.each do |a| define_method a do @opts[a] end define_method "#{a}=" do |val| @opts[a] = val end end end
Source
# File lib/mini_exiftool.rb, line 345 def self.original_tag tag unless defined? @@all_tags_map @@all_tags_map = pstore_get :all_tags_map end @@all_tags_map[tag] end
Returns the original ExifTool name of the given tag
Source
# File lib/mini_exiftool.rb, line 367 def self.pstore_dir unless defined? @@pstore_dir @@pstore_dir = if env = ENV['MINI_EXIFTOOL_PSTORE_DIR'] env elsif defined?(Gem.cache_home) && File.writable?(Gem.cache_home) File.join(Gem.cache_home, 'mini_exiftool') else # This fallback will hopefully work on *NIX and Windows systems cache_dir = ENV['USERPROFILE'] || Dir.tmpdir File.join(cache_dir, 'mini_exiftool') end end @@pstore_dir end
Source
# File lib/mini_exiftool.rb, line 383 def self.pstore_dir= dir @@pstore_dir = dir end
Source
# File lib/mini_exiftool.rb, line 508 def self.pstore_get attribute load_or_create_pstore unless defined? @@pstore result = nil @@pstore.transaction(true) do |ps| result = ps[attribute] end result end
Source
# File lib/mini_exiftool.rb, line 361 def self.unify tag tag.to_s.gsub(/[-_]/,'').downcase end
Public Instance Methods
Source
# File lib/mini_exiftool.rb, line 173 def [] tag @changed_values[tag] || @values[tag] end
Returns the value of a tag.
Source
# File lib/mini_exiftool.rb, line 178 def []= tag, val @changed_values[tag] = val end
Set the value of a tag.
Source
# File lib/mini_exiftool.rb, line 184 def changed? tag=false if tag @changed_values.include? tag else !@changed_values.empty? end end
Returns true if any tag value is changed or if the value of a given tag is changed.
Source
# File lib/mini_exiftool.rb, line 135 def load filename_or_io if filename_or_io.respond_to?(:to_str) || filename_or_io.kind_of?(Pathname) # String-like unless filename_or_io && File.exist?(filename_or_io) raise MiniExiftool::Error.new("File '#{filename_or_io}' does not exist.") end if File.directory?(filename_or_io) raise MiniExiftool::Error.new("'#{filename_or_io}' is a directory.") end @filename = filename_or_io.to_s elsif filename_or_io.respond_to? :read # IO-like @io = filename_or_io @filename = '-' else raise MiniExiftool::Error.new("Could not open filename_or_io.") end @values.clear @changed_values.clear params = '-j ' params << (@opts[:numerical] ? '-n ' : '') params << (@opts[:composite] ? '' : '-e ') params << (@opts[:coord_format] ? "-c #{escape(@opts[:coord_format])}" : '') params << (@opts[:fast] ? '-fast ' : '') params << (@opts[:fast2] ? '-fast2 ' : '') params << generate_encoding_params if run(cmd_gen(params, @filename)) parse_output else raise MiniExiftool::Error.new(@error_text) end self end
Load the tags of filename or io.
Source
# File lib/mini_exiftool.rb, line 168 def reload load @filename end
Reload the tags of an already read file.
Source
# File lib/mini_exiftool.rb, line 193 def revert tag=nil if tag val = @changed_values.delete(tag) res = val != nil else res = @changed_values.size > 0 @changed_values.clear end res end
Revert all changes or the change of a given tag.
Source
# File lib/mini_exiftool.rb, line 215 def save if @io raise MiniExiftool::Error.new('No writing support when using an IO.') end return false if @changed_values.empty? @errors.clear temp_file = Tempfile.new('mini_exiftool') temp_file.close temp_filename = temp_file.path FileUtils.cp filename.encode(@@fs_enc), temp_filename all_ok = true @changed_values.each do |tag, val| original_tag = MiniExiftool.original_tag(tag) arr_val = val.kind_of?(Array) ? val : [val] arr_val.map! {|e| convert_before_save(e)} params = '-q -P -overwrite_original ' params << (arr_val.detect {|x| x.kind_of?(Numeric)} ? '-n ' : '') params << (@opts[:ignore_minor_errors] ? '-m ' : '') params << generate_encoding_params arr_val.each do |v| params << %Q(-#{original_tag}=#{escape(v)} ) end result = run(cmd_gen(params, temp_filename)) unless result all_ok = false @errors[tag] = @error_text.gsub(/Nothing to do.\n\z/, '').chomp end end if all_ok FileUtils.cp temp_filename, filename.encode(@@fs_enc) reload end temp_file.delete all_ok end
Save the changes to the file.
Source
# File lib/mini_exiftool.rb, line 251 def save! unless save err = [] @errors.each do |key, value| err << "(#{key}) #{value}" end raise MiniExiftool::Error.new("MiniExiftool couldn't save. The following errors occurred: #{err.empty? ? "None" : err.join(", ")}") end end
Source
# File lib/mini_exiftool.rb, line 277 def to_hash result = {} @values.each do |k,v| result[MiniExiftool.original_tag(k)] = v end result end
Returns a hash of the original loaded values of the MiniExiftool
instance.
Source
# File lib/mini_exiftool.rb, line 287 def to_yaml to_hash.to_yaml end
Returns a YAML representation of the original loaded values of the MiniExiftool
instance.
Private Instance Methods
Source
# File lib/mini_exiftool.rb, line 447 def adapt_encoding @output.force_encoding('UTF-8') if @opts[:replace_invalid_chars] && !@output.valid_encoding? @output.encode!('UTF-16le', invalid: :replace, replace: @opts[:replace_invalid_chars]).encode!('UTF-8') end end
Source
# File lib/mini_exiftool.rb, line 394 def cmd_gen arg_str='', filename [+@@cmd, arg_str.encode('UTF-8'), escape(filename.encode(@@fs_enc))].map {|s| s.force_encoding('UTF-8')}.join(' ') end
Source
# File lib/mini_exiftool.rb, line 454 def convert_after_load tag, value return value unless value.kind_of?(String) return value unless value.valid_encoding? case value when /^\d{4}:\d\d:\d\d \d\d:\d\d:\d\d/ s = value.sub(/^(\d+):(\d+):/, '\1-\2-') begin if @opts[:timestamps] == Time value = Time.parse(s) elsif @opts[:timestamps] == DateTime value = DateTime.parse(s) else raise MiniExiftool::Error.new("Value #{@opts[:timestamps]} not allowed for option timestamps.") end rescue ArgumentError, RangeError value = false end when /^\+\d+\.\d+$/ value = value.to_f when /^0\d+$/ # no conversion => String when /^-?\d+$/ value = value.to_i when %r(^(\d+)/(\d+)$) value = Rational($1.to_i, $2.to_i) rescue value when /^[\d ]+$/ # nothing => String end value end
Source
# File lib/mini_exiftool.rb, line 420 def convert_before_save val case val when Time val = val.strftime('%Y:%m:%d %H:%M:%S') end val end
Source
# File lib/mini_exiftool.rb, line 546 def escape val '"' << val.to_s.gsub(/([\\"`])/, "\\\\\\1") << '"' end
Source
# File lib/mini_exiftool.rb, line 555 def generate_encoding_params params = '' @@encoding_types.each do |enc_type| if enc_val = @opts[MiniExiftool.encoding_opt(enc_type)] params << "-charset #{enc_type}=#{enc_val} " end end params end
Source
# File lib/mini_exiftool.rb, line 428 def method_missing symbol, *args tag_name = symbol.id2name if tag_name =~ /^(.+)=$/ self[$1] = args.first else self[tag_name] end end
Source
# File lib/mini_exiftool.rb, line 442 def parse_output adapt_encoding set_values JSON.parse(@output).first end
Source
# File lib/mini_exiftool.rb, line 437 def respond_to_missing? symbol, *args tag_name = MiniExiftool.unify(symbol.id2name) !!(tag_name =~ /=$/) || @values.key?(tag_name) || super end
Source
# File lib/mini_exiftool.rb, line 398 def run cmd if $DEBUG $stderr.puts cmd end status = Open3.popen3(cmd) do |inp, out, err, thr| if @io begin IO.copy_stream @io, inp rescue Errno::EPIPE # Output closed, no problem rescue ::IOError => e raise MiniExiftool::Error.new("IO is not readable.") end inp.close end @output = out.read @error_text = err.read thr.value.exitstatus end status == 0 end
Source
# File lib/mini_exiftool.rb, line 502 def set_opts_by_heuristic @opts[:composite] = tags.include?('ImageSize') @opts[:numerical] = self.file_size.kind_of?(Integer) @opts[:timestamps] = self.FileModifyDate.kind_of?(DateTime) ? DateTime : Time end
Source
# File lib/mini_exiftool.rb, line 485 def set_values hash hash.each_pair do |tag,val| @values[tag] = convert_after_load(tag, val) end # Remove filename specific tags use attr_reader # MiniExiftool#filename instead # Cause: value of tag filename and attribute # filename have different content, the latter # holds the filename with full path (like the # sourcefile tag) and the former the basename # of the filename also there is no official # "original tag name" for sourcefile %w(directory filename sourcefile).each do |t| @values.delete(t) end end