phtools 0.7.1 → 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +71 -3
- data/TODO.md +5 -0
- data/exe/phrename +32 -17
- data/lib/phbackup.rb +1 -1
- data/lib/phmove.rb +1 -1
- data/lib/phrename.rb +65 -15
- data/lib/phtools/error.rb +15 -13
- data/lib/phtools/runner.rb +2 -2
- data/lib/phtools/version.rb +1 -1
- data/phtools.gemspec +1 -2
- metadata +7 -8
- data/lib/phtools/mini_exiftool-2.3.0anb.rb +0 -703
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdb79b3f95706f20dc3105e159bba0228c5b7542
|
4
|
+
data.tar.gz: 976d6852897171998919dcbb3d771f46d729ae10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e996378046496d338500dfb1d8c4762b8f640684469db603c84a89a29f68d1219e96341c610254de6146785e2295b13caccae6ec64a97915c03bc10587c539b
|
7
|
+
data.tar.gz: 5214e3b953ef6dfd445df54f05115adb887cb4f595fed8f57b03cbafe875afa870d0aff7fee5176895d1fcf6015fae38830891d98e7dd5250e9ebf8a44fb6fe6
|
data/README.md
CHANGED
@@ -13,6 +13,8 @@ PHTOOLS is an instrument intended for photographers\photo enthusiasts who:
|
|
13
13
|
* appreciate the use of internal metadata (EXIF, XMP etc.) beleiving it is the best way to keep context info of the picture
|
14
14
|
* are Ok with the use of Command Line tools
|
15
15
|
|
16
|
+
**********
|
17
|
+
|
16
18
|
## Installation
|
17
19
|
### Install for usage
|
18
20
|
1. Get the latest [ruby](https://www.ruby-lang.org/) (>= 2.3) installed.
|
@@ -35,13 +37,17 @@ bundle exec cucumber
|
|
35
37
|
bundle exec guard
|
36
38
|
```
|
37
39
|
|
40
|
+
**********
|
41
|
+
|
38
42
|
## PHTOOLS Use cases
|
39
|
-
### Use Case 1. Collect photos, videos, raw-photos from different sources into one place (for further processing)
|
40
43
|
|
44
|
+
### USE CASE 1. Collect photos, videos, raw-photos from different sources into one place (for further processing)
|
41
45
|
#### Given
|
42
46
|
I have copies of SD Cards with photos, videos taken with DSLR camera on my Hard Disk in `~/Desktop/SDCard1` and in `~/Desktop/SDCard2`.
|
43
47
|
|
44
|
-
And I have empty folder `~/Desktop/assets_staging`
|
48
|
+
And I have empty folder `~/Desktop/assets_staging` (lets call it _working folder_).
|
49
|
+
|
50
|
+
And I want all the photo-video files from SD copies (including ones placed deep inside the folder structure of the SD card) to be moved to the _working folder_.
|
45
51
|
|
46
52
|
#### When
|
47
53
|
I run:
|
@@ -56,8 +62,11 @@ And all videos are moved to `~/Desktop/assets_staging/VIDEO`.
|
|
56
62
|
|
57
63
|
And all raw photo-files are moved to `~/Desktop/assets_staging/RAW`.
|
58
64
|
|
59
|
-
|
65
|
+
==========
|
60
66
|
|
67
|
+
### USE CASE 2. Renaming files in accordance with PHTOOLS standard
|
68
|
+
|
69
|
+
#### USE CASE 2.1 Mass rename photos in accordance with PHTOOLS standard (and don't forget to backup before)
|
61
70
|
#### Given
|
62
71
|
I have dozens of photo-files in my working folder `~/Desktop/assets_staging`.
|
63
72
|
|
@@ -75,6 +84,65 @@ I get all photos in `~/Desktop/assets_staging` renamed according to PHTOOLS stan
|
|
75
84
|
|
76
85
|
And I have all original photo-files are backed-up to `~/Desktop/assets_staging/backup`.
|
77
86
|
|
87
|
+
==========
|
88
|
+
|
89
|
+
#### USE CASE 2.2 Rename photos back to it's original names
|
90
|
+
#### Given
|
91
|
+
I have several photo files in my working folder `~/Desktop/assets_staging` renamed to PHTOOLS standard.
|
92
|
+
|
93
|
+
And I want to get all the files renamed back to it's original names (given by DSLR camera)
|
94
|
+
|
95
|
+
#### When
|
96
|
+
I run:
|
97
|
+
```sh
|
98
|
+
cd ~/Desktop/assets_staging
|
99
|
+
phls | phrename --clean
|
100
|
+
```
|
101
|
+
|
102
|
+
#### Then
|
103
|
+
I get all photos in `~/Desktop/assets_staging` renamed to it's original names.
|
104
|
+
|
105
|
+
==========
|
106
|
+
|
107
|
+
#### USE CASE 2.3 Change author nickname in the filenames
|
108
|
+
#### Given
|
109
|
+
I have several photo files in my working folder `~/Desktop/assets_staging` renamed to PHTOOLS standard. Some photos were made by ANB, some photos were made by Alex (nick _ALE_)
|
110
|
+
|
111
|
+
And I want to change the author NICKNAME _ALE_ to _ALX_.
|
112
|
+
|
113
|
+
#### When
|
114
|
+
I run:
|
115
|
+
```sh
|
116
|
+
cd ~/Desktop/assets_staging
|
117
|
+
phls '*ALE*'| phrename -a alx
|
118
|
+
```
|
119
|
+
|
120
|
+
#### Then
|
121
|
+
I get all _ALE_ photos in `~/Desktop/assets_staging` renamed to _ALX_ nickname.
|
122
|
+
|
123
|
+
And all _ANB_ photos are kept unchanged.
|
124
|
+
|
125
|
+
_Note. `phrename` is smart enough to let the user to run it several times on one file. Every time `phrename -a` invoked it overwrites information added by previuos `phrename` run._
|
126
|
+
|
127
|
+
==========
|
128
|
+
|
129
|
+
#### USE CASE 2.4 Adjust date-time in the filename
|
130
|
+
#### Given
|
131
|
+
I have several video files taken by iPhone in my working folder `~/Desktop/assets_staging` renamed to PHTOOLS standard.
|
132
|
+
|
133
|
+
And I've found out that iPhone gives the wrong value to CreateDate tag (in my case the error is minus 2 hours to real time of creation). Because of that 'phrename' gives the wrong _YYYYmmdd-HHMMSS_ timestamp in the filename. I want to get the correct date-time info in the names of video files.
|
134
|
+
|
135
|
+
#### When
|
136
|
+
I run:
|
137
|
+
```sh
|
138
|
+
cd ~/Desktop/assets_staging
|
139
|
+
phls '*.mov'| phrename --shift_time 7200
|
140
|
+
```
|
141
|
+
|
142
|
+
#### Then
|
143
|
+
I get all _mov_ files in `~/Desktop/assets_staging` renamed with correct _YYYYmmdd-HHMMSS_ timestamp (+ 7200 second = + 2 hours than it was before).
|
144
|
+
|
145
|
+
**********
|
78
146
|
|
79
147
|
## PHTOOLS concepts
|
80
148
|
### PHTOOLS Standard file name
|
data/TODO.md
CHANGED
@@ -9,3 +9,8 @@
|
|
9
9
|
- [ ] phmove: make options to set video, raw folder names
|
10
10
|
- [x] phrename: add -c --clean option (based on ftclname functionality)
|
11
11
|
- [x] phrename: add -s --shift_time option (based on ftfixdate functionality)
|
12
|
+
- [x] phrename: by-default read tags for DateTime in this order: DateTimeOriginal, DateCreated, CreateDate, DigitalCreationDate, FileModifyDate (eqv to File::mtime). 1st non-zero value will be taken as a master photo creation timestamp. It means no longer names like '00000101-000000_ANB IMG_0183.PNG' will appear.
|
13
|
+
- [x] phrename: make it safe and smart. Once the file was renamed to PHTOOL standard, re-run of phrename should not change the date-time info unless options -t or -s are used. If user wants to reset file name using exif tag - 1st clean it `phrename --clean`, then rename `phrename -a anb`
|
14
|
+
- [ ] phrename: make new usage mode: `phrename -t TAG`. Useful if user wants to re-set date-time using TAG keeping author-nickname unchanged
|
15
|
+
- [x] get rid of nesty
|
16
|
+
- [x] switch to fresh MiniExiftool gem
|
data/exe/phrename
CHANGED
@@ -11,24 +11,38 @@ module PhTools
|
|
11
11
|
***************************************************
|
12
12
|
phtools - *Keep Your Photos In Order* (c) ANB
|
13
13
|
***************************************************
|
14
|
-
#{tool_name} renames the input
|
15
|
-
|
16
|
-
|
17
|
-
YYYYmmdd-HHMMSS - Date-Time of photo creation (DateTimeOriginal tag),
|
14
|
+
#{tool_name} renames the input file to Standard Name:
|
15
|
+
YYYYmmdd-HHMMSS_AAA ORIGINAL.EXT, where:
|
16
|
+
YYYYmmdd-HHMMSS - Date-Time of photo creation,
|
18
17
|
AAA - the author nickname,
|
19
18
|
ORIGINAL.EXT - the photo name given by digital camera.
|
20
|
-
|
19
|
+
By default date-time information will be taken from EXIF area as the 1st non-zero
|
20
|
+
value of the tags (shown in the order the program scans values):
|
21
|
+
EXIF:DateTimeOriginal -> IPTC:DateCreated + IPTC:TimeCreated -> XMP:DateCreated ->
|
22
|
+
-> EXIF:CreateDate -> XMP:CreateDate -> IPTC:DigitalCreationDate + IPTC:DigitalCreationTime ->
|
23
|
+
-> FileModifyDate
|
21
24
|
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
|
|
25
|
-
|
26
|
+
Input file should be one of the supported types: #{file_type * ','}.
|
27
|
+
#{tool_name} acts as a 'filter' meaning it expects the input files to be passed
|
28
|
+
to STDIN and after the job is done it produces STDOUT with the list of renamed
|
29
|
+
files. In other words this command is intended to be used with other programs
|
30
|
+
connected via pipes, e.g.:
|
31
|
+
phls | #{tool_name} -a anb | phmove ~/targed/folder
|
32
|
+
|
33
|
+
The program is designed to be safe to re-run on the same file several times
|
34
|
+
- every re-run produces the same result (idempotent behaviour).
|
35
|
+
Once the file was renamed to Standard Name, the date-time kept in the name
|
36
|
+
is considered as a master date-time of the photo creation and will not be
|
37
|
+
changed by re-running #{tool_name} unless user explicitly sets '-t' or '-s' option.
|
38
|
+
|
39
|
+
This program uses external utility ExifTool created by Phil Harvey
|
26
40
|
(http://www.sno.phy.queensu.ca/~phil/exiftool/).
|
27
41
|
|
28
42
|
Usage:
|
29
|
-
#{tool_name} -a NICK [-
|
30
|
-
#{tool_name} -c [-D]
|
43
|
+
#{tool_name} -a NICK [-t TAG] [-D]
|
31
44
|
#{tool_name} -s DELTA [-D]
|
45
|
+
#{tool_name} -c [-D]
|
32
46
|
#{tool_name} -h | --help
|
33
47
|
#{tool_name} -v | --version
|
34
48
|
|
@@ -37,19 +51,20 @@ Options:
|
|
37
51
|
supports only latin ASCII chars (e.g. ANB).
|
38
52
|
No digits, no spaces, no other non-word chars allowed.
|
39
53
|
|
40
|
-
-t TAG --tag_date=TAG
|
41
|
-
info
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
-c --clean Rename file(s) back to original name(s)
|
54
|
+
-t TAG --tag_date=TAG Force program to use TAG as a Date-Time creation
|
55
|
+
info instead of standard phtools tags.
|
56
|
+
You can retreive all existing tags using command:
|
57
|
+
`phls filename|phmtags -f` OR
|
58
|
+
`exiftool -s filename`
|
46
59
|
|
47
60
|
-s DELTA --shift_time=DELTA DELTA (in seconds) will be added to Date-Time value
|
48
|
-
|
61
|
+
kept in the name. If DELTA is positive the photo
|
49
62
|
will become yonger: e.g. 20140720-100005, DELTA = 65
|
50
63
|
result = 20140720-100110. If DELTA is negative the photo
|
51
64
|
will get DELTA seconds older.
|
52
65
|
|
66
|
+
-c --clean Rename file back to it's original name
|
67
|
+
|
53
68
|
-D --debug Turn on debugging (verbose) mode
|
54
69
|
-h --help Show this screen.
|
55
70
|
-v --version Show version.
|
data/lib/phbackup.rb
CHANGED
@@ -36,7 +36,7 @@ module PhTools
|
|
36
36
|
def process_file(phfile)
|
37
37
|
backup_path = File.join(@backup_dir,
|
38
38
|
phfile.basename + phfile.extname)
|
39
|
-
FileUtils.cp(phfile.filename, backup_path, verbose: PhTools.debug)
|
39
|
+
FileUtils.cp(phfile.filename, backup_path, preserve: true, verbose: PhTools.debug)
|
40
40
|
phfile
|
41
41
|
rescue
|
42
42
|
raise PhTools::Error, "file copying to #{@backup_dir}"
|
data/lib/phmove.rb
CHANGED
@@ -51,7 +51,7 @@ module PhTools
|
|
51
51
|
phfile_out.dirname = @target_folder
|
52
52
|
end
|
53
53
|
|
54
|
-
FileUtils.mv(phfile.filename, phfile_out.filename) unless phfile == phfile_out
|
54
|
+
FileUtils.mv(phfile.filename, phfile_out.filename, verbose: PhTools.debug) unless phfile == phfile_out
|
55
55
|
phfile_out
|
56
56
|
rescue SystemCallError => e
|
57
57
|
raise PhTools::Error, 'file moving - ' + e.message
|
data/lib/phrename.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
# (c) ANB Andrew Bizyaev
|
4
4
|
|
5
5
|
require 'date'
|
6
|
+
require 'mini_exiftool'
|
6
7
|
require 'phtools/runner'
|
7
|
-
require 'phtools/mini_exiftool-2.3.0anb'
|
8
8
|
|
9
9
|
module PhTools
|
10
10
|
class Phrename < Runner
|
@@ -32,31 +32,81 @@ module PhTools
|
|
32
32
|
|
33
33
|
def process_file(phfile)
|
34
34
|
phfile_out = phfile.clone
|
35
|
+
info_msg = ''
|
35
36
|
case @mode
|
36
37
|
when :rename
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
38
|
+
if phfile_out.basename_is_standard? and @user_tag_date.empty?
|
39
|
+
# change only author, keeping date-time safe
|
40
|
+
phfile_out.standardize!(author: @author)
|
41
|
+
info_msg = "'#{phfile.basename+phfile.extname}' already standard name. Keeping date-time part unchanged"
|
42
|
+
else #full rename
|
43
|
+
begin
|
44
|
+
tags = MiniExiftool.new(phfile.filename,
|
45
|
+
replace_invalid_chars: true,
|
46
|
+
composite: true,
|
47
|
+
timestamps: DateTime)
|
48
|
+
rescue
|
49
|
+
raise PhTools::Error, 'EXIF tags reading'
|
50
|
+
end
|
51
|
+
if @user_tag_date.empty?
|
52
|
+
# searching for DateTime stamp value in the tags using priority:
|
53
|
+
# EXIF:DateTimeOriginal -> IPTC:DateCreated + IPTC:TimeCreated -> XMP:DateCreated -> EXIF:CreateDate -> XMP:CreateDate -> IPTC:DigitalCreationDate + IPTC:DigitalCreationTime -> FileModifyDate
|
54
|
+
if !tags.date_time_original.nil? && tags.date_time_original.kind_of?(DateTime)
|
55
|
+
# EXIF:DateTimeOriginal or IPTC:DateCreated + IPTC:TimeCreated
|
56
|
+
dto = tags.date_time_original
|
57
|
+
tag_used = "DateTimeOriginal"
|
58
|
+
|
59
|
+
elsif !tags.date_created.nil? && tags.date_created.kind_of?(DateTime)
|
60
|
+
# XMP:DateCreated
|
61
|
+
dto = tags.date_created
|
62
|
+
tag_used = "DateCreated"
|
63
|
+
|
64
|
+
elsif !tags.create_date.nil? && tags.create_date.kind_of?(DateTime)
|
65
|
+
# EXIF:CreateDate or XMP:CreateDate
|
66
|
+
dto = tags.create_date
|
67
|
+
tag_used = "CreateDate"
|
68
|
+
|
69
|
+
elsif !tags.digital_creation_date.nil? &&
|
70
|
+
!tags.digital_creation_time.nil? &&
|
71
|
+
tags.digital_creation_date.kind_of?(String) &&
|
72
|
+
tags.digital_creation_time.kind_of?(String)
|
73
|
+
# IPTC:DigitalCreationDate + IPTC:DigitalCreationTime
|
74
|
+
dcdt = tags.digital_creation_date + " " + tags.digital_creation_time
|
75
|
+
begin
|
76
|
+
s = dcdt.sub(/^(\d+):(\d+):/, '\1-\2-')
|
77
|
+
dto = DateTime.parse(s)
|
78
|
+
rescue ArgumentError
|
79
|
+
dto = PhFile::ZERO_DATE
|
80
|
+
end
|
81
|
+
tag_used = "DigitalCreationDate + DigitalCreationTime"
|
82
|
+
|
83
|
+
else
|
84
|
+
# FileModifyDate
|
85
|
+
dto = File.mtime(phfile.filename).to_datetime
|
86
|
+
tag_used = "FileModifyDate"
|
87
|
+
end
|
88
|
+
|
89
|
+
else
|
90
|
+
# tag is set by the user
|
91
|
+
fail PhTools::Error, "tag #{@user_tag_date} is not found in a file" unless tags[@user_tag_date]
|
92
|
+
fail PhTools::Error, "tag #{@user_tag_date} is not a DateTime type" unless tags[@user_tag_date].kind_of?(DateTime)
|
93
|
+
dto = tags[@user_tag_date] || PhFile::ZERO_DATE
|
94
|
+
tag_used = "#{@user_tag_date}"
|
95
|
+
end
|
96
|
+
phfile_out.standardize!(date_time: dto, author: @author)
|
97
|
+
info_msg = "'#{phfile.basename+phfile.extname}' using tag '#{tag_used}' for date-time"
|
48
98
|
end
|
49
|
-
phfile_out.standardize!(date_time: dto, author: @author)
|
50
99
|
|
51
100
|
when :clean
|
52
101
|
phfile_out.cleanse!
|
53
102
|
|
54
103
|
when :shift_time
|
55
|
-
fail PhTools::Error, '
|
104
|
+
fail PhTools::Error, 'non-standard file name' unless phfile_out.basename_is_standard?
|
56
105
|
phfile_out.standardize!(date_time: phfile_out.date_time + @shift_seconds*(1.0/86400))
|
57
106
|
end
|
58
107
|
|
59
|
-
FileUtils.mv(phfile.filename, phfile_out.filename) unless phfile == phfile_out
|
108
|
+
FileUtils.mv(phfile.filename, phfile_out.filename, verbose: PhTools.debug) unless phfile == phfile_out
|
109
|
+
PhTools.puts_error info_msg unless info_msg.empty?
|
60
110
|
phfile_out
|
61
111
|
|
62
112
|
rescue => e
|
data/lib/phtools/error.rb
CHANGED
@@ -2,9 +2,6 @@
|
|
2
2
|
# encoding: UTF-8
|
3
3
|
# (c) ANB Andrew Bizyaev
|
4
4
|
|
5
|
-
require 'nesty'
|
6
|
-
|
7
|
-
# Foto tools
|
8
5
|
module PhTools
|
9
6
|
@debug = false
|
10
7
|
def self.debug=(val)
|
@@ -15,19 +12,24 @@ module PhTools
|
|
15
12
|
@debug
|
16
13
|
end
|
17
14
|
|
15
|
+
def self.drill_down_error(e, level, prefix)
|
16
|
+
return if e.nil?
|
17
|
+
STDERR.puts "#{prefix}: CAUSE#{level}: #{e.class} - #{e.message}"
|
18
|
+
e.backtrace.each do |b|
|
19
|
+
STDERR.puts "#{prefix}: CAUSE#{level} BACKTRACE: #{b}"
|
20
|
+
end
|
21
|
+
drill_down_error(e.cause, level+1, prefix)
|
22
|
+
end
|
23
|
+
|
18
24
|
def self.puts_error(msg, e = nil)
|
19
25
|
prefix = File.basename($PROGRAM_NAME, '.rb')
|
20
26
|
STDERR.puts "#{prefix}: #{msg}"
|
21
|
-
if @debug
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
e.backtrace.each do |b|
|
26
|
-
STDERR.puts "#{prefix}: BACKTRACE: #{b}"
|
27
|
-
end
|
28
|
-
end
|
27
|
+
drill_down_error(e, 0, prefix) if @debug
|
28
|
+
end
|
29
|
+
|
30
|
+
class Error < StandardError
|
29
31
|
end
|
30
32
|
|
31
|
-
class
|
32
|
-
|
33
|
+
class ExiftoolTagger < Error
|
34
|
+
end
|
33
35
|
end
|
data/lib/phtools/runner.rb
CHANGED
@@ -51,8 +51,8 @@ module PhTools
|
|
51
51
|
filename = line.chomp
|
52
52
|
begin
|
53
53
|
PhFile.validate_file!(filename, @file_type)
|
54
|
-
|
55
|
-
@os.output process_file(
|
54
|
+
phfile = PhFile.new(filename)
|
55
|
+
@os.output process_file(phfile)
|
56
56
|
rescue PhTools::Error => e
|
57
57
|
PhTools.puts_error "ERROR: '#{filename}' - #{e.message}", e
|
58
58
|
end
|
data/lib/phtools/version.rb
CHANGED
data/phtools.gemspec
CHANGED
@@ -47,9 +47,8 @@ Gem::Specification.new do |spec|
|
|
47
47
|
spec.add_development_dependency 'rb-notifu', '>= 0'
|
48
48
|
end
|
49
49
|
|
50
|
-
spec.add_runtime_dependency 'nesty', '~> 1.0'
|
51
50
|
spec.add_runtime_dependency 'docopt', '~> 0.5'
|
52
|
-
|
51
|
+
spec.add_runtime_dependency 'mini_exiftool', '~> 2.8'
|
53
52
|
spec.add_runtime_dependency 'activesupport', '~> 3.2'
|
54
53
|
spec.add_runtime_dependency 'i18n'
|
55
54
|
|
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.7.
|
4
|
+
version: 0.7.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Bizyaev
|
@@ -179,33 +179,33 @@ dependencies:
|
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '0'
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
|
-
name:
|
182
|
+
name: docopt
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
184
184
|
requirements:
|
185
185
|
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: '
|
187
|
+
version: '0.5'
|
188
188
|
type: :runtime
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: '
|
194
|
+
version: '0.5'
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
|
-
name:
|
196
|
+
name: mini_exiftool
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
198
198
|
requirements:
|
199
199
|
- - "~>"
|
200
200
|
- !ruby/object:Gem::Version
|
201
|
-
version: '
|
201
|
+
version: '2.8'
|
202
202
|
type: :runtime
|
203
203
|
prerelease: false
|
204
204
|
version_requirements: !ruby/object:Gem::Requirement
|
205
205
|
requirements:
|
206
206
|
- - "~>"
|
207
207
|
- !ruby/object:Gem::Version
|
208
|
-
version: '
|
208
|
+
version: '2.8'
|
209
209
|
- !ruby/object:Gem::Dependency
|
210
210
|
name: activesupport
|
211
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -276,7 +276,6 @@ files:
|
|
276
276
|
- lib/phtagset.rb
|
277
277
|
- lib/phtools.rb
|
278
278
|
- lib/phtools/error.rb
|
279
|
-
- lib/phtools/mini_exiftool-2.3.0anb.rb
|
280
279
|
- lib/phtools/ph_file.rb
|
281
280
|
- lib/phtools/runner.rb
|
282
281
|
- lib/phtools/utils.rb
|
@@ -1,703 +0,0 @@
|
|
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
|