Skip to content

Instantly share code, notes, and snippets.

@tzvetkoff
Created August 16, 2012 15:55
Show Gist options
  • Save tzvetkoff/3371287 to your computer and use it in GitHub Desktop.
Save tzvetkoff/3371287 to your computer and use it in GitHub Desktop.
renegator.rb
#!/usr/bin/env ruby
require 'set'
require 'cgi'
require 'exifr'
require 'rmagick'
require 'fileutils'
module HTML
INDENT_TAGS = Set[
:head, :body, :style,
:header, :footer, :article, :aside,
:div,
:dl,
:table, :colgroup, :thead, :tbody, :tfoot, :tr, :td,
:ul, :li,
]
NEW_LINE_TAGS = Set[
:html, :meta, :link,
:head, :body, :style,
:header, :footer, :article, :aside,
:div,
:dl,
:table, :colgroup, :thead, :tbody, :tfoot, :tr, :td,
:ul, :li,
]
XML_TAGS = Set[
:meta, :link,
:img, :hr, :br,
]
class Builder
def initialize(&block)
@content = "<!DOCTYPE html>\n"
instance_eval(&block) if block_given?
end
def method_missing(meth, *args, &block)
attributes = if args.first.is_a?(Hash)
' ' + attribute_string_from_hash(args.shift)
elsif args.last.is_a?(Hash)
' ' + attribute_string_from_hash(args.pop)
else
''
end
new_line = NEW_LINE_TAGS.include?(meth) && "\n" || ''
if block_given?
content, @content = @content, ''
instance_eval(&block)
content, @content = @content, content
else
content = args.join("\n")
end
if content.empty? && XML_TAGS.include?(meth)
@content << "<#{meth}#{attributes} />#{new_line}"
else
if content.empty?
new_line = ''
end
if INDENT_TAGS.include?(meth)
content = indent(content, 1)
end
@content << "<#{meth}#{attributes}>#{new_line}#{content}#{new_line}</#{meth}>\n"
end
@content
end
def to_s
@content.split("\n").map(&:rstrip).reject { |line| line.empty? }.join("\n")
end
private
def escape_html_special_chars(str)
CGI.escape_html(str.to_s)
end
def attribute_string_from_hash(hash)
hash.map { |key, value| "#{escape_html_special_chars(key)}=\"#{escape_html_special_chars(value)}\"" }.join(' ')
end
def indent(str, level, with = ' ')
str.split("\n").map { |line| with * level + line }.join("\n")
end
end
end
sorted = Hash.new { |hash, key| hash[key] = [] }
Dir['Library/*/**/*.*'].each { |filename|
key = nil
begin
exif = EXIFR::JPEG.new(filename)
key = exif.date_time && exif.date_time.strftime('%Y-%m') || 'Sometime'
date = exif.date_time && exif.date_time.strftime('%A, %e %B %Y, %H:%M') || 'Unknown'
gps = exif.gps && "#{exif.gps[:latitude]},#{exif.gps[:longitude]}" || nil
gps_short = exif.gps && "#{exif.gps[:latitude].round(6)}, #{exif.gps[:longitude].round(6)}" || 'Unknown'
camera = (exif.make || exif.model) && "#{exif.make} #{exif.model}" || 'Unknown'
rescue Exception => e
# not an image - ignore
else
sorted[key] << {
filename: filename,
date: date,
gps: gps,
gps_short: gps_short,
camera: camera,
}
# create thumbnails too
destination = filename.dup
destination['Library/'] = 'Library/.Thumbnails/'
FileUtils.mkdir_p(File.dirname(destination))
image = Magick::Image.read(filename).first
image.crop_resized!(256, 256, Magick::CenterGravity)
image.write(destination)
end
}
sorted = Hash[sorted.sort]
index = 0
doc = HTML::Builder.new {
html {
head {
meta :'http-equiv' => 'Content-Type', content: 'text/html; charset=UTF-8'
title 'hi! :-)'
link rel: 'stylesheet', type: 'text/css', media: 'screen, projection', href: 'Gallery.css'
}
body {
h1 'hi! :-)'
sorted.each { |date, files|
date_humanized = if date =~ /^\d{4}-\d{2}$/
Time.new(*date.split('-').map(&:to_i)).strftime('%b %Y')
else
date
end
h2 date_humanized
ul(class: 'lb-album') {
files.each { |hash|
filename = hash[:filename].split('/')[1..9999].join('/')
li {
a(href: "#image-#{index}") {
img src: "./.Thumbnails/#{filename}", alt: "#{File.basename(filename)}"
}
table(cellspacing: 0, cellpadding: 0) {
colgroup {
col width: 50
col
}
tbody {
tr {
th 'Date:'
td hash[:date]
}
tr {
th 'GPS:'
if hash[:gps]
td {
a hash[:gps_short], href: "http://maps.google.com/?q=#{hash[:gps]}", target: 'blank'
}
else
td hash[:gps_short]
end
}
tr {
th 'Camera:'
td hash[:camera]
}
}
}
div(class: 'lb-overlay', id: "image-#{index}") {
a(href: "./#{filename}") {
img src: "./#{filename}", alt: "#{File.basename(filename)}"
}
a 'Close', href: '#close', class: 'close'
}
}
index += 1
}
}
}
}
}
}
File.write('Library/Gallery.html', doc)
css = <<-CSS
/* page styles */
* {
margin: 0;
padding: 0;
}
body {
padding: 20px;
background: #ffffff;
color: #333333;
font: 12px/15px Helvetica, sans-serif;
}
img {
border: 0 none;
}
h1 {
font-size: 20px;
line-height: 30px;
margin: 0 0 10px 0;
}
h2 {
font-size: 16px;
}
h2 + ul {
overflow: hidden;
margin: 10px 0 10px 0;
list-style: none outside;
}
h2 + ul > li {
position: relative;
float: left;
margin: 0 10px 10px 0;
padding: 10px;
background: rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
h2 + ul > li > a {
position: relative;
float: left;
}
h2 + ul > li > a > span {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
padding: 10px;
color: rgba(27, 54, 81, 0.8);
text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.6);
font-size: 15px;
text-decoration: none;
opacity: 0;
background: rgba(222, 222, 222, 0.9);
-webkit-transition: opacity 0.3s linear;
transition: opacity 0.3s linear;
}
h2 + ul > li > a:hover > span {
opacity: 1;
}
h2 + ul > li > table {
margin: 10px 0 0 0;
width: 100%;
clear: left;
float: left;
}
h2 + ul > li > table th,
h2 + ul > li > table th {
padding: 0 5px;
}
h2 + ul > li > table th {
text-align: right;
}
h2 + ul > li > table td {
text-align: left;
}
h2 + ul > li > table td a {
color: #990000;
text-decoration: none;
}
h2 + ul > li > table td a:hover {
text-decoration: underline;
}
/* lightbox "overlay" */
.lb-overlay {
width: 0px;
height: 0px;
position: fixed;
overflow: hidden;
left: 0px;
top: 0px;
padding: 0px;
z-index: 99;
text-align: center;
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(255,255,255,0.56)), color-stop(100%,rgba(241,210,194,1)));
background: -webkit-radial-gradient(center, ellipse cover, rgba(255,255,255,0.56) 0%,rgba(241,210,194,1) 100%);
}
.lb-overlay > a.close {
background: rgba(27,54,81,0.8);
z-index: 1001;
color: #fff;
position: absolute;
top: 30px;
left: 50%;
font-size: 15px;
line-height: 40px;
text-align: center;
text-decoration: none;
width: 100px;
height: 40px;
overflow: hidden;
margin-left: -50px;
opacity: 0;
-webkit-box-shadow: 0px 1px 2px rgba(0,0,0,0.3);
box-shadow: 0px 1px 2px rgba(0,0,0,0.3);
-webkit-transition: opacity 0.3s linear 1.2s;
transition: opacity 0.3s linear 1.2s;
}
.lb-overlay img {
max-height: 100%;
position: relative;
-webkit-box-shadow: 1px 1px 4px rgba(0,0,0,0.3);
box-shadow: 0px 2px 7px rgba(0,0,0,0.2);
}
.lb-overlay:target {
width: auto;
height: auto;
bottom: 0px;
right: 0px;
padding: 50px 50px 20px 50px;
}
.lb-overlay:target img {
-webkit-animation: scaleDown 1.2s ease-in-out;
animation: scaleDown 1.2s ease-in-out;
}
.lb-overlay:target > a.close {
opacity: 1;
}
/* animations */
@-webkit-keyframes scaleDown {
0% { -webkit-transform: scale(10, 10); opacity: 0; }
100% { -webkit-transform: scale(1, 1); opacity: 1; }
}
@keyframes scaleDown {
0% { transform: scale(10, 10); opacity: 0; }
100% { transform: scale(1, 1); opacity: 1; }
}
x:-o-prefocus, .lb-overlay img {
height: 100%;
}
CSS
File.write('Library/Gallery.css', css)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment