checkpoint

This commit is contained in:
voussoir 2016-10-29 18:46:23 -07:00
parent 198900c990
commit 25a4b69cd8
5 changed files with 185 additions and 33 deletions

View file

@ -33,6 +33,7 @@ MOTD_STRINGS = [
#'Buckle up, it\'s time to:',
]
THUMBDIR = phototagger.DEFAULT_THUMBDIR
ERROR_INVALID_ACTION = 'Invalid action'
ERROR_NO_TAG_GIVEN = 'No tag name supplied'
ERROR_TAG_TOO_SHORT = 'Not enough valid chars'
@ -317,7 +318,7 @@ def get_album_html(albumid):
response = flask.render_template(
'album.html',
album=album,
child_albums=album['sub_albums'],
child_albums=[jsonify_album(P_album(x)) for x in album['sub_albums']],
photos=album['photos'],
)
return response
@ -392,7 +393,8 @@ def get_photo_json(photoid):
def get_search_core():
print(request.args)
# EXTENSION
# FILENAME & EXTENSION
filename_terms = request.args.get('filename', None)
extension_string = request.args.get('extension', None)
extension_not_string = request.args.get('extension_not', None)
mimetype_string = request.args.get('mimetype', None)
@ -464,6 +466,7 @@ def get_search_core():
'created': created,
'extension': extension_list,
'extension_not': extension_not_list,
'filename': filename_terms,
'has_tags': has_tags,
'mimetype': mimetype_list,
'tag_musts': tag_musts,

View file

@ -2,6 +2,8 @@
# py -i etiquette_easy.py
import phototagger
import os
import sys
P = phototagger.PhotoDB()
import traceback

View file

@ -61,6 +61,7 @@ SQL_ALBUM_COLUMNS = [
'id',
'title',
'description',
'associated_directory'
]
SQL_PHOTO_COLUMNS = [
'id',
@ -112,11 +113,12 @@ PRAGMA cache_size = 10000;
CREATE TABLE IF NOT EXISTS albums(
id TEXT,
title TEXT,
description TEXT
description TEXT,
associated_directory TEXT COLLATE NOCASE
);
CREATE TABLE IF NOT EXISTS photos(
id TEXT,
filepath TEXT,
filepath TEXT COLLATE NOCASE,
extension TEXT,
width INT,
height INT,
@ -160,7 +162,7 @@ CREATE INDEX IF NOT EXISTS index_albumrel_photoid on album_photo_rel(photoid);
-- Photo
CREATE INDEX IF NOT EXISTS index_photo_id on photos(id);
CREATE INDEX IF NOT EXISTS index_photo_path on photos(filepath);
CREATE INDEX IF NOT EXISTS index_photo_path on photos(filepath COLLATE NOCASE);
CREATE INDEX IF NOT EXISTS index_photo_created on photos(created);
CREATE INDEX IF NOT EXISTS index_photo_extension on photos(extension);
@ -213,6 +215,10 @@ def _helper_extension(ext):
ext = set(ext)
return ext
def _helper_filenamefilter(subject, terms):
basename = subject.lower()
return all(term in basename for term in terms)
def _helper_minmax(key, value, minimums, maximums):
'''
When searching, this function dissects a hyphenated range string
@ -686,10 +692,27 @@ class PDBAlbumMixin:
def get_album(self, id):
return self.get_thing_by_id('album', id)
def get_album_by_path(self, filepath):
'''
Return the album with the `associated_directory` of this value, NOT case-sensitive.
'''
filepath = os.path.abspath(filepath)
self.cur.execute('SELECT * FROM albums WHERE associated_directory == ?', [filepath])
f = self.cur.fetchone()
if f is None:
raise NoSuchAlbum(filepath)
return self.get_album(f[SQL_ALBUM['id']])
def get_albums(self):
yield from self.get_things(thing_type='album')
def new_album(self, title=None, description=None, photos=None, commit=True):
def new_album(self,
associated_directory=None,
commit=True,
description=None,
photos=None,
title=None,
):
'''
Create a new album. Photos can be added now or later.
'''
@ -697,6 +720,9 @@ class PDBAlbumMixin:
albumid = self.generate_id('tags')
title = title or ''
description = description or ''
if associated_directory is not None:
associated_directory = os.path.abspath(associated_directory)
if not isinstance(title, str):
raise TypeError('Title must be string, not %s' % type(title))
@ -707,8 +733,9 @@ class PDBAlbumMixin:
data[SQL_ALBUM['id']] = albumid
data[SQL_ALBUM['title']] = title
data[SQL_ALBUM['description']] = description
data[SQL_ALBUM['associated_directory']] = associated_directory
self.cur.execute('INSERT INTO albums VALUES(?, ?, ?)', data)
self.cur.execute('INSERT INTO albums VALUES(?, ?, ?, ?)', data)
album = Album(self, data)
if photos:
for photo in photos:
@ -716,6 +743,7 @@ class PDBAlbumMixin:
album.add_photo(photo, commit=False)
if commit:
log.debug('Committing - new Album')
self.commit()
return album
@ -851,6 +879,7 @@ class PDBPhotoMixin:
created=None,
extension=None,
extension_not=None,
filename=None,
has_tags=None,
mimetype=None,
tag_musts=None,
@ -878,6 +907,10 @@ class PDBPhotoMixin:
extension_not:
A string or list of strings of unacceptable file extensions.
filename:
A string or list of strings which will be split into words. The file's basename
must include every word, NOT case-sensitive.
has_tags:
If True, require that the Photo has >=1 tag.
If False, require that the Photo has no tags.
@ -934,6 +967,11 @@ class PDBPhotoMixin:
extension_not = _helper_extension(extension_not)
mimetype = _helper_extension(mimetype)
if filename is not None:
if not isinstance(filename, str):
filename = ' '.join(filename)
filename = set(term.lower() for term in filename.strip().split(' '))
if (tag_musts or tag_mays or tag_forbids) and tag_expression:
raise XORException('Expression filter cannot be used with musts, mays, forbids')
@ -984,6 +1022,11 @@ class PDBPhotoMixin:
continue
if mimetype and photo.mimetype() not in mimetype:
#print('Failed mimetype')
continue
if filename and not _helper_filenamefilter(subject=photo.basename, terms=filename):
#print('Failed filename')
continue
if any(not fetch[SQL_PHOTO[key]] or fetch[SQL_PHOTO[key]] > value for (key, value) in maximums.items()):
@ -1173,7 +1216,9 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
def digest_directory(self, directory, exclude_directories=None, exclude_filenames=None, commit=True):
'''
Create an album, and add the directory's contents to it.
Create an album, and add the directory's contents to it recursively.
If a Photo object already exists for a file, it will be added to the correct album.
'''
if not os.path.isdir(directory):
raise ValueError('Not a directory: %s' % directory)
@ -1189,22 +1234,41 @@ class PhotoDB(PDBAlbumMixin, PDBPhotoMixin, PDBTagMixin):
]
directory = spinal.str_to_fp(directory)
directory.correct_case()
generator = spinal.walk_generator(
directory,
exclude_directories=exclude_directories,
exclude_filenames=exclude_filenames,
yield_style='nested',
)
album = self.new_album(title=directory.basename, commit=False)
try:
album = self.get_album_by_path(directory.absolute_path)
except NoSuchAlbum:
album = self.new_album(
associated_directory=directory.absolute_path,
commit=False,
title=directory.basename,
)
albums = {directory.absolute_path: album}
for (current_location, directories, files) in generator:
current_album = albums.get(current_location.absolute_path, None)
if current_album is None:
current_album = self.new_album(title=current_location.basename, commit=False)
print('Created %s' % current_album.title)
try:
current_album = self.get_album_by_path(current_location.absolute_path)
except NoSuchAlbum:
current_album = self.new_album(
associated_directory=current_location.absolute_path,
commit=False,
title=current_location.basename,
)
print('Created %s' % current_album.title)
albums[current_location.absolute_path] = current_album
parent = albums[current_location.parent.absolute_path]
parent.add(current_album, commit=False)
try:
parent.add(current_album, commit=False)
except GroupExists:
pass
#print('Added to %s' % parent.title)
for filepath in files:
try:
@ -1561,6 +1625,7 @@ class Album(ObjectBase, GroupableMixin):
self.photodb = photodb
self.id = row_tuple[SQL_ALBUM['id']]
self.title = row_tuple[SQL_ALBUM['title']]
self.name = 'Album %s' % self.id
self.description = row_tuple[SQL_ALBUM['description']]
self.group_getter = self.photodb.get_album
@ -1736,14 +1801,7 @@ class Photo(ObjectBase):
special:
For videos, you can provide a `timestamp` to take the thumbnail from.
'''
chunked_id = chunk_sequence(self.id, 3)
basename = chunked_id[-1]
folder = chunked_id[:-1]
folder = os.sep.join(folder)
folder = os.path.join(self.photodb.thumbnail_folder, folder)
if folder:
os.makedirs(folder, exist_ok=True)
hopeful_filepath = os.path.join(folder, basename) + '.jpg'
hopeful_filepath = self.make_thumbnail_filepath()
return_filepath = None
mime = self.mimetype()
@ -1828,6 +1886,17 @@ class Photo(ObjectBase):
return False
def make_thumbnail_filepath(self):
chunked_id = chunk_sequence(self.id, 3)
basename = chunked_id[-1]
folder = chunked_id[:-1]
folder = os.sep.join(folder)
folder = os.path.join(self.photodb.thumbnail_folder, folder)
if folder:
os.makedirs(folder, exist_ok=True)
hopeful_filepath = os.path.join(folder, basename) + '.jpg'
return hopeful_filepath
def mimetype(self):
return get_mimetype(self.real_filepath)

View file

@ -6,6 +6,9 @@
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/common.css">
<script src="/static/common.js"></script>
{% set filename = photo["id"] + "." + photo["extension"] %}
{% set link = "/file/" + filename %}
{% set mimetype=photo["mimetype"] %}
</head>
<style>
#content_body
@ -46,19 +49,26 @@
flex-direction: column;
justify-content: center;
align-items: center;
max-height: 100%;
max-width: 100%;
height: 100%;
width: 100%;
}
.photo_object a
{
max-height: 100%;
max-width: 100%;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
#photo_img_holder
{
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background-repeat: no-repeat;
}
.photo_object img
{
max-height: 100%;
@ -100,12 +110,12 @@
<h4>File info</h4>
<ul id="metadata">
{% if photo["width"] %}
<li>{{photo["width"]}}x{{photo["height"]}} px</li>
<li>{{photo["ratio"]}} aspect ratio</li>
<li>{{photo["bytestring"]}}</li>
<li>Dimensions: {{photo["width"]}}x{{photo["height"]}} px</li>
<li>Aspect ratio: {{photo["ratio"]}}</li>
<li>Size: {{photo["bytestring"]}}</li>
{% endif %}
<li>{{photo["duration"]}}</li>
{% if photo["duration"] %}
<li>Duration: {{photo["duration"]}}</li>
{% endif %}
<li><a href="/file/{{photo["id"]}}.{{photo["extension"]}}?download=1">Download as {{photo["id"]}}.{{photo["extension"]}}</a></li>
<li><a href="/file/{{photo["id"]}}.{{photo["extension"]}}?download=1&original_filename=1">Download as "{{photo["filename"]}}"</a></li>
@ -126,13 +136,11 @@
</div>
<div id="right">
<!-- THE PHOTO ITSELF -->
<div class="photo_object">
{% set filename = photo["id"] + "." + photo["extension"] %}
{% set link = "/file/" + filename %}
{% set mimetype=photo["mimetype"] %}
{% if mimetype == "image" %}
<!-- <a target="_blank" href="{{link}}"><img src="{{link}}"></a> -->
<img src="{{link}}">
<div id="photo_img_holder"><img id="photo_img" src="{{link}}" onclick="toggle_hoverzoom()" onload="this.style.opacity=0.99"></div>
<!-- <img src="{{link}}"> -->
{% elif mimetype == "video" %}
<video src="{{link}}" controls preload=none {%if photo["has_thumbnail"]%}poster="/thumbnail/{{photo["id"]}}.jpg"{%endif%}></video>
{% elif mimetype == "audio" %}
@ -152,6 +160,72 @@ var add_tag_button = document.getElementById('add_tag_button');
var message_area = document.getElementById('message_area');
add_tag_box.onkeydown = function(){entry_with_history_hook(add_tag_box, add_tag_button)};
function enable_hoverzoom()
{
console.log("enable");
div = document.getElementById("photo_img_holder");
img = document.getElementById("photo_img");
if (img.naturalWidth < div.offsetWidth && img.naturalHeight < div.offsetHeight)
{
return;
}
img.style.opacity = 0;
img.style.display = "none";
div.style.cursor = "zoom-out";
div.style.backgroundImage = "url('{{link}}')";
div.onmousemove = move_hoverzoom;
setTimeout(function(){div.onclick = disable_hoverzoom;}, 100);
return true;
}
function disable_hoverzoom()
{
console.log("disable");
div = document.getElementById("photo_img_holder");
img = document.getElementById("photo_img");
img.style.opacity = 100;
div.style.cursor = "";
img.style.display="";
div.style.backgroundImage = "none";
div.onmousemove = null;
div.onclick = null;
}
function toggle_hoverzoom()
{
img = document.getElementById("photo_img");
if (img.style.opacity === "0")
{
disable_hoverzoom();
}
else
{
enable_hoverzoom();
}
}
function move_hoverzoom(event)
{
div = document.getElementById("photo_img_holder");
img = document.getElementById("photo_img");
var x;
var y;
if (img.naturalWidth < div.offsetWidth)
{
x = (img.naturalWidth - div.offsetWidth) / 2;
}
else
{
x = (img.naturalWidth - div.offsetWidth) * (event.offsetX / div.offsetWidth);
}
if (img.naturalHeight < div.offsetHeight)
{
y = (img.naturalHeight - div.offsetHeight) / 2;
}
else
{
y = (img.naturalHeight - div.offsetHeight) * (event.offsetY / div.offsetHeight);
}
//console.log(x);
div.style.backgroundPosition=(-x)+"px "+(-y)+"px";
}
function receive_callback(response)
{
var tagname = response["tagname"];

View file

@ -189,6 +189,10 @@ form
<br>
<span>Other filters</span>
<input type="text" class="basic_param"
value="{%if search_kwargs['filename']%}{{search_kwargs['filename']}}{%endif%}"
name="filename" placeholder="Filename">
<input type="text" class="basic_param"
value="{%if search_kwargs['mimetype']%}{{search_kwargs['mimetype']}}{%endif%}"
name="mimetype" placeholder="Mimetype(s)">