pano 0.0.1
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.
- data/bin/pntool +6 -0
- data/lib/mask.png +0 -0
- data/lib/mask_last.png +0 -0
- data/lib/pano.rb +10 -0
- data/lib/pano/image.rb +58 -0
- data/lib/pano/image_file.rb +55 -0
- data/lib/pano/image_file_set.rb +99 -0
- metadata +71 -0
data/bin/pntool
ADDED
data/lib/mask.png
ADDED
Binary file
|
data/lib/mask_last.png
ADDED
Binary file
|
data/lib/pano.rb
ADDED
data/lib/pano/image.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Pano
|
4
|
+
module Image
|
5
|
+
|
6
|
+
def read_info file_path
|
7
|
+
json = `exiftool -d "%Y-%m-%d %H:%M:%S %z" -j '#{file_path}' 2>&1`
|
8
|
+
|
9
|
+
return {:error => "File not found"} if json =~ /^File not found/i
|
10
|
+
|
11
|
+
data = JSON.parse(json)[0]
|
12
|
+
data[:width] = data["ImageWidth"]
|
13
|
+
data[:height] = data["ImageHeight"]
|
14
|
+
data[:size] = data["ImageSize"]
|
15
|
+
data[:file_size] = data["FileSize"]
|
16
|
+
data[:camera_model] = data["Model"]
|
17
|
+
data[:keywords] = data["Keywords"] || ""
|
18
|
+
data[:owner_name] = data["OwnerName"] # camera owner.
|
19
|
+
data[:iptc_digest] = data["IPTCDigest"] # thinking this can be used to detect photographers if OwnerName undefined
|
20
|
+
data[:created_at] = try_parse_date data, "CreateDate"
|
21
|
+
data[:modified_at] = try_parse_date data, "ModifyDate"
|
22
|
+
data[:file_modified_at] = try_parse_date data, "FileModifyDate"
|
23
|
+
data[:orientation] = data[:width] > data[:height] ? 'h' : 'v'
|
24
|
+
data[:fov] = data["FOV"].to_f
|
25
|
+
|
26
|
+
if data["PhotoshopQuality"].present?
|
27
|
+
quality = data["PhotoshopQuality"]
|
28
|
+
data[:quality] = quality <= 9 ? quality * 10 : quality
|
29
|
+
end
|
30
|
+
|
31
|
+
data
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def try_parse_date data, key
|
37
|
+
s = data[key]
|
38
|
+
s.blank? ? nil : Time.parse(s)
|
39
|
+
end
|
40
|
+
|
41
|
+
def normalize_thumb_options options={}
|
42
|
+
options.reverse_merge(default_thumb_options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def suffix_based_on_options opts
|
46
|
+
suffix = "#{opts[:size]}q#{opts[:quality]}"
|
47
|
+
suffix << 'w' if opts[:watermarked]
|
48
|
+
suffix << 'f' if opts[:force]
|
49
|
+
suffix << 'p' if opts[:protected]
|
50
|
+
suffix << "cbt#{opts[:crop_before_to]}" if opts[:crop_before_to]
|
51
|
+
suffix << 'lo' if opts[:flop]
|
52
|
+
suffix << 'li' if opts[:flip]
|
53
|
+
suffix << 'dne' if opts[:do_not_enlarge]
|
54
|
+
suffix << 't' if opts[:top]
|
55
|
+
suffix
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Pano
|
2
|
+
class ImageFile
|
3
|
+
include Image
|
4
|
+
|
5
|
+
attr_reader :jpg_path, :raw_path, :info
|
6
|
+
|
7
|
+
def initialize path
|
8
|
+
if path =~ /\.jpg$/i
|
9
|
+
@jpg_path = path
|
10
|
+
@raw_path = path.sub(/\.jpg$/i, ".CR2")
|
11
|
+
else
|
12
|
+
@raw_path = path
|
13
|
+
@jpg_path = path.sub(/\.cr2$/i, ".JPG")
|
14
|
+
end
|
15
|
+
|
16
|
+
if File.exist?(@jpg_path)
|
17
|
+
@info = read_info(@jpg_path)
|
18
|
+
elsif File.exist?(raw_path)
|
19
|
+
@info = read_info(@raw_path)
|
20
|
+
else
|
21
|
+
raise "File not found '#{path}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def for_pano?
|
26
|
+
@for_pano ||= @info[:fov] > 95 && @info["SelfTimer"] == "2 s" && @info["Orientation"] == "Rotate 270 CW"
|
27
|
+
end
|
28
|
+
|
29
|
+
def bracketed?
|
30
|
+
@bracketed ||= @info["BracketMode"] == "AEB" && @info["BracketShotNumber"] == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def bracketing_number
|
34
|
+
b = @info["BracketValue"]
|
35
|
+
b == 0 ? 0 : b < 0 ? 1 : 2
|
36
|
+
end
|
37
|
+
|
38
|
+
def list_info
|
39
|
+
@info.each_pair do |k, v|
|
40
|
+
puts "#{k} - #{v}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def created_at
|
45
|
+
@created_at ||= @info[:created_at]
|
46
|
+
end
|
47
|
+
|
48
|
+
def copy_to dest_dir
|
49
|
+
FileUtils.mkpath dest_dir;
|
50
|
+
FileUtils.cp(@raw_path, dest_dir) if File.exist?(@raw_path)
|
51
|
+
FileUtils.cp(@jpg_path, dest_dir) if File.exist?(@jpg_path)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Pano
|
2
|
+
|
3
|
+
class ImageFileSet
|
4
|
+
|
5
|
+
attr_reader :base_path, :files
|
6
|
+
|
7
|
+
def initialize dir_path
|
8
|
+
@base_path = dir_path
|
9
|
+
paths = Dir[File.join(@base_path,"*.JPG")]
|
10
|
+
@files = paths.map {|path| ImageFile.new path }
|
11
|
+
@files.sort_by &:created_at
|
12
|
+
end
|
13
|
+
|
14
|
+
def split_images
|
15
|
+
pano = []
|
16
|
+
panos = []
|
17
|
+
misc = []
|
18
|
+
i = 0
|
19
|
+
while i < @files.length
|
20
|
+
file = @files[i]
|
21
|
+
if file.for_pano?
|
22
|
+
if pano.length < 13 * 3
|
23
|
+
pano << file
|
24
|
+
else
|
25
|
+
last = pano.last
|
26
|
+
if (file.created_at - last.created_at) > 1.5.minutes
|
27
|
+
panos << pano
|
28
|
+
pano = []
|
29
|
+
end
|
30
|
+
pano << file
|
31
|
+
end
|
32
|
+
else
|
33
|
+
if pano.present?
|
34
|
+
panos << pano
|
35
|
+
pano = []
|
36
|
+
end
|
37
|
+
misc << file
|
38
|
+
end
|
39
|
+
i+=1
|
40
|
+
end
|
41
|
+
if pano.present?
|
42
|
+
panos << pano
|
43
|
+
end
|
44
|
+
[panos, misc]
|
45
|
+
end
|
46
|
+
|
47
|
+
def spit_and_copy_files
|
48
|
+
puts "analysing files..."
|
49
|
+
panos, misc = split_images
|
50
|
+
panos.each_with_index do |pano, index|
|
51
|
+
pano_dir = File.join(@base_path, "pano.#{index}")
|
52
|
+
puts "coping to #{pano_dir}"
|
53
|
+
pano.each do |file|
|
54
|
+
file.copy_to pano_dir
|
55
|
+
end
|
56
|
+
i = 0
|
57
|
+
pano.each_slice(3) do |files|
|
58
|
+
i+=1
|
59
|
+
fused = enfuse(File.join(pano_dir, "fused"), "%02d" % i, files)
|
60
|
+
if (1..6).include? i
|
61
|
+
system "convert #{fused} #{TOOL_ROOT}/lib/mask.png \
|
62
|
+
+matte -compose CopyOpacity -composite \
|
63
|
+
#{fused}"
|
64
|
+
elsif i == 13
|
65
|
+
system "convert #{fused} #{TOOL_ROOT}/lib/mask_last.png \
|
66
|
+
+matte -compose CopyOpacity -composite \
|
67
|
+
#{fused}"
|
68
|
+
end
|
69
|
+
system "mogrift -rotate -90 #{fused}"
|
70
|
+
system "exiftool -overwrite_original -TagsFromFile #{files.first.jpg_path} #{fused}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
misc_dir = File.join(@base_path, "misc")
|
75
|
+
puts "coping to #{misc_dir}"
|
76
|
+
misc.each do |file|
|
77
|
+
file.copy_to misc_dir
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def enfuse dir, name, files = []
|
83
|
+
return if files.blank?
|
84
|
+
FileUtils.mkpath dir
|
85
|
+
contrast_weight = 0.6
|
86
|
+
entropy_weight = 0.4
|
87
|
+
exposure_weight = 0.5
|
88
|
+
saturation_weight = 0.2
|
89
|
+
target = File.join(dir, name + ".tif")
|
90
|
+
input = files.map {|file| file.jpg_path }.join(" ")
|
91
|
+
options = "--contrast-weight=#{contrast_weight} --entropy-weight=#{entropy_weight} --exposure-weight=#{exposure_weight} --saturation-weight=#{saturation_weight} --compression=LZW"
|
92
|
+
system "enfuse #{options} -o #{target} #{input}"
|
93
|
+
|
94
|
+
target
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pano
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yury Korolev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-28 00:00:00 +03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.3.5
|
24
|
+
version:
|
25
|
+
description: Split and fuse images for pano
|
26
|
+
email:
|
27
|
+
- yury.korolev@gmail.com
|
28
|
+
executables:
|
29
|
+
- pntool
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files: []
|
33
|
+
|
34
|
+
files:
|
35
|
+
- lib/pano/image.rb
|
36
|
+
- lib/pano/image_file.rb
|
37
|
+
- lib/pano/image_file_set.rb
|
38
|
+
- lib/pano.rb
|
39
|
+
- bin/pntool
|
40
|
+
- lib/mask.png
|
41
|
+
- lib/mask_last.png
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://anjlab.com
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.5
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.5
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: "\"Just prepare photos for pano\""
|
70
|
+
test_files: []
|
71
|
+
|