Skip to content

Commit

Permalink
Merge pull request #18 from cccraig/support-piwigo-11
Browse files Browse the repository at this point in the history
Support piwigo 11
  • Loading branch information
cccraig authored Aug 7, 2021
2 parents 65a35fc + e23fcec commit 4ea80b5
Show file tree
Hide file tree
Showing 27 changed files with 315 additions and 132 deletions.
Empty file modified .gitignore
100644 → 100755
Empty file.
Empty file modified LICENSE
100644 → 100755
Empty file.
18 changes: 16 additions & 2 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,26 @@ This is a fork of the [piwigo-facetag plugin](https://github.com/pommes-frites/p

6) Does not require Jquery (scripts or styles) if you're into that.

7) Will auto capitalize names, e.g. pinkie jenkins => Pinkie Jenkins.

8) Ability to save the cropped tag locally to your server (future automatic labeling).

## Use
MugShot works mostly the same as the original piwigo-facetag plugin. When activated, there will be a button that appears on the picture pages. The button looks like a man wearing a bowler hat. Click this button and then you can click and drag on the image to make a box around someones face. On mouseup, an input box will appear where you can type the persons name. As you type, a list of available names will appear once the number of possible matches is less than 10. You can click a name to select it or, if there is only one option, press enter. Pressing enter with the text input in focus will hide the tag but this is not necessary; Once you type the name you can immediately start selecting another face. Once you are finished, click the green "SAVE" button. To delete a tag, click the red "X" inside the tag bounding box, then click save. To modify a tag, double click the tag, edit the text, then click save. An FYI, the database trigger used in the old plugin is still there. Deleting tags will also delete the corresponding MugShot tag.

If a user does not belong to a group allowed to use MugShot, they will still be able to see tags created by other users but will have no editing capabilities and cannot create their own face tags. The script files that are loaded for these users simply do not have the required functions. Furthermore, the necessary ws API functions are disabled. Users must also be logged in to create tags.

I would eventually like to include some sort of automatic facial recognition and tagging component. I've added a small piece of code that will crop user faces based on the select frame and save the crop to a folder with 0760 permission. The code uses Imagick to do this and will simply ignore this section if Imagick is not found. This is an **optional** feature which defaults to off. Be forewarned it does slow things down a bit. Also, just to be clear, all the cropped images stay on your server. The facial recognition component will need to know who it's looking at which means it needs training data, i.e. the cropped images.

## Testing
Tested to work on the bootstrap_default, bootstrap_darkroom, dark, clear, and sylvia themes. Tested to be *partially* working on elegant and smartpocket desktop. MugShot tags created while using a fully working theme seem to display properly in these styles. *However*, when creating a new tag the mouse may not align properly, and/or the tag button is not visible due to the buttons html.

## Future
I would eventually like to include some sort of automatic facial recognition and tagging component. I've added a small piece of code that will crop user faces based on the select frame and save the crop to a folder with 0760 permission. The code uses Imagick to do this and will simply ignore this section if Imagick is not found. This is an **optional** feature which defaults to off. Be forewarned it does slow things down a bit. Also, just to be clear, all the cropped images stay on your server. The facial recognition component will need to know who it's looking at which means it needs training data, i.e. the cropped images.

Current plan is to use [DeepFace](https://github.com/serengil/deepface) for training the model. I found it pretty easy to use and you can train it based on the training data generated by MugShot in two lines of python.

```python
from deepface import DeepFace

DeepFace.stream("/<YourPiwigoInstall>/plugins/MugShot/training")
```
If you've labeled a few photos with yourself in them, you can run the above code with a webcam plugged in and it will identify you. What remains here is code to periodically rebuild the model (as more face tags are added) and then dynamically run it for images as they are uploaded. Things to think about include grabbing the ratio and position of the image at its current size. This is important for ensuring that when the image is resized in Piwigo the face label can be correspondingly updated.
66 changes: 32 additions & 34 deletions admin.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,61 +8,58 @@
/*
* Include some php files for functions and such.
*/
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
include_once(PHPWG_ROOT_PATH.'admin/include/tabsheet.class.php');
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');


$my_base_url = get_root_url().'admin.php?page=plugin-'.basename(dirname(__FILE__));

/*
* Exit if user status is not okay
*/
check_status(ACCESS_ADMINISTRATOR);
check_status(ACCESS_WEBMASTER);



/*
* Update the parameters and escape the serialized string.
*/
if(isset($_POST['save'])) {
unset($_POST['save']);
conf_update_param(MUGSHOT_ID, pwg_db_real_escape_string(serialize($_POST)));
array_push($page['infos'], l10n('Information data registered in database'));
}

// +-----------------------------------------------------------------------+
// | Tabssheet
// +-----------------------------------------------------------------------+
$tabsheet = new tabsheet();


/*
* Create tab for the admin page
*/
$tabs = array(
array(
'code' => 'config',
'label' => l10n('Configuration'),
),
);

$tab_codes = array_map(create_function('$a', 'return $a["code"];'), $tabs);
if (empty($conf['MugShot_tabs']))
{
$conf['MugShot_tabs'] = $tabs;
}

if (isset($_GET['tab']) && in_array($_GET['tab'], $tab_codes)) {
$page['tab'] = $_GET['tab'];
} else {
$page['tab'] = $tabs[0]['code'];
$page['tab'] = isset($_GET['tab']) ? $_GET['tab'] : $conf['MugShot_tabs'][0]['code'];

if (!in_array($page['tab'], $conf['MugShot_tabs'][0])) die('Hacking attempt!');

foreach ($conf['MugShot_tabs'] as $tab)
{
$tabsheet->add($tab['code'], $tab['label'], $my_base_url.'-'.$tab['code']);
}

$tabsheet->select($page['tab']);

$tabsheet->assign();



/*
* Assign the tabs to tabsheet
* Update the parameters and escape the serialized string.
*/
$tabsheet = new tabsheet();
foreach ($tabs as $tab)
{
$tabsheet->add(
$tab['code'],
$tab['label'],
MUGSHOT_BASE_URL.'-'.$tab['code']
);
}
$tabsheet->select($page['tab']);
$tabsheet->assign();
if(isset($_POST['save'])) {
unset($_POST['save']);
conf_update_param(MUGSHOT_ID, pwg_db_real_escape_string(serialize($_POST)));
array_push($page['infos'], l10n('Information data registered in database'));
}



Expand All @@ -82,7 +79,7 @@
*/
$template -> assign(
array(
'PLUGIN_ACTION' => get_root_url() . 'admin.php?page=plugin-MugShot-admin'
'PLUGIN_ACTION' => get_root_url() . 'admin.php?page=plugin-MugShot'
)
);

Expand All @@ -109,6 +106,7 @@
* Fetch groups for mugshot
*/
$query = 'SELECT id FROM '.GROUPS_TABLE.';';

$group_ids = query2array($query, null, 'id');

$template->assign(
Expand Down
37 changes: 12 additions & 25 deletions css/admin_style.css
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

.mugshot-icon {
font-family: 'mugshot' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
Expand All @@ -38,22 +37,11 @@
z-index: 1500;
}

.mugshot-frame {
margin: 0;
padding: 0;
position: absolute;
visibility: visible;
z-index: 1500;
}

#theImage:hover .mugshot-frame,
#theImage:hover .mugshot-active {
.mugshot-selecting .mugshot-frame,
.mugshot-active {
border: 2px solid #ccc;
border-radius: 2px;
}

#theImage:hover .mugshot-frame:not(.mugshot-active) .mugshot-frame-name {
display: block;
visibility: visible !important;
}

.mugshot-frame-name {
Expand Down Expand Up @@ -86,22 +74,14 @@
color: inherit;
}

.mugshot-active {
visibility: visible !important;
}

.mugshot-frame:hover .mugshot-delete {
.mugshot-selecting .mugshot-delete {
visibility: visible;
}

.mugshot-mousetrap {
pointer-events: none;
}

.mugshot-active {
visibility: visible !important;
}

.mugshot-textbox {
background: white;
color: black;
Expand Down Expand Up @@ -184,8 +164,9 @@
display: block !important;
}

#reflay,
#MugShotDiv {
/*border: 1px solid red;*/
/* border: 1px solid red; */
display: inline-block;
overflow: visible;
pointer-events: none;
Expand All @@ -196,3 +177,9 @@
overflow: hidden;
position: relative;
}

#overlay, .overlay {
position: absolute;
top: 0;
left: 0;
}
Empty file modified css/fonts/mugshot.eot
100644 → 100755
Empty file.
Empty file modified css/fonts/mugshot.svg
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified css/fonts/mugshot.ttf
100644 → 100755
Empty file.
Empty file modified css/fonts/mugshot.woff
100644 → 100755
Empty file.
Empty file modified css/selection.json
100644 → 100755
Empty file.
8 changes: 1 addition & 7 deletions css/style.css
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@
z-index: 1500;
}

#theImage:hover .mugshot-frame,
#theImage:hover .mugshot-active {
border: 2px solid #ccc;
border-radius: 2px;
}

#theImage:hover .mugshot-frame-name {
#MugShotDiv:hover .mugshot-frame-name {
display: block;
}

Expand Down
106 changes: 78 additions & 28 deletions include/capture.php
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
defined('MUGSHOT_PATH') or die('Hacking attempt!');

include_once(PHPWG_ROOT_PATH . 'admin/include/functions.php');
include_once(MUGSHOT_PATH . 'include/nn.training.thumbnails.php');

function add_mugshot_methods($arr) {
$service = &$arr[0];
Expand All @@ -14,6 +15,25 @@ function add_mugshot_methods($arr) {
);
}

/**
* Converts names to ucfirst case, as determined by a space between surnames.
*/
function get_pretty_name($labeledTagName) {
$safeName = pwg_db_real_escape_string($labeledTagName);

$splitName = explode(" ", $safeName);

$arraySize = count($splitName);

$prettyName = array();

for ($i=0; $i < $arraySize; $i++) {
$prettyName[$i] = ucfirst($splitName[$i]);
}

return implode(" ", $prettyName);
}

// Add the posted mugshots to the database
function book_mugshots($data, &$service) {

Expand All @@ -25,69 +45,99 @@ function book_mugshots($data, &$service) {
$plugin_config = unserialize(conf_get_param(MUGSHOT_ID));

unset($data['imageId']);
$tagSql = '';
$varString = '';
$dString = '';
$imageIdTagIdInsertionString = ''; //
$faceTagPositionsInsertionString = ''; // Variable string. Groups data for entry in SQL database.
$deleteTagQuery = ''; // Delete string. Tags in the current image being removed.

$totalImageTags = count($data);

foreach ($data as $key => $value) {
$tag = pwg_db_real_escape_string($value['tagId']);
$name = pwg_db_real_escape_string($value['name']);
$labeledTagName = get_pretty_name($value['name']);

// If something empty was submitted, just ignore it.
if ($labeledTagName == '') {
continue;
}

$existingTagId = pwg_db_real_escape_string($value['tagId']);
$top = pwg_db_real_escape_string($value['top']);
$left = pwg_db_real_escape_string($value['left']);
$width = pwg_db_real_escape_string($value['width']);
$height = pwg_db_real_escape_string($value['height']);
$imgW = pwg_db_real_escape_string($value['imageWidth']);
$imgH = pwg_db_real_escape_string($value['imageHeight']);
$rm = pwg_db_real_escape_string($value['removeThis']);
$tagName = ($tag == -1 && $name != '') ? tag_id_from_tag_name($name) : $tag;

// If it's a brand new tag, we won't have sent a tag ID back with the data.
$newTagId = ($existingTagId == -1) ? tag_id_from_tag_name($labeledTagName) : $existingTagId;

// Create or remove the training thumbnails, depending on webmaster settings.
if ($plugin_config['autotag']) {
$sql = "SELECT * FROM `". IMAGES_TABLE . "` WHERE `id`=".$imageId.";";

$imgData = pwg_db_fetch_assoc(pwg_query($sql));

// DeepFace requires the persons name and an appended integer for the count of the images, i.e. pinkie_jenkins5, pinkie_jenkins6.
// By adding the imageId (unique) and tagId (unique per image) we get a simple way of achieving this without doing
// an SQL query or counting the images in the directory. While $imgNumber is not guaranteed to be globally unique,
// it will always be unique within our directory structure which is ../pinkie_jenkins/pinkie_jenkins<ImgId><TagId>.
$imgNumber = ($existingTagId != -1) ? $imageId.$existingTagId : $imageId.$newTagId;

// Remove or add cropped faces in the images to a directory.
if(extension_loaded('imagick') === true && $rm == 0 && $width >= 40 && $height >= 40) {
crop_image_faces($imgData['path'], $imgNumber, $labeledTagName, $imgW, $imgH, $width, $height, $left, $top);
}

if($rm == 1) {
delete_image_faces($labeledTagName, $imgNumber);
}
}

// Remove a mugshot
if ($rm == 1) {
$dString .= ($tag != '') ? $tag . ',' : '';
$deleteTagQuery .= ($existingTagId != '') ? $existingTagId . ',' : '';
continue;
}

// Update a mugshot
if ($tag == -1 && $name != '') {
$varString .= "('" . $tagName . "','" . $imageId . "','" . $top . "','" . $left . "','";
$varString .= $width . "','" . $height . "','" . $imgW . "','" . $imgH . "'),";
$tagSql .= "('" . $imageId . "','" . $tagName . "'),";
} elseif ($tag > 0 && $name != '') {
$url = strtolower(str_replace(' ', '_', $name));
$sql = "UPDATE " . TAGS_TABLE . " AS tt SET tt.name='" . $name . "', tt.url_name='" . $url . "' WHERE tt.id='" . $tag . "';";
if ($existingTagId == -1) {
$faceTagPositionsInsertionString .= "('$newTagId','$imageId','$top','$left','$width','$height','$imgW','$imgH'),";
$imageIdTagIdInsertionString .= "('$imageId','$newTagId'),";
} elseif ($existingTagId > 0 && $labeledTagName != '') {
$url = strtolower(str_replace(' ', '_', $labeledTagName));
$sql = "UPDATE " . TAGS_TABLE . " AS tt SET tt.name='" . $labeledTagName . "', tt.url_name='" . $url . "' WHERE tt.id='" . $existingTagId . "';";
$r = pwg_query($sql);
}
}

// Add new mugshot
if ($varString !== '') {
$varString = substr(trim($varString), 0, -1);
if ($faceTagPositionsInsertionString !== '') {
$faceTagPositionsInsertionString = substr(trim($faceTagPositionsInsertionString), 0, -1);
$frameSql = "INSERT INTO " . MUGSHOT_TABLE . " (`tag_id`, `image_id`, `top`, `lft`, `width`, `height`, `image_width`, `image_height`) ";
$frameSql .= "VALUES " . $varString . " ON DUPLICATE KEY UPDATE `top`=VALUES(`top`), ";
$frameSql .= "`lft`=VALUES(`lft`), `width`=VALUES(`width`), `height`=VALUES(`height`), ";
$frameSql .= "`image_width`=VALUES(`image_width`), `image_height`=VALUES(`image_height`);";
$tagSql = substr(trim($tagSql), 0, -1);
$tagSql = "INSERT IGNORE INTO " . IMAGE_TAG_TABLE . " (`image_id`, `tag_id`) VALUES " . $tagSql . ';';
$tagResult = pwg_query($tagSql);
$frameSql .= "VALUES " . $faceTagPositionsInsertionString . " ON DUPLICATE KEY UPDATE `top`=VALUES(`top`), ";
$frameSql .= "`lft`=VALUES(`lft`), `width`=VALUES(`width`), `height`=VALUES(`height`), `image_width`=VALUES(`image_width`), `image_height`=VALUES(`image_height`);";
$imageIdTagIdInsertionString = substr(trim($imageIdTagIdInsertionString), 0, -1);
$imageIdTagIdInsertionString = "INSERT IGNORE INTO " . IMAGE_TAG_TABLE . " (`image_id`, `tag_id`) VALUES " . $imageIdTagIdInsertionString . ';';
$existingTagIdResult = pwg_query($imageIdTagIdInsertionString);
$frameResult = pwg_query($frameSql);
} else {
$tagResult = true;
$existingTagIdResult = true;
$frameResult = true;
}

// Delete mugshot
if ($dString !== '') {
$dString = '(' . substr(trim($dString), 0, -1) . ')';
$deleteSql1 = "DELETE FROM `face_tag_positions` WHERE `tag_id` IN " . $dString . " AND `image_id`=".$imageId.";";
$deleteSql2 = "DELETE FROM " . IMAGE_TAG_TABLE . " WHERE `tag_id` IN " . $dString . ";";
if ($deleteTagQuery !== '') {
$deleteTagQuery = '(' . substr(trim($deleteTagQuery), 0, -1) . ')';
$deleteSql1 = "DELETE FROM " . MUGSHOT_TABLE . " WHERE `tag_id` IN $deleteTagQuery AND `image_id`='$imageId';";
$deleteSql2 = "DELETE FROM " . IMAGE_TAG_TABLE . " WHERE `tag_id` IN $deleteTagQuery;";
$dResult1 = pwg_query($deleteSql1);
$dResult2 = pwg_query($deleteSql2);
} else {
$dResult1 = true;
$dResult2 = true;
}

return json_encode([$tagResult, $frameResult, $dResult1, $dResult2]);
return json_encode([$existingTagIdResult, $frameResult, $dResult1, $dResult2]);
}


Expand Down
Loading

0 comments on commit 4ea80b5

Please sign in to comment.