diff --git a/TODO b/TODO index e1b2630..cc08613 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,11 @@ ## GENERAL - * create a new table file_face_refimg_link: - file_id, face_enc, ref_img (can be null) + * allow rotate of image (permanently on FS, so its right everywhere) + + * improve photo browser -> view file, rather than just allowing browser to show image + * need AI code to: - scan for unknown faces, instead of storing array of all faces in FILE->FACES use table above one row per FILE & FACE with refimg_link as null to start - * when we do ai matching, we find all refimg is null (for a specific file) and match that - * need pa_job_mgr AI jobs to have low-level functions: + - make use of new FACE structures/links ... (code for this is in pa_job_manager, just not being called/used as yet) && something like: FindUnknownFacesInFile() MatchRefImgWithUnknownFace() Then create wrapper funcs: diff --git a/ai.py b/ai.py index 29b3fe3..d551b9b 100644 --- a/ai.py +++ b/ai.py @@ -10,6 +10,9 @@ from person import Person, PersonRefimgLink from refimg import Refimg from flask_login import login_required, current_user +from job import Job, JobExtra, Joblog, NewJob + + # pylint: disable=no-member ################################################################################ @@ -28,3 +31,22 @@ def aistats(): last_fname = e.name entry['people'].append( { 'tag': p.tag } ) return render_template("aistats.html", page_title='Placeholder', entries=entries) + + +################################################################################ +# /run_ai_on -> CAM: needs more thought (what actual params, e.g list of file - +# tick, but which face or faces? are we forcing a re-finding of unknown faces +# or just looking for matches? (maybe in the long run there are different +# routes, not params - stuff we will work out as we go) +################################################################################ +@app.route("/run_ai_on", methods=["POST"]) +@login_required +def run_ai_on(): + jex=[] + for el in request.form: + jex.append( JobExtra( name=f"{el}", value=request.form[el] ) ) + print( f"would create new job with extras={jex}" ) + job=NewJob( "run_ai_on", 0, None, jex ) + st.SetAlert("success") + st.SetMessage( f"Created Job #{job.id} to Look for face(s) in selected file(s)") + return render_template("base.html") diff --git a/pa_job_manager.py b/pa_job_manager.py index dcd1349..b601662 100644 --- a/pa_job_manager.py +++ b/pa_job_manager.py @@ -137,9 +137,13 @@ class Entry(Base): in_dir = relationship ("Dir", secondary="entry_dir_link", uselist=False ) def FullPathOnFS(self): - s=self.in_dir.in_path.path_prefix + '/' - if len(self.in_dir.rel_path) > 0: - s += self.in_dir.rel_path + '/' + if self.in_dir: + s=self.in_dir.in_path.path_prefix + '/' + if len(self.in_dir.rel_path) > 0: + s += self.in_dir.rel_path + '/' + # this occurs when we have a dir that is the root of a path + else: + s=self.dir_details.in_path.path_prefix+'/' s += self.name return s @@ -431,6 +435,8 @@ def RunJob(job): JobRestoreFiles(job) elif job.name == "processai": JobProcessAI(job) + elif job.name == "run_ai_on": + JobRunAIOn(job) else: print("ERROR: Requested to process unknown job type: {}".format(job.name)) # okay, we finished a job, so check for any jobs that are dependant on this and run them... @@ -940,6 +946,44 @@ def JobProcessAI(job): FinishJob(job, "Finished Processesing AI") return +def WrapperForScanFileForPerson(job, entry): + which_person=[jex.value for jex in job.extra if jex.name == "person"][0] + if which_person == "all": + ppl=session.query(Person).all() + else: + ppl=session.query(Person).filter(Person.tag==which_person).all() + for person in ppl: + print( f"(wrapper) call == ScanFileForPerson( {entry.id}, {person.id}, force=False )" ) + return + +def JobRunAIOn(job): + AddLogForJob(job, f"INFO: Starting looking For faces in files job...") + which_person=[jex.value for jex in job.extra if jex.name == "person"][0] + if which_person == "all": + ppl=session.query(Person).all() + else: + ppl=session.query(Person).filter(Person.tag==which_person).all() + print( "JobRunAIOn() called" ) + for person in ppl: + print( f"person={person.tag}" ) + + for jex in job.extra: + if 'eid-' in jex.name: + entry=session.query(Entry).get(jex.value) + print( f'en={entry}' ) + if entry.type.name == 'Directory': + ProcessFilesInDir( job, entry, WrapperForScanFileForPerson ) + elif entry.type.name == 'Image': + which_file=session.query(Entry).join(File).filter(Entry.id==jex.value).first() + print( f"JobRunAIOn: file to process had id: {which_file.id}" ) + for person in ppl: + print( f"call == ScanFileForPerson( {which_file.id}, {person.id}, force=False )" ) + else: + AddLogForJob( job, f'Not processing Entry: {entry.name} - not an image' ) + print(" HARD EXITING to keep testing " ) + exit( -1 ) + FinishJob(job, "Finished Processesing AI") + return def GenHashAndThumb(job, e): # commit every 100 files to see progress being made but not hammer the database @@ -1056,6 +1100,7 @@ def compareAI(known_encoding, unknown_encoding): def ProcessFilesInDir(job, e, file_func): if DEBUG==1: + print( f"???? e={e}" ) print( f"DEBUG: ProcessFilesInDir: {e.FullPathOnFS()}") if e.type.name != 'Directory': file_func(job, e) diff --git a/templates/files.html b/templates/files.html index 78c78c3..2802bc4 100644 --- a/templates/files.html +++ b/templates/files.html @@ -222,7 +222,7 @@ {% endif %} {# if this dir is the toplevel of the cwd, show the folder icon #} {% if dirname| TopLevelFolderOf(cwd) %} -
+
{{obj.name}}
@@ -270,6 +270,13 @@ function GetSelnAsData() return to_del } +function RunAIOnSeln(person) +{ + post_data = GetSelnAsData() + post_data += '&person='+person.replace('ai-','') + $.ajax({ type: 'POST', data: post_data, url: '/run_ai_on', success: function(data){ window.location='/'; return false; } }) +} + function DelDBox(del_or_undel) { to_del = GetSelnAsData() @@ -457,6 +464,34 @@ function NoSel() { $('.figure').click( function(e) { DoSel(e, this ); SetButtonState(); return false; }); $(document).on('click', function(e) { $('.highlight').removeClass('highlight') ; SetButtonState() }); + +// different context menu on directory +$.contextMenu({ + selector: '.dir', + build: function($triggerElement, e){ + if( NoSel() ) + DoSel(e, e.currentTarget ) + item_list = { + ai: { + name: "Scan file for faces", + items: { + {% for p in people %} + "ai-{{p.tag}}": {"name": "{{p.tag}}"}, + {% endfor %} + "ai-all": {"name": "all"}, + } + } + } + return { + callback: function( key, options) { + if( key.startsWith("ai")) { RunAIOnSeln(key) } + }, + items: item_list + }; + } +}); + +// different context menu on files $.contextMenu({ selector: '.figure', build: function($triggerElement, e){ @@ -474,6 +509,7 @@ $.contextMenu({ {% for p in people %} "ai-{{p.tag}}": {"name": "{{p.tag}}"}, {% endfor %} + "ai-all": {"name": "all"}, } } } @@ -493,7 +529,7 @@ $.contextMenu({ if( key == "move" ) { MoveDBox() } if( key == "del" ) { DelDBox('Delete') } if( key == "undel" ) { DelDBox('Restore') } - if( key.startsWith("ai")) { console.log( key +'was chosen')} + if( key.startsWith("ai")) { RunAIOnSeln(key) } }, items: item_list };