checkpoint
This commit is contained in:
parent
198900c990
commit
25a4b69cd8
5 changed files with 185 additions and 33 deletions
|
@ -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,
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
# py -i etiquette_easy.py
|
||||
|
||||
import phototagger
|
||||
import os
|
||||
import sys
|
||||
P = phototagger.PhotoDB()
|
||||
import traceback
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -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)">
|
||||
|
|
Loading…
Reference in a new issue