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 ADDED
@@ -0,0 +1,6 @@
1
+ #!ruby
2
+
3
+ require 'pano'
4
+
5
+ set = Pano::ImageFileSet.new FileUtils.pwd
6
+ set.spit_and_copy_files
data/lib/mask.png ADDED
Binary file
data/lib/mask_last.png ADDED
Binary file
data/lib/pano.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'JSON'
2
+ require 'active_support'
3
+
4
+ TOOL_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
5
+
6
+ module Pano
7
+ autoload :Image, 'pano/image'
8
+ autoload :ImageFile, 'pano/image_file'
9
+ autoload :ImageFileSet, 'pano/image_file_set'
10
+ end
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
+