pano 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+