pax_global_header 0000666 0000000 0000000 00000000064 12153010610 0014477 g ustar 00root root 0000000 0000000 52 comment=3e6551cdc7c1d6a06bac5e49039fdc0189911b0b
photofloat-0~20120917+dfsg/ 0000775 0000000 0000000 00000000000 12153010610 0015322 5 ustar 00root root 0000000 0000000 photofloat-0~20120917+dfsg/scanner/ 0000775 0000000 0000000 00000000000 12153010610 0016753 5 ustar 00root root 0000000 0000000 photofloat-0~20120917+dfsg/scanner/.gitignore 0000664 0000000 0000000 00000000037 12153010610 0020743 0 ustar 00root root 0000000 0000000 upload.sh
*.pyc
cache/*
test/*
photofloat-0~20120917+dfsg/scanner/CachePath.py 0000664 0000000 0000000 00000002637 12153010610 0021155 0 ustar 00root root 0000000 0000000 import os.path
from datetime import datetime
def message(category, text):
if message.level <= 0:
sep = " "
else:
sep = "--"
print "%s %s%s[%s]%s%s" % (datetime.now().isoformat(), max(0, message.level) * " |", sep, category, max(1, (14 - len(category))) * " ", text)
message.level = -1
def next_level():
message.level += 1
def back_level():
message.level -= 1
def set_cache_path_base(base):
trim_base.base = base
def untrim_base(path):
return os.path.join(trim_base.base, path)
def trim_base_custom(path, base):
if path.startswith(base):
path = path[len(base):]
if path.startswith('/'):
path = path[1:]
return path
def trim_base(path):
return trim_base_custom(path, trim_base.base)
def cache_base(path):
path = trim_base(path).replace('/', '-').replace(' ', '_').replace('(', '').replace('&', '').replace(',', '').replace(')', '').replace('#', '').replace('[', '').replace(']', '').replace('"', '').replace("'", '').replace('_-_', '-').lower()
while path.find("--") != -1:
path = path.replace("--", "-")
while path.find("__") != -1:
path = path.replace("__", "_")
if len(path) == 0:
path = "root"
return path
def json_cache(path):
return cache_base(path) + ".json"
def image_cache(path, size, square=False):
if square:
suffix = str(size) + "s"
else:
suffix = str(size)
return cache_base(path) + "_" + suffix + ".jpg"
def file_mtime(path):
return datetime.fromtimestamp(int(os.path.getmtime(path)))
photofloat-0~20120917+dfsg/scanner/PhotoAlbum.py 0000664 0000000 0000000 00000032306 12153010610 0021403 0 ustar 00root root 0000000 0000000 from CachePath import *
from datetime import datetime
import json
import os
import os.path
from PIL import Image
from PIL.ExifTags import TAGS
import gc
class Album(object):
def __init__(self, path):
self._path = trim_base(path)
self._photos = list()
self._albums = list()
self._photos_sorted = True
self._albums_sorted = True
@property
def photos(self):
return self._photos
@property
def albums(self):
return self._albums
@property
def path(self):
return self._path
def __str__(self):
return self.path
@property
def cache_path(self):
return json_cache(self.path)
@property
def date(self):
self._sort()
if len(self._photos) == 0 and len(self._albums) == 0:
return datetime(1900, 1, 1)
elif len(self._photos) == 0:
return self._albums[-1].date
elif len(self._albums) == 0:
return self._photos[-1].date
return max(self._photos[-1].date, self._albums[-1].date)
def __cmp__(self, other):
return cmp(self.date, other.date)
def add_photo(self, photo):
self._photos.append(photo)
self._photos_sorted = False
def add_album(self, album):
self._albums.append(album)
self._albums_sorted = False
def _sort(self):
if not self._photos_sorted:
self._photos.sort()
self._photos_sorted = True
if not self._albums_sorted:
self._albums.sort()
self._albums_sorted = True
@property
def empty(self):
if len(self._photos) != 0:
return False
if len(self._albums) == 0:
return True
for album in self._albums:
if not album.empty:
return False
return True
def cache(self, base_dir):
self._sort()
fp = open(os.path.join(base_dir, self.cache_path), 'w')
json.dump(self, fp, cls=PhotoAlbumEncoder)
fp.close()
@staticmethod
def from_cache(path):
fp = open(path, "r")
dictionary = json.load(fp)
fp.close()
return Album.from_dict(dictionary)
@staticmethod
def from_dict(dictionary, cripple=True):
album = Album(dictionary["path"])
for photo in dictionary["photos"]:
album.add_photo(Photo.from_dict(photo, untrim_base(album.path)))
if not cripple:
for subalbum in dictionary["albums"]:
album.add_album(Album.from_dict(subalbum), cripple)
album._sort()
return album
def to_dict(self, cripple=True):
self._sort()
subalbums = []
if cripple:
for sub in self._albums:
if not sub.empty:
subalbums.append({ "path": trim_base_custom(sub.path, self._path), "date": sub.date })
else:
for sub in self._albums:
if not sub.empty:
subalbums.append(sub)
return { "path": self.path, "date": self.date, "albums": subalbums, "photos": self._photos }
def photo_from_path(self, path):
for photo in self._photos:
if trim_base(path) == photo._path:
return photo
return None
class Photo(object):
thumb_sizes = [ (75, True), (150, True), (640, False), (800, False), (1024, False) ]
def __init__(self, path, thumb_path=None, attributes=None):
self._path = trim_base(path)
self.is_valid = True
try:
mtime = file_mtime(path)
except KeyboardInterrupt:
raise
except:
self.is_valid = False
return
if attributes is not None and attributes["dateTimeFile"] >= mtime:
self._attributes = attributes
return
self._attributes = {}
self._attributes["dateTimeFile"] = mtime
try:
image = Image.open(path)
except KeyboardInterrupt:
raise
except:
self.is_valid = False
return
self._metadata(image)
self._thumbnails(image, thumb_path, path)
def _metadata(self, image):
self._attributes["size"] = image.size
self._orientation = 1
try:
info = image._getexif()
except KeyboardInterrupt:
raise
except:
return
if not info:
return
exif = {}
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
if isinstance(value, str):
value = value.strip().partition("\x00")[0]
if isinstance(decoded, str) and decoded.startswith("DateTime"):
try:
value = datetime.strptime(value, '%Y:%m:%d %H:%M:%S')
except KeyboardInterrupt:
raise
except:
continue
exif[decoded] = value
if "Orientation" in exif:
self._orientation = exif["Orientation"];
if self._orientation in range(5, 9):
self._attributes["size"] = (self._attributes["size"][1], self._attributes["size"][0])
if self._orientation - 1 < len(self._metadata.orientation_list):
self._attributes["orientation"] = self._metadata.orientation_list[self._orientation - 1]
if "Make" in exif:
self._attributes["make"] = exif["Make"]
if "Model" in exif:
self._attributes["model"] = exif["Model"]
if "ApertureValue" in exif:
self._attributes["aperture"] = exif["ApertureValue"]
elif "FNumber" in exif:
self._attributes["aperture"] = exif["FNumber"]
if "FocalLength" in exif:
self._attributes["focalLength"] = exif["FocalLength"]
if "ISOSpeedRatings" in exif:
self._attributes["iso"] = exif["ISOSpeedRatings"]
if "ISO" in exif:
self._attributes["iso"] = exif["ISO"]
if "PhotographicSensitivity" in exif:
self._attributes["iso"] = exif["PhotographicSensitivity"]
if "ExposureTime" in exif:
self._attributes["exposureTime"] = exif["ExposureTime"]
if "Flash" in exif and exif["Flash"] in self._metadata.flash_dictionary:
try:
self._attributes["flash"] = self._metadata.flash_dictionary[exif["Flash"]]
except KeyboardInterrupt:
raise
except:
pass
if "LightSource" in exif and exif["LightSource"] in self._metadata.light_source_dictionary:
try:
self._attributes["lightSource"] = self._metadata.light_source_dictionary[exif["LightSource"]]
except KeyboardInterrupt:
raise
except:
pass
if "ExposureProgram" in exif and exif["ExposureProgram"] < len(self._metadata.exposure_list):
self._attributes["exposureProgram"] = self._metadata.exposure_list[exif["ExposureProgram"]]
if "SpectralSensitivity" in exif:
self._attributes["spectralSensitivity"] = exif["SpectralSensitivity"]
if "MeteringMode" in exif and exif["MeteringMode"] < len(self._metadata.metering_list):
self._attributes["meteringMode"] = self._metadata.metering_list[exif["MeteringMode"]]
if "SensingMethod" in exif and exif["SensingMethod"] < len(self._metadata.sensing_method_list):
self._attributes["sensingMethod"] = self._metadata.sensing_method_list[exif["SensingMethod"]]
if "SceneCaptureType" in exif and exif["SceneCaptureType"] < len(self._metadata.scene_capture_type_list):
self._attributes["sceneCaptureType"] = self._metadata.scene_capture_type_list[exif["SceneCaptureType"]]
if "SubjectDistanceRange" in exif and exif["SubjectDistanceRange"] < len(self._metadata.subject_distance_range_list):
self._attributes["subjectDistanceRange"] = self._metadata.subject_distance_range_list[exif["SubjectDistanceRange"]]
if "ExposureCompensation" in exif:
self._attributes["exposureCompensation"] = exif["ExposureCompensation"]
if "ExposureBiasValue" in exif:
self._attributes["exposureCompensation"] = exif["ExposureBiasValue"]
if "DateTimeOriginal" in exif:
self._attributes["dateTimeOriginal"] = exif["DateTimeOriginal"]
if "DateTime" in exif:
self._attributes["dateTime"] = exif["DateTime"]
_metadata.flash_dictionary = {0x0: "No Flash", 0x1: "Fired",0x5: "Fired, Return not detected",0x7: "Fired, Return detected",0x8: "On, Did not fire",0x9: "On, Fired",0xd: "On, Return not detected",0xf: "On, Return detected",0x10: "Off, Did not fire",0x14: "Off, Did not fire, Return not detected",0x18: "Auto, Did not fire",0x19: "Auto, Fired",0x1d: "Auto, Fired, Return not detected",0x1f: "Auto, Fired, Return detected",0x20: "No flash function",0x30: "Off, No flash function",0x41: "Fired, Red-eye reduction",0x45: "Fired, Red-eye reduction, Return not detected",0x47: "Fired, Red-eye reduction, Return detected",0x49: "On, Red-eye reduction",0x4d: "On, Red-eye reduction, Return not detected",0x4f: "On, Red-eye reduction, Return detected",0x50: "Off, Red-eye reduction",0x58: "Auto, Did not fire, Red-eye reduction",0x59: "Auto, Fired, Red-eye reduction",0x5d: "Auto, Fired, Red-eye reduction, Return not detected",0x5f: "Auto, Fired, Red-eye reduction, Return detected"}
_metadata.light_source_dictionary = {0: "Unknown", 1: "Daylight", 2: "Fluorescent", 3: "Tungsten (incandescent light)", 4: "Flash", 9: "Fine weather", 10: "Cloudy weather", 11: "Shade", 12: "Daylight fluorescent (D 5700 - 7100K)", 13: "Day white fluorescent (N 4600 - 5400K)", 14: "Cool white fluorescent (W 3900 - 4500K)", 15: "White fluorescent (WW 3200 - 3700K)", 17: "Standard light A", 18: "Standard light B", 19: "Standard light C", 20: "D55", 21: "D65", 22: "D75", 23: "D50", 24: "ISO studio tungsten"}
_metadata.metering_list = ["Unknown", "Average", "Center-weighted average", "Spot", "Multi-spot", "Multi-segment", "Partial"]
_metadata.exposure_list = ["Not Defined", "Manual", "Program AE", "Aperture-priority AE", "Shutter speed priority AE", "Creative (Slow speed)", "Action (High speed)", "Portrait", "Landscape", "Bulb"]
_metadata.orientation_list = ["Horizontal (normal)", "Mirror horizontal", "Rotate 180", "Mirror vertical", "Mirror horizontal and rotate 270 CW", "Rotate 90 CW", "Mirror horizontal and rotate 90 CW", "Rotate 270 CW"]
_metadata.sensing_method_list = ["Not defined", "One-chip color area sensor", "Two-chip color area sensor", "Three-chip color area sensor", "Color sequential area sensor", "Trilinear sensor", "Color sequential linear sensor"]
_metadata.scene_capture_type_list = ["Standard", "Landscape", "Portrait", "Night scene"]
_metadata.subject_distance_range_list = ["Unknown", "Macro", "Close view", "Distant view"]
def _thumbnail(self, image, thumb_path, original_path, size, square=False):
thumb_path = os.path.join(thumb_path, image_cache(self._path, size, square))
info_string = "%s -> %spx" % (os.path.basename(original_path), str(size))
if square:
info_string += ", square"
message("thumbing", info_string)
if os.path.exists(thumb_path) and file_mtime(thumb_path) >= self._attributes["dateTimeFile"]:
return
gc.collect()
try:
image = image.copy()
except KeyboardInterrupt:
raise
except:
try:
image = image.copy() # we try again to work around PIL bug
except KeyboardInterrupt:
raise
except:
message("corrupt image", os.path.basename(original_path))
return
if square:
if image.size[0] > image.size[1]:
left = (image.size[0] - image.size[1]) / 2
top = 0
right = image.size[0] - ((image.size[0] - image.size[1]) / 2)
bottom = image.size[1]
else:
left = 0
top = (image.size[1] - image.size[0]) / 2
right = image.size[0]
bottom = image.size[1] - ((image.size[1] - image.size[0]) / 2)
image = image.crop((left, top, right, bottom))
gc.collect()
image.thumbnail((size, size), Image.ANTIALIAS)
try:
image.save(thumb_path, "JPEG", quality=88)
except KeyboardInterrupt:
os.unlink(thumb_path)
raise
except:
message("save failure", os.path.basename(thumb_path))
os.unlink(thumb_path)
def _thumbnails(self, image, thumb_path, original_path):
mirror = image
if self._orientation == 2:
# Vertical Mirror
mirror = image.transpose(Image.FLIP_LEFT_RIGHT)
elif self._orientation == 3:
# Rotation 180
mirror = image.transpose(Image.ROTATE_180)
elif self._orientation == 4:
# Horizontal Mirror
mirror = image.transpose(Image.FLIP_TOP_BOTTOM)
elif self._orientation == 5:
# Horizontal Mirror + Rotation 270
mirror = image.transpose(Image.FLIP_TOP_BOTTOM).transpose(Image.ROTATE_270)
elif self._orientation == 6:
# Rotation 270
mirror = image.transpose(Image.ROTATE_270)
elif self._orientation == 7:
# Vertical Mirror + Rotation 270
mirror = image.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270)
elif self._orientation == 8:
# Rotation 90
mirror = image.transpose(Image.ROTATE_90)
for size in Photo.thumb_sizes:
self._thumbnail(mirror, thumb_path, original_path, size[0], size[1])
@property
def name(self):
return os.path.basename(self._path)
def __str__(self):
return self.name
@property
def path(self):
return self._path
@property
def image_caches(self):
return [image_cache(self._path, size[0], size[1]) for size in Photo.thumb_sizes]
@property
def date(self):
if not self.is_valid:
return datetime(1900, 1, 1)
if "dateTimeOriginal" in self._attributes:
return self._attributes["dateTimeOriginal"]
elif "dateTime" in self._attributes:
return self._attributes["dateTime"]
else:
return self._attributes["dateTimeFile"]
def __cmp__(self, other):
date_compare = cmp(self.date, other.date)
if date_compare == 0:
return cmp(self.name, other.name)
return date_compare
@property
def attributes(self):
return self._attributes
@staticmethod
def from_dict(dictionary, basepath):
del dictionary["date"]
path = os.path.join(basepath, dictionary["name"])
del dictionary["name"]
for key, value in dictionary.items():
if key.startswith("dateTime"):
try:
dictionary[key] = datetime.strptime(dictionary[key], "%a %b %d %H:%M:%S %Y")
except KeyboardInterrupt:
raise
except:
pass
return Photo(path, None, dictionary)
def to_dict(self):
photo = { "name": self.name, "date": self.date }
photo.update(self.attributes)
return photo
class PhotoAlbumEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime("%a %b %d %H:%M:%S %Y")
if isinstance(obj, Album) or isinstance(obj, Photo):
return obj.to_dict()
return json.JSONEncoder.default(self, obj)
photofloat-0~20120917+dfsg/scanner/TreeWalker.py 0000664 0000000 0000000 00000007053 12153010610 0021377 0 ustar 00root root 0000000 0000000 import os
import os.path
import sys
from datetime import datetime
from PhotoAlbum import Photo, Album, PhotoAlbumEncoder
from CachePath import *
import json
class TreeWalker:
def __init__(self, album_path, cache_path):
self.album_path = os.path.abspath(album_path).decode(sys.getfilesystemencoding())
self.cache_path = os.path.abspath(cache_path).decode(sys.getfilesystemencoding())
set_cache_path_base(self.album_path)
self.all_albums = list()
self.all_photos = list()
self.walk(self.album_path)
self.big_lists()
self.remove_stale()
message("complete", "")
def walk(self, path):
next_level()
message("walking", os.path.basename(path))
cache = os.path.join(self.cache_path, json_cache(path))
cached = False
cached_album = None
if os.path.exists(cache):
try:
cached_album = Album.from_cache(cache)
if file_mtime(path) <= file_mtime(cache):
message("full cache", os.path.basename(path))
cached = True
album = cached_album
for photo in album.photos:
self.all_photos.append(photo)
else:
message("partial cache", os.path.basename(path))
except KeyboardInterrupt:
raise
except:
message("corrupt cache", os.path.basename(path))
cached_album = None
if not cached:
album = Album(path)
for entry in os.listdir(path):
if entry[0] == '.':
continue
try:
entry = entry.decode(sys.getfilesystemencoding())
except KeyboardInterrupt:
raise
except:
pass
entry = os.path.join(path, entry)
if os.path.isdir(entry):
album.add_album(self.walk(entry))
elif not cached and os.path.isfile(entry):
next_level()
cache_hit = False
if cached_album:
cached_photo = cached_album.photo_from_path(entry)
if cached_photo and file_mtime(entry) <= cached_photo.attributes["dateTimeFile"]:
message("cache hit", os.path.basename(entry))
cache_hit = True
photo = cached_photo
if not cache_hit:
message("metainfo", os.path.basename(entry))
photo = Photo(entry, self.cache_path)
if photo.is_valid:
self.all_photos.append(photo)
album.add_photo(photo)
else:
message("unreadable", os.path.basename(entry))
back_level()
if not album.empty:
message("caching", os.path.basename(path))
album.cache(self.cache_path)
self.all_albums.append(album)
else:
message("empty", os.path.basename(path))
back_level()
return album
def big_lists(self):
photo_list = []
self.all_photos.sort()
for photo in self.all_photos:
photo_list.append(photo.path)
message("caching", "all photos path list")
fp = open(os.path.join(self.cache_path, "all_photos.json"), 'w')
json.dump(photo_list, fp, cls=PhotoAlbumEncoder)
fp.close()
photo_list.reverse()
message("caching", "latest photos path list")
fp = open(os.path.join(self.cache_path, "latest_photos.json"), 'w')
json.dump(photo_list[0:27], fp, cls=PhotoAlbumEncoder)
fp.close()
def remove_stale(self):
message("cleanup", "building stale list")
all_cache_entries = { "all_photos.json": True, "latest_photos.json": True }
for album in self.all_albums:
all_cache_entries[album.cache_path] = True
for photo in self.all_photos:
for entry in photo.image_caches:
all_cache_entries[entry] = True
message("cleanup", "searching for stale cache entries")
for cache in os.listdir(self.cache_path):
try:
cache = cache.decode(sys.getfilesystemencoding())
except KeyboardInterrupt:
raise
except:
pass
if cache not in all_cache_entries:
message("cleanup", os.path.basename(cache))
os.unlink(os.path.join(self.cache_path, cache))
photofloat-0~20120917+dfsg/scanner/main.py 0000775 0000000 0000000 00000000647 12153010610 0020263 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
from TreeWalker import TreeWalker
from CachePath import message
import sys
def main():
reload(sys)
sys.setdefaultencoding("UTF-8")
if len(sys.argv) != 3:
print "usage: %s ALBUM_PATH CACHE_PATH" % sys.argv[0]
return
try:
TreeWalker(sys.argv[1], sys.argv[2])
except KeyboardInterrupt:
message("keyboard", "CTRL+C pressed, quitting.")
sys.exit(-97)
if __name__ == "__main__":
main()
photofloat-0~20120917+dfsg/web/ 0000775 0000000 0000000 00000000000 12153010610 0016077 5 ustar 00root root 0000000 0000000 photofloat-0~20120917+dfsg/web/.gitignore 0000664 0000000 0000000 00000000012 12153010610 0020060 0 ustar 00root root 0000000 0000000 upload.sh
photofloat-0~20120917+dfsg/web/.htaccess 0000664 0000000 0000000 00000001475 12153010610 0017704 0 ustar 00root root 0000000 0000000 AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript application/json
Header set Cache-Control "max-age=29030400, public"
Header set Cache-Control "max-age=5184000, public"
Header set Cache-Control "max-age=2678400, public"
Header set Cache-Control "max-age=3600, public"
deny from all
RewriteEngine On
RewriteBase /
RewriteRule ^redirect\.php$ - [L]
RewriteCond %{QUERY_STRING} _escaped_fragment_=
RewriteRule . staticrender.php [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /redirect.php [L]
photofloat-0~20120917+dfsg/web/Makefile 0000664 0000000 0000000 00000002062 12153010610 0017537 0 ustar 00root root 0000000 0000000 JS_DIR = js
CSS_DIR = css
JS_MIN = $(JS_DIR)/scripts.min.js
CSS_MIN = $(CSS_DIR)/styles.min.css
JS_MIN_FILES := $(sort $(patsubst %.js, %.min.js, $(filter-out %.min.js, $(wildcard $(JS_DIR)/*.js))))
CSS_MIN_FILES := $(sort $(patsubst %.css, %.min.css, $(filter-out %.min.css, $(wildcard $(CSS_DIR)/*.css))))
JS_COMPILER = yui-compressor --type js
CSS_COMPILER = yui-compressor --type css
.PHONY: all clean
all: $(JS_MIN) $(CSS_MIN)
%.min.js: %.js
@echo "Compiling javascript" $<
@$(JS_COMPILER) -o $@ $<
%.min.css: %.css
@echo "Compiling stylesheet" $<
@$(CSS_COMPILER) -o $@ $<
$(JS_MIN): $(JS_MIN_FILES)
@echo "Assembling compiled javascripts"
@cat $^ > $@
$(CSS_MIN): $(CSS_MIN_FILES)
@echo "Assembling compiled stylesheets"
@cat $^ > $@
empty :=
space := $(empty) $(empty)
classpath := $(subst $(space),:,$(wildcard /usr/share/java/htmlunit*.jar))
utils/ServerExecute.class: utils/ServerExecute.java
@echo "Building HtmlUnit wrapper."
@javac -classpath $(classpath) -d utils $^
clean:
@rm -fv $(JS_MIN) $(JS_MIN_FILES) $(CSS_MIN) $(CSS_MIN_FILES)
photofloat-0~20120917+dfsg/web/css/ 0000775 0000000 0000000 00000000000 12153010610 0016667 5 ustar 00root root 0000000 0000000 photofloat-0~20120917+dfsg/web/css/.gitignore 0000664 0000000 0000000 00000000012 12153010610 0020650 0 ustar 00root root 0000000 0000000 *.min.css
photofloat-0~20120917+dfsg/web/css/.htaccess 0000664 0000000 0000000 00000000101 12153010610 0020455 0 ustar 00root root 0000000 0000000
deny from all
photofloat-0~20120917+dfsg/web/css/000-controls.css 0000664 0000000 0000000 00000007034 12153010610 0021545 0 ustar 00root root 0000000 0000000 body {
margin: 0;
padding: 0;
background-color: #222222;
font-family: "LM Roman", "Georgia", "Palatino Linotype", "Palatino", "Times New Roman", "Times", serif;
color: #FFFFFF;
}
a {
color: #84AAC2;
text-decoration: none;
}
a:hover {
color: #FFAD27;
}
#title {
position: absolute;
top: 0;
padding: 0.4em;
font-weight: bold;
font-size: 1.15em;
}
#loading {
display: none;
}
#album-view {
position: absolute;
top: 2.5em;
padding: 1em;
}
#thumbs {
clear: both;
line-height: 0;
}
#thumbs img {
border: 0;
margin: 0;
padding: 0;
}
.current-thumb {
border-top: 1px solid #FFAD27 !important;
}
#subalbums {
padding-top: 1.5em;
}
.album-button {
float: left;
display: block;
width: 150px;
height: 60px;
text-align: center;
font-style: italic;
font-size: 12px;
background-repeat: no-repeat;
background-position: top;
padding-top: 150px;
background-image: url(../img/image-placeholder.png);
}
#next, #back {
position: absolute;
width: auto;
font-size: 4.5em;
line-height: 0;
top: 40%;
font-weight: bold;
opacity: 0.35;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=35)";
filter: alpha(opacity=35);
}
#back {
left: 0.1em;
}
#next {
right: 0.1em;
}
#photo {
border: 0;
left: 0;
}
#photo-view {
position: absolute;
bottom: 150px;
top: 2.5em;
overflow: hidden;
margin-bottom: 0.5em;
left: 0;
right: 0;
text-align: center;
}
#photo-box {
display: inline;
}
#photo-links {
background-color: #000000;
font-weight: bold;
height: 10px;
font-size: 10px;
line-height: 7px;
padding-top: 3px;
padding-bottom: 3px;
padding-right: 10px;
padding-left: 10px;
display: none;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
-moz-border-top-right-radius: 5px;
-moz-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 5px;
}
#metadata {
background-color: #000000;
width: 340px;
font-size: 12px;
line-height: 12px;
padding-top: 3px;
padding-bottom: 3px;
padding-right: 10px;
padding-left: 10px;
display: none;
margin: 0 auto;
margin-top: 1px;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
-moz-border-top-right-radius: 5px;
-moz-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 5px;
opacity: 0.5;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
filter: alpha(opacity=50);
overflow: auto;
}
#metadata table {
margin: auto auto;
text-align: left;
}
#metadata tr {
height: 14px;
}
#photo-bar {
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
}
#fullscreen, #fullscreen-divider {
display: none;
}
.photo-view-container {
position: absolute;
height: 150px;
width: 100%;
bottom: 0;
top: auto !important;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
padding: 0 !important;
text-align: center;
}
#powered-by {
clear: both;
text-align: center;
font-size: 0.85em;
font-style: italic;
font-weight: bold;
text-shadow: rgba(0,0,0,0.5) -1px 0, rgba(0,0,0,0.3) 0 -1px, rgba(255,255,255,0.5) 0 1px, rgba(0,0,0,0.3) -1px -2px;
color: #FFAD27;
}
#error-overlay, #error-text, #auth-text {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
}
#error-overlay {
background-color: #000000;
}
#error-text, #auth-text {
position: fixed;
padding-top: 20%;
text-align: center;
}
#error-text {
font-size: 4em;
font-weight: bold;
font-style: italic;
}
#auth-text input {
color: rgb(0, 0, 0);
background-color: rgb(200, 200, 200);
border: 0;
font-family: inherit;
font-size: 2em;
font-weight: bold;
font-style: italic;
}
photofloat-0~20120917+dfsg/web/css/001-fonts.css 0000664 0000000 0000000 00000001156 12153010610 0021033 0 ustar 00root root 0000000 0000000 @font-face {
font-family: "LM Roman";
font-weight: bold;
font-style: normal;
src: url("../fonts/lmroman10-bold.otf");
}
@font-face {
font-family: "LM Roman";
font-weight: bold;
font-style: italic;
src: url("../fonts/lmroman10-bolditalic.otf");
}
@font-face {
font-family: "LM Roman";
font-weight: normal;
font-style: italic;
src: url("../fonts/lmroman10-italic.otf");
}
@font-face {
font-family: "LM Roman";
font-weight: normal;
font-style: normal;
src: url("../fonts/lmroman10-regular.otf");
} photofloat-0~20120917+dfsg/web/css/002-mobile.css 0000664 0000000 0000000 00000000311 12153010610 0021142 0 ustar 00root root 0000000 0000000 @media handheld, only screen and (max-height: 640px) {
#photo-view {
top: 0;
bottom: 0;
margin: 0;
}
.photo-view-container {
display: none;
}
#title {
font-size: 1em;
z-index: 1;
}
}
photofloat-0~20120917+dfsg/web/img/ 0000775 0000000 0000000 00000000000 12153010610 0016653 5 ustar 00root root 0000000 0000000 photofloat-0~20120917+dfsg/web/img/image-placeholder.png 0000664 0000000 0000000 00000066166 12153010610 0022742 0 ustar 00root root 0000000 0000000 PNG
IHDR <q sRGB bKGD pHYs 7] 7]F] IDATxyeU;by*UsT%UIhB Bpj6^j7^۫`1A 4*լ*+_x}s{"JjγVxs~ps\7us\7us\7us\7us\7us\7us\7us\7us\[brߛz% Ѓ%Yd _7pPlnai
Qig xL)sӞnW hrN
01cr9f
T`
;-}9ۻu_?=oR&f9ଵTb(B@nt"5, aH˘}NJ{ݿ25jgoͰ1ݗqe8-e!lw {H2$-_;&qNDklyv`Xi÷ű&
;P
۶oi[fװܺ[1\B?(Ƞhrm3fMw3v-UdqSHqqpg$_gҞ]ŏm-+k
2M(ڊ5xMX7&C+[!0@Ah &G@lLQ&q Ka!0ڠWcD˻,`Iysyr~p+,:[2J9l[F
)`!]E6o+zXu4@P37G%eE')0C=B{3a c>_z}~ӿؤO#$clFcBMs4oyz7r\rB0Do%7' ABė$Ɣ&3ĝXǬ\jP=p2$~,ľ;rUz<BQF545 L2xAB HqTH)R$3hMP\dk(l"X
k\!=)MPR~Ùu,?/-/ ]!+`FmF?fogl"33 6_<_},zʌ1{0wu<X#V]?pOQq:N=ssFc==f4Vװ3h7>=Y 1R^:*2\È%Q
|bT8z `+AXOB`BHB"@Wg3qb,K,H3
o/Zd6n+9g0YkD2k}o42nxaIZMWНwVdHhhU/ѨШv_~gQta_ ᚹn(F\]l\Ű茄P`21I(IF mZKbT*"$jKq|cw&"c,K`;xc6N~2>,luVLW_Kh2AR1RJ#
1Fl3O`o䙅:q F7c!qo5Wzۯ`:AbP'Xe4ICM,!%,?+[&:vx a+X&,_Kjc<>`}[{0=IlQ#L~L0QW`c!QtQbl#Aؗht44t
*50)d/&8+2⚷Anh#Xo+^ZjgƊ1Kf[N)Zs c]azZ3^gEAj[,M3=x!0| 5~^3`<M
R8Gd(@:/=vB&MȣW5^Q2{wN9HّϽ~Vh4812hb/ig-"[%ľ@]DBH|Ė@t`[VA$-O7ڑh;8dTC#N|eo)9t$Nr'C<߁aI0HmiZu[J%2HV"êh/)%1E Eg>ϖDee?MW
[jAHl0N%
I 6ok-dCk|]J("*R\dzRtXo7B<-ȍM$0.l i0!=vlpB>ry,uJ7CĨd#ܹy;j
8X)P[]\@L#5/vp.V^
P"H aA D"eokRBꡐ:hHbl\gag}.XH["B 8ԤOòшg![HʡJB_lԧ`2݇~.!
>+H<߶l>XGDZ=LvIlK^/\g(yzy*ec,u1s ,I,5m
%J$q5G-V"bǦLhoBJͺ%@!ɅnkG9P4>r\3_|j
+bYAz>wP07z!OS)H*z@>Pâ [F{uRU)4+!mЌ|ukvM!bldn
Ӱ$5v[vw_u'brXHn{;QybYalb$Nb|#S&5/䜱X
-B\FرݱW7ɏɆKI=W