phtools 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/README.md +41 -19
- data/TODO.md +5 -2
- data/exe/phmove +7 -7
- data/exe/phrename +49 -0
- data/lib/phmove.rb +22 -16
- data/lib/phrename.rb +33 -1
- data/lib/phtools/mini_exiftool-2.3.0anb.rb +703 -0
- data/lib/phtools/ph_file.rb +1 -1
- data/lib/phtools/runner.rb +2 -2
- data/lib/phtools/version.rb +1 -1
- data/lib/phtools.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f645b7d7f323aba0cea04b8a640b3d84d383b20
|
4
|
+
data.tar.gz: 0ccb3bb5953014a8cf1f194f35d62be78a6a5828
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eec57813519d3cc5f553f92f8e867101e52e218a09bd9396f874b175ad06616555c83c67f09c036b835ff967f50393b239e61e5882ec3692f5997b8cee0b4de7
|
7
|
+
data.tar.gz: f4b0f09eb7f01aafb9bef15af263117e442799a6899baa4b5cd8b5e35bd71e3c43c7ad672050418b43f620084edc3e8646c1be68425c6730700d2c18fdbd48c2
|
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
--format
|
1
|
+
--format progress
|
2
2
|
--color
|
data/README.md
CHANGED
@@ -1,22 +1,31 @@
|
|
1
1
|
[](https://rubygems.org/gems/phtools)
|
2
2
|
# PHTOOLS by ANB
|
3
|
-
A bundle of small CLI tools for arranging, renaming, tagging of the photo and video files. Helps keep photo-video assets in order.
|
3
|
+
A bundle of small CLI tools for arranging, renaming, tagging of the photo and video files. Helps to keep photo-video assets in order.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Rationale
|
6
|
+
PHTOOLS is an instrument intended for photographers\photo enthusiasts who:
|
7
|
+
* own tons of photo-video files and want to keep it in order
|
8
|
+
* really don't like the way how digital cameras name the files: P1193691.JPG, IMP_1409.JPG, _DSC1459.ARW etc.
|
9
|
+
* for photo storage prefer usage of traditional File System (folder structure) instead of "black box" databases of media managers (like iPhoto, Photoshop etc.)
|
10
|
+
* would like to have date-time-original info in the name of the file
|
11
|
+
* expects that sorting folder content "by name" will arrange photo-video assets in chronological order
|
12
|
+
* for some events (wedding, holydays etc.) have photos from different authors and would like to keep visible author name (nik) in the file name
|
13
|
+
* appreciate the use of internal metadata (EXIF, XMP etc.) beleiving it is the best way to keep context info of the picture
|
14
|
+
* are Ok with the use of Command Line tools
|
15
|
+
|
16
|
+
## Installation
|
6
17
|
### Install for usage
|
7
|
-
Get the latest [ruby](https://www.ruby-lang.org/) (>= 2.3) installed.
|
18
|
+
1. Get the latest [ruby](https://www.ruby-lang.org/) (>= 2.3) installed.
|
19
|
+
2. Install ExifTool by Phil Harvey (http://www.sno.phy.queensu.ca/~phil/exiftool/).
|
20
|
+
3. `gem install phtools`
|
21
|
+
4. Get list of phtools: `phtools`
|
22
|
+
5. Get usage info for particular command: `phls -h`
|
8
23
|
|
9
|
-
Install ExifTool by Phil Harvey (http://www.sno.phy.queensu.ca/~phil/exiftool/)
|
10
|
-
```
|
11
|
-
gem install phtools
|
12
|
-
```
|
13
24
|
### Install for development
|
14
|
-
Fork or download from GitHub.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
```
|
19
|
-
Develop, test:
|
25
|
+
1. Fork or download from GitHub.
|
26
|
+
2. Install dependencies: `bundle install`
|
27
|
+
3. Develop.
|
28
|
+
4. Test:
|
20
29
|
```sh
|
21
30
|
bundle exec rspec
|
22
31
|
bundle exec cucumber
|
@@ -29,21 +38,34 @@ bundle exec guard
|
|
29
38
|
## PHTOOLS Use cases
|
30
39
|
### Use Case 1. Collect photos, videos, raw-photos from different sources into one place (for further processing)
|
31
40
|
|
32
|
-
####Given
|
33
|
-
I have copies of SD Cards with photos, videos taken with DSLR camera on my Hard Disk in `~/
|
41
|
+
#### Given
|
42
|
+
I have copies of SD Cards with photos, videos taken with DSLR camera on my Hard Disk in `~/Desktop/SDCard1` and in `~/Desktop/SDCard2`.
|
34
43
|
|
35
44
|
And I have empty folder `~/Desktop/assets_staging` I would like to collect all the photo-files to.
|
36
45
|
|
37
|
-
####When
|
46
|
+
#### When
|
38
47
|
I run:
|
39
48
|
```sh
|
40
|
-
|
41
|
-
phls -R ~/path/to/copy/SDCard1 ~/path/to/copy/SDCard2 | phmove -a
|
49
|
+
phls -R ~/Desktop/SDCard1 ~/Desktop/SDCard2 | phmove -a ~/Desktop/assets_staging
|
42
50
|
```
|
43
51
|
|
44
|
-
####Then
|
52
|
+
#### Then
|
45
53
|
I get all photos moved to `~/Desktop/assets_staging`.
|
46
54
|
|
47
55
|
And all videos are moved to `~/Desktop/assets_staging/VIDEO`.
|
48
56
|
|
49
57
|
And all raw photo-files are moved to `~/Desktop/assets_staging/RAW`.
|
58
|
+
|
59
|
+
## PHTOOLS concepts
|
60
|
+
### PHTOOLS Standard file name
|
61
|
+
PHTOOLS standard file name looks like this: **`YYYYmmdd-HHMMSS_AAA ORIGINAL.EXT`**, where
|
62
|
+
|
63
|
+
**YYYYmmdd-HHMMSS** - photo creation datestamp (year-month-day-hours-minutes-seconds). By default PHTOOLS use the value of EXIF tag `DateTimeOriginal` or `CreateDate` for this purpose.
|
64
|
+
|
65
|
+
**AAA** - author nikname. 3 character long, only latin alphabet supported.
|
66
|
+
|
67
|
+
**ORIGINAL.EXT** - original file name, created by digital camera.
|
68
|
+
|
69
|
+
For example, the digital camera photo file `P1193691.JPG`, taken by AndrewBiz (aka ANB), after PHTOOLS processing will look like:
|
70
|
+
`20160902-174939_ANB P1193691.JPG`
|
71
|
+
|
data/TODO.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
- [x] core - runner.rb: print class instance variables in debug mode
|
2
2
|
- [x] phls: use init method to initialize variables
|
3
3
|
- [x] phls: change -r to -R
|
4
|
+
- [ ] phls: make it work with .folders (like ftls did)
|
4
5
|
- [x] phmove: create phmove tool based on ftarrange code (see ftools repo)
|
5
|
-
- [
|
6
|
-
- [
|
6
|
+
- [x] phmove: make target_folder as parameter not an option
|
7
|
+
- [x] phmove: -a (--arrange) parameter means to put photo, video, raw files into separate folders inside target. If -a is not set all files are moved to root of target directory (plain collection of files)
|
8
|
+
- [ ] phmove: delete unused empty RAW and VIDEO folders
|
9
|
+
- [ ] phmove: make options to set video, raw folder names
|
data/exe/phmove
CHANGED
@@ -11,22 +11,22 @@ module PhTools
|
|
11
11
|
***************************************************
|
12
12
|
phtools - *Keep Your Photos In Order* (c) ANB
|
13
13
|
***************************************************
|
14
|
-
#{tool_name} moves input file(s) into
|
15
|
-
|
16
|
-
subfolders.
|
14
|
+
#{tool_name} moves input file(s) into TARGET_FOLDER
|
15
|
+
If --arrange option is set it separates photo files, RAW photo files and VIDEO files
|
16
|
+
to corresponding subfolders.
|
17
17
|
phtools friendly files: #{file_type * ','}
|
18
18
|
|
19
19
|
Optimized to be used with other *phtools* via pipes.
|
20
|
-
Example: phls | phrename -a anb | #{tool_name}
|
20
|
+
Example: phls | phrename -a anb | #{tool_name} -a target/folder
|
21
21
|
|
22
22
|
Usage:
|
23
|
-
#{tool_name} [-
|
23
|
+
#{tool_name} [-D] [-a] TARGET_FOLDER
|
24
24
|
#{tool_name} -h | --help
|
25
25
|
#{tool_name} -v | --version
|
26
26
|
|
27
27
|
Options:
|
28
|
-
-
|
29
|
-
|
28
|
+
-a --arrange Move photos to TARGET_FOLDER, videos to TARGET_FOLDER/VIDEO
|
29
|
+
raw-files to TARGET_FOLDER/RAW
|
30
30
|
-D --debug Turn on debugging (verbose) mode
|
31
31
|
-h --help Show this screen.
|
32
32
|
-v --version Show version.
|
data/exe/phrename
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
# (c) ANB Andrew Bizyaev
|
4
|
+
|
5
|
+
module PhTools
|
6
|
+
tool_name = File.basename(__FILE__)
|
7
|
+
require "#{tool_name}"
|
8
|
+
|
9
|
+
file_type = FILE_TYPE_IMAGE + FILE_TYPE_VIDEO + FILE_TYPE_AUDIO
|
10
|
+
usage = <<DOCOPT
|
11
|
+
***************************************************
|
12
|
+
phtools - *Keep Your Photos In Order* (c) ANB
|
13
|
+
***************************************************
|
14
|
+
#{tool_name} renames the input files based on EXIF
|
15
|
+
DateTimeOriginal (or CreateDate) tag and author NICKNAME provided.
|
16
|
+
The target file name format is YYYYmmdd-HHMMSS_AAA ORIGINAL.EXT, where:
|
17
|
+
YYYYmmdd-HHMMSS - Date-Time of photo creation (DateTimeOriginal tag),
|
18
|
+
AAA - the author nickname,
|
19
|
+
ORIGINAL.EXT - the photo name given by digital camera.
|
20
|
+
Input file should be one of the types: #{file_type * ','}
|
21
|
+
Example: input file DSC03455.JPG will be renamed to 20130108-124145_ANB DSC03455.JPG
|
22
|
+
#{tool_name} is optimized to be used with other *phtools* via pipes, e.g.:
|
23
|
+
phls | #{tool_name} -a anb
|
24
|
+
|
25
|
+
This program uses external utility ExifTool by Phil Harvey
|
26
|
+
(http://www.sno.phy.queensu.ca/~phil/exiftool/). Make sure you have exiftool installed.
|
27
|
+
|
28
|
+
Usage:
|
29
|
+
#{tool_name} -a NICKNAME [-D] [-t TAG]
|
30
|
+
#{tool_name} -h | --help
|
31
|
+
#{tool_name} -v | --version
|
32
|
+
|
33
|
+
Options:
|
34
|
+
-a NICKNAME --author=NICKNAME Author nickname size should be #{PhFile::NICKNAME_SIZE} chars,
|
35
|
+
have no spaces and other non-word chars,
|
36
|
+
have no digits,
|
37
|
+
have only ASCII chars (e.g. ANB)
|
38
|
+
-t TAG --tag_date=TAG Set the TAG name containing Date_Time creation
|
39
|
+
info. The TAG value will be used instead of
|
40
|
+
standard DateTimeOriginal tag.
|
41
|
+
All existing tags and tag names you can get
|
42
|
+
using command `phls filename|phmtags -f`
|
43
|
+
-D --debug Turn on debugging (verbose) mode
|
44
|
+
-h --help Show this screen.
|
45
|
+
-v --version Show version.
|
46
|
+
DOCOPT
|
47
|
+
|
48
|
+
PhTools.const_get(tool_name.capitalize).new(usage, file_type).run!
|
49
|
+
end
|
data/lib/phmove.rb
CHANGED
@@ -7,43 +7,49 @@ require 'phtools/runner'
|
|
7
7
|
module PhTools
|
8
8
|
class Phmove < Runner
|
9
9
|
def self.about
|
10
|
-
"moves input files
|
10
|
+
"moves input files to target folder"
|
11
11
|
end
|
12
12
|
private
|
13
13
|
|
14
14
|
def validate_options
|
15
|
-
@
|
16
|
-
@
|
17
|
-
|
15
|
+
@target_folder = @options_cli['TARGET_FOLDER']
|
16
|
+
@arrange = @options_cli['--arrange']
|
17
|
+
if @arrange
|
18
|
+
@raw_folder = File.join(@target_folder, 'RAW')
|
19
|
+
@video_folder = File.join(@target_folder, 'VIDEO')
|
20
|
+
else
|
21
|
+
@raw_folder = @target_folder
|
22
|
+
@video_folder = @target_folder
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
def process_before
|
21
|
-
fail PhTools::Error, "#{@
|
22
|
-
fail PhTools::Error, "#{@
|
27
|
+
fail PhTools::Error, "#{@target_folder} does not exist" unless File.exist?(@target_folder)
|
28
|
+
fail PhTools::Error, "#{@target_folder} is not a directory" unless File.directory?(@target_folder)
|
23
29
|
begin
|
24
30
|
Dir.mkdir @raw_folder unless Dir.exist?(@raw_folder)
|
25
31
|
Dir.mkdir @video_folder unless Dir.exist?(@video_folder)
|
26
32
|
rescue
|
27
|
-
raise PhTools::Error, "Unable to make dir inside '#{@
|
33
|
+
raise PhTools::Error, "Unable to make dir inside '#{@target_folder}'"
|
28
34
|
end
|
29
35
|
end
|
30
36
|
|
31
|
-
def process_file(
|
32
|
-
|
33
|
-
file_type =
|
37
|
+
def process_file(phfile)
|
38
|
+
phfile_out = phfile.clone
|
39
|
+
file_type = phfile.extname.slice(1..-1).downcase
|
34
40
|
case
|
35
41
|
when FILE_TYPE_IMAGE_NORMAL.include?(file_type)
|
36
|
-
|
42
|
+
phfile_out.dirname = @target_folder
|
37
43
|
when FILE_TYPE_IMAGE_RAW.include?(file_type)
|
38
|
-
|
44
|
+
phfile_out.dirname = @raw_folder
|
39
45
|
when FILE_TYPE_VIDEO.include?(file_type)
|
40
|
-
|
46
|
+
phfile_out.dirname = @video_folder
|
41
47
|
when FILE_TYPE_AUDIO.include?(file_type)
|
42
|
-
|
48
|
+
phfile_out.dirname = @target_folder
|
43
49
|
end
|
44
50
|
|
45
|
-
FileUtils.mv(
|
46
|
-
|
51
|
+
FileUtils.mv(phfile.filename, phfile_out.filename) unless phfile == phfile_out
|
52
|
+
phfile_out
|
47
53
|
rescue SystemCallError => e
|
48
54
|
raise PhTools::Error, 'file moving - ' + e.message
|
49
55
|
end
|
data/lib/phrename.rb
CHANGED
@@ -3,11 +3,43 @@
|
|
3
3
|
# (c) ANB Andrew Bizyaev
|
4
4
|
|
5
5
|
require 'phtools/runner'
|
6
|
+
require 'phtools/mini_exiftool-2.3.0anb'
|
6
7
|
|
7
8
|
module PhTools
|
8
9
|
class Phrename < Runner
|
9
10
|
def self.about
|
10
|
-
"
|
11
|
+
"renames input files to phtools standard"
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def validate_options
|
17
|
+
@author = @options_cli['--author'].upcase || ''
|
18
|
+
ok, msg = PhFile.validate_author(@author)
|
19
|
+
fail PhTools::Error, msg unless ok
|
20
|
+
@user_tag_date = @options_cli['--tag_date'] || ''
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_file(phfile)
|
24
|
+
phfile_out = phfile.clone
|
25
|
+
begin
|
26
|
+
tag = MiniExiftool.new(phfile.filename, timestamps: DateTime)
|
27
|
+
rescue
|
28
|
+
raise PhTools::Error, 'EXIF tags reading'
|
29
|
+
end
|
30
|
+
if @user_tag_date.empty?
|
31
|
+
dto = tag.date_time_original || tag.create_date || PhFile::ZERO_DATE
|
32
|
+
else
|
33
|
+
fail PhTools::Error, "tag #{@user_tag_date} is not found" unless tag[@user_tag_date]
|
34
|
+
fail PhTools::Error, "tag #{@user_tag_date} is not a DateTime type" unless tag[@user_tag_date].kind_of?(DateTime)
|
35
|
+
dto = tag[@user_tag_date] || PhFile::ZERO_DATE
|
36
|
+
end
|
37
|
+
phfile_out.standardize!(date_time: dto, author: @author)
|
38
|
+
FileUtils.mv(phfile.filename, phfile_out.filename) unless
|
39
|
+
phfile == phfile_out
|
40
|
+
phfile_out
|
41
|
+
rescue => e
|
42
|
+
raise PhTools::Error, 'file renaming - ' + e.message
|
11
43
|
end
|
12
44
|
end
|
13
45
|
end
|
@@ -0,0 +1,703 @@
|
|
1
|
+
# -- encoding: utf-8 --
|
2
|
+
#
|
3
|
+
# MiniExiftool
|
4
|
+
#
|
5
|
+
# This library is wrapper for the Exiftool command-line
|
6
|
+
# application (http://www.sno.phy.queensu.ca/~phil/exiftool/)
|
7
|
+
# written by Phil Harvey.
|
8
|
+
# Read and write access is done in a clean OO manner.
|
9
|
+
#
|
10
|
+
# Author: Jan Friedrich
|
11
|
+
# Copyright (c) 2007-2013 by Jan Friedrich
|
12
|
+
# Licensed under the GNU LESSER GENERAL PUBLIC LICENSE,
|
13
|
+
# Version 2.1, February 1999
|
14
|
+
#
|
15
|
+
|
16
|
+
require 'fileutils'
|
17
|
+
require 'json'
|
18
|
+
require 'pstore'
|
19
|
+
require 'rational'
|
20
|
+
require 'rbconfig'
|
21
|
+
require 'set'
|
22
|
+
require 'tempfile'
|
23
|
+
require 'time'
|
24
|
+
require 'nesty' # ANB
|
25
|
+
|
26
|
+
# Simple OO access to the Exiftool command-line application.
|
27
|
+
class MiniExiftool
|
28
|
+
|
29
|
+
VERSION = '2.3.0'
|
30
|
+
|
31
|
+
# Name of the Exiftool command-line application
|
32
|
+
@@cmd = 'exiftool'
|
33
|
+
|
34
|
+
# Hash of the standard options used when call MiniExiftool.new
|
35
|
+
@@opts = { :numerical => false, :composite => true, :ignore_minor_errors => false,
|
36
|
+
:replace_invalid_chars => false, :timestamps => Time }
|
37
|
+
|
38
|
+
# Encoding of the filesystem (filenames in command line)
|
39
|
+
@@fs_enc = Encoding.find('filesystem')
|
40
|
+
|
41
|
+
def self.opts_accessor *attrs
|
42
|
+
attrs.each do |a|
|
43
|
+
define_method a do
|
44
|
+
@opts[a]
|
45
|
+
end
|
46
|
+
define_method "#{a}=" do |val|
|
47
|
+
@opts[a] = val
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :filename, :errors
|
53
|
+
|
54
|
+
opts_accessor :numerical, :composite, :ignore_minor_errors,
|
55
|
+
:replace_invalid_chars, :timestamps
|
56
|
+
|
57
|
+
@@encoding_types = %w(exif iptc xmp png id3 pdf photoshop quicktime aiff mie vorbis)
|
58
|
+
|
59
|
+
def self.encoding_opt enc_type
|
60
|
+
(enc_type.to_s + '_encoding').to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
@@encoding_types.each do |enc_type|
|
64
|
+
opts_accessor encoding_opt(enc_type)
|
65
|
+
end
|
66
|
+
|
67
|
+
# +opts+ support at the moment
|
68
|
+
# * <code>:numerical</code> for numerical values, default is +false+
|
69
|
+
# * <code>:composite</code> for including composite tags while loading,
|
70
|
+
# default is +true+
|
71
|
+
# * <code>:ignore_minor_errors</code> ignore minor errors (See -m-option
|
72
|
+
# of the exiftool command-line application, default is +false+)
|
73
|
+
# * <code>:coord_format</code> set format for GPS coordinates (See
|
74
|
+
# -c-option of the exiftool command-line application, default is +nil+
|
75
|
+
# that means exiftool standard)
|
76
|
+
# * <code>:replace_invalid_chars</code> replace string for invalid
|
77
|
+
# UTF-8 characters or +false+ if no replacing should be done,
|
78
|
+
# default is +false+
|
79
|
+
# * <code>:timestamps</code> generating DateTime objects instead of
|
80
|
+
# Time objects if set to <code>DateTime</code>, default is +Time+
|
81
|
+
#
|
82
|
+
# <b>ATTENTION:</b> Time objects are created using <code>Time.local</code>
|
83
|
+
# therefore they use <em>your local timezone</em>, DateTime objects instead
|
84
|
+
# are created <em>without timezone</em>!
|
85
|
+
# * <code>:exif_encoding</code>, <code>:iptc_encoding</code>,
|
86
|
+
# <code>:xmp_encoding</code>, <code>:png_encoding</code>,
|
87
|
+
# <code>:id3_encoding</code>, <code>:pdf_encoding</code>,
|
88
|
+
# <code>:photoshop_encoding</code>, <code>:quicktime_encoding</code>,
|
89
|
+
# <code>:aiff_encoding</code>, <code>:mie_encoding</code>,
|
90
|
+
# <code>:vorbis_encoding</code> to set this specific encoding (see
|
91
|
+
# -charset option of the exiftool command-line application, default is
|
92
|
+
# +nil+: no encoding specified)
|
93
|
+
def initialize filename=nil, opts={}
|
94
|
+
@opts = @@opts.merge opts
|
95
|
+
if @opts[:convert_encoding]
|
96
|
+
warn 'Option :convert_encoding is not longer supported!'
|
97
|
+
warn 'Please use the String#encod* methods.'
|
98
|
+
end
|
99
|
+
@values = TagHash.new
|
100
|
+
@changed_values = TagHash.new
|
101
|
+
@errors = TagHash.new
|
102
|
+
load filename unless filename.nil?
|
103
|
+
end
|
104
|
+
|
105
|
+
def initialize_from_hash hash # :nodoc:
|
106
|
+
set_values hash
|
107
|
+
set_opts_by_heuristic
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
def initialize_from_json json # :nodoc:
|
112
|
+
@output = json
|
113
|
+
@errors.clear
|
114
|
+
parse_output
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# Load the tags of filename.
|
119
|
+
def load filename
|
120
|
+
MiniExiftool.setup
|
121
|
+
unless filename && File.exist?(filename)
|
122
|
+
raise MiniExiftool::Error.new("File '#{filename}' does not exist.")
|
123
|
+
end
|
124
|
+
if File.directory?(filename)
|
125
|
+
raise MiniExiftool::Error.new("'#{filename}' is a directory.")
|
126
|
+
end
|
127
|
+
@filename = filename
|
128
|
+
@values.clear
|
129
|
+
@changed_values.clear
|
130
|
+
params = '-j '
|
131
|
+
params << (@opts[:numerical] ? '-n ' : '')
|
132
|
+
params << (@opts[:composite] ? '' : '-e ')
|
133
|
+
params << (@opts[:coord_format] ? "-c \"#{@opts[:coord_format]}\"" : '')
|
134
|
+
@@encoding_types.each do |enc_type|
|
135
|
+
if enc_val = @opts[MiniExiftool.encoding_opt(enc_type)]
|
136
|
+
params << "-charset #{enc_type}=#{enc_val} "
|
137
|
+
end
|
138
|
+
end
|
139
|
+
if run(cmd_gen(params, @filename))
|
140
|
+
parse_output
|
141
|
+
else
|
142
|
+
raise MiniExiftool::Error.new(@error_text)
|
143
|
+
end
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
# Reload the tags of an already read file.
|
148
|
+
def reload
|
149
|
+
load @filename
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the value of a tag.
|
153
|
+
def [] tag
|
154
|
+
@changed_values[tag] || @values[tag]
|
155
|
+
end
|
156
|
+
|
157
|
+
# Set the value of a tag.
|
158
|
+
def []= tag, val
|
159
|
+
@changed_values[tag] = val
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns true if any tag value is changed or if the value of a
|
163
|
+
# given tag is changed.
|
164
|
+
def changed? tag=false
|
165
|
+
if tag
|
166
|
+
@changed_values.include? tag
|
167
|
+
else
|
168
|
+
!@changed_values.empty?
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Revert all changes or the change of a given tag.
|
173
|
+
def revert tag=nil
|
174
|
+
if tag
|
175
|
+
val = @changed_values.delete(tag)
|
176
|
+
res = val != nil
|
177
|
+
else
|
178
|
+
res = @changed_values.size > 0
|
179
|
+
@changed_values.clear
|
180
|
+
end
|
181
|
+
res
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns an array of the tags (original tag names) of the read file.
|
185
|
+
def tags
|
186
|
+
@values.keys.map { |key| MiniExiftool.original_tag(key) }
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns an array of all changed tags.
|
190
|
+
def changed_tags
|
191
|
+
@changed_values.keys.map { |key| MiniExiftool.original_tag(key) }
|
192
|
+
end
|
193
|
+
|
194
|
+
# Save the changes to the file.
|
195
|
+
def save
|
196
|
+
MiniExiftool.setup
|
197
|
+
return false if @changed_values.empty?
|
198
|
+
@errors.clear
|
199
|
+
temp_file = Tempfile.new('mini_exiftool')
|
200
|
+
temp_file.close
|
201
|
+
temp_filename = temp_file.path
|
202
|
+
FileUtils.cp filename.encode(@@fs_enc), temp_filename
|
203
|
+
all_ok = true
|
204
|
+
@changed_values.each do |tag, val|
|
205
|
+
original_tag = MiniExiftool.original_tag(tag)
|
206
|
+
arr_val = val.kind_of?(Array) ? val : [val]
|
207
|
+
arr_val.map! {|e| convert_before_save(e)}
|
208
|
+
params = '-q -P -overwrite_original '
|
209
|
+
params << (arr_val.detect {|x| x.kind_of?(Numeric)} ? '-n ' : '')
|
210
|
+
params << (@opts[:ignore_minor_errors] ? '-m ' : '')
|
211
|
+
arr_val.each do |v|
|
212
|
+
params << %Q(-#{original_tag}=#{escape(v)} )
|
213
|
+
end
|
214
|
+
result = run(cmd_gen(params, temp_filename))
|
215
|
+
unless result
|
216
|
+
all_ok = false
|
217
|
+
@errors[tag] = @error_text.gsub(/Nothing to do.\n\z/, '').chomp
|
218
|
+
end
|
219
|
+
end
|
220
|
+
if all_ok
|
221
|
+
FileUtils.cp temp_filename, filename.encode(@@fs_enc)
|
222
|
+
reload
|
223
|
+
end
|
224
|
+
temp_file.delete
|
225
|
+
all_ok
|
226
|
+
end
|
227
|
+
|
228
|
+
def save!
|
229
|
+
unless save
|
230
|
+
err = []
|
231
|
+
@errors.each do |key, value|
|
232
|
+
err << "(#{key}) #{value}"
|
233
|
+
end
|
234
|
+
raise MiniExiftool::Error.new("MiniExiftool couldn't save. The following errors occurred: #{err.empty? ? "None" : err.join(", ")}")
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns a hash of the original loaded values of the MiniExiftool
|
239
|
+
# instance.
|
240
|
+
def to_hash
|
241
|
+
result = {}
|
242
|
+
@values.each do |k,v|
|
243
|
+
result[MiniExiftool.original_tag(k)] = v
|
244
|
+
end
|
245
|
+
result
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns a YAML representation of the original loaded values of the
|
249
|
+
# MiniExiftool instance.
|
250
|
+
def to_yaml
|
251
|
+
to_hash.to_yaml
|
252
|
+
end
|
253
|
+
|
254
|
+
# Create a MiniExiftool instance from a hash. Default value
|
255
|
+
# conversions will be applied if neccesary.
|
256
|
+
def self.from_hash hash, opts={}
|
257
|
+
instance = MiniExiftool.new nil, opts
|
258
|
+
instance.initialize_from_hash hash
|
259
|
+
instance
|
260
|
+
end
|
261
|
+
|
262
|
+
# Create a MiniExiftool instance from JSON data. Default value
|
263
|
+
# conversions will be applied if neccesary.
|
264
|
+
def self.from_json json, opts={}
|
265
|
+
instance = MiniExiftool.new nil, opts
|
266
|
+
instance.initialize_from_json json
|
267
|
+
instance
|
268
|
+
end
|
269
|
+
|
270
|
+
# Create a MiniExiftool instance from YAML data created with
|
271
|
+
# MiniExiftool#to_yaml
|
272
|
+
def self.from_yaml yaml, opts={}
|
273
|
+
MiniExiftool.from_hash YAML.load(yaml), opts
|
274
|
+
end
|
275
|
+
|
276
|
+
# Returns the command name of the called Exiftool application.
|
277
|
+
def self.command
|
278
|
+
@@cmd
|
279
|
+
end
|
280
|
+
|
281
|
+
# Setting the command name of the called Exiftool application.
|
282
|
+
def self.command= cmd
|
283
|
+
@@cmd = cmd
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns the options hash.
|
287
|
+
def self.opts
|
288
|
+
@@opts
|
289
|
+
end
|
290
|
+
|
291
|
+
# Returns a set of all known tags of Exiftool.
|
292
|
+
def self.all_tags
|
293
|
+
unless defined? @@all_tags
|
294
|
+
@@all_tags = pstore_get :all_tags
|
295
|
+
end
|
296
|
+
@@all_tags
|
297
|
+
end
|
298
|
+
|
299
|
+
# Returns a set of all possible writable tags of Exiftool.
|
300
|
+
def self.writable_tags
|
301
|
+
unless defined? @@writable_tags
|
302
|
+
@@writable_tags = pstore_get :writable_tags
|
303
|
+
end
|
304
|
+
@@writable_tags
|
305
|
+
end
|
306
|
+
|
307
|
+
# Returns the original Exiftool name of the given tag
|
308
|
+
def self.original_tag tag
|
309
|
+
unless defined? @@all_tags_map
|
310
|
+
@@all_tags_map = pstore_get :all_tags_map
|
311
|
+
end
|
312
|
+
@@all_tags_map[tag]
|
313
|
+
end
|
314
|
+
|
315
|
+
# Returns the version of the Exiftool command-line application.
|
316
|
+
def self.exiftool_version
|
317
|
+
output = `#{MiniExiftool.command} -ver 2>&1`
|
318
|
+
unless $?.exitstatus == 0
|
319
|
+
raise MiniExiftool::Error.new("Command '#{MiniExiftool.command}' not found")
|
320
|
+
end
|
321
|
+
output.chomp!
|
322
|
+
end
|
323
|
+
|
324
|
+
def self.unify tag
|
325
|
+
tag.to_s.gsub(/[-_]/,'').downcase
|
326
|
+
end
|
327
|
+
|
328
|
+
# Exception class
|
329
|
+
class MiniExiftool::Error < Nesty::NestedStandardError; end # ANB
|
330
|
+
|
331
|
+
############################################################################
|
332
|
+
private
|
333
|
+
############################################################################
|
334
|
+
|
335
|
+
@@setup_done = false
|
336
|
+
def self.setup
|
337
|
+
return if @@setup_done
|
338
|
+
@@error_file = Tempfile.new 'errors'
|
339
|
+
@@error_file.close
|
340
|
+
@@setup_done = true
|
341
|
+
end
|
342
|
+
|
343
|
+
def cmd_gen arg_str='', filename
|
344
|
+
[@@cmd, arg_str.encode('UTF-8'), escape(filename.encode(@@fs_enc))].map {|s| s.force_encoding('UTF-8')}.join(' ')
|
345
|
+
end
|
346
|
+
|
347
|
+
def run cmd
|
348
|
+
if $DEBUG
|
349
|
+
$stderr.puts cmd
|
350
|
+
end
|
351
|
+
@output = `#{cmd} 2>#{@@error_file.path}`
|
352
|
+
@status = $?
|
353
|
+
unless @status.exitstatus == 0
|
354
|
+
@error_text = File.readlines(@@error_file.path).join
|
355
|
+
@error_text.force_encoding('UTF-8')
|
356
|
+
return false
|
357
|
+
else
|
358
|
+
@error_text = ''
|
359
|
+
return true
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def convert_before_save val
|
364
|
+
case val
|
365
|
+
when Time
|
366
|
+
val = val.strftime('%Y:%m:%d %H:%M:%S')
|
367
|
+
end
|
368
|
+
val
|
369
|
+
end
|
370
|
+
|
371
|
+
def method_missing symbol, *args
|
372
|
+
tag_name = symbol.id2name
|
373
|
+
if tag_name.sub!(/=$/, '')
|
374
|
+
self[tag_name] = args.first
|
375
|
+
else
|
376
|
+
self[tag_name]
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def parse_output
|
381
|
+
adapt_encoding
|
382
|
+
set_values JSON.parse(@output).first
|
383
|
+
end
|
384
|
+
|
385
|
+
def adapt_encoding
|
386
|
+
@output.force_encoding('UTF-8')
|
387
|
+
if @opts[:replace_invalid_chars] && !@output.valid_encoding?
|
388
|
+
@output.encode!('UTF-16le', invalid: :replace, replace: @opts[:replace_invalid_chars]).encode!('UTF-8')
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def convert_after_load tag, value
|
393
|
+
return value unless value.kind_of?(String)
|
394
|
+
return value unless value.valid_encoding?
|
395
|
+
case value
|
396
|
+
when /^\d{4}:\d\d:\d\d \d\d:\d\d:\d\d/
|
397
|
+
s = value.sub(/^(\d+):(\d+):/, '\1-\2-')
|
398
|
+
begin
|
399
|
+
if @opts[:timestamps] == Time
|
400
|
+
value = Time.parse(s)
|
401
|
+
elsif @opts[:timestamps] == DateTime
|
402
|
+
value = DateTime.parse(s)
|
403
|
+
else
|
404
|
+
raise MiniExiftool::Error.new("Value #{@opts[:timestamps]} not allowed for option timestamps.")
|
405
|
+
end
|
406
|
+
rescue ArgumentError
|
407
|
+
value = false
|
408
|
+
end
|
409
|
+
when /^\+\d+\.\d+$/
|
410
|
+
value = value.to_f
|
411
|
+
when /^0+[1-9]+$/
|
412
|
+
# nothing => String
|
413
|
+
when /^-?\d+$/
|
414
|
+
value = value.to_i
|
415
|
+
when %r(^(\d+)/(\d+)$)
|
416
|
+
value = Rational($1.to_i, $2.to_i)
|
417
|
+
when /^[\d ]+$/
|
418
|
+
# nothing => String
|
419
|
+
end
|
420
|
+
value
|
421
|
+
end
|
422
|
+
|
423
|
+
def set_values hash
|
424
|
+
hash.each_pair do |tag,val|
|
425
|
+
@values[tag] = convert_after_load(tag, val)
|
426
|
+
end
|
427
|
+
# Remove filename specific tags use attr_reader
|
428
|
+
# MiniExiftool#filename instead
|
429
|
+
# Cause: value of tag filename and attribute
|
430
|
+
# filename have different content, the latter
|
431
|
+
# holds the filename with full path (like the
|
432
|
+
# sourcefile tag) and the former the basename
|
433
|
+
# of the filename also there is no official
|
434
|
+
# "original tag name" for sourcefile
|
435
|
+
%w(directory filename sourcefile).each do |t|
|
436
|
+
@values.delete(t)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def set_opts_by_heuristic
|
441
|
+
@opts[:composite] = tags.include?('ImageSize')
|
442
|
+
@opts[:numerical] = self.file_size.kind_of?(Integer)
|
443
|
+
@opts[:timestamps] = self.FileModifyDate.kind_of?(DateTime) ? DateTime : Time
|
444
|
+
end
|
445
|
+
|
446
|
+
def self.pstore_get attribute
|
447
|
+
load_or_create_pstore unless defined? @@pstore
|
448
|
+
result = nil
|
449
|
+
@@pstore.transaction(true) do |ps|
|
450
|
+
result = ps[attribute]
|
451
|
+
end
|
452
|
+
result
|
453
|
+
end
|
454
|
+
|
455
|
+
@@running_on_windows = /mswin|mingw|cygwin/ === RbConfig::CONFIG['host_os']
|
456
|
+
|
457
|
+
def self.load_or_create_pstore
|
458
|
+
# This will hopefully work on *NIX and Windows systems
|
459
|
+
home = ENV['HOME'] || ENV['HOMEDRIVE'] + ENV['HOMEPATH'] || ENV['USERPROFILE']
|
460
|
+
subdir = @@running_on_windows ? '_mini_exiftool' : '.mini_exiftool'
|
461
|
+
FileUtils.mkdir_p(File.join(home, subdir))
|
462
|
+
pstore_filename = File.join(home, subdir, 'exiftool_tags_' << exiftool_version.gsub('.', '_') << '.pstore')
|
463
|
+
@@pstore = PStore.new pstore_filename
|
464
|
+
if !File.exist?(pstore_filename) || File.size(pstore_filename) == 0
|
465
|
+
@@pstore.transaction do |ps|
|
466
|
+
ps[:all_tags] = all_tags = determine_tags('list')
|
467
|
+
ps[:writable_tags] = determine_tags('listw')
|
468
|
+
map = {}
|
469
|
+
all_tags.each { |k| map[unify(k)] = k }
|
470
|
+
ps[:all_tags_map] = map
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def self.determine_tags arg
|
476
|
+
output = `#{@@cmd} -#{arg}`
|
477
|
+
lines = output.split(/\n/)
|
478
|
+
tags = Set.new
|
479
|
+
lines.each do |line|
|
480
|
+
next unless line =~ /^\s/
|
481
|
+
tags |= line.chomp.split
|
482
|
+
end
|
483
|
+
tags
|
484
|
+
end
|
485
|
+
|
486
|
+
if @@running_on_windows
|
487
|
+
def escape val
|
488
|
+
'"' << val.to_s.gsub(/([\\"])/, "\\\\\\1") << '"'
|
489
|
+
end
|
490
|
+
else
|
491
|
+
def escape val
|
492
|
+
'"' << val.to_s.gsub(/([\\"$])/, "\\\\\\1") << '"'
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
# Hash with indifferent access:
|
497
|
+
# DateTimeOriginal == datetimeoriginal == date_time_original
|
498
|
+
class TagHash < Hash # :nodoc:
|
499
|
+
def[] k
|
500
|
+
super(unify(k))
|
501
|
+
end
|
502
|
+
def []= k, v
|
503
|
+
super(unify(k), v)
|
504
|
+
end
|
505
|
+
def delete k
|
506
|
+
super(unify(k))
|
507
|
+
end
|
508
|
+
|
509
|
+
def unify tag
|
510
|
+
MiniExiftool.unify tag
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
# ANB - dump of real @values:
|
516
|
+
# exiftoolversion=9.41
|
517
|
+
# filesize=128 kB
|
518
|
+
# filemodifydate=2014-04-02T23:00:50+04:00
|
519
|
+
# fileaccessdate=2014-04-02T23:00:51+04:00
|
520
|
+
# fileinodechangedate=2014-04-02T23:00:50+04:00
|
521
|
+
# filepermissions=rw-r--r--
|
522
|
+
# filetype=JPEG
|
523
|
+
# mimetype=image/jpeg
|
524
|
+
# jfifversion=1.01
|
525
|
+
# exifbyteorder=Little-endian (Intel, II)
|
526
|
+
# imagedescription=
|
527
|
+
# make=SONY
|
528
|
+
# model=SLT-A65V
|
529
|
+
# xresolution=350
|
530
|
+
# yresolution=350
|
531
|
+
# resolutionunit=inches
|
532
|
+
# software=SLT-A65V v1.05
|
533
|
+
# modifydate=2014-04-02T20:50:32+00:00
|
534
|
+
# artist=Andrey Bizyaev (photographer); Andrey Bizyaev (camera owner)
|
535
|
+
# ycbcrpositioning=Co-sited
|
536
|
+
# copyright=2013 (c) Andrey Bizyaev. All Rights Reserved.
|
537
|
+
# exposuretime=1/100
|
538
|
+
# fnumber=5.6
|
539
|
+
# exposureprogram=Program AE
|
540
|
+
# iso=160
|
541
|
+
# sensitivitytype=Recommended Exposure Index
|
542
|
+
# recommendedexposureindex=160
|
543
|
+
# exifversion=230
|
544
|
+
# datetimeoriginal=2013-01-03T15:39:08+00:00
|
545
|
+
# createdate=2013-01-03T15:39:08+00:00
|
546
|
+
# componentsconfiguration=Y, Cb, Cr, -
|
547
|
+
# compressedbitsperpixel=2
|
548
|
+
# brightnessvalue=6.3375
|
549
|
+
# exposurecompensation=0
|
550
|
+
# maxaperturevalue=5.6
|
551
|
+
# meteringmode=Multi-segment
|
552
|
+
# lightsource=Unknown
|
553
|
+
# flash=Off, Did not fire
|
554
|
+
# focallength=55.0 mm
|
555
|
+
# quality=Fine
|
556
|
+
# flashexposurecomp=0
|
557
|
+
# teleconverter=None
|
558
|
+
# whitebalancefinetune=0
|
559
|
+
# rating=0
|
560
|
+
# brightness=0
|
561
|
+
# longexposurenoisereduction=On (unused)
|
562
|
+
# highisonoisereduction=Normal
|
563
|
+
# hdr=Off; Uncorrected image
|
564
|
+
# multiframenoisereduction=Off
|
565
|
+
# pictureeffect=Off
|
566
|
+
# softskineffect=Off
|
567
|
+
# vignettingcorrection=Auto
|
568
|
+
# lateralchromaticaberration=Auto
|
569
|
+
# distortioncorrection=Off
|
570
|
+
# wbshiftabgm=0 0
|
571
|
+
# faceinfooffset=94
|
572
|
+
# sonydatetime=2013-01-03T15:39:08+00:00
|
573
|
+
# sonyimagewidth=6000
|
574
|
+
# facesdetected=0
|
575
|
+
# faceinfolength=37
|
576
|
+
# metaversion=DC7303320222000
|
577
|
+
# maxaperture=5.3
|
578
|
+
# minaperture=33
|
579
|
+
# flashstatus=Built-in Flash present
|
580
|
+
# imagecount=8330
|
581
|
+
# lensmount=A-Mount
|
582
|
+
# lensformat=APS-C
|
583
|
+
# sequenceimagenumber=1
|
584
|
+
# sequencefilenumber=1
|
585
|
+
# releasemode2=Normal
|
586
|
+
# shotnumbersincepowerup=2
|
587
|
+
# sequencelength=1 shot
|
588
|
+
# cameraorientation=Horizontal (normal)
|
589
|
+
# quality2=JPEG
|
590
|
+
# sonyimageheight=4000
|
591
|
+
# modelreleaseyear=2011
|
592
|
+
# batterylevel=18%
|
593
|
+
# afpointsselected=(all)
|
594
|
+
# fileformat=ARW 2.3
|
595
|
+
# sonymodelid=SLT-A65 / SLT-A65V
|
596
|
+
# creativestyle=Standard
|
597
|
+
# colortemperature=Auto
|
598
|
+
# colorcompensationfilter=0
|
599
|
+
# scenemode=Auto
|
600
|
+
# zonematching=ISO Setting Used
|
601
|
+
# dynamicrangeoptimizer=Auto
|
602
|
+
# imagestabilization=On
|
603
|
+
# lenstype=Sony DT 16-105mm F3.5-5.6 (SAL16105)
|
604
|
+
# colormode=Standard
|
605
|
+
# lensspec=DT 16-105mm F3.5-5.6
|
606
|
+
# fullimagesize=6000x4000
|
607
|
+
# previewimagesize=1616x1080
|
608
|
+
# flashlevel=Normal
|
609
|
+
# releasemode=Normal
|
610
|
+
# sequencenumber=Single
|
611
|
+
# antiblur=On (Shooting)
|
612
|
+
# intelligentauto=On
|
613
|
+
# whitebalance=Auto
|
614
|
+
# usercomment=
|
615
|
+
# flashpixversion=100
|
616
|
+
# colorspace=sRGB
|
617
|
+
# exifimagewidth=800
|
618
|
+
# exifimageheight=534
|
619
|
+
# interopindex=R98 - DCF basic file (sRGB)
|
620
|
+
# interopversion=100
|
621
|
+
# relatedimagewidth=6000
|
622
|
+
# relatedimageheight=4000
|
623
|
+
# filesource=Digital Camera
|
624
|
+
# scenetype=Directly photographed
|
625
|
+
# customrendered=Normal
|
626
|
+
# exposuremode=Auto
|
627
|
+
# focallengthin35mmformat=82 mm
|
628
|
+
# scenecapturetype=Standard
|
629
|
+
# contrast=Normal
|
630
|
+
# saturation=Normal
|
631
|
+
# sharpness=Normal
|
632
|
+
# imageuniqueid=20140402-205030-0001
|
633
|
+
# lensinfo=16-105mm f/3.5-5.6
|
634
|
+
# lensmodel=DT 16-105mm F3.5-5.6
|
635
|
+
# gpsversionid=2.3.0.0
|
636
|
+
# gpslatituderef=North
|
637
|
+
# gpslongituderef=East
|
638
|
+
# gpsaltituderef=Above Sea Level
|
639
|
+
# gpstimestamp=11:39:09.588
|
640
|
+
# gpsstatus=Measurement Active
|
641
|
+
# gpsmeasuremode=3-Dimensional Measurement
|
642
|
+
# gpsdop=2.0026
|
643
|
+
# gpsspeedref=km/h
|
644
|
+
# gpsspeed=1.097
|
645
|
+
# gpstrackref=True North
|
646
|
+
# gpstrack=357.15
|
647
|
+
# gpsmapdatum=WGS-84
|
648
|
+
# gpsdatestamp=2013:01:03
|
649
|
+
# gpsdifferential=No Correction
|
650
|
+
# printimversion=300
|
651
|
+
# compression=JPEG (old-style)
|
652
|
+
# orientation=Horizontal (normal)
|
653
|
+
# thumbnailoffset=21840
|
654
|
+
# thumbnaillength=5859
|
655
|
+
# xmptoolkit=Image::ExifTool 9.41
|
656
|
+
# location=Дворцовая пл.
|
657
|
+
# locationshowncity=Санкт-Петербург
|
658
|
+
# locationshowncountrycode=RU
|
659
|
+
# locationshowncountryname=Russia
|
660
|
+
# locationshownprovincestate=Санкт-Петербург
|
661
|
+
# locationshownsublocation=Дворцовая пл.
|
662
|
+
# locationshownworldregion=Europe
|
663
|
+
# creator=["Andrey Bizyaev (photographer)", "Andrey Bizyaev (camera owner)"]
|
664
|
+
# rights=2013 (c) Andrey Bizyaev. All Rights Reserved.
|
665
|
+
# subject=["before-what-travel", "before-who-Andrew", "before-where-Baltic", "before-when-day", "before-why-vacation", "before-how-fine", "before-method-digicam"]
|
666
|
+
# collectionname=S-Peterburg Travel
|
667
|
+
# collectionuri=anblab.net
|
668
|
+
# country=Russia
|
669
|
+
# state=Санкт-Петербург
|
670
|
+
# iptcdigest=1569c0bffab4b64cb1107134254cf97d
|
671
|
+
# currentiptcdigest=7be9172b29568717f9bc0976c93ba53d
|
672
|
+
# codedcharacterset=UTF8
|
673
|
+
# enveloperecordversion=4
|
674
|
+
# keywords=["before-what-travel", "before-who-Andrew", "before-where-Baltic", "before-when-day", "before-why-vacation", "before-how-fine", "before-method-digicam"]
|
675
|
+
# byline=["Andrey Bizyaev (photographer)", "Andrey Bizyaev (camera owner)"]
|
676
|
+
# city=Санкт-Петербург
|
677
|
+
# sublocation=Дворцовая пл.
|
678
|
+
# provincestate=Санкт-Петербург
|
679
|
+
# countryprimarylocationname=Russia
|
680
|
+
# copyrightnotice=2013 (c) Andrey Bizyaev. All Rights Reserved.
|
681
|
+
# applicationrecordversion=4
|
682
|
+
# imagewidth=800
|
683
|
+
# imageheight=534
|
684
|
+
# encodingprocess=Baseline DCT, Huffman coding
|
685
|
+
# bitspersample=8
|
686
|
+
# colorcomponents=3
|
687
|
+
# ycbcrsubsampling=YCbCr4:4:4 (1 1)
|
688
|
+
# aperture=5.6
|
689
|
+
# gpsaltitude=0.5 m Above Sea Level
|
690
|
+
# gpsdatetime=2013-01-03T11:39:09+00:00
|
691
|
+
# gpslatitude=60 deg 0' 0.00" N
|
692
|
+
# gpslongitude=25 deg 0' 0.00" E
|
693
|
+
# gpsposition=60 deg 0' 0.00" N, 25 deg 0' 0.00" E
|
694
|
+
# imagesize=800x534
|
695
|
+
# lensid=Sony DT 16-105mm F3.5-5.6 (SAL16105)
|
696
|
+
# scalefactor35efl=1.5
|
697
|
+
# shutterspeed=1/100
|
698
|
+
# thumbnailimage=(Binary data 5859 bytes)
|
699
|
+
# circleofconfusion=0.020 mm
|
700
|
+
# fov=24.8 deg
|
701
|
+
# focallength35efl=55.0 mm (35 mm equivalent: 82.0 mm)
|
702
|
+
# hyperfocaldistance=26.80 m
|
703
|
+
# lightvalue=10.9
|
data/lib/phtools/ph_file.rb
CHANGED
data/lib/phtools/runner.rb
CHANGED
@@ -51,8 +51,8 @@ module PhTools
|
|
51
51
|
ARGF.each_line do |line|
|
52
52
|
filename = line.chomp
|
53
53
|
begin
|
54
|
-
|
55
|
-
ftfile =
|
54
|
+
PhFile.validate_file!(filename, @file_type)
|
55
|
+
ftfile = PhFile.new(filename)
|
56
56
|
@os.output process_file(ftfile)
|
57
57
|
rescue PhTools::Error => e
|
58
58
|
PhTools.puts_error "ERROR: '#{filename}' - #{e.message}", e
|
data/lib/phtools/version.rb
CHANGED
data/lib/phtools.rb
CHANGED
@@ -27,7 +27,7 @@ Please run phtools in a terminal via CLI commands:
|
|
27
27
|
phfixfmd\t(#{Phfixfmd::about}),
|
28
28
|
phls\t(#{Phls::about}),
|
29
29
|
phmtags\t(#{Phmtags::about}),
|
30
|
-
phrename
|
30
|
+
phrename\t(#{Phrename::about}),
|
31
31
|
phtagset\t(#{Phtagset::about}).
|
32
32
|
For more information run these commands with -h option.
|
33
33
|
General info about phtools usage see at https://github.com/AndrewBiz/phtools.git
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phtools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Bizyaev
|
@@ -241,6 +241,7 @@ email:
|
|
241
241
|
executables:
|
242
242
|
- phls
|
243
243
|
- phmove
|
244
|
+
- phrename
|
244
245
|
- phtools
|
245
246
|
extensions: []
|
246
247
|
extra_rdoc_files: []
|
@@ -260,6 +261,7 @@ files:
|
|
260
261
|
- bin/stmux
|
261
262
|
- exe/phls
|
262
263
|
- exe/phmove
|
264
|
+
- exe/phrename
|
263
265
|
- exe/phtools
|
264
266
|
- lib/phbackup.rb
|
265
267
|
- lib/phclname.rb
|
@@ -273,6 +275,7 @@ files:
|
|
273
275
|
- lib/phtagset.rb
|
274
276
|
- lib/phtools.rb
|
275
277
|
- lib/phtools/error.rb
|
278
|
+
- lib/phtools/mini_exiftool-2.3.0anb.rb
|
276
279
|
- lib/phtools/ph_file.rb
|
277
280
|
- lib/phtools/runner.rb
|
278
281
|
- lib/phtools/utils/os.rb
|