Code Monkey home page Code Monkey logo

multi_exiftool's Introduction

MultiExiftool test workflow

Description

This library is a wrapper for the ExifTool command-line application (https://exiftool.org) written by Phil Harvey. It is designed for dealing with multiple files at once by creating commands to call exiftool with various arguments, call it and parsing the results.

Examples

Reading

require 'multi_exiftool'

# Object oriented approach
reader = MultiExiftool::Reader.new
reader.filenames = Dir['*.jpg']
results = reader.read
unless reader.errors.empty?
  $stderr.puts reader.errors
end
results.each do |values|
  puts "#{values.file_name}: #{values.comment}"
end

# Functional approach
results, errors = MultiExiftool.read(Dir['*.jpg'])
unless errors.empty?
  $stderr.puts reader.errors
end
results.each do |values|
  puts "#{values.file_name}: #{values.comment}"
end

Writing

require 'multi_exiftool'

# Object oriented approach
writer = MultiExiftool::Writer.new
writer.filenames = Dir['*.jpg']
writer.values = {creator: 'Jan Friedrich', copyright: 'Public Domain'}
if writer.write
  puts 'ok'
else
  puts writer.errors
end

# Functional approach
errors = MultiExiftool.write(Dir['*.jpg'],  {creator: 'Jan Friedrich', copyright: 'Public Domain'})
if errors.empty?
  puts 'ok'
else
  puts writer.errors
end

If it is necessary to write different values to multiple files there is batch processing

require 'multi_exiftool'

# Object oriented approach

batch = MultiExiftool::Batch.new
Dir['*.jpg'].each_with_index do |filename, i|
  values = {creator: 'Jan Friedrich', copyright: 'Public Domain', comment: "This is file number #{i+1}."}
  batch.write filename, values
end
if batch.execute
  puts 'ok'
else
  puts batch.errors
end

# Functional approach

errors = MultiExiftool.batch do
  Dir['*.jpg'].each_with_index do |filename, i|
    values = {creator: 'Jan Friedrich', copyright: 'Public Domain', comment: "This is file number #{i+1}."}
    write filename, values
  end
end
if errors.empty?
  puts 'ok'
else
  puts errors
end

# or alternative with block parameter as yielded Batch instance

errors = MultiExiftool.batch do |batch|
  Dir['*.jpg'].each_with_index do |filename, i|
    values = {creator: 'Jan Friedrich', copyright: 'Public Domain', comment: "This is file number #{i+1}."}
    batch.write filename, values
  end
end
if errors.empty?
  puts 'ok'
else
  puts errors
end

Deleting

# Delete ALL values
errors = MultiExiftool.delete_values(Dir['*.jpg'])
if errors.empty?
  puts 'ok'
else
  puts writer.errors
end

# Delete values for tags Author and Title
errors = MultiExiftool.delete_values(Dir['*.jpg'], tags: %w(author title))
if errors.empty?
  puts 'ok'
else
  puts writer.errors
end

Further Examples

See the examples in the examples directory.

Automatic conversion of values

By default values are converted to useful instances of Ruby classes. The following conversions are implemented at the moment:

  • Timestamps => Time (with local time zone of no one given)
  • values of form "n/m" => Rational except PartOfSet and Track

The conversion is done in the method Values#convert. So you can change it's behaviour as following examples show.

Example 1

module MyConversion
  def convert tag, val
    val # no conversion at all
  end
end

MultiExiftool::Values.prepend MyConversion

Example 2

module MultiExiftool
  module MyConversion
    def convert tag, val
      converted_val = super
      case converted_val
      when Time
        converted_val.utc # convert Time objects to utc
      when Rational
        val # no conversion
      else
        converted_val # use default conversion
      end
    end
  end

  Values.prepend MyConversion
end

Example 3

m = Module.new do
  def convert tag, val
    if val =~ MultiExiftool::Values::REGEXP_TIMESTAMP
      val # no conversion
    else
      super # use default conversion
    end
  end
end
MultiExiftool::Values.prepend m

The method Values#convert is called each time a value is fetched.

Requirements

  • Ruby 1.9.1 or higher
  • An installation of the ExifTool command-line application (version 11.10 or higher). Instructions for installation you can find under https://exiftool.org/install.html .

Installation

First you need ExifTool (see under Requirements above). Then you can simply install the gem with

gem install multi_exiftool

or in your Gemfile

gem 'multi_exiftool'

Contribution

The code is also hosted in a git repository at http://github.com/janfri/multi_exiftool or https://bitbucket.org/janfri/multi_exiftool feel free to contribute!

Versioning

MultiExiftool follows Semantic Versioning, both SemVer and SemVerTag.

Author

Jan Friedrich [email protected]

MIT License

See file LICENSE for details.

multi_exiftool's People

Contributors

andyjbas avatar ccoenen avatar janfri avatar sztheory avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

multi_exiftool's Issues

No errors in non-empty error message

After reading a directory, multi_exiftool's error contains "17214 image files read", but no actual error.

ARGV.each do |dir|
  r = ''
  r = '**' if @options[:recursive] == true
  allfiles = Dir.glob(File.join(File.expand_path(dir), r, '*.orf'))
  next if allfiles.empty?

  results, errors = MultiExiftool.read(allfiles)
  
  unless errors.empty?
      p errors
      raise('Reading exifs failed')
  end
end

In irb, I went over each file in that directory and got no error:

allfiles = Dir.glob(File.join(File.expand_path(dir), r, '*.orf'))
allfiles.each do |f|
  results, errors = MultiExiftool.read([f])
   
  unless errors.empty?
    p errors
    raise("Reading #{f} failed")
  end
end

This error occurs only in one directory, in the other eight I worked on multi_exiftool behaves as expected.

[QUESTION] Is it thread safe?

Hello!

It is designed for dealing with multiple files at once by creating commands to call exiftool with various arguments, call it and parsing the results.

Here is nothing about thread safe. The question is: can I process N files in parallel mode with this gem in the Sidekiq/Xxx with concurrency > 1?

Thanks,
Aleksei.

ArgumentError (mon out of range)

I get this error any time I try to call .to_json, .to_h, .to_hash on a blob... I can't tell what specificall ymight be causing it but maybe the special japanese characters. (I'm using heroku dynos)

#<MultiExiftool::Values:0x0000000004b88978 @values={"sourcefile"=>"/tmp/output20180817-4-1fcwtvo", "exiftoolversion"=>11.1, "filename"=>"output20180817-4-1fcwtvo", "directory"=>"/tmp", "filesize"=>"1836 kB", "filemodifydate"=>"2018:08:17 17:14:35+00:00", "fileaccessdate"=>"2018:08:17 17:14:35+00:00", "fileinodechangedate"=>"2018:08:17 17:14:35+00:00", "filepermissions"=>"rw-------", "filetype"=>"JPEG", "filetypeextension"=>"jpg", "mimetype"=>"image/jpeg", "jfifversion"=>1.02, "exifbyteorder"=>"Little-endian (Intel, II)", "imagedescription"=>"OLYMPUS DIGITAL CAMERA         ", "make"=>"OLYMPUS IMAGING CORP.", "model"=>"uD600,S600", "orientation"=>"Horizontal (normal)", "xresolution"=>314, "yresolution"=>314, "resolutionunit"=>"inches", "software"=>"Adobe Photoshop 7.0", "modifydate"=>"2007:08:10 15:19:31", "ycbcrpositioning"=>"Co-sited", "printimversion"=>"0300", "exposuretime"=>"1/60", "fnumber"=>3.1, "exposureprogram"=>"Program AE", "iso"=>64, "exifversion"=>"0221", "datetimeoriginal"=>"0000:00:00 00:00:00", "createdate"=>"0000:00:00 00:00:00", "componentsconfiguration"=>"Y, Cb, Cr, -", "exposurecompensation"=>0, "maxaperturevalue"=>3.1, "meteringmode"=>"Multi-segment", "lightsource"=>"Unknown", "flash"=>"Off, Did not fire", "focallength"=>"5.8 mm", "usercomment"=>"", "flashpixversion"=>"0100", "colorspace"=>"Uncalibrated", "exifimagewidth"=>1500, "exifimageheight"=>1125, "filesource"=>"Digital Camera", "customrendered"=>"Normal", "exposuremode"=>"Auto", "whitebalance"=>"Auto", "digitalzoomratio"=>1, "scenecapturetype"=>"Standard", "gaincontrol"=>"None", "contrast"=>"Normal", "saturation"=>"Normal", "sharpness"=>"Normal", "compression"=>"JPEG (old-style)", "thumbnailoffset"=>1508, "thumbnaillength"=>4375, "currentiptcdigest"=>"58c5fce11cb7b6677ed96b6a4583803a", "applicationrecordversion"=>2, "captionabstract"=>"OLYMPUS DIGITAL CAMERA         ", "iptcdigest"=>"58c5fce11cb7b6677ed96b6a4583803a", "displayedunitsx"=>"inches", "displayedunitsy"=>"inches", "printstyle"=>"Centered", "printposition"=>"0 0", "printscale"=>1, "globalangle"=>30, "globalaltitude"=>30, "copyrightflag"=>false, "urllist"=>[], "slicesgroupname"=>"Thistlegorm_train_parts_large", "numslices"=>1, "photoshopthumbnail"=>"(Binary data 4375 bytes, use -b option to extract)", "hasrealmergeddata"=>"Yes", "writername"=>"Adobe Photoshop", "readername"=>"Adobe Photoshop 7.0", "photoshopquality"=>12, "photoshopformat"=>"Progressive", "progressivescans"=>"5 Scans", "xmptoolkit"=>"XMP toolkit 2.8.2-33, framework 1.5", "about"=>"uuid:d63ffc88-4769-11dc-b660-bc8bd0216326", "creatortool"=>"Adobe Photoshop Elements for Windows, version 2.0", "documentid"=>"adobe:docid:photoshop:66e6f959-f580-11db-bff7-cd62ba455141", "description"=>"OLYMPUS DIGITAL CAMERA         ", "profilecmmtype"=>"Apple Computer Inc.", "profileversion"=>"2.2.0", "profileclass"=>"Input Device Profile", "colorspacedata"=>"RGB ", "profileconnectionspace"=>"XYZ ", "profiledatetime"=>"2003:07:01 00:00:00", "profilefilesignature"=>"acsp", "primaryplatform"=>"Apple Computer Inc.", "cmmflags"=>"Not Embedded, Independent", "devicemanufacturer"=>"Apple Computer Inc.", "devicemodel"=>"", "deviceattributes"=>"Reflective, Glossy, Positive, Color", "renderingintent"=>"Perceptual", "connectionspaceilluminant"=>"0.9642 1 0.82491", "profilecreator"=>"Apple Computer Inc.", "profileid"=>0, "redmatrixcolumn"=>"0.45427 0.24263 0.01482", "greenmatrixcolumn"=>"0.35332 0.67441 0.09042", "bluematrixcolumn"=>"0.15662 0.08336 0.71953", "mediawhitepoint"=>"0.95047 1 1.0891", "chromaticadaptation"=>"1.04788 0.02292 -0.0502 0.02957 0.99049 -0.01706 -0.00923 0.01508 0.75165", "redtrc"=>"(Binary data 14 bytes, use -b option to extract)", "greentrc"=>"(Binary data 14 bytes, use -b option to extract)", "bluetrc"=>"(Binary data 14 bytes, use -b option to extract)", "profiledescription"=>"Camera RGB Profile", "profilecopyright"=>"Copyright 2003 Apple Computer Inc., all rights reserved.", "profiledescriptionml"=>"Camera RGB Profile", "profiledescriptionmleses"=>"Perfil RGB para Cámara", "profiledescriptionmldadk"=>"RGB-beskrivelse til Kamera", "profiledescriptionmldede"=>"RGB-Profil für Kameras", "profiledescriptionmlfifi"=>"Kameran RGB-profiili", "profiledescriptionmlfrfu"=>"Profil RVB de l’appareil-photo", "profiledescriptionmlitit"=>"Profilo RGB Fotocamera", "profiledescriptionmlnlnl"=>"RGB-profiel Camera", "profiledescriptionmlnono"=>"RGB-kameraprofil", "profiledescriptionmlptbr"=>"Perfil RGB de Câmera", "profiledescriptionmlsvse"=>"RGB-profil för Kamera", "profiledescriptionmljajp"=>"カメラ RGB プロファイル", "profiledescriptionmlkokr"=>"카메라 RGB 프로파일", "profiledescriptionmlzhtw"=>"數位相機 RGB 色彩描述", "profiledescriptionmlzhcn"=>"相机 RGB 描述文件", "dctencodeversion"=>100, "app14flags0"=>"[14]", "app14flags1"=>"(none)", "colortransform"=>"YCbCr", "imagewidth"=>1500, "imageheight"=>1125, "encodingprocess"=>"Progressive DCT, Huffman coding", "bitspersample"=>8, "colorcomponents"=>3, "ycbcrsubsampling"=>"YCbCr4:4:4 (1 1)", "aperture"=>3.1, "imagesize"=>"1500x1125", "megapixels"=>1.7, "shutterspeed"=>"1/60", "thumbnailimage"=>"(Binary data 4375 bytes, use -b option to extract)", "focallength35efl"=>"5.8 mm", "lightvalue"=>9.8}>

error:

irb(main):075:0> results[40].to_h
Traceback (most recent call last):
        1: from (irb):75
ArgumentError (mon out of range)

Stop on MultiExiftool.Executable.parse_errors

I wrote a patch, then it works, but I do not known why.

module MultiExiftool
# Mixin for Reader and Writer.
module Executable
def parse_errors
if @stderr.stat.size.zero?
@errors = []
else
@errors = @stderr.read.lines(chomp: true).select {|l| l =~ /^(Error|Warning):/}
end
end
end
end

Support for `-config`?

Hello!

I have an .ExifTool_config file that defines some custom tags. To use it, the exiftool CLI has the -config option, which must come before all other arguments on the command line. However, when I try to do this with MultiExiftool:

      writer = MultiExiftool::Writer.new
      writer.options = { 'config' => '/path/to/.ExifTool_config', 'P' => true, 'E' => true }
      writer.filenames = file_path
      writer.overwrite_original = true
      writer.values = values

The -config option is always preceded by some -charset options:

=> ["-charset",                                                                                                                        
  "FileName=utf8",                                                                                                                      
  "-charset",                                                                                                                         
  "utf8",                                                                                                                            
  "-config",                                                                                                                        
  "/path/to/.ExifTool_config",
  "-P",                                                                                                                               
  "-E",                                                                                                                               
  "-overwrite_original",
  ...

Is there some way to get the .exiftool_args to start with -config?

Image that always freezes on read

I believe I've found an image that freezes the tool every time it is read.

a.jpg.zip

Steps to reproduce:

multi_exiftool_reader = MultiExiftool::Reader.new
multi_exiftool_reader.filenames = [Rails.root.join('a.jpg')]
multi_exiftool_reader.read

I inspected the image with exiftool and I imagine the cause is the huge field "Mask Group Based Corr Mask Masks Dabs".

I've never seen anything like this before, so any ideas how to avoid a freeze are welcomed.

Does not handle spaces on windows when using forward slashes

If i give MultiExiftool a path with spaces, it will break them up into multiple paths:

require 'multi_exiftool'
# => true
reader = MultiExiftool::Reader.new
# => #<MultiExiftool::Reader:0x3a16b68 @exiftool_command="exiftool", @options={}, @filenames=[], @option_mapping={:numerical=>:n, :group=>:g}>
reader.tags = ['-subject']
# => ["-subject"]
reader.exiftool_command = File.join(Dir.pwd, 'exiftool')
# => "C:/tmp/exiftool"
reader.filenames = ['C:/tmp/2009-03-07 Test Path/IMG_4224.JPG']
# => ['C:/tmp/2009-03-07 Test Path/IMG_4224.JPG']
values = reader.read
# => []
reader.errors
# => ["File not found: C:/tmp/2009-03-07/", "File not found: Test/", "File not found: Path/IMG_4224.JPG"]

ArgumentError - invalid datetime

i'm getting ArgumentError: argument out of range when trying to process mp3 file.

I've found out it is caused by invalid date in datetimeoriginal tag - 2022:25:01 16:25 (day and month are switched)

imho this should be escaped to skip the tag or raise some custom error, because ArgumentError is confusing for the user

Batch processing with different values

Hi - Just looking at batching changes to a large number of files using this - it doesn't look like a batch of files can receive different values per file.

Would it be difficult to modify values_args to take an array of valid values in the same order as the filenames array and have the values zip together as the command is built?

Apologies for not putting in a PR - my ruby is rusty rn

Cheers
b

Issue with subseconds

I'm currently tracking an issue with subseconds which looks like it was introduced in 0.6.0.

I'm seeing that when I call

image_exif.SubSecDateTimeOriginal

0.5.0 and below produce the result:

2012:07:13 19:36:01.32

0.6.0 and above produces:

2012-07-13 19:36:01 +0100

Calling subsec on the times from 0.6.0 and above results in 0.

This seems to be the case for all the files I've tested with.

I've attached a sample image:
sample

This is the test code to reproduce:

#!/usr/bin/ruby

gem 'multi_exiftool', '0.9.0'

require 'multi_exiftool'
multi_exiftool_reader = MultiExiftool::Reader.new
multi_exiftool_reader.filenames = Dir['*.jpg']
multi_exiftool_reader_results = multi_exiftool_reader.read
image_exif = multi_exiftool_reader_results.first
puts image_exif.SubSecDateTimeOriginal

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.