Merge branch 'master' of mara.ddp.net:photoassistant

This commit is contained in:
2021-01-13 13:54:33 +11:00
17 changed files with 508 additions and 130 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,6 @@
__pycache__/ __pycache__/
photos/ photos/
images_to_process/ images_to_process/
new_img_dir/
static/* static/*
reference_images/*

15
ai.py Normal file
View File

@@ -0,0 +1,15 @@
from wtforms import SubmitField, StringField, HiddenField, validators, Form
from flask_wtf import FlaskForm
from flask import request, render_template, redirect
from main import db, app, ma
from sqlalchemy import Sequence
from sqlalchemy.exc import SQLAlchemyError
from status import st, Status
################################################################################
# /refimg/<id> -> GET/POST(save or delete) -> shows/edits/delets a single
# refimg
################################################################################
@app.route("/aistats", methods=["GET", "POST"])
def aistats():
return render_template("aistats.html", page_title='Placeholder', alert=st.GetAlert(), message=st.GetMessage() )

109
files.py
View File

@@ -14,6 +14,7 @@ import exifread
import base64 import base64
import numpy import numpy
import cv2 import cv2
import time
################################################################################ ################################################################################
# Local Class imports # Local Class imports
@@ -22,11 +23,8 @@ from settings import Settings
class FileData(): class FileData():
def __init__(self): def __init__(self):
self.view_path=''
self.view_list=[] self.view_list=[]
self.symlink=''
self.file_list=[]
################################################################################ ################################################################################
# Utility Functions for Files # Utility Functions for Files
################################################################################ ################################################################################
@@ -91,7 +89,6 @@ class FileData():
bt = thumb_buf.tostring() bt = thumb_buf.tostring()
fthumbnail = base64.b64encode(bt) fthumbnail = base64.b64encode(bt)
fthumbnail = str(fthumbnail)[2:-1] fthumbnail = str(fthumbnail)[2:-1]
print(fthumbnail)
return fthumbnail return fthumbnail
@@ -100,43 +97,55 @@ class FileData():
# multiple valid paths in import_path) # # multiple valid paths in import_path) #
############################################################################## ##############################################################################
def GenerateFileData(self): def GenerateFileData(self):
sets = Settings.query.filter(Settings.name=="import_path").all() settings = Settings.query.all()
paths= sets[0].value.split("#") last_import_date = settings[0].last_import_date
paths = settings[0].import_path.split("#")
for path in paths: for path in paths:
print( "GenerateFileData: Checking {}".format( path ) )
path = self.FixPath(path) path = self.FixPath(path)
if os.path.exists( path ): if os.path.exists( path ):
self.view_path = path
# to serve static content of the images, we create a symlink # to serve static content of the images, we create a symlink
# from inside the static subdir of each import_path that exists # from inside the static subdir of each import_path that exists
self.symlink = self.FixPath('static/{}'.format( os.path.basename(path[0:-1]))) symlink = self.FixPath('static/{}'.format( os.path.basename(path[0:-1])))
if not os.path.exists(self.symlink): if not os.path.exists(symlink):
os.symlink(path, self.symlink) os.symlink(path, symlink)
self.file_list.append(glob.glob(self.view_path + '**', recursive=True)) file_list=[]
for file in self.file_list[0]: file_list.append(glob.glob(path + '**', recursive=True))
fthumbnail = None for file in file_list[0]:
if file == path: if file == path:
continue continue
if os.path.isdir(file): stat = os.stat(file)
ftype = 'Directory' if last_import_date == 0 or stat.st_ctime > last_import_date:
elif self.isImage(file): print( "{} - {} is newer than {}".format( file, stat.st_ctime, last_import_date ) )
ftype = 'Image' fthumbnail = None
fthumbnail = self.getExif(file) if os.path.isdir(file):
elif self.isVideo(file): ftype = 'Directory'
ftype = 'Video' elif self.isImage(file):
fthumbnail = self.generateVideoThumbnail(file) ftype = 'Image'
else: fthumbnail = self.getExif(file)
ftype = 'File' elif self.isVideo(file):
ftype = 'Video'
fthumbnail = self.generateVideoThumbnail(file)
else:
ftype = 'File'
if ftype != "Directory": if ftype != "Directory":
fhash=self.md5(file) fhash=self.md5(file)
else: else:
fhash=None fhash=None
fsize = round(os.stat(file).st_size/(1024*1024)) fsize = round(os.stat(file).st_size/(1024*1024))
fname=file.replace(path, "") fname=file.replace(path, "")
self.view_list.append( Files( name=fname, type=ftype, size_mb=fsize, hash=fhash, thumbnail=fthumbnail )) path_prefix=symlink.replace(path,"")
file_obj = Files( name=fname, type=ftype, size_mb=fsize, hash=fhash, path_prefix=path_prefix, thumbnail=fthumbnail )
db.session.add(file_obj)
else:
print( "{} - {} is OLDER than {}".format( file, stat.st_ctime, last_import_date ) )
settings[0].last_import_date = time.time()
db.session.commit()
self.view_list = Files.query.all()
return self return self
################################################################################ ################################################################################
@@ -147,35 +156,59 @@ class Files(db.Model):
id = db.Column(db.Integer, db.Sequence('files_id_seq'), primary_key=True ) id = db.Column(db.Integer, db.Sequence('files_id_seq'), primary_key=True )
name = db.Column(db.String, unique=True, nullable=False ) name = db.Column(db.String, unique=True, nullable=False )
type = db.Column(db.String, unique=False, nullable=False) type = db.Column(db.String, unique=False, nullable=False)
path_prefix = db.Column(db.String, unique=False, nullable=False)
size_mb = db.Column(db.Integer, unique=False, nullable=False) size_mb = db.Column(db.Integer, unique=False, nullable=False)
# hash might not be unique, this could be the source of dupe problems # hash might not be unique, this could be the source of dupe problems
hash = db.Column(db.Integer, unique=True, nullable=True) hash = db.Column(db.Integer, unique=True, nullable=True)
thumbnail = db.Column(db.LargeBinary, unique=False, nullable=True) thumbnail = db.Column(db.String, unique=False, nullable=True)
def __repr__(self): def __repr__(self):
return "<id: {}, name: {}>".format(self.id, self.name ) return "<id: {}, name: {}>".format(self.id, self.name )
### Initiatlise the file data set (GenerateFileData is clever enough to not
### re-process files when we run twice in quick succession, e.g. when running
### Flask in DEBUG mode
filedata = FileData()
filedata.GenerateFileData()
################################################################################ ################################################################################
# /file_list -> show detailed file list of files from import_path(s) # /file_list -> show detailed file list of files from import_path(s)
################################################################################ ################################################################################
@app.route("/file_list", methods=["GET"]) @app.route("/file_list", methods=["GET"])
def file_list(): def file_list():
filedata = FileData() return render_template("file_list.html", page_title='View Files (details)', file_data=filedata, alert=st.GetAlert(), message=st.GetMessage() )
file_data=filedata.GenerateFileData()
return render_template("file_list.html", page_title='View Files (details)', file_data=file_data, alert=st.GetAlert(), message=st.GetMessage() )
################################################################################ ################################################################################
# /files -> show thumbnail view of files from import_path(s) # /files -> show thumbnail view of files from import_path(s)
################################################################################ ################################################################################
@app.route("/files", methods=["GET"]) @app.route("/files", methods=["GET"])
def files(): def files():
filedata = FileData() return render_template("files.html", page_title='View Files', file_data=filedata, alert=st.GetAlert(), message=st.GetMessage() )
file_data=filedata.GenerateFileData()
return render_template("files.html", page_title='View Files', file_data=file_data, alert=st.GetAlert(), message=st.GetMessage() )
################################################################################
# /files/scannow -> allows us to force a check for new files
################################################################################
@app.route("/files/scannow", methods=["GET"])
def scannow():
filedata.GenerateFileData()
return render_template("base.html", page_title='Forced look for new items', file_data=filedata, alert="success", message="Scanned for new files" )
################################################################################
# /files/forcescan -> deletes old data in DB, and does a brand new scan
################################################################################
@app.route("/files/forcescan", methods=["GET"])
def forcescan():
Files.query.delete()
Settings.query.all()[0].last_import_date=0
db.session.commit()
filedata.GenerateFileData()
return render_template("base.html", page_title='Forced look for new items', file_data=filedata, alert="success", message="Forced remove and recreation of all file data" )
################################################################################
# /static -> returns the contents of any file referenced inside /static.
# we create/use symlinks in static/ to reference the images to show
################################################################################
@app.route("/static/<filename>") @app.route("/static/<filename>")
def custom_static(filename): def custom_static(filename):
return send_from_directory("static/", filename) return send_from_directory("static/", filename)

12
main.py
View File

@@ -29,6 +29,18 @@ Bootstrap(app)
################################# Now, import non-book classes ################################### ################################# Now, import non-book classes ###################################
from settings import Settings from settings import Settings
from files import Files from files import Files
from person import Person
from refimg import Refimg
from ai import *
####################################### CLASSES / DB model #######################################
class Person_Refimg_Link(db.Model):
__tablename__ = "person_refimg_link"
person_id = db.Column(db.Integer, db.ForeignKey('person.id'), unique=True, nullable=False, primary_key=True)
refimg_id = db.Column(db.Integer, db.ForeignKey('refimg.id'), unique=True, nullable=False, primary_key=True)
def __repr__(self):
return "<person_id: {}, refimg_id>".format(self.person_id, self.refimg_id)
####################################### GLOBALS ####################################### ####################################### GLOBALS #######################################
# allow jinja2 to call these python functions directly # allow jinja2 to call these python functions directly

112
person.py Normal file
View File

@@ -0,0 +1,112 @@
from wtforms import SubmitField, StringField, HiddenField, validators, Form
from flask_wtf import FlaskForm
from flask import request, render_template, redirect
from main import db, app, ma
from sqlalchemy import Sequence
from sqlalchemy.exc import SQLAlchemyError
from status import st, Status
from files import Files
################################################################################
# Class describing Person in the database, and via sqlalchemy, connected to the DB as well
################################################################################
class Person(db.Model):
id = db.Column(db.Integer, db.Sequence('person_id_seq'), primary_key=True )
tag = db.Column(db.String(48), unique=False, nullable=False)
surname = db.Column(db.String(48), unique=False, nullable=False)
firstname = db.Column(db.String(48), unique=False, nullable=False)
def __repr__(self):
return "<tag: {}, firstname: {}, surname: {}>".format(self.tag,self.firstname, self.surname)
class File_Person_Link(db.Model):
__tablename__ = "file_person_link"
file_id = db.Column(db.Integer, db.ForeignKey('files.id'), unique=True, nullable=False, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'), unique=True, nullable=False, primary_key=True)
def __repr__(self):
return "<file_id: {}, person_id: {}>".format(self.file_id, self.person_id)
################################################################################
# Helper class that inherits a .dump() method to turn class Person into json / useful in jinja2
################################################################################
class PersonSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Person
ordered = True
################################################################################
# Helper class that defines a form for person, used to make html <form>, with field validation (via wtforms)
################################################################################
class PersonForm(FlaskForm):
id = HiddenField()
tag = StringField('Tag (searchable name):', [validators.DataRequired()])
firstname = StringField('FirstName(s):', [validators.DataRequired()])
surname = StringField('Surname:', [validators.DataRequired()])
submit = SubmitField('Save' )
delete = SubmitField('Delete' )
################################################################################
# Routes for person data
#
# /persons -> GET only -> prints out list of all persons
################################################################################
@app.route("/persons", methods=["GET"])
def persons():
persons = Person.query.all()
return render_template("persons.html", persons=persons, alert=st.GetAlert(), message=st.GetMessage() )
################################################################################
# /person -> GET/POST -> creates a new person type and when created, takes you back to /persons
################################################################################
@app.route("/person", methods=["GET", "POST"])
def new_person():
form = PersonForm(request.form)
page_title='Create new Person'
if 'surname' not in request.form:
return render_template("person.html", form=form, page_title=page_title )
else:
person = Person( tag=request.form["tag"], surname=request.form["surname"], firstname=request.form["firstname"] )
try:
db.session.add(person)
db.session.commit()
print(person)
st.SetMessage( "Created new Person ({})".format(person.tag) )
return redirect( '/persons' )
except SQLAlchemyError as e:
st.SetAlert( "danger" )
st.SetMessage( "<b>Failed to add Person:</b>&nbsp;{}".format(e.orig) )
return render_template("person.html", form=form, page_title=page_title, alert=st.GetAlert(), message=st.GetMessage() )
################################################################################
# /person/<id> -> GET/POST(save or delete) -> shows/edits/delets a single
# person
################################################################################
@app.route("/person/<id>", methods=["GET", "POST"])
def person(id):
form = PersonForm(request.form)
page_title='Edit Person'
if request.method == 'POST':
try:
person = Person.query.get(id)
if 'delete' in request.form:
st.SetMessage("Successfully deleted Person: ({})".format( person.tag ) )
person = Person.query.filter(Person.id==id).delete()
if 'submit' in request.form and form.validate():
st.SetMessage("Successfully Updated Person: (From: {}, {}, {})".format(person.tag, person.firstname, person.surname) )
person.tag = request.form['tag']
person.surname = request.form['surname']
person.firstname = request.form['firstname']
st.AppendMessage(" To: ({}, {}, {})".format(person.tag, person.firstname, person.surname) )
db.session.commit()
return redirect( '/persons' )
except SQLAlchemyError as e:
st.SetAlert( "danger" )
st.SetMessage( "<b>Failed to modify Person:</b>&nbsp;{}".format(e.orig) )
return render_template("person.html", form=form, page_title=page_title, alert=st.GetAlert(), message=st.GetMessage() )
else:
person = Person.query.get(id)
form = PersonForm(request.values, obj=person)
return render_template("person.html", object=person, form=form, page_title = page_title, alert=st.GetAlert(), message=st.GetMessage() )

105
refimg.py Normal file
View File

@@ -0,0 +1,105 @@
from wtforms import SubmitField, StringField, HiddenField, FileField, validators, Form
from flask_wtf import FlaskForm
from flask import request, render_template, redirect
from main import db, app, ma
from sqlalchemy import Sequence
from sqlalchemy.exc import SQLAlchemyError
from status import st, Status
import os
################################################################################
# Class describing Refimg in the database, and via sqlalchemy, connected to the DB as well
################################################################################
class Refimg(db.Model):
id = db.Column(db.Integer, db.Sequence('refimg_id_seq'), primary_key=True )
fname = db.Column(db.String(256), unique=True, nullable=False)
def __repr__(self):
return "<tag: {}, fname: {}>".format(self.id, self.fname )
################################################################################
# Helper class that inherits a .dump() method to turn class Refimg into json / useful in jinja2
################################################################################
class RefimgSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Refimg
ordered = True
################################################################################
# Helper class that defines a form for refimg, used to make html <form>, with field validation (via wtforms)
################################################################################
class RefimgForm(FlaskForm):
id = HiddenField()
fname = StringField('File name:', [validators.DataRequired()])
refimg_file = FileField('File name:')
submit = SubmitField('Save' )
delete = SubmitField('Delete' )
################################################################################
# Routes for refimg data
#
# /refimgs -> GET only -> prints out list of all refimgs
################################################################################
@app.route("/refimgs", methods=["GET"])
def refimgs():
refimgs = Refimg.query.all()
return render_template("refimgs.html", refimgs=refimgs, alert=st.GetAlert(), message=st.GetMessage() )
################################################################################
# /refimg -> GET/POST -> creates a new refimg type and when created, takes you back to /refimgs
################################################################################
@app.route("/refimg", methods=["GET", "POST"])
def new_refimg():
form = RefimgForm(request.form)
page_title='Create new Reference Image'
if request.method == 'GET':
return render_template("refimg.html", form=form, page_title=page_title )
else:
# save the actual uploaded image to reference_images/
f=request.files['refimg_file']
f.save(os.path.join("reference_images/", request.form["fname"]))
# now save into the DB
refimg = Refimg( fname=request.form["fname"] )
try:
db.session.add(refimg)
db.session.commit()
st.SetMessage( "Created new Refimg ({})".format(refimg.fname) )
return redirect( '/refimgs' )
except SQLAlchemyError as e:
st.SetAlert( "danger" )
st.SetMessage( "<b>Failed to add Refimg:</b>&nbsp;{}".format(e.orig) )
return render_template("refimg.html", form=form, page_title=page_title, alert=st.GetAlert(), message=st.GetMessage() )
################################################################################
# /refimg/<id> -> GET/POST(save or delete) -> shows/edits/delets a single
# refimg
################################################################################
@app.route("/refimg/<id>", methods=["GET", "POST"])
def refimg(id):
form = RefimgForm(request.form)
page_title='Edit Reference Image'
if request.method == 'POST':
try:
refimg = Refimg.query.get(id)
os.remove("reference_images/{}".format(refimg.fname) )
if 'delete' in request.form:
st.SetMessage("Successfully deleted Refimg: ({})".format( refimg.fname ) )
refimg = Refimg.query.filter(Refimg.id==id).delete()
if 'submit' in request.form and form.validate():
st.SetMessage("Successfully Updated Refimg: (From: {})".format(refimg.fname))
refimg.fname = request.form['fname']
st.AppendMessage(" To: ({})".format(refimg.fname) )
# save the actual uploaded image to reference_images/
f=request.files['refimg_file']
f.save(os.path.join("reference_images/", request.form["fname"]))
db.session.commit()
return redirect( '/refimgs' )
except SQLAlchemyError as e:
st.SetAlert( "danger" )
st.SetMessage( "<b>Failed to modify Refimg:</b>&nbsp;{}".format(e.orig) )
return render_template("refimg.html", form=form, page_title=page_title, alert=st.GetAlert(), message=st.GetMessage() )
else:
refimg = Refimg.query.get(id)
form = RefimgForm(request.values, obj=refimg)
return render_template("refimg.html", object=refimg, form=form, page_title = page_title, alert=st.GetAlert(), message=st.GetMessage() )

View File

@@ -1,4 +1,4 @@
from wtforms import SubmitField, StringField, HiddenField, validators, Form from wtforms import SubmitField, StringField, FloatField, HiddenField, validators, Form
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask import request, render_template, redirect from flask import request, render_template, redirect
from main import db, app, ma from main import db, app, ma
@@ -11,13 +11,13 @@ from status import st, Status
################################################################################ ################################################################################
class Settings(db.Model): class Settings(db.Model):
id = db.Column(db.Integer, db.Sequence('settings_id_seq'), primary_key=True ) id = db.Column(db.Integer, db.Sequence('settings_id_seq'), primary_key=True )
name = db.Column(db.String, unique=True, nullable=True ) import_path = db.Column(db.String)
value = db.Column(db.String, unique=True, nullable=True ) last_import_date = db.Column(db.Float)
def __repr__(self): def __repr__(self):
return "<id: {}, name: {}, value: {}>".format(self.id, self.name, self.value) return "<id: {}, import_path: {}, last_import_date: {}>".format(self.id, self.import_path, self.last_import_date)
############################################################################### ################################################################################
# Helper class that inherits a .dump() method to turn class Author into json / useful in jinja2 # Helper class that inherits a .dump() method to turn class Author into json / useful in jinja2
################################################################################ ################################################################################
class SettingsSchema(ma.SQLAlchemyAutoSchema): class SettingsSchema(ma.SQLAlchemyAutoSchema):
@@ -25,7 +25,7 @@ class SettingsSchema(ma.SQLAlchemyAutoSchema):
model = Settings model = Settings
ordered = True ordered = True
settings_schema=SettingsSchema() settings_schema = SettingsSchema(many=True)
################################################################################ ################################################################################
# Helper class that defines a form for Settings, used to make html <form> # Helper class that defines a form for Settings, used to make html <form>
@@ -33,41 +33,36 @@ settings_schema=SettingsSchema()
################################################################################ ################################################################################
class SettingsForm(FlaskForm): class SettingsForm(FlaskForm):
id = HiddenField() id = HiddenField()
name = StringField('Name:', [validators.DataRequired()]) import_path = StringField('Path to import from:', [validators.DataRequired()])
value = StringField('value:', [validators.DataRequired()]) last_import_date = FloatField('Date of last import:', [validators.DataRequired()])
submit = SubmitField('Save' ) submit = SubmitField('Save' )
################################################################################ ################################################################################
# /settings -> show current settings # /settings -> show current settings
################################################################################ ################################################################################
@app.route("/settings", methods=["GET"]) @app.route("/settings", methods=["GET", "POST"])
def settings(): def settings():
sets = Settings.query.all()
form = SettingsForm()
page_title='Show Settings'
return render_template("settings.html", objects=sets, form=form, page_title = page_title, alert=st.GetAlert(), message=st.GetMessage() )
################################################################################
# /setting/<id> -> show current settings
################################################################################
@app.route("/setting/<id>", methods=["GET", "POST"])
def setting(id):
form = SettingsForm(request.form) form = SettingsForm(request.form)
page_title='Settings'
if request.method == 'POST' and form.validate(): if request.method == 'POST' and form.validate():
try: try:
# HACK, I don't really need an id here, but sqlalchemy get weird
# without one, so just grab the id of the only row there, it will
# do...
id = Settings.query.all()[0].id
s = Settings.query.get(id) s = Settings.query.get(id)
if 'submit' in request.form: if 'submit' in request.form:
st.SetMessage("Successfully Updated Setting (name={})".format(s.name) ) st.SetMessage("Successfully Updated Settings" )
s.name = request.form['name'] s.import_path = request.form['import_path']
s.value = request.form['value'] s.last_import_date = request.form['last_import_date']
db.session.commit() db.session.commit()
return redirect( '/settings' ) return redirect( '/settings' )
except SQLAlchemyError as e: except SQLAlchemyError as e:
st.SetAlert( "danger" ) st.SetAlert( "danger" )
st.SetMessage( "<b>Failed to modify Setting:</b>&nbsp;{}".format(e.orig) ) st.SetMessage( "<b>Failed to modify Setting:</b>&nbsp;{}".format(e.orig) )
return render_template("edit_setting.html", form=form, page_title=page_title, alert=st.GetAlert(), message=st.GetMessage() ) return render_template("settings.html", form=form, page_title=page_title, alert=st.GetAlert(), message=st.GetMessage() )
else: else:
s = Settings.query.get(id) tmp_sets = Settings.query.all()
page_title='Edit Setting: {}'.format(s.name) sets = settings_schema.dump( tmp_sets )
form = SettingsForm(request.values, obj=s) form = SettingsForm(obj=tmp_sets[0])
return render_template("setting.html", objects=s, form=form, page_title = page_title, alert=st.GetAlert(), message=st.GetMessage() ) return render_template("settings.html", form=form, page_title = page_title, alert=st.GetAlert(), message=st.GetMessage() )

5
templates/aistats.html Normal file
View File

@@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block main_content %}
<h3>Placeholder</h3>
{% endblock main_content %}

View File

@@ -49,15 +49,37 @@
<a class="dropdown-item" href="{{url_for('file_list')}}">View Details</a> <a class="dropdown-item" href="{{url_for('file_list')}}">View Details</a>
</div> </div>
</div class="nav-item dropdown"> </div class="nav-item dropdown">
<div class="nav-item dropdown">
<a class="nav-item dropdown nav-link dropdown-toggle" href="#" id="PersonMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Person</a>
<div class="dropdown-menu" aria-labelledby="AIMenuPerson">
<a class="dropdown-item" href="{{url_for('new_person')}}">Create Person</a>
<a class="dropdown-item" href="{{url_for('persons')}}">Show People</a>
</div>
</div class="nav-item dropdown">
<div class="nav-item dropdown">
<a class="nav-item dropdown nav-link dropdown-toggle" href="#" id="RefMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Ref Image</a>
<div class="dropdown-menu" aria-labelledby="AIMenu">
<a class="dropdown-item" href="{{url_for('new_refimg')}}">Create Reference Image</a>
<a class="dropdown-item" href="{{url_for('refimgs')}}">View Reference Images</a>
</div>
</div class="nav-item dropdown">
<div class="nav-item dropdown">
<a class="nav-item dropdown nav-link dropdown-toggle" href="#" id="AIMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">AI</a>
<div class="dropdown-menu" aria-labelledby="AIMenu">
<a class="dropdown-item" href="{{url_for('aistats')}}">View Stats</a>
</div>
</div class="nav-item dropdown">
<div class="nav-item dropdown"> <div class="nav-item dropdown">
<a class="nav-item dropdown nav-link dropdown-toggle" href="#" id="AdminMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Admin</a> <a class="nav-item dropdown nav-link dropdown-toggle" href="#" id="AdminMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Admin</a>
<div class="dropdown-menu" aria-labelledby="AdminMenu"> <div class="dropdown-menu" aria-labelledby="AdminMenu">
<a class="dropdown-item" href="{{url_for('settings')}}">Settings</a> <a class="dropdown-item" href="{{url_for('settings')}}">Edit Settings</a>
<a class="dropdown-item" href="{{url_for('scannow')}}">Scan now (for new files)</a>
<a class="dropdown-item" href="{{url_for('forcescan')}}">Force Scan (delete data & rebuild)</a>
</div class="dropdow-menu"> </div class="dropdow-menu">
</div class="nav-item dropdown"> </div class="nav-item dropdown">
</div clas="navbar-nav"> </div clas="navbar-nav">
<form class="form-inline my-2 my-lg-0" method="POST" action="/search"> <form class="form-inline my-2 my-lg-0" method="POST" action="/search">
<input class="form-control mr-sm-2" type="search" placeholder="Search by title" aria-label="Search" name="term"> <input class="form-control mr-sm-2" type="search" placeholder="by file, date (YYYMMDD) or tag" aria-label="Search" name="term">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form> </form>
</div class="collapse navbar-collapse"> </div class="collapse navbar-collapse">

View File

@@ -2,24 +2,37 @@
<div class="container"> <div class="container">
<h3 class="offset-lg-2">{{page_title}} -- {{file_data.view_path}}</h3> <h3 class="offset-lg-2">{{page_title}} -- {{file_data.view_path}}</h3>
<div class="row"> <div class="row">
<table class="table table table-sm col-xl-12"> <table class="table table-striped table-sm col-xl-12">
<thead><tr class="thead-light"><th>Name</th><th>Size (MB)</th><th>Hash</th></tr></thead><tbody> <thead><tr class="thead-light"><th>Name</th><th>Size (MB)</th><th>Path Prefix</th><th>Hash</th></tr></thead><tbody>
{% for obj in file_data.view_list %} {% for obj in file_data.view_list %}
<tr><td> <tr><td>
{% if obj.type=="Directory" %} {% if obj.type == "Directory" %}
<i class="fas fa-folder"></i> <i style="font-size:48;" class="fas fa-folder"></i><br><span class="figure-caption">{{obj.name}}</span>
{% elif obj.type=="Image" %} {% else %}
<i class="fas fa-file-image"></i> <figure class="figure" font-size: 24px;>
{% elif obj.type=="Video" %} <div style="position:relative; width:100%">
<i class="fas fa-file-video"></i> {% if obj.type=="Image" %}
{% else %} {% set icon="fa-file-image" %}
<i class="fas fa-question-circle"></i> <a href="{{obj.path_prefix}}/{{obj.name}}">
{% endif %} {% elif obj.type == "Video" %}
{% if obj.type=="Image" %} {% set icon="fa-film" %}
<a href="{{file_data.symlink}}/{{obj.name}}"><img width="128" height="128" src="data:image/jpeg;base64,{{obj.thumbnail}}"></img></a> {% elif obj.type == "Directory" %}
{% endif %} {% set icon="fa-folder" %}
{{obj.name}} {% else %}
</td></td><td>{{obj.size_mb}}</td><td>{{obj.hash}}</tr> {% set icon="fa-question-circle" %}
{% endif %}
<img class="thumb" style="display:block" height="48" src="data:image/jpeg;base64,{{obj.thumbnail}}"></img>
{% if obj.type=="Image" %}
</a>
{% endif %}
<div style="position:absolute; top: 2; left: 2;">
<i style="font-size:16;background-color:black;color:white" class="fas {{icon}}"></i>
</div>
</div>
<figcaption class="figure-caption">{{obj.name}}</figcaption>
</figure>
{% endif %}
</td></td><td>{{obj.size_mb}}</td><td>{{obj.path_prefix}}</td><td>{{obj.hash}}</tr>
{% endfor %} {% endfor %}
</tbody></table> </tbody></table>
</div class="row"> </div class="row">

View File

@@ -7,30 +7,36 @@
<button class="btn btn-outline-info disabled" disabled>Size:</button> <button class="btn btn-outline-info disabled" disabled>Size:</button>
</div> </div>
<div class="input-group-append"> <div class="input-group-append">
<button id="sz-but-64" class="sz-but btn btn-outline-info" onClick="ChangeSize(64)">XS</button> <button class="sz-but btn btn-outline-info" onClick="ChangeSize(this,64)">XS</button>
</div> </div>
<div class="input-group-append"> <div class="input-group-append">
<button id="sz-but-96" class="sz-but btn btn-outline-info" onClick="ChangeSize(96)">S</button> <button class="sz-but btn btn-outline-info" onClick="ChangeSize(this,96)">S</button>
</div> </div>
<div class="input-group-append"> <div class="input-group-append">
<button id="sz-but-128" class="sz-but btn btn-info" onClick="ChangeSize(128)">M</button> <button class="sz-but btn btn-info" onClick="ChangeSize(this,128)">M</button>
</div> </div>
<div class="input-group-append"> <div class="input-group-append">
<button id="sz-but-192" class="sz-but btn btn-outline-info" onClick="ChangeSize(192)">L</button> <button class="sz-but btn btn-outline-info" onClick="ChangeSize(this,192)">L</button>
</div> </div>
<div class="input-group-append"> <div class="input-group-append">
<button id="sz-but-256" class="sz-but btn btn-outline-info" onClick="ChangeSize(256)">XL</button> <button class="sz-but btn btn-outline-info" onClick="ChangeSize(this,256)">XL</button>
</div> </div>
</div> </div>
<br>
<div class="row"> <div class="row">
{% for obj in file_data.view_list %} {% for obj in file_data.view_list %}
{% if obj.type != "Directory" %} {% if obj.type != "Directory" %}
<center> <center>
<figure class="figure px-2" font-size: 24px;> <figure class="figure px-2" font-size: 24px;>
{% if obj.type=="Image" %} {% if obj.type=="Image" %}
<a href="{{file_data.symlink}}/{{obj.name}}"><img class="thumb" height="128" src="data:image/jpeg;base64,{{obj.thumbnail}}"></img></a> <a href="{{obj.path_prefix}}/{{obj.name}}"><img class="thumb" height="128" src="data:image/jpeg;base64,{{obj.thumbnail}}"></img></a>
{% elif obj.type == "Video" %} {% elif obj.type == "Video" %}
<img class="thumb" height="128" src="data:image/jpeg;base64,{{obj.thumbnail}}"></img> <div style="position:relative; width:100%">
<img class="thumb" style="display:block" height="128" src="data:image/jpeg;base64,{{obj.thumbnail}}"></img>
<div style="position:absolute; top: 2; left: 2;">
<i style="font-size:32;background-color:black;color:white" class="fas fa-film"></i>
</div>
</div>
{% endif %} {% endif %}
<figcaption class="figure-caption text-center">{{obj.name}}</figcaption> <figcaption class="figure-caption text-center">{{obj.name}}</figcaption>
</figure> </figure>
@@ -42,10 +48,11 @@
{% endblock main_content %} {% endblock main_content %}
{% block script_content %} {% block script_content %}
<script> <script>
function ChangeSize(sz) function ChangeSize(clicked_button,sz)
{ {
console.log(clicked_button)
old_but=$('.sz-but.btn-info').removeClass('btn-info').addClass('btn-outline-info') old_but=$('.sz-but.btn-info').removeClass('btn-info').addClass('btn-outline-info')
$('#sz-but-'+sz).addClass('btn-info').removeClass('btn-outline-info') $(clicked_button).addClass('btn-info').removeClass('btn-outline-info')
$('.thumb').attr( {height: sz, style: 'font-size:'+sz } ) $('.thumb').attr( {height: sz, style: 'font-size:'+sz } )
} }
</script> </script>

View File

@@ -8,7 +8,7 @@
{{field}}<br> {{field}}<br>
{% elif field.type != 'SubmitField' %} {% elif field.type != 'SubmitField' %}
<div class="form-row col-lg-12"> <div class="form-row col-lg-12">
{{ field.label( class="col-lg-2" ) }} {{ field.label( class="col-lg-4" ) }}
{{ field( class="form-control col-lg-4" ) }} {{ field( class="form-control col-lg-4" ) }}
</div class="form-row col-lg-12"> </div class="form-row col-lg-12">
{% endif %} {% endif %}
@@ -17,7 +17,7 @@
<br> <br>
</div class="row"> </div class="row">
<div class="form-row col-lg-12"> <div class="form-row col-lg-12">
{{ form.submit( class="btn btn-primary offset-lg-2 col-lg-2" )}} {{ form.submit( class="btn btn-primary offset-lg-4 col-lg-2" )}}
{% if 'Edit' in page_title %} {% if 'Edit' in page_title %}
{{ form.delete( class="btn btn-outline-danger col-lg-2" )}} {{ form.delete( class="btn btn-outline-danger col-lg-2" )}}
{% endif %} {% endif %}

16
templates/persons.html Normal file
View File

@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block main_content %}
<h3>Show All People</h3>
<table id="person_table" class="table table-striped table-sm" data-toolbar="#toolbar" data-search="true">
<thead>
<tr class="thead-light"><th>Tag</th><th>Firstname(s)</th><th>Surname</th></tr>
</thead>
<tbody>
{% for person in persons %}
<tr><td><a href="{{url_for('person', id=person.id )}}">{{person.tag}}</td><td>{{person.firstname}}</td>
<td>{{person.surname}}</td></tr>
{% endfor %}
</tbody>
</table>
{% endblock main_content %}

42
templates/refimg.html Normal file
View File

@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block main_content %}
<div class="container">
<h3 class="offset-lg-2">{{page_title}}</h3>
<div class="row">
<form class="form form-inline col-xl-12" action="" method="POST" enctype="multipart/form-data">
{{form.id}}
{{form.csrf_token}}
<div class="form-row col-lg-12">
{{ form.fname.label( class="col-lg-2" ) }}
{{ form.fname( id="fname", class="form-control col-lg-4" ) }}
&nbsp;
<label class="btn btn-outline-primary col-lg-2">
Choose File
<input name="refimg_file" type="file" onChange="DoMagic()" style="display:none;" id="new_file_chooser">
</label>
</div class="form-row col-lg-12">
<div class="row col-lg-12">
<br>
</div class="row">
<div class="form-row col-lg-12">
{{ form.submit( class="btn btn-primary offset-lg-2 col-lg-2" )}}
{% if 'Edit' in page_title %}
{{ form.delete( class="btn btn-outline-danger col-lg-2" )}}
{% endif %}
</div class="form-row">
</form>
</div class="row">
</div class="container">
{% endblock main_content %}
{% block script_content %}
<script>
function DoMagic() {
str=$("#new_file_chooser").val()
console.log(str)
str=str.replace('C:\\fakepath\\', '' )
console.log(str)
$("#fname").val(str)
}
</script>
{% endblock script_content %}

15
templates/refimgs.html Normal file
View File

@@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block main_content %}
<h3>Show All Reference Images</h3>
<table id="refimg_table" class="table table-striped table-sm" data-toolbar="#toolbar" data-search="true">
<thead>
<tr class="thead-light"><th>Filename</th></tr>
</thead>
<tbody>
{% for refimg in refimgs %}
<tr><td><a href="{{url_for('refimg', id=refimg.id )}}">{{refimg.fname}}</td></tr>
{% endfor %}
</tbody>
</table>
{% endblock main_content %}

View File

@@ -1,25 +0,0 @@
{% extends "base.html" %} {% block main_content %}
<div class="container">
<h3 class="offset-lg-2">{{page_title}}</h3>
<div class="row">
<form class="form form-inline col-xl-12" action="" method="POST">
{% for field in form %}
{% if field.type == 'HiddenField' or field.type == 'CSRFTokenField' %}
{{field}}
{% elif field.type != 'SubmitField' %}
<div class="form-row col-lg-12">
{{ field.label( class="col-lg-2" ) }}
{{ field( class="form-control col-lg-4" ) }}
</div class="form-row col-lg-12">
{% endif %}
{% endfor %}
<div class="row col-lg-12">
<br>
</div class="row">
<div class="form-row col-lg-12">
{{ form.submit( class="btn btn-primary offset-lg-2 col-lg-2" )}}
</div class="form-row">
</form>
</div class="row">
</div class="container">
{% endblock main_content %}

View File

@@ -1,13 +1,22 @@
{% extends "base.html" %} {% block main_content %} {% extends "base.html" %} {% block main_content %}
<div class="container"> <div class="container">
<h3 class="offset-lg-2">{{page_title}}</h3> <h3 class="offset-lg-2">{{page_title}}</h3>
<div class="row"> <div class="row">
<table id="settings_tbl" class="table table-sm"> <form class="form form-inline col-xl-12" action="" method="POST">
<thead><tr class="thead-light"><th>Name</th><th>Value</th></tr></thead><tbody> {% for field in form %}
{% for obj in objects %} {% if field.type == 'HiddenField' or field.type == 'CSRFTokenField' %}
<tr><td><a href="{{url_for('setting', id=obj.id)}}">{{obj.name}}</a></td><td>{{obj.value}}</td></tr> {{field}}<br>
{% endfor %} {% elif field.type != 'SubmitField' %}
</tbody></table> <div class="form-row col-lg-12">
</div class="row"> {{ field.label( class="col-lg-2" ) }}
</div class="container"> {{ field( class="form-control col-lg-10" ) }}
</div class="form-row col-lg-12">
{% endif %}
{% endfor %}
<div class="form-row col-lg-12">
{{form.submit(class="btn btn-primary offset-lg-2 col-lg-2" )}}
</div class="row">
</div class="form">
</div class="row">
</div class="container">
{% endblock main_content %} {% endblock main_content %}