phtools 0.7.1 → 0.7.7
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/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
|