Compare commits
8 Commits
f369b6d796
...
846bdc4e52
| Author | SHA1 | Date | |
|---|---|---|---|
| 846bdc4e52 | |||
| 541cbec0de | |||
| 071164d381 | |||
| 1adb1bc7af | |||
| 747f524343 | |||
| 4feae96511 | |||
| e6c9429466 | |||
| 005b5be2b9 |
4
TODO
4
TODO
@@ -1,5 +1,9 @@
|
|||||||
###
|
###
|
||||||
#
|
#
|
||||||
|
# fix all face, right-click options:
|
||||||
|
# [DONE] add to new, add to existing
|
||||||
|
# [TODO] 4 x override*
|
||||||
|
#
|
||||||
#1 get override data into view
|
#1 get override data into view
|
||||||
# also all the add ref img/add override, etc are non-functional - FIX the override* stuff first to get table/naming consistency as that is half the problem
|
# also all the add ref img/add override, etc are non-functional - FIX the override* stuff first to get table/naming consistency as that is half the problem
|
||||||
# NMO data -> there is an NMO object (just NMO names/types - |json), then there is per face level data - this should be a reference from Face and Schema/marshmallow
|
# NMO data -> there is an NMO object (just NMO names/types - |json), then there is per face level data - this should be a reference from Face and Schema/marshmallow
|
||||||
|
|||||||
44
files.py
44
files.py
@@ -267,25 +267,6 @@ FOT_Schema = FaceOverrideTypeSchema(many=True)
|
|||||||
path_Schema = PathSchema(many=True)
|
path_Schema = PathSchema(many=True)
|
||||||
person_Schema = PersonSchema(many=True)
|
person_Schema = PersonSchema(many=True)
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# util function to just update the current/first/last positions needed for
|
|
||||||
# viewing / using pa_user_state DB table
|
|
||||||
################################################################################
|
|
||||||
def UpdatePref( pref, OPT ):
|
|
||||||
last_used=datetime.now(pytz.utc)
|
|
||||||
if OPT.current>0:
|
|
||||||
pref.current=OPT.current
|
|
||||||
if OPT.first_eid>0:
|
|
||||||
pref.first_eid=OPT.first_eid
|
|
||||||
if OPT.last_eid>0:
|
|
||||||
pref.last_eid=OPT.last_eid
|
|
||||||
if OPT.num_entries>0:
|
|
||||||
pref.num_entries=OPT.num_entries
|
|
||||||
pref.last_used=last_used
|
|
||||||
db.session.add(pref)
|
|
||||||
db.session.commit()
|
|
||||||
return
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# /get_entries_by_ids -> route where we supply list of entry ids (for next/prev
|
# /get_entries_by_ids -> route where we supply list of entry ids (for next/prev
|
||||||
# page of data we want to show). Returns json of all matching entries
|
# page of data we want to show). Returns json of all matching entries
|
||||||
@@ -512,8 +493,6 @@ def files_rbp():
|
|||||||
def search(search_term):
|
def search(search_term):
|
||||||
OPT=States( request )
|
OPT=States( request )
|
||||||
OPT.search_term = search_term
|
OPT.search_term = search_term
|
||||||
OPT.folders = False
|
|
||||||
|
|
||||||
query_data=GetSearchQueryData( OPT )
|
query_data=GetSearchQueryData( OPT )
|
||||||
return render_template("files.html", page_title='View Files', search_term=search_term, query_data=query_data, OPT=OPT )
|
return render_template("files.html", page_title='View Files', search_term=search_term, query_data=query_data, OPT=OPT )
|
||||||
|
|
||||||
@@ -668,29 +647,6 @@ def view():
|
|||||||
data=db.session.execute(stmt).unique().scalars().all()
|
data=db.session.execute(stmt).unique().scalars().all()
|
||||||
return jsonify(entries_schema.dump(data))
|
return jsonify(entries_schema.dump(data))
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
"""
|
|
||||||
# process any overrides
|
|
||||||
for face in e.file_details.faces:
|
|
||||||
# now get any relevant override and store it in objs...
|
|
||||||
fnmo = FaceNoMatchOverride.query.filter(FaceNoMatchOverride.face_id==face.id).first()
|
|
||||||
if fnmo:
|
|
||||||
face.no_match_override=fnmo
|
|
||||||
mo = FaceForceMatchOverride.query.filter(FaceForceMatchOverride.face_id==face.id).first()
|
|
||||||
if mo:
|
|
||||||
mo.type = FaceOverrideType.query.filter( FaceOverrideType.name== 'Manual match to existing person' ).first()
|
|
||||||
face.manual_override=mo
|
|
||||||
|
|
||||||
NMO_data = FaceOverrideType.query.all()
|
|
||||||
setting = Settings.query.first()
|
|
||||||
imp_path = setting.import_path
|
|
||||||
st_path = setting.storage_path
|
|
||||||
bin_path = setting.recycle_bin_path
|
|
||||||
# print( f"BUG-DEBUG: /view/id GET route - OPT={OPT}, eids={eids}, current={int(id)} ")
|
|
||||||
return render_template("viewer.html", current=int(id), eids=eids, objs=objs, OPT=OPT, NMO_data=NMO_data, imp_path=imp_path, st_path=st_path, bin_path=bin_path )
|
|
||||||
"""
|
|
||||||
|
|
||||||
# route called from front/end - if multiple images are being transformed, each transorm == a separate call
|
# route called from front/end - if multiple images are being transformed, each transorm == a separate call
|
||||||
# to this route (and therefore a separate transorm job. Each reponse allows the f/e to check the
|
# to this route (and therefore a separate transorm job. Each reponse allows the f/e to check the
|
||||||
# specific transorm job is finished (/check_transform_job) which will be called (say) every 1 sec. from f/e
|
# specific transorm job is finished (/check_transform_job) which will be called (say) every 1 sec. from f/e
|
||||||
|
|||||||
@@ -614,9 +614,7 @@ function isLastPage(pageNumber)
|
|||||||
function getPageNumberForId(id) {
|
function getPageNumberForId(id) {
|
||||||
const idx = entryList.indexOf(id);
|
const idx = entryList.indexOf(id);
|
||||||
// should be impossible but jic
|
// should be impossible but jic
|
||||||
if (idx === -1) {
|
if (idx === -1) { return -1 }
|
||||||
return -1; // or null, if you prefer
|
|
||||||
}
|
|
||||||
return Math.floor(idx / OPT.how_many) + 1;
|
return Math.floor(idx / OPT.how_many) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -298,6 +298,23 @@ function OverrideForceMatch( person_id, key )
|
|||||||
} )
|
} )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function that handles the POSTed data that comes back when we add a
|
||||||
|
// reference image to a new or existing person (right-click on a face)
|
||||||
|
// used in success callbacks from CreatePersonAndRefimg() and AddRefimgTo()
|
||||||
|
function handleAddRefimgData(key, data)
|
||||||
|
{
|
||||||
|
document.viewing.file_details.faces[item[key].which_face].refimg=data.refimg
|
||||||
|
document.viewing.file_details.faces[item[key].which_face].refimg_lnk={}
|
||||||
|
// if we used this img, for now set distance to 0 - it is an exact match!
|
||||||
|
document.viewing.file_details.faces[item[key].which_face].refimg_lnk.face_distance=0.0
|
||||||
|
$('#dbox').modal('hide')
|
||||||
|
$('#faces').prop('checked',true)
|
||||||
|
DrawImg()
|
||||||
|
CheckForJobs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// when we right-click a face and make a new person, this code creates and
|
||||||
|
// associates the face
|
||||||
function CreatePersonAndRefimg( key )
|
function CreatePersonAndRefimg( key )
|
||||||
{
|
{
|
||||||
d='&face_id='+item[key].id
|
d='&face_id='+item[key].id
|
||||||
@@ -306,29 +323,17 @@ function CreatePersonAndRefimg( key )
|
|||||||
+'&surname='+$('#surname').val()
|
+'&surname='+$('#surname').val()
|
||||||
+'&refimg_data='+item[key].refimg_data
|
+'&refimg_data='+item[key].refimg_data
|
||||||
$.ajax({ type: 'POST', data: d, url: '/match_with_create_person',
|
$.ajax({ type: 'POST', data: d, url: '/match_with_create_person',
|
||||||
success: function(data) {
|
success: function(data) { handleAddRefimgData(key, data ) },
|
||||||
document.viewing.file_details.faces[item[key].which_face].refimg.person.tag=data.who
|
|
||||||
document.viewing.file_details.faces[item[key].which_face].facefile_lnk.face_distance=data.distance
|
|
||||||
$('#dbox').modal('hide')
|
|
||||||
$('#faces').prop('checked',true)
|
|
||||||
DrawImg()
|
|
||||||
CheckForJobs()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when we right-click a face and connect to an existing person, this connects
|
||||||
|
// the refimg and associates the face
|
||||||
function AddRefimgTo( person_id, key, search )
|
function AddRefimgTo( person_id, key, search )
|
||||||
{
|
{
|
||||||
d='&face_id='+item[key].id+'&person_id='+person_id+'&refimg_data='+item[key].refimg_data+'&search='+search
|
d='&face_id='+item[key].id+'&person_id='+person_id+'&refimg_data='+item[key].refimg_data+'&search='+search
|
||||||
$.ajax({ type: 'POST', data: d, url: '/add_refimg_to_person',
|
$.ajax({ type: 'POST', data: d, url: '/add_refimg_to_person',
|
||||||
success: function(data) {
|
success: function(data) { handleAddRefimgData(key, data ) },
|
||||||
document.viewing.file_details.faces[item[key].which_face].refimg.person.tag=data.who
|
|
||||||
document.viewing.file_details.faces[item[key].which_face].facefile_lnk.face_distance=data.distance
|
|
||||||
$('#dbox').modal('hide')
|
|
||||||
$('#faces').prop('checked',true)
|
|
||||||
DrawImg()
|
|
||||||
CheckForJobs()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
person.py
57
person.py
@@ -3,8 +3,9 @@ from flask_wtf import FlaskForm
|
|||||||
from flask import request, render_template, redirect, url_for, make_response, jsonify
|
from flask import request, render_template, redirect, url_for, make_response, jsonify
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from settings import Settings, AIModel
|
from settings import Settings, AIModel
|
||||||
from sqlalchemy import Sequence, func
|
from sqlalchemy import Sequence, func, select
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import joinedload
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from shared import GenFace, GenThumb, PA
|
from shared import GenFace, GenThumb, PA
|
||||||
@@ -114,7 +115,7 @@ def AddRefimgToPerson( filename, person ):
|
|||||||
SetFELog( f"<b>Failed to add Refimg:</b> {e.orig}", "danger" )
|
SetFELog( f"<b>Failed to add Refimg:</b> {e.orig}", "danger" )
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
SetFELog( f"<b>Failed to modify Refimg:</b> {e}", "danger" )
|
SetFELog( f"<b>Failed to modify Refimg:</b> {e}", "danger" )
|
||||||
return
|
return refimg
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# TempRefimgFile: helper function that takes data POST'd (from dialog box to
|
# TempRefimgFile: helper function that takes data POST'd (from dialog box to
|
||||||
@@ -182,9 +183,12 @@ def match_with_create_person():
|
|||||||
p = Person( tag=request.form["tag"], surname=request.form["surname"], firstname=request.form["firstname"] )
|
p = Person( tag=request.form["tag"], surname=request.form["surname"], firstname=request.form["firstname"] )
|
||||||
# add this fname (of temp refimg) to person
|
# add this fname (of temp refimg) to person
|
||||||
fname=TempRefimgFile( request.form['refimg_data'], p.tag )
|
fname=TempRefimgFile( request.form['refimg_data'], p.tag )
|
||||||
AddRefimgToPerson( fname, p )
|
r=AddRefimgToPerson( fname, p )
|
||||||
SetFELog( f"Created person: {p.tag}" )
|
SetFELog( f"Created person: {p.tag}" )
|
||||||
return make_response( jsonify( who=p.tag, distance='0.0' ) )
|
refimg_schema=RefimgSchema(many=False)
|
||||||
|
r_data=refimg_schema.dump(r)
|
||||||
|
|
||||||
|
return make_response( jsonify( refimg=r_data, who=p.tag, distance='0.0' ) )
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# /person/<id> -> GET/POST(save or delete) -> shows/edits/delets a single person
|
# /person/<id> -> GET/POST(save or delete) -> shows/edits/delets a single person
|
||||||
@@ -267,7 +271,7 @@ def add_refimg():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
SetFELog( f"<b>Failed to load reference image:</b> {e}", "danger" )
|
SetFELog( f"<b>Failed to load reference image:</b> {e}", "danger" )
|
||||||
|
|
||||||
AddRefimgToPerson( fname, person )
|
r=AddRefimgToPerson( fname, person )
|
||||||
return redirect( url_for( 'person', id=person.id) )
|
return redirect( url_for( 'person', id=person.id) )
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -289,6 +293,29 @@ def find_persons(who):
|
|||||||
|
|
||||||
return make_response( resp )
|
return make_response( resp )
|
||||||
|
|
||||||
|
class FaceRefimgLinkSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta: model = FaceRefimgLink
|
||||||
|
face_distance = ma.auto_field() # Explicitly include face_distance
|
||||||
|
load_instance = True
|
||||||
|
|
||||||
|
class PersonSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta: model=Person
|
||||||
|
load_instance = True
|
||||||
|
|
||||||
|
class RefimgSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model = Refimg
|
||||||
|
exclude = ('face',)
|
||||||
|
load_instance = True
|
||||||
|
person = ma.Nested(PersonSchema)
|
||||||
|
|
||||||
|
class FaceSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model=Face
|
||||||
|
exclude = ('face',)
|
||||||
|
load_instance = True
|
||||||
|
refimg = ma.Nested(RefimgSchema,allow_none=True)
|
||||||
|
refimg_lnk = ma.Nested(FaceRefimgLinkSchema,allow_none=True)
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# /add_refimg_to_person/ -> POST
|
# /add_refimg_to_person/ -> POST
|
||||||
@@ -296,12 +323,19 @@ def find_persons(who):
|
|||||||
@app.route("/add_refimg_to_person", methods=["POST"])
|
@app.route("/add_refimg_to_person", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def add_refimg_to_person():
|
def add_refimg_to_person():
|
||||||
f = Face.query.get( request.form['face_id'] )
|
stmt = select(Face).options( joinedload(Face.refimg_lnk) ).where(Face.id == request.form['face_id'])
|
||||||
p = Person.query.get( request.form['person_id'] )
|
f=db.session.execute(stmt).scalars().first()
|
||||||
|
stmt = select(Person).options( joinedload(Person.refimg) ).where(Person.id == request.form['person_id'])
|
||||||
|
p=db.session.execute(stmt).scalars().first()
|
||||||
|
|
||||||
# add this fname (of temp refimg) to person
|
# add this fname (of temp refimg) to person
|
||||||
fname=TempRefimgFile( request.form['refimg_data'], p.tag )
|
fname=TempRefimgFile( request.form['refimg_data'], p.tag )
|
||||||
AddRefimgToPerson( fname, p )
|
r=AddRefimgToPerson( fname, p )
|
||||||
|
|
||||||
|
# connect the refimg to the face in the db, now we have added this refimg to the person
|
||||||
|
frl=FaceRefimgLink( face_id=f.id, refimg_id=r.id, face_distance=0 )
|
||||||
|
db.session.add(frl)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
if request.form['search'] == "true":
|
if request.form['search'] == "true":
|
||||||
jex=[]
|
jex=[]
|
||||||
@@ -316,7 +350,12 @@ def add_refimg_to_person():
|
|||||||
jex.append( JobExtra( name=f"path_type", value=str(ptype.id) ) )
|
jex.append( JobExtra( name=f"path_type", value=str(ptype.id) ) )
|
||||||
job=NewJob( name="run_ai_on_path", num_files=0, wait_for=None, jex=jex, desc="Look for face(s) in storage path(s)" )
|
job=NewJob( name="run_ai_on_path", num_files=0, wait_for=None, jex=jex, desc="Look for face(s) in storage path(s)" )
|
||||||
|
|
||||||
return make_response( jsonify( who=p.tag, distance='0.0' ) )
|
refimg_schema=RefimgSchema(many=False)
|
||||||
|
r_data=refimg_schema.dump(r)
|
||||||
|
frl_schema=FaceRefimgLinkSchema(many=False)
|
||||||
|
frl_data=refimg_schema.dump(r)
|
||||||
|
|
||||||
|
return make_response( jsonify( refimg=r_data, frl=frl_data ) )
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# /add_force_match_override -> POST
|
# /add_force_match_override -> POST
|
||||||
|
|||||||
@@ -27,10 +27,9 @@ class PA_UserState(db.Model):
|
|||||||
folders = db.Column(db.Boolean, unique=False, nullable=False )
|
folders = db.Column(db.Boolean, unique=False, nullable=False )
|
||||||
root = db.Column(db.String, unique=False, nullable=False )
|
root = db.Column(db.String, unique=False, nullable=False )
|
||||||
cwd = db.Column(db.String, unique=False, nullable=False )
|
cwd = db.Column(db.String, unique=False, nullable=False )
|
||||||
search_term = db.Column(db.String, unique=False, nullable=False )
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<pa_user_dn: {self.pa_user_dn}, path_type: {self.path_type}, noo: {self.noo}, grouping: {self.grouping}, how_many: {self.how_many}, size: {self.size}, folders: {self.folders}, root: {self.root}, cwd: {self.cwd}, search_term: {self.orig_search_term}>"
|
return f"<pa_user_dn: {self.pa_user_dn}, path_type: {self.path_type}, noo: {self.noo}, grouping: {self.grouping}, how_many: {self.how_many}, size: {self.size}, folders: {self.folders}, root: {self.root}, cwd: {self.cwd}>"
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|||||||
@@ -50,11 +50,10 @@ CREATE TABLE pa_user(
|
|||||||
default_storage_folders BOOLEAN,
|
default_storage_folders BOOLEAN,
|
||||||
CONSTRAINT pk_pa_user_id PRIMARY KEY(id) );
|
CONSTRAINT pk_pa_user_id PRIMARY KEY(id) );
|
||||||
|
|
||||||
-- this is totally not 3rd normal form, but when I made it that, it was so complex, it was stupid
|
-- FIXME: NEED TO RETHINK THIS, not sure this even needs to be in the DB
|
||||||
-- so for the little data here, I'm deliberately doing a redundant data structure
|
|
||||||
CREATE TABLE pa_user_state ( id INTEGER, pa_user_dn VARCHAR(128), path_type VARCHAR(16),
|
CREATE TABLE pa_user_state ( id INTEGER, pa_user_dn VARCHAR(128), path_type VARCHAR(16),
|
||||||
noo VARCHAR(16), grouping VARCHAR(16), how_many INTEGER, size INTEGER, folders BOOLEAN,
|
noo VARCHAR(16), grouping VARCHAR(16), how_many INTEGER, size INTEGER, folders BOOLEAN,
|
||||||
root VARCHAR, cwd VARCHAR, search_term VARCHAR,
|
root VARCHAR, cwd VARCHAR,
|
||||||
CONSTRAINT fk_pa_user_dn FOREIGN KEY (pa_user_dn) REFERENCES pa_user(dn),
|
CONSTRAINT fk_pa_user_dn FOREIGN KEY (pa_user_dn) REFERENCES pa_user(dn),
|
||||||
CONSTRAINT pk_pa_user_states_id PRIMARY KEY(id ) );
|
CONSTRAINT pk_pa_user_states_id PRIMARY KEY(id ) );
|
||||||
|
|
||||||
|
|||||||
@@ -252,10 +252,6 @@
|
|||||||
var grayscale=0
|
var grayscale=0
|
||||||
var throbber=0
|
var throbber=0
|
||||||
|
|
||||||
var imp_path="static/Import/{{imp_path}}"
|
|
||||||
var st_path="static/Storage/{{st_path}}"
|
|
||||||
var bin_path="static/Bin/{{bin_path}}"
|
|
||||||
|
|
||||||
function PrettyFname(fname)
|
function PrettyFname(fname)
|
||||||
{
|
{
|
||||||
s='<span class="alert alert-secondary py-2">'
|
s='<span class="alert alert-secondary py-2">'
|
||||||
|
|||||||
@@ -56,32 +56,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div class="col-7">
|
</div class="col-7">
|
||||||
|
|
||||||
<div class="row pt-5">
|
|
||||||
<alert class="alert alert-warning">The following values are based on the defaults above and subsequent changes as you navigate the application and are not set by hand. The following content is for checking/debugging only.</alert>
|
|
||||||
</div class="row">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<table id="pa_user_state_tbl" class="table table-striped table-sm" data-toolbar="#toolbar" data-search="true">
|
|
||||||
<thead>
|
|
||||||
<tr class="table-primary"><th>Path</th><th>New or Oldest</th><th>How Many</th><th>Folders?</th><th>Group by</th><th>Thumb size</th><th>DB retrieve offset</th><th>Root</th><th>cwd</th></tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for st in states %}
|
|
||||||
<tr>
|
|
||||||
<td>{{st.path_type}}</td>
|
|
||||||
<td>{{st.noo}}</td>
|
|
||||||
<td>{{st.how_many}}</td>
|
|
||||||
<td>{{st.folders}}</td>
|
|
||||||
<td>{{st.grouping}}</td>
|
|
||||||
<td>{{st.size}}</td>
|
|
||||||
<td>{{st.root}}</td>
|
|
||||||
<td>{{st.cwd}}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div class="row">
|
|
||||||
</div class="container-fluid">
|
</div class="container-fluid">
|
||||||
{% endblock main_content %}
|
{% endblock main_content %}
|
||||||
{% block script_content %}
|
{% block script_content %}
|
||||||
|
|||||||
Reference in New Issue
Block a user