galleruby 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/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README.md +124 -0
- data/Rakefile +2 -0
- data/bin/gallerubify +163 -0
- data/bin/make_titles +71 -0
- data/config.yml.dist +4 -0
- data/galleruby.gemspec +25 -0
- data/lib/galleruby.rb +6 -0
- data/lib/galleruby/album.rb +246 -0
- data/lib/galleruby/template.rb +108 -0
- data/lib/galleruby/utilities.rb +44 -0
- data/lib/galleruby/version.rb +3 -0
- data/static/close.png +0 -0
- data/static/galleruby.css +73 -0
- data/static/galleruby.js +103 -0
- data/static/jquery-1.5.min.js +16 -0
- data/static/next.png +0 -0
- data/static/previous.png +0 -0
- data/templates/album.haml +62 -0
- data/templates/album.per_date.haml +6 -0
- data/templates/album.per_date.per_image.haml +4 -0
- data/templates/footer.haml +10 -0
- data/templates/header.haml +10 -0
- data/templates/index.haml +18 -0
- data/templates/index.per_year.haml +5 -0
- data/templates/index.per_year.per_album.haml +4 -0
- metadata +105 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
Galleruby
|
2
|
+
=========
|
3
|
+
|
4
|
+
Galleruby is a simple Ruby script to automatically generate a static HTML
|
5
|
+
gallery (series of different albums) for your local photo collection. It's
|
6
|
+
written to publish my personal photos on Amazon S3. I just run this script then
|
7
|
+
s3sync.rb the resulting output to my S3 bucket.
|
8
|
+
|
9
|
+
You can see an example setup of Galleruby running on Amazon S3 here:
|
10
|
+
|
11
|
+
[http://galleruby.devsoft.no](http://galleruby.devsoft.no)
|
12
|
+
|
13
|
+
It's not very configurable, and it makes some assumptions that might not be true
|
14
|
+
for your picture setup. I'm aware of the following ones:
|
15
|
+
|
16
|
+
* All photos need the EXIF DateTime tag set.
|
17
|
+
* Files need to have jpg or jpeg as their extension (case insensitive).
|
18
|
+
* Your albums are sorted into directories in a common source directory, and albums do not have sub-directories.
|
19
|
+
|
20
|
+
If you remove any of these limitations, or find others, please let me know! :-)
|
21
|
+
|
22
|
+
As an example of the layout, this is what
|
23
|
+
[http://galleruby.devsoft.no](http://galleruby.devsoft.no) has locally:
|
24
|
+
~/Pictures/Albums/
|
25
|
+
Hiking at Daley Ranch/
|
26
|
+
IMG_0832.JPG
|
27
|
+
IMG_0855.JPG
|
28
|
+
IMG_0864.JPG
|
29
|
+
IMG_0868.JPG
|
30
|
+
IMG_0877.JPG
|
31
|
+
IMG_0890.JPG
|
32
|
+
Joshua Tree Climbing/
|
33
|
+
IMG_4420.JPG
|
34
|
+
IMG_4425.JPG
|
35
|
+
IMG_4428.JPG
|
36
|
+
IMG_4429.JPG
|
37
|
+
IMG_4437.JPG
|
38
|
+
IMG_4450.JPG
|
39
|
+
IMG_4455.JPG
|
40
|
+
IMG_4458.JPG
|
41
|
+
IMG_4460.JPG
|
42
|
+
IMG_4461.JPG
|
43
|
+
IMG_4467.JPG
|
44
|
+
|
45
|
+
Galleruby isn't very user-friendly, but it gets the job done for me - and maybe
|
46
|
+
it'll get the job done for you too! (or maybe some day grow into something more
|
47
|
+
general, if I get some user feedback)
|
48
|
+
|
49
|
+
Dependencies
|
50
|
+
============
|
51
|
+
|
52
|
+
Galleruby has two external gem dependencies:
|
53
|
+
|
54
|
+
* RMagick
|
55
|
+
* HAML
|
56
|
+
|
57
|
+
You can install these using:
|
58
|
+
gem install rmagick haml
|
59
|
+
|
60
|
+
Using Galleruby
|
61
|
+
===============
|
62
|
+
|
63
|
+
First, Galleruby identifies albums eligible for upload by looking for a
|
64
|
+
.galleruby.yml file in the album directory. This file is expected to initially
|
65
|
+
contain the user-displayed title of the album (e.g. "Birthday party!") and the
|
66
|
+
shortname of the album, which is what the server-side output directory will be
|
67
|
+
called (e.g. "birthdayparty").
|
68
|
+
|
69
|
+
So, to get started, you need to generate these files using the make_titles.rb
|
70
|
+
script. It will suggest defaults you can use by pressing enter - and if you
|
71
|
+
don't want a directory included, press ctrl+D and it'll forever skip this
|
72
|
+
directory when you run make_titles.rb (and not create a .galleruby.yml). To
|
73
|
+
revert this behavior for a directory, delete the .galleruby.skip file.
|
74
|
+
./make_titles.rb ~/Pictures/Albums
|
75
|
+
|
76
|
+
Second, you just need to run gallerubify - but copy config.yml.dist to
|
77
|
+
config.yml and edit it first. Running gallerubify will take some time, as it's
|
78
|
+
generating three resized versions of your files for publishing. You can change
|
79
|
+
what these sizes are by editing config.yml.
|
80
|
+
./gallerubify.rb ~/Pictures/Albums
|
81
|
+
|
82
|
+
Third, you need to put the static directory in your output dir:
|
83
|
+
cp -r static output/
|
84
|
+
|
85
|
+
Example
|
86
|
+
=======
|
87
|
+
|
88
|
+
Here's how you'd get started, assuming the above layout. Notice that defaults
|
89
|
+
were accepted for most values except the title of the birthday party album, and
|
90
|
+
that we skipped publishing "Very private photos" by pressing ctrl-D.
|
91
|
+
|
92
|
+
$ ./make_titles.rb ~/Pictures/Albums
|
93
|
+
> Directory Hiking at Daley Ranch, 6 files
|
94
|
+
What should the title be? [Hiking at Daley Ranch]
|
95
|
+
|
96
|
+
What should the link name be? [hikingatdaleyranch]
|
97
|
+
|
98
|
+
> Directory Joshua Tree Climbing, 11 files
|
99
|
+
What should the title be? [Joshua Tree Climbing]
|
100
|
+
|
101
|
+
What should the link name be? [joshuatreeclimbing]
|
102
|
+
|
103
|
+
> Directory Very Private Album, 1 files
|
104
|
+
What should the title be? [Very Private Album]
|
105
|
+
^D
|
106
|
+
Skipping album
|
107
|
+
|
108
|
+
$ ./gallerubify.rb ~/Pictures/Albums
|
109
|
+
Hiking at Daley Ranch: Processing album
|
110
|
+
Hiking at Daley Ranch: Rendering HTML
|
111
|
+
Joshua Tree Climbing: Processing album
|
112
|
+
Joshua Tree Climbing: Rendering HTML
|
113
|
+
All done! Generating index.
|
114
|
+
|
115
|
+
$ cp -vr static output/
|
116
|
+
static -> output/static
|
117
|
+
static/close.png -> output/static/close.png
|
118
|
+
static/galleruby.css -> output/static/galleruby.css
|
119
|
+
static/galleruby.js -> output/static/galleruby.js
|
120
|
+
static/jquery-1.5.min.js -> output/static/jquery-1.5.min.js
|
121
|
+
static/next.png -> output/static/next.png
|
122
|
+
static/previous.png -> output/static/previous.png
|
123
|
+
|
124
|
+
$ s3sync.rb -vrp output/ my_gallery_bucket:
|
data/Rakefile
ADDED
data/bin/gallerubify
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'yaml'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
require 'galleruby'
|
8
|
+
|
9
|
+
def main
|
10
|
+
# These are the default options
|
11
|
+
default_config = {
|
12
|
+
:title => 'My Gallery',
|
13
|
+
:thumb => [320, 256],
|
14
|
+
:medium => [800, 600],
|
15
|
+
:large => [1280, 1024],
|
16
|
+
:templates => "#{File.dirname(__FILE__)}/../templates",
|
17
|
+
:static => "#{File.dirname(__FILE__)}/../static",
|
18
|
+
:output => 'output',
|
19
|
+
:verbose => false,
|
20
|
+
:force => false
|
21
|
+
}
|
22
|
+
|
23
|
+
config_file = nil
|
24
|
+
config = {}
|
25
|
+
parser = OptionParser.new do |opts|
|
26
|
+
opts.banner = "Usage: gallerubify [OPTION] ... DIR"
|
27
|
+
opts.program_name = 'gallerubify'
|
28
|
+
opts.version = Galleruby::VERSION
|
29
|
+
|
30
|
+
opts.on("-c", "--config FILE", "Read configuration options from FILE.") do |c|
|
31
|
+
config_file = c
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-o", "--output DIRECTORY", "Generates output gallery in DIRECTORY instead of the default 'output'") do |d|
|
35
|
+
config[:output] = d
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("-t", "--title TITLE", "Set gallery title to TITLE") do |t|
|
39
|
+
config[:title] = t
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("--templates DIRECTORY", "Read templates from DIRECTORY.") do |d|
|
43
|
+
config[:templates] = d
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-s", "--static DIRECTORY", "Read static files from DIRECTORY.") do |d|
|
47
|
+
config[:static] = d
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-f", "--[no-]force", "Force regeneration of HTML.") do |f|
|
51
|
+
config[:force] = f
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
55
|
+
config[:verbose] = v
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
59
|
+
puts opts
|
60
|
+
return 0
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on_tail("--version", "Show version") do
|
64
|
+
puts opts.ver
|
65
|
+
return 0
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
begin
|
70
|
+
parser.parse!
|
71
|
+
rescue OptionParser::ParseError
|
72
|
+
parser.warn $!
|
73
|
+
return 1
|
74
|
+
end
|
75
|
+
|
76
|
+
if ARGV.empty? then
|
77
|
+
puts parser
|
78
|
+
return 1
|
79
|
+
end
|
80
|
+
|
81
|
+
directory = ARGV[0]
|
82
|
+
|
83
|
+
if not config_file.nil? then
|
84
|
+
loaded_config = YAML.load(File.read(config_file)) || {}
|
85
|
+
loaded_config.keys.each do |key|
|
86
|
+
loaded_config[key.to_sym] = loaded_config[key]
|
87
|
+
loaded_config.delete(key)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
loaded_config = {}
|
91
|
+
end
|
92
|
+
|
93
|
+
# This sets the right order of precedence of default config, config file and
|
94
|
+
# command-line options. Commandline options are the most important, then
|
95
|
+
# loaded options, and at last it falls back to the default config.
|
96
|
+
config = default_config.merge(loaded_config.merge(config))
|
97
|
+
|
98
|
+
# We make sure to copy all the static resources.
|
99
|
+
puts "Copying static resources to output."
|
100
|
+
Dir["#{config[:static]}/*"].each do |path|
|
101
|
+
output_path = "#{config[:output]}/static/#{File.basename(path)}"
|
102
|
+
FileUtils.cp(path, output_path)
|
103
|
+
puts "cp #{path} #{output_path}" if config[:verbose]
|
104
|
+
end
|
105
|
+
|
106
|
+
puts "Done! Enumerating albums."
|
107
|
+
|
108
|
+
# This dynamically generates a list of all the HAML templates referenced during
|
109
|
+
# a render of album.haml, which we use to figure out if any of them have been
|
110
|
+
# modified since last we generated.
|
111
|
+
deps = Galleruby::TemplateDependencyCalculator.new('album', config)
|
112
|
+
templates_modified = deps.files.collect { |file|
|
113
|
+
path = deps.template_path(file)
|
114
|
+
File.exist?(path) ? File.mtime(path) : Time.now
|
115
|
+
}
|
116
|
+
templates_modified = templates_modified.max
|
117
|
+
|
118
|
+
encountered_links = {}
|
119
|
+
|
120
|
+
# We iterate over each directory inside the directory passed on the commandline,
|
121
|
+
# checking if any of them are considered valid albums (have .galleruby.yml etc,
|
122
|
+
# see Album#valid?) and regenerate thumbnails & HTML if its needed.
|
123
|
+
albums_by_year = Hash.new { |hash, key| hash[key] = [] }
|
124
|
+
Dir.new(directory).each do |album|
|
125
|
+
album = Galleruby::Album.new(directory, album)
|
126
|
+
|
127
|
+
next if not album.valid?
|
128
|
+
|
129
|
+
if encountered_links.has_key?(album.link) then
|
130
|
+
puts "#{album.name}: WARNING! This album has the same link name as '#{encountered_links[album.link]}', skipping."
|
131
|
+
next
|
132
|
+
end
|
133
|
+
|
134
|
+
encountered_links[album.link] = album.name
|
135
|
+
|
136
|
+
if config[:force] or album.needs_updating?(config[:output], templates_modified) then
|
137
|
+
puts "#{album.name}: Processing album"
|
138
|
+
if not album.process(config, config[:output]) then
|
139
|
+
puts "#{album.name}: WARNING! No images to process, skipping"
|
140
|
+
next
|
141
|
+
end
|
142
|
+
|
143
|
+
puts "#{album.name}: Rendering HTML"
|
144
|
+
album.render_to(config, config[:output])
|
145
|
+
else
|
146
|
+
puts "#{album.name}: No update needed, skipping"
|
147
|
+
end
|
148
|
+
|
149
|
+
albums_by_year[album.year] << album
|
150
|
+
end
|
151
|
+
|
152
|
+
puts "All done! Generating index."
|
153
|
+
|
154
|
+
# Finally we generate the index unconditionally, since it's a really cheap
|
155
|
+
# operation. It's possible that we should not do this unless neeed, so that
|
156
|
+
# index.html's mtime will have some value.
|
157
|
+
albums_by_year = albums_by_year.sort_by { |e| e[0] }.reverse.map {|year, albums| {:year => year, :albums => albums.map {|album| album.template_info }.sort_by {|album| album[:first]}.reverse } }
|
158
|
+
Galleruby::Template.new('index', config).render_to("#{config[:output]}/index.html", {:albums_by_year => albums_by_year}, config[:output])
|
159
|
+
|
160
|
+
return 0
|
161
|
+
end
|
162
|
+
|
163
|
+
exit main
|
data/bin/make_titles
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ftools'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
if ARGV.empty? then
|
8
|
+
puts "Syntax: #{$0} <directory>"
|
9
|
+
exit(1);
|
10
|
+
end
|
11
|
+
|
12
|
+
directory = ARGV[0]
|
13
|
+
Dir.new(directory).each { |album|
|
14
|
+
settings_file = "#{directory}/#{album}/.galleruby.yml"
|
15
|
+
skip_file = "#{directory}/#{album}/.galleruby.skip"
|
16
|
+
|
17
|
+
next if album.start_with? '.'
|
18
|
+
next if not File.directory? "#{directory}/#{album}"
|
19
|
+
next if File.exist? skip_file
|
20
|
+
|
21
|
+
info = {}
|
22
|
+
if File.exist? settings_file then
|
23
|
+
info = YAML::load(File.read(settings_file))
|
24
|
+
end
|
25
|
+
|
26
|
+
next if info.has_key? 'link' and info.has_key? 'title'
|
27
|
+
|
28
|
+
puts "> Directory #{album}, #{Dir.entries(directory + "/" + album).length - 2} files"
|
29
|
+
|
30
|
+
if not info.has_key? 'title' then
|
31
|
+
default_title = album.sub(/^\d+-\d+-\d+( - \d+)?/, '').strip
|
32
|
+
puts " What should the title be? [#{default_title}]"
|
33
|
+
title = STDIN.gets
|
34
|
+
if title.nil?
|
35
|
+
FileUtils.touch skip_file
|
36
|
+
puts " Skipping album"
|
37
|
+
next
|
38
|
+
else
|
39
|
+
title = title.chomp
|
40
|
+
end
|
41
|
+
|
42
|
+
if title.empty? then
|
43
|
+
title = default_title
|
44
|
+
end
|
45
|
+
|
46
|
+
info['title'] = title
|
47
|
+
end
|
48
|
+
|
49
|
+
if not info.has_key? 'link' then
|
50
|
+
default_link = info['title'].sub(/^\d+-\d+-\d+( - \d+)?/, '').downcase
|
51
|
+
default_link = default_link.sub('ø', 'oe').sub('å', 'aa').sub('æ', 'ae')
|
52
|
+
default_link.gsub!(/[^a-z0-9_-]/, '')
|
53
|
+
puts " What should the link name be? [#{default_link}]"
|
54
|
+
link = STDIN.gets
|
55
|
+
if link.nil?
|
56
|
+
FileUtils.touch skip_file
|
57
|
+
puts " Skipping album"
|
58
|
+
next
|
59
|
+
else
|
60
|
+
link = link.chomp
|
61
|
+
end
|
62
|
+
|
63
|
+
if link.empty? then
|
64
|
+
link = default_link
|
65
|
+
end
|
66
|
+
|
67
|
+
info['link'] = link
|
68
|
+
end
|
69
|
+
|
70
|
+
File.open(settings_file, 'w') {|file| file.write(YAML.dump(info)) }
|
71
|
+
}
|
data/config.yml.dist
ADDED
data/galleruby.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "galleruby/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "galleruby"
|
7
|
+
s.version = Galleruby::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Jørgen P. Tjernø"]
|
10
|
+
s.email = ["jorgenpt@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/jorgenpt/galleruby"
|
12
|
+
s.summary = %q{A tool to automatically generate a static HTML gallery}
|
13
|
+
s.description = %q{Galleruby allows you to automatically generate a static HTML gallery from a set of directories containing photos - each directory an album.
|
14
|
+
It is indended to allow you to publish this on static file hosts like Amazon S3.}
|
15
|
+
|
16
|
+
s.rubyforge_project = "galleruby"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_dependency 'haml'
|
24
|
+
s.add_dependency 'rmagick'
|
25
|
+
end
|
data/lib/galleruby.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
require 'rmagick'
|
4
|
+
|
5
|
+
module Galleruby
|
6
|
+
TRACK_ALLOCATIONS = false
|
7
|
+
EXIF_DATE_FORMAT = '%Y:%m:%d %H:%M:%S'
|
8
|
+
|
9
|
+
class Album
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# Album representing the passed in name in the passed in directory.
|
13
|
+
def initialize(directory, name)
|
14
|
+
@name = name
|
15
|
+
@path = "#{directory}/#{name}"
|
16
|
+
@settings_file = "#{@path}/.galleruby.yml"
|
17
|
+
@skip_file = "#{@path}/.galleruby.skip"
|
18
|
+
|
19
|
+
|
20
|
+
skiplist_file = "#{@skip_file}list"
|
21
|
+
if File.exist? skiplist_file then
|
22
|
+
@skiplist = YAML::load(File.read(skiplist_file))
|
23
|
+
end
|
24
|
+
@skiplist ||= []
|
25
|
+
|
26
|
+
if valid? then
|
27
|
+
@info = YAML::load(File.read(@settings_file))
|
28
|
+
# YAML serializes DateTime as Time, so we convert back.
|
29
|
+
@info['first'] = @info['first'].to_datetime if @info.has_key?('first')
|
30
|
+
end
|
31
|
+
|
32
|
+
@info ||= {}
|
33
|
+
|
34
|
+
@images_by_date = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# Whether or not the input directory is considered a valid Galleruby album,
|
38
|
+
# i.e. if it has the metadata-file, is not a 'hidden' directory and does not
|
39
|
+
# have a blacklist (.galleruby.skip) file.
|
40
|
+
def valid?
|
41
|
+
return false if @name.start_with? '.'
|
42
|
+
return false if not File.directory? @path
|
43
|
+
return false if not File.exist? @settings_file
|
44
|
+
return false if File.exist? @skip_file
|
45
|
+
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
|
49
|
+
# When the output HTML was last generated.
|
50
|
+
def last_updated output_directory
|
51
|
+
output_file = "#{output_directory}/#{@info['link']}/index.html"
|
52
|
+
if File.exist? output_file then
|
53
|
+
File.mtime output_file
|
54
|
+
else
|
55
|
+
Time.at(0)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Whether or not the album needs to update the generated HTML file and
|
60
|
+
# possibly the resized images, based on when the input images were modified
|
61
|
+
# and when the input HAML templates were last modified.
|
62
|
+
def needs_updating?(output_directory, templates_modified)
|
63
|
+
updated = last_updated output_directory
|
64
|
+
|
65
|
+
# If the template is more recent, we need to update.
|
66
|
+
return true if templates_modified > updated
|
67
|
+
|
68
|
+
# If any of the images are more recent, we need to update.
|
69
|
+
Dir.new(@path).each do |entry|
|
70
|
+
next if not entry.match /\.jpe?g$/i
|
71
|
+
return true if updated < File.mtime("#{@path}/#{entry}")
|
72
|
+
end
|
73
|
+
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
|
77
|
+
# Whether or not the passed in filename needs to be generated from its
|
78
|
+
# input, given when the input was last updated.
|
79
|
+
def file_needs_updating?(output_filename, original_mtime)
|
80
|
+
return true if not File.exist? output_filename
|
81
|
+
return true if File.mtime(output_filename) < original_mtime
|
82
|
+
|
83
|
+
# TODO: Check for 'first' etc in @info.
|
84
|
+
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
# Process generates any resized images for the album as needed, and also
|
89
|
+
# generates metadata about the album that's cached in .galleruby.yml inside
|
90
|
+
# the albums source directory
|
91
|
+
def process(config, output_directory)
|
92
|
+
to_process = []
|
93
|
+
Dir.new(@path).each { |entry|
|
94
|
+
next if not entry.match /\.jpe?g$/i
|
95
|
+
next if @skiplist.include? entry
|
96
|
+
|
97
|
+
to_process << entry
|
98
|
+
}
|
99
|
+
|
100
|
+
return false if to_process.empty?
|
101
|
+
|
102
|
+
output_album = "#{output_directory}/#{@info['link']}"
|
103
|
+
output_thumb = "#{output_album}/small"
|
104
|
+
output_medium = "#{output_album}/medium"
|
105
|
+
output_large = "#{output_album}/large"
|
106
|
+
|
107
|
+
FileUtils.mkdir_p [output_thumb, output_medium, output_large]
|
108
|
+
|
109
|
+
@images_by_date = Hash.new {|hash, key| hash[key] = [] }
|
110
|
+
first_taken, last_taken = nil, nil
|
111
|
+
|
112
|
+
# We go over each (loosely defined) valid image in the directory, and
|
113
|
+
# generate any thumbnail, medium or large versions needed. In addition, we
|
114
|
+
# find the range of the EXIF DateTime header for the album, so that we
|
115
|
+
# can store that as metadata for the album.
|
116
|
+
to_process.each do |entry|
|
117
|
+
filename = "#{@path}/#{entry}"
|
118
|
+
thumb_filename = "#{output_thumb}/#{entry}"
|
119
|
+
medium_filename = "#{output_medium}/#{entry}"
|
120
|
+
large_filename = "#{output_large}/#{entry}"
|
121
|
+
|
122
|
+
image = LazyObject.new { o = Magick::Image.read(filename).first; o.auto_orient!; o }
|
123
|
+
original_mtime = File.mtime filename
|
124
|
+
|
125
|
+
if file_needs_updating?(large_filename, original_mtime) then
|
126
|
+
new_image = image.resize_to_fit(*config[:large])
|
127
|
+
new_image.write(large_filename)
|
128
|
+
image.destroy!
|
129
|
+
|
130
|
+
image = new_image
|
131
|
+
end
|
132
|
+
|
133
|
+
if file_needs_updating?(medium_filename, original_mtime) then
|
134
|
+
medium_image = image.resize_to_fit(*config[:medium])
|
135
|
+
medium_image.write(medium_filename)
|
136
|
+
medium_image.destroy!
|
137
|
+
end
|
138
|
+
|
139
|
+
if file_needs_updating?(thumb_filename, original_mtime) then
|
140
|
+
thumb_image = image.resize_to_fit(*config[:thumb])
|
141
|
+
thumb_image.write(thumb_filename)
|
142
|
+
else
|
143
|
+
thumb_image = Magick::Image.ping(thumb_filename).first
|
144
|
+
end
|
145
|
+
|
146
|
+
taken = thumb_image.get_exif_by_entry('DateTimeOriginal').first[1]
|
147
|
+
taken = DateTime.strptime(taken, EXIF_DATE_FORMAT)
|
148
|
+
|
149
|
+
if last_taken.nil? then
|
150
|
+
last_taken = taken
|
151
|
+
else
|
152
|
+
last_taken = taken if taken > last_taken
|
153
|
+
end
|
154
|
+
|
155
|
+
if first_taken.nil? then
|
156
|
+
first_taken = taken
|
157
|
+
else
|
158
|
+
first_taken = taken if taken < first_taken
|
159
|
+
end
|
160
|
+
|
161
|
+
@images_by_date[taken.strftime('%F')] << {
|
162
|
+
:taken => taken,
|
163
|
+
:data => {
|
164
|
+
:filename => entry,
|
165
|
+
:thumb_width => thumb_image.columns,
|
166
|
+
:thumb_height => thumb_image.rows
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
thumb_image.destroy!
|
171
|
+
if not image.nil? and (image.is_a?(LazyObject) and image.was_initialized?) then
|
172
|
+
image.destroy!
|
173
|
+
end
|
174
|
+
|
175
|
+
if TRACK_ALLOCATIONS and num_allocated > 0 then
|
176
|
+
puts "#{name}: Num allocated: #{num_allocated}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
@info['first'] = first_taken
|
181
|
+
if first_taken.strftime('%F') == last_taken.strftime('%F') then
|
182
|
+
@info['date'] = first_taken.strftime('%e. %b, %Y').lstrip
|
183
|
+
else
|
184
|
+
range_start = first_taken.strftime('%e').lstrip
|
185
|
+
if first_taken.year == last_taken.year then
|
186
|
+
if first_taken.month != last_taken.month then
|
187
|
+
range_start << first_taken.strftime('. %b')
|
188
|
+
end
|
189
|
+
else
|
190
|
+
range_start << first_taken.strftime('. %b, %Y')
|
191
|
+
end
|
192
|
+
|
193
|
+
date_range = "#{range_start} - #{last_taken.strftime('%e. %b, %Y').lstrip}"
|
194
|
+
@info['date'] = date_range
|
195
|
+
end
|
196
|
+
|
197
|
+
# Here we write out the original metadata + the EXIF date range we've
|
198
|
+
# identified.
|
199
|
+
File.open(@settings_file, 'w') { |file| file.write(YAML.dump(@info)) }
|
200
|
+
|
201
|
+
return true
|
202
|
+
end
|
203
|
+
|
204
|
+
# Create a HTML-file for the album.
|
205
|
+
def render_to(config, output_directory)
|
206
|
+
images_by_date = @images_by_date.sort.map do |day, images|
|
207
|
+
{
|
208
|
+
:date => Date.strptime(day),
|
209
|
+
:images => images.sort_by {|image| image[:taken]}.map {|image| image[:data]}
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
output_file = "#{output_directory}/#{@info['link']}/index.html"
|
214
|
+
Template.new('album', config).render_to(output_file, {:title => @info['title'], :images_by_date => images_by_date}, output_directory)
|
215
|
+
end
|
216
|
+
|
217
|
+
# The year that the first photo was taken in.
|
218
|
+
def year
|
219
|
+
@info['first'].strftime('%Y')
|
220
|
+
end
|
221
|
+
|
222
|
+
def link
|
223
|
+
@info['link']
|
224
|
+
end
|
225
|
+
|
226
|
+
# Data needed for generation of the index document.
|
227
|
+
def template_info
|
228
|
+
{:name => @info['title'], :link => @info['link'], :date => @info['date'], :first => @info['first']}
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# If we TRACK_ALLOCATIONS, we keep a count of "currently allocated images", so
|
233
|
+
# that we can identify memory leaks.
|
234
|
+
num_allocated = 0
|
235
|
+
if TRACK_ALLOCATIONS then
|
236
|
+
Magick.trace_proc = Proc.new do |which, description, id, method|
|
237
|
+
if which == :c then
|
238
|
+
num_allocated += 1
|
239
|
+
elsif which == :d then
|
240
|
+
num_allocated -= 1
|
241
|
+
else
|
242
|
+
puts "#{which} #{id} #{description} (from #{method})"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|