first commit
This commit is contained in:
commit
0265f6b91c
446
facerecognition/CHANGELOG.md
Normal file
446
facerecognition/CHANGELOG.md
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.9.51] - 2024-06-18
|
||||||
|
- Fix admin page when for any reason disable pdlib.
|
||||||
|
- Adds batch option for face clustering. Issue #712 (Among many others)
|
||||||
|
- Shows how many processed images there are in the stats command.
|
||||||
|
- Add setting to enable facial recognition for all users by default.
|
||||||
|
|
||||||
|
## [0.9.50] - 2024-05-22
|
||||||
|
- Enable Nextcloud 29 and drop 27.
|
||||||
|
- Add support to authorization imaginary with key. PR #746, Issue #700. Thanks
|
||||||
|
to fabalexsie.
|
||||||
|
- Add button to "Review ignored people". PR #747, Issue #735. Thanks to wronny.
|
||||||
|
- Add the sixth model to the application. Aka DlibTaguchiHog model. =)
|
||||||
|
This model was trained from scratch by Taguchi Tokuji to slightly improve the
|
||||||
|
bias of the original model on non-Caucasian/American people, training with a
|
||||||
|
greater number of Japanese and others Asians people. It obtained a similar
|
||||||
|
result in the LFW tests, slightly lower, but within the acceptable margins of
|
||||||
|
error.
|
||||||
|
This model should improve the behavior of the application with people with
|
||||||
|
these traits.
|
||||||
|
- Update translations. Many thanks to everyone!.
|
||||||
|
|
||||||
|
## [0.9.40] - 2024-04-24
|
||||||
|
- Enable PHP 8.3 and NC28
|
||||||
|
- Add special modes to background_job command that allows images to be analyzed
|
||||||
|
in multiple processes to improve speed.
|
||||||
|
- Add special mode to sync-album command to generate combined albums with multiple
|
||||||
|
people. PR #709
|
||||||
|
|
||||||
|
## [0.9.31] - 2023-08-24
|
||||||
|
- Be sure to open the model before getting relevant info. Issue #423
|
||||||
|
|
||||||
|
## [0.9.30] - 2023-08-23
|
||||||
|
- Implement the Chinese Whispers Clustering algorithm in native PHP.
|
||||||
|
- Open the model before requesting information. Issue #679
|
||||||
|
- If Imaginary is configured, check that it is accessible before using it.
|
||||||
|
- If Memories is installed, show people's photos in this app.
|
||||||
|
- Add face thumbnail when search persons.
|
||||||
|
- Disable auto rotate for HEIF images in imaginary. Issue #662
|
||||||
|
- Add the option to print the progress in json format.
|
||||||
|
|
||||||
|
## [0.9.20] - 2023-06-14
|
||||||
|
- Add support for (Now old) Nextcloud 26.
|
||||||
|
- Add support to NC27 for early testing.
|
||||||
|
- Clean some code an split great classed to improve maintenance.
|
||||||
|
- Don't catch Imaginary exceptions. Issue #658
|
||||||
|
- Update french translation thanks to Jérémie Tarot.
|
||||||
|
|
||||||
|
## [0.9.12] - 2023-03-25
|
||||||
|
- Add support for using imaginary to create the temporary files.
|
||||||
|
This add support for images heic, tiff, and many more. Issue #494,
|
||||||
|
#215 and #348 among many other reports.
|
||||||
|
- Memory optimization in face clustering task. Part of issue #339
|
||||||
|
In my tests, it reduces between 33% and 39% of memory, and as an
|
||||||
|
additional improvement, there was also a reduction in time of around
|
||||||
|
19%. There are still several improvements to be made, but it is a
|
||||||
|
good step.
|
||||||
|
- Modernizes the construction of the javascript code. Issue #613
|
||||||
|
- Fix Unhandled exception and Albums are not being created. Issue #634
|
||||||
|
|
||||||
|
## [0.9.11] - 2022-12-28
|
||||||
|
- Fix migrations on PostgreSQL. Issue #619 and #615
|
||||||
|
- Fix OCS Api (API V1). Thanks to nkming2
|
||||||
|
|
||||||
|
## [0.9.10] - 2022-12-12
|
||||||
|
- Just bump version, to remove beta label and allow installing in NC25
|
||||||
|
- **Gratitude**: @pulsejet, very kindly accept the integration of this
|
||||||
|
application into your super cool photo gallery called [Memories](https://github.com/pulsejet/memories).
|
||||||
|
If you didn't know about this project, I invite you to give it a try,
|
||||||
|
that will pleasantly surprise you. :tada:
|
||||||
|
Thanks again. :smiley:
|
||||||
|
- New Russian translation thanks to Regardo.
|
||||||
|
|
||||||
|
## [0.9.10-beta.2] - 2022-11-17
|
||||||
|
### Added
|
||||||
|
- Adds an API version 2 that theoretically is enough for any client.
|
||||||
|
- Note that can change minimally until we release the stable version.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Only fix tests.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Use x, y, width, height to save face detections on database.
|
||||||
|
|
||||||
|
## [0.9.10-beta.1] - 2022-09-27
|
||||||
|
### Added
|
||||||
|
- Support Nextcloud 25
|
||||||
|
- A little love to the whole application to improve styles and texts.
|
||||||
|
- Show the image viewer when click come image on our "gallery".
|
||||||
|
Is press Control+Click it will open the file as before.
|
||||||
|
- Don´t allow run two face:commands simultaneously to prevent errors.
|
||||||
|
- Some optimizations on several queries of main view.
|
||||||
|
- Add a new command <face:sync-albums> to create photos albums of persons.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Rephrase I'm not sure button to better indicate what it does. Issue #544
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Change Person to people since Persons in a very formal word of lawyers.
|
||||||
|
- Edit people's names in the same side tab instead of using dialogs.
|
||||||
|
This is really forced by changes to the viewer which retains focus, and
|
||||||
|
avoids typing anywhere.
|
||||||
|
It also has a regression that misses the autocomplete. :disapointed:
|
||||||
|
- Typo fix. Just use plurals on stats table.
|
||||||
|
- Show the faces of the latest photos, and sort photos by upload order.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Update German translation thanks to lollos78.
|
||||||
|
- Update Italian (Italy) translation thanks to lollos78.
|
||||||
|
- Update of many other translations. Thank you so much everyone.
|
||||||
|
|
||||||
|
## [0.9.5] - 2022-05-07
|
||||||
|
### Added
|
||||||
|
- Just enable Nextcloud 24
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix NotFoundException on NC24. Issue #574
|
||||||
|
|
||||||
|
## [0.9.1] - 2021-12-15
|
||||||
|
### Fixed
|
||||||
|
- Fix Ignore persons feature. Issue #542
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Update Czech translation thanks to Pavel Borecki.
|
||||||
|
- Update Italian translation thanks to axl84.
|
||||||
|
|
||||||
|
## [0.9.0] - 2021-12-13
|
||||||
|
### Added
|
||||||
|
- Add an extra step to setup. You must indicate exactly how much memory you want
|
||||||
|
to assing for image processing. See occ face:setup --memory doc on readme.
|
||||||
|
- Adds the option to effectively ignore persons when assigning names. See issue
|
||||||
|
#486 #504.
|
||||||
|
- It also allows you to hide persons that you have already named. Issue #405
|
||||||
|
- Implement the option of: This is not such a person. Issue #506 and part of
|
||||||
|
#158
|
||||||
|
- Enable NC23
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Update some translations. Thank you so much everyone.
|
||||||
|
|
||||||
|
## [0.8.5] - 2021-11-20
|
||||||
|
### Added
|
||||||
|
- Initial support for php 8. See issue #456
|
||||||
|
- Add link to show photos of person on sidebar.
|
||||||
|
- Add static analysis, phpunit and lintian test using github workflow.
|
||||||
|
- Add an real OCS public API to get all persons. See PR #512.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix sidebar view when user has disable it.
|
||||||
|
- Set the Image Area slider to the maximum allowed by the model. See issue #527
|
||||||
|
- Don't try to force the setCreationTime argument to be DateTime. See PR #526
|
||||||
|
- Migrate hooks to OCP event listeners. See PR #511
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- New Czech translation thanks to Pavel Borecki, and update others. Thank you so
|
||||||
|
much everyone.
|
||||||
|
|
||||||
|
## [0.8.3] - 2021-07-08
|
||||||
|
### Added
|
||||||
|
- Initial support for NC22.
|
||||||
|
- Update translations.
|
||||||
|
|
||||||
|
## [0.8.2] - 2021-05-17
|
||||||
|
### Added
|
||||||
|
- Add links in thumbnails of rename persons dialogs. Issue #396
|
||||||
|
- Initial autocomplete feature for names. Issue #306
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Respect .noimage file, since it is also used in Photos. Issue #446
|
||||||
|
- Fix delete files due some change on ORM with NC21. Issue #471
|
||||||
|
- Some fixes on make clean.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- New Italian translation thanks to axl84, and update others. Thank you so much
|
||||||
|
everyone.
|
||||||
|
|
||||||
|
## [0.8.1] - 2021-03-18
|
||||||
|
### Fixed
|
||||||
|
- Register the Hooks within the Bootstrap mechanism, removing many undesirable
|
||||||
|
logs. Similar to https://github.com/nextcloud/server/issues/22590
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- New Korean (Korea) translation thanks to HyeongJong Choi
|
||||||
|
- Updating many others translations from Transifex. This time I cannot
|
||||||
|
individualize your changes to thank you properly, but thank you very much to
|
||||||
|
all the translators.
|
||||||
|
|
||||||
|
## [0.8.0] - 2021-03-17
|
||||||
|
### Added
|
||||||
|
- Increase the supported version only to NC21. Thanks to @szaimen. See issue #429
|
||||||
|
- Add support for unified search, being able to search the photos of your loved
|
||||||
|
ones from anywhere in nextcloud. Thanks to @dassio. See PR #344
|
||||||
|
- Add defer-clustering option. It changes the order of execution of the process
|
||||||
|
deferring the face clustering at the end of the analysis to get persons in a
|
||||||
|
simple execution of the command. Thanks to @cliffalbert. See issue #371
|
||||||
|
|
||||||
|
## [0.7.2] - 2020-12-10
|
||||||
|
### Added
|
||||||
|
- Add an external model that allows run the photos analysis outside of your
|
||||||
|
Nextcoud instance freeing server resources. This is generic and just define a
|
||||||
|
reference api, and we share a reference example equivalent to model 1. See
|
||||||
|
issue #210, #238, and PR #389.
|
||||||
|
See: https://github.com/matiasdelellis/facerecognition-external-model
|
||||||
|
- Allow setting a custom model path, useful for configurations like Object
|
||||||
|
Storage as Primary Storage. This thanks to David Kang. See #381 and #390.
|
||||||
|
- Add memory info and pdlib version to admin page. PR #385
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Some messages improved thanks to Robin @Derkades. They were not translated yet
|
||||||
|
and will probably change again. Please be patient.
|
||||||
|
|
||||||
|
## [0.7.1] - 2020-11-17
|
||||||
|
### Added
|
||||||
|
- Add support for analyzing photos from group folders. Issue #364
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed Prevent error when pretty url is disabled on subfolder #379
|
||||||
|
- Fix responses when you want to see another user's faces. Issue #352
|
||||||
|
- Fix enconde name to URL params queries. Issue #359
|
||||||
|
- Typo in title. Issue #357 and #380. Thanks to @strugee
|
||||||
|
- Try to improve some messages.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Add Dutch translation thanks to Robin Slot.
|
||||||
|
- Update Macedonian language thanks to Сашко Тодоров
|
||||||
|
- Update French language thanks to Tho Vi
|
||||||
|
- Update Serbian language thanks to Branko Kokanovic.
|
||||||
|
- Update Spanish language thanks to Matias De lellis.
|
||||||
|
|
||||||
|
## [0.7.0] - 2020-10-24
|
||||||
|
### Added
|
||||||
|
- Support to Nextcloud 20. Issue #343 and #347. Thanks to xiangbin li.
|
||||||
|
- Add a dialog to assign names to the new persons found.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- The main view of face clusters change switches to a view of persons. We
|
||||||
|
consider person to the set of all faces with the same name, regardless of the
|
||||||
|
clusters. Fix issue #334 and parts of #134.
|
||||||
|
- The second viev show all photos of a person. The server thumbnails are reused,
|
||||||
|
and therefore the performance is drastically improved. In general fix issue
|
||||||
|
#193 and parts of #134.
|
||||||
|
- Finally, the third view, allows you to see all the clusters as before, only to
|
||||||
|
fix name errors if necessary.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix crashed on postWrite and prevent other apps to work. Issue #341
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Update French tranlation thanks to Tho Vi
|
||||||
|
|
||||||
|
## [0.6.3] - 2020-08-28
|
||||||
|
### Changed
|
||||||
|
- Reduce the minimum system memory to 1GB. Issue #319 and others.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix migration command due 'Invalid datetime format'. Issue #320
|
||||||
|
- Fix can't change model to 4, migration says model <4> not installed. Issue #318
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Update French tranlation thanks to Tho Vi
|
||||||
|
|
||||||
|
## [0.6.2] - 2020-08-14
|
||||||
|
### Added
|
||||||
|
- Introduduce a new model (Model 4, aka DlibCnnHog5) that is 2 times slower, but
|
||||||
|
much more accurate, which now is in testing stages, and we invite you to test
|
||||||
|
since probably will be the next recommended model. See PR #313 for details.
|
||||||
|
- Add face:migrate command that allows to migrate the faces obtained in a model
|
||||||
|
to a new one. Still recommended to fully analyze the images when changing
|
||||||
|
models, but can save a lot of time migrating them. See PR #309
|
||||||
|
- Add face:reset --model command to just reset current model.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- At least 1000 faces are needed for to make an initial clustering.
|
||||||
|
- Don't group faces smaller than 40 pixels, which are supposed to be of poor
|
||||||
|
quality. This is configurable within an advanced hidden setting. PR #299
|
||||||
|
- All reset commands require a confirmation to work.
|
||||||
|
- Hint the 4x3 relation when model recommending memory values.
|
||||||
|
|
||||||
|
## Deprecated
|
||||||
|
- After many analysis, we discourage the use of model 2 (Aka DlibCnn68). We
|
||||||
|
still recommend model 1, and model 3 for low-resource devices. You can migrate
|
||||||
|
the faces using the new command, but we recommend analyzing them again.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix estimated time in the administration panel. See RP #297
|
||||||
|
- Fix that removing .nomedia file does not trigger facerecognition when next
|
||||||
|
analysis starts. Issue #304
|
||||||
|
- Fix travis tests and lot of scrutinizer reports.
|
||||||
|
- Fix that if increase the minimum confidence dont cluster any face in model 3
|
||||||
|
- Log the system info before return any error. Part of issue #278
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Add Macedonian translation thanks to Сашко Тодоров
|
||||||
|
|
||||||
|
## [0.6.1] - 2020-06-27
|
||||||
|
### Changed
|
||||||
|
- Adjust the appstore makefile rule to ignore vue and teplates files.
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
- Fix dump models table when none was installed yet. Issue #276.
|
||||||
|
- Fix integer overflow on 32 bit systems.. Issue #278.
|
||||||
|
- Fix Admin page when not model installed. Issue #284.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Update German (Germany) translations thanks to ProfDrJones.
|
||||||
|
|
||||||
|
## [0.6.0] - 2020-05-04
|
||||||
|
### Added
|
||||||
|
- Experimental support of External Storage. Issue #212
|
||||||
|
- Add some documentation about how expect to install, test and use the application
|
||||||
|
- Optionally you can install Pdlib 1.0.2 from https://github.com/matiasdelellis/pdlib that increase the speed of clustering drastically.
|
||||||
|
- The sidebar has been rewritten to show it in the Photos application.
|
||||||
|
- Add php-bzip2 as a necessary dependency to install the models.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Also adds a minimum php memory requirement for HOG. 128 MB.
|
||||||
|
- Indicate which model is enabled in the summary table of the setup --model command.
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
- Consider the memory configured in php as a dependency on the models.
|
||||||
|
- Improves the description of errors and prints the links to documentation whenever it can.
|
||||||
|
- Fix plurals in admin panel. Issue #256.
|
||||||
|
- Use paginated queries when search persons on file app. Fix part of issue #263.
|
||||||
|
- Search mainly faces, rather than persons in file view. Fix issue #264.
|
||||||
|
- Don't compare a good face with a bad one. This greatly improves the quality of the faces clusters.
|
||||||
|
- Fix that sidebar says no faces were found when just not clustered yet. Issue #255.
|
||||||
|
- Fix tests on Nextcloud 18 and 19.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- New French tranlation thanks to Florian Carpentier
|
||||||
|
- Update Chinese translation thanks to yui874397188 and Jack Frost
|
||||||
|
- Update German translation thanks to Johannes Szeibert
|
||||||
|
- Update Polish translation thanks to Piotr Esse
|
||||||
|
- Update Spanish translation thanks to Matias De lellis
|
||||||
|
|
||||||
|
## [0.5.14] - 2020-03-30
|
||||||
|
### Bug fixes
|
||||||
|
- Fix image will be skipped due 'Unable to open file: /tmp/oc_tmp_###'. Issue #242
|
||||||
|
|
||||||
|
## [0.5.13] - 2020-03-29
|
||||||
|
### Added
|
||||||
|
- Add face:reset --all|--clustering command. Details in README.md file.
|
||||||
|
- Add face:stats command. Details in README.md file.
|
||||||
|
- Add face:progress command. Details in README.md file.
|
||||||
|
- Multiple model support was implemented. Details on Wiki.
|
||||||
|
- Allow to change the model using the face:setup command.
|
||||||
|
- Allow configure the supported mimetype. Details on Wiki.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Change to use area of the images instead of memory as the main parameter.
|
||||||
|
- Don't upsampling image on CNN, better pass a bigger picture.
|
||||||
|
- Don't install any model by default, just dump list of models.
|
||||||
|
- Better message and print system/php memory as debug.
|
||||||
|
- Test: Move to new repo that use reprepro and use bionic.
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
- Fix show not grouped on main view.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Add Portuguese (Brazil) thanks to Marcelo Rovani.
|
||||||
|
- Update Chinese language thanks to Jack Frost.
|
||||||
|
- Update German language thanks to Mark Ziegler.
|
||||||
|
- Update Spanish language thanks to Matias De lellis.
|
||||||
|
|
||||||
|
## [0.5.12] - 2020-01-06
|
||||||
|
### Bug fixes
|
||||||
|
- Force cast to integer after multiplying by a number. Issue #199, #208
|
||||||
|
- Fix initial view when enable analysis and still don't analyze anything. Issue #225 and others.
|
||||||
|
- face-preview: Person photo is blank #226
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Change the minimum memory requirement to 2GB. If you have less use Swap.
|
||||||
|
- Change the minimum memory assigned for analysis to 1.2GB.
|
||||||
|
- Change the maximum memory assigned for analysis to 8 GB.
|
||||||
|
- Change the formula to calculate the area according to memory. Issue $220, #176, and others.
|
||||||
|
- Implement a settings service where to handle everything a little more clean.
|
||||||
|
- Move FaceManagementService together with the others services.
|
||||||
|
- Test node binary needed to build handlebars templates. Issue #223 and #217
|
||||||
|
- Fix some grammatical errors and typos. Issue 224
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Add German translation thanks to Mark Ziegler.
|
||||||
|
- Update Chinese language thanks to Jack Frost.
|
||||||
|
- Update Spanish language thanks to Matias De lellis.
|
||||||
|
|
||||||
|
## [0.5.11] - 2019-12-20
|
||||||
|
### Added
|
||||||
|
- Add custom exclusion folder option beyond the .nomedia file. Issue #171
|
||||||
|
- Add sidebar to folders which allows to enable/disable these.
|
||||||
|
- Add support for encrypted storage. Issue #201
|
||||||
|
- Add experimental support for shared storage. Issue #26
|
||||||
|
- Add support to Nextcloud 18
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Fix travis CI test.
|
||||||
|
- General cleaning of a lot of code and doc.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Update Chinese language thanks to Jack Frost.
|
||||||
|
- Update Spanish language thanks to Matias De lellis.
|
||||||
|
|
||||||
|
## [0.5.10] - 2019-12-06
|
||||||
|
### Added
|
||||||
|
- Add a button to show all clusters with same person name.
|
||||||
|
- Add a button to go back and show all clusters.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Select the name when open the rename dialog.
|
||||||
|
- Remove the spaces at both ends of the names before saving them.
|
||||||
|
- Force delete invalid entries when a file does not exist. Issue #154
|
||||||
|
- Improve tests broken since adding the face:setup command.
|
||||||
|
- Move from deprecated database.xml to use DB migrations.
|
||||||
|
- Try to be consistent in that we work with clusters at least in api/endpoints.
|
||||||
|
- Fix most of the 'App is not compliant' reports. Issue #72
|
||||||
|
- Remove Nextcloud 15 support. If you need it, ask for help.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Update Chinese language thanks to Jack Frost.
|
||||||
|
- Update Serbian language thanks to Branko Kokanovic.
|
||||||
|
- Update Spanish language thanks to Matias De lellis.
|
||||||
|
|
||||||
|
## [0.5.9] - 2019-11-25
|
||||||
|
### Changed
|
||||||
|
- Migrate deprecated json_array db type to json.
|
||||||
|
|
||||||
|
## [0.5.8] - 2019-11-22
|
||||||
|
### Added
|
||||||
|
- A personal settings panel to enable the analysis by each user.
|
||||||
|
- Progressive discovery of faces in the photos of the users.
|
||||||
|
- Automatic clustering of similar faces as persons.
|
||||||
|
- Can view and rename face groups in the personal settings pane.
|
||||||
|
- A side panel where you can see the persons in a photo and rename them.
|
||||||
|
- Can search for all the photos where a person appears just by typing the name.
|
||||||
|
- A admin settings panel to configure the main options.
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
- Added Chinese language thanks to Jack Frost.
|
||||||
|
- Added Polish language thanks to Olaf Lipinski.
|
||||||
|
- Added Serbian language thanks to Branko Kokanovic.
|
||||||
|
- Added Spanish language thanks to Matias De lellis.
|
||||||
|
|
||||||
|
## [0.5.4] – 2017-10-04
|
||||||
|
|
||||||
|
- Initial release
|
||||||
82
facerecognition/ISSUE_TEMPLATE.md
Normal file
82
facerecognition/ISSUE_TEMPLATE.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
Hey, Thanks for reporting issues back to Nextcloud Face Recognition. Please, try to complete this report in detail so we can help you easier. :smile:
|
||||||
|
|
||||||
|
Make sure you read all the [documentation](https://github.com/matiasdelellis/facerecognition/wiki), and the [FAQ](https://github.com/matiasdelellis/facerecognition/wiki/FAQ), and that the issue has not been reported before. :wink:
|
||||||
|
|
||||||
|
### Expected behaviour
|
||||||
|
|
||||||
|
Tell us what should happen
|
||||||
|
|
||||||
|
### Actual behaviour
|
||||||
|
|
||||||
|
Tell us what happens instead
|
||||||
|
|
||||||
|
### Steps to reproduce
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
### Server configuration
|
||||||
|
|
||||||
|
* **Operating system:**
|
||||||
|
|
||||||
|
* **Pdlib version:**
|
||||||
|
|
||||||
|
* **How is DLib installed:** Make sure it is working correctly with this [tool](https://github.com/matiasdelellis/pdlib-min-test-suite)
|
||||||
|
|
||||||
|
* **How is PDlib installed:** Make sure it is working correctly with this [tool](https://github.com/matiasdelellis/pdlib-min-test-suite)
|
||||||
|
|
||||||
|
* **PHP version:**
|
||||||
|
|
||||||
|
* **Web server:**
|
||||||
|
|
||||||
|
* **Database:**
|
||||||
|
|
||||||
|
* **Nextcloud version:**
|
||||||
|
|
||||||
|
### Client configuration
|
||||||
|
|
||||||
|
* **Browser:**
|
||||||
|
|
||||||
|
* **Operating system:**
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
#### Background task log with debug.
|
||||||
|
<details>
|
||||||
|
<summary>sudo -u apache php occ -vvv face:background_job</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
Insert your background log here
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Web server error log
|
||||||
|
<details>
|
||||||
|
<summary>Web server error log</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
Insert your webserver log here
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Nextcloud log (data/nextcloud.log)
|
||||||
|
<details>
|
||||||
|
<summary>Nextcloud log</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
Insert your Nextcloud log here
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### Browser log
|
||||||
|
<details>
|
||||||
|
<summary>Browser log</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
Insert your browser log here, this could for example include:
|
||||||
|
|
||||||
|
a) The javascript console log
|
||||||
|
b) The network log
|
||||||
|
c) ...
|
||||||
|
```
|
||||||
|
</details>
|
||||||
661
facerecognition/LICENSE
Normal file
661
facerecognition/LICENSE
Normal file
@ -0,0 +1,661 @@
|
|||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
213
facerecognition/README.md
Normal file
213
facerecognition/README.md
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
# Face Recognition
|
||||||
|
|
||||||
|

|
||||||
|
[](https://scrutinizer-ci.com/g/matiasdelellis/facerecognition/?branch=master)
|
||||||
|
[](https://scrutinizer-ci.com/g/matiasdelellis/facerecognition/?branch=master)
|
||||||
|
[](https://www.codacy.com/app/stalker314314/facerecognition?utm_source=github.com&utm_medium=referral&utm_content=matiasdelellis/facerecognition&utm_campaign=Badge_Grade)
|
||||||
|

|
||||||
|
[](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||||
|
|
||||||
|
Nextcloud app that implement a basic facial recognition system.
|
||||||
|
|
||||||
|
FaceRecognition is a Nextcloud application with a goal of recognizing, analyzing
|
||||||
|
and aggregating face data in users images, and providing additional
|
||||||
|
functionalities on top of these information, all with built-in privacy of
|
||||||
|
Nextcloud. Imagine Google Photos, but only for faces (not detecting objects…)
|
||||||
|
and in such way that your images never leave your Nextcloud instance. :smiley:
|
||||||
|
|
||||||
|
The application listens to the creation of new image files, and queues them for
|
||||||
|
later analysis. A scheduled task (Or admin on demand) take this queue, and
|
||||||
|
analyze the images for looking faces and if possible identify them grouping by
|
||||||
|
similarity.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## How to use it?
|
||||||
|
|
||||||
|
The administrator must properly configure the application, and once it is
|
||||||
|
working, the user must accept that he wants to allow the analysis of his images
|
||||||
|
to discover his friends.
|
||||||
|
Finally the user can use the application in three ways
|
||||||
|
|
||||||
|
1. In the user settings there is a 'Face Recognition' panel where first of all
|
||||||
|
each user must enable the analysis. Once enabled, you will progressively see
|
||||||
|
the discovery of your friends, and you can assign them names.
|
||||||
|
2. In the file application the user can search by typing your friend's name,
|
||||||
|
and it will show all the photos.
|
||||||
|
3. In the side panel of the file application, a 'Persons' tab is added where
|
||||||
|
you can see a list of your friends in the photo, and rename them. Also you can
|
||||||
|
select the folders you want to ignore for the process.
|
||||||
|
3. In the side panel of the Photos application, a 'Persons' tab is added where
|
||||||
|
you can see a list of your friends in the photo, and rename them.
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
If you'd like to support the creation and maintenance of this software, consider donating.
|
||||||
|
|
||||||
|
[](https://github.com/matiasdelellis/facerecognition/wiki/Donate)
|
||||||
|
[](https://github.com/matiasdelellis/facerecognition/wiki/Donate)
|
||||||
|
[](https://github.com/matiasdelellis/facerecognition/wiki/Donate)
|
||||||
|
|
||||||
|
## Installation, configuration and usage
|
||||||
|
|
||||||
|
#### Requirements
|
||||||
|
|
||||||
|
* Nextcloud 22+
|
||||||
|
* [Dlib PHP bindings](https://github.com/goodspb/pdlib)
|
||||||
|
* [PHP Bzip2](https://www.php.net/manual/en/book.bzip2.php)
|
||||||
|
* 1GB of RAM
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
|
||||||
|
Ideally once you meet the requirements, you can install and enable it from the
|
||||||
|
nextcloud app store. For details and advanced information read the documentation
|
||||||
|
about [installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation).
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
|
||||||
|
Before proceeding to analyze the images, you must indicate how much memory you
|
||||||
|
want to assigns to image processing and then must properly install and configure
|
||||||
|
the pretrained models using the `occ face:setup` command. For details and
|
||||||
|
advanced information read the documentation about [models](https://github.com/matiasdelellis/facerecognition/wiki/Models#install-models).
|
||||||
|
|
||||||
|
Then you must indicate the size of the images used in the temporary files from
|
||||||
|
the Nextcloud settings panel. This configuration will depend on your
|
||||||
|
installation and has a direct impact on memory consumption. For details and
|
||||||
|
advanced information read the documentation about [Temporary files](https://github.com/matiasdelellis/facerecognition/wiki/Settings#temporary-files).
|
||||||
|
|
||||||
|
#### Test the application
|
||||||
|
|
||||||
|
We recommend test the application intensively before proceeding to analyze the
|
||||||
|
real data of the users. For this you can create a new user in your Nextcloud
|
||||||
|
instance and upload some photos from the internet. Then you must run the
|
||||||
|
`occ face:background_job -u new_user -t 900` command for this user and evaluate
|
||||||
|
the result. For details and advanced information read the documentation of this
|
||||||
|
command below.
|
||||||
|
|
||||||
|
#### Schedule background job
|
||||||
|
|
||||||
|
The application is designed to run as a scheduled task. This allows analyze the
|
||||||
|
photos and showing the results to the user progressively. You can read about
|
||||||
|
some ways to configure it within our documentation about [Schedule Background Task](https://github.com/matiasdelellis/facerecognition/wiki/Schedule-Background-Task).
|
||||||
|
|
||||||
|
## occ commands
|
||||||
|
|
||||||
|
The application add commands to the [Nexcloud's command-line interface](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/occ_command.html).
|
||||||
|
|
||||||
|
#### Initial setup
|
||||||
|
|
||||||
|
`occ face:setup [-M|--memory MEMORY] [-m|--model MODEL]`
|
||||||
|
|
||||||
|
This command is responsible for making the necessary settings to use the
|
||||||
|
application.
|
||||||
|
|
||||||
|
If `MEMORY` is supplied, it will establish the maximum memory to be used for the
|
||||||
|
processing of the images. This value will be limited according to the memory
|
||||||
|
available by the system and the php configuration. You can use numbers as bytes
|
||||||
|
(1073741824 for 1GB), or subfixed with units (1024M or 1G) but note that it is
|
||||||
|
without space.
|
||||||
|
|
||||||
|
If `MODEL_ID` is supplied. the pre-trained model for facial recognition will be
|
||||||
|
installed.
|
||||||
|
|
||||||
|
You must perform both settings before continuing with any application command.
|
||||||
|
If you do not supply any of these options, the command will return the current
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
#### Face analysis
|
||||||
|
|
||||||
|
`occ face:background_job [-u|--user_id USER_ID] [-t|--timeout TIMEOUT] [--defer-clustering] [-M|--max_image_area MAX_IMAGE_AREA]`
|
||||||
|
|
||||||
|
This command will do all the work. It is responsible for searching the images,
|
||||||
|
analyzing them and clustering faces found in them in groups of similar people.
|
||||||
|
|
||||||
|
Beware that this command can take a lot of CPU and memory! Before you put it to
|
||||||
|
cron job, it is advised to try it out manually first, just to be sure you have
|
||||||
|
all requirements and you have enough resources on your machine.
|
||||||
|
|
||||||
|
Command is designed to be run continuously, so you will want to schedule it with
|
||||||
|
cron to be executed every once in a while, together with a specified timeout. It
|
||||||
|
can be run every 15 minutes with timeout of `-t 900` (so, it will stop itself
|
||||||
|
automatically after 15 minutes and cron will start it again), or once a day with
|
||||||
|
timeout of 2 hours, like `-t 7200`.
|
||||||
|
|
||||||
|
If `USER_ID` is supplied, it will just loop over files of a given user. Keep in
|
||||||
|
mind that each user must enable the analysis individually, and otherwise this
|
||||||
|
command will ignore the user.
|
||||||
|
|
||||||
|
If `TIMEOUT` is supplied it will stop after the indicated seconds, and continue
|
||||||
|
in the next execution. Use this value in conjunction with the times of the
|
||||||
|
scheduled task to distribute the system load during the day.
|
||||||
|
|
||||||
|
If `MAX_IMAGE_AREA` is supplied caps the maximum area (in pixels^2) of the image
|
||||||
|
to be fed to neural network, effectively lowering needed memory. Use this
|
||||||
|
if face detection crashes randomly.
|
||||||
|
|
||||||
|
If use the `--defer-clustering` option, it changes the order of execution of the
|
||||||
|
process deferring the face clustering at the end of the analysis to get persons
|
||||||
|
in a simple execution of the command.
|
||||||
|
|
||||||
|
#### Create albums in the Photos app
|
||||||
|
|
||||||
|
`face:sync-albums [-u|--user_id USER_ID]`
|
||||||
|
|
||||||
|
This command creates photo albums within the Nexcloud Photos app, with photos of
|
||||||
|
each person found.
|
||||||
|
|
||||||
|
Note that these albums are editable in the Photos app, and any changes will be
|
||||||
|
ignored and reverted on the next run of this command.
|
||||||
|
|
||||||
|
This command is also designed to be run regularly to sync any user changes, as
|
||||||
|
this command is the only one that will update albums.
|
||||||
|
|
||||||
|
If `USER_ID` is provided, it will just sync albums for this user.
|
||||||
|
|
||||||
|
#### Resetting information
|
||||||
|
|
||||||
|
`occ face:reset [--all] [--model] [--image-errors] [--clustering] [-u|--user_id USER_ID]`
|
||||||
|
|
||||||
|
This command can completely wipe out all images, faces and cluster of persons.
|
||||||
|
It is ideal if you want to start from scratch for any reason.
|
||||||
|
|
||||||
|
You must specify if you wish to completely reset the database `[--all]` or just
|
||||||
|
the current model `[--model]` and all images must be analyzed again, or or you
|
||||||
|
can reset only the clustering of persons `[--clustering]` and only clustering
|
||||||
|
needs to be done again, or reset only the images that had errors
|
||||||
|
`[--image-errors]` to try to analyze them again.
|
||||||
|
|
||||||
|
If `USER_ID` is provided, it will just reset the information of a particular
|
||||||
|
user.
|
||||||
|
|
||||||
|
#### Migrate models
|
||||||
|
|
||||||
|
`occ face:migrate [-m|--model_id MODEL_ID] [-u|--user_id USER_ID]`
|
||||||
|
|
||||||
|
This command allows to migrate the faces obtained in a model to a new one. Note
|
||||||
|
that the persons name are not migrated, and the user must rename them again.
|
||||||
|
Always is recommended to analyze from scratch any configured model, but you can
|
||||||
|
save a lot of time migrating it.
|
||||||
|
|
||||||
|
You must specify which model you want to migrate using the `MODEL_ID` option.
|
||||||
|
|
||||||
|
If `USER_ID` is provided, just migrate the faces for the given user.
|
||||||
|
|
||||||
|
#### Statistics
|
||||||
|
|
||||||
|
`occ face:stats [-u|--user_id USER_ID] [-j|--json]`
|
||||||
|
|
||||||
|
This command return a summary of the number of images, faces and persons found.
|
||||||
|
|
||||||
|
If `USER_ID` is provided, just return the stats for the given user.
|
||||||
|
|
||||||
|
If use the `--json` argument, it prints the stats in a json format more suitable
|
||||||
|
to parse with other tools.
|
||||||
|
|
||||||
|
#### Progress
|
||||||
|
|
||||||
|
`occ face:progress`
|
||||||
|
|
||||||
|
This command just return the progress of the analysis and an estimated time to
|
||||||
|
complete.
|
||||||
|
|
||||||
|
If use the `--json` argument, it prints the stats in a json format more suitable
|
||||||
|
to parse with other tools.
|
||||||
33
facerecognition/TESTING.md
Normal file
33
facerecognition/TESTING.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
All tests should be easily executed with simple:
|
||||||
|
```bash
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
from root directory. However, you will not be able to execute integration tests out of the box, as they are modifying local instance. For those, you will have to explicitely export env variable:
|
||||||
|
```bash
|
||||||
|
export TRAVIS=1
|
||||||
|
```
|
||||||
|
to be able to run them. Again – make sure you are running test server and watch out as those tests can create havoc with your instance.
|
||||||
|
|
||||||
|
Once you have all requirements satisified and you want to run test over and over again, it all boils down to calling:
|
||||||
|
```bash
|
||||||
|
phpunit -c phpunit.xml
|
||||||
|
```
|
||||||
|
from root directory. You can target specific tests with `--filter` etc., but this is all *regular* PHP unit testing, just type `phpunit -h` for more details.
|
||||||
|
|
||||||
|
If you are missing PHPUnit, you can follow their [official guide](https://phpunit.de/getting-started/phpunit-7.html), but you should get binary with somwething like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget -O phpunit https://phar.phpunit.de/phpunit-7.phar
|
||||||
|
chmod +x phpunit
|
||||||
|
./phpunit --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing tests
|
||||||
|
|
||||||
|
As by standard definition, unit tests are not modifying external resources and should not touch database. If possible, you should try testing with unit tests, and if it not – add yourself to integration tests.
|
||||||
|
|
||||||
|
Other than that, you are good to go – there are already some unit and integration tests in `tests/` directory, and you can always take a look at [Nextcloud unit testing official documentation](https://docs.nextcloud.com/server/14/developer_manual/core/unit-testing.html).
|
||||||
59
facerecognition/appinfo/info.xml
Normal file
59
facerecognition/appinfo/info.xml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||||
|
<id>facerecognition</id>
|
||||||
|
<name>Face Recognition</name>
|
||||||
|
<summary>A face recognition app</summary>
|
||||||
|
<description>< for details.
|
||||||
|
|
||||||
|
⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.
|
||||||
|
|
||||||
|
- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!
|
||||||
|
- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!
|
||||||
|
- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.
|
||||||
|
- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.
|
||||||
|
- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!
|
||||||
|
]]>
|
||||||
|
</description>
|
||||||
|
<version>0.9.51</version>
|
||||||
|
<licence>agpl</licence>
|
||||||
|
<author>Matias De lellis</author>
|
||||||
|
<author>Branko Kokanovic</author>
|
||||||
|
<namespace>FaceRecognition</namespace>
|
||||||
|
<types>
|
||||||
|
<filesystem/>
|
||||||
|
</types>
|
||||||
|
<category>multimedia</category>
|
||||||
|
<website>https://github.com/matiasdelellis/facerecognition</website>
|
||||||
|
<bugs>https://github.com/matiasdelellis/facerecognition/issues</bugs>
|
||||||
|
<repository type="git">https://github.com/matiasdelellis/facerecognition.git</repository>
|
||||||
|
<screenshot small-thumbnail="https://matiasdelellis.github.io/img/facerecognition/facerecognition-persons-view-small.jpeg">https://matiasdelellis.github.io/img/facerecognition/facerecognition-persons-view.jpeg</screenshot>
|
||||||
|
<screenshot>https://matiasdelellis.github.io/img/facerecognition/facerecognition-person-photos.jpeg</screenshot>
|
||||||
|
<screenshot>https://matiasdelellis.github.io/img/facerecognition/facerecognition-photos-integration.jpeg</screenshot>
|
||||||
|
<screenshot>https://matiasdelellis.github.io/img/facerecognition/facerecognition-assign-initial-name.jpeg</screenshot>
|
||||||
|
<dependencies>
|
||||||
|
<php min-version="8.0" max-version="8.3" />
|
||||||
|
<nextcloud min-version="28" max-version="29"/>
|
||||||
|
</dependencies>
|
||||||
|
<repair-steps>
|
||||||
|
<uninstall>
|
||||||
|
<step>OCA\FaceRecognition\Migration\RemoveFullImageScanDoneFlag</step>
|
||||||
|
</uninstall>
|
||||||
|
</repair-steps>
|
||||||
|
<commands>
|
||||||
|
<command>OCA\FaceRecognition\Command\BackgroundCommand</command>
|
||||||
|
<command>OCA\FaceRecognition\Command\MigrateCommand</command>
|
||||||
|
<command>OCA\FaceRecognition\Command\ProgressCommand</command>
|
||||||
|
<command>OCA\FaceRecognition\Command\ResetCommand</command>
|
||||||
|
<command>OCA\FaceRecognition\Command\SetupCommand</command>
|
||||||
|
<command>OCA\FaceRecognition\Command\StatsCommand</command>
|
||||||
|
<command>OCA\FaceRecognition\Command\SyncAlbumsCommand</command>
|
||||||
|
</commands>
|
||||||
|
<settings>
|
||||||
|
<admin>OCA\FaceRecognition\Settings\Admin</admin>
|
||||||
|
<admin-section>OCA\FaceRecognition\Settings\AdminSection</admin-section>
|
||||||
|
<personal>OCA\FaceRecognition\Settings\Personal</personal>
|
||||||
|
<personal-section>OCA\FaceRecognition\Settings\PersonalSection</personal-section>
|
||||||
|
</settings>
|
||||||
|
</info>
|
||||||
217
facerecognition/appinfo/routes.php
Normal file
217
facerecognition/appinfo/routes.php
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<?php
|
||||||
|
return ['routes' =>
|
||||||
|
[
|
||||||
|
/*
|
||||||
|
* Persons
|
||||||
|
*/
|
||||||
|
// Get all face clusters with faces and file asociated.
|
||||||
|
[
|
||||||
|
'name' => 'person#index',
|
||||||
|
'url' => '/persons',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Get all images filtered by Name.
|
||||||
|
[
|
||||||
|
'name' => 'person#find',
|
||||||
|
'url' => '/person/{personName}',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Change name to a person.
|
||||||
|
[
|
||||||
|
'name' => 'person#updateName',
|
||||||
|
'url' => '/person/{personName}',
|
||||||
|
'verb' => 'PUT'
|
||||||
|
],
|
||||||
|
// Change visibility to a person.
|
||||||
|
[
|
||||||
|
'name' => 'person#setVisibility',
|
||||||
|
'url' => '/person/{personName}/visibility',
|
||||||
|
'verb' => 'POST'
|
||||||
|
],
|
||||||
|
// Get all names filtered by an query.
|
||||||
|
[
|
||||||
|
'name' => 'person#autocomplete',
|
||||||
|
'url' => '/autocomplete/{query}',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
* Clusters
|
||||||
|
*/
|
||||||
|
// Get a cluster by Id.
|
||||||
|
[
|
||||||
|
'name' => 'cluster#find',
|
||||||
|
'url' => '/cluster/{id}',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Get all clusters filtered by Name.
|
||||||
|
[
|
||||||
|
'name' => 'cluster#findByName',
|
||||||
|
'url' => '/clusters/{personName}',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Get all clusters unassigned clusters.
|
||||||
|
[
|
||||||
|
'name' => 'cluster#findUnassigned',
|
||||||
|
'url' => '/clusters',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Get all clusters ignored clusters.
|
||||||
|
[
|
||||||
|
'name' => 'cluster#findIgnored',
|
||||||
|
'url' => '/clustersIgnored',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Change visibility to cluster
|
||||||
|
[
|
||||||
|
'name' => 'cluster#setVisibility',
|
||||||
|
'url' => '/cluster/{id}/visibility',
|
||||||
|
'verb' => 'POST'
|
||||||
|
],
|
||||||
|
// Detach Face from cluster
|
||||||
|
[
|
||||||
|
'name' => 'cluster#detachFace',
|
||||||
|
'url' => '/cluster/{id}/detach',
|
||||||
|
'verb' => 'PUT'
|
||||||
|
],
|
||||||
|
// Change name to a cluster.
|
||||||
|
[
|
||||||
|
'name' => 'cluster#updateName',
|
||||||
|
'url' => '/cluster/{id}',
|
||||||
|
'verb' => 'PUT'
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
* Face thumbails
|
||||||
|
*/
|
||||||
|
// Get a face Thumb
|
||||||
|
[
|
||||||
|
'name' => 'face#getThumb',
|
||||||
|
'url' => '/face/{id}/thumb/{size}',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Get a face Thumb of some person
|
||||||
|
[
|
||||||
|
'name' => 'face#getPersonThumb',
|
||||||
|
'url' => '/person/{name}/thumb/{size}',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
* File and Folders
|
||||||
|
*/
|
||||||
|
// Get persons from path
|
||||||
|
[
|
||||||
|
'name' => 'file#getPersonsFromPath',
|
||||||
|
'url' => '/file',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Get folder preferences
|
||||||
|
[
|
||||||
|
'name' => 'file#getFolderOptions',
|
||||||
|
'url' => '/folder',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Set folder preferences
|
||||||
|
[
|
||||||
|
'name' => 'file#setFolderOptions',
|
||||||
|
'url' => '/folder',
|
||||||
|
'verb' => 'PUT'
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
* Settings
|
||||||
|
*/
|
||||||
|
// User settings
|
||||||
|
[
|
||||||
|
'name' => 'settings#setUserValue',
|
||||||
|
'url' => '/setuservalue',
|
||||||
|
'verb' => 'POST'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'settings#getUserValue',
|
||||||
|
'url' => '/getuservalue',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// App settings
|
||||||
|
[
|
||||||
|
'name' => 'settings#setAppValue',
|
||||||
|
'url' => '/setappvalue',
|
||||||
|
'verb' => 'POST'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'settings#getAppValue',
|
||||||
|
'url' => '/getappvalue',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
* Status of process.
|
||||||
|
*/
|
||||||
|
// Get process status.
|
||||||
|
[
|
||||||
|
'name' => 'process#index',
|
||||||
|
'url' => '/process',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Face Recognition API V2
|
||||||
|
*/
|
||||||
|
// Get all named persons
|
||||||
|
[
|
||||||
|
'name' => 'api#getPersonsV2',
|
||||||
|
'url' => '/api/2.0/persons',
|
||||||
|
'verb' => 'GET',
|
||||||
|
],
|
||||||
|
// Get all photos associated to a person
|
||||||
|
[
|
||||||
|
'name' => 'api#getPerson',
|
||||||
|
'url' => '/api/2.0/person/{personName}',
|
||||||
|
'verb' => 'GET',
|
||||||
|
],
|
||||||
|
// Change name to a person or hide it
|
||||||
|
[
|
||||||
|
'name' => 'Api#updatePerson',
|
||||||
|
'url' => '/api/2.0/person/{personName}',
|
||||||
|
'verb' => 'PUT'
|
||||||
|
],
|
||||||
|
// Change name to a cluster or hide it.
|
||||||
|
[
|
||||||
|
'name' => 'Api#updateCluster',
|
||||||
|
'url' => '/api/2.0/cluster/{clusterId}',
|
||||||
|
'verb' => 'PUT'
|
||||||
|
],
|
||||||
|
// Get all names filtered by an query.
|
||||||
|
[
|
||||||
|
'name' => 'Api#autocomplete',
|
||||||
|
'url' => '/api/2.0/autocomplete',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Get all unassigned clusters to name
|
||||||
|
[
|
||||||
|
'name' => 'Api#discoverPerson',
|
||||||
|
'url' => '/api/2.0/discover',
|
||||||
|
'verb' => 'GET'
|
||||||
|
],
|
||||||
|
// Detach Face from cluster
|
||||||
|
[
|
||||||
|
'name' => 'Api#detachFace',
|
||||||
|
'url' => '/api/2.0/face/{faceId}/detach',
|
||||||
|
'verb' => 'PUT'
|
||||||
|
],
|
||||||
|
|
||||||
|
], 'ocs' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OCS Person API V1
|
||||||
|
*/
|
||||||
|
// Get all named persons
|
||||||
|
[
|
||||||
|
'name' => 'ocs_api#getPersonsV1',
|
||||||
|
'url' => '/api/v1/persons',
|
||||||
|
'verb' => 'GET',
|
||||||
|
],
|
||||||
|
// Get all faces associated to a person
|
||||||
|
[
|
||||||
|
'name' => 'ocs_api#getFacesByPerson',
|
||||||
|
'url' => '/api/v1/person/{name}/faces',
|
||||||
|
'verb' => 'GET',
|
||||||
|
],
|
||||||
|
|
||||||
|
]];
|
||||||
121
facerecognition/css/facerecognition.css
Normal file
121
facerecognition/css/facerecognition.css
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#div-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#optional-buttons-div {
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-back {
|
||||||
|
background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmlld2JveD0iMCAwIDE2IDE2IgogICB3aWR0aD0iMTYiCiAgIHZlcnNpb249IjEuMSIKICAgaGVpZ2h0PSIxNiIKICAgaWQ9InN2ZzQwIgogICBzb2RpcG9kaTpkb2NuYW1lPSJjb25maXJtLnN2ZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wLjEgKDNiYzJlODEzZjUsIDIwMjAtMDktMDcpIj4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE0NiI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMKICAgICBpZD0iZGVmczQ0IiAvPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDI1IgogICAgIGlkPSJuYW1lZHZpZXc0MiIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6em9vbT0iNTEuODc1IgogICAgIGlua3NjYXBlOmN4PSI4IgogICAgIGlua3NjYXBlOmN5PSI4IgogICAgIGlua3NjYXBlOndpbmRvdy14PSIwIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIwIgogICAgIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjEiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNDAiIC8+CiAgPHBhdGgKICAgICBkPSJtIDcuNDk4MzI2NSwxNS41MjQxNyBjIDAuODk3NCwwIDEuMzQwNCwtMS4wOTA5IDAuNjk3MywtMS43MTY4IGwgLTQuNzgzNywtNC43ODMxOTk4IEggMTQuOTg0OTI3IGMgMS4zNTIzLDAuMDE5MTI1IDEuMzUyMywtMi4wMTkxIDAsLTIgSCAzLjQxMjkyNjUgbCA0Ljc4MzIsLTQuNzgzMTk5NiBjIDAuOTgxNjMsLTAuOTQyNTEgLTAuNDcxNTUsLTIuMzk1NzAwMDQgLTEuNDE0MSwtMS40MTQxMDAwNCBMIDAuMjkwOTI2NDgsNy4zMTY4NzAyIGMgLTAuMzg3LDAuMzg3OCAtMC4zOTEsMS4wMjI4IDAsMS40MTQgTCA2Ljc4MTUyNjUsMTUuMjIxMTcgYyAwLjE4ODMsMC4xOTM1IDAuNDQ2OCwwLjMwMjY4IDAuNzE2OCwwLjMwMjcgeiIKICAgICBpZD0icGF0aDM4IiAvPgo8L3N2Zz4K")
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-box,
|
||||||
|
.person-box {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.person-desc {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: -5px;
|
||||||
|
color: var(--color-text-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster-title {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 22px;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-name {
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 18px;;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: rgba(127,127,127,.25) !important;
|
||||||
|
}
|
||||||
|
.person-name {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.persons-previews {
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faces-previews {
|
||||||
|
margin-left: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.face-preview-big {
|
||||||
|
background-color: rgba(210, 210, 210, .75);
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 128px;
|
||||||
|
width: 128px;
|
||||||
|
margin: 0 auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-preview-big {
|
||||||
|
background-color: rgba(210, 210, 210, .75);
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 12px;
|
||||||
|
height: 128px;
|
||||||
|
width: 128px;
|
||||||
|
margin: 0 auto;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.face-preview {
|
||||||
|
background-color: rgba(210, 210, 210, .75);
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 2px;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.face-preview-big:hover,
|
||||||
|
.face-preview:hover {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Admin page
|
||||||
|
*/
|
||||||
|
.icon-align {
|
||||||
|
padding: 11px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-slider {
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-ranged > label:first-child {
|
||||||
|
display: inline-block;
|
||||||
|
width: 25%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.span-highlighted {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: darkgray;
|
||||||
|
}
|
||||||
1
facerecognition/css/facerecognition.scss
Normal file
1
facerecognition/css/facerecognition.scss
Normal file
@ -0,0 +1 @@
|
|||||||
|
// Fake sass to fix test until fix webpack..
|
||||||
51
facerecognition/css/fr-dialogs.css
Normal file
51
facerecognition/css/fr-dialogs.css
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/* Dialog */
|
||||||
|
|
||||||
|
#fr-assign-dialog-input,
|
||||||
|
#fr-rename-dialog-input,
|
||||||
|
#fr-detach-face-dialog-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.face-preview-dialog {
|
||||||
|
background-color: rgba(210, 210, 210, .75);
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Autocomplete */
|
||||||
|
|
||||||
|
ul.autocomplete {
|
||||||
|
width: 90%;
|
||||||
|
display: none;
|
||||||
|
border: 1px solid #999;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #fff;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
overflow: auto;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.autocomplete li {
|
||||||
|
padding: .3em 1em;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: default;
|
||||||
|
color: var(--color-text-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.autocomplete li.selected {
|
||||||
|
background: var(--color-background-dark);
|
||||||
|
color: var(--color-main-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.autocomplete li.no-results {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.autocomplete mark {
|
||||||
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
1
facerecognition/img/app-dark.svg
Normal file
1
facerecognition/img/app-dark.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 16 16" height="16" width="16" version="1.1"><path d="m10 1c-1.75 0-3 1.43-3 2.8 0 1.4 0.1 2.4 0.8 3.5 0.2 0.29 0.5 0.35 0.7 0.6 0.135 0.5 0.24 1 0.1 1.5-0.28 0.1-0.525 0.22-0.8 0.33-0.085-0.15-0.23-0.2-0.47-0.4-0.73-0.44-1.56-0.75-2.33-1.04-0.1-0.37-0.1-0.65 0-1 0.156-0.166 0.37-0.27 0.5-0.43 0.46-0.6 0.5-1.654 0.5-2.37 0-1.06-0.954-1.9-2-1.9-1.17 0-2 1-2 1.9 0 0.93 0.034 1.64 0.5 2.37 0.13 0.2 0.367 0.26 0.5 0.43 0.1 0.33 0.1 0.654 0 1-0.85 0.3-1.6 0.64-2.34 1.04-0.57 0.4-0.52 0.205-0.66 1.53-0.11 1.06 2.335 1.13 4 1.13 0.06 0 0.11 0 0.17 0-0.054 0.274-0.1 0.63-0.17 1.3-0.16 1.59 3.5 1.7 6 1.7s6.16-0.1 6-1.7c-0.215-2-0.23-1.71-1-2.3-1.1-0.654-2.45-1.17-3.6-1.6-0.15-0.56-0.04-0.97 0.1-1.5 0.235-0.25 0.5-0.36 0.7-0.6 0.7-0.885 0.8-2.425 0.8-3.5 0-1.6-1.43-2.8-3-2.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 838 B |
4
facerecognition/img/app.svg
Normal file
4
facerecognition/img/app.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.1" viewBox="0 0 32 32">
|
||||||
|
<path style="block-progression:tb;color:#000000;text-transform:none;text-indent:0" d="m8.24 4.42c-2.2265 0-4.1153 1.6283-4.1153 3.7296 0.0158 0.66417 0.0752 1.4832 0.47155 3.2152v0.0429l0.0429 0.0429c0.12723 0.36445 0.31239 0.57294 0.55728 0.85739 0.24489 0.28444 0.53685 0.61924 0.81448 0.90025 0.0327 0.0331 0.0536 0.0535 0.0857 0.0858 0.0551 0.23961 0.12176 0.49747 0.17147 0.72878 0.13227 0.61541 0.11871 1.0512 0.0857 1.2003-0.95674 0.33594-2.147 0.73601-3.2151 1.2003-0.59962 0.26069-1.1422 0.49348-1.5861 0.77165-0.44388 0.27818-0.88534 0.48834-1.0288 1.1146-0.002 0.0285-0.002 0.0572 0 0.0858-0.1402 1.2874-0.35229 3.1806-0.51441 4.4584-0.035 0.26898 0.10676 0.55252 0.34294 0.68591 1.9392 1.0475 4.9181 1.4691 7.8876 1.4576 2.9695-0.0116 5.9247-0.45797 7.8019-1.4576 0.23617-0.13338 0.37794-0.41693 0.34294-0.68591-0.0518-0.39943-0.11534-1.3001-0.17147-2.1863-0.0561-0.88623-0.10486-1.758-0.17147-2.2721-0.0232-0.12744-0.0835-0.2479-0.17147-0.34295-0.59633-0.71211-1.4873-1.1474-2.5292-1.5862-0.95121-0.40054-2.0664-0.81649-3.1722-1.2861-0.0619-0.13788-0.12337-0.53903 0-1.1575 0.0331-0.16606 0.085-0.34392 0.12861-0.51443 0.10392-0.1164 0.18492-0.21152 0.30007-0.34296 0.2456-0.28032 0.50949-0.57438 0.72875-0.85738 0.21926-0.28301 0.39864-0.52579 0.51441-0.85739l0.0429-0.0429c0.44807-1.8085 0.44831-2.5631 0.47155-3.2152v-0.0429c0-2.1013-1.8888-3.7296-4.1153-3.7296zm11.772-3.4224c-3.2461 0-5.9999 2.3739-5.9999 5.4374 0.023 0.9683 0.10964 2.1624 0.68749 4.6874v0.0625l0.0625 0.0625c0.1855 0.53134 0.45545 0.83529 0.81249 1.25 0.35704 0.4147 0.7827 0.90279 1.1875 1.3125 0.0476 0.0482 0.0781 0.0781 0.12499 0.12502 0.0803 0.34932 0.17752 0.72527 0.25 1.0625 0.19284 0.8972 0.17306 1.5326 0.12501 1.75-1.3949 0.48977-3.1303 1.073-4.6874 1.75-0.87422 0.38005-1.6653 0.71943-2.3125 1.125-0.64717 0.40555-1.2908 0.71195-1.5 1.625-0.003 0.0416-0.003 0.0834 0 0.12502-0.20442 1.8769-0.51363 4.637-0.74999 6.4999-0.051 0.39215 0.15566 0.80553 0.49999 0.99999 2.8273 1.5272 7.1704 2.1419 11.5 2.125 4.3294-0.0168 8.6379-0.66766 11.375-2.125 0.34433-0.19446 0.55102-0.60784 0.49999-0.99999-0.0755-0.58232-0.16816-1.8954-0.25-3.1875-0.0818-1.292-0.15288-2.563-0.24998-3.3125-0.0339-0.18578-0.12172-0.36141-0.25-0.49999-0.86942-1.0382-2.1684-1.6728-3.6875-2.3125-1.3868-0.58395-3.0127-1.1904-4.6249-1.875-0.0902-0.20102-0.17988-0.78586 0-1.6875 0.0483-0.2421 0.12394-0.50141 0.1875-0.74999 0.15152-0.1697 0.26961-0.30838 0.4375-0.5 0.35807-0.40868 0.74281-0.83739 1.0625-1.25 0.31967-0.41259 0.58121-0.76654 0.74999-1.25l0.0625-0.0625c0.65328-2.6366 0.65362-3.7367 0.6875-4.6874v-0.0625c0-3.0635-2.7538-5.4374-5.9999-5.4374z" fill="#FFF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
53
facerecognition/img/back.svg
Normal file
53
facerecognition/img/back.svg
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
viewbox="0 0 16 16"
|
||||||
|
width="16"
|
||||||
|
version="1.1"
|
||||||
|
height="16"
|
||||||
|
id="svg40"
|
||||||
|
sodipodi:docname="confirm.svg"
|
||||||
|
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
||||||
|
<metadata
|
||||||
|
id="metadata46">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs44" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1025"
|
||||||
|
id="namedview42"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="51.875"
|
||||||
|
inkscape:cx="8"
|
||||||
|
inkscape:cy="8"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg40" />
|
||||||
|
<path
|
||||||
|
d="m 7.4983265,15.52417 c 0.8974,0 1.3404,-1.0909 0.6973,-1.7168 l -4.7837,-4.7831998 H 14.984927 c 1.3523,0.019125 1.3523,-2.0191 0,-2 H 3.4129265 l 4.7832,-4.7831996 c 0.98163,-0.94251 -0.47155,-2.39570004 -1.4141,-1.41410004 L 0.29092648,7.3168702 c -0.387,0.3878 -0.391,1.0228 0,1.414 L 6.7815265,15.22117 c 0.1883,0.1935 0.4468,0.30268 0.7168,0.3027 z"
|
||||||
|
id="path38" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
facerecognition/img/glasses.png
Normal file
BIN
facerecognition/img/glasses.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
BIN
facerecognition/img/mustache.png
Normal file
BIN
facerecognition/img/mustache.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
2
facerecognition/js/facerecognition-admin.js
Normal file
2
facerecognition/js/facerecognition-admin.js
Normal file
File diff suppressed because one or more lines are too long
486
facerecognition/js/facerecognition-dialogs.js
Normal file
486
facerecognition/js/facerecognition-dialogs.js
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
/*
|
||||||
|
* @copyright 2019-2021 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author 2019 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this class to ease the usage of jquery dialogs
|
||||||
|
*/
|
||||||
|
const FrDialogs = {
|
||||||
|
|
||||||
|
hide: function (faces, callback) {
|
||||||
|
return $.when(this._getMessageTemplate()).then(function ($tmpl) {
|
||||||
|
var dialogName = 'fr-hidee-dialog';
|
||||||
|
var dialogId = '#' + dialogName;
|
||||||
|
var $dlg = $tmpl.octemplate({
|
||||||
|
dialog_name: dialogName,
|
||||||
|
title: t('facerecognition', 'Hide person'),
|
||||||
|
message: t('facerecognition', 'You can still see that person in the photos, but assigning a name will only be for that photo.'),
|
||||||
|
type: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
$dlg.append($('<br/>'));
|
||||||
|
|
||||||
|
var div = $('<div/>').attr('style', 'text-align: center');
|
||||||
|
$dlg.append(div);
|
||||||
|
|
||||||
|
for (var face of faces) {
|
||||||
|
if (face['fileUrl'] !== undefined) {
|
||||||
|
div.append($('<a href="' + face['fileUrl'] + '" target="_blank"><img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/></a>'));
|
||||||
|
} else {
|
||||||
|
div.append($('<img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/>'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('body').append($dlg);
|
||||||
|
|
||||||
|
// wrap callback in _.once():
|
||||||
|
// only call callback once and not twice (button handler and close
|
||||||
|
// event) but call it for the close event, if ESC or the x is hit
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback = _.once(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonlist = [{
|
||||||
|
text: t('facerecognition', 'Cancel'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
text: t('facerecognition', 'Hide'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultButton: true
|
||||||
|
}];
|
||||||
|
|
||||||
|
$(dialogId).ocdialog({
|
||||||
|
closeOnEscape: true,
|
||||||
|
modal: true,
|
||||||
|
buttons: buttonlist,
|
||||||
|
close: function () {
|
||||||
|
// callback is already fired if Yes/No is clicked directly
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
rename: function (name, faces, callback) {
|
||||||
|
return $.when(this._getMessageTemplate()).then(function ($tmpl) {
|
||||||
|
var dialogName = 'fr-rename-dialog';
|
||||||
|
var dialogId = '#' + dialogName;
|
||||||
|
var $dlg = $tmpl.octemplate({
|
||||||
|
dialog_name: dialogName,
|
||||||
|
title: t('facerecognition', 'Rename person'),
|
||||||
|
message: t('facerecognition', 'Please enter a name to rename the person'),
|
||||||
|
type: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
$dlg.append($('<br/>'));
|
||||||
|
|
||||||
|
var div = $('<div/>').attr('style', 'text-align: center');
|
||||||
|
$dlg.append(div);
|
||||||
|
|
||||||
|
for (var face of faces) {
|
||||||
|
if (face['fileUrl'] !== undefined) {
|
||||||
|
div.append($('<a href="' + face['fileUrl'] + '" target="_blank"><img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/></a>'));
|
||||||
|
} else {
|
||||||
|
div.append($('<img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/>'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = $('<input/>').attr('type', 'text').attr('id', dialogName + '-input').attr('placeholder', name).attr('value', name);
|
||||||
|
$dlg.append(input);
|
||||||
|
|
||||||
|
$('body').append($dlg);
|
||||||
|
|
||||||
|
// wrap callback in _.once():
|
||||||
|
// only call callback once and not twice (button handler and close
|
||||||
|
// event) but call it for the close event, if ESC or the x is hit
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback = _.once(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonlist = [{
|
||||||
|
text: t('facerecognition', 'Cancel'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(false, input.val().trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
text: t('facerecognition', 'Rename'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(true, input.val().trim());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultButton: true
|
||||||
|
}];
|
||||||
|
|
||||||
|
$(dialogId).ocdialog({
|
||||||
|
closeOnEscape: true,
|
||||||
|
modal: true,
|
||||||
|
buttons: buttonlist,
|
||||||
|
close: function () {
|
||||||
|
// callback is already fired if Yes/No is clicked directly
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(false, input.val());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new AutoComplete({
|
||||||
|
input: document.getElementById(dialogName + "-input"),
|
||||||
|
lookup (query) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
$.get(OC.generateUrl('/apps/facerecognition/autocomplete/' + query)).done(function (names) {
|
||||||
|
resolve(names);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
silent: true,
|
||||||
|
highlight: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$(dialogId + "-input").keydown(function(event) {
|
||||||
|
// It only prevents the that change the image when you press arrow keys.
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
// It only prevents the that change the image when you press enter.
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
detachFace: function (face, oldName, callback) {
|
||||||
|
return $.when(this._getMessageTemplate()).then(function ($tmpl) {
|
||||||
|
var dialogName = 'fr-detach-face-dialog';
|
||||||
|
var dialogId = '#' + dialogName;
|
||||||
|
var $dlg = $tmpl.octemplate({
|
||||||
|
dialog_name: dialogName,
|
||||||
|
title: t('facerecognition', 'This person is not {name}', {name: oldName}),
|
||||||
|
message: t('facerecognition', 'Optionally you can assign the correct name'),
|
||||||
|
type: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
$dlg.append($('<br/>'));
|
||||||
|
|
||||||
|
var div = $('<div/>').attr('style', 'text-align: center');
|
||||||
|
$dlg.append(div);
|
||||||
|
|
||||||
|
div.append($('<img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/>'));
|
||||||
|
|
||||||
|
var input = $('<input/>').attr('type', 'text').attr('id', dialogName + '-input').attr('placeholder', t('facerecognition', 'Please assign a name to this person.'));
|
||||||
|
$dlg.append(input);
|
||||||
|
|
||||||
|
$('body').append($dlg);
|
||||||
|
|
||||||
|
// wrap callback in _.once():
|
||||||
|
// only call callback once and not twice (button handler and close
|
||||||
|
// event) but call it for the close event, if ESC or the x is hit
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback = _.once(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonlist = [{
|
||||||
|
text: t('facerecognition', 'Cancel'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(false, null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
text: t('facerecognition', 'Save'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(true, input.val().trim().length > 0 ? input.val().trim() : null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultButton: true
|
||||||
|
}];
|
||||||
|
|
||||||
|
$(dialogId).ocdialog({
|
||||||
|
closeOnEscape: true,
|
||||||
|
modal: true,
|
||||||
|
buttons: buttonlist,
|
||||||
|
close: function () {
|
||||||
|
// callback is already fired if Yes/No is clicked directly
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(false, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new AutoComplete({
|
||||||
|
input: document.getElementById(dialogName + "-input"),
|
||||||
|
lookup (query) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
$.get(OC.generateUrl('/apps/facerecognition/autocomplete/' + query)).done(function (names) {
|
||||||
|
resolve(names);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
silent: true,
|
||||||
|
highlight: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$(dialogId + "-input").keydown(function(event) {
|
||||||
|
// It only prevents the that change the image when you press arrow keys.
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
// It only prevents the that change the image when you press enter.
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
assignName: function (faces, callback) {
|
||||||
|
return $.when(this._getMessageTemplate()).then(function ($tmpl) {
|
||||||
|
var dialogName = 'fr-assign-dialog';
|
||||||
|
var dialogId = '#' + dialogName;
|
||||||
|
var $dlg = $tmpl.octemplate({
|
||||||
|
dialog_name: dialogName,
|
||||||
|
title: t('facerecognition', 'Add name'),
|
||||||
|
message: t('facerecognition', 'Please assign a name to this person.'),
|
||||||
|
type: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
$dlg.append($('<br/>'));
|
||||||
|
|
||||||
|
var div = $('<div/>').attr('style', 'text-align: center');
|
||||||
|
$dlg.append(div);
|
||||||
|
|
||||||
|
for (var face of faces) {
|
||||||
|
if (face['fileUrl'] !== undefined) {
|
||||||
|
div.append($('<a href="' + face['fileUrl'] + '" target="_blank"><img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/></a>'));
|
||||||
|
} else {
|
||||||
|
div.append($('<img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/>'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = $('<input/>').attr('type', 'text').attr('id', dialogName + '-input').attr('placeholder', t('facerecognition', 'Please assign a name to this person.'));
|
||||||
|
$dlg.append(input);
|
||||||
|
|
||||||
|
$('body').append($dlg);
|
||||||
|
|
||||||
|
// wrap callback in _.once():
|
||||||
|
// only call callback once and not twice (button handler and close
|
||||||
|
// event) but call it for the close event, if ESC or the x is hit
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback = _.once(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonlist = [{
|
||||||
|
text: t('facerecognition', 'Ignore'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(true, null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
text: t('facerecognition', 'Skip for now'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(true, '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultButton: false
|
||||||
|
}, {
|
||||||
|
text: t('facerecognition', 'Save'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(true, input.val().trim());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultButton: true
|
||||||
|
}];
|
||||||
|
|
||||||
|
$(dialogId).ocdialog({
|
||||||
|
closeOnEscape: true,
|
||||||
|
modal: true,
|
||||||
|
buttons: buttonlist,
|
||||||
|
close: function () {
|
||||||
|
// callback is already fired if Yes/No is clicked directly
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(false, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new AutoComplete({
|
||||||
|
input: document.getElementById(dialogName + "-input"),
|
||||||
|
lookup (query) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
$.get(OC.generateUrl('/apps/facerecognition/autocomplete/' + query)).done(function (names) {
|
||||||
|
resolve(names);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
silent: true,
|
||||||
|
highlight: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$(dialogId + "-input").keydown(function(event) {
|
||||||
|
// It only prevents the that change the image when you press arrow keys.
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
// It only prevents the that change the image when you press enter.
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
assignIgnored: function (faces, callback) {
|
||||||
|
return $.when(this._getMessageTemplate()).then(function ($tmpl) {
|
||||||
|
var dialogName = 'fr-assign-dialog';
|
||||||
|
var dialogId = '#' + dialogName;
|
||||||
|
var $dlg = $tmpl.octemplate({
|
||||||
|
dialog_name: dialogName,
|
||||||
|
title: t('facerecognition', 'Add name'),
|
||||||
|
message: t('facerecognition', 'Please assign a name to this person.'),
|
||||||
|
type: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
|
$dlg.append($('<br/>'));
|
||||||
|
|
||||||
|
var div = $('<div/>').attr('style', 'text-align: center');
|
||||||
|
$dlg.append(div);
|
||||||
|
|
||||||
|
for (var face of faces) {
|
||||||
|
if (face['fileUrl'] !== undefined) {
|
||||||
|
div.append($('<a href="' + face['fileUrl'] + '" target="_blank"><img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/></a>'));
|
||||||
|
} else {
|
||||||
|
div.append($('<img class="face-preview-dialog" src="' + face['thumbUrl'] + '" width="50" height="50"/>'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = $('<input/>').attr('type', 'text').attr('id', dialogName + '-input').attr('placeholder', t('facerecognition', 'Please assign a name to this person.'));
|
||||||
|
$dlg.append(input);
|
||||||
|
|
||||||
|
$('body').append($dlg);
|
||||||
|
|
||||||
|
// wrap callback in _.once():
|
||||||
|
// only call callback once and not twice (button handler and close
|
||||||
|
// event) but call it for the close event, if ESC or the x is hit
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback = _.once(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonlist = [{
|
||||||
|
text: t('facerecognition', 'Keep ignored'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(true, null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
text: t('facerecognition', 'Save'),
|
||||||
|
click: function () {
|
||||||
|
$(dialogId).ocdialog('close');
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(true, input.val().trim());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultButton: true
|
||||||
|
}];
|
||||||
|
|
||||||
|
$(dialogId).ocdialog({
|
||||||
|
closeOnEscape: true,
|
||||||
|
modal: true,
|
||||||
|
buttons: buttonlist,
|
||||||
|
close: function () {
|
||||||
|
// callback is already fired if Yes/No is clicked directly
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback(false, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new AutoComplete({
|
||||||
|
input: document.getElementById(dialogName + "-input"),
|
||||||
|
lookup (query) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
$.get(OC.generateUrl('/apps/facerecognition/autocomplete/' + query)).done(function (names) {
|
||||||
|
resolve(names);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
silent: true,
|
||||||
|
highlight: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$(dialogId + "-input").keydown(function(event) {
|
||||||
|
// It only prevents the that change the image when you press arrow keys.
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
// It only prevents the that change the image when you press enter.
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getMessageTemplate: function () {
|
||||||
|
var defer = $.Deferred();
|
||||||
|
if (!this.$messageTemplate) {
|
||||||
|
var self = this;
|
||||||
|
$.get(OC.filePath('core', 'templates', 'message.html'), function (tmpl) {
|
||||||
|
self.$messageTemplate = $(tmpl);
|
||||||
|
defer.resolve(self.$messageTemplate);
|
||||||
|
})
|
||||||
|
.fail(function (jqXHR, textStatus, errorThrown) {
|
||||||
|
defer.reject(jqXHR.status, errorThrown);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
defer.resolve(this.$messageTemplate);
|
||||||
|
}
|
||||||
|
return defer.promise();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
2
facerecognition/js/facerecognition-personal.js
Normal file
2
facerecognition/js/facerecognition-personal.js
Normal file
File diff suppressed because one or more lines are too long
3
facerecognition/js/facerecognition-sidebar.js
Normal file
3
facerecognition/js/facerecognition-sidebar.js
Normal file
File diff suppressed because one or more lines are too long
51
facerecognition/js/facerecognition-sidebar.js.LICENSE.txt
Normal file
51
facerecognition/js/facerecognition-sidebar.js.LICENSE.txt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*!
|
||||||
|
* Vue.js v2.7.14
|
||||||
|
* (c) 2014-2022 Evan You
|
||||||
|
* Released under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* escape-html
|
||||||
|
* Copyright(c) 2012-2013 TJ Holowaychuk
|
||||||
|
* Copyright(c) 2015 Andreas Lubbe
|
||||||
|
* Copyright(c) 2015 Tiancheng "Timothy" Gu
|
||||||
|
* MIT Licensed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* focus-trap 7.4.3
|
||||||
|
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* tabbable 6.1.2
|
||||||
|
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! @license DOMPurify 2.4.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.4.5/LICENSE */
|
||||||
|
|
||||||
|
/*! For license information please see NcAppSidebar.js.LICENSE.txt */
|
||||||
|
|
||||||
|
/*! For license information please see NcAppSidebarTab.js.LICENSE.txt */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
223
facerecognition/js/facerecognition-templates.js
Normal file
223
facerecognition/js/facerecognition-templates.js
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
(function() {
|
||||||
|
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
|
templates['personal'] = template({"1":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <h2 style=\"display: flex;\">\n <a class=\"title-icon icon-back\"></a>\n <span class=\"title-name\">"
|
||||||
|
+ container.escapeExpression(((helper = (helper = lookupProperty(helpers,"personName") || (depth0 != null ? lookupProperty(depth0,"personName") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"personName","hash":{},"data":data,"loc":{"start":{"line":6,"column":27},"end":{"line":6,"column":41}}}) : helper)))
|
||||||
|
+ "</span>\n </h2>\n <div id=\"persons-navigation\">\n"
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"each").call(alias1,(depth0 != null ? lookupProperty(depth0,"clustersByName") : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":9,"column":2},"end":{"line":22,"column":11}}})) != null ? stack1 : "")
|
||||||
|
+ " </div>\n";
|
||||||
|
},"2":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, helper, alias1=container.lambda, alias2=container.escapeExpression, alias3=depth0 != null ? depth0 : (container.nullContext || {}), alias4=container.hooks.helperMissing, alias5="function", lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <h2 class=\"cluster-title\" style=\"display: flex;\">\n <span class=\"title-name\">"
|
||||||
|
+ alias2(alias1((depth0 != null ? lookupProperty(depth0,"name") : depth0), depth0))
|
||||||
|
+ "</span>\n <a id=\"rename-cluster\" title=\""
|
||||||
|
+ alias2(((helper = (helper = lookupProperty(helpers,"renameHint") || (depth0 != null ? lookupProperty(depth0,"renameHint") : depth0)) != null ? helper : alias4),(typeof helper === alias5 ? helper.call(alias3,{"name":"renameHint","hash":{},"data":data,"loc":{"start":{"line":12,"column":34},"end":{"line":12,"column":48}}}) : helper)))
|
||||||
|
+ "\" class=\"title-icon icon-rename\" data-id=\""
|
||||||
|
+ alias2(alias1((depth0 != null ? lookupProperty(depth0,"id") : depth0), depth0))
|
||||||
|
+ "\"></a>\n <a id=\"hide-cluster\" title=\""
|
||||||
|
+ alias2(((helper = (helper = lookupProperty(helpers,"hideHint") || (depth0 != null ? lookupProperty(depth0,"hideHint") : depth0)) != null ? helper : alias4),(typeof helper === alias5 ? helper.call(alias3,{"name":"hideHint","hash":{},"data":data,"loc":{"start":{"line":13,"column":32},"end":{"line":13,"column":44}}}) : helper)))
|
||||||
|
+ "\" class=\"title-icon icon-disabled-user\" data-id=\""
|
||||||
|
+ alias2(alias1((depth0 != null ? lookupProperty(depth0,"id") : depth0), depth0))
|
||||||
|
+ "\"></a>\n </h2>\n <div class=\"faces-previews\">\n"
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"each").call(alias3,(depth0 != null ? lookupProperty(depth0,"faces") : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":16,"column":4},"end":{"line":20,"column":13}}})) != null ? stack1 : "")
|
||||||
|
+ " </div>\n";
|
||||||
|
},"3":function(container,depth0,helpers,partials,data) {
|
||||||
|
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <a target=\"_blank\" href=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"fileUrl") || (depth0 != null ? lookupProperty(depth0,"fileUrl") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"fileUrl","hash":{},"data":data,"loc":{"start":{"line":17,"column":30},"end":{"line":17,"column":41}}}) : helper)))
|
||||||
|
+ "\">\n <div class=\"face-preview lozad\" data-background-image=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"thumbUrl") || (depth0 != null ? lookupProperty(depth0,"thumbUrl") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"thumbUrl","hash":{},"data":data,"loc":{"start":{"line":18,"column":61},"end":{"line":18,"column":73}}}) : helper)))
|
||||||
|
+ "\" width=\"50\" height=\"50\"></div>\n </a>\n";
|
||||||
|
},"5":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"personName") : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.program(9, data, 0),"data":data,"loc":{"start":{"line":24,"column":0},"end":{"line":101,"column":0}}})) != null ? stack1 : "");
|
||||||
|
},"6":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <h2 style=\"display: flex;\">\n <a class=\"title-icon icon-back\"></a>\n <span class=\"title-name\">"
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"personName") || (depth0 != null ? lookupProperty(depth0,"personName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"personName","hash":{},"data":data,"loc":{"start":{"line":27,"column":27},"end":{"line":27,"column":41}}}) : helper)))
|
||||||
|
+ "</span>\n <a id=\"rename-person\" title=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"renameHint") || (depth0 != null ? lookupProperty(depth0,"renameHint") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"renameHint","hash":{},"data":data,"loc":{"start":{"line":28,"column":31},"end":{"line":28,"column":45}}}) : helper)))
|
||||||
|
+ "\" class=\"title-icon icon-rename\" data-id=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"personName") || (depth0 != null ? lookupProperty(depth0,"personName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"personName","hash":{},"data":data,"loc":{"start":{"line":28,"column":87},"end":{"line":28,"column":101}}}) : helper)))
|
||||||
|
+ "\"></a>\n <a id=\"hide-person\" title=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"hideHint") || (depth0 != null ? lookupProperty(depth0,"hideHint") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"hideHint","hash":{},"data":data,"loc":{"start":{"line":29,"column":29},"end":{"line":29,"column":41}}}) : helper)))
|
||||||
|
+ "\" class=\"title-icon icon-disabled-user\" data-id=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"personName") || (depth0 != null ? lookupProperty(depth0,"personName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"personName","hash":{},"data":data,"loc":{"start":{"line":29,"column":90},"end":{"line":29,"column":104}}}) : helper)))
|
||||||
|
+ "\"></a>\n </h2>\n <button id=\"review-person-clusters\" type=\"button\" class=\"primary\">\n "
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"showMoreButton") || (depth0 != null ? lookupProperty(depth0,"showMoreButton") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"showMoreButton","hash":{},"data":data,"loc":{"start":{"line":32,"column":2},"end":{"line":32,"column":20}}}) : helper)))
|
||||||
|
+ "\n </button>\n <div id=\"optional-buttons-div\"></div>\n <div class=\"persons-previews\">\n"
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"each").call(alias1,(depth0 != null ? lookupProperty(depth0,"personImages") : depth0),{"name":"each","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":36,"column":2},"end":{"line":40,"column":11}}})) != null ? stack1 : "")
|
||||||
|
+ " </div>\n";
|
||||||
|
},"7":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <div class=\"image-box\">\n <div class=\"file-preview-big lozad\" data-background-delimiter=\"\\\" data-background-image=\""
|
||||||
|
+ ((stack1 = ((helper = (helper = lookupProperty(helpers,"thumbUrl") || (depth0 != null ? lookupProperty(depth0,"thumbUrl") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"thumbUrl","hash":{},"data":data,"loc":{"start":{"line":38,"column":93},"end":{"line":38,"column":107}}}) : helper))) != null ? stack1 : "")
|
||||||
|
+ "\" width=\"128\" height=\"128\" data-id=\""
|
||||||
|
+ container.escapeExpression(((helper = (helper = lookupProperty(helpers,"filename") || (depth0 != null ? lookupProperty(depth0,"filename") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"filename","hash":{},"data":data,"loc":{"start":{"line":38,"column":143},"end":{"line":38,"column":155}}}) : helper)))
|
||||||
|
+ "\"></div>\n </div>\n";
|
||||||
|
},"9":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"persons") : depth0),{"name":"if","hash":{},"fn":container.program(10, data, 0),"inverse":container.program(19, data, 0),"data":data,"loc":{"start":{"line":42,"column":0},"end":{"line":101,"column":0}}})) != null ? stack1 : "");
|
||||||
|
},"10":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <h2>\n "
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"appName") || (depth0 != null ? lookupProperty(depth0,"appName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"appName","hash":{},"data":data,"loc":{"start":{"line":44,"column":2},"end":{"line":44,"column":13}}}) : helper)))
|
||||||
|
+ "\n </h2>\n <input id=\"enableFacerecognition\" name=\"enableFacerecognition\" type=\"checkbox\" class=\"checkbox\" value=\"1\" "
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"enabled") : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":46,"column":107},"end":{"line":46,"column":136}}})) != null ? stack1 : "")
|
||||||
|
+ ">\n <label for=\"enableFacerecognition\">"
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"enableDescription") || (depth0 != null ? lookupProperty(depth0,"enableDescription") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"enableDescription","hash":{},"data":data,"loc":{"start":{"line":47,"column":36},"end":{"line":47,"column":57}}}) : helper)))
|
||||||
|
+ "</label><br>\n <div id=\"optional-buttons-div\">\n"
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"hasUnamed") : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":49,"column":2},"end":{"line":51,"column":9}}})) != null ? stack1 : "")
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"hasHidden") : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":52,"column":2},"end":{"line":54,"column":9}}})) != null ? stack1 : "")
|
||||||
|
+ " </div>\n <div class=\"persons-previews\">\n"
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"each").call(alias1,(depth0 != null ? lookupProperty(depth0,"persons") : depth0),{"name":"each","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":57,"column":2},"end":{"line":65,"column":11}}})) != null ? stack1 : "")
|
||||||
|
+ " </div>\n";
|
||||||
|
},"11":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "checked";
|
||||||
|
},"13":function(container,depth0,helpers,partials,data) {
|
||||||
|
var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <button id=\"show-more-clusters\" type=\"button\" class=\"primary\">"
|
||||||
|
+ container.escapeExpression(((helper = (helper = lookupProperty(helpers,"reviewPeopleMsg") || (depth0 != null ? lookupProperty(depth0,"reviewPeopleMsg") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"reviewPeopleMsg","hash":{},"data":data,"loc":{"start":{"line":50,"column":65},"end":{"line":50,"column":84}}}) : helper)))
|
||||||
|
+ "</button>\n";
|
||||||
|
},"15":function(container,depth0,helpers,partials,data) {
|
||||||
|
var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <button id=\"show-ignored-clusters\" type=\"button\" class=\"primary\">"
|
||||||
|
+ container.escapeExpression(((helper = (helper = lookupProperty(helpers,"reviewIgnoredMsg") || (depth0 != null ? lookupProperty(depth0,"reviewIgnoredMsg") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"reviewIgnoredMsg","hash":{},"data":data,"loc":{"start":{"line":53,"column":68},"end":{"line":53,"column":88}}}) : helper)))
|
||||||
|
+ "</button>\n";
|
||||||
|
},"17":function(container,depth0,helpers,partials,data) {
|
||||||
|
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <div class=\"person-box\" data-id=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":58,"column":36},"end":{"line":58,"column":44}}}) : helper)))
|
||||||
|
+ "\">\n <div class=\"face-preview-big lozad\" data-background-image=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"thumbUrl") || (depth0 != null ? lookupProperty(depth0,"thumbUrl") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"thumbUrl","hash":{},"data":data,"loc":{"start":{"line":59,"column":63},"end":{"line":59,"column":75}}}) : helper)))
|
||||||
|
+ "\" width=\"128\" height=\"128\"></div>\n <p class=\"person-name\">\n <span>"
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"name") || (depth0 != null ? lookupProperty(depth0,"name") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data,"loc":{"start":{"line":61,"column":11},"end":{"line":61,"column":19}}}) : helper)))
|
||||||
|
+ "</span>\n </p>\n <p class=\"person-desc\">"
|
||||||
|
+ alias4((lookupProperty(helpers,"noPhotos")||(depth0 && lookupProperty(depth0,"noPhotos"))||alias2).call(alias1,(depth0 != null ? lookupProperty(depth0,"count") : depth0),{"name":"noPhotos","hash":{},"data":data,"loc":{"start":{"line":63,"column":27},"end":{"line":63,"column":45}}}))
|
||||||
|
+ "</p>\n </div>\n";
|
||||||
|
},"19":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"loaded") : depth0),{"name":"if","hash":{},"fn":container.program(20, data, 0),"inverse":container.program(22, data, 0),"data":data,"loc":{"start":{"line":67,"column":0},"end":{"line":101,"column":0}}})) != null ? stack1 : "");
|
||||||
|
},"20":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <h2>\n "
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"appName") || (depth0 != null ? lookupProperty(depth0,"appName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"appName","hash":{},"data":data,"loc":{"start":{"line":69,"column":2},"end":{"line":69,"column":13}}}) : helper)))
|
||||||
|
+ "\n </h2>\n <input id=\"enableFacerecognition\" name=\"enableFacerecognition\" type=\"checkbox\" class=\"checkbox\" value=\"1\" "
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"enabled") : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":71,"column":107},"end":{"line":71,"column":136}}})) != null ? stack1 : "")
|
||||||
|
+ ">\n <label for=\"enableFacerecognition\">"
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"enableDescription") || (depth0 != null ? lookupProperty(depth0,"enableDescription") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"enableDescription","hash":{},"data":data,"loc":{"start":{"line":72,"column":36},"end":{"line":72,"column":57}}}) : helper)))
|
||||||
|
+ "</label><br>\n <div id=\"optional-buttons-div\">\n"
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"hasUnamed") : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":74,"column":2},"end":{"line":76,"column":9}}})) != null ? stack1 : "")
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"if").call(alias1,(depth0 != null ? lookupProperty(depth0,"hasHidden") : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data,"loc":{"start":{"line":77,"column":2},"end":{"line":79,"column":9}}})) != null ? stack1 : "")
|
||||||
|
+ " </div>\n <div class=\"emptycontent\">\n <div class=\"icon-contacts-dark svg\"></div>\n <h2>\n "
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"emptyMsg") || (depth0 != null ? lookupProperty(depth0,"emptyMsg") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"emptyMsg","hash":{},"data":data,"loc":{"start":{"line":84,"column":3},"end":{"line":84,"column":15}}}) : helper)))
|
||||||
|
+ "\n </h2>\n <p>\n "
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"emptyHint") || (depth0 != null ? lookupProperty(depth0,"emptyHint") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"emptyHint","hash":{},"data":data,"loc":{"start":{"line":87,"column":2},"end":{"line":87,"column":15}}}) : helper)))
|
||||||
|
+ "\n </p>\n </div>\n";
|
||||||
|
},"22":function(container,depth0,helpers,partials,data) {
|
||||||
|
var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=container.hooks.helperMissing, alias3="function", alias4=container.escapeExpression, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return " <h2>\n "
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"appName") || (depth0 != null ? lookupProperty(depth0,"appName") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"appName","hash":{},"data":data,"loc":{"start":{"line":92,"column":2},"end":{"line":92,"column":13}}}) : helper)))
|
||||||
|
+ "\n </h2>\n <div class=\"emptycontent\">\n <div class=\"icon-contacts-dark svg\"></div>\n <h2>\n "
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"loadingMsg") || (depth0 != null ? lookupProperty(depth0,"loadingMsg") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"loadingMsg","hash":{},"data":data,"loc":{"start":{"line":97,"column":3},"end":{"line":97,"column":17}}}) : helper)))
|
||||||
|
+ "\n </h2>\n <img class=\"loadingimport\" src=\""
|
||||||
|
+ alias4(((helper = (helper = lookupProperty(helpers,"loadingIcon") || (depth0 != null ? lookupProperty(depth0,"loadingIcon") : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"loadingIcon","hash":{},"data":data,"loc":{"start":{"line":99,"column":34},"end":{"line":99,"column":49}}}) : helper)))
|
||||||
|
+ "\"/>\n </div>\n";
|
||||||
|
},"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return parent[propertyName];
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return "<div class=\"section\" id=\"facerecognition\">\n\n"
|
||||||
|
+ ((stack1 = lookupProperty(helpers,"if").call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? lookupProperty(depth0,"clustersByName") : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(5, data, 0),"data":data,"loc":{"start":{"line":3,"column":0},"end":{"line":101,"column":7}}})) != null ? stack1 : "")
|
||||||
|
+ "\n</div>";
|
||||||
|
},"useData":true});
|
||||||
|
})();
|
||||||
2
facerecognition/js/vendor/autocomplete.js
vendored
Normal file
2
facerecognition/js/vendor/autocomplete.js
vendored
Normal file
File diff suppressed because one or more lines are too long
158
facerecognition/js/vendor/egg.js
vendored
Normal file
158
facerecognition/js/vendor/egg.js
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// thatmikeflynn.com/egg.js/
|
||||||
|
/*
|
||||||
|
Copyright (c) 2015 Mike Flynn
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
function Egg(/* keySequence, fn, metadata */) {
|
||||||
|
this.eggs = [];
|
||||||
|
this.hooks = [];
|
||||||
|
this.kps = [];
|
||||||
|
this.activeEgg = '';
|
||||||
|
// for now we'll just ignore the shift key to allow capital letters
|
||||||
|
this.ignoredKeys = [16];
|
||||||
|
|
||||||
|
if(arguments.length) {
|
||||||
|
this.AddCode.apply(this, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to call passed function bound to Egg object instance
|
||||||
|
Egg.prototype.__execute = function(fn) {
|
||||||
|
return typeof fn === 'function' && fn.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// converts literal character values to keyCodes
|
||||||
|
Egg.prototype.__toCharCodes = function(keys) {
|
||||||
|
var special = {
|
||||||
|
"slash": 191, "up": 38, "down": 40, "left": 37, "right": 39, "enter": 13, "space": 32, "ctrl": 17, "alt": 18, "tab": 9, "esc": 27
|
||||||
|
},
|
||||||
|
specialKeys = Object.keys(special);
|
||||||
|
|
||||||
|
if(typeof keys === 'string') {
|
||||||
|
// make sure there isn't any whitespace
|
||||||
|
keys = keys.split(',').map(function(key){
|
||||||
|
return key.trim();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var characterKeyCodes = keys.map(function(key) {
|
||||||
|
// check if it's already a keycode
|
||||||
|
if(key === parseInt(key, 10)) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup in named key map
|
||||||
|
if(specialKeys.indexOf(key) > -1) {
|
||||||
|
return special[key];
|
||||||
|
}
|
||||||
|
// it's a letter, return the char code for it
|
||||||
|
return (key).charCodeAt(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
return characterKeyCodes.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keycode lookup: http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
|
||||||
|
Egg.prototype.AddCode = function(keys, fn, metadata) {
|
||||||
|
this.eggs.push({keys: this.__toCharCodes(keys), fn: fn, metadata: metadata});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Egg.prototype.AddHook = function(fn) {
|
||||||
|
this.hooks.push(fn);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Egg.prototype.handleEvent = function(e) {
|
||||||
|
var keyCode = e.which;
|
||||||
|
var isLetter = keyCode >= 65 && keyCode <= 90;
|
||||||
|
/*
|
||||||
|
This prevents find as you type in Firefox.
|
||||||
|
Only prevent default behavior for letters A-Z.
|
||||||
|
I want keys like page up/down to still work.
|
||||||
|
*/
|
||||||
|
if ( e.type === "keydown" && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey ) {
|
||||||
|
var tag = e.target.tagName;
|
||||||
|
|
||||||
|
if ( ( tag === "HTML" || tag === "BODY" ) && isLetter ) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( e.type === "keyup" && this.eggs.length > 0 ) {
|
||||||
|
// keydown defaults all letters to uppercase
|
||||||
|
if(isLetter) {
|
||||||
|
if(!e.shiftKey) {
|
||||||
|
// convert to lower case letter
|
||||||
|
keyCode = keyCode + 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that it's not an ignored key (shift for one)
|
||||||
|
if(this.ignoredKeys.indexOf(keyCode) === -1) {
|
||||||
|
this.kps.push(keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eggs.forEach(function(currentEgg, i) {
|
||||||
|
var foundEgg = this.kps.toString().indexOf(currentEgg.keys) >= 0;
|
||||||
|
|
||||||
|
if(foundEgg) {
|
||||||
|
// Reset keys; if more keypresses occur while the callback is executing, it could retrigger the match
|
||||||
|
this.kps = [];
|
||||||
|
// Set the activeEgg to this one
|
||||||
|
this.activeEgg = currentEgg;
|
||||||
|
// if callback is a function, call it
|
||||||
|
this.__execute(currentEgg.fn, this);
|
||||||
|
// Call the hooks
|
||||||
|
this.hooks.forEach(this.__execute, this);
|
||||||
|
|
||||||
|
this.activeEgg = '';
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Egg.prototype.Listen = function() {
|
||||||
|
// Standards compliant only. Don't waste time on IE8.
|
||||||
|
if ( document.addEventListener !== void 0 ) {
|
||||||
|
document.addEventListener( "keydown", this, false );
|
||||||
|
document.addEventListener( "keyup", this, false );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Egg.prototype.listen = Egg.prototype.Listen;
|
||||||
|
Egg.prototype.addCode = Egg.prototype.AddCode;
|
||||||
|
Egg.prototype.addHook = Egg.prototype.AddHook;
|
||||||
|
|
||||||
|
// EGGSAMPLE
|
||||||
|
// var egg = new Egg();
|
||||||
|
// egg
|
||||||
|
// .AddCode("up,up,down,down,left,right,left,right,b,a", function() {
|
||||||
|
// alert("Konami!");
|
||||||
|
// }, "konami-code")
|
||||||
|
// .AddHook(function(){
|
||||||
|
// console.log("Hook called for: " + this.activeEgg.keys);
|
||||||
|
// console.log(this.activeEgg.metadata);
|
||||||
|
// }).Listen();
|
||||||
5210
facerecognition/js/vendor/handlebars.js
vendored
Normal file
5210
facerecognition/js/vendor/handlebars.js
vendored
Normal file
File diff suppressed because one or more lines are too long
202
facerecognition/js/vendor/lozad.js
vendored
Normal file
202
facerecognition/js/vendor/lozad.js
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/*! lozad.js - v1.16.0 - 2020-09-06
|
||||||
|
* https://github.com/ApoorvSaxena/lozad.js
|
||||||
|
* Copyright (c) 2020 Apoorv Saxena; Licensed MIT */
|
||||||
|
|
||||||
|
|
||||||
|
(function (global, factory) {
|
||||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||||
|
typeof define === 'function' && define.amd ? define(factory) :
|
||||||
|
(global.lozad = factory());
|
||||||
|
}(this, (function () { 'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect IE browser
|
||||||
|
* @const {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var isIE = typeof document !== 'undefined' && document.documentMode;
|
||||||
|
|
||||||
|
var defaultConfig = {
|
||||||
|
rootMargin: '0px',
|
||||||
|
threshold: 0,
|
||||||
|
load: function load(element) {
|
||||||
|
if (element.nodeName.toLowerCase() === 'picture') {
|
||||||
|
var img = element.querySelector('img');
|
||||||
|
var append = false;
|
||||||
|
|
||||||
|
if (img === null) {
|
||||||
|
img = document.createElement('img');
|
||||||
|
append = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIE && element.getAttribute('data-iesrc')) {
|
||||||
|
img.src = element.getAttribute('data-iesrc');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.getAttribute('data-alt')) {
|
||||||
|
img.alt = element.getAttribute('data-alt');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (append) {
|
||||||
|
element.append(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.nodeName.toLowerCase() === 'video' && !element.getAttribute('data-src')) {
|
||||||
|
if (element.children) {
|
||||||
|
var childs = element.children;
|
||||||
|
var childSrc = void 0;
|
||||||
|
for (var i = 0; i <= childs.length - 1; i++) {
|
||||||
|
childSrc = childs[i].getAttribute('data-src');
|
||||||
|
if (childSrc) {
|
||||||
|
childs[i].src = childSrc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.getAttribute('data-poster')) {
|
||||||
|
element.poster = element.getAttribute('data-poster');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.getAttribute('data-src')) {
|
||||||
|
element.src = element.getAttribute('data-src');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.getAttribute('data-srcset')) {
|
||||||
|
element.setAttribute('srcset', element.getAttribute('data-srcset'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var backgroundImageDelimiter = ',';
|
||||||
|
if (element.getAttribute('data-background-delimiter')) {
|
||||||
|
backgroundImageDelimiter = element.getAttribute('data-background-delimiter');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.getAttribute('data-background-image')) {
|
||||||
|
element.style.backgroundImage = 'url(\'' + element.getAttribute('data-background-image').split(backgroundImageDelimiter).join('\'),url(\'') + '\')';
|
||||||
|
} else if (element.getAttribute('data-background-image-set')) {
|
||||||
|
var imageSetLinks = element.getAttribute('data-background-image-set').split(backgroundImageDelimiter);
|
||||||
|
var firstUrlLink = imageSetLinks[0].substr(0, imageSetLinks[0].indexOf(' ')) || imageSetLinks[0]; // Substring before ... 1x
|
||||||
|
firstUrlLink = firstUrlLink.indexOf('url(') === -1 ? 'url(' + firstUrlLink + ')' : firstUrlLink;
|
||||||
|
if (imageSetLinks.length === 1) {
|
||||||
|
element.style.backgroundImage = firstUrlLink;
|
||||||
|
} else {
|
||||||
|
element.setAttribute('style', (element.getAttribute('style') || '') + ('background-image: ' + firstUrlLink + '; background-image: -webkit-image-set(' + imageSetLinks + '); background-image: image-set(' + imageSetLinks + ')'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.getAttribute('data-toggle-class')) {
|
||||||
|
element.classList.toggle(element.getAttribute('data-toggle-class'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loaded: function loaded() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function markAsLoaded(element) {
|
||||||
|
element.setAttribute('data-loaded', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function preLoad(element) {
|
||||||
|
if (element.getAttribute('data-placeholder-background')) {
|
||||||
|
element.style.background = element.getAttribute('data-placeholder-background');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isLoaded = function isLoaded(element) {
|
||||||
|
return element.getAttribute('data-loaded') === 'true';
|
||||||
|
};
|
||||||
|
|
||||||
|
var onIntersection = function onIntersection(load, loaded) {
|
||||||
|
return function (entries, observer) {
|
||||||
|
entries.forEach(function (entry) {
|
||||||
|
if (entry.intersectionRatio > 0 || entry.isIntersecting) {
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
|
||||||
|
if (!isLoaded(entry.target)) {
|
||||||
|
load(entry.target);
|
||||||
|
markAsLoaded(entry.target);
|
||||||
|
loaded(entry.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var getElements = function getElements(selector) {
|
||||||
|
var root = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : document;
|
||||||
|
|
||||||
|
if (selector instanceof Element) {
|
||||||
|
return [selector];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selector instanceof NodeList) {
|
||||||
|
return selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.querySelectorAll(selector);
|
||||||
|
};
|
||||||
|
|
||||||
|
function lozad () {
|
||||||
|
var selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '.lozad';
|
||||||
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
|
|
||||||
|
var _Object$assign = Object.assign({}, defaultConfig, options),
|
||||||
|
root = _Object$assign.root,
|
||||||
|
rootMargin = _Object$assign.rootMargin,
|
||||||
|
threshold = _Object$assign.threshold,
|
||||||
|
load = _Object$assign.load,
|
||||||
|
loaded = _Object$assign.loaded;
|
||||||
|
|
||||||
|
var observer = void 0;
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined' && window.IntersectionObserver) {
|
||||||
|
observer = new IntersectionObserver(onIntersection(load, loaded), {
|
||||||
|
root: root,
|
||||||
|
rootMargin: rootMargin,
|
||||||
|
threshold: threshold
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var elements = getElements(selector, root);
|
||||||
|
for (var i = 0; i < elements.length; i++) {
|
||||||
|
preLoad(elements[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
observe: function observe() {
|
||||||
|
var elements = getElements(selector, root);
|
||||||
|
|
||||||
|
for (var _i = 0; _i < elements.length; _i++) {
|
||||||
|
if (isLoaded(elements[_i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observer) {
|
||||||
|
observer.observe(elements[_i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
load(elements[_i]);
|
||||||
|
markAsLoaded(elements[_i]);
|
||||||
|
loaded(elements[_i]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
triggerLoad: function triggerLoad(element) {
|
||||||
|
if (isLoaded(element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
load(element);
|
||||||
|
markAsLoaded(element);
|
||||||
|
loaded(element);
|
||||||
|
},
|
||||||
|
|
||||||
|
observer: observer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return lozad;
|
||||||
|
|
||||||
|
})));
|
||||||
95
facerecognition/l10n/cs.js
Normal file
95
facerecognition/l10n/cs.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "Analýza je dokončena",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n obrázek byl zanalyzován","%n obrázky byly zanalyzovány","%n obrázků bylo zanalyzováno","%n obrázky byly zanalyzovány"],
|
||||||
|
"Analyzing images" : "Analyzují se obrázky",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["nalezen %n obrázek","nalezeny %n obrázky","nalezeno %n obrázků","nalezeny %n obrázky"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n obrázek ve frontě","%n obrázky ve frontě","%n obrázků ve frontě","%n obrázky ve frontě"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Bude dokončeno přibližně v {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Analýza zatím nebyla spuštěna",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Změny byly uloženy. Projeví se při příští analýze.",
|
||||||
|
"The change could not be applied." : "Změny se nepodařilo uplatnit.",
|
||||||
|
"Hide person" : "Skrýt osobu",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Pořád tuto osobu uvidíte ve fotkách, ale přiřazení jména bude pouze pro konkrétní fotku.",
|
||||||
|
"Cancel" : "Storno",
|
||||||
|
"Hide" : "Skrýt",
|
||||||
|
"Rename person" : "Přejmenovat osobu",
|
||||||
|
"Please enter a name to rename the person" : "Zadejte nové jméno pro přejmenování osoby",
|
||||||
|
"Rename" : "Přejmenovat",
|
||||||
|
"This person is not {name}" : "Tato osoba není {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Volitelně můžete přiřadit správné jméno",
|
||||||
|
"Please assign a name to this person." : "Přiřaďte této osobě jméno",
|
||||||
|
"Save" : "Uložit",
|
||||||
|
"Add name" : "Přidat jméno",
|
||||||
|
"Ignore" : "Ignorovat",
|
||||||
|
"Skip for now" : "Protentokrát přeskočit",
|
||||||
|
"There was an error trying to show your friends" : "Došlo k chybě při pokusu o zobrazení vašich přátel",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Analýza je zapnutá – prosíme o strpení, brzy zde uvidíte své přátele.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Analýza je vypnutá. Veškeré informace ohledně rozpoznávání obličejů budou brzy odstraněny.",
|
||||||
|
"There was an error renaming this person" : "Při přejmenovávání této osoby došlo k chybě",
|
||||||
|
"There was an error ignoring this person" : "Došlo k chybě při ignorování této osoby",
|
||||||
|
"Face Recognition" : "Rozpoznávání obličejů",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Zde můžete vidět fotky vašich přátel, kteří byli rozpoznáni",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analyzovat mé obrázky a seskupit mé milované s podobnými tvářemi",
|
||||||
|
"Looking for your recognized friends" : "Vyhledávání vašich rozpoznaných přátel",
|
||||||
|
"Review face groups" : "Prohlédněte si skupiny obličejů",
|
||||||
|
"The analysis is disabled" : "Analýza je vypnutá",
|
||||||
|
"Enable it to find your loved ones" : "Pokud chcete najít své milované, zapněte ji",
|
||||||
|
"Hide it" : "Skrýt",
|
||||||
|
"Review people found" : "Prohlédněte si nalezené osoby",
|
||||||
|
"Your friends have not been recognized yet" : "Vaši přátelé zatím nebyli rozpoznáni",
|
||||||
|
"Please, be patient" : "Vyčkejte prosím",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Ztratíte všechny zanalyzované informace a pokud znovu zapnete, začne se od začátku.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Chcete vypnout seskupování podle obličejů?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Došlo k chybě při pokusu o nalezení fotek vašich přátel",
|
||||||
|
"An error occurred while hiding this person" : "Došlo k chybě při skrývání této osoby",
|
||||||
|
"There was an error renaming this cluster of faces" : "Došlo k chybě při přejmenovávání tohoto klastru obličejů",
|
||||||
|
"An error occurred while hiding this group of faces" : "Došlo k chybě při skrývání této skupiny obličejů",
|
||||||
|
"_%n image_::_%n images_" : ["%n obrázek","%n obrázky","%n obrázků","%n obrázky"],
|
||||||
|
"You must be administrator to configure this feature" : "Abyste mohli nastavovat tuto funkci, je třeba, abyste byli správci",
|
||||||
|
"The format seems to be incorrect." : "Formát se nezdá být správný.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Zdá se, že zatím nemáte nastavený žádný analytický model",
|
||||||
|
"The minimum recommended area is %s" : "Nejmenší doporučená oblast je %s",
|
||||||
|
"The maximum recommended area is %s" : "Největší doporučená oblast je %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Model není doporučen pro oblast větší než %s",
|
||||||
|
"It seems you don't have any model installed." : "Zdá se, že nemáte nainstalovaný žádný model.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Zdá se, že je pořád ještě zapotřebí nastavit přiřazení paměti pro zpracovávání obrazu.",
|
||||||
|
"Not installed" : "Nenainstalováno",
|
||||||
|
"Not configured." : "Nenastaveno.",
|
||||||
|
"A face recognition app" : "Aplikace pro rozpoznávání obličejů",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Zjišťujte a seskupujte obličeje svých milovaných ve svém cloudu**\n\n⚠️ Tato aplikace pro své fungování vyžaduje přinejmenším 1GB operační paměti navíc! Podrobnosti viz [požadavky](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations).\n\n⚠️ Nasazení této aplikace vyžaduje přístup k terminálu a dokonce ruční instalaci dalšího software. Podrobnosti viz [instalace](https://github.com/matiasdelellis/facerecognition/wiki/Installation).\n\n- **😏 Zjišťování obličejů z obrázků:** Aplikaci FaceRecognition použijte ke zjištění _libovolného_ obličeje v _jakékoli_ z vašich obrázků!\n- **👪 Seskupování obličejů k osobám:** Zjištěné obličeje jsou seskupené na základě podobnosti a aplikace FaceRecognition pak může rozpoznávat osoby!\n- **🔒 Vestavěná ochrana soukromí:** Váš cloud neopouštějí žádná data. Ve výchozím nastavení je vždy vypnuté a každý z uživatelů si určuje, zda chce či nechce zapnout rozpoznávání obličejů. Obrázky z každé složky lze v případě potřeby vynechat z rozpoznávání obličejů.\n- **⚙️ Síla umělé inteligence:** Aplikace FaceRecognition zapřahá sílu AI a už vytvořených modelů neuronových sítí prostřednictvím rozsáhlého využívání softwarové knihovny [DLib](http://dlib.net/).\n- **🚀 Postavte si svou vlastní věc:** Aplikace FaceRecognition je jen základním stavebním kamenem. Prostřednictvím aplikačního program. rozhraní aplikace FaceRecognition, je možné si vytvářet své vlastní pokročilé scénáře – automatické přidávání štítků k obrázkům, propojení kontaktů a osob, sdílení obrázků od konkrétní osoby… Stojíme o vaše nápady!",
|
||||||
|
"See other photos" : "Zobrazit ostatní fotky",
|
||||||
|
"Facial recognition is disabled" : "Rozpoznávání obličejů je vypnuté",
|
||||||
|
"Facial recognition is disabled for this folder" : "Pro tuto složku je rozpoznávání obličejů vypnuté",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Tento typ úložiště není pro analýzu fotek podporován",
|
||||||
|
"No people found" : "Nenalezeni žádní lidé",
|
||||||
|
"This image is not yet analyzed" : "Tento obrázek ještě není zanalyzován",
|
||||||
|
"Search for persons in the photos of this directory" : "Hledat osoby na fotkách v této složce",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Ignorovány jsou také fotky, které se nenacházejí v galerii",
|
||||||
|
"People" : "Lidé",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Viz <a target=\"_blank\" href=\"{docsLink}\">dokumentace ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Pokud chcete zapnout, otevřete <a target=\"_blank\" href=\"{settingsLink}\">nastavení ↗</a>",
|
||||||
|
"Open Documentation" : "Otevřít dokumentaci",
|
||||||
|
"Temporary files" : "Dočasné soubory",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "V průběhu analýzy, dočasné soubory slouží pro zajištění homogenity mezi všemi obrázky.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Malé obrázky umožňují rychlou analýzu, ale mohou být přehlédnuty ty nejmenší obličeje ve vašich fotkách. Velké obrázky mohou zlepšit výsledky, ale analýza bude pomalejší.",
|
||||||
|
"Smaller images" : "Menší obrázky",
|
||||||
|
"Larger images" : "Větší obrázky",
|
||||||
|
"Restore" : "Obnovit",
|
||||||
|
"Clustering threshold" : "Práh pro shlukování",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Osoby jsou určovány jako skupiny podobných obličejů a aby je bylo možné získat, je třeba porovnat všechny obličeje. Když jsou porovnávány, práh slouží k určení, zda mají být seskupeny.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Nízký práh seskupí pouze velmi podobné tváře, nicméně zpočátku bude třeba pojmenovat mnoho skupin. Vyšší práh je přizpůsobivější ohledně seskupování obličejů a tím je vytvořeno méně skupin, ale může docházet k záměně podobných osob.",
|
||||||
|
"Small threshold" : "Nižší práh",
|
||||||
|
"Higher threshold" : "Vyšší práh",
|
||||||
|
"Minimum confidence" : "Minimální jistota",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Minimální jistota určuje jak spolehlivé musí být zjištění (nikoli ještě rozpoznání) obličeje, aby bylo vyzkoušeno jeho rozpoznání a tím zařazení do skupiny. Rozmazané obličeje nebo ty nafocené z nevhodného úhlu by měly mít jistotu skoro 0,0 a nejlepší obrázky skoro 1,0.",
|
||||||
|
"Lower minimum confidence" : "Nižší minimální jistota",
|
||||||
|
"Higher minimum confidence" : "Vyšší minimální jistota",
|
||||||
|
"Configuration information" : "Informace o nastaveních",
|
||||||
|
"Current model:" : "Stávající model:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Nejvýše paměti, přiřazené pro zpracovávání obrazu:",
|
||||||
|
"Current status" : "Stávající stav",
|
||||||
|
"Stopped" : "Zastaveno"
|
||||||
|
},
|
||||||
|
"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;");
|
||||||
93
facerecognition/l10n/cs.json
Normal file
93
facerecognition/l10n/cs.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "Analýza je dokončena",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n obrázek byl zanalyzován","%n obrázky byly zanalyzovány","%n obrázků bylo zanalyzováno","%n obrázky byly zanalyzovány"],
|
||||||
|
"Analyzing images" : "Analyzují se obrázky",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["nalezen %n obrázek","nalezeny %n obrázky","nalezeno %n obrázků","nalezeny %n obrázky"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n obrázek ve frontě","%n obrázky ve frontě","%n obrázků ve frontě","%n obrázky ve frontě"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Bude dokončeno přibližně v {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Analýza zatím nebyla spuštěna",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Změny byly uloženy. Projeví se při příští analýze.",
|
||||||
|
"The change could not be applied." : "Změny se nepodařilo uplatnit.",
|
||||||
|
"Hide person" : "Skrýt osobu",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Pořád tuto osobu uvidíte ve fotkách, ale přiřazení jména bude pouze pro konkrétní fotku.",
|
||||||
|
"Cancel" : "Storno",
|
||||||
|
"Hide" : "Skrýt",
|
||||||
|
"Rename person" : "Přejmenovat osobu",
|
||||||
|
"Please enter a name to rename the person" : "Zadejte nové jméno pro přejmenování osoby",
|
||||||
|
"Rename" : "Přejmenovat",
|
||||||
|
"This person is not {name}" : "Tato osoba není {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Volitelně můžete přiřadit správné jméno",
|
||||||
|
"Please assign a name to this person." : "Přiřaďte této osobě jméno",
|
||||||
|
"Save" : "Uložit",
|
||||||
|
"Add name" : "Přidat jméno",
|
||||||
|
"Ignore" : "Ignorovat",
|
||||||
|
"Skip for now" : "Protentokrát přeskočit",
|
||||||
|
"There was an error trying to show your friends" : "Došlo k chybě při pokusu o zobrazení vašich přátel",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Analýza je zapnutá – prosíme o strpení, brzy zde uvidíte své přátele.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Analýza je vypnutá. Veškeré informace ohledně rozpoznávání obličejů budou brzy odstraněny.",
|
||||||
|
"There was an error renaming this person" : "Při přejmenovávání této osoby došlo k chybě",
|
||||||
|
"There was an error ignoring this person" : "Došlo k chybě při ignorování této osoby",
|
||||||
|
"Face Recognition" : "Rozpoznávání obličejů",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Zde můžete vidět fotky vašich přátel, kteří byli rozpoznáni",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analyzovat mé obrázky a seskupit mé milované s podobnými tvářemi",
|
||||||
|
"Looking for your recognized friends" : "Vyhledávání vašich rozpoznaných přátel",
|
||||||
|
"Review face groups" : "Prohlédněte si skupiny obličejů",
|
||||||
|
"The analysis is disabled" : "Analýza je vypnutá",
|
||||||
|
"Enable it to find your loved ones" : "Pokud chcete najít své milované, zapněte ji",
|
||||||
|
"Hide it" : "Skrýt",
|
||||||
|
"Review people found" : "Prohlédněte si nalezené osoby",
|
||||||
|
"Your friends have not been recognized yet" : "Vaši přátelé zatím nebyli rozpoznáni",
|
||||||
|
"Please, be patient" : "Vyčkejte prosím",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Ztratíte všechny zanalyzované informace a pokud znovu zapnete, začne se od začátku.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Chcete vypnout seskupování podle obličejů?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Došlo k chybě při pokusu o nalezení fotek vašich přátel",
|
||||||
|
"An error occurred while hiding this person" : "Došlo k chybě při skrývání této osoby",
|
||||||
|
"There was an error renaming this cluster of faces" : "Došlo k chybě při přejmenovávání tohoto klastru obličejů",
|
||||||
|
"An error occurred while hiding this group of faces" : "Došlo k chybě při skrývání této skupiny obličejů",
|
||||||
|
"_%n image_::_%n images_" : ["%n obrázek","%n obrázky","%n obrázků","%n obrázky"],
|
||||||
|
"You must be administrator to configure this feature" : "Abyste mohli nastavovat tuto funkci, je třeba, abyste byli správci",
|
||||||
|
"The format seems to be incorrect." : "Formát se nezdá být správný.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Zdá se, že zatím nemáte nastavený žádný analytický model",
|
||||||
|
"The minimum recommended area is %s" : "Nejmenší doporučená oblast je %s",
|
||||||
|
"The maximum recommended area is %s" : "Největší doporučená oblast je %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Model není doporučen pro oblast větší než %s",
|
||||||
|
"It seems you don't have any model installed." : "Zdá se, že nemáte nainstalovaný žádný model.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Zdá se, že je pořád ještě zapotřebí nastavit přiřazení paměti pro zpracovávání obrazu.",
|
||||||
|
"Not installed" : "Nenainstalováno",
|
||||||
|
"Not configured." : "Nenastaveno.",
|
||||||
|
"A face recognition app" : "Aplikace pro rozpoznávání obličejů",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Zjišťujte a seskupujte obličeje svých milovaných ve svém cloudu**\n\n⚠️ Tato aplikace pro své fungování vyžaduje přinejmenším 1GB operační paměti navíc! Podrobnosti viz [požadavky](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations).\n\n⚠️ Nasazení této aplikace vyžaduje přístup k terminálu a dokonce ruční instalaci dalšího software. Podrobnosti viz [instalace](https://github.com/matiasdelellis/facerecognition/wiki/Installation).\n\n- **😏 Zjišťování obličejů z obrázků:** Aplikaci FaceRecognition použijte ke zjištění _libovolného_ obličeje v _jakékoli_ z vašich obrázků!\n- **👪 Seskupování obličejů k osobám:** Zjištěné obličeje jsou seskupené na základě podobnosti a aplikace FaceRecognition pak může rozpoznávat osoby!\n- **🔒 Vestavěná ochrana soukromí:** Váš cloud neopouštějí žádná data. Ve výchozím nastavení je vždy vypnuté a každý z uživatelů si určuje, zda chce či nechce zapnout rozpoznávání obličejů. Obrázky z každé složky lze v případě potřeby vynechat z rozpoznávání obličejů.\n- **⚙️ Síla umělé inteligence:** Aplikace FaceRecognition zapřahá sílu AI a už vytvořených modelů neuronových sítí prostřednictvím rozsáhlého využívání softwarové knihovny [DLib](http://dlib.net/).\n- **🚀 Postavte si svou vlastní věc:** Aplikace FaceRecognition je jen základním stavebním kamenem. Prostřednictvím aplikačního program. rozhraní aplikace FaceRecognition, je možné si vytvářet své vlastní pokročilé scénáře – automatické přidávání štítků k obrázkům, propojení kontaktů a osob, sdílení obrázků od konkrétní osoby… Stojíme o vaše nápady!",
|
||||||
|
"See other photos" : "Zobrazit ostatní fotky",
|
||||||
|
"Facial recognition is disabled" : "Rozpoznávání obličejů je vypnuté",
|
||||||
|
"Facial recognition is disabled for this folder" : "Pro tuto složku je rozpoznávání obličejů vypnuté",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Tento typ úložiště není pro analýzu fotek podporován",
|
||||||
|
"No people found" : "Nenalezeni žádní lidé",
|
||||||
|
"This image is not yet analyzed" : "Tento obrázek ještě není zanalyzován",
|
||||||
|
"Search for persons in the photos of this directory" : "Hledat osoby na fotkách v této složce",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Ignorovány jsou také fotky, které se nenacházejí v galerii",
|
||||||
|
"People" : "Lidé",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Viz <a target=\"_blank\" href=\"{docsLink}\">dokumentace ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Pokud chcete zapnout, otevřete <a target=\"_blank\" href=\"{settingsLink}\">nastavení ↗</a>",
|
||||||
|
"Open Documentation" : "Otevřít dokumentaci",
|
||||||
|
"Temporary files" : "Dočasné soubory",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "V průběhu analýzy, dočasné soubory slouží pro zajištění homogenity mezi všemi obrázky.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Malé obrázky umožňují rychlou analýzu, ale mohou být přehlédnuty ty nejmenší obličeje ve vašich fotkách. Velké obrázky mohou zlepšit výsledky, ale analýza bude pomalejší.",
|
||||||
|
"Smaller images" : "Menší obrázky",
|
||||||
|
"Larger images" : "Větší obrázky",
|
||||||
|
"Restore" : "Obnovit",
|
||||||
|
"Clustering threshold" : "Práh pro shlukování",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Osoby jsou určovány jako skupiny podobných obličejů a aby je bylo možné získat, je třeba porovnat všechny obličeje. Když jsou porovnávány, práh slouží k určení, zda mají být seskupeny.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Nízký práh seskupí pouze velmi podobné tváře, nicméně zpočátku bude třeba pojmenovat mnoho skupin. Vyšší práh je přizpůsobivější ohledně seskupování obličejů a tím je vytvořeno méně skupin, ale může docházet k záměně podobných osob.",
|
||||||
|
"Small threshold" : "Nižší práh",
|
||||||
|
"Higher threshold" : "Vyšší práh",
|
||||||
|
"Minimum confidence" : "Minimální jistota",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Minimální jistota určuje jak spolehlivé musí být zjištění (nikoli ještě rozpoznání) obličeje, aby bylo vyzkoušeno jeho rozpoznání a tím zařazení do skupiny. Rozmazané obličeje nebo ty nafocené z nevhodného úhlu by měly mít jistotu skoro 0,0 a nejlepší obrázky skoro 1,0.",
|
||||||
|
"Lower minimum confidence" : "Nižší minimální jistota",
|
||||||
|
"Higher minimum confidence" : "Vyšší minimální jistota",
|
||||||
|
"Configuration information" : "Informace o nastaveních",
|
||||||
|
"Current model:" : "Stávající model:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Nejvýše paměti, přiřazené pro zpracovávání obrazu:",
|
||||||
|
"Current status" : "Stávající stav",
|
||||||
|
"Stopped" : "Zastaveno"
|
||||||
|
},"pluralForm" :"nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;"
|
||||||
|
}
|
||||||
89
facerecognition/l10n/de.js
Normal file
89
facerecognition/l10n/de.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "Die Analyse ist beendet",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n Bilder wurden analysiert","%n Bilder wurden analysiert"],
|
||||||
|
"Analyzing images" : "Bilder werden analysiert",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n Bilder wurden gefunden","%n Bilder wurden gefunden"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n Bilder in der Warteschlange","%n Bilder in der Warteschlange"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Endet ungefähr {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Die Analyse wurde noch nicht gestartet",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Die Änderungen wurden gespeichert und werden bei der nächsten Analyse angewendet.",
|
||||||
|
"The change could not be applied." : "Die Änderung konnte nicht angewendet werden.",
|
||||||
|
"Hide person" : "Person ausblenden",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Du kannst diese Person immer noch auf den Fotos sehen, aber die Zuweisung eines Namens gilt nur für dieses Foto.",
|
||||||
|
"Cancel" : "Abbrechen",
|
||||||
|
"Hide" : "Ausblenden",
|
||||||
|
"Rename person" : "Person umbenennen",
|
||||||
|
"Please enter a name to rename the person" : "Bitte gib einen neuen Namen für die Person ein",
|
||||||
|
"Rename" : "Umbenennen",
|
||||||
|
"This person is not {name}" : "Diese Person ist nicht {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Optional kann der richtige Name vergeben werden",
|
||||||
|
"Please assign a name to this person." : "Bitte benenne die Person.",
|
||||||
|
"Save" : "Speichern",
|
||||||
|
"Ignore" : "Ignorieren",
|
||||||
|
"There was an error trying to show your friends" : "Es gab einen Fehler beim Versuch Deine Freunde anzuzeigen",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Die Analyse ist aktiviert. Habe etwas Geduld, bald wirst Du hier Deine Freunde sehen.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Die Analyse ist deaktiviert. Bald werden alle Informationen zur Gesichtserkennung entfernt.",
|
||||||
|
"There was an error renaming this person" : "Es gab einen Fehler beim Umbenennen dieser Person",
|
||||||
|
"There was an error ignoring this person" : "Fehler beim Ignorieren dieser Person",
|
||||||
|
"Face Recognition" : "Gesichtserkennung",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Hier kannst Du Fotos von Deinen Freunden sehen, welche gefunden wurden",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analysiere meine Bilder und gruppiere meine Lieben mit ähnlichen Gesichtern",
|
||||||
|
"Looking for your recognized friends" : "Auf der Suche nach Deinen erkannten Freunden",
|
||||||
|
"The analysis is disabled" : "Die Analyse ist deaktiviert",
|
||||||
|
"Enable it to find your loved ones" : "Aktiviere es um Deine Lieben zu finden",
|
||||||
|
"Hide it" : "Ausblenden",
|
||||||
|
"Your friends have not been recognized yet" : "Deine Freunde wurden noch nicht analysiert",
|
||||||
|
"Please, be patient" : "Bitte habe Geduld",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Du wirst alle Informationen der Analyse verlieren und nach dem Reaktivieren geht es wieder von vorne los.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Magst Du das Gruppieren nach Gesichtern deaktivieren?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Es gab einen Fehler beim Versuch Bilder von Deinem Freund zu finden",
|
||||||
|
"An error occurred while hiding this person" : "Beim Ausblenden dieser Person ist ein Fehler aufgetreten",
|
||||||
|
"There was an error renaming this cluster of faces" : "Es ist ein Fehler beim Umbenennen dieser Gruppe von Gesichtern aufgetreten.",
|
||||||
|
"An error occurred while hiding this group of faces" : "Beim Ausblenden dieser Gruppe von Gesichtern ist ein Fehler aufgetreten",
|
||||||
|
"_%n image_::_%n images_" : ["%n Bilder","%n Bilder"],
|
||||||
|
"You must be administrator to configure this feature" : "Du musst Administrator sein, um diese Funktion einrichten zu können",
|
||||||
|
"The format seems to be incorrect." : "Das Format scheint ungültig zu sein.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Anscheinend hast Du noch kein Analysemodell eingerichtet",
|
||||||
|
"The minimum recommended area is %s" : "Der empfohlene Mindestbereich ist %s",
|
||||||
|
"The maximum recommended area is %s" : "Der empfohlene Maximalbereich ist %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Das Modell empfiehlt keinen größeren Bereich als %s",
|
||||||
|
"It seems you don't have any model installed." : "Anscheinend hast Du kein Modell installiert",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Es scheint so, dass Du den zugewiesenen Speicher für die Bildverarbeitung noch konfigurieren musst.",
|
||||||
|
"Not installed" : "Nicht installiert",
|
||||||
|
"Not configured." : "Nicht konfiguriert.",
|
||||||
|
"A face recognition app" : "Eine App zur Gesichtserkennung",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Entdecke und gruppiere die Gesichter Deiner Liebsten in deiner Cloud*\n\n⚠️ Diese Anwendung benötigt mindestens 1GB Arbeitsspeicher, um zu funktionieren! Siehe [Anforderungen] (https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) für Details.\n\n⚠️ Die Einrichtung dieser Anwendung erfordert Zugriff auf das Terminal und die Installation zusätzlicher Software. Siehe [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) für Details.\n\n- **😏 Gesichter auf Bildern erkennen:** Benütze die FaceRecognition-App um _jedes_ Gesicht in _jedem_ Deinen Bildern zu erkennen!\n- **👪 Gesichter zu Personen gruppieren:** Die erkannten Gesichter werden aufgrund der Ähnlichkeit gruppiert und dann kann die FaceRecognition-App Personen erkennen!\n- **🔒 Eingebaute Privatsphäre:** Keine Daten verlassen Ihre Cloud. Die Standardeinstellungen sind immer ausgeschaltet und jeder Benutzer steuert die Aktivierung / Deaktivierung der Gesichtserkennung. Bilder aus jedem Verzeichnis können bei Bedarf von der Gesichtserkennung ausgeschlossen werden.\n- **⚙️ Leistung der KI:** Die Gesichtserkennungs-App nutzt die Leistung der KI und erstellt neuronale Netzwerkmodelle durch umfangreiche Nutzung der [DLib](http://dlib.net/)-Bibliothek.\n- **🚀 Bauen Sie Ihr eigenes Ding:** Die FaceRecognition-App ist nur ein grundlegender Baustein. Durch die FaceRecognition-API kannst Du Deine fortgeschrittenen Szenarien erstellen - automatisch Schlagworte zu Bildern hinzufügen, Kontakte und Personen verbinden, Bilder von bestimmten Personen teilen... Wir wollen Deine Ideen hören!",
|
||||||
|
"Facial recognition is disabled" : "Gesichtserkennung ist deaktiviert",
|
||||||
|
"Facial recognition is disabled for this folder" : "Gesichtserkennung ist für diesen Ordner deaktiviert",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Der Speichertyp wird für die Analyse Deiner Bilder nicht unterstützt",
|
||||||
|
"No people found" : "Keine Personen gefunden",
|
||||||
|
"This image is not yet analyzed" : "Dieses Bild ist noch nicht analysiert",
|
||||||
|
"Search for persons in the photos of this directory" : "Suche nach Personen auf den Bildern in diesem Ordner",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Bilder, welche nicht in der Gallerie sind, werden ebenso ignoriert",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Siehe <a target=\"_blank\" href=\"{docsLink}\">Dokumentation ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Zur Aktivierung die <a target=\"_blank\" href=\"{settingsLink}\">Einstellungen↗</a>öffnen",
|
||||||
|
"Open Documentation" : "Dokumentation öffnen",
|
||||||
|
"Temporary files" : "Temporäre Dateien",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Während der Analyse werden temporäre Dateien verwendet, um die Homogenität zwischen allen Bildern sicherzustellen.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Kleine Bilder ermöglichen eine schnelle Analyse, können aber dazu führen, dass kleinsten Gesichter in Deinen Fotos nicht erkannt werden. Große Bilder können die Erkennung verbessern, die Analyse ist jedoch langsamer.",
|
||||||
|
"Smaller images" : "Kleinere Bilder",
|
||||||
|
"Larger images" : "Größere Bilder",
|
||||||
|
"Restore" : "Wiederherstellen",
|
||||||
|
"Clustering threshold" : "Schwellenwert für das Clustering",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Personen werden als Gruppen von ähnlichen Gesichtern bestimmt. Um diese zu ermitteln, müssen alle gefundenen Gesichter verglichen werden. Für den Vergleich, ob Gesichter gruppiert werden, wird ein Schwellenwert verwendet.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Durch einen kleinen Schwellenwert werden nur sehr ähnliche Gesichter gruppiert. Dadurch können anfänglich sehr viele Gruppen zu benennen sein. Ein größerer Schwellenwert ist flexibler, um die Gesichter zu gruppieren und führt zu weniger Gruppen. Ähnliche Personen können hierdurch verwechselt werden.",
|
||||||
|
"Small threshold" : "Kleiner Schwellenwert",
|
||||||
|
"Higher threshold" : "Hoher Schwellenwert",
|
||||||
|
"Minimum confidence" : "Mindestvertrauen",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Das Mindestvertrauen bestimmt wie zuverlässig die Gesichtserkennung sein muss, um die Person zu gruppieren. Unscharfe oder falsch ausgerichtete Gesichter hätten ein Vertrauen näher an 0,0 und die besten Bilder nahe 1,0.",
|
||||||
|
"Lower minimum confidence" : "Niedrigeres Mindestvertrauen",
|
||||||
|
"Higher minimum confidence" : "Höchstes Mindestvertrauen",
|
||||||
|
"Configuration information" : "Einrichtungsinformationen",
|
||||||
|
"Current model:" : "Aktuelles Modell:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Maximal zugewiesener Speicher für die Bildverarbeitung:",
|
||||||
|
"Current status" : "Aktueller Status",
|
||||||
|
"Stopped" : "Gestoppt"
|
||||||
|
},
|
||||||
|
"nplurals=2; plural=(n != 1);");
|
||||||
87
facerecognition/l10n/de.json
Normal file
87
facerecognition/l10n/de.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "Die Analyse ist beendet",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n Bilder wurden analysiert","%n Bilder wurden analysiert"],
|
||||||
|
"Analyzing images" : "Bilder werden analysiert",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n Bilder wurden gefunden","%n Bilder wurden gefunden"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n Bilder in der Warteschlange","%n Bilder in der Warteschlange"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Endet ungefähr {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Die Analyse wurde noch nicht gestartet",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Die Änderungen wurden gespeichert und werden bei der nächsten Analyse angewendet.",
|
||||||
|
"The change could not be applied." : "Die Änderung konnte nicht angewendet werden.",
|
||||||
|
"Hide person" : "Person ausblenden",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Du kannst diese Person immer noch auf den Fotos sehen, aber die Zuweisung eines Namens gilt nur für dieses Foto.",
|
||||||
|
"Cancel" : "Abbrechen",
|
||||||
|
"Hide" : "Ausblenden",
|
||||||
|
"Rename person" : "Person umbenennen",
|
||||||
|
"Please enter a name to rename the person" : "Bitte gib einen neuen Namen für die Person ein",
|
||||||
|
"Rename" : "Umbenennen",
|
||||||
|
"This person is not {name}" : "Diese Person ist nicht {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Optional kann der richtige Name vergeben werden",
|
||||||
|
"Please assign a name to this person." : "Bitte benenne die Person.",
|
||||||
|
"Save" : "Speichern",
|
||||||
|
"Ignore" : "Ignorieren",
|
||||||
|
"There was an error trying to show your friends" : "Es gab einen Fehler beim Versuch Deine Freunde anzuzeigen",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Die Analyse ist aktiviert. Habe etwas Geduld, bald wirst Du hier Deine Freunde sehen.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Die Analyse ist deaktiviert. Bald werden alle Informationen zur Gesichtserkennung entfernt.",
|
||||||
|
"There was an error renaming this person" : "Es gab einen Fehler beim Umbenennen dieser Person",
|
||||||
|
"There was an error ignoring this person" : "Fehler beim Ignorieren dieser Person",
|
||||||
|
"Face Recognition" : "Gesichtserkennung",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Hier kannst Du Fotos von Deinen Freunden sehen, welche gefunden wurden",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analysiere meine Bilder und gruppiere meine Lieben mit ähnlichen Gesichtern",
|
||||||
|
"Looking for your recognized friends" : "Auf der Suche nach Deinen erkannten Freunden",
|
||||||
|
"The analysis is disabled" : "Die Analyse ist deaktiviert",
|
||||||
|
"Enable it to find your loved ones" : "Aktiviere es um Deine Lieben zu finden",
|
||||||
|
"Hide it" : "Ausblenden",
|
||||||
|
"Your friends have not been recognized yet" : "Deine Freunde wurden noch nicht analysiert",
|
||||||
|
"Please, be patient" : "Bitte habe Geduld",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Du wirst alle Informationen der Analyse verlieren und nach dem Reaktivieren geht es wieder von vorne los.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Magst Du das Gruppieren nach Gesichtern deaktivieren?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Es gab einen Fehler beim Versuch Bilder von Deinem Freund zu finden",
|
||||||
|
"An error occurred while hiding this person" : "Beim Ausblenden dieser Person ist ein Fehler aufgetreten",
|
||||||
|
"There was an error renaming this cluster of faces" : "Es ist ein Fehler beim Umbenennen dieser Gruppe von Gesichtern aufgetreten.",
|
||||||
|
"An error occurred while hiding this group of faces" : "Beim Ausblenden dieser Gruppe von Gesichtern ist ein Fehler aufgetreten",
|
||||||
|
"_%n image_::_%n images_" : ["%n Bilder","%n Bilder"],
|
||||||
|
"You must be administrator to configure this feature" : "Du musst Administrator sein, um diese Funktion einrichten zu können",
|
||||||
|
"The format seems to be incorrect." : "Das Format scheint ungültig zu sein.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Anscheinend hast Du noch kein Analysemodell eingerichtet",
|
||||||
|
"The minimum recommended area is %s" : "Der empfohlene Mindestbereich ist %s",
|
||||||
|
"The maximum recommended area is %s" : "Der empfohlene Maximalbereich ist %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Das Modell empfiehlt keinen größeren Bereich als %s",
|
||||||
|
"It seems you don't have any model installed." : "Anscheinend hast Du kein Modell installiert",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Es scheint so, dass Du den zugewiesenen Speicher für die Bildverarbeitung noch konfigurieren musst.",
|
||||||
|
"Not installed" : "Nicht installiert",
|
||||||
|
"Not configured." : "Nicht konfiguriert.",
|
||||||
|
"A face recognition app" : "Eine App zur Gesichtserkennung",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Entdecke und gruppiere die Gesichter Deiner Liebsten in deiner Cloud*\n\n⚠️ Diese Anwendung benötigt mindestens 1GB Arbeitsspeicher, um zu funktionieren! Siehe [Anforderungen] (https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) für Details.\n\n⚠️ Die Einrichtung dieser Anwendung erfordert Zugriff auf das Terminal und die Installation zusätzlicher Software. Siehe [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) für Details.\n\n- **😏 Gesichter auf Bildern erkennen:** Benütze die FaceRecognition-App um _jedes_ Gesicht in _jedem_ Deinen Bildern zu erkennen!\n- **👪 Gesichter zu Personen gruppieren:** Die erkannten Gesichter werden aufgrund der Ähnlichkeit gruppiert und dann kann die FaceRecognition-App Personen erkennen!\n- **🔒 Eingebaute Privatsphäre:** Keine Daten verlassen Ihre Cloud. Die Standardeinstellungen sind immer ausgeschaltet und jeder Benutzer steuert die Aktivierung / Deaktivierung der Gesichtserkennung. Bilder aus jedem Verzeichnis können bei Bedarf von der Gesichtserkennung ausgeschlossen werden.\n- **⚙️ Leistung der KI:** Die Gesichtserkennungs-App nutzt die Leistung der KI und erstellt neuronale Netzwerkmodelle durch umfangreiche Nutzung der [DLib](http://dlib.net/)-Bibliothek.\n- **🚀 Bauen Sie Ihr eigenes Ding:** Die FaceRecognition-App ist nur ein grundlegender Baustein. Durch die FaceRecognition-API kannst Du Deine fortgeschrittenen Szenarien erstellen - automatisch Schlagworte zu Bildern hinzufügen, Kontakte und Personen verbinden, Bilder von bestimmten Personen teilen... Wir wollen Deine Ideen hören!",
|
||||||
|
"Facial recognition is disabled" : "Gesichtserkennung ist deaktiviert",
|
||||||
|
"Facial recognition is disabled for this folder" : "Gesichtserkennung ist für diesen Ordner deaktiviert",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Der Speichertyp wird für die Analyse Deiner Bilder nicht unterstützt",
|
||||||
|
"No people found" : "Keine Personen gefunden",
|
||||||
|
"This image is not yet analyzed" : "Dieses Bild ist noch nicht analysiert",
|
||||||
|
"Search for persons in the photos of this directory" : "Suche nach Personen auf den Bildern in diesem Ordner",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Bilder, welche nicht in der Gallerie sind, werden ebenso ignoriert",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Siehe <a target=\"_blank\" href=\"{docsLink}\">Dokumentation ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Zur Aktivierung die <a target=\"_blank\" href=\"{settingsLink}\">Einstellungen↗</a>öffnen",
|
||||||
|
"Open Documentation" : "Dokumentation öffnen",
|
||||||
|
"Temporary files" : "Temporäre Dateien",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Während der Analyse werden temporäre Dateien verwendet, um die Homogenität zwischen allen Bildern sicherzustellen.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Kleine Bilder ermöglichen eine schnelle Analyse, können aber dazu führen, dass kleinsten Gesichter in Deinen Fotos nicht erkannt werden. Große Bilder können die Erkennung verbessern, die Analyse ist jedoch langsamer.",
|
||||||
|
"Smaller images" : "Kleinere Bilder",
|
||||||
|
"Larger images" : "Größere Bilder",
|
||||||
|
"Restore" : "Wiederherstellen",
|
||||||
|
"Clustering threshold" : "Schwellenwert für das Clustering",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Personen werden als Gruppen von ähnlichen Gesichtern bestimmt. Um diese zu ermitteln, müssen alle gefundenen Gesichter verglichen werden. Für den Vergleich, ob Gesichter gruppiert werden, wird ein Schwellenwert verwendet.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Durch einen kleinen Schwellenwert werden nur sehr ähnliche Gesichter gruppiert. Dadurch können anfänglich sehr viele Gruppen zu benennen sein. Ein größerer Schwellenwert ist flexibler, um die Gesichter zu gruppieren und führt zu weniger Gruppen. Ähnliche Personen können hierdurch verwechselt werden.",
|
||||||
|
"Small threshold" : "Kleiner Schwellenwert",
|
||||||
|
"Higher threshold" : "Hoher Schwellenwert",
|
||||||
|
"Minimum confidence" : "Mindestvertrauen",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Das Mindestvertrauen bestimmt wie zuverlässig die Gesichtserkennung sein muss, um die Person zu gruppieren. Unscharfe oder falsch ausgerichtete Gesichter hätten ein Vertrauen näher an 0,0 und die besten Bilder nahe 1,0.",
|
||||||
|
"Lower minimum confidence" : "Niedrigeres Mindestvertrauen",
|
||||||
|
"Higher minimum confidence" : "Höchstes Mindestvertrauen",
|
||||||
|
"Configuration information" : "Einrichtungsinformationen",
|
||||||
|
"Current model:" : "Aktuelles Modell:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Maximal zugewiesener Speicher für die Bildverarbeitung:",
|
||||||
|
"Current status" : "Aktueller Status",
|
||||||
|
"Stopped" : "Gestoppt"
|
||||||
|
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||||
|
}
|
||||||
102
facerecognition/l10n/de_DE.js
Normal file
102
facerecognition/l10n/de_DE.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "Die Analyse ist beendet",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n Bild wurde analysiert","%n Bilder wurden analysiert"],
|
||||||
|
"Analyzing images" : "Bilder werden analysiert",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n Bilder wurden gefunden","%n Bilder wurden gefunden"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n Bild in der Warteschlange","%n Bilder in der Warteschlange"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Endet ungefähr {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Die Analyse wurde noch nicht gestartet",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Die Änderungen wurden gespeichert und werden bei der nächsten Analyse angewendet.",
|
||||||
|
"The change could not be applied." : "Die Änderung konnte nicht angewendet werden.",
|
||||||
|
"Done" : "Erledigt",
|
||||||
|
"Hide person" : "Person ausblenden",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Sie können diese Person immer noch auf den Fotos sehen, aber die Zuweisung eines Namens gilt nur für dieses Foto.",
|
||||||
|
"Cancel" : "Abbrechen",
|
||||||
|
"Hide" : "Ausblenden",
|
||||||
|
"Rename person" : "Person umbenennen",
|
||||||
|
"Please enter a name to rename the person" : "Bitte geben Sie einen neuen Namen für die Person ein",
|
||||||
|
"Rename" : "Umbenennen",
|
||||||
|
"This person is not {name}" : "Diese Person ist nicht {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Optional kann der richtige Name vergeben werden",
|
||||||
|
"Please assign a name to this person." : "Bitte benenne die Person.",
|
||||||
|
"Save" : "Speichern",
|
||||||
|
"Add name" : "Name hinzufügen",
|
||||||
|
"Ignore" : "Ignorieren",
|
||||||
|
"Skip for now" : "Vorerst überspringen",
|
||||||
|
"There was an error trying to show your friends" : "Es gab einen Fehler beim Versuch Ihre Freunde anzuzeigen",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Die Analyse ist aktiviert. Haben Sie etwas Geduld, bald werden Sie hier Ihre Freunde sehen.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Die Analyse ist deaktiviert. Bald werden alle Informationen zur Gesichtserkennung entfernt.",
|
||||||
|
"There was an error renaming this person" : "Es gab einen Fehler beim Umbenennen dieser Person",
|
||||||
|
"There was an error ignoring this person" : "Fehler beim Ignorieren dieser Person",
|
||||||
|
"Face Recognition" : "Gesichtserkennung",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Hier können Sie Fotos von Ihren Freunden sehen, welche gefunden wurden",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analysiere meine Bilder und gruppiere meine Lieben mit ähnlichen Gesichtern",
|
||||||
|
"Looking for your recognized friends" : "Auf der Suche nach Ihren erkannten Freunden",
|
||||||
|
"Review face groups" : "Gesichtergruppen überprüfen",
|
||||||
|
"The analysis is disabled" : "Die Analyse ist deaktiviert",
|
||||||
|
"Enable it to find your loved ones" : "Aktiviere es um Deine Lieben zu finden",
|
||||||
|
"Hide it" : "Ausblenden",
|
||||||
|
"Review people found" : "Gefundene Personen prüfen",
|
||||||
|
"Your friends have not been recognized yet" : "Ihre Freunde wurden noch nicht analysiert",
|
||||||
|
"Please, be patient" : "Bitte haben Sie Geduld",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Sie werden alle Informationen der Analyse verlieren und nach dem Reaktivieren geht es wieder von vorne los.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Möchten Sie das Gruppieren nach Gesichtern deaktivieren?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Es gab einen Fehler beim Versuch Bilder von Ihrem Freund zu finden",
|
||||||
|
"An error occurred while hiding this person" : "Beim Ausblenden dieser Person ist ein Fehler aufgetreten",
|
||||||
|
"There was an error renaming this cluster of faces" : "Es ist ein Fehler beim Umbenennen dieser Gruppe von Gesichtern aufgetreten.",
|
||||||
|
"An error occurred while hiding this group of faces" : "Beim Ausblenden dieser Gruppe von Gesichtern ist ein Fehler aufgetreten",
|
||||||
|
"_%n image_::_%n images_" : ["%n Bild ","%n Bilder"],
|
||||||
|
"You must be administrator to configure this feature" : "Sie müssen Administrator sein, um diese Funktion einrichten zu können",
|
||||||
|
"The format seems to be incorrect." : "Das Format scheint ungültig zu sein.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Anscheinend haben Sie noch kein Analysemodell eingerichtet",
|
||||||
|
"The minimum recommended area is %s" : "Der empfohlene Mindestbereich ist %s",
|
||||||
|
"The maximum recommended area is %s" : "Der empfohlene Maximalbereich ist %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Das Modell empfiehlt keinen größeren Bereich als %s",
|
||||||
|
"It seems you don't have any model installed." : "Anscheinend haben Sie kein Modell installiert",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Es scheint so, dass Sie den zugewiesenen Speicher für die Bildverarbeitung noch konfigurieren müssen.",
|
||||||
|
"Not installed" : "Nicht installiert",
|
||||||
|
"Not configured." : "Nicht konfiguriert.",
|
||||||
|
"A face recognition app" : "Eine App zur Gesichtserkennung",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Entdecke und gruppiere die Gesichter Ihrer Liebsten in deiner Cloud*\n\n⚠️ Diese Anwendung benötigt mindestens 1GB Arbeitsspeicher, um zu funktionieren! Siehe [Anforderungen] (https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) für Details.\n\n⚠️ Die Einrichtung dieser Anwendung erfordert Zugriff auf das Terminal und die Installation zusätzlicher Software. Siehe [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) für Details.\n\n- **😏 Gesichter auf Bildern erkennen:** Benutzen Sie die FaceRecognition-App um _jedes_ Gesicht in _jedem_ Ihrer Bilder zu erkennen!\n- **👪 Gesichter zu Personen gruppieren:** Die erkannten Gesichter werden aufgrund der Ähnlichkeit gruppiert und dann kann die FaceRecognition-App Personen erkennen!\n- **🔒 Eingebaute Privatsphäre:** Keine Daten verlassen Ihre Cloud. Die Standardeinstellungen sind immer ausgeschaltet und jeder Benutzer steuert die Aktivierung / Deaktivierung der Gesichtserkennung. Bilder aus jedem Verzeichnis können bei Bedarf von der Gesichtserkennung ausgeschlossen werden.\n- **⚙️ Leistung der KI:** Die Gesichtserkennungs-App nutzt die Leistung der KI und erstellt neuronale Netzwerkmodelle durch umfangreiche Nutzung der [DLib](http://dlib.net/)-Bibliothek.\n- **🚀 Bauen Sie Ihr eigenes Ding:** Die FaceRecognition-App ist nur ein grundlegender Baustein. Durch die FaceRecognition-API können Sie Ihre fortgeschrittenen Szenarien erstellen - automatisch Schlagworte zu Bildern hinzufügen, Kontakte und Personen verbinden, Bilder von bestimmten Personen teilen... Wir wollen Ihre Ideen hören!",
|
||||||
|
"See other photos" : "Andere Fotos ansehen",
|
||||||
|
"This photo will be separated from the person. If you rename it again, it will only be done on this photo. If you want to change the name of all the photos of this person, you must go to the image view and edit there." : "Dieses Foto wird von der Person getrennt. Wenn Sie es erneut umbenennen, wird dies nur für dieses Foto durchgeführt. Wenn Sie den Namen aller Fotos dieser Person ändern möchten, müssen Sie zur Bildansicht gehen und es dort bearbeiten.",
|
||||||
|
"Facial recognition is disabled" : "Gesichtserkennung ist deaktiviert",
|
||||||
|
"Facial recognition is disabled for this folder" : "Gesichtserkennung ist für diesen Ordner deaktiviert",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Der Speichertyp wird für die Analyse Ihrer Bilder nicht unterstützt",
|
||||||
|
"No people found" : "Keine Personen gefunden",
|
||||||
|
"This image is not yet analyzed" : "Dieses Bild ist noch nicht analysiert",
|
||||||
|
"Search for persons in the photos of this directory" : "Suche nach Personen auf den Bildern in diesem Ordner",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Bilder, welche nicht in der Gallerie sind, werden ebenso ignoriert",
|
||||||
|
"People" : "Personen",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Siehe <a target=\"_blank\" href=\"{docsLink}\">Dokumentation ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Zur Aktivierung die <a target=\"_blank\" href=\"{settingsLink}\">Einstellungen↗</a>öffnen",
|
||||||
|
"Open Documentation" : "Dokumentation öffnen",
|
||||||
|
"Temporary files" : "Temporäre Dateien",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Während der Analyse werden temporäre Dateien verwendet, um die Homogenität zwischen allen Bildern sicherzustellen.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Kleine Bilder ermöglichen eine schnelle Analyse, können aber dazu führen, dass kleinsten Gesichter in Ihren Fotos nicht erkannt werden. Große Bilder können die Erkennung verbessern, die Analyse ist jedoch langsamer.",
|
||||||
|
"Smaller images" : "Kleinere Bilder",
|
||||||
|
"Larger images" : "Größere Bilder",
|
||||||
|
"Restore" : "Wiederherstellen",
|
||||||
|
"Clustering threshold" : "Schwellenwert für das Clustering",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Personen werden als Gruppen von ähnlichen Gesichtern bestimmt. Um diese zu ermitteln, müssen alle gefundenen Gesichter verglichen werden. Für den Vergleich, ob Gesichter gruppiert werden, wird ein Schwellenwert verwendet.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Durch einen kleinen Schwellenwert werden nur sehr ähnliche Gesichter gruppiert. Dadurch können anfänglich sehr viele Gruppen zu benennen sein. Ein größerer Schwellenwert ist flexibler, um die Gesichter zu gruppieren und führt zu weniger Gruppen. Ähnliche Personen können hierdurch verwechselt werden.",
|
||||||
|
"Small threshold" : "Kleiner Schwellenwert",
|
||||||
|
"Higher threshold" : "Hoher Schwellenwert",
|
||||||
|
"Minimum confidence" : "Mindestvertrauen",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Das Mindestvertrauen bestimmt wie zuverlässig die Gesichtserkennung sein muss, um die Person zu gruppieren. Unscharfe oder falsch ausgerichtete Gesichter hätten ein Vertrauen näher an 0,0 und die besten Bilder nahe 1,0.",
|
||||||
|
"Lower minimum confidence" : "Niedrigeres Mindestvertrauen",
|
||||||
|
"Higher minimum confidence" : "Höchstes Mindestvertrauen",
|
||||||
|
"Minimum of faces in cluster" : "Minimum an Gesichtern im Cluster",
|
||||||
|
"The minimum number of faces that a cluster must have to display it to the user." : "Die Mindestanzahl von Gesichtern, die ein Cluster haben muss, um dem Benutzer angezeigt zu werden.",
|
||||||
|
"These faces clusters will not be shown as a suggestion, but can always be renamed eventually in the side panel." : "Diese Gesichtercluster werden nicht als Vorschlag angezeigt, können aber jederzeit in der Seitenleiste umbenannt werden.",
|
||||||
|
"Less faces" : "Weniger Gesichter",
|
||||||
|
"More faces" : "Mehr Gesichter",
|
||||||
|
"Configuration information" : "Einrichtungsinformationen",
|
||||||
|
"Current model:" : "Aktuelles Modell:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Maximal zugewiesener Speicher für die Bildverarbeitung:",
|
||||||
|
"Current status" : "Aktueller Status",
|
||||||
|
"Stopped" : "Gestoppt"
|
||||||
|
},
|
||||||
|
"nplurals=2; plural=(n != 1);");
|
||||||
100
facerecognition/l10n/de_DE.json
Normal file
100
facerecognition/l10n/de_DE.json
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "Die Analyse ist beendet",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n Bild wurde analysiert","%n Bilder wurden analysiert"],
|
||||||
|
"Analyzing images" : "Bilder werden analysiert",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n Bilder wurden gefunden","%n Bilder wurden gefunden"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n Bild in der Warteschlange","%n Bilder in der Warteschlange"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Endet ungefähr {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Die Analyse wurde noch nicht gestartet",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Die Änderungen wurden gespeichert und werden bei der nächsten Analyse angewendet.",
|
||||||
|
"The change could not be applied." : "Die Änderung konnte nicht angewendet werden.",
|
||||||
|
"Done" : "Erledigt",
|
||||||
|
"Hide person" : "Person ausblenden",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Sie können diese Person immer noch auf den Fotos sehen, aber die Zuweisung eines Namens gilt nur für dieses Foto.",
|
||||||
|
"Cancel" : "Abbrechen",
|
||||||
|
"Hide" : "Ausblenden",
|
||||||
|
"Rename person" : "Person umbenennen",
|
||||||
|
"Please enter a name to rename the person" : "Bitte geben Sie einen neuen Namen für die Person ein",
|
||||||
|
"Rename" : "Umbenennen",
|
||||||
|
"This person is not {name}" : "Diese Person ist nicht {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Optional kann der richtige Name vergeben werden",
|
||||||
|
"Please assign a name to this person." : "Bitte benenne die Person.",
|
||||||
|
"Save" : "Speichern",
|
||||||
|
"Add name" : "Name hinzufügen",
|
||||||
|
"Ignore" : "Ignorieren",
|
||||||
|
"Skip for now" : "Vorerst überspringen",
|
||||||
|
"There was an error trying to show your friends" : "Es gab einen Fehler beim Versuch Ihre Freunde anzuzeigen",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Die Analyse ist aktiviert. Haben Sie etwas Geduld, bald werden Sie hier Ihre Freunde sehen.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Die Analyse ist deaktiviert. Bald werden alle Informationen zur Gesichtserkennung entfernt.",
|
||||||
|
"There was an error renaming this person" : "Es gab einen Fehler beim Umbenennen dieser Person",
|
||||||
|
"There was an error ignoring this person" : "Fehler beim Ignorieren dieser Person",
|
||||||
|
"Face Recognition" : "Gesichtserkennung",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Hier können Sie Fotos von Ihren Freunden sehen, welche gefunden wurden",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analysiere meine Bilder und gruppiere meine Lieben mit ähnlichen Gesichtern",
|
||||||
|
"Looking for your recognized friends" : "Auf der Suche nach Ihren erkannten Freunden",
|
||||||
|
"Review face groups" : "Gesichtergruppen überprüfen",
|
||||||
|
"The analysis is disabled" : "Die Analyse ist deaktiviert",
|
||||||
|
"Enable it to find your loved ones" : "Aktiviere es um Deine Lieben zu finden",
|
||||||
|
"Hide it" : "Ausblenden",
|
||||||
|
"Review people found" : "Gefundene Personen prüfen",
|
||||||
|
"Your friends have not been recognized yet" : "Ihre Freunde wurden noch nicht analysiert",
|
||||||
|
"Please, be patient" : "Bitte haben Sie Geduld",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Sie werden alle Informationen der Analyse verlieren und nach dem Reaktivieren geht es wieder von vorne los.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Möchten Sie das Gruppieren nach Gesichtern deaktivieren?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Es gab einen Fehler beim Versuch Bilder von Ihrem Freund zu finden",
|
||||||
|
"An error occurred while hiding this person" : "Beim Ausblenden dieser Person ist ein Fehler aufgetreten",
|
||||||
|
"There was an error renaming this cluster of faces" : "Es ist ein Fehler beim Umbenennen dieser Gruppe von Gesichtern aufgetreten.",
|
||||||
|
"An error occurred while hiding this group of faces" : "Beim Ausblenden dieser Gruppe von Gesichtern ist ein Fehler aufgetreten",
|
||||||
|
"_%n image_::_%n images_" : ["%n Bild ","%n Bilder"],
|
||||||
|
"You must be administrator to configure this feature" : "Sie müssen Administrator sein, um diese Funktion einrichten zu können",
|
||||||
|
"The format seems to be incorrect." : "Das Format scheint ungültig zu sein.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Anscheinend haben Sie noch kein Analysemodell eingerichtet",
|
||||||
|
"The minimum recommended area is %s" : "Der empfohlene Mindestbereich ist %s",
|
||||||
|
"The maximum recommended area is %s" : "Der empfohlene Maximalbereich ist %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Das Modell empfiehlt keinen größeren Bereich als %s",
|
||||||
|
"It seems you don't have any model installed." : "Anscheinend haben Sie kein Modell installiert",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Es scheint so, dass Sie den zugewiesenen Speicher für die Bildverarbeitung noch konfigurieren müssen.",
|
||||||
|
"Not installed" : "Nicht installiert",
|
||||||
|
"Not configured." : "Nicht konfiguriert.",
|
||||||
|
"A face recognition app" : "Eine App zur Gesichtserkennung",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Entdecke und gruppiere die Gesichter Ihrer Liebsten in deiner Cloud*\n\n⚠️ Diese Anwendung benötigt mindestens 1GB Arbeitsspeicher, um zu funktionieren! Siehe [Anforderungen] (https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) für Details.\n\n⚠️ Die Einrichtung dieser Anwendung erfordert Zugriff auf das Terminal und die Installation zusätzlicher Software. Siehe [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) für Details.\n\n- **😏 Gesichter auf Bildern erkennen:** Benutzen Sie die FaceRecognition-App um _jedes_ Gesicht in _jedem_ Ihrer Bilder zu erkennen!\n- **👪 Gesichter zu Personen gruppieren:** Die erkannten Gesichter werden aufgrund der Ähnlichkeit gruppiert und dann kann die FaceRecognition-App Personen erkennen!\n- **🔒 Eingebaute Privatsphäre:** Keine Daten verlassen Ihre Cloud. Die Standardeinstellungen sind immer ausgeschaltet und jeder Benutzer steuert die Aktivierung / Deaktivierung der Gesichtserkennung. Bilder aus jedem Verzeichnis können bei Bedarf von der Gesichtserkennung ausgeschlossen werden.\n- **⚙️ Leistung der KI:** Die Gesichtserkennungs-App nutzt die Leistung der KI und erstellt neuronale Netzwerkmodelle durch umfangreiche Nutzung der [DLib](http://dlib.net/)-Bibliothek.\n- **🚀 Bauen Sie Ihr eigenes Ding:** Die FaceRecognition-App ist nur ein grundlegender Baustein. Durch die FaceRecognition-API können Sie Ihre fortgeschrittenen Szenarien erstellen - automatisch Schlagworte zu Bildern hinzufügen, Kontakte und Personen verbinden, Bilder von bestimmten Personen teilen... Wir wollen Ihre Ideen hören!",
|
||||||
|
"See other photos" : "Andere Fotos ansehen",
|
||||||
|
"This photo will be separated from the person. If you rename it again, it will only be done on this photo. If you want to change the name of all the photos of this person, you must go to the image view and edit there." : "Dieses Foto wird von der Person getrennt. Wenn Sie es erneut umbenennen, wird dies nur für dieses Foto durchgeführt. Wenn Sie den Namen aller Fotos dieser Person ändern möchten, müssen Sie zur Bildansicht gehen und es dort bearbeiten.",
|
||||||
|
"Facial recognition is disabled" : "Gesichtserkennung ist deaktiviert",
|
||||||
|
"Facial recognition is disabled for this folder" : "Gesichtserkennung ist für diesen Ordner deaktiviert",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Der Speichertyp wird für die Analyse Ihrer Bilder nicht unterstützt",
|
||||||
|
"No people found" : "Keine Personen gefunden",
|
||||||
|
"This image is not yet analyzed" : "Dieses Bild ist noch nicht analysiert",
|
||||||
|
"Search for persons in the photos of this directory" : "Suche nach Personen auf den Bildern in diesem Ordner",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Bilder, welche nicht in der Gallerie sind, werden ebenso ignoriert",
|
||||||
|
"People" : "Personen",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Siehe <a target=\"_blank\" href=\"{docsLink}\">Dokumentation ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Zur Aktivierung die <a target=\"_blank\" href=\"{settingsLink}\">Einstellungen↗</a>öffnen",
|
||||||
|
"Open Documentation" : "Dokumentation öffnen",
|
||||||
|
"Temporary files" : "Temporäre Dateien",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Während der Analyse werden temporäre Dateien verwendet, um die Homogenität zwischen allen Bildern sicherzustellen.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Kleine Bilder ermöglichen eine schnelle Analyse, können aber dazu führen, dass kleinsten Gesichter in Ihren Fotos nicht erkannt werden. Große Bilder können die Erkennung verbessern, die Analyse ist jedoch langsamer.",
|
||||||
|
"Smaller images" : "Kleinere Bilder",
|
||||||
|
"Larger images" : "Größere Bilder",
|
||||||
|
"Restore" : "Wiederherstellen",
|
||||||
|
"Clustering threshold" : "Schwellenwert für das Clustering",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Personen werden als Gruppen von ähnlichen Gesichtern bestimmt. Um diese zu ermitteln, müssen alle gefundenen Gesichter verglichen werden. Für den Vergleich, ob Gesichter gruppiert werden, wird ein Schwellenwert verwendet.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Durch einen kleinen Schwellenwert werden nur sehr ähnliche Gesichter gruppiert. Dadurch können anfänglich sehr viele Gruppen zu benennen sein. Ein größerer Schwellenwert ist flexibler, um die Gesichter zu gruppieren und führt zu weniger Gruppen. Ähnliche Personen können hierdurch verwechselt werden.",
|
||||||
|
"Small threshold" : "Kleiner Schwellenwert",
|
||||||
|
"Higher threshold" : "Hoher Schwellenwert",
|
||||||
|
"Minimum confidence" : "Mindestvertrauen",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Das Mindestvertrauen bestimmt wie zuverlässig die Gesichtserkennung sein muss, um die Person zu gruppieren. Unscharfe oder falsch ausgerichtete Gesichter hätten ein Vertrauen näher an 0,0 und die besten Bilder nahe 1,0.",
|
||||||
|
"Lower minimum confidence" : "Niedrigeres Mindestvertrauen",
|
||||||
|
"Higher minimum confidence" : "Höchstes Mindestvertrauen",
|
||||||
|
"Minimum of faces in cluster" : "Minimum an Gesichtern im Cluster",
|
||||||
|
"The minimum number of faces that a cluster must have to display it to the user." : "Die Mindestanzahl von Gesichtern, die ein Cluster haben muss, um dem Benutzer angezeigt zu werden.",
|
||||||
|
"These faces clusters will not be shown as a suggestion, but can always be renamed eventually in the side panel." : "Diese Gesichtercluster werden nicht als Vorschlag angezeigt, können aber jederzeit in der Seitenleiste umbenannt werden.",
|
||||||
|
"Less faces" : "Weniger Gesichter",
|
||||||
|
"More faces" : "Mehr Gesichter",
|
||||||
|
"Configuration information" : "Einrichtungsinformationen",
|
||||||
|
"Current model:" : "Aktuelles Modell:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Maximal zugewiesener Speicher für die Bildverarbeitung:",
|
||||||
|
"Current status" : "Aktueller Status",
|
||||||
|
"Stopped" : "Gestoppt"
|
||||||
|
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||||
|
}
|
||||||
108
facerecognition/l10n/es.js
Normal file
108
facerecognition/l10n/es.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "El análisis ha terminado",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n imagen analizada","%n imágenes fueron analizadas","%n imágenes fueron analizadas"],
|
||||||
|
"Analyzing images" : "Analizando imágenes",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n imagen detectada","%n imágenes detectadas","%n imágenes detectadas"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n imagen en cola","%n imágenes en cola","%n imágenes en cola"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Finaliza aproximadamente {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "El análisis no se ha iniciado todavía",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Los cambios fueron guardados. Se tendrá en cuenta en el siguiente análisis.",
|
||||||
|
"The change could not be applied." : "El cambio no pudo ser aplicado.",
|
||||||
|
"Done" : "Hecho",
|
||||||
|
"Hide person" : "Ocultar persona",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Aún puedes ver a esa persona en las fotos, pero asignarle un nombre solo será para esa foto.",
|
||||||
|
"Cancel" : "Cancelar",
|
||||||
|
"Hide" : "Ocultar",
|
||||||
|
"Rename person" : "Renombrar persona",
|
||||||
|
"Please enter a name to rename the person" : "Por favor ingrese un nombre para renombrar la persona",
|
||||||
|
"Rename" : "Renombrar",
|
||||||
|
"This person is not {name}" : "Esta persona no es {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Opcionalmente puede asignar el nombre correcto",
|
||||||
|
"Please assign a name to this person." : "Por favor, asigne un nombre a esta persona.",
|
||||||
|
"Save" : "Guardar",
|
||||||
|
"Add name" : "Agregar nombre",
|
||||||
|
"Ignore" : "Ignorar",
|
||||||
|
"Skip for now" : "Omitir por ahora",
|
||||||
|
"Keep ignored" : "Mantener ignorado",
|
||||||
|
"There was an error trying to show your friends" : "Hubo un error al tratar de mostrar a tus amigos",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "El análisis está habilitado, por favor sea paciente, pronto verá a sus amigos aquí.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "El análisis está deshabilitado. Pronto se eliminará toda la información encontrada para el reconocimiento facial.",
|
||||||
|
"You don't have more people to recognize." : "No tienes más personas a las que reconocer.",
|
||||||
|
"There was an error renaming this person" : "Se ha producido un error al cambiar el nombre de esta persona",
|
||||||
|
"There was an error ignoring this person" : "Hubo un error al ignorar a esta persona",
|
||||||
|
"You no longer have people ignored" : "Ya no tienes gente ignorada",
|
||||||
|
"Face Recognition" : "Reconocimiento Facial",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Aquí puedes ver fotos de tus amigos reconocidos",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analiza mis imágenes y agrupa a mis seres queridos con caras similares",
|
||||||
|
"Looking for your recognized friends" : "Buscando tus amigos reconocidos",
|
||||||
|
"Review face groups" : "Revisar grupos de caras",
|
||||||
|
"Review ignored people" : "Revisar personas ignoradas",
|
||||||
|
"The analysis is disabled" : "El análisis está deshabilitado",
|
||||||
|
"Enable it to find your loved ones" : "Habilítalo para encontrar a tus seres queridos",
|
||||||
|
"Hide it" : "Ocultarlo",
|
||||||
|
"Review people found" : "Revisar personas encontradas",
|
||||||
|
"Your friends have not been recognized yet" : "Tus amigos aún no han sido reconocidos",
|
||||||
|
"Please, be patient" : "Por favor sea paciente",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Perderá toda la información analizada y, si la vuelve a habilitar, comenzará de cero.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "¿Quieres desactivar la agrupación por caras?",
|
||||||
|
"You dont have more people to recognize." : "No tienes más personas para reconocer.",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Hubo un error al intentar encontrar fotos de tu amigo",
|
||||||
|
"An error occurred while hiding this person" : "Se produjo un error al ocultar a esta persona",
|
||||||
|
"There was an error renaming this cluster of faces" : "Se produjo un error al cambiar el nombre de este grupo de caras.",
|
||||||
|
"An error occurred while hiding this group of faces" : "Se produjo un error al ocultar este grupo de caras",
|
||||||
|
"_%n image_::_%n images_" : ["%n imagen","%n imágenes","%n imágenes"],
|
||||||
|
"You must be administrator to configure this feature" : "Debes ser administrador para configurar esta función",
|
||||||
|
"The format seems to be incorrect." : "El formato parece ser incorrecto.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Parece que aún no ha configurado ningún modelo de análisis",
|
||||||
|
"The minimum recommended area is %s" : "El área mínima recomendada es %s",
|
||||||
|
"The maximum recommended area is %s" : "El área máxima recomendada es %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "El modelo no recomienda un área mayor que %s",
|
||||||
|
"Images of %s" : "Imágenes de %s",
|
||||||
|
"It seems you don't have any model installed." : "Parece que no tienes ningún modelo instalado.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Parece que todavía tienes que configurar la memoria asignada para el procesamiento de imágenes.",
|
||||||
|
"Not installed" : "No instalado",
|
||||||
|
"Not configured." : "No configurado.",
|
||||||
|
"A face recognition app" : "Una aplicación de reconocimiento facial",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "Detecta y agrupa rostros de tu ser querido en tu nube\n\n⚠️ ¡Esta aplicación requiere un mínimo de 1GB de memoria RAM para funcionar!. Ver [Requisitos](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) para más detalles.\n\n⚠️ La configuración de esta aplicación requiere acceso al terminal e incluso ensuciarse las manos con la instalación de software adicional. Ver [Instalación](https://github.com/matiasdelellis/facerecognition/wiki/Installation) para más detalles.\n\n- **😏 Detecta rostros de imágenes:** ¡Usa la aplicación FaceRecognition para detectar cualquier rostro en cualquiera de tus imágenes!\n- **👪 Agrupar rostros con personas:** Los rostros detectados se agrupan según la similitud y luego la aplicación FaceRecognition puede reconocer personas.\n- **🔒Privacidad incorporada:** No hay datos saliendo de su nube. Los valores predeterminados siempre están desactivados y cada usuario controla habilitar / deshabilitar la detección de rostros. Las imágenes de cada directorio pueden excluirse de la detección de rostros, si es necesario.\n- **⚙️Poder de la inteligencia artificial:** La aplicación FaceRecognition aprovecha la potencia de la inteligencia artificial y los modelos de red neuronal ya construidos a través del uso extenso de la biblioteca [DLib](http://dlib.net/).\n- **🚀 Construye lo tuyo:** La aplicación FaceRecognition es solo un elemento básico. A través de la API de FaceRecognition, puede crear sus escenarios avanzados: Agregar etiquetas automáticamente a las imágenes, conectar contactos y personas, compartir imágenes de personas específicas... ¡Queremos escuchar sus ideas!",
|
||||||
|
"See other photos" : "Ver otras fotos",
|
||||||
|
"This photo will be separated from the person. If you rename it again, it will only be done on this photo. If you want to change the name of all the photos of this person, you must go to the image view and edit there." : "Esta foto se separará de la persona. Si vuelves a cambiarle el nombre, solo se hará en esta foto. Si desea cambiar el nombre de todas las fotos de esta persona, debe ir a la vista de imágenes y editar allí.",
|
||||||
|
"Facial recognition is disabled" : "El reconocimiento facial está desactivado",
|
||||||
|
"Facial recognition is disabled for this folder" : "El reconocimiento facial está deshabilitado para esta carpeta",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "El tipo de almacenamiento no es compatible para analizar sus fotos",
|
||||||
|
"No people found" : "No se encontraron personas",
|
||||||
|
"This image is not yet analyzed" : "Esta imagen aún no ha sido analizada",
|
||||||
|
"Search for persons in the photos of this directory" : "Buscar personas en las fotos de este directorio",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Las fotos que no están en la galería también se ignoran",
|
||||||
|
"People" : "Personas",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Ver <a target=\"_blank\" href=\"{docsLink}\">documentación ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Abra la <a target=\"_blank\" href=\"{settingsLink}\">configuración ↗</a> para habilitarlo",
|
||||||
|
"Open Documentation" : "Abrir Documentación",
|
||||||
|
"Temporary files" : "Archivos temporales",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durante el análisis, se utilizan archivos temporales para garantizar la homogeneidad entre todas las imágenes.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Las imágenes pequeñas permiten un análisis rápido, pero puedes perder las caras más pequeñas de sus fotos. Las imágenes grandes pueden mejorar los resultados, pero el análisis será más lento.",
|
||||||
|
"Smaller images" : "Imágenes más pequeñas",
|
||||||
|
"Larger images" : "Imágenes más grandes",
|
||||||
|
"Restore" : "Restaurar",
|
||||||
|
"Clustering threshold" : "Umbral de agrupación",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Las personas se determinan como grupos de caras similares y para obtenerlas se deben comparar todas las caras encontradas. Cuando se comparan, se utiliza un umbral para determinar si deben agruparse.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Un umbral pequeño solo agrupará caras muy similares, pero inicialmente tendrá muchos grupos para nombrar. Un umbral mayor es más flexible para agrupar las caras y obtendrá menos grupos, pero pudiendo confundir a personas similares.",
|
||||||
|
"Small threshold" : "Umbral pequeño",
|
||||||
|
"Higher threshold" : "Umbral más alto",
|
||||||
|
"Minimum confidence" : "Confianza mínima",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "La confianza mínima determina qué tan confiable debe ser una detección de rostros para intentar agruparla. Las caras borrosas o desalineadas tendrían una confianza más cercana a 0.0, y las mejores imágenes cercanas a 1.0.",
|
||||||
|
"Lower minimum confidence" : "Confianza mínima más baja",
|
||||||
|
"Higher minimum confidence" : "Mayor confianza mínima",
|
||||||
|
"Minimum of faces in cluster" : "Mínimo de caras en clúster",
|
||||||
|
"The minimum number of faces that a cluster must have to display it to the user." : "El número mínimo de caras que debe tener un clúster para mostrárselo al usuario.",
|
||||||
|
"These faces clusters will not be shown as a suggestion, but can always be renamed eventually in the side panel." : "Estos grupos de caras no se mostrarán como una sugerencia, pero siempre se pueden cambiar de nombre eventualmente en el panel lateral.",
|
||||||
|
"Less faces" : "Menos caras",
|
||||||
|
"More faces" : "Más caras",
|
||||||
|
"Configuration information" : "Información de la configuración",
|
||||||
|
"Current model:" : "Modelo actual:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Memoria máxima asignada para el procesamiento de imágenes:",
|
||||||
|
"Current status" : "Estado actual",
|
||||||
|
"Stopped" : "Detenido"
|
||||||
|
},
|
||||||
|
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||||
106
facerecognition/l10n/es.json
Normal file
106
facerecognition/l10n/es.json
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "El análisis ha terminado",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n imagen analizada","%n imágenes fueron analizadas","%n imágenes fueron analizadas"],
|
||||||
|
"Analyzing images" : "Analizando imágenes",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n imagen detectada","%n imágenes detectadas","%n imágenes detectadas"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n imagen en cola","%n imágenes en cola","%n imágenes en cola"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Finaliza aproximadamente {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "El análisis no se ha iniciado todavía",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Los cambios fueron guardados. Se tendrá en cuenta en el siguiente análisis.",
|
||||||
|
"The change could not be applied." : "El cambio no pudo ser aplicado.",
|
||||||
|
"Done" : "Hecho",
|
||||||
|
"Hide person" : "Ocultar persona",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Aún puedes ver a esa persona en las fotos, pero asignarle un nombre solo será para esa foto.",
|
||||||
|
"Cancel" : "Cancelar",
|
||||||
|
"Hide" : "Ocultar",
|
||||||
|
"Rename person" : "Renombrar persona",
|
||||||
|
"Please enter a name to rename the person" : "Por favor ingrese un nombre para renombrar la persona",
|
||||||
|
"Rename" : "Renombrar",
|
||||||
|
"This person is not {name}" : "Esta persona no es {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Opcionalmente puede asignar el nombre correcto",
|
||||||
|
"Please assign a name to this person." : "Por favor, asigne un nombre a esta persona.",
|
||||||
|
"Save" : "Guardar",
|
||||||
|
"Add name" : "Agregar nombre",
|
||||||
|
"Ignore" : "Ignorar",
|
||||||
|
"Skip for now" : "Omitir por ahora",
|
||||||
|
"Keep ignored" : "Mantener ignorado",
|
||||||
|
"There was an error trying to show your friends" : "Hubo un error al tratar de mostrar a tus amigos",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "El análisis está habilitado, por favor sea paciente, pronto verá a sus amigos aquí.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "El análisis está deshabilitado. Pronto se eliminará toda la información encontrada para el reconocimiento facial.",
|
||||||
|
"You don't have more people to recognize." : "No tienes más personas a las que reconocer.",
|
||||||
|
"There was an error renaming this person" : "Se ha producido un error al cambiar el nombre de esta persona",
|
||||||
|
"There was an error ignoring this person" : "Hubo un error al ignorar a esta persona",
|
||||||
|
"You no longer have people ignored" : "Ya no tienes gente ignorada",
|
||||||
|
"Face Recognition" : "Reconocimiento Facial",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Aquí puedes ver fotos de tus amigos reconocidos",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analiza mis imágenes y agrupa a mis seres queridos con caras similares",
|
||||||
|
"Looking for your recognized friends" : "Buscando tus amigos reconocidos",
|
||||||
|
"Review face groups" : "Revisar grupos de caras",
|
||||||
|
"Review ignored people" : "Revisar personas ignoradas",
|
||||||
|
"The analysis is disabled" : "El análisis está deshabilitado",
|
||||||
|
"Enable it to find your loved ones" : "Habilítalo para encontrar a tus seres queridos",
|
||||||
|
"Hide it" : "Ocultarlo",
|
||||||
|
"Review people found" : "Revisar personas encontradas",
|
||||||
|
"Your friends have not been recognized yet" : "Tus amigos aún no han sido reconocidos",
|
||||||
|
"Please, be patient" : "Por favor sea paciente",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Perderá toda la información analizada y, si la vuelve a habilitar, comenzará de cero.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "¿Quieres desactivar la agrupación por caras?",
|
||||||
|
"You dont have more people to recognize." : "No tienes más personas para reconocer.",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Hubo un error al intentar encontrar fotos de tu amigo",
|
||||||
|
"An error occurred while hiding this person" : "Se produjo un error al ocultar a esta persona",
|
||||||
|
"There was an error renaming this cluster of faces" : "Se produjo un error al cambiar el nombre de este grupo de caras.",
|
||||||
|
"An error occurred while hiding this group of faces" : "Se produjo un error al ocultar este grupo de caras",
|
||||||
|
"_%n image_::_%n images_" : ["%n imagen","%n imágenes","%n imágenes"],
|
||||||
|
"You must be administrator to configure this feature" : "Debes ser administrador para configurar esta función",
|
||||||
|
"The format seems to be incorrect." : "El formato parece ser incorrecto.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Parece que aún no ha configurado ningún modelo de análisis",
|
||||||
|
"The minimum recommended area is %s" : "El área mínima recomendada es %s",
|
||||||
|
"The maximum recommended area is %s" : "El área máxima recomendada es %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "El modelo no recomienda un área mayor que %s",
|
||||||
|
"Images of %s" : "Imágenes de %s",
|
||||||
|
"It seems you don't have any model installed." : "Parece que no tienes ningún modelo instalado.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Parece que todavía tienes que configurar la memoria asignada para el procesamiento de imágenes.",
|
||||||
|
"Not installed" : "No instalado",
|
||||||
|
"Not configured." : "No configurado.",
|
||||||
|
"A face recognition app" : "Una aplicación de reconocimiento facial",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "Detecta y agrupa rostros de tu ser querido en tu nube\n\n⚠️ ¡Esta aplicación requiere un mínimo de 1GB de memoria RAM para funcionar!. Ver [Requisitos](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) para más detalles.\n\n⚠️ La configuración de esta aplicación requiere acceso al terminal e incluso ensuciarse las manos con la instalación de software adicional. Ver [Instalación](https://github.com/matiasdelellis/facerecognition/wiki/Installation) para más detalles.\n\n- **😏 Detecta rostros de imágenes:** ¡Usa la aplicación FaceRecognition para detectar cualquier rostro en cualquiera de tus imágenes!\n- **👪 Agrupar rostros con personas:** Los rostros detectados se agrupan según la similitud y luego la aplicación FaceRecognition puede reconocer personas.\n- **🔒Privacidad incorporada:** No hay datos saliendo de su nube. Los valores predeterminados siempre están desactivados y cada usuario controla habilitar / deshabilitar la detección de rostros. Las imágenes de cada directorio pueden excluirse de la detección de rostros, si es necesario.\n- **⚙️Poder de la inteligencia artificial:** La aplicación FaceRecognition aprovecha la potencia de la inteligencia artificial y los modelos de red neuronal ya construidos a través del uso extenso de la biblioteca [DLib](http://dlib.net/).\n- **🚀 Construye lo tuyo:** La aplicación FaceRecognition es solo un elemento básico. A través de la API de FaceRecognition, puede crear sus escenarios avanzados: Agregar etiquetas automáticamente a las imágenes, conectar contactos y personas, compartir imágenes de personas específicas... ¡Queremos escuchar sus ideas!",
|
||||||
|
"See other photos" : "Ver otras fotos",
|
||||||
|
"This photo will be separated from the person. If you rename it again, it will only be done on this photo. If you want to change the name of all the photos of this person, you must go to the image view and edit there." : "Esta foto se separará de la persona. Si vuelves a cambiarle el nombre, solo se hará en esta foto. Si desea cambiar el nombre de todas las fotos de esta persona, debe ir a la vista de imágenes y editar allí.",
|
||||||
|
"Facial recognition is disabled" : "El reconocimiento facial está desactivado",
|
||||||
|
"Facial recognition is disabled for this folder" : "El reconocimiento facial está deshabilitado para esta carpeta",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "El tipo de almacenamiento no es compatible para analizar sus fotos",
|
||||||
|
"No people found" : "No se encontraron personas",
|
||||||
|
"This image is not yet analyzed" : "Esta imagen aún no ha sido analizada",
|
||||||
|
"Search for persons in the photos of this directory" : "Buscar personas en las fotos de este directorio",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Las fotos que no están en la galería también se ignoran",
|
||||||
|
"People" : "Personas",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Ver <a target=\"_blank\" href=\"{docsLink}\">documentación ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Abra la <a target=\"_blank\" href=\"{settingsLink}\">configuración ↗</a> para habilitarlo",
|
||||||
|
"Open Documentation" : "Abrir Documentación",
|
||||||
|
"Temporary files" : "Archivos temporales",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durante el análisis, se utilizan archivos temporales para garantizar la homogeneidad entre todas las imágenes.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Las imágenes pequeñas permiten un análisis rápido, pero puedes perder las caras más pequeñas de sus fotos. Las imágenes grandes pueden mejorar los resultados, pero el análisis será más lento.",
|
||||||
|
"Smaller images" : "Imágenes más pequeñas",
|
||||||
|
"Larger images" : "Imágenes más grandes",
|
||||||
|
"Restore" : "Restaurar",
|
||||||
|
"Clustering threshold" : "Umbral de agrupación",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Las personas se determinan como grupos de caras similares y para obtenerlas se deben comparar todas las caras encontradas. Cuando se comparan, se utiliza un umbral para determinar si deben agruparse.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Un umbral pequeño solo agrupará caras muy similares, pero inicialmente tendrá muchos grupos para nombrar. Un umbral mayor es más flexible para agrupar las caras y obtendrá menos grupos, pero pudiendo confundir a personas similares.",
|
||||||
|
"Small threshold" : "Umbral pequeño",
|
||||||
|
"Higher threshold" : "Umbral más alto",
|
||||||
|
"Minimum confidence" : "Confianza mínima",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "La confianza mínima determina qué tan confiable debe ser una detección de rostros para intentar agruparla. Las caras borrosas o desalineadas tendrían una confianza más cercana a 0.0, y las mejores imágenes cercanas a 1.0.",
|
||||||
|
"Lower minimum confidence" : "Confianza mínima más baja",
|
||||||
|
"Higher minimum confidence" : "Mayor confianza mínima",
|
||||||
|
"Minimum of faces in cluster" : "Mínimo de caras en clúster",
|
||||||
|
"The minimum number of faces that a cluster must have to display it to the user." : "El número mínimo de caras que debe tener un clúster para mostrárselo al usuario.",
|
||||||
|
"These faces clusters will not be shown as a suggestion, but can always be renamed eventually in the side panel." : "Estos grupos de caras no se mostrarán como una sugerencia, pero siempre se pueden cambiar de nombre eventualmente en el panel lateral.",
|
||||||
|
"Less faces" : "Menos caras",
|
||||||
|
"More faces" : "Más caras",
|
||||||
|
"Configuration information" : "Información de la configuración",
|
||||||
|
"Current model:" : "Modelo actual:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Memoria máxima asignada para el procesamiento de imágenes:",
|
||||||
|
"Current status" : "Estado actual",
|
||||||
|
"Stopped" : "Detenido"
|
||||||
|
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||||
|
}
|
||||||
95
facerecognition/l10n/fr.js
Normal file
95
facerecognition/l10n/fr.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "L'analyse est terminée",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%nimage a été analysée","%nimages ont été analysées","%nimages ont été analysées"],
|
||||||
|
"Analyzing images" : "Analyse des images en cours",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%nimage détectée","%n images détectées","%n images détectées"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n image en attente","%n images en attente d'analyse","%n images en attente d'analyse"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Se termine approximativement {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "L'analyse n'a pas encore commencé",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Les modifications ont été sauvegardées. Elles seront prises en compte lors de la prochaine analyse.",
|
||||||
|
"The change could not be applied." : "La modification n'a pas pu être effectuée.",
|
||||||
|
"Hide person" : "Câcher la personne",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Vous pouvez toujours voir cette personne dans les photos, mais l’attribution d’un nom ne sera que pour cette photo.",
|
||||||
|
"Cancel" : "Annuler",
|
||||||
|
"Hide" : "Câcher",
|
||||||
|
"Rename person" : "Renommer la personne",
|
||||||
|
"Please enter a name to rename the person" : "Veuillez entrer un nom pour renommer la personne",
|
||||||
|
"Rename" : "Renommer ",
|
||||||
|
"This person is not {name}" : "Cette personne n'est pas {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Si vous le souhaitez, vous pouvez attribuer le bon nom",
|
||||||
|
"Please assign a name to this person." : "Veuillez attribuer un nom à cette personne.",
|
||||||
|
"Save" : "Enregistrer",
|
||||||
|
"Add name" : "Ajouter le nom",
|
||||||
|
"Ignore" : "Ignorer",
|
||||||
|
"Skip for now" : "Passer pour le moment",
|
||||||
|
"There was an error trying to show your friends" : "Une erreur est survenue en essayant d’afficher vos amis",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "L’analyse est activée, veuillez patienter, vos amis apparaîtront bientôt ici",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "L’analyse est désactivée. Toutes les informations trouvées pour la reconnaissance faciale seront bientôt supprimées.",
|
||||||
|
"There was an error renaming this person" : "Il y a eu une erreur en renommant cette personne",
|
||||||
|
"There was an error ignoring this person" : "Une erreur s'est produite en ignorant cette personne",
|
||||||
|
"Face Recognition" : "Reconnaissance faciale",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Vous pouvez voir ici des photos de vos amis qui ont été reconnus",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analyser mes images et grouper les personnes qui ont des visages semblables",
|
||||||
|
"Looking for your recognized friends" : "Recherche de vos amis reconnus",
|
||||||
|
"Review face groups" : "Passer en revue les groupes de visages",
|
||||||
|
"The analysis is disabled" : "L’analyse est désactivée",
|
||||||
|
"Enable it to find your loved ones" : "Activez pour trouver vos proches",
|
||||||
|
"Hide it" : "Cachez-la",
|
||||||
|
"Review people found" : "Passer en revue les personnes trouvées",
|
||||||
|
"Your friends have not been recognized yet" : "Vos amis n’ont pas encore été reconnus",
|
||||||
|
"Please, be patient" : "Veuillez patienter",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Vous perdrez toutes les informations analysées, et si vous le réactivez, vous devrez recommencer depuis le début.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Voulez-vous désactiver le groupage par visage ?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Une erreur est survenue en essayant de trouver les photos de vos amis",
|
||||||
|
"An error occurred while hiding this person" : "Une erreur s'est produite en cachant cette personne",
|
||||||
|
"There was an error renaming this cluster of faces" : "Il y a eu une erreur en renommant ce groupe de visages",
|
||||||
|
"An error occurred while hiding this group of faces" : "Une erreur s'est produite en ignorant ce groupe de visages",
|
||||||
|
"_%n image_::_%n images_" : ["%n image","%n images","%n images"],
|
||||||
|
"You must be administrator to configure this feature" : "Vous devez être administrateur pour configurer cette fonctionnalité",
|
||||||
|
"The format seems to be incorrect." : "Le format semble être incorrect.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Il semble que vous n’ayez pas encore configuré de modèle d’analyse",
|
||||||
|
"The minimum recommended area is %s" : "La dimension minimum recommandée est %s",
|
||||||
|
"The maximum recommended area is %s" : "La dimension maximum recommandée est %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Le modèle ne recommande pas une dimension supérieure à %s",
|
||||||
|
"It seems you don't have any model installed." : "Il semble que vous n’ayez installé aucun modèle.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Apparemment, vous avez encore à configurer la mémoire affectée au traitement d'image.",
|
||||||
|
"Not installed" : "Non installé",
|
||||||
|
"Not configured." : "Non configuré.",
|
||||||
|
"A face recognition app" : "Une application de reconnaissance faciale",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Détectez et groupez les visages de vos proches dans le cloud**\n\n⚠️ Cette application requiert au minimum 2Gb de mémoire RAM pour fonctionner ! Voyez les [Prérequis](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) pour les détails.\n\n⚠️ La configuration de cette application nécessite l’accès au terminal et même l’installation d’un logiciel supplémentaire. Voyez [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) pour les détails.\n\n- **😏 Détecte les visages depuis les images :** Utilisez l’application FaceRecognition pour détecter _tous_ les visages dans _toutes_ vos images !\n- **👪 Assimile les visages à des personnes :** Les visages détectés sont regroupés ensemble sur base de leur similitude et l’application FaceRecognition peut alors reconnaître des personnes ! \n- **🔒 Vie privée intégrée :** Aucune donnée ne quitte votre cloud. Les paramètres par défaut sont toujours désactivés et chaque utilisateur contrôle l’activation/la désactivation de la reconnaissance faciale. Les images de chaque répertoire peuvent être exclues de la reconnaissance faciale, si besoin. \n- **⚙️ Pouvoir de l’IA :** L’application FaceRecognition exploite la puissance de l’IA et des modèles de systèmes neuronaux déjà construits grâce à l’utilisation étendue de la bibliothèque [DLib](http://dlib.net/).\n- **🚀 Construisez votre propre idée :** L’application FaceRecognition n’est qu’un élément de base. Grâce à l’API FaceRecognition, vous pouvez construire vos propres scénarios avancés : ajouter automatiquement des tags aux images, connecter des contacts et des personnes, partager des images d'une personne spécifique... Nous voulons entendre vos idées !",
|
||||||
|
"See other photos" : "Voir d'autres photos",
|
||||||
|
"Facial recognition is disabled" : "La reconnaissance des visages est désactivée",
|
||||||
|
"Facial recognition is disabled for this folder" : "La reconnaissance des visages est désactivée pour ce dossier ",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Ce type de stockage n’est pas supporté pour analyser vos photos",
|
||||||
|
"No people found" : "Aucune personne n'a été détectée ",
|
||||||
|
"This image is not yet analyzed" : "Cette image n’a pas encore été analysée",
|
||||||
|
"Search for persons in the photos of this directory" : "Rechercher les personnes dans les photos de ce répertoire",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Les photos qui ne sont pas dans la galerie sont aussi ignorées",
|
||||||
|
"People" : "Personnes",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Voyez <a target=\"_blank\" href=\"{docsLink}\">la documentation ↗</a>",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Ouvrir <a target=\"_blank\" href=\"{settingsLink}\">paramètres ↗ </a> pour l’activer",
|
||||||
|
"Open Documentation" : "Documentation ouverte",
|
||||||
|
"Temporary files" : "Fichiers temporaires",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durant l’analyse, des fichiers temporaires sont utilisés pour assurer l’homogénéité entre toutes les images.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "De petites images permettent une analyse rapide, mais vous pouvez perdre les plus petits visages sur les photos. De grandes images peuvent améliorer les résultats, mais l’analyse sera plus lente. ",
|
||||||
|
"Smaller images" : "Images plus petites",
|
||||||
|
"Larger images" : "Images plus grandes",
|
||||||
|
"Restore" : "Restaurer ",
|
||||||
|
"Clustering threshold" : "Seuil de groupement",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Les personnes sont déterminées comme des groupes de visages similaires et pour les obtenir, tous les visages trouvés doivent être comparés. Lorsqu'ils sont comparés, un seuil est utilisé pour déterminer s'ils doivent être regroupés.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Un petit seuil ne regroupera que des visages très similaires, mais vous aurez initialement de nombreux groupes à nommer. Un seuil plus grand est plus flexible pour regrouper les visages, obtenant moins de groupes, mais peut confondre des personnes similaires.",
|
||||||
|
"Small threshold" : "Petit seuil",
|
||||||
|
"Higher threshold" : "Seuil plus élevé",
|
||||||
|
"Minimum confidence" : "Confiance minimale ",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "La confiance minimale détermine le degré de fiabilité d'une détection de visage pour tenter de le regrouper. Les visages flous ou mal alignés auraient un niveau de confiance plus proche de 0,0, et les meilleures images proches de 1,0. ",
|
||||||
|
"Lower minimum confidence" : "Confiance minimale plus faible",
|
||||||
|
"Higher minimum confidence" : "Confiance minimale plus haute",
|
||||||
|
"Configuration information" : "Information de configuration",
|
||||||
|
"Current model:" : "Modèle actuel :",
|
||||||
|
"Maximum memory assigned for image processing:" : "Mémoire maximale affectée au traitement d'image :",
|
||||||
|
"Current status" : "État actuel",
|
||||||
|
"Stopped" : "Arrêté"
|
||||||
|
},
|
||||||
|
"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||||
93
facerecognition/l10n/fr.json
Normal file
93
facerecognition/l10n/fr.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "L'analyse est terminée",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%nimage a été analysée","%nimages ont été analysées","%nimages ont été analysées"],
|
||||||
|
"Analyzing images" : "Analyse des images en cours",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%nimage détectée","%n images détectées","%n images détectées"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n image en attente","%n images en attente d'analyse","%n images en attente d'analyse"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Se termine approximativement {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "L'analyse n'a pas encore commencé",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Les modifications ont été sauvegardées. Elles seront prises en compte lors de la prochaine analyse.",
|
||||||
|
"The change could not be applied." : "La modification n'a pas pu être effectuée.",
|
||||||
|
"Hide person" : "Câcher la personne",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Vous pouvez toujours voir cette personne dans les photos, mais l’attribution d’un nom ne sera que pour cette photo.",
|
||||||
|
"Cancel" : "Annuler",
|
||||||
|
"Hide" : "Câcher",
|
||||||
|
"Rename person" : "Renommer la personne",
|
||||||
|
"Please enter a name to rename the person" : "Veuillez entrer un nom pour renommer la personne",
|
||||||
|
"Rename" : "Renommer ",
|
||||||
|
"This person is not {name}" : "Cette personne n'est pas {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Si vous le souhaitez, vous pouvez attribuer le bon nom",
|
||||||
|
"Please assign a name to this person." : "Veuillez attribuer un nom à cette personne.",
|
||||||
|
"Save" : "Enregistrer",
|
||||||
|
"Add name" : "Ajouter le nom",
|
||||||
|
"Ignore" : "Ignorer",
|
||||||
|
"Skip for now" : "Passer pour le moment",
|
||||||
|
"There was an error trying to show your friends" : "Une erreur est survenue en essayant d’afficher vos amis",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "L’analyse est activée, veuillez patienter, vos amis apparaîtront bientôt ici",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "L’analyse est désactivée. Toutes les informations trouvées pour la reconnaissance faciale seront bientôt supprimées.",
|
||||||
|
"There was an error renaming this person" : "Il y a eu une erreur en renommant cette personne",
|
||||||
|
"There was an error ignoring this person" : "Une erreur s'est produite en ignorant cette personne",
|
||||||
|
"Face Recognition" : "Reconnaissance faciale",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Vous pouvez voir ici des photos de vos amis qui ont été reconnus",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analyser mes images et grouper les personnes qui ont des visages semblables",
|
||||||
|
"Looking for your recognized friends" : "Recherche de vos amis reconnus",
|
||||||
|
"Review face groups" : "Passer en revue les groupes de visages",
|
||||||
|
"The analysis is disabled" : "L’analyse est désactivée",
|
||||||
|
"Enable it to find your loved ones" : "Activez pour trouver vos proches",
|
||||||
|
"Hide it" : "Cachez-la",
|
||||||
|
"Review people found" : "Passer en revue les personnes trouvées",
|
||||||
|
"Your friends have not been recognized yet" : "Vos amis n’ont pas encore été reconnus",
|
||||||
|
"Please, be patient" : "Veuillez patienter",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Vous perdrez toutes les informations analysées, et si vous le réactivez, vous devrez recommencer depuis le début.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Voulez-vous désactiver le groupage par visage ?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Une erreur est survenue en essayant de trouver les photos de vos amis",
|
||||||
|
"An error occurred while hiding this person" : "Une erreur s'est produite en cachant cette personne",
|
||||||
|
"There was an error renaming this cluster of faces" : "Il y a eu une erreur en renommant ce groupe de visages",
|
||||||
|
"An error occurred while hiding this group of faces" : "Une erreur s'est produite en ignorant ce groupe de visages",
|
||||||
|
"_%n image_::_%n images_" : ["%n image","%n images","%n images"],
|
||||||
|
"You must be administrator to configure this feature" : "Vous devez être administrateur pour configurer cette fonctionnalité",
|
||||||
|
"The format seems to be incorrect." : "Le format semble être incorrect.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Il semble que vous n’ayez pas encore configuré de modèle d’analyse",
|
||||||
|
"The minimum recommended area is %s" : "La dimension minimum recommandée est %s",
|
||||||
|
"The maximum recommended area is %s" : "La dimension maximum recommandée est %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Le modèle ne recommande pas une dimension supérieure à %s",
|
||||||
|
"It seems you don't have any model installed." : "Il semble que vous n’ayez installé aucun modèle.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Apparemment, vous avez encore à configurer la mémoire affectée au traitement d'image.",
|
||||||
|
"Not installed" : "Non installé",
|
||||||
|
"Not configured." : "Non configuré.",
|
||||||
|
"A face recognition app" : "Une application de reconnaissance faciale",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Détectez et groupez les visages de vos proches dans le cloud**\n\n⚠️ Cette application requiert au minimum 2Gb de mémoire RAM pour fonctionner ! Voyez les [Prérequis](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) pour les détails.\n\n⚠️ La configuration de cette application nécessite l’accès au terminal et même l’installation d’un logiciel supplémentaire. Voyez [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) pour les détails.\n\n- **😏 Détecte les visages depuis les images :** Utilisez l’application FaceRecognition pour détecter _tous_ les visages dans _toutes_ vos images !\n- **👪 Assimile les visages à des personnes :** Les visages détectés sont regroupés ensemble sur base de leur similitude et l’application FaceRecognition peut alors reconnaître des personnes ! \n- **🔒 Vie privée intégrée :** Aucune donnée ne quitte votre cloud. Les paramètres par défaut sont toujours désactivés et chaque utilisateur contrôle l’activation/la désactivation de la reconnaissance faciale. Les images de chaque répertoire peuvent être exclues de la reconnaissance faciale, si besoin. \n- **⚙️ Pouvoir de l’IA :** L’application FaceRecognition exploite la puissance de l’IA et des modèles de systèmes neuronaux déjà construits grâce à l’utilisation étendue de la bibliothèque [DLib](http://dlib.net/).\n- **🚀 Construisez votre propre idée :** L’application FaceRecognition n’est qu’un élément de base. Grâce à l’API FaceRecognition, vous pouvez construire vos propres scénarios avancés : ajouter automatiquement des tags aux images, connecter des contacts et des personnes, partager des images d'une personne spécifique... Nous voulons entendre vos idées !",
|
||||||
|
"See other photos" : "Voir d'autres photos",
|
||||||
|
"Facial recognition is disabled" : "La reconnaissance des visages est désactivée",
|
||||||
|
"Facial recognition is disabled for this folder" : "La reconnaissance des visages est désactivée pour ce dossier ",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Ce type de stockage n’est pas supporté pour analyser vos photos",
|
||||||
|
"No people found" : "Aucune personne n'a été détectée ",
|
||||||
|
"This image is not yet analyzed" : "Cette image n’a pas encore été analysée",
|
||||||
|
"Search for persons in the photos of this directory" : "Rechercher les personnes dans les photos de ce répertoire",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Les photos qui ne sont pas dans la galerie sont aussi ignorées",
|
||||||
|
"People" : "Personnes",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Voyez <a target=\"_blank\" href=\"{docsLink}\">la documentation ↗</a>",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Ouvrir <a target=\"_blank\" href=\"{settingsLink}\">paramètres ↗ </a> pour l’activer",
|
||||||
|
"Open Documentation" : "Documentation ouverte",
|
||||||
|
"Temporary files" : "Fichiers temporaires",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durant l’analyse, des fichiers temporaires sont utilisés pour assurer l’homogénéité entre toutes les images.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "De petites images permettent une analyse rapide, mais vous pouvez perdre les plus petits visages sur les photos. De grandes images peuvent améliorer les résultats, mais l’analyse sera plus lente. ",
|
||||||
|
"Smaller images" : "Images plus petites",
|
||||||
|
"Larger images" : "Images plus grandes",
|
||||||
|
"Restore" : "Restaurer ",
|
||||||
|
"Clustering threshold" : "Seuil de groupement",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Les personnes sont déterminées comme des groupes de visages similaires et pour les obtenir, tous les visages trouvés doivent être comparés. Lorsqu'ils sont comparés, un seuil est utilisé pour déterminer s'ils doivent être regroupés.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Un petit seuil ne regroupera que des visages très similaires, mais vous aurez initialement de nombreux groupes à nommer. Un seuil plus grand est plus flexible pour regrouper les visages, obtenant moins de groupes, mais peut confondre des personnes similaires.",
|
||||||
|
"Small threshold" : "Petit seuil",
|
||||||
|
"Higher threshold" : "Seuil plus élevé",
|
||||||
|
"Minimum confidence" : "Confiance minimale ",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "La confiance minimale détermine le degré de fiabilité d'une détection de visage pour tenter de le regrouper. Les visages flous ou mal alignés auraient un niveau de confiance plus proche de 0,0, et les meilleures images proches de 1,0. ",
|
||||||
|
"Lower minimum confidence" : "Confiance minimale plus faible",
|
||||||
|
"Higher minimum confidence" : "Confiance minimale plus haute",
|
||||||
|
"Configuration information" : "Information de configuration",
|
||||||
|
"Current model:" : "Modèle actuel :",
|
||||||
|
"Maximum memory assigned for image processing:" : "Mémoire maximale affectée au traitement d'image :",
|
||||||
|
"Current status" : "État actuel",
|
||||||
|
"Stopped" : "Arrêté"
|
||||||
|
},"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||||
|
}
|
||||||
89
facerecognition/l10n/it.js
Normal file
89
facerecognition/l10n/it.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "L'analisi è terminata",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n immagine analizzata","%n immagini analizzate","%n immagini analizzate"],
|
||||||
|
"Analyzing images" : "Analisi delle immagini in corso",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n immagine trovata","%n immagini trovate","%n immagini trovate"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n immagine in coda","%n immagini in coda","%n immagini in coda"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Tempo stimato residuo {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "L'analisi non è ancora iniziata",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Le modifiche sono state salvate. Verranno tenute in considerazione durante la prossima analisi.",
|
||||||
|
"The change could not be applied." : "Non è possibile applicare le modifiche.",
|
||||||
|
"Hide person" : "Nascondi Persona",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Potrai ancora vedere la persona tra le foto, ma assegnargli un nome solo per questa foto.",
|
||||||
|
"Cancel" : "Annulla",
|
||||||
|
"Hide" : "Nascondi",
|
||||||
|
"Rename person" : "Rinomina persona",
|
||||||
|
"Please enter a name to rename the person" : "Inserisci il nuovo nome della persona",
|
||||||
|
"Rename" : "Rinomina",
|
||||||
|
"This person is not {name}" : "Questa persona non è {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Puoi assegnare opzionalmente il nome corretto",
|
||||||
|
"Please assign a name to this person." : "Dai un nome a questa persona.",
|
||||||
|
"Save" : "Salva",
|
||||||
|
"Ignore" : "Ignora",
|
||||||
|
"There was an error trying to show your friends" : "C'è stato un errore cercando di mostrare i tuoi amici",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "L'analisi è stata abilitata, per favore si paziente, presto vedrai qui i tuoi amici.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "L'analisi è disabilitata. Le informazioni sul riconoscimento facciale verranno rimosse a breve.",
|
||||||
|
"There was an error renaming this person" : "C'è stato un errore nel rinominare la persona.",
|
||||||
|
"There was an error ignoring this person" : "C'è stato un errore nell'ignorare questa persona",
|
||||||
|
"Face Recognition" : "Riconoscimento Facciale",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Qui puoi vedere le foto riconosciute dei tuoi amici.",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analizza le mie immagini e raggruppa gli amici con facce simili",
|
||||||
|
"Looking for your recognized friends" : "Ricerca i tuoi amici riconosciuti",
|
||||||
|
"The analysis is disabled" : "L'analisi è disabilitata",
|
||||||
|
"Enable it to find your loved ones" : "Attiva per cercare i tuoi amici",
|
||||||
|
"Hide it" : "Nascondila",
|
||||||
|
"Your friends have not been recognized yet" : "I tuoi amici non sono ancora stati riconosciuti",
|
||||||
|
"Please, be patient" : "Per favore, sii paziente",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Perderai tutte le informazioni analizzate, se le riattivi, inizierà l'analisi da capo.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Vuoi disattivare il raggruppamento per facce?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "C'è stato un errore nel cercare le foto dei tuoi amici",
|
||||||
|
"An error occurred while hiding this person" : "C'è stato un errore nel nascondere questa persona",
|
||||||
|
"There was an error renaming this cluster of faces" : "C'è stato un errore rinominando questo cluster di facce.",
|
||||||
|
"An error occurred while hiding this group of faces" : "C'è stato un errore nel nascondere questi gruppi di facce",
|
||||||
|
"_%n image_::_%n images_" : ["%n immagine","%n immagini","%n immagini"],
|
||||||
|
"You must be administrator to configure this feature" : "Devi essere amministratore per configurare questa feature",
|
||||||
|
"The format seems to be incorrect." : "Il formato non sembra essere corretto.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Sembra che tu non abbia ancora impostato un modello di analisi",
|
||||||
|
"The minimum recommended area is %s" : "L'area minima consigliata è %s",
|
||||||
|
"The maximum recommended area is %s" : "L'area massima consigliata è %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Le aree più grandi di %s sono sconsigliate per questo modello",
|
||||||
|
"It seems you don't have any model installed." : "Sembra che tu non abbia modelli di analisi installati.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Sembra che tu non abbia ancora assegnato memoria per l'elaborazione immagini",
|
||||||
|
"Not installed" : "Non installata",
|
||||||
|
"Not configured." : "Non configurato.",
|
||||||
|
"A face recognition app" : "Un app per il riconoscimento facciale",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Identifica i volti dei tuoi amici nel tuo cloud**\n\n⚠️ L'applicazione richiede almeno 1GB di memoria RAM per funzionare! Controlla [i requisiti](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) per ulteriori dettagli.\n\n⚠️ La messa in opera di quest'app richiede l'accesso al terminale ed anche sporcasi le mani con l'installazione di software addizionale.\n Controlla l'[installazione](https://github.com/matiasdelellis/facerecognition/wiki/Installation) per ulteriori dettagli.\n\n- **😏 Identifica le facce nelle immagini:** Usa l'app FaceRecognition per trovare ogni faccia in ognuna delle tue immagini!\n- **👪 Raggruppa le facce per persona:** Le faccie rilevate sono raggruppate in base alle similarità così che l'app FaceRecognition può riconoscere quelle della stessa persona.\n- **🔒 Privacy integrata:** Nessuna immagine lascia il tuo cloud. Di base il riconoscimento è disabilitato ed ogni utente potrà abilitarlo\\disabilitarlo. Se necessario potranno essere escluse le immagini cartella per cartella.\n- **⚙️ Il potere dell'IA:** L'app FaceRecognition fa leva sull'intelligenza artificiale (e delle reti neurali integrate nei modelli di riconoscimento) attraverso l'uso intensivo della libreria [DLib](http://dlib.net/).\n- **🚀 Crea ciò che vuoi:** L'app FaceRecognition è solo un blocco di base. Attraverso le API di FaceRecognition, potrai costruire scenari avanzati - aggiungere in automatico tag alle immagini, connettere i tuoi contatti e persone, condividere le immagini di una particolare persona… Vogliamo sentire le tue idee!",
|
||||||
|
"Facial recognition is disabled" : "Il riconoscimento facciale è disabilitato",
|
||||||
|
"Facial recognition is disabled for this folder" : "Il riconoscimento facciale è disabilitato per questa cartella",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Questo tipo di archivio non è supportato per l'analisi delle immagini.",
|
||||||
|
"No people found" : "Nessuna persona trovata",
|
||||||
|
"This image is not yet analyzed" : "Questa immagine non è ancora stata analizzata",
|
||||||
|
"Search for persons in the photos of this directory" : "Ricerca le persone tra le foto in questa cartella.",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Anche le immagini che non sono nella galleria verranno ignorate.",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Controlla <a target=\"_blank\" href=\"{docsLink}\">la documentazione</a>",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Aprire<a target=\"_blank\" href=\"{settingsLink}\"> le impostazioni</a>per abilitarlo",
|
||||||
|
"Open Documentation" : "Apri la documentazione",
|
||||||
|
"Temporary files" : "File temporanei",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durante l'analisi i file temporanei sono usati per garantire omogeneità tra le immagini.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Immagini piccole permettono un analisi veloce, ma potresti perdere le faccie più piccole nelle tue foto. Immagini grande possono migliorare i risultati, ma l'analisi sarà più lenta.",
|
||||||
|
"Smaller images" : "Immagini più piccole",
|
||||||
|
"Larger images" : "Immagini più grandi",
|
||||||
|
"Restore" : "Ripristina",
|
||||||
|
"Clustering threshold" : "Soglia di clustering",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Le persone sono determinate come gruppo di facce simili e per ottenerle tutte le facce devono essere comparate. Quando sono comparate la soglia è utilizzata per determinare quando facce verranno raggruppate.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Una soglia piccola genererà solo gruppi di facce molti simili, ma inizialmente dovrai assegnare molti più nomi. Una soglia grande è più flessibile nel raggruppare le facce ottenendo quindi meno gruppi, ma potrebbero essere confuse le persone con facce simili.",
|
||||||
|
"Small threshold" : "Soglia più piccola",
|
||||||
|
"Higher threshold" : "Soglia più grande",
|
||||||
|
"Minimum confidence" : "Confidenza minima",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "La confidenza minima determina quanto precisa deve essere una faccia riconosciuta per essere raggruppata. Immagini sfuocate o non allineate hanno una confidenza più vicina a 0.0, le immagini migliori sono più vicine all' 1.0.",
|
||||||
|
"Lower minimum confidence" : "Confidenza minima più bassa",
|
||||||
|
"Higher minimum confidence" : "Confidenza minima più alta",
|
||||||
|
"Configuration information" : "Informazioni di configurazione",
|
||||||
|
"Current model:" : "Modello attuale:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Memoria massima associata all'elaborazione immagini:",
|
||||||
|
"Current status" : "Stato attuale",
|
||||||
|
"Stopped" : "Fermato"
|
||||||
|
},
|
||||||
|
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||||
87
facerecognition/l10n/it.json
Normal file
87
facerecognition/l10n/it.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "L'analisi è terminata",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n immagine analizzata","%n immagini analizzate","%n immagini analizzate"],
|
||||||
|
"Analyzing images" : "Analisi delle immagini in corso",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n immagine trovata","%n immagini trovate","%n immagini trovate"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n immagine in coda","%n immagini in coda","%n immagini in coda"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Tempo stimato residuo {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "L'analisi non è ancora iniziata",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Le modifiche sono state salvate. Verranno tenute in considerazione durante la prossima analisi.",
|
||||||
|
"The change could not be applied." : "Non è possibile applicare le modifiche.",
|
||||||
|
"Hide person" : "Nascondi Persona",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Potrai ancora vedere la persona tra le foto, ma assegnargli un nome solo per questa foto.",
|
||||||
|
"Cancel" : "Annulla",
|
||||||
|
"Hide" : "Nascondi",
|
||||||
|
"Rename person" : "Rinomina persona",
|
||||||
|
"Please enter a name to rename the person" : "Inserisci il nuovo nome della persona",
|
||||||
|
"Rename" : "Rinomina",
|
||||||
|
"This person is not {name}" : "Questa persona non è {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Puoi assegnare opzionalmente il nome corretto",
|
||||||
|
"Please assign a name to this person." : "Dai un nome a questa persona.",
|
||||||
|
"Save" : "Salva",
|
||||||
|
"Ignore" : "Ignora",
|
||||||
|
"There was an error trying to show your friends" : "C'è stato un errore cercando di mostrare i tuoi amici",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "L'analisi è stata abilitata, per favore si paziente, presto vedrai qui i tuoi amici.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "L'analisi è disabilitata. Le informazioni sul riconoscimento facciale verranno rimosse a breve.",
|
||||||
|
"There was an error renaming this person" : "C'è stato un errore nel rinominare la persona.",
|
||||||
|
"There was an error ignoring this person" : "C'è stato un errore nell'ignorare questa persona",
|
||||||
|
"Face Recognition" : "Riconoscimento Facciale",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Qui puoi vedere le foto riconosciute dei tuoi amici.",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analizza le mie immagini e raggruppa gli amici con facce simili",
|
||||||
|
"Looking for your recognized friends" : "Ricerca i tuoi amici riconosciuti",
|
||||||
|
"The analysis is disabled" : "L'analisi è disabilitata",
|
||||||
|
"Enable it to find your loved ones" : "Attiva per cercare i tuoi amici",
|
||||||
|
"Hide it" : "Nascondila",
|
||||||
|
"Your friends have not been recognized yet" : "I tuoi amici non sono ancora stati riconosciuti",
|
||||||
|
"Please, be patient" : "Per favore, sii paziente",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Perderai tutte le informazioni analizzate, se le riattivi, inizierà l'analisi da capo.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Vuoi disattivare il raggruppamento per facce?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "C'è stato un errore nel cercare le foto dei tuoi amici",
|
||||||
|
"An error occurred while hiding this person" : "C'è stato un errore nel nascondere questa persona",
|
||||||
|
"There was an error renaming this cluster of faces" : "C'è stato un errore rinominando questo cluster di facce.",
|
||||||
|
"An error occurred while hiding this group of faces" : "C'è stato un errore nel nascondere questi gruppi di facce",
|
||||||
|
"_%n image_::_%n images_" : ["%n immagine","%n immagini","%n immagini"],
|
||||||
|
"You must be administrator to configure this feature" : "Devi essere amministratore per configurare questa feature",
|
||||||
|
"The format seems to be incorrect." : "Il formato non sembra essere corretto.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Sembra che tu non abbia ancora impostato un modello di analisi",
|
||||||
|
"The minimum recommended area is %s" : "L'area minima consigliata è %s",
|
||||||
|
"The maximum recommended area is %s" : "L'area massima consigliata è %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Le aree più grandi di %s sono sconsigliate per questo modello",
|
||||||
|
"It seems you don't have any model installed." : "Sembra che tu non abbia modelli di analisi installati.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Sembra che tu non abbia ancora assegnato memoria per l'elaborazione immagini",
|
||||||
|
"Not installed" : "Non installata",
|
||||||
|
"Not configured." : "Non configurato.",
|
||||||
|
"A face recognition app" : "Un app per il riconoscimento facciale",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Identifica i volti dei tuoi amici nel tuo cloud**\n\n⚠️ L'applicazione richiede almeno 1GB di memoria RAM per funzionare! Controlla [i requisiti](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) per ulteriori dettagli.\n\n⚠️ La messa in opera di quest'app richiede l'accesso al terminale ed anche sporcasi le mani con l'installazione di software addizionale.\n Controlla l'[installazione](https://github.com/matiasdelellis/facerecognition/wiki/Installation) per ulteriori dettagli.\n\n- **😏 Identifica le facce nelle immagini:** Usa l'app FaceRecognition per trovare ogni faccia in ognuna delle tue immagini!\n- **👪 Raggruppa le facce per persona:** Le faccie rilevate sono raggruppate in base alle similarità così che l'app FaceRecognition può riconoscere quelle della stessa persona.\n- **🔒 Privacy integrata:** Nessuna immagine lascia il tuo cloud. Di base il riconoscimento è disabilitato ed ogni utente potrà abilitarlo\\disabilitarlo. Se necessario potranno essere escluse le immagini cartella per cartella.\n- **⚙️ Il potere dell'IA:** L'app FaceRecognition fa leva sull'intelligenza artificiale (e delle reti neurali integrate nei modelli di riconoscimento) attraverso l'uso intensivo della libreria [DLib](http://dlib.net/).\n- **🚀 Crea ciò che vuoi:** L'app FaceRecognition è solo un blocco di base. Attraverso le API di FaceRecognition, potrai costruire scenari avanzati - aggiungere in automatico tag alle immagini, connettere i tuoi contatti e persone, condividere le immagini di una particolare persona… Vogliamo sentire le tue idee!",
|
||||||
|
"Facial recognition is disabled" : "Il riconoscimento facciale è disabilitato",
|
||||||
|
"Facial recognition is disabled for this folder" : "Il riconoscimento facciale è disabilitato per questa cartella",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Questo tipo di archivio non è supportato per l'analisi delle immagini.",
|
||||||
|
"No people found" : "Nessuna persona trovata",
|
||||||
|
"This image is not yet analyzed" : "Questa immagine non è ancora stata analizzata",
|
||||||
|
"Search for persons in the photos of this directory" : "Ricerca le persone tra le foto in questa cartella.",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Anche le immagini che non sono nella galleria verranno ignorate.",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Controlla <a target=\"_blank\" href=\"{docsLink}\">la documentazione</a>",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Aprire<a target=\"_blank\" href=\"{settingsLink}\"> le impostazioni</a>per abilitarlo",
|
||||||
|
"Open Documentation" : "Apri la documentazione",
|
||||||
|
"Temporary files" : "File temporanei",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durante l'analisi i file temporanei sono usati per garantire omogeneità tra le immagini.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Immagini piccole permettono un analisi veloce, ma potresti perdere le faccie più piccole nelle tue foto. Immagini grande possono migliorare i risultati, ma l'analisi sarà più lenta.",
|
||||||
|
"Smaller images" : "Immagini più piccole",
|
||||||
|
"Larger images" : "Immagini più grandi",
|
||||||
|
"Restore" : "Ripristina",
|
||||||
|
"Clustering threshold" : "Soglia di clustering",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Le persone sono determinate come gruppo di facce simili e per ottenerle tutte le facce devono essere comparate. Quando sono comparate la soglia è utilizzata per determinare quando facce verranno raggruppate.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Una soglia piccola genererà solo gruppi di facce molti simili, ma inizialmente dovrai assegnare molti più nomi. Una soglia grande è più flessibile nel raggruppare le facce ottenendo quindi meno gruppi, ma potrebbero essere confuse le persone con facce simili.",
|
||||||
|
"Small threshold" : "Soglia più piccola",
|
||||||
|
"Higher threshold" : "Soglia più grande",
|
||||||
|
"Minimum confidence" : "Confidenza minima",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "La confidenza minima determina quanto precisa deve essere una faccia riconosciuta per essere raggruppata. Immagini sfuocate o non allineate hanno una confidenza più vicina a 0.0, le immagini migliori sono più vicine all' 1.0.",
|
||||||
|
"Lower minimum confidence" : "Confidenza minima più bassa",
|
||||||
|
"Higher minimum confidence" : "Confidenza minima più alta",
|
||||||
|
"Configuration information" : "Informazioni di configurazione",
|
||||||
|
"Current model:" : "Modello attuale:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Memoria massima associata all'elaborazione immagini:",
|
||||||
|
"Current status" : "Stato attuale",
|
||||||
|
"Stopped" : "Fermato"
|
||||||
|
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||||
|
}
|
||||||
89
facerecognition/l10n/it_IT.js
Normal file
89
facerecognition/l10n/it_IT.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "L'analisi è terminata",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n immagine analizzato","%n immagini analizzate","%n immagini analizzate"],
|
||||||
|
"Analyzing images" : "Analisi delle immagini in corso",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n immagine trovato","%n immagini trovate","%n immagini trovate"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n immagine in coda","%n immagini in coda","%n immagini in coda"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Tempo stimato residuo {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "L'analisi non è ancora iniziata",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Le modifiche sono state salvate. Verranno tenute in considerazione durante la prossima analisi.",
|
||||||
|
"The change could not be applied." : "Non è possibile applicare le modifiche.",
|
||||||
|
"Hide person" : "Nascondi Persona",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Potrai ancora vedere la persona tra le foto, ma assegnargli un nome solo per questa foto.",
|
||||||
|
"Cancel" : "Annulla",
|
||||||
|
"Hide" : "Nascondi",
|
||||||
|
"Rename person" : "Rinomina persona",
|
||||||
|
"Please enter a name to rename the person" : "Inserisci il nuovo nome della persona",
|
||||||
|
"Rename" : "Rinomina",
|
||||||
|
"This person is not {name}" : "Questa persona non è {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Puoi assegnare opzionalmente il nome corretto",
|
||||||
|
"Please assign a name to this person." : "Si prega di assegnare un nome a questa persona",
|
||||||
|
"Save" : "Salva",
|
||||||
|
"Ignore" : "Ignora",
|
||||||
|
"There was an error trying to show your friends" : "C'è stato un errore cercando di mostrare i suoi amici",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "L'analisi è stata abilitata, per favore si paziente, presto vedrà qui i suoi amici.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "L'analisi è disabilitata. Le informazioni sul riconoscimento facciale verranno rimosse a breve.",
|
||||||
|
"There was an error renaming this person" : "Si è verificato un errore nel rinominare la persona",
|
||||||
|
"There was an error ignoring this person" : "C'è stato un errore nell'ignorare questa persona",
|
||||||
|
"Face Recognition" : "Riconoscimento Facciale",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Qui può vedere le foto riconosciute dei suoi amici.",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analizza le mie immagini e raggruppa gli amici con facce simili",
|
||||||
|
"Looking for your recognized friends" : "Ricerca i suoi amici riconosciuti",
|
||||||
|
"The analysis is disabled" : "L'analisi è disabilitata",
|
||||||
|
"Enable it to find your loved ones" : "Attiva per cercare i suoi amici",
|
||||||
|
"Hide it" : "Nascondila",
|
||||||
|
"Your friends have not been recognized yet" : "I suoi amici non sono ancora stati riconosciuti",
|
||||||
|
"Please, be patient" : "Per favore, sii paziente",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Perderà tutte le informazioni analizzate, se lo riattiva, inizierà l'analisi da capo.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Vuole disattivare il raggruppamento per facce?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "C'è stato un errore nel cercare le foto dei suoi amici",
|
||||||
|
"An error occurred while hiding this person" : "Si è verificato un errore nel nascondere questa persona",
|
||||||
|
"There was an error renaming this cluster of faces" : "Si è verificato errore rinominando questo cluster di facce.",
|
||||||
|
"An error occurred while hiding this group of faces" : "Si è verificato un errore nel nascondere questi gruppi di facce",
|
||||||
|
"_%n image_::_%n images_" : ["%n immagine","%n immagini","%n immagini"],
|
||||||
|
"You must be administrator to configure this feature" : "Deve essere amministratore per configurare questa funzione",
|
||||||
|
"The format seems to be incorrect." : "Il formato non sembra essere corretto.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Sembra che lei non abbia ancora impostato un modello di analisi",
|
||||||
|
"The minimum recommended area is %s" : "L'area minima consigliata è %s",
|
||||||
|
"The maximum recommended area is %s" : "L'area massima consigliata è %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Le aree più grandi di %s sono sconsigliate per questo modello",
|
||||||
|
"It seems you don't have any model installed." : "Sembra che lei non ha modelli di analisi installati.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Sembra che tu debba ancora configurare la memoria assegnata per l'elaborazione delle immagini.",
|
||||||
|
"Not installed" : "Non installato",
|
||||||
|
"Not configured." : "Non configurato.",
|
||||||
|
"A face recognition app" : "Un'app per il riconoscimento facciale",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Identifica i volti dei suoi amici nella sua cloud**\n\n⚠️ L'applicazione richiede almeno 1GB di memoria RAM per funzionare! Controlla [i requisiti](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) per ulteriori dettagli.\n\n⚠️ La messa in opera di quest'app richiede l'accesso al terminale ed anche sporcasi le mani con l'installazione di software addizionale.\n Controlla l'[installazione](https://github.com/matiasdelellis/facerecognition/wiki/Installation) per ulteriori dettagli.\n\n- **😏 Identifica le facce nelle immagini:** Usa l'app FaceRecognition per trovare ogni faccia in ognuna delle sue immagini!\n- **👪 Raggruppa le facce per persona:** Le facce rilevate sono raggruppate in base alle similarità così che l'app FaceRecognition può riconoscere quelle della stessa persona.\n- **🔒 Privacy integrata:** Nessuna immagine lascia la sua cloud. Di base il riconoscimento è disabilitato e ogni utente potrà abilitarlo\\disabilitarlo. Se necessario potranno essere escluse le immagini cartella per cartella.\n- **⚙️ Il potere dell'IA:** L'app FaceRecognition fa leva sull'intelligenza artificiale (e delle reti neurali integrate nei modelli di riconoscimento) attraverso l'uso intensivo della libreria [DLib](http://dlib.net/).\n- **🚀 Crea ciò che vuole:** L'app FaceRecognition è solo un blocco di base. Attraverso le API di FaceRecognition, potrà costruire scenari avanzati - aggiungere in automatico tag alle immagini, connettere i suoi contatti e persone, condividere le immagini di una particolare persona… Vogliamo sentire le sue idee!",
|
||||||
|
"Facial recognition is disabled" : "Il riconoscimento facciale è disabilitato",
|
||||||
|
"Facial recognition is disabled for this folder" : "Il riconoscimento facciale è disabilitato per questa cartella",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Questo tipo di archivio non è supportato per l'analisi delle immagini.",
|
||||||
|
"No people found" : "Nessuna persona trovata",
|
||||||
|
"This image is not yet analyzed" : "Questa immagine non è ancora stata analizzata",
|
||||||
|
"Search for persons in the photos of this directory" : "Ricerca le persone tra le foto in questa cartella.",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Anche le immagini che non sono nella galleria verranno ignorate.",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Controlla <a target=\"_blank\" href=\"{docsLink}\">la documentazione</a>",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Apri <a target=\"_blank\" href=\"{settingsLink}\"> le impostazioni</a>per abilitarlo",
|
||||||
|
"Open Documentation" : "Apri la documentazione",
|
||||||
|
"Temporary files" : "File temporanei",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durante l'analisi i file temporanei sono usati per garantire omogeneità tra le immagini.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Immagini piccole permettono un analisi veloce, ma potresti perdere le facce più piccole nelle tue foto. Immagini grande possono migliorare i risultati, ma l'analisi sarà più lenta.",
|
||||||
|
"Smaller images" : "Immagini più piccole",
|
||||||
|
"Larger images" : "Immagini più grandi",
|
||||||
|
"Restore" : "Ripristina",
|
||||||
|
"Clustering threshold" : "Soglia di clustering",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Le persone sono determinate come gruppo di facce simili e per ottenerle tutte le facce devono essere comparate. Quando sono comparate la soglia è utilizzata per determinare quando facce verranno raggruppate.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Una soglia piccola genererà solo gruppi di facce molti simili, ma inizialmente dovrai assegnare molti più nomi. Una soglia grande è più flessibile nel raggruppare le facce ottenendo quindi meno gruppi, ma potrebbero essere confuse le persone con facce simili.",
|
||||||
|
"Small threshold" : "Soglia più piccola",
|
||||||
|
"Higher threshold" : "Soglia più grande",
|
||||||
|
"Minimum confidence" : "Confidenza minima",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "La confidenza minima determina quanto precisa deve essere una faccia riconosciuta per essere raggruppata. Immagini sfuocate o non allineate hanno una confidenza più vicina a 0.0, le immagini migliori sono più vicine all' 1.0.",
|
||||||
|
"Lower minimum confidence" : "Confidenza minima più bassa",
|
||||||
|
"Higher minimum confidence" : "Confidenza minima più alta",
|
||||||
|
"Configuration information" : "Informazioni di configurazione",
|
||||||
|
"Current model:" : "Modello attuale:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Memoria massima assegnata per l'elaborazione delle immagini:",
|
||||||
|
"Current status" : "Stato attuale",
|
||||||
|
"Stopped" : "Fermato"
|
||||||
|
},
|
||||||
|
"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||||
87
facerecognition/l10n/it_IT.json
Normal file
87
facerecognition/l10n/it_IT.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "L'analisi è terminata",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n immagine analizzato","%n immagini analizzate","%n immagini analizzate"],
|
||||||
|
"Analyzing images" : "Analisi delle immagini in corso",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n immagine trovato","%n immagini trovate","%n immagini trovate"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n immagine in coda","%n immagini in coda","%n immagini in coda"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Tempo stimato residuo {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "L'analisi non è ancora iniziata",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Le modifiche sono state salvate. Verranno tenute in considerazione durante la prossima analisi.",
|
||||||
|
"The change could not be applied." : "Non è possibile applicare le modifiche.",
|
||||||
|
"Hide person" : "Nascondi Persona",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Potrai ancora vedere la persona tra le foto, ma assegnargli un nome solo per questa foto.",
|
||||||
|
"Cancel" : "Annulla",
|
||||||
|
"Hide" : "Nascondi",
|
||||||
|
"Rename person" : "Rinomina persona",
|
||||||
|
"Please enter a name to rename the person" : "Inserisci il nuovo nome della persona",
|
||||||
|
"Rename" : "Rinomina",
|
||||||
|
"This person is not {name}" : "Questa persona non è {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Puoi assegnare opzionalmente il nome corretto",
|
||||||
|
"Please assign a name to this person." : "Si prega di assegnare un nome a questa persona",
|
||||||
|
"Save" : "Salva",
|
||||||
|
"Ignore" : "Ignora",
|
||||||
|
"There was an error trying to show your friends" : "C'è stato un errore cercando di mostrare i suoi amici",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "L'analisi è stata abilitata, per favore si paziente, presto vedrà qui i suoi amici.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "L'analisi è disabilitata. Le informazioni sul riconoscimento facciale verranno rimosse a breve.",
|
||||||
|
"There was an error renaming this person" : "Si è verificato un errore nel rinominare la persona",
|
||||||
|
"There was an error ignoring this person" : "C'è stato un errore nell'ignorare questa persona",
|
||||||
|
"Face Recognition" : "Riconoscimento Facciale",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Qui può vedere le foto riconosciute dei suoi amici.",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analizza le mie immagini e raggruppa gli amici con facce simili",
|
||||||
|
"Looking for your recognized friends" : "Ricerca i suoi amici riconosciuti",
|
||||||
|
"The analysis is disabled" : "L'analisi è disabilitata",
|
||||||
|
"Enable it to find your loved ones" : "Attiva per cercare i suoi amici",
|
||||||
|
"Hide it" : "Nascondila",
|
||||||
|
"Your friends have not been recognized yet" : "I suoi amici non sono ancora stati riconosciuti",
|
||||||
|
"Please, be patient" : "Per favore, sii paziente",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Perderà tutte le informazioni analizzate, se lo riattiva, inizierà l'analisi da capo.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Vuole disattivare il raggruppamento per facce?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "C'è stato un errore nel cercare le foto dei suoi amici",
|
||||||
|
"An error occurred while hiding this person" : "Si è verificato un errore nel nascondere questa persona",
|
||||||
|
"There was an error renaming this cluster of faces" : "Si è verificato errore rinominando questo cluster di facce.",
|
||||||
|
"An error occurred while hiding this group of faces" : "Si è verificato un errore nel nascondere questi gruppi di facce",
|
||||||
|
"_%n image_::_%n images_" : ["%n immagine","%n immagini","%n immagini"],
|
||||||
|
"You must be administrator to configure this feature" : "Deve essere amministratore per configurare questa funzione",
|
||||||
|
"The format seems to be incorrect." : "Il formato non sembra essere corretto.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Sembra che lei non abbia ancora impostato un modello di analisi",
|
||||||
|
"The minimum recommended area is %s" : "L'area minima consigliata è %s",
|
||||||
|
"The maximum recommended area is %s" : "L'area massima consigliata è %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Le aree più grandi di %s sono sconsigliate per questo modello",
|
||||||
|
"It seems you don't have any model installed." : "Sembra che lei non ha modelli di analisi installati.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Sembra che tu debba ancora configurare la memoria assegnata per l'elaborazione delle immagini.",
|
||||||
|
"Not installed" : "Non installato",
|
||||||
|
"Not configured." : "Non configurato.",
|
||||||
|
"A face recognition app" : "Un'app per il riconoscimento facciale",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Identifica i volti dei suoi amici nella sua cloud**\n\n⚠️ L'applicazione richiede almeno 1GB di memoria RAM per funzionare! Controlla [i requisiti](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) per ulteriori dettagli.\n\n⚠️ La messa in opera di quest'app richiede l'accesso al terminale ed anche sporcasi le mani con l'installazione di software addizionale.\n Controlla l'[installazione](https://github.com/matiasdelellis/facerecognition/wiki/Installation) per ulteriori dettagli.\n\n- **😏 Identifica le facce nelle immagini:** Usa l'app FaceRecognition per trovare ogni faccia in ognuna delle sue immagini!\n- **👪 Raggruppa le facce per persona:** Le facce rilevate sono raggruppate in base alle similarità così che l'app FaceRecognition può riconoscere quelle della stessa persona.\n- **🔒 Privacy integrata:** Nessuna immagine lascia la sua cloud. Di base il riconoscimento è disabilitato e ogni utente potrà abilitarlo\\disabilitarlo. Se necessario potranno essere escluse le immagini cartella per cartella.\n- **⚙️ Il potere dell'IA:** L'app FaceRecognition fa leva sull'intelligenza artificiale (e delle reti neurali integrate nei modelli di riconoscimento) attraverso l'uso intensivo della libreria [DLib](http://dlib.net/).\n- **🚀 Crea ciò che vuole:** L'app FaceRecognition è solo un blocco di base. Attraverso le API di FaceRecognition, potrà costruire scenari avanzati - aggiungere in automatico tag alle immagini, connettere i suoi contatti e persone, condividere le immagini di una particolare persona… Vogliamo sentire le sue idee!",
|
||||||
|
"Facial recognition is disabled" : "Il riconoscimento facciale è disabilitato",
|
||||||
|
"Facial recognition is disabled for this folder" : "Il riconoscimento facciale è disabilitato per questa cartella",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Questo tipo di archivio non è supportato per l'analisi delle immagini.",
|
||||||
|
"No people found" : "Nessuna persona trovata",
|
||||||
|
"This image is not yet analyzed" : "Questa immagine non è ancora stata analizzata",
|
||||||
|
"Search for persons in the photos of this directory" : "Ricerca le persone tra le foto in questa cartella.",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Anche le immagini che non sono nella galleria verranno ignorate.",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Controlla <a target=\"_blank\" href=\"{docsLink}\">la documentazione</a>",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Apri <a target=\"_blank\" href=\"{settingsLink}\"> le impostazioni</a>per abilitarlo",
|
||||||
|
"Open Documentation" : "Apri la documentazione",
|
||||||
|
"Temporary files" : "File temporanei",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durante l'analisi i file temporanei sono usati per garantire omogeneità tra le immagini.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Immagini piccole permettono un analisi veloce, ma potresti perdere le facce più piccole nelle tue foto. Immagini grande possono migliorare i risultati, ma l'analisi sarà più lenta.",
|
||||||
|
"Smaller images" : "Immagini più piccole",
|
||||||
|
"Larger images" : "Immagini più grandi",
|
||||||
|
"Restore" : "Ripristina",
|
||||||
|
"Clustering threshold" : "Soglia di clustering",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Le persone sono determinate come gruppo di facce simili e per ottenerle tutte le facce devono essere comparate. Quando sono comparate la soglia è utilizzata per determinare quando facce verranno raggruppate.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Una soglia piccola genererà solo gruppi di facce molti simili, ma inizialmente dovrai assegnare molti più nomi. Una soglia grande è più flessibile nel raggruppare le facce ottenendo quindi meno gruppi, ma potrebbero essere confuse le persone con facce simili.",
|
||||||
|
"Small threshold" : "Soglia più piccola",
|
||||||
|
"Higher threshold" : "Soglia più grande",
|
||||||
|
"Minimum confidence" : "Confidenza minima",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "La confidenza minima determina quanto precisa deve essere una faccia riconosciuta per essere raggruppata. Immagini sfuocate o non allineate hanno una confidenza più vicina a 0.0, le immagini migliori sono più vicine all' 1.0.",
|
||||||
|
"Lower minimum confidence" : "Confidenza minima più bassa",
|
||||||
|
"Higher minimum confidence" : "Confidenza minima più alta",
|
||||||
|
"Configuration information" : "Informazioni di configurazione",
|
||||||
|
"Current model:" : "Modello attuale:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Memoria massima assegnata per l'elaborazione delle immagini:",
|
||||||
|
"Current status" : "Stato attuale",
|
||||||
|
"Stopped" : "Fermato"
|
||||||
|
},"pluralForm" :"nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||||
|
}
|
||||||
65
facerecognition/l10n/ko_KR.js
Normal file
65
facerecognition/l10n/ko_KR.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "분석이 완로됨",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n개의 이미지가 분석되었습니다"],
|
||||||
|
"Analyzing images" : "이미지 분석중",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n개의 이미지가 감지됨"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n개의 이미지가 대기열에 있음"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "완료 예상: {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "분석이 아직 시작되지 않았습니다",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "변경점이 저장되었습니다. 이 변경점은 다음 분석때 적용됩니다.",
|
||||||
|
"The change could not be applied." : "변경점이 적용되지 않았습니다.",
|
||||||
|
"Cancel" : "취소",
|
||||||
|
"Rename person" : "이름변경",
|
||||||
|
"Please enter a name to rename the person" : "이 인물에 대해 변경할 이름을 입력하세요",
|
||||||
|
"Rename" : "이름변경",
|
||||||
|
"Please assign a name to this person." : "이 인물의 이름을 입력해주세요.",
|
||||||
|
"Save" : "저장",
|
||||||
|
"There was an error trying to show your friends" : "인물을 불러오는 중 오류가 발생했습니다",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "분석이 활성화되었습니다. 분석이 완료되면 여기에 표시됩니다.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "분석이 비활성화되었습니다. 지금까지 분석된 정보가 곧 삭제됩니다.",
|
||||||
|
"There was an error renaming this person" : "이 사람의 이름을 변경화는 중 오류가 발생했습니다",
|
||||||
|
"Face Recognition" : "얼굴인식",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "여기에서 인식된 사진을 볼 수 있습니다.",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "내 사진을 분석하고 비슷한 얼굴을 그룹화힙니다.",
|
||||||
|
"Looking for your recognized friends" : "인식된 얼굴을 불러오는 중 입니다",
|
||||||
|
"The analysis is disabled" : "분석이 비활성화되어있습니다",
|
||||||
|
"Enable it to find your loved ones" : "사랑하는 사람을 찾을 수 있도록 활성화",
|
||||||
|
"Your friends have not been recognized yet" : "사람이 아직 인식되지 않았습니다",
|
||||||
|
"Please, be patient" : "잠시만 기다려주세요",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "지금까지 분석된 정보를 잃게되며, 다시 활성화시 처음부터 분석을 시작합니다.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "정말로 얼굴인식을 비활성화하시겠습니까?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "사진을 찾는 중 오류가 발생했습니다",
|
||||||
|
"There was an error renaming this cluster of faces" : "이 얼굴 클러스터의 이름을 변경하는 중 오류가 발생했습니다",
|
||||||
|
"_%n image_::_%n images_" : ["%n장"],
|
||||||
|
"You must be administrator to configure this feature" : "이 기능을 변경하려면 관리자이어야합니다",
|
||||||
|
"The format seems to be incorrect." : "형식이 잘못된거같습니다",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "아직 분석 모델을 설정한거같지않습니다",
|
||||||
|
"The minimum recommended area is %s" : "최소 권장 범위는 %s입니다",
|
||||||
|
"The maximum recommended area is %s" : "최대 권장 범위는 %s입니다",
|
||||||
|
"The model does not recommend an area greater than %s" : "이 모델은 %s보다 큰 값을 권장하지 않습니다",
|
||||||
|
"It seems you don't have any model installed." : "아직 아무 모델도 설치되어있지 않은거 같습니다.",
|
||||||
|
"A face recognition app" : "얼굴인식 앱",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**클라우드에서 사랑하는 사람의 얼굴을 인식하고 그룹화**\n\n⚠️ 이 앱은 최소 1GB의 램을 요구합니다! 자세한 사항은 [요구사항](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations)을 확인하세요.\n\n⚠️ 이 앱을 설치하기 위해서는 터미널 액세스가 필요하고 추가 소프트웨어를 설치해야하며 손을 더럽힐 수 있습니다. 자세한 사항은 [설치](https://github.com/matiasdelellis/facerecognition/wiki/Installation)을 확인하세요.\n\n- **😏 내 사진에서 얼굴을 찾기:** FaceRecognition 앱을 이용해 당신의 _모든_ 사진에서 _모든_ 얼굴을 찾으세요!\n- **👪 얼굴에 대한 인물별 그룹화:** 감지된 얼굴은 유사도에 따라 그룹화되며, FaceRecognition 앱은 각각의 사람을 구별할 수 있습니다!\n- **🔒 개인정보보호 우선:** 어떠한 데이터도 클라우드 외부로 나가지 않습니다. 얼굴인식은 기본적으로 항상 꺼져있으며 각 사용자는 얼굴인식을 활성화/비활성화 할 수 있습니다. 필요한 경우 폴더들을 얼굴인식에서 제외할 수 있습니다.\n- **⚙️ AI의 힘으로:** FaceRecognition 앱은 [DLib](http://dlib.net/) 라이브러리를 이용한 AI의 힘과 미리 구축된 신경망 네트워크 모델을 이용합니다.\n- **🚀 자신의 것으로 만드세요:** FaceRecognition 앱은 기본적인 빌딩 블록일 뿐입니다. FaceRecognition API를 통해, 이미지에 자동으로 태그를 추가하고, 연락처와 사람을 연결하고, 특정 사람의 이미지를 공유와 같은 여러 고급 시나리오를 구축할 수 있습니다. 여려분의 아이디어를 듣고 싶습니다!",
|
||||||
|
"Facial recognition is disabled" : "얼굴인식이 비활성화되어있습니다",
|
||||||
|
"Facial recognition is disabled for this folder" : "이 폴더에 대한 얼굴인식 비활성화",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "이 유형의 저장소는 분석을 지원하지 않습니다",
|
||||||
|
"No people found" : "발견된 사람 없음",
|
||||||
|
"This image is not yet analyzed" : "이 이미지는 아직 분석되지 않았습니다",
|
||||||
|
"Search for persons in the photos of this directory" : "이 폴더의 사진에서 사람 검색",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "갤러리에 있지 않은 사진은 무시됩니다",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "<a target=\"_blank\" href=\"{docsLink}\">문서 ↗</a>보기.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "<a target=\"_blank\" href=\"{settingsLink}\">설정</a>을 열어 활성화하세요",
|
||||||
|
"Open Documentation" : "문서 열기",
|
||||||
|
"Temporary files" : "임시 파일",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "임시 파일은 분석중 모든 이미지 간의 동질성을 보장하기 위해 사용됩니다.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "작은 이미지는 빠른 분석을 가능케하지만 사진의 작은 얼굴은 무시될 수 있고, 큰 이미지는 결과를 향상시키지만 분석이 느려집니다.",
|
||||||
|
"Restore" : "복원",
|
||||||
|
"Minimum confidence" : "최소 신뢰도",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "최소 신뢰도는 그룹화를 하기 위해 얼굴을 얼마나 신뢰할 수 있는지를 결정합니다. 흐리거나 잘 보이지 않는 이미지의 얼굴은 0.0에 가까운 신뢰도를 가지지만 1.0에 가까이 갈 수록 최상의 이미지를 의미합니다.",
|
||||||
|
"Configuration information" : "현재 정보",
|
||||||
|
"Current status" : "현재 상태",
|
||||||
|
"Stopped" : "중지됨"
|
||||||
|
},
|
||||||
|
"nplurals=1; plural=0;");
|
||||||
63
facerecognition/l10n/ko_KR.json
Normal file
63
facerecognition/l10n/ko_KR.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "분석이 완로됨",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n개의 이미지가 분석되었습니다"],
|
||||||
|
"Analyzing images" : "이미지 분석중",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n개의 이미지가 감지됨"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n개의 이미지가 대기열에 있음"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "완료 예상: {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "분석이 아직 시작되지 않았습니다",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "변경점이 저장되었습니다. 이 변경점은 다음 분석때 적용됩니다.",
|
||||||
|
"The change could not be applied." : "변경점이 적용되지 않았습니다.",
|
||||||
|
"Cancel" : "취소",
|
||||||
|
"Rename person" : "이름변경",
|
||||||
|
"Please enter a name to rename the person" : "이 인물에 대해 변경할 이름을 입력하세요",
|
||||||
|
"Rename" : "이름변경",
|
||||||
|
"Please assign a name to this person." : "이 인물의 이름을 입력해주세요.",
|
||||||
|
"Save" : "저장",
|
||||||
|
"There was an error trying to show your friends" : "인물을 불러오는 중 오류가 발생했습니다",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "분석이 활성화되었습니다. 분석이 완료되면 여기에 표시됩니다.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "분석이 비활성화되었습니다. 지금까지 분석된 정보가 곧 삭제됩니다.",
|
||||||
|
"There was an error renaming this person" : "이 사람의 이름을 변경화는 중 오류가 발생했습니다",
|
||||||
|
"Face Recognition" : "얼굴인식",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "여기에서 인식된 사진을 볼 수 있습니다.",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "내 사진을 분석하고 비슷한 얼굴을 그룹화힙니다.",
|
||||||
|
"Looking for your recognized friends" : "인식된 얼굴을 불러오는 중 입니다",
|
||||||
|
"The analysis is disabled" : "분석이 비활성화되어있습니다",
|
||||||
|
"Enable it to find your loved ones" : "사랑하는 사람을 찾을 수 있도록 활성화",
|
||||||
|
"Your friends have not been recognized yet" : "사람이 아직 인식되지 않았습니다",
|
||||||
|
"Please, be patient" : "잠시만 기다려주세요",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "지금까지 분석된 정보를 잃게되며, 다시 활성화시 처음부터 분석을 시작합니다.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "정말로 얼굴인식을 비활성화하시겠습니까?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "사진을 찾는 중 오류가 발생했습니다",
|
||||||
|
"There was an error renaming this cluster of faces" : "이 얼굴 클러스터의 이름을 변경하는 중 오류가 발생했습니다",
|
||||||
|
"_%n image_::_%n images_" : ["%n장"],
|
||||||
|
"You must be administrator to configure this feature" : "이 기능을 변경하려면 관리자이어야합니다",
|
||||||
|
"The format seems to be incorrect." : "형식이 잘못된거같습니다",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "아직 분석 모델을 설정한거같지않습니다",
|
||||||
|
"The minimum recommended area is %s" : "최소 권장 범위는 %s입니다",
|
||||||
|
"The maximum recommended area is %s" : "최대 권장 범위는 %s입니다",
|
||||||
|
"The model does not recommend an area greater than %s" : "이 모델은 %s보다 큰 값을 권장하지 않습니다",
|
||||||
|
"It seems you don't have any model installed." : "아직 아무 모델도 설치되어있지 않은거 같습니다.",
|
||||||
|
"A face recognition app" : "얼굴인식 앱",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**클라우드에서 사랑하는 사람의 얼굴을 인식하고 그룹화**\n\n⚠️ 이 앱은 최소 1GB의 램을 요구합니다! 자세한 사항은 [요구사항](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations)을 확인하세요.\n\n⚠️ 이 앱을 설치하기 위해서는 터미널 액세스가 필요하고 추가 소프트웨어를 설치해야하며 손을 더럽힐 수 있습니다. 자세한 사항은 [설치](https://github.com/matiasdelellis/facerecognition/wiki/Installation)을 확인하세요.\n\n- **😏 내 사진에서 얼굴을 찾기:** FaceRecognition 앱을 이용해 당신의 _모든_ 사진에서 _모든_ 얼굴을 찾으세요!\n- **👪 얼굴에 대한 인물별 그룹화:** 감지된 얼굴은 유사도에 따라 그룹화되며, FaceRecognition 앱은 각각의 사람을 구별할 수 있습니다!\n- **🔒 개인정보보호 우선:** 어떠한 데이터도 클라우드 외부로 나가지 않습니다. 얼굴인식은 기본적으로 항상 꺼져있으며 각 사용자는 얼굴인식을 활성화/비활성화 할 수 있습니다. 필요한 경우 폴더들을 얼굴인식에서 제외할 수 있습니다.\n- **⚙️ AI의 힘으로:** FaceRecognition 앱은 [DLib](http://dlib.net/) 라이브러리를 이용한 AI의 힘과 미리 구축된 신경망 네트워크 모델을 이용합니다.\n- **🚀 자신의 것으로 만드세요:** FaceRecognition 앱은 기본적인 빌딩 블록일 뿐입니다. FaceRecognition API를 통해, 이미지에 자동으로 태그를 추가하고, 연락처와 사람을 연결하고, 특정 사람의 이미지를 공유와 같은 여러 고급 시나리오를 구축할 수 있습니다. 여려분의 아이디어를 듣고 싶습니다!",
|
||||||
|
"Facial recognition is disabled" : "얼굴인식이 비활성화되어있습니다",
|
||||||
|
"Facial recognition is disabled for this folder" : "이 폴더에 대한 얼굴인식 비활성화",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "이 유형의 저장소는 분석을 지원하지 않습니다",
|
||||||
|
"No people found" : "발견된 사람 없음",
|
||||||
|
"This image is not yet analyzed" : "이 이미지는 아직 분석되지 않았습니다",
|
||||||
|
"Search for persons in the photos of this directory" : "이 폴더의 사진에서 사람 검색",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "갤러리에 있지 않은 사진은 무시됩니다",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "<a target=\"_blank\" href=\"{docsLink}\">문서 ↗</a>보기.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "<a target=\"_blank\" href=\"{settingsLink}\">설정</a>을 열어 활성화하세요",
|
||||||
|
"Open Documentation" : "문서 열기",
|
||||||
|
"Temporary files" : "임시 파일",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "임시 파일은 분석중 모든 이미지 간의 동질성을 보장하기 위해 사용됩니다.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "작은 이미지는 빠른 분석을 가능케하지만 사진의 작은 얼굴은 무시될 수 있고, 큰 이미지는 결과를 향상시키지만 분석이 느려집니다.",
|
||||||
|
"Restore" : "복원",
|
||||||
|
"Minimum confidence" : "최소 신뢰도",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "최소 신뢰도는 그룹화를 하기 위해 얼굴을 얼마나 신뢰할 수 있는지를 결정합니다. 흐리거나 잘 보이지 않는 이미지의 얼굴은 0.0에 가까운 신뢰도를 가지지만 1.0에 가까이 갈 수록 최상의 이미지를 의미합니다.",
|
||||||
|
"Configuration information" : "현재 정보",
|
||||||
|
"Current status" : "현재 상태",
|
||||||
|
"Stopped" : "중지됨"
|
||||||
|
},"pluralForm" :"nplurals=1; plural=0;"
|
||||||
|
}
|
||||||
89
facerecognition/l10n/mk.js
Normal file
89
facerecognition/l10n/mk.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "Анализата е завршена",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["1 слика се анализира","%n слики се анализираат"],
|
||||||
|
"Analyzing images" : "Анализирање на слики",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["Детектирана е 1 слика","Детектирани се %n слики"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["1 слика е на чекање","%n слики се на чекање"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Приближно преостанато време {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Анализата не е започната",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Промените се зачувани. Ќе бидат имплементирани на следната анализа.",
|
||||||
|
"The change could not be applied." : "Промените неможат да се аплицираат.",
|
||||||
|
"Hide person" : "Сокриј го лицето",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Сеуште можете да го видите лицето на фотографиите, но доделувањето на име ќе биде само за таа фотографија.",
|
||||||
|
"Cancel" : "Откажи",
|
||||||
|
"Hide" : "Сокриј",
|
||||||
|
"Rename person" : "Преименувај го лицето",
|
||||||
|
"Please enter a name to rename the person" : "Внесете го новото име",
|
||||||
|
"Rename" : "Преимени",
|
||||||
|
"This person is not {name}" : "Оваа особа не е {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Опционално можете за напишете точно име",
|
||||||
|
"Please assign a name to this person." : "Наведете име на оваа личност.",
|
||||||
|
"Save" : "Зачувај",
|
||||||
|
"Ignore" : "Игнорирај",
|
||||||
|
"There was an error trying to show your friends" : "Настана грешка при обидот за прикажување на вашите пријатели",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Анализата е овозможена, ве молиме бидете трпеливи, наскоро ќе можете да ги видите вашите пријатели овде.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Анализата е оневозможена. Наскоро сите информации во врска со пронајдени ликови на фотографии ќе бидат избришани.",
|
||||||
|
"There was an error renaming this person" : "Настана грешка при обидот за преименување на ова лице",
|
||||||
|
"There was an error ignoring this person" : "Настана грешка при обидот за игнорирање на ова лице",
|
||||||
|
"Face Recognition" : "Препознавање на ликови",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Овде ќе можете да ги видите сите слики со пронајдени ликови",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Анализирај ги моите слики и групирај ги моите најблиски со слични лица",
|
||||||
|
"Looking for your recognized friends" : "Пребарување на вашите препознати пријатели",
|
||||||
|
"The analysis is disabled" : "Анализата е оневозможена",
|
||||||
|
"Enable it to find your loved ones" : "Овозможете го да ги најдете вашите најблиски",
|
||||||
|
"Hide it" : "Сокриј го",
|
||||||
|
"Your friends have not been recognized yet" : "Вашите пријатели сè уште не се препознати",
|
||||||
|
"Please, be patient" : "Бидете трпеливи",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Ќе ги изгубите сите анализирани информации и доколку го реактивирате, ќе започнете од нула.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Дали сакате да го деактивирате групирањето по лица?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Настана грешка при обидот за пребарување слики на вашите пријатели",
|
||||||
|
"An error occurred while hiding this person" : "Настана грешка при обидот за сокривање на ова лице",
|
||||||
|
"There was an error renaming this cluster of faces" : "Настана грешка при обидот за преименување на кластерот од лица",
|
||||||
|
"An error occurred while hiding this group of faces" : "Настана грешка при обидот за сокривање на оваа група на лица",
|
||||||
|
"_%n image_::_%n images_" : ["%n фотографија","%n фотографии"],
|
||||||
|
"You must be administrator to configure this feature" : "Вие мора да бидете администратор за да ја конфигурирате оваа можност",
|
||||||
|
"The format seems to be incorrect." : "Највероватно форматот е погрешен.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Највероватно немате избрано ниеден модел за анализа.",
|
||||||
|
"The minimum recommended area is %s" : "Минималната препорачана површина е %s",
|
||||||
|
"The maximum recommended area is %s" : "Максималната препорачана површина е %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Избраниот модел не препорачува површина поголема од %s",
|
||||||
|
"It seems you don't have any model installed." : "Сеуште намате инсталирано модел.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Се чини дека треба да ја конфигурирате доделената меморија за обработка на слики.",
|
||||||
|
"Not installed" : "Не е инсталирано",
|
||||||
|
"Not configured." : "Не е конфигурирано.",
|
||||||
|
"A face recognition app" : "Апликација за препознавање на ликови",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!",
|
||||||
|
"Facial recognition is disabled" : "Препознавањето на ликови е оневозможено",
|
||||||
|
"Facial recognition is disabled for this folder" : "Препознавањето на ликови е оневозможено за оваа папка",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Видот на складиштето не е поддржан за анализа на твојте слики",
|
||||||
|
"No people found" : "Не се пронајдени лица",
|
||||||
|
"This image is not yet analyzed" : "ОВаа слика сеуште не е анализирана",
|
||||||
|
"Search for persons in the photos of this directory" : "Пребарувај лица на сликите во оваа папка",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Сликите кој не се во галеријата исто така се игнорирани",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Види во <a target=\"_blank\" href=\"{docsLink}\">документацијата ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Отвори <a target=\"_blank\" href=\"{settingsLink}\">прилагодувања ↗</a> за да го овозможиш",
|
||||||
|
"Open Documentation" : "Отвори ја документацијата",
|
||||||
|
"Temporary files" : "Привремени датотеки",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "За време на анализата, привремените датотеки се користат за да се обезбеди хомогеност помеѓу сите слики.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Малите слики овозможуваат брза анализа, но можете да ги изгубите најмалите лица на вашите фотографии. Големите слики можат да ги подобрат резултатите, но анализата ќе биде побавна.",
|
||||||
|
"Smaller images" : "Помали слики",
|
||||||
|
"Larger images" : "Поголеми слики",
|
||||||
|
"Restore" : "Врати",
|
||||||
|
"Clustering threshold" : "Праг на групирање",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Лицата се определуваат како групи на слични лица и за да се определат, мора да се споредат сите пронајдени лица. Кога ќе се споредат, се користи праг за да се утврди дали треба да се групираат.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Мал праг само ќе групира многу слични лица, но првично ќе имате многу групи за именување. Поголемиот праг е пофлексибилен за групирање на лицата и за добивање помалку групи, но може да се помешаат слични лица.",
|
||||||
|
"Small threshold" : "Мал праг",
|
||||||
|
"Higher threshold" : "Голем праг",
|
||||||
|
"Minimum confidence" : "Минимална доверба",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Минималната самодоверба одредува колку ликот е сигурен за да го групира. Замаглените или ликовите од агол ќе имаат доверба поблизу до 0.0, а најдобрите слики близу до 1.0.",
|
||||||
|
"Lower minimum confidence" : "Помала минимална доверба",
|
||||||
|
"Higher minimum confidence" : "Поголема минимална доверба",
|
||||||
|
"Configuration information" : "Информации за конфигурацијата",
|
||||||
|
"Current model:" : "Моментален модел:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Максимална меморија доделена за обработка на слики:",
|
||||||
|
"Current status" : "Моментален статус:",
|
||||||
|
"Stopped" : "Стопиран"
|
||||||
|
},
|
||||||
|
"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;");
|
||||||
87
facerecognition/l10n/mk.json
Normal file
87
facerecognition/l10n/mk.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "Анализата е завршена",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["1 слика се анализира","%n слики се анализираат"],
|
||||||
|
"Analyzing images" : "Анализирање на слики",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["Детектирана е 1 слика","Детектирани се %n слики"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["1 слика е на чекање","%n слики се на чекање"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Приближно преостанато време {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Анализата не е започната",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Промените се зачувани. Ќе бидат имплементирани на следната анализа.",
|
||||||
|
"The change could not be applied." : "Промените неможат да се аплицираат.",
|
||||||
|
"Hide person" : "Сокриј го лицето",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Сеуште можете да го видите лицето на фотографиите, но доделувањето на име ќе биде само за таа фотографија.",
|
||||||
|
"Cancel" : "Откажи",
|
||||||
|
"Hide" : "Сокриј",
|
||||||
|
"Rename person" : "Преименувај го лицето",
|
||||||
|
"Please enter a name to rename the person" : "Внесете го новото име",
|
||||||
|
"Rename" : "Преимени",
|
||||||
|
"This person is not {name}" : "Оваа особа не е {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Опционално можете за напишете точно име",
|
||||||
|
"Please assign a name to this person." : "Наведете име на оваа личност.",
|
||||||
|
"Save" : "Зачувај",
|
||||||
|
"Ignore" : "Игнорирај",
|
||||||
|
"There was an error trying to show your friends" : "Настана грешка при обидот за прикажување на вашите пријатели",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Анализата е овозможена, ве молиме бидете трпеливи, наскоро ќе можете да ги видите вашите пријатели овде.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Анализата е оневозможена. Наскоро сите информации во врска со пронајдени ликови на фотографии ќе бидат избришани.",
|
||||||
|
"There was an error renaming this person" : "Настана грешка при обидот за преименување на ова лице",
|
||||||
|
"There was an error ignoring this person" : "Настана грешка при обидот за игнорирање на ова лице",
|
||||||
|
"Face Recognition" : "Препознавање на ликови",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Овде ќе можете да ги видите сите слики со пронајдени ликови",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Анализирај ги моите слики и групирај ги моите најблиски со слични лица",
|
||||||
|
"Looking for your recognized friends" : "Пребарување на вашите препознати пријатели",
|
||||||
|
"The analysis is disabled" : "Анализата е оневозможена",
|
||||||
|
"Enable it to find your loved ones" : "Овозможете го да ги најдете вашите најблиски",
|
||||||
|
"Hide it" : "Сокриј го",
|
||||||
|
"Your friends have not been recognized yet" : "Вашите пријатели сè уште не се препознати",
|
||||||
|
"Please, be patient" : "Бидете трпеливи",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Ќе ги изгубите сите анализирани информации и доколку го реактивирате, ќе започнете од нула.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Дали сакате да го деактивирате групирањето по лица?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Настана грешка при обидот за пребарување слики на вашите пријатели",
|
||||||
|
"An error occurred while hiding this person" : "Настана грешка при обидот за сокривање на ова лице",
|
||||||
|
"There was an error renaming this cluster of faces" : "Настана грешка при обидот за преименување на кластерот од лица",
|
||||||
|
"An error occurred while hiding this group of faces" : "Настана грешка при обидот за сокривање на оваа група на лица",
|
||||||
|
"_%n image_::_%n images_" : ["%n фотографија","%n фотографии"],
|
||||||
|
"You must be administrator to configure this feature" : "Вие мора да бидете администратор за да ја конфигурирате оваа можност",
|
||||||
|
"The format seems to be incorrect." : "Највероватно форматот е погрешен.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Највероватно немате избрано ниеден модел за анализа.",
|
||||||
|
"The minimum recommended area is %s" : "Минималната препорачана површина е %s",
|
||||||
|
"The maximum recommended area is %s" : "Максималната препорачана површина е %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Избраниот модел не препорачува површина поголема од %s",
|
||||||
|
"It seems you don't have any model installed." : "Сеуште намате инсталирано модел.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Се чини дека треба да ја конфигурирате доделената меморија за обработка на слики.",
|
||||||
|
"Not installed" : "Не е инсталирано",
|
||||||
|
"Not configured." : "Не е конфигурирано.",
|
||||||
|
"A face recognition app" : "Апликација за препознавање на ликови",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!",
|
||||||
|
"Facial recognition is disabled" : "Препознавањето на ликови е оневозможено",
|
||||||
|
"Facial recognition is disabled for this folder" : "Препознавањето на ликови е оневозможено за оваа папка",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Видот на складиштето не е поддржан за анализа на твојте слики",
|
||||||
|
"No people found" : "Не се пронајдени лица",
|
||||||
|
"This image is not yet analyzed" : "ОВаа слика сеуште не е анализирана",
|
||||||
|
"Search for persons in the photos of this directory" : "Пребарувај лица на сликите во оваа папка",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Сликите кој не се во галеријата исто така се игнорирани",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Види во <a target=\"_blank\" href=\"{docsLink}\">документацијата ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Отвори <a target=\"_blank\" href=\"{settingsLink}\">прилагодувања ↗</a> за да го овозможиш",
|
||||||
|
"Open Documentation" : "Отвори ја документацијата",
|
||||||
|
"Temporary files" : "Привремени датотеки",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "За време на анализата, привремените датотеки се користат за да се обезбеди хомогеност помеѓу сите слики.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Малите слики овозможуваат брза анализа, но можете да ги изгубите најмалите лица на вашите фотографии. Големите слики можат да ги подобрат резултатите, но анализата ќе биде побавна.",
|
||||||
|
"Smaller images" : "Помали слики",
|
||||||
|
"Larger images" : "Поголеми слики",
|
||||||
|
"Restore" : "Врати",
|
||||||
|
"Clustering threshold" : "Праг на групирање",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Лицата се определуваат како групи на слични лица и за да се определат, мора да се споредат сите пронајдени лица. Кога ќе се споредат, се користи праг за да се утврди дали треба да се групираат.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Мал праг само ќе групира многу слични лица, но првично ќе имате многу групи за именување. Поголемиот праг е пофлексибилен за групирање на лицата и за добивање помалку групи, но може да се помешаат слични лица.",
|
||||||
|
"Small threshold" : "Мал праг",
|
||||||
|
"Higher threshold" : "Голем праг",
|
||||||
|
"Minimum confidence" : "Минимална доверба",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Минималната самодоверба одредува колку ликот е сигурен за да го групира. Замаглените или ликовите од агол ќе имаат доверба поблизу до 0.0, а најдобрите слики близу до 1.0.",
|
||||||
|
"Lower minimum confidence" : "Помала минимална доверба",
|
||||||
|
"Higher minimum confidence" : "Поголема минимална доверба",
|
||||||
|
"Configuration information" : "Информации за конфигурацијата",
|
||||||
|
"Current model:" : "Моментален модел:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Максимална меморија доделена за обработка на слики:",
|
||||||
|
"Current status" : "Моментален статус:",
|
||||||
|
"Stopped" : "Стопиран"
|
||||||
|
},"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"
|
||||||
|
}
|
||||||
88
facerecognition/l10n/nl.js
Normal file
88
facerecognition/l10n/nl.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "De analyse is voltooid",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n afbeelding is geanalyseerd","%n afbeeldingen zijn geanalyseerd"],
|
||||||
|
"Analyzing images" : "Afbeeldingen aan het analyseren",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n afbeelding gedetecteerd","%n afbeeldingen getecteerd"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n afbeelding in de wachtrij","%n afbeeldingen in de wachtrij"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Klaar over ongeveer {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "De analyse is nog niet begonnen",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "De wijzigingen zijn opgeslagen en worden meegenomen in de volgende analyse.",
|
||||||
|
"The change could not be applied." : "De wijziging kon niet doorgevoerd worden.",
|
||||||
|
"Hide person" : "Verberg persoon",
|
||||||
|
"Cancel" : "Annuleren",
|
||||||
|
"Hide" : "Verbergen",
|
||||||
|
"Rename person" : "Persoon hernoemen",
|
||||||
|
"Please enter a name to rename the person" : "Vul een naam in voor deze persoon",
|
||||||
|
"Rename" : "Hernoemen",
|
||||||
|
"This person is not {name}" : "Deze persoon is niet {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Eventueel kan je de correcte naam toewijzen",
|
||||||
|
"Please assign a name to this person." : "Geef deze persoon een naam.",
|
||||||
|
"Save" : "Opslaan",
|
||||||
|
"Ignore" : "Negeren",
|
||||||
|
"There was an error trying to show your friends" : "Er is een fout opgetreden bij het ophalen van gezichten",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Gezichtsherkenning staat aan, je zal na een tijdje hier personen zien.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Gezichtsherkenning is uitgeschakeld. Alle informatie voor gezichtsherkenning wordt verwijderd.",
|
||||||
|
"There was an error renaming this person" : "Er is een fout opgetreden bij het hernoemen van deze persoon",
|
||||||
|
"There was an error ignoring this person" : "Er ging iets mis met het negeren van deze persoon",
|
||||||
|
"Face Recognition" : "Gezichtsherkenning",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Hier zie je foto's van herkende personen",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Zoek voor gezichten in mijn foto's en groepeer ze per persoon",
|
||||||
|
"Looking for your recognized friends" : "Herkende gezichten aan het opzoeken",
|
||||||
|
"The analysis is disabled" : "Gezichtsanalyse is uitgeschakeld",
|
||||||
|
"Enable it to find your loved ones" : "Zet het aan om vrienden en familie te vinden",
|
||||||
|
"Hide it" : "Verberg het",
|
||||||
|
"Your friends have not been recognized yet" : "Er zijn nog geen personen gevonden",
|
||||||
|
"Please, be patient" : "Het kan even duren, heb geduld",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Alle informatie zal verloren gaan en als je het weer aanzet moet je opnieuw beginnen.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Wil je gezichten groeperen uitzetten?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Fout bij het vinden van foto's",
|
||||||
|
"An error occurred while hiding this person" : "Er ging iets mis tijdens het verbergen van deze persoon",
|
||||||
|
"There was an error renaming this cluster of faces" : "Fout bij het hernoemen van gezichtencluster",
|
||||||
|
"An error occurred while hiding this group of faces" : "Er ging iets mis tijdens het verbergen van deze groep met gezichten",
|
||||||
|
"_%n image_::_%n images_" : ["%n afbeelding","%n afbeeldingen"],
|
||||||
|
"You must be administrator to configure this feature" : "Alleen beheerders kunnen dit instellen",
|
||||||
|
"The format seems to be incorrect." : "Het formaat is incorrect.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Je hebt nog geen analysemodel ingesteld",
|
||||||
|
"The minimum recommended area is %s" : "De aangerade minimumgrootte is %s",
|
||||||
|
"The maximum recommended area is %s" : "De aangerade maximumgrootte is %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Het model raad groottes boven %s af",
|
||||||
|
"It seems you don't have any model installed." : "Het lijkt erop dat je geen model geïnstalleerd hebt.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Het ziet er naar uit dat je het geheugenlimiet nog moet instellen voor gezichtsherkenning ",
|
||||||
|
"Not installed" : "Niet geïnstalleerd",
|
||||||
|
"Not configured." : "Niet geconfigureerd",
|
||||||
|
"A face recognition app" : "Een gezichtsherkenning app",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Detecteer en groepeer gezichten van vrienden en familie in jouw cloud**\n\n⚠️ Deze applicatie vereist een minimum van 1GB werkgeheugen! Lees [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) (Engels) voor meer informatie.\n\n⚠️ Het installeren van deze app vereist toegang tot een terminal en zelfs het installeren van extra software. Lees [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) (Engels) voor meer informatie.\n\n- **😏 Detecteer gezichten in afbeeldingen:** Gebruik de FaceRecognition app om elk gezicht in al je afbeeldingen te vinden!\n- **👪 Groeper gezichten tot personen:** Gevonden gezichten worden gegroepeerd gebaseerd op overeenkomsten.\n- **🔒 Privacy ingebouwd:** Geen data verlaat jouw cloud. Gezichtsherkenning staat standaard uit en elke gebruiker kan kiezen om het aan te zetten. Individuele mappen kunnen genegeerd worden.\n- **⚙️ Kracht van KI:** FaceRecognition app gebruikt de kracht van kunstmatige intelligentie en voorgebouwde neurale netwerken via de [DLib](http://dlib.net/) bibliotheek.\n- **🚀 Bouw je eigen dingen:** FaceRecognition app is een basaal bouwblok waarvan je de API kunt gebruiken om je eigen geavanceerde functionaliteit toe te voegen zoals het automatisch toevoegen van labels aan afbeldingen, contacten en personen verbinden en afbeeldingen van een specifiek persoon delen. Laat je ideëen horen!",
|
||||||
|
"Facial recognition is disabled" : "Gezichtsherkenning",
|
||||||
|
"Facial recognition is disabled for this folder" : "Gezichtsherkenning is uitgeschakeld voor deze map",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Dit type opslag wordt niet ondersteund voor gezichtsherkenning",
|
||||||
|
"No people found" : "Geen personen gevonden",
|
||||||
|
"This image is not yet analyzed" : "Deze afbeelding is nog niet geanalyseerd",
|
||||||
|
"Search for persons in the photos of this directory" : "Zoeken voor personen in foto's in deze map",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Foto's die niet in de gallerij staan worden ook genegeerd",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Raadpleeg de <a target=\"_blank\" href=\"{docsLink}\">handleiding ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Open <a target=\"_blank\" href=\"{settingsLink}\">instellingen ↗</a>om het aan te zetten",
|
||||||
|
"Open Documentation" : "Open Handleiding",
|
||||||
|
"Temporary files" : "Tijdelijke bestanden",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Tijdens de analyse worden tijdelijke bestanden gebruikt om gelijkwaardigheid tussen afbeeldingen te garanderen.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Kleine afbeeldingen maken de analyse sneller, maar zorgen ervoor dat kleine gezichten niet te herkennen zijn. Grote afbeeldingen zorgen voor betere resultaten maar zijn langzamer.",
|
||||||
|
"Smaller images" : "Kleinere afbeeldingen",
|
||||||
|
"Larger images" : "Grotere afbeeldingen",
|
||||||
|
"Restore" : "Herstellen",
|
||||||
|
"Clustering threshold" : "Groeperings drempelwaarde",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Personen worden gegroepeerd op basis van soort gelijken gezichten,hiervoor moeten alle gezichten met elkaar vergeleken worden. Wanneer ze worden vergeleken is een een drempelwaarde, die bepaald of ze bij elkaar gegroepeerd moeten worden.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Een lage drempelwaarde zal resulteren in een groep met heel erg op elkaar lijkende gezichten,hierdoor zal je veel groepen moeten benoemen. Een hoge drempelwaarde is meer flexibel een laat grotere verschillen door, hierdoor kunnen er personen door elkaar gehaald worden.",
|
||||||
|
"Small threshold" : "Lage drempelwaarde",
|
||||||
|
"Higher threshold" : "Hoge drempelwaarde",
|
||||||
|
"Minimum confidence" : "Minimum vetrouwen",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Het minimum betrouwbaarheid bepaalt hoe betrouwbaar een gezicht moet zijn om het te groeperen. Vervaagde of verkeerd uitgelijnde gezichten hebben een betrouwbaarheid richting 0.0, en de beste afbeeldingen richting 1.0.",
|
||||||
|
"Lower minimum confidence" : "Lager minimale zekerheid",
|
||||||
|
"Higher minimum confidence" : "Hoger minimale zekerheid",
|
||||||
|
"Configuration information" : "Configuratie informatie",
|
||||||
|
"Current model:" : "Huidig model:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Maximaal geheugen toegewezen aan foto analyse:",
|
||||||
|
"Current status" : "Huidige status",
|
||||||
|
"Stopped" : "Gestopt"
|
||||||
|
},
|
||||||
|
"nplurals=2; plural=(n != 1);");
|
||||||
86
facerecognition/l10n/nl.json
Normal file
86
facerecognition/l10n/nl.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "De analyse is voltooid",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n afbeelding is geanalyseerd","%n afbeeldingen zijn geanalyseerd"],
|
||||||
|
"Analyzing images" : "Afbeeldingen aan het analyseren",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n afbeelding gedetecteerd","%n afbeeldingen getecteerd"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n afbeelding in de wachtrij","%n afbeeldingen in de wachtrij"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Klaar over ongeveer {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "De analyse is nog niet begonnen",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "De wijzigingen zijn opgeslagen en worden meegenomen in de volgende analyse.",
|
||||||
|
"The change could not be applied." : "De wijziging kon niet doorgevoerd worden.",
|
||||||
|
"Hide person" : "Verberg persoon",
|
||||||
|
"Cancel" : "Annuleren",
|
||||||
|
"Hide" : "Verbergen",
|
||||||
|
"Rename person" : "Persoon hernoemen",
|
||||||
|
"Please enter a name to rename the person" : "Vul een naam in voor deze persoon",
|
||||||
|
"Rename" : "Hernoemen",
|
||||||
|
"This person is not {name}" : "Deze persoon is niet {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Eventueel kan je de correcte naam toewijzen",
|
||||||
|
"Please assign a name to this person." : "Geef deze persoon een naam.",
|
||||||
|
"Save" : "Opslaan",
|
||||||
|
"Ignore" : "Negeren",
|
||||||
|
"There was an error trying to show your friends" : "Er is een fout opgetreden bij het ophalen van gezichten",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Gezichtsherkenning staat aan, je zal na een tijdje hier personen zien.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Gezichtsherkenning is uitgeschakeld. Alle informatie voor gezichtsherkenning wordt verwijderd.",
|
||||||
|
"There was an error renaming this person" : "Er is een fout opgetreden bij het hernoemen van deze persoon",
|
||||||
|
"There was an error ignoring this person" : "Er ging iets mis met het negeren van deze persoon",
|
||||||
|
"Face Recognition" : "Gezichtsherkenning",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Hier zie je foto's van herkende personen",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Zoek voor gezichten in mijn foto's en groepeer ze per persoon",
|
||||||
|
"Looking for your recognized friends" : "Herkende gezichten aan het opzoeken",
|
||||||
|
"The analysis is disabled" : "Gezichtsanalyse is uitgeschakeld",
|
||||||
|
"Enable it to find your loved ones" : "Zet het aan om vrienden en familie te vinden",
|
||||||
|
"Hide it" : "Verberg het",
|
||||||
|
"Your friends have not been recognized yet" : "Er zijn nog geen personen gevonden",
|
||||||
|
"Please, be patient" : "Het kan even duren, heb geduld",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Alle informatie zal verloren gaan en als je het weer aanzet moet je opnieuw beginnen.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Wil je gezichten groeperen uitzetten?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Fout bij het vinden van foto's",
|
||||||
|
"An error occurred while hiding this person" : "Er ging iets mis tijdens het verbergen van deze persoon",
|
||||||
|
"There was an error renaming this cluster of faces" : "Fout bij het hernoemen van gezichtencluster",
|
||||||
|
"An error occurred while hiding this group of faces" : "Er ging iets mis tijdens het verbergen van deze groep met gezichten",
|
||||||
|
"_%n image_::_%n images_" : ["%n afbeelding","%n afbeeldingen"],
|
||||||
|
"You must be administrator to configure this feature" : "Alleen beheerders kunnen dit instellen",
|
||||||
|
"The format seems to be incorrect." : "Het formaat is incorrect.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Je hebt nog geen analysemodel ingesteld",
|
||||||
|
"The minimum recommended area is %s" : "De aangerade minimumgrootte is %s",
|
||||||
|
"The maximum recommended area is %s" : "De aangerade maximumgrootte is %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Het model raad groottes boven %s af",
|
||||||
|
"It seems you don't have any model installed." : "Het lijkt erop dat je geen model geïnstalleerd hebt.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Het ziet er naar uit dat je het geheugenlimiet nog moet instellen voor gezichtsherkenning ",
|
||||||
|
"Not installed" : "Niet geïnstalleerd",
|
||||||
|
"Not configured." : "Niet geconfigureerd",
|
||||||
|
"A face recognition app" : "Een gezichtsherkenning app",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Detecteer en groepeer gezichten van vrienden en familie in jouw cloud**\n\n⚠️ Deze applicatie vereist een minimum van 1GB werkgeheugen! Lees [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) (Engels) voor meer informatie.\n\n⚠️ Het installeren van deze app vereist toegang tot een terminal en zelfs het installeren van extra software. Lees [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) (Engels) voor meer informatie.\n\n- **😏 Detecteer gezichten in afbeeldingen:** Gebruik de FaceRecognition app om elk gezicht in al je afbeeldingen te vinden!\n- **👪 Groeper gezichten tot personen:** Gevonden gezichten worden gegroepeerd gebaseerd op overeenkomsten.\n- **🔒 Privacy ingebouwd:** Geen data verlaat jouw cloud. Gezichtsherkenning staat standaard uit en elke gebruiker kan kiezen om het aan te zetten. Individuele mappen kunnen genegeerd worden.\n- **⚙️ Kracht van KI:** FaceRecognition app gebruikt de kracht van kunstmatige intelligentie en voorgebouwde neurale netwerken via de [DLib](http://dlib.net/) bibliotheek.\n- **🚀 Bouw je eigen dingen:** FaceRecognition app is een basaal bouwblok waarvan je de API kunt gebruiken om je eigen geavanceerde functionaliteit toe te voegen zoals het automatisch toevoegen van labels aan afbeldingen, contacten en personen verbinden en afbeeldingen van een specifiek persoon delen. Laat je ideëen horen!",
|
||||||
|
"Facial recognition is disabled" : "Gezichtsherkenning",
|
||||||
|
"Facial recognition is disabled for this folder" : "Gezichtsherkenning is uitgeschakeld voor deze map",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Dit type opslag wordt niet ondersteund voor gezichtsherkenning",
|
||||||
|
"No people found" : "Geen personen gevonden",
|
||||||
|
"This image is not yet analyzed" : "Deze afbeelding is nog niet geanalyseerd",
|
||||||
|
"Search for persons in the photos of this directory" : "Zoeken voor personen in foto's in deze map",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Foto's die niet in de gallerij staan worden ook genegeerd",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Raadpleeg de <a target=\"_blank\" href=\"{docsLink}\">handleiding ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Open <a target=\"_blank\" href=\"{settingsLink}\">instellingen ↗</a>om het aan te zetten",
|
||||||
|
"Open Documentation" : "Open Handleiding",
|
||||||
|
"Temporary files" : "Tijdelijke bestanden",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Tijdens de analyse worden tijdelijke bestanden gebruikt om gelijkwaardigheid tussen afbeeldingen te garanderen.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Kleine afbeeldingen maken de analyse sneller, maar zorgen ervoor dat kleine gezichten niet te herkennen zijn. Grote afbeeldingen zorgen voor betere resultaten maar zijn langzamer.",
|
||||||
|
"Smaller images" : "Kleinere afbeeldingen",
|
||||||
|
"Larger images" : "Grotere afbeeldingen",
|
||||||
|
"Restore" : "Herstellen",
|
||||||
|
"Clustering threshold" : "Groeperings drempelwaarde",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Personen worden gegroepeerd op basis van soort gelijken gezichten,hiervoor moeten alle gezichten met elkaar vergeleken worden. Wanneer ze worden vergeleken is een een drempelwaarde, die bepaald of ze bij elkaar gegroepeerd moeten worden.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Een lage drempelwaarde zal resulteren in een groep met heel erg op elkaar lijkende gezichten,hierdoor zal je veel groepen moeten benoemen. Een hoge drempelwaarde is meer flexibel een laat grotere verschillen door, hierdoor kunnen er personen door elkaar gehaald worden.",
|
||||||
|
"Small threshold" : "Lage drempelwaarde",
|
||||||
|
"Higher threshold" : "Hoge drempelwaarde",
|
||||||
|
"Minimum confidence" : "Minimum vetrouwen",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Het minimum betrouwbaarheid bepaalt hoe betrouwbaar een gezicht moet zijn om het te groeperen. Vervaagde of verkeerd uitgelijnde gezichten hebben een betrouwbaarheid richting 0.0, en de beste afbeeldingen richting 1.0.",
|
||||||
|
"Lower minimum confidence" : "Lager minimale zekerheid",
|
||||||
|
"Higher minimum confidence" : "Hoger minimale zekerheid",
|
||||||
|
"Configuration information" : "Configuratie informatie",
|
||||||
|
"Current model:" : "Huidig model:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Maximaal geheugen toegewezen aan foto analyse:",
|
||||||
|
"Current status" : "Huidige status",
|
||||||
|
"Stopped" : "Gestopt"
|
||||||
|
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||||
|
}
|
||||||
102
facerecognition/l10n/pl.js
Normal file
102
facerecognition/l10n/pl.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "Analiza zakończona",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["Przeanalizowano %n obraz","Przeanalizowano %n obrazy","Przeanalizowano %n obrazów","Przeanalizowano %n obrazów"],
|
||||||
|
"Analyzing images" : "Analizuje obrazy",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["Wykryto %n obraz","Wykryto %n obrazy","Wykryto %n obrazów","Wykryto %n obrazów"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n obraz w kolejce","%n obrazy w kolejce","%n obrazów w kolejce","%n obrazów w kolejce"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Koniec za około {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Analiza jeszcze się nie rozpoczęła",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Zmiany zostały zapisane. Będą wzięte pod uwagę przy następnej analizie.",
|
||||||
|
"The change could not be applied." : "Zmiana nie mogła zostać zapisana.",
|
||||||
|
"Done" : "Zrobione",
|
||||||
|
"Hide person" : "Ukryj osobę",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Nadal możesz zobaczyć tę osobę na zdjęciach, ale przypisanie nazwy będzie dotyczyło tylko tego zdjęcia.",
|
||||||
|
"Cancel" : "Anuluj",
|
||||||
|
"Hide" : "Ukryj",
|
||||||
|
"Rename person" : "Zmień nazwę osoby",
|
||||||
|
"Please enter a name to rename the person" : "Wprowadź nazwę, aby zmienić nazwę osoby",
|
||||||
|
"Rename" : "Zmień nazwę",
|
||||||
|
"This person is not {name}" : "Ta osoba nie jest {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Opcjonalnie możesz przypisać poprawną nazwę",
|
||||||
|
"Please assign a name to this person." : "Przypisz nazwę tej osobie.",
|
||||||
|
"Save" : "Zapisz",
|
||||||
|
"Add name" : "Dodaj nazwę",
|
||||||
|
"Ignore" : "Ignoruj",
|
||||||
|
"Skip for now" : "Pomiń to na razie",
|
||||||
|
"There was an error trying to show your friends" : "Wystąpił błąd podczas próby pokazania znajomych",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Analiza jest włączona, proszę czekać, niedługo będzie można zobaczyć tu swoich znajomych.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Analiza jest wyłączona. Niedługo informacje o rozpoznawaniu twarzy zostaną usunięte.",
|
||||||
|
"There was an error renaming this person" : "Wystąpił błąd przy zmienianiu nazwy tej osoby",
|
||||||
|
"There was an error ignoring this person" : "Podczas ignorowania tej osoby wystąpił błąd",
|
||||||
|
"Face Recognition" : "Rozpoznawanie twarzy",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Tu możesz zobaczyć zdjęcia swoich znajomych które zostały rozpoznane.",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Przeanalizuj moje obrazy i pogrupuj moich bliskich z podobnymi twarzami.",
|
||||||
|
"Looking for your recognized friends" : "Szukam Twoich rozpoznanych znajomych",
|
||||||
|
"Review face groups" : "Przejrzyj grupy twarzy",
|
||||||
|
"The analysis is disabled" : "Analiza jest wyłączona",
|
||||||
|
"Enable it to find your loved ones" : "Włącz, aby znaleźć Twoich bliskich",
|
||||||
|
"Hide it" : "Ukryj to",
|
||||||
|
"Review people found" : "Przejrzyj znalezione osoby",
|
||||||
|
"Your friends have not been recognized yet" : "Twoi znajomi nie zostali jeszcze rozpoznani",
|
||||||
|
"Please, be patient" : "Proszę czekać",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Stracisz cala informacje o analizie i jeżeli włączysz to ponownie zaczniesz od zera.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Czy chciałbyś wyłączyć grupowanie według twarzy?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Wystąpił błąd podczas próby znalezienia zdjęć Twojego znajomego",
|
||||||
|
"An error occurred while hiding this person" : "Wystąpił błąd podczas ukrywania tej osoby",
|
||||||
|
"There was an error renaming this cluster of faces" : "Wystąpił błąd podczas zmiany nazwy grupy twarzy",
|
||||||
|
"An error occurred while hiding this group of faces" : "Podczas ukrywania tej grupy twarzy wystąpił błąd",
|
||||||
|
"_%n image_::_%n images_" : ["%n obraz","%n obrazy","%n obrazów","%n obrazów"],
|
||||||
|
"You must be administrator to configure this feature" : "Aby skonfigurować tę funkcję, musisz być administratorem",
|
||||||
|
"The format seems to be incorrect." : "Format jest niepoprawny.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Wygląda na to, że nie skonfigurowałeś jeszcze żadnego modelu analitycznego",
|
||||||
|
"The minimum recommended area is %s" : "Minimalny zalecany obszar to %s",
|
||||||
|
"The maximum recommended area is %s" : "Maksymalny zalecany obszar to %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Model nie zaleca obszaru większego niż %s",
|
||||||
|
"It seems you don't have any model installed." : "Wygląda na to, że nie masz zainstalowanego żadnego modelu.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Wydaje się, że nadal musisz skonfigurować przypisaną pamięć do przetwarzania obrazu.",
|
||||||
|
"Not installed" : "Nie zainstalowany",
|
||||||
|
"Not configured." : "Nie skonfigurowane.",
|
||||||
|
"A face recognition app" : "Aplikacja do rozpoznawania twarzy",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Wykrywaj i grupuj twarze ukochanej osoby w chmurze**\n\n⚠️ Aplikacja wymaga do działania minimum 1 GB pamięci RAM! Szczegółowe informacje można znaleźć w [Wymagania](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations).\n\n⚠️ Konfiguracja aplikacji wymaga dostępu do terminala, a nawet ubrudzenia rąk podczas instalacji dodatkowego oprogramowania. Szczegółowe informacje można znaleźć w sekcji [Instalacja](https://github.com/matiasdelellis/facerecognition/wiki/Installation).\n\n- **😏 Wykrywaj twarze na zdjęciach:** Użyj aplikacji FaceRecognition, aby wykryć _dowolną_ twarz na _dowolnym_ zdjęciu!\n- **👪 Grupuj twarze do osób:** Wykryte twarze są grupowane na podstawie podobieństwa, a aplikacja FaceRecognition może rozpoznawać te osoby!\n- **🔒 Wbudowana prywatność:** Żadne dane nie opuszczają Twojej chmury. Domyślnie są zawsze wyłączone, a każdy użytkownik kontroluje włączanie/wyłączanie wykrywania twarzy. W razie potrzeby obrazy z każdego katalogu można wykluczyć z wykrywania twarzy.\n- **⚙️ Moc sztucznej inteligencji:** aplikacja FaceRecognition wykorzystuje moc sztucznej inteligencji i już zbudowanych modeli sieci neuronowych dzięki szerokiemu wykorzystaniu biblioteki [DLib](http://dlib.net/)).\n- **🚀 Zbuduj własny obiekt:** aplikacja FaceRecognition to tylko podstawowy element konstrukcyjny. Dzięki FaceRecognition API możesz budować swoje zaawansowane scenariusze - automatycznie dodawać etykiety do zdjęć, łączyć kontakty i osoby, udostępniać zdjęcia od określonej osoby… Chcemy poznać Twoje pomysły!",
|
||||||
|
"See other photos" : "Zobacz inne zdjęcia",
|
||||||
|
"This photo will be separated from the person. If you rename it again, it will only be done on this photo. If you want to change the name of all the photos of this person, you must go to the image view and edit there." : "To zdjęcie zostanie oddzielone od osoby. Jeśli ponownie zmienisz nazwę, zostanie to zrobione tylko w przypadku tego zdjęcia. Jeśli chcesz zmienić nazwę wszystkich zdjęć tej osoby, musisz przejść do widoku obrazu i tam dokonać edycji.",
|
||||||
|
"Facial recognition is disabled" : "Rozpoznawanie twarzy wyłączone",
|
||||||
|
"Facial recognition is disabled for this folder" : "Rozpoznawanie twarzy jest wyłączone w tym katalogu",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Ten rodzaj pamięci nie jest obsługiwany do analizowania zdjęć",
|
||||||
|
"No people found" : "Nie znaleziono osób",
|
||||||
|
"This image is not yet analyzed" : "Obraz jeszcze nie przeanalizowany",
|
||||||
|
"Search for persons in the photos of this directory" : "Szukaj osób na zdjęciach w tym katalogu",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Zdjęcia, których nie ma w galerii, również są ignorowane",
|
||||||
|
"People" : "Ludzie",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Zobacz <a target=\"_blank\" href=\"{docsLink}\">dokumentację ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Otwórz <a target=\"_blank\" href=\"{settingsLink}\">ustawienia ↗</a>, aby je włączyć",
|
||||||
|
"Open Documentation" : "Otwarta dokumentacja",
|
||||||
|
"Temporary files" : "Pliki tymczasowe",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Podczas analizy używane są pliki tymczasowe, aby zapewnić jednorodność wszystkich obrazów.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Małe obrazy pozwalają na szybką analizę, ale możesz stracić najmniejsze twarze na swoich zdjęciach. Duże obrazy mogą poprawić wyniki, ale analiza będzie wolniejsza.",
|
||||||
|
"Smaller images" : "Mniejsze obrazy",
|
||||||
|
"Larger images" : "Większe obrazy",
|
||||||
|
"Restore" : "Odtwórz",
|
||||||
|
"Clustering threshold" : "Próg grupowania",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Osoby są określane jako grupy podobnych twarzy i aby je uzyskać, należy porównać wszystkie znalezione twarze. Podczas porównywania stosuje się próg, aby określić, czy należy je zgrupować.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Mały próg grupuje tylko bardzo podobne twarze, ale początkowo będziesz mieć wiele grup do nazwania. Większy próg umożliwia bardziej elastyczne grupowanie twarzy oraz uzyskiwanie mniejszej liczby grup, ale może mylić podobne osoby.",
|
||||||
|
"Small threshold" : "Mały próg",
|
||||||
|
"Higher threshold" : "Wyższy próg",
|
||||||
|
"Minimum confidence" : "Minimalna pewność",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Minimalna pewność determinuje, jak dokładnie twarz musi zostać rozpoznana żeby przydzielić ją do grupy. Rozmazane albo krzywo ustawione twarze będą miały pewność rozpoznania bliską 0.0 gdzie najlepsze zdjęcia będą miały pewność bliską 1.0.",
|
||||||
|
"Lower minimum confidence" : "Niższe minimalne zaufanie",
|
||||||
|
"Higher minimum confidence" : "Wyższe minimalne zaufanie",
|
||||||
|
"Minimum of faces in cluster" : "Minimalna liczba twarzy w klastrze",
|
||||||
|
"The minimum number of faces that a cluster must have to display it to the user." : "Minimalna liczba twarzy, jakie musi posiadać klaster, aby wyświetlić go użytkownikowi.",
|
||||||
|
"These faces clusters will not be shown as a suggestion, but can always be renamed eventually in the side panel." : "Te skupiska twarzy nie będą wyświetlane jako sugestia, ale zawsze można zmienić ich nazwę w panelu bocznym.",
|
||||||
|
"Less faces" : "Mniej twarzy",
|
||||||
|
"More faces" : "Więcej twarzy",
|
||||||
|
"Configuration information" : "Informacje o konfiguracji",
|
||||||
|
"Current model:" : "Aktualny model:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Maksymalna pamięć przypisana do przetwarzania obrazu:",
|
||||||
|
"Current status" : "Aktualny status",
|
||||||
|
"Stopped" : "Zatrzymane"
|
||||||
|
},
|
||||||
|
"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);");
|
||||||
100
facerecognition/l10n/pl.json
Normal file
100
facerecognition/l10n/pl.json
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "Analiza zakończona",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["Przeanalizowano %n obraz","Przeanalizowano %n obrazy","Przeanalizowano %n obrazów","Przeanalizowano %n obrazów"],
|
||||||
|
"Analyzing images" : "Analizuje obrazy",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["Wykryto %n obraz","Wykryto %n obrazy","Wykryto %n obrazów","Wykryto %n obrazów"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n obraz w kolejce","%n obrazy w kolejce","%n obrazów w kolejce","%n obrazów w kolejce"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Koniec za około {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Analiza jeszcze się nie rozpoczęła",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Zmiany zostały zapisane. Będą wzięte pod uwagę przy następnej analizie.",
|
||||||
|
"The change could not be applied." : "Zmiana nie mogła zostać zapisana.",
|
||||||
|
"Done" : "Zrobione",
|
||||||
|
"Hide person" : "Ukryj osobę",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Nadal możesz zobaczyć tę osobę na zdjęciach, ale przypisanie nazwy będzie dotyczyło tylko tego zdjęcia.",
|
||||||
|
"Cancel" : "Anuluj",
|
||||||
|
"Hide" : "Ukryj",
|
||||||
|
"Rename person" : "Zmień nazwę osoby",
|
||||||
|
"Please enter a name to rename the person" : "Wprowadź nazwę, aby zmienić nazwę osoby",
|
||||||
|
"Rename" : "Zmień nazwę",
|
||||||
|
"This person is not {name}" : "Ta osoba nie jest {name}",
|
||||||
|
"Optionally you can assign the correct name" : "Opcjonalnie możesz przypisać poprawną nazwę",
|
||||||
|
"Please assign a name to this person." : "Przypisz nazwę tej osobie.",
|
||||||
|
"Save" : "Zapisz",
|
||||||
|
"Add name" : "Dodaj nazwę",
|
||||||
|
"Ignore" : "Ignoruj",
|
||||||
|
"Skip for now" : "Pomiń to na razie",
|
||||||
|
"There was an error trying to show your friends" : "Wystąpił błąd podczas próby pokazania znajomych",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Analiza jest włączona, proszę czekać, niedługo będzie można zobaczyć tu swoich znajomych.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Analiza jest wyłączona. Niedługo informacje o rozpoznawaniu twarzy zostaną usunięte.",
|
||||||
|
"There was an error renaming this person" : "Wystąpił błąd przy zmienianiu nazwy tej osoby",
|
||||||
|
"There was an error ignoring this person" : "Podczas ignorowania tej osoby wystąpił błąd",
|
||||||
|
"Face Recognition" : "Rozpoznawanie twarzy",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Tu możesz zobaczyć zdjęcia swoich znajomych które zostały rozpoznane.",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Przeanalizuj moje obrazy i pogrupuj moich bliskich z podobnymi twarzami.",
|
||||||
|
"Looking for your recognized friends" : "Szukam Twoich rozpoznanych znajomych",
|
||||||
|
"Review face groups" : "Przejrzyj grupy twarzy",
|
||||||
|
"The analysis is disabled" : "Analiza jest wyłączona",
|
||||||
|
"Enable it to find your loved ones" : "Włącz, aby znaleźć Twoich bliskich",
|
||||||
|
"Hide it" : "Ukryj to",
|
||||||
|
"Review people found" : "Przejrzyj znalezione osoby",
|
||||||
|
"Your friends have not been recognized yet" : "Twoi znajomi nie zostali jeszcze rozpoznani",
|
||||||
|
"Please, be patient" : "Proszę czekać",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Stracisz cala informacje o analizie i jeżeli włączysz to ponownie zaczniesz od zera.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Czy chciałbyś wyłączyć grupowanie według twarzy?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Wystąpił błąd podczas próby znalezienia zdjęć Twojego znajomego",
|
||||||
|
"An error occurred while hiding this person" : "Wystąpił błąd podczas ukrywania tej osoby",
|
||||||
|
"There was an error renaming this cluster of faces" : "Wystąpił błąd podczas zmiany nazwy grupy twarzy",
|
||||||
|
"An error occurred while hiding this group of faces" : "Podczas ukrywania tej grupy twarzy wystąpił błąd",
|
||||||
|
"_%n image_::_%n images_" : ["%n obraz","%n obrazy","%n obrazów","%n obrazów"],
|
||||||
|
"You must be administrator to configure this feature" : "Aby skonfigurować tę funkcję, musisz być administratorem",
|
||||||
|
"The format seems to be incorrect." : "Format jest niepoprawny.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Wygląda na to, że nie skonfigurowałeś jeszcze żadnego modelu analitycznego",
|
||||||
|
"The minimum recommended area is %s" : "Minimalny zalecany obszar to %s",
|
||||||
|
"The maximum recommended area is %s" : "Maksymalny zalecany obszar to %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Model nie zaleca obszaru większego niż %s",
|
||||||
|
"It seems you don't have any model installed." : "Wygląda na to, że nie masz zainstalowanego żadnego modelu.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Wydaje się, że nadal musisz skonfigurować przypisaną pamięć do przetwarzania obrazu.",
|
||||||
|
"Not installed" : "Nie zainstalowany",
|
||||||
|
"Not configured." : "Nie skonfigurowane.",
|
||||||
|
"A face recognition app" : "Aplikacja do rozpoznawania twarzy",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Wykrywaj i grupuj twarze ukochanej osoby w chmurze**\n\n⚠️ Aplikacja wymaga do działania minimum 1 GB pamięci RAM! Szczegółowe informacje można znaleźć w [Wymagania](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations).\n\n⚠️ Konfiguracja aplikacji wymaga dostępu do terminala, a nawet ubrudzenia rąk podczas instalacji dodatkowego oprogramowania. Szczegółowe informacje można znaleźć w sekcji [Instalacja](https://github.com/matiasdelellis/facerecognition/wiki/Installation).\n\n- **😏 Wykrywaj twarze na zdjęciach:** Użyj aplikacji FaceRecognition, aby wykryć _dowolną_ twarz na _dowolnym_ zdjęciu!\n- **👪 Grupuj twarze do osób:** Wykryte twarze są grupowane na podstawie podobieństwa, a aplikacja FaceRecognition może rozpoznawać te osoby!\n- **🔒 Wbudowana prywatność:** Żadne dane nie opuszczają Twojej chmury. Domyślnie są zawsze wyłączone, a każdy użytkownik kontroluje włączanie/wyłączanie wykrywania twarzy. W razie potrzeby obrazy z każdego katalogu można wykluczyć z wykrywania twarzy.\n- **⚙️ Moc sztucznej inteligencji:** aplikacja FaceRecognition wykorzystuje moc sztucznej inteligencji i już zbudowanych modeli sieci neuronowych dzięki szerokiemu wykorzystaniu biblioteki [DLib](http://dlib.net/)).\n- **🚀 Zbuduj własny obiekt:** aplikacja FaceRecognition to tylko podstawowy element konstrukcyjny. Dzięki FaceRecognition API możesz budować swoje zaawansowane scenariusze - automatycznie dodawać etykiety do zdjęć, łączyć kontakty i osoby, udostępniać zdjęcia od określonej osoby… Chcemy poznać Twoje pomysły!",
|
||||||
|
"See other photos" : "Zobacz inne zdjęcia",
|
||||||
|
"This photo will be separated from the person. If you rename it again, it will only be done on this photo. If you want to change the name of all the photos of this person, you must go to the image view and edit there." : "To zdjęcie zostanie oddzielone od osoby. Jeśli ponownie zmienisz nazwę, zostanie to zrobione tylko w przypadku tego zdjęcia. Jeśli chcesz zmienić nazwę wszystkich zdjęć tej osoby, musisz przejść do widoku obrazu i tam dokonać edycji.",
|
||||||
|
"Facial recognition is disabled" : "Rozpoznawanie twarzy wyłączone",
|
||||||
|
"Facial recognition is disabled for this folder" : "Rozpoznawanie twarzy jest wyłączone w tym katalogu",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Ten rodzaj pamięci nie jest obsługiwany do analizowania zdjęć",
|
||||||
|
"No people found" : "Nie znaleziono osób",
|
||||||
|
"This image is not yet analyzed" : "Obraz jeszcze nie przeanalizowany",
|
||||||
|
"Search for persons in the photos of this directory" : "Szukaj osób na zdjęciach w tym katalogu",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Zdjęcia, których nie ma w galerii, również są ignorowane",
|
||||||
|
"People" : "Ludzie",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Zobacz <a target=\"_blank\" href=\"{docsLink}\">dokumentację ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Otwórz <a target=\"_blank\" href=\"{settingsLink}\">ustawienia ↗</a>, aby je włączyć",
|
||||||
|
"Open Documentation" : "Otwarta dokumentacja",
|
||||||
|
"Temporary files" : "Pliki tymczasowe",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Podczas analizy używane są pliki tymczasowe, aby zapewnić jednorodność wszystkich obrazów.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Małe obrazy pozwalają na szybką analizę, ale możesz stracić najmniejsze twarze na swoich zdjęciach. Duże obrazy mogą poprawić wyniki, ale analiza będzie wolniejsza.",
|
||||||
|
"Smaller images" : "Mniejsze obrazy",
|
||||||
|
"Larger images" : "Większe obrazy",
|
||||||
|
"Restore" : "Odtwórz",
|
||||||
|
"Clustering threshold" : "Próg grupowania",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Osoby są określane jako grupy podobnych twarzy i aby je uzyskać, należy porównać wszystkie znalezione twarze. Podczas porównywania stosuje się próg, aby określić, czy należy je zgrupować.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Mały próg grupuje tylko bardzo podobne twarze, ale początkowo będziesz mieć wiele grup do nazwania. Większy próg umożliwia bardziej elastyczne grupowanie twarzy oraz uzyskiwanie mniejszej liczby grup, ale może mylić podobne osoby.",
|
||||||
|
"Small threshold" : "Mały próg",
|
||||||
|
"Higher threshold" : "Wyższy próg",
|
||||||
|
"Minimum confidence" : "Minimalna pewność",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Minimalna pewność determinuje, jak dokładnie twarz musi zostać rozpoznana żeby przydzielić ją do grupy. Rozmazane albo krzywo ustawione twarze będą miały pewność rozpoznania bliską 0.0 gdzie najlepsze zdjęcia będą miały pewność bliską 1.0.",
|
||||||
|
"Lower minimum confidence" : "Niższe minimalne zaufanie",
|
||||||
|
"Higher minimum confidence" : "Wyższe minimalne zaufanie",
|
||||||
|
"Minimum of faces in cluster" : "Minimalna liczba twarzy w klastrze",
|
||||||
|
"The minimum number of faces that a cluster must have to display it to the user." : "Minimalna liczba twarzy, jakie musi posiadać klaster, aby wyświetlić go użytkownikowi.",
|
||||||
|
"These faces clusters will not be shown as a suggestion, but can always be renamed eventually in the side panel." : "Te skupiska twarzy nie będą wyświetlane jako sugestia, ale zawsze można zmienić ich nazwę w panelu bocznym.",
|
||||||
|
"Less faces" : "Mniej twarzy",
|
||||||
|
"More faces" : "Więcej twarzy",
|
||||||
|
"Configuration information" : "Informacje o konfiguracji",
|
||||||
|
"Current model:" : "Aktualny model:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Maksymalna pamięć przypisana do przetwarzania obrazu:",
|
||||||
|
"Current status" : "Aktualny status",
|
||||||
|
"Stopped" : "Zatrzymane"
|
||||||
|
},"pluralForm" :"nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);"
|
||||||
|
}
|
||||||
89
facerecognition/l10n/pt_BR.js
Normal file
89
facerecognition/l10n/pt_BR.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "A análise foi concluída.",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["imagem foi analisadas","%nimagens foram analisadas","%nimagens foram analisadas"],
|
||||||
|
"Analyzing images" : "Analisando imagens",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["imagem detectada","%n imagens detectadas","%n imagens detectadas"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["imagem na fila","%nimagens na fila","%nimagens na fila"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Termina aproximadamente em {estimadoFinalize}",
|
||||||
|
"The analysis is not started yet" : "A analise ainda não iniciou",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "As mudanças foram salvas. Elas terão efeito na conta na próxima analise.",
|
||||||
|
"The change could not be applied." : "As alterações não serão aplicadas.",
|
||||||
|
"Hide person" : "Ocultar pessoa",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Você ainda pode ver essa pessoa em Fotos, mas atribuir um nome será apenas para essa foto.",
|
||||||
|
"Cancel" : "Cancelar",
|
||||||
|
"Hide" : "Ocultar",
|
||||||
|
"Rename person" : "Renomear pessoa",
|
||||||
|
"Please enter a name to rename the person" : "Insira um nome para renomear a pessoa",
|
||||||
|
"Rename" : "Renomear",
|
||||||
|
"This person is not {name}" : "Esta pessoa não é {nome}",
|
||||||
|
"Optionally you can assign the correct name" : "Opcionalmente, você pode atribuir o nome correto",
|
||||||
|
"Please assign a name to this person." : "Atribua um nome a esta pessoa",
|
||||||
|
"Save" : "Salvar",
|
||||||
|
"Ignore" : "Ignorar",
|
||||||
|
"There was an error trying to show your friends" : "Ocorreu um erro ao tentar mostrar aos seus amigos",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "A análise está habilitada, por favor seja paciente, em breve você verá seus amigos aqui.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "A análise está desativada. Em breve todas as informações encontradas para reconhecimento facial serão removidas.",
|
||||||
|
"There was an error renaming this person" : "Ocorreu um erro renomeando essa pessoa",
|
||||||
|
"There was an error ignoring this person" : "Ocorreu um erro ao ignorar esta pessoa",
|
||||||
|
"Face Recognition" : "Reconhecimento Facial",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Aqui você pode ver fotos de seus amigos que serão reconhecidos",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analisar minhas imagens e agrupar meus entes queridos com rostos semelhantes",
|
||||||
|
"Looking for your recognized friends" : "Procurando por seus amigos reconhecidos",
|
||||||
|
"The analysis is disabled" : "A análise esta desabilitada",
|
||||||
|
"Enable it to find your loved ones" : "Ative-o para encontrar seus entes queridos",
|
||||||
|
"Hide it" : "Oculte",
|
||||||
|
"Your friends have not been recognized yet" : "Seus amigos ainda não foram reconhecidos",
|
||||||
|
"Please, be patient" : "Por favor, seja paciente",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Você perderá todas as informações analisadas e se reativá-la, começará do zero.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Quer desativar o agrupamento de faces?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Ocorreu um erro quando tentou descobrir fotos dos seus amigos",
|
||||||
|
"An error occurred while hiding this person" : "Ocorreu um erro ao ocultar esta pessoa",
|
||||||
|
"There was an error renaming this cluster of faces" : "Ocorreu um erro ao renomear este grupo de faces",
|
||||||
|
"An error occurred while hiding this group of faces" : "Ocorreu um erro ao ocultar este grupo de faces",
|
||||||
|
"_%n image_::_%n images_" : ["imagem","imagens","imagens"],
|
||||||
|
"You must be administrator to configure this feature" : "Você deve ser administrador para configurar este recurso",
|
||||||
|
"The format seems to be incorrect." : "O formato parece estar incorreto",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Parece que você ainda não configurou nenhum modelo de análise",
|
||||||
|
"The minimum recommended area is %s" : "A área mínima recomendada é %s",
|
||||||
|
"The maximum recommended area is %s" : "A área máxima recomendada é %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "O modelo não recomenda uma área maior que %s",
|
||||||
|
"It seems you don't have any model installed." : "Parece que você não tem nenhum modelo instalado.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Parece que você ainda precisa configurar a memória atribuída para processamento de imagem.",
|
||||||
|
"Not installed" : "Não instalado",
|
||||||
|
"Not configured." : "Não configurado.",
|
||||||
|
"A face recognition app" : "Um aplicativo de reconhecimento facial",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "** Detecte e agrupe faces de seus entes queridos em sua nuvem **\n\n⚠️ Este aplicativo requer no mínimo, 1 GB de memória RAM para funcionar! Consulte [Requisitos] (https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) para obter detalhes.\n\n⚠️ A configuração deste aplicativo requer acesso ao terminal e até mesmo conhecimentos para a instalação de software adicional. Veja [Instalação] (https://github.com/matiasdelellis/facerecognition/wiki/Installation) para detalhes.\n\n- **😏Detectar faces nas imagens: ** Use o aplicativo FaceRecognition para detectar qualquer face em qualquer uma das suas imagens!\n- ** 👪 Agrupe faces de pessoas: ** As faces detectadas são agrupadas com base na semelhança e, em seguida o aplicativo FaceRecognition pode reconhece-las !\n- ** 🔒 Privacidade integrada: ** Nenhum dado está deixando sua nuvem. Por padrão estão sempre desligados e cada usuário controla a habilitação / desabilitação da detecção de face. Imagens de todos os diretórios podem ser excluídas da reconhecimento facial, se necessário.\n- ** ⚙️ Power da IA: ** O app FaceRecognition aproveita o poder da IA e modelos de rede neural já construídos por meio do uso extensivo da biblioteca [DLib] (http://dlib.net/).\n- ** 🚀 Construa o que você quiser: ** O app FaceRecognition é apenas um bloco de construção básico. Através da API FaceRecognition, você pode construir seus cenários avançados - adicionar tags automaticamente às imagens, conectar contatos e pessoas, compartilhar imagens de pessoas específicas ... Queremos ouvir suas idéias!",
|
||||||
|
"Facial recognition is disabled" : "Reconhecimento facial esta desabilitado",
|
||||||
|
"Facial recognition is disabled for this folder" : "Reconhecimento facial esta desabilitado para este diretório",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "O tipo de armazenamento não é compatível para analisar suas fotos",
|
||||||
|
"No people found" : "Nenhuma pessoa encontrada",
|
||||||
|
"This image is not yet analyzed" : "Esta imagem ainda não foi analisada",
|
||||||
|
"Search for persons in the photos of this directory" : "Procurar por pessoas nas fotos deste diretório",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "As fotos que não estão na galeria também são ignoradas",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Veja a documentação<a target=\"_blank\" href=\"{docsLink}\"> ↗.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Abra as <a target=\"_blank\" href=\"{settingsLink}\">configurações ↗ para habilitá-lo",
|
||||||
|
"Open Documentation" : "Abrir Documentação",
|
||||||
|
"Temporary files" : "Arquivos temporários",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durante a análise, arquivos temporários são usados para garantir a homogeneidade entre todas as imagens.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Imagens pequenas permitem uma análise rápida, mas você pode perder as faces menores de suas fotos. Imagens grandes podem melhorar os resultados, mas a análise será mais lenta.",
|
||||||
|
"Smaller images" : "Imagens menores",
|
||||||
|
"Larger images" : "Imagens maiores",
|
||||||
|
"Restore" : "Restaurar",
|
||||||
|
"Clustering threshold" : "Limite de agrupamento",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "As pessoas são determinadas como grupos de faces semelhantes e para obtê-los é necessário comparar todos as faces encontradas. Quando elas são comparadas, um limite é usado para determinar se elas devem ser agrupadas.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Um limite menor agrupará apenas faces muito semelhantes, mas inicialmente você terá muitos grupos para nomear. Um limite maior é mais flexível para agrupar as faces e obter menos grupos, mas podendo confundir pessoas semelhantes.",
|
||||||
|
"Small threshold" : "Menor Limite",
|
||||||
|
"Higher threshold" : "Maior Limite",
|
||||||
|
"Minimum confidence" : "Confiança mínima",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "A confiança mínima determina o quão confiável deve ser uma detecção de face para tentar agrupá-la. Faces desfocadas ou desalinhadas teriam uma confiança próxima de 0,0, e as melhores imagens próximas a 1,0",
|
||||||
|
"Lower minimum confidence" : "Confiança mínima mais baixa",
|
||||||
|
"Higher minimum confidence" : "Maior confiança mínima",
|
||||||
|
"Configuration information" : "Informações de configuração",
|
||||||
|
"Current model:" : "Modelo atual:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Memória máxima atribuída para processamento de imagem:",
|
||||||
|
"Current status" : "Status atual",
|
||||||
|
"Stopped" : "Parado"
|
||||||
|
},
|
||||||
|
"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
|
||||||
87
facerecognition/l10n/pt_BR.json
Normal file
87
facerecognition/l10n/pt_BR.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "A análise foi concluída.",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["imagem foi analisadas","%nimagens foram analisadas","%nimagens foram analisadas"],
|
||||||
|
"Analyzing images" : "Analisando imagens",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["imagem detectada","%n imagens detectadas","%n imagens detectadas"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["imagem na fila","%nimagens na fila","%nimagens na fila"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Termina aproximadamente em {estimadoFinalize}",
|
||||||
|
"The analysis is not started yet" : "A analise ainda não iniciou",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "As mudanças foram salvas. Elas terão efeito na conta na próxima analise.",
|
||||||
|
"The change could not be applied." : "As alterações não serão aplicadas.",
|
||||||
|
"Hide person" : "Ocultar pessoa",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Você ainda pode ver essa pessoa em Fotos, mas atribuir um nome será apenas para essa foto.",
|
||||||
|
"Cancel" : "Cancelar",
|
||||||
|
"Hide" : "Ocultar",
|
||||||
|
"Rename person" : "Renomear pessoa",
|
||||||
|
"Please enter a name to rename the person" : "Insira um nome para renomear a pessoa",
|
||||||
|
"Rename" : "Renomear",
|
||||||
|
"This person is not {name}" : "Esta pessoa não é {nome}",
|
||||||
|
"Optionally you can assign the correct name" : "Opcionalmente, você pode atribuir o nome correto",
|
||||||
|
"Please assign a name to this person." : "Atribua um nome a esta pessoa",
|
||||||
|
"Save" : "Salvar",
|
||||||
|
"Ignore" : "Ignorar",
|
||||||
|
"There was an error trying to show your friends" : "Ocorreu um erro ao tentar mostrar aos seus amigos",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "A análise está habilitada, por favor seja paciente, em breve você verá seus amigos aqui.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "A análise está desativada. Em breve todas as informações encontradas para reconhecimento facial serão removidas.",
|
||||||
|
"There was an error renaming this person" : "Ocorreu um erro renomeando essa pessoa",
|
||||||
|
"There was an error ignoring this person" : "Ocorreu um erro ao ignorar esta pessoa",
|
||||||
|
"Face Recognition" : "Reconhecimento Facial",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Aqui você pode ver fotos de seus amigos que serão reconhecidos",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Analisar minhas imagens e agrupar meus entes queridos com rostos semelhantes",
|
||||||
|
"Looking for your recognized friends" : "Procurando por seus amigos reconhecidos",
|
||||||
|
"The analysis is disabled" : "A análise esta desabilitada",
|
||||||
|
"Enable it to find your loved ones" : "Ative-o para encontrar seus entes queridos",
|
||||||
|
"Hide it" : "Oculte",
|
||||||
|
"Your friends have not been recognized yet" : "Seus amigos ainda não foram reconhecidos",
|
||||||
|
"Please, be patient" : "Por favor, seja paciente",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Você perderá todas as informações analisadas e se reativá-la, começará do zero.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Quer desativar o agrupamento de faces?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Ocorreu um erro quando tentou descobrir fotos dos seus amigos",
|
||||||
|
"An error occurred while hiding this person" : "Ocorreu um erro ao ocultar esta pessoa",
|
||||||
|
"There was an error renaming this cluster of faces" : "Ocorreu um erro ao renomear este grupo de faces",
|
||||||
|
"An error occurred while hiding this group of faces" : "Ocorreu um erro ao ocultar este grupo de faces",
|
||||||
|
"_%n image_::_%n images_" : ["imagem","imagens","imagens"],
|
||||||
|
"You must be administrator to configure this feature" : "Você deve ser administrador para configurar este recurso",
|
||||||
|
"The format seems to be incorrect." : "O formato parece estar incorreto",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Parece que você ainda não configurou nenhum modelo de análise",
|
||||||
|
"The minimum recommended area is %s" : "A área mínima recomendada é %s",
|
||||||
|
"The maximum recommended area is %s" : "A área máxima recomendada é %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "O modelo não recomenda uma área maior que %s",
|
||||||
|
"It seems you don't have any model installed." : "Parece que você não tem nenhum modelo instalado.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Parece que você ainda precisa configurar a memória atribuída para processamento de imagem.",
|
||||||
|
"Not installed" : "Não instalado",
|
||||||
|
"Not configured." : "Não configurado.",
|
||||||
|
"A face recognition app" : "Um aplicativo de reconhecimento facial",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "** Detecte e agrupe faces de seus entes queridos em sua nuvem **\n\n⚠️ Este aplicativo requer no mínimo, 1 GB de memória RAM para funcionar! Consulte [Requisitos] (https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) para obter detalhes.\n\n⚠️ A configuração deste aplicativo requer acesso ao terminal e até mesmo conhecimentos para a instalação de software adicional. Veja [Instalação] (https://github.com/matiasdelellis/facerecognition/wiki/Installation) para detalhes.\n\n- **😏Detectar faces nas imagens: ** Use o aplicativo FaceRecognition para detectar qualquer face em qualquer uma das suas imagens!\n- ** 👪 Agrupe faces de pessoas: ** As faces detectadas são agrupadas com base na semelhança e, em seguida o aplicativo FaceRecognition pode reconhece-las !\n- ** 🔒 Privacidade integrada: ** Nenhum dado está deixando sua nuvem. Por padrão estão sempre desligados e cada usuário controla a habilitação / desabilitação da detecção de face. Imagens de todos os diretórios podem ser excluídas da reconhecimento facial, se necessário.\n- ** ⚙️ Power da IA: ** O app FaceRecognition aproveita o poder da IA e modelos de rede neural já construídos por meio do uso extensivo da biblioteca [DLib] (http://dlib.net/).\n- ** 🚀 Construa o que você quiser: ** O app FaceRecognition é apenas um bloco de construção básico. Através da API FaceRecognition, você pode construir seus cenários avançados - adicionar tags automaticamente às imagens, conectar contatos e pessoas, compartilhar imagens de pessoas específicas ... Queremos ouvir suas idéias!",
|
||||||
|
"Facial recognition is disabled" : "Reconhecimento facial esta desabilitado",
|
||||||
|
"Facial recognition is disabled for this folder" : "Reconhecimento facial esta desabilitado para este diretório",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "O tipo de armazenamento não é compatível para analisar suas fotos",
|
||||||
|
"No people found" : "Nenhuma pessoa encontrada",
|
||||||
|
"This image is not yet analyzed" : "Esta imagem ainda não foi analisada",
|
||||||
|
"Search for persons in the photos of this directory" : "Procurar por pessoas nas fotos deste diretório",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "As fotos que não estão na galeria também são ignoradas",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Veja a documentação<a target=\"_blank\" href=\"{docsLink}\"> ↗.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Abra as <a target=\"_blank\" href=\"{settingsLink}\">configurações ↗ para habilitá-lo",
|
||||||
|
"Open Documentation" : "Abrir Documentação",
|
||||||
|
"Temporary files" : "Arquivos temporários",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Durante a análise, arquivos temporários são usados para garantir a homogeneidade entre todas as imagens.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Imagens pequenas permitem uma análise rápida, mas você pode perder as faces menores de suas fotos. Imagens grandes podem melhorar os resultados, mas a análise será mais lenta.",
|
||||||
|
"Smaller images" : "Imagens menores",
|
||||||
|
"Larger images" : "Imagens maiores",
|
||||||
|
"Restore" : "Restaurar",
|
||||||
|
"Clustering threshold" : "Limite de agrupamento",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "As pessoas são determinadas como grupos de faces semelhantes e para obtê-los é necessário comparar todos as faces encontradas. Quando elas são comparadas, um limite é usado para determinar se elas devem ser agrupadas.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Um limite menor agrupará apenas faces muito semelhantes, mas inicialmente você terá muitos grupos para nomear. Um limite maior é mais flexível para agrupar as faces e obter menos grupos, mas podendo confundir pessoas semelhantes.",
|
||||||
|
"Small threshold" : "Menor Limite",
|
||||||
|
"Higher threshold" : "Maior Limite",
|
||||||
|
"Minimum confidence" : "Confiança mínima",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "A confiança mínima determina o quão confiável deve ser uma detecção de face para tentar agrupá-la. Faces desfocadas ou desalinhadas teriam uma confiança próxima de 0,0, e as melhores imagens próximas a 1,0",
|
||||||
|
"Lower minimum confidence" : "Confiança mínima mais baixa",
|
||||||
|
"Higher minimum confidence" : "Maior confiança mínima",
|
||||||
|
"Configuration information" : "Informações de configuração",
|
||||||
|
"Current model:" : "Modelo atual:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Memória máxima atribuída para processamento de imagem:",
|
||||||
|
"Current status" : "Status atual",
|
||||||
|
"Stopped" : "Parado"
|
||||||
|
},"pluralForm" :"nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
|
||||||
|
}
|
||||||
95
facerecognition/l10n/ru.js
Normal file
95
facerecognition/l10n/ru.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "Анализ завершён",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n изображение проанализировано","%n изображений проанализировано","%n изображений проанализировано","%n изображений проанализировано"],
|
||||||
|
"Analyzing images" : "Анализ изображений",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n изображение обнаружено","%n изображений проанализировано","%n изображений обнаружено","%n изображений обнаружено"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n изображение в очереди","%n изображений в очереди","%n изображений в очереди","%n изображений в очереди"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Окончание приблизительно {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Анализ еще не начат",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Изменения сохранены. Они будут учтены в следующем анализе.",
|
||||||
|
"The change could not be applied." : "Это изменение не может быть применено.",
|
||||||
|
"Hide person" : "Скрыть человека",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Вы все еще можете увидеть этого человека на фотографиях, но назначение имени будет только для этой фотографии.",
|
||||||
|
"Cancel" : "Отмена",
|
||||||
|
"Hide" : "Скрыть",
|
||||||
|
"Rename person" : "Переименовать человека",
|
||||||
|
"Please enter a name to rename the person" : "Пожалуйста, введите имя, чтобы переименовать человека",
|
||||||
|
"Rename" : "Переименовать ",
|
||||||
|
"This person is not {name}" : "Этот человек не {name}",
|
||||||
|
"Optionally you can assign the correct name" : "При желании можно назначить правильное имя",
|
||||||
|
"Please assign a name to this person." : "Пожалуйста, назначьте имя этому человеку.",
|
||||||
|
"Save" : "Сохранить",
|
||||||
|
"Add name" : "Добавить имя",
|
||||||
|
"Ignore" : "Игнорировать",
|
||||||
|
"Skip for now" : "Пропустить пока",
|
||||||
|
"There was an error trying to show your friends" : "Произошла ошибка при попытке показать друзьям",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Анализ включен, пожалуйста, будьте терпеливы, вы скоро увидите ваших друзей здесь.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Анализ отключен. Скоро вся информация, найденная для распознавания лиц, будет удалена.",
|
||||||
|
"There was an error renaming this person" : "Произошла ошибка при переименовании этого человека",
|
||||||
|
"There was an error ignoring this person" : "Произошла ошибка при игнорировании человека",
|
||||||
|
"Face Recognition" : "Распознавание лиц",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Здесь вы можете увидеть фотографии ваших друзей, которые были распознаны",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Проанализировать мои изображения и группы моих близких с похожими лицами",
|
||||||
|
"Looking for your recognized friends" : "Ищем Ваших признанных друзей",
|
||||||
|
"Review face groups" : "Просмотр групп лиц",
|
||||||
|
"The analysis is disabled" : "Анализ отключен",
|
||||||
|
"Enable it to find your loved ones" : "Включите чтобы найти ваших близких",
|
||||||
|
"Hide it" : "Скрыть это",
|
||||||
|
"Review people found" : "Посмотреть найденных людей",
|
||||||
|
"Your friends have not been recognized yet" : "Твои друзья не были пока распознаны",
|
||||||
|
"Please, be patient" : "Пожалуйста, будьте терпеливы",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Вы потеряете всю проанализированную информацию, и если вы включите ее, то начнете с нуля.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Вы хотите деактивировать группировку по лицам?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Произошла ошибка при попытке найти фотографии вашего друга",
|
||||||
|
"An error occurred while hiding this person" : "Произошла ошибка при скрытии этого человека",
|
||||||
|
"There was an error renaming this cluster of faces" : "Произошла ошибка при переименования этого кластера лиц",
|
||||||
|
"An error occurred while hiding this group of faces" : "Произошла ошибка при скрытии этой группы лиц",
|
||||||
|
"_%n image_::_%n images_" : ["%n изображение","%n изображений","%n изображений","%n изображений"],
|
||||||
|
"You must be administrator to configure this feature" : "Чтобы настроить эту функцию, необходимо быть администратором",
|
||||||
|
"The format seems to be incorrect." : "Формат кажется неправильный.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Кажется, вы еще не установили ни одной модели анализа",
|
||||||
|
"The minimum recommended area is %s" : "Минимально рекомендуемая область является %s",
|
||||||
|
"The maximum recommended area is %s" : "Максимально рекомендуемая область является %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Модель не рекомендует область, превышающую %s",
|
||||||
|
"It seems you don't have any model installed." : "Похоже, у вас не установлено ни одной модели.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Кажется, что вам все еще нужно настроить назначенную память для обработки изображений.",
|
||||||
|
"Not installed" : "Нет установленных",
|
||||||
|
"Not configured." : "Не настроены.",
|
||||||
|
"A face recognition app" : "Приложение для распознавания лиц",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Обнаружение и группировка лиц ваших близких в собственном облаке**\n\n⚠️ Это приложение требует минимум 1 Гб оперативной памяти для работы! Подробнее [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations).\n\n⚠️ Установка этого приложения требует доступа к терминалу и даже запачкать руки с установкой дополнительного программного обеспечения. Подробнее [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Обнаружение лиц с изображений:** Используйте приложение FaceRecognition для обнаружения _любого_ лица на _любом_ из ваших изображений!\n- **👪 Группировка лиц по людям:** Обнаруженные лица группируются на основе сходства, а затем приложение FaceRecognition может распознавать людей!\n- **🔒 Встроенная конфиденциальность:** Никакие данные не покидают вашего облака. Значения по умолчанию всегда выключены, и каждый пользователь контролирует включение/отключение распознавания лица. Изображения из каждого каталога могут быть исключены из распознавания лиц, если это необходимо.\n- **⚙️ Мощь ИИ:** FaceRecognition приложение использует мощь ИИ и уже построенных моделей нейронных сетей через широкое использование библиотеки [DLib](http://dlib.net/).\n- **🚀 Используйте по своему:** Приложение FaceRecognition - это просто базовый строительный блок. С помощью FaceRecognition API вы можете создавать свои продвинутые сценарии - автоматически добавлять теги в изображения, соединять контакты и людей, обмениваться изображениями от конкретного человека... Мы хотим услышать ваши идеи!",
|
||||||
|
"See other photos" : "Смотреть другие фотографии",
|
||||||
|
"Facial recognition is disabled" : "Распознавание лиц отключено",
|
||||||
|
"Facial recognition is disabled for this folder" : "Распознавание лиц отключено для этой папки",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Тип хранилища не поддерживает анализ ваших фотографий",
|
||||||
|
"No people found" : "Люди не найдены",
|
||||||
|
"This image is not yet analyzed" : "Это изображение еще не проанализировано",
|
||||||
|
"Search for persons in the photos of this directory" : "Поиск лиц в фотографиях этого каталога",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Фотографии, которых нет в галерее, также игнорируются",
|
||||||
|
"People" : "Люди",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Подробнее в <a target=\"_blank\" href=\"{docsLink}\">документации ↗ </a>",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Открыть <a target=\"_blank\" href=\"{settingsLink}\">настройки ↗ </a> чтобы включить",
|
||||||
|
"Open Documentation" : "Открыть документацию",
|
||||||
|
"Temporary files" : "Временные файлы",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Во время анализа временные файлы используются для обеспечения однородности между всеми изображениями.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Небольшие изображения позволяют быстро проанализировать, но вы можете потерять самые маленькие лица ваших фотографий. Большие изображения могут улучшить результаты, но анализ будет медленнее.",
|
||||||
|
"Smaller images" : "Меньшие изображения",
|
||||||
|
"Larger images" : "Большие изображения",
|
||||||
|
"Restore" : "Восстановить",
|
||||||
|
"Clustering threshold" : "Порог кластеризации",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Лица определяются как группы с похожими лицами, и для их получения все найденные лица должны быть сопоставлены. При их сопоставлении используется пороговое значение для определения того, следует ли их группировать.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Небольшой порог будет только группировать очень похожие лица, но изначально у вас будет много групп для названия. Более высокий порог позволяет более гибко сгруппировать лица и получить меньше групп, но при этом может запутаться в схожих лиц.",
|
||||||
|
"Small threshold" : "Нижний порог",
|
||||||
|
"Higher threshold" : "Верхний порог",
|
||||||
|
"Minimum confidence" : "Минимальная уверенность",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Минимальная уверенность определяет, насколько надежным должно быть обнаружение лица, чтобы попытаться сгруппировать его. Размытые или смещенные грани имеют уверенность, близкую к 0,0, а лучшие изображения - близкую к 1,0.",
|
||||||
|
"Lower minimum confidence" : "Низкая минимальная уверенность",
|
||||||
|
"Higher minimum confidence" : "Высокая минимальная уверенность",
|
||||||
|
"Configuration information" : "Сведения о конфигурации",
|
||||||
|
"Current model:" : "Текущая модель:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Максимальная память для обработки изображений:",
|
||||||
|
"Current status" : "Текущее состояние",
|
||||||
|
"Stopped" : "Остановлено"
|
||||||
|
},
|
||||||
|
"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);");
|
||||||
93
facerecognition/l10n/ru.json
Normal file
93
facerecognition/l10n/ru.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "Анализ завершён",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n изображение проанализировано","%n изображений проанализировано","%n изображений проанализировано","%n изображений проанализировано"],
|
||||||
|
"Analyzing images" : "Анализ изображений",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n изображение обнаружено","%n изображений проанализировано","%n изображений обнаружено","%n изображений обнаружено"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n изображение в очереди","%n изображений в очереди","%n изображений в очереди","%n изображений в очереди"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Окончание приблизительно {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Анализ еще не начат",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Изменения сохранены. Они будут учтены в следующем анализе.",
|
||||||
|
"The change could not be applied." : "Это изменение не может быть применено.",
|
||||||
|
"Hide person" : "Скрыть человека",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "Вы все еще можете увидеть этого человека на фотографиях, но назначение имени будет только для этой фотографии.",
|
||||||
|
"Cancel" : "Отмена",
|
||||||
|
"Hide" : "Скрыть",
|
||||||
|
"Rename person" : "Переименовать человека",
|
||||||
|
"Please enter a name to rename the person" : "Пожалуйста, введите имя, чтобы переименовать человека",
|
||||||
|
"Rename" : "Переименовать ",
|
||||||
|
"This person is not {name}" : "Этот человек не {name}",
|
||||||
|
"Optionally you can assign the correct name" : "При желании можно назначить правильное имя",
|
||||||
|
"Please assign a name to this person." : "Пожалуйста, назначьте имя этому человеку.",
|
||||||
|
"Save" : "Сохранить",
|
||||||
|
"Add name" : "Добавить имя",
|
||||||
|
"Ignore" : "Игнорировать",
|
||||||
|
"Skip for now" : "Пропустить пока",
|
||||||
|
"There was an error trying to show your friends" : "Произошла ошибка при попытке показать друзьям",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Анализ включен, пожалуйста, будьте терпеливы, вы скоро увидите ваших друзей здесь.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Анализ отключен. Скоро вся информация, найденная для распознавания лиц, будет удалена.",
|
||||||
|
"There was an error renaming this person" : "Произошла ошибка при переименовании этого человека",
|
||||||
|
"There was an error ignoring this person" : "Произошла ошибка при игнорировании человека",
|
||||||
|
"Face Recognition" : "Распознавание лиц",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Здесь вы можете увидеть фотографии ваших друзей, которые были распознаны",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Проанализировать мои изображения и группы моих близких с похожими лицами",
|
||||||
|
"Looking for your recognized friends" : "Ищем Ваших признанных друзей",
|
||||||
|
"Review face groups" : "Просмотр групп лиц",
|
||||||
|
"The analysis is disabled" : "Анализ отключен",
|
||||||
|
"Enable it to find your loved ones" : "Включите чтобы найти ваших близких",
|
||||||
|
"Hide it" : "Скрыть это",
|
||||||
|
"Review people found" : "Посмотреть найденных людей",
|
||||||
|
"Your friends have not been recognized yet" : "Твои друзья не были пока распознаны",
|
||||||
|
"Please, be patient" : "Пожалуйста, будьте терпеливы",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Вы потеряете всю проанализированную информацию, и если вы включите ее, то начнете с нуля.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Вы хотите деактивировать группировку по лицам?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Произошла ошибка при попытке найти фотографии вашего друга",
|
||||||
|
"An error occurred while hiding this person" : "Произошла ошибка при скрытии этого человека",
|
||||||
|
"There was an error renaming this cluster of faces" : "Произошла ошибка при переименования этого кластера лиц",
|
||||||
|
"An error occurred while hiding this group of faces" : "Произошла ошибка при скрытии этой группы лиц",
|
||||||
|
"_%n image_::_%n images_" : ["%n изображение","%n изображений","%n изображений","%n изображений"],
|
||||||
|
"You must be administrator to configure this feature" : "Чтобы настроить эту функцию, необходимо быть администратором",
|
||||||
|
"The format seems to be incorrect." : "Формат кажется неправильный.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Кажется, вы еще не установили ни одной модели анализа",
|
||||||
|
"The minimum recommended area is %s" : "Минимально рекомендуемая область является %s",
|
||||||
|
"The maximum recommended area is %s" : "Максимально рекомендуемая область является %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Модель не рекомендует область, превышающую %s",
|
||||||
|
"It seems you don't have any model installed." : "Похоже, у вас не установлено ни одной модели.",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "Кажется, что вам все еще нужно настроить назначенную память для обработки изображений.",
|
||||||
|
"Not installed" : "Нет установленных",
|
||||||
|
"Not configured." : "Не настроены.",
|
||||||
|
"A face recognition app" : "Приложение для распознавания лиц",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Обнаружение и группировка лиц ваших близких в собственном облаке**\n\n⚠️ Это приложение требует минимум 1 Гб оперативной памяти для работы! Подробнее [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations).\n\n⚠️ Установка этого приложения требует доступа к терминалу и даже запачкать руки с установкой дополнительного программного обеспечения. Подробнее [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Обнаружение лиц с изображений:** Используйте приложение FaceRecognition для обнаружения _любого_ лица на _любом_ из ваших изображений!\n- **👪 Группировка лиц по людям:** Обнаруженные лица группируются на основе сходства, а затем приложение FaceRecognition может распознавать людей!\n- **🔒 Встроенная конфиденциальность:** Никакие данные не покидают вашего облака. Значения по умолчанию всегда выключены, и каждый пользователь контролирует включение/отключение распознавания лица. Изображения из каждого каталога могут быть исключены из распознавания лиц, если это необходимо.\n- **⚙️ Мощь ИИ:** FaceRecognition приложение использует мощь ИИ и уже построенных моделей нейронных сетей через широкое использование библиотеки [DLib](http://dlib.net/).\n- **🚀 Используйте по своему:** Приложение FaceRecognition - это просто базовый строительный блок. С помощью FaceRecognition API вы можете создавать свои продвинутые сценарии - автоматически добавлять теги в изображения, соединять контакты и людей, обмениваться изображениями от конкретного человека... Мы хотим услышать ваши идеи!",
|
||||||
|
"See other photos" : "Смотреть другие фотографии",
|
||||||
|
"Facial recognition is disabled" : "Распознавание лиц отключено",
|
||||||
|
"Facial recognition is disabled for this folder" : "Распознавание лиц отключено для этой папки",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Тип хранилища не поддерживает анализ ваших фотографий",
|
||||||
|
"No people found" : "Люди не найдены",
|
||||||
|
"This image is not yet analyzed" : "Это изображение еще не проанализировано",
|
||||||
|
"Search for persons in the photos of this directory" : "Поиск лиц в фотографиях этого каталога",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Фотографии, которых нет в галерее, также игнорируются",
|
||||||
|
"People" : "Люди",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Подробнее в <a target=\"_blank\" href=\"{docsLink}\">документации ↗ </a>",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Открыть <a target=\"_blank\" href=\"{settingsLink}\">настройки ↗ </a> чтобы включить",
|
||||||
|
"Open Documentation" : "Открыть документацию",
|
||||||
|
"Temporary files" : "Временные файлы",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Во время анализа временные файлы используются для обеспечения однородности между всеми изображениями.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Небольшие изображения позволяют быстро проанализировать, но вы можете потерять самые маленькие лица ваших фотографий. Большие изображения могут улучшить результаты, но анализ будет медленнее.",
|
||||||
|
"Smaller images" : "Меньшие изображения",
|
||||||
|
"Larger images" : "Большие изображения",
|
||||||
|
"Restore" : "Восстановить",
|
||||||
|
"Clustering threshold" : "Порог кластеризации",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "Лица определяются как группы с похожими лицами, и для их получения все найденные лица должны быть сопоставлены. При их сопоставлении используется пороговое значение для определения того, следует ли их группировать.",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "Небольшой порог будет только группировать очень похожие лица, но изначально у вас будет много групп для названия. Более высокий порог позволяет более гибко сгруппировать лица и получить меньше групп, но при этом может запутаться в схожих лиц.",
|
||||||
|
"Small threshold" : "Нижний порог",
|
||||||
|
"Higher threshold" : "Верхний порог",
|
||||||
|
"Minimum confidence" : "Минимальная уверенность",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Минимальная уверенность определяет, насколько надежным должно быть обнаружение лица, чтобы попытаться сгруппировать его. Размытые или смещенные грани имеют уверенность, близкую к 0,0, а лучшие изображения - близкую к 1,0.",
|
||||||
|
"Lower minimum confidence" : "Низкая минимальная уверенность",
|
||||||
|
"Higher minimum confidence" : "Высокая минимальная уверенность",
|
||||||
|
"Configuration information" : "Сведения о конфигурации",
|
||||||
|
"Current model:" : "Текущая модель:",
|
||||||
|
"Maximum memory assigned for image processing:" : "Максимальная память для обработки изображений:",
|
||||||
|
"Current status" : "Текущее состояние",
|
||||||
|
"Stopped" : "Остановлено"
|
||||||
|
},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"
|
||||||
|
}
|
||||||
62
facerecognition/l10n/sr.js
Normal file
62
facerecognition/l10n/sr.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "Анализа је завршена",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n слика је анализирана","%n слике је анализиране","%n слика је анализирано "],
|
||||||
|
"Analyzing images" : "Анализирам слике",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n слика пронађена","%n слике пронађене","%n слика пронађено"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n слика на чекању","%n слике на чекању","%n слика на чекању"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Завршиће се одокативно {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Анализа још није започела",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Измене су сачуване. Биће узете у обзир при следећој анализи.",
|
||||||
|
"The change could not be applied." : "Измене не могу да се примене.",
|
||||||
|
"Cancel" : "Поништи",
|
||||||
|
"Rename person" : "Преименуј особу",
|
||||||
|
"Please enter a name to rename the person" : "Унесите ново име за особу",
|
||||||
|
"Rename" : "Преименуј",
|
||||||
|
"Save" : "Сачувај",
|
||||||
|
"There was an error trying to show your friends" : "Грешка при приказивању Ваших пријатеља",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Анализа је укључена, будите стрпљиви, ускоро би требало да видите пријатеље овде.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Анализа је искључена. Ускоро ће сви подаци везани за препознавање лица бити уклоњени. ",
|
||||||
|
"There was an error renaming this person" : "Догодила се грешка приликом преименовања ове особе",
|
||||||
|
"Face Recognition" : "Препознавање лица",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Овде можете видети све слике Ваших пријатеља који су препознати",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Анализирај моје слике и групиши моје вољене према сличности лица",
|
||||||
|
"Looking for your recognized friends" : "Тражим препознате пријатеље",
|
||||||
|
"The analysis is disabled" : "Анализа је искључена",
|
||||||
|
"Enable it to find your loved ones" : "Укључите је да нађете Ваше вољене особе",
|
||||||
|
"Your friends have not been recognized yet" : "Ваши пријатељи још нису препознати",
|
||||||
|
"Please, be patient" : "Будите стрпљиви",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Изгубићете све анализиране податке до сада, а ако је укључите поново, почећете од почетка.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Да ли желите да искључите груписање по лицима?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Догодила се грешка приликом тражења слика Ваших пријатеља",
|
||||||
|
"You must be administrator to configure this feature" : "Морате бити администратор да подесите ову функционалност",
|
||||||
|
"The format seems to be incorrect." : "Формат је неисправан.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Изгледа да још нисте подесили модел за анализу слика",
|
||||||
|
"The minimum recommended area is %s" : "Најмања препоручена површина је %s",
|
||||||
|
"The maximum recommended area is %s" : "Највећа препоручена површина је %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Овај модел не подржава површину већу од %s",
|
||||||
|
"It seems you don't have any model installed." : "Изгледа да немате ниједан инсталирани модел.",
|
||||||
|
"A face recognition app" : "Апликација препознавања лица",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Препознајте и групишите лица Ваших вољених у Вашем облаку**\n\n⚠️ Ова апликација захтева минимум 1GB RAM меморије за исправан рад! Погледајте [захтеве](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) за детаље.\n\n⚠️ Подешавање ове апликације захтева приступ терминалу и да се човек мало помучи, пошто треба инсталирати додатни софтвер. Погледајте [инсталацију](https://github.com/matiasdelellis/facerecognition/wiki/Installation) за детаље.\n\n- **😏 Пропознаје лица из слика:** Користите FaceRecognition апликацију да препознате _било које_ лице на _било којој_ слици!\n- **👪 Групишите лица у особе:** Препозната лица се групишу заједно на основу сличности и онда FaceRecognition апликација може да препозна појединачне особе!\n- **🔒 Уграђена приватност:** Подаци не напуштају Ваш облак. Подразумеване вредности су увек такве да је све искључено и сваки корисник контролише укључивање/искључивање препознавања лица за себе. Слике из сваког директоријума појединачно се могу изузети из препознавања лица, по потреби.\n- **⚙️ Снага вештачке интелигенције:** FaceRecognition апликација користи AI (вештачку интелигенцију) и већ прегенерисане моделе неуронских мрежа кроз обилно коришћење [DLib](http://dlib.net/) библиотеке.\n- **🚀 Направите Вашу ствар:** FaceRecognition апликација је само основни градивни елемент. Кроз FaceRecognition API, можете направити Ви Ваше напредне сценарије коришћења - аутоматско додавање ознака на слике, повезивање особа са контактима, дељење слика само одређене особе… Желимо да чујемо Ваше идеје!",
|
||||||
|
"Facial recognition is disabled" : "Препознавање лица је искључено",
|
||||||
|
"Facial recognition is disabled for this folder" : "Препознавање лица је искључено у овој фасцикли",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Овај тип складишта није подржан за анализу слика",
|
||||||
|
"No people found" : "Нема нађених особа",
|
||||||
|
"This image is not yet analyzed" : "Ова слика још није анализирана",
|
||||||
|
"Search for persons in the photos of this directory" : "Тражи особе у сликама у овом директоријуму",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Слике које нису у галерији се такође игноришу",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Погледајте <a target=\"_blank\" href=\"{docsLink}\">документацију ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Отворите <a target=\"_blank\" href=\"{settingsLink}\">поставке</a> да је укључите",
|
||||||
|
"Open Documentation" : "Отвори документацију",
|
||||||
|
"Temporary files" : "Привремени фајлови",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Привремени фајлови се користе да обезбеде хомогеност између свих слика за време анализе.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Мале слике омогућују брзу анализу, али на њима се тешко препознају мала лица. Велике слике побољшавају резултат, али је анализа спорија.",
|
||||||
|
"Restore" : "Поврати",
|
||||||
|
"Minimum confidence" : "Минимално поуздање",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Минимално поуздање колико поуздано препознавање лица треба да буде да покушамо његово груписање. Нејасна или непоравната лица имају поуздање ближе вредности 0.0, а најбоље слике су ближе вредности 1.0.",
|
||||||
|
"Configuration information" : "Информације о поставкама",
|
||||||
|
"Current status" : "Тренутно стање",
|
||||||
|
"Stopped" : "Заустављено"
|
||||||
|
},
|
||||||
|
"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);");
|
||||||
60
facerecognition/l10n/sr.json
Normal file
60
facerecognition/l10n/sr.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "Анализа је завршена",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["%n слика је анализирана","%n слике је анализиране","%n слика је анализирано "],
|
||||||
|
"Analyzing images" : "Анализирам слике",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["%n слика пронађена","%n слике пронађене","%n слика пронађено"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["%n слика на чекању","%n слике на чекању","%n слика на чекању"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "Завршиће се одокативно {estimatedFinalize}",
|
||||||
|
"The analysis is not started yet" : "Анализа још није започела",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "Измене су сачуване. Биће узете у обзир при следећој анализи.",
|
||||||
|
"The change could not be applied." : "Измене не могу да се примене.",
|
||||||
|
"Cancel" : "Поништи",
|
||||||
|
"Rename person" : "Преименуј особу",
|
||||||
|
"Please enter a name to rename the person" : "Унесите ново име за особу",
|
||||||
|
"Rename" : "Преименуј",
|
||||||
|
"Save" : "Сачувај",
|
||||||
|
"There was an error trying to show your friends" : "Грешка при приказивању Ваших пријатеља",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "Анализа је укључена, будите стрпљиви, ускоро би требало да видите пријатеље овде.",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "Анализа је искључена. Ускоро ће сви подаци везани за препознавање лица бити уклоњени. ",
|
||||||
|
"There was an error renaming this person" : "Догодила се грешка приликом преименовања ове особе",
|
||||||
|
"Face Recognition" : "Препознавање лица",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "Овде можете видети све слике Ваших пријатеља који су препознати",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "Анализирај моје слике и групиши моје вољене према сличности лица",
|
||||||
|
"Looking for your recognized friends" : "Тражим препознате пријатеље",
|
||||||
|
"The analysis is disabled" : "Анализа је искључена",
|
||||||
|
"Enable it to find your loved ones" : "Укључите је да нађете Ваше вољене особе",
|
||||||
|
"Your friends have not been recognized yet" : "Ваши пријатељи још нису препознати",
|
||||||
|
"Please, be patient" : "Будите стрпљиви",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "Изгубићете све анализиране податке до сада, а ако је укључите поново, почећете од почетка.",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "Да ли желите да искључите груписање по лицима?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "Догодила се грешка приликом тражења слика Ваших пријатеља",
|
||||||
|
"You must be administrator to configure this feature" : "Морате бити администратор да подесите ову функционалност",
|
||||||
|
"The format seems to be incorrect." : "Формат је неисправан.",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "Изгледа да још нисте подесили модел за анализу слика",
|
||||||
|
"The minimum recommended area is %s" : "Најмања препоручена површина је %s",
|
||||||
|
"The maximum recommended area is %s" : "Највећа препоручена површина је %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "Овај модел не подржава површину већу од %s",
|
||||||
|
"It seems you don't have any model installed." : "Изгледа да немате ниједан инсталирани модел.",
|
||||||
|
"A face recognition app" : "Апликација препознавања лица",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**Препознајте и групишите лица Ваших вољених у Вашем облаку**\n\n⚠️ Ова апликација захтева минимум 1GB RAM меморије за исправан рад! Погледајте [захтеве](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) за детаље.\n\n⚠️ Подешавање ове апликације захтева приступ терминалу и да се човек мало помучи, пошто треба инсталирати додатни софтвер. Погледајте [инсталацију](https://github.com/matiasdelellis/facerecognition/wiki/Installation) за детаље.\n\n- **😏 Пропознаје лица из слика:** Користите FaceRecognition апликацију да препознате _било које_ лице на _било којој_ слици!\n- **👪 Групишите лица у особе:** Препозната лица се групишу заједно на основу сличности и онда FaceRecognition апликација може да препозна појединачне особе!\n- **🔒 Уграђена приватност:** Подаци не напуштају Ваш облак. Подразумеване вредности су увек такве да је све искључено и сваки корисник контролише укључивање/искључивање препознавања лица за себе. Слике из сваког директоријума појединачно се могу изузети из препознавања лица, по потреби.\n- **⚙️ Снага вештачке интелигенције:** FaceRecognition апликација користи AI (вештачку интелигенцију) и већ прегенерисане моделе неуронских мрежа кроз обилно коришћење [DLib](http://dlib.net/) библиотеке.\n- **🚀 Направите Вашу ствар:** FaceRecognition апликација је само основни градивни елемент. Кроз FaceRecognition API, можете направити Ви Ваше напредне сценарије коришћења - аутоматско додавање ознака на слике, повезивање особа са контактима, дељење слика само одређене особе… Желимо да чујемо Ваше идеје!",
|
||||||
|
"Facial recognition is disabled" : "Препознавање лица је искључено",
|
||||||
|
"Facial recognition is disabled for this folder" : "Препознавање лица је искључено у овој фасцикли",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "Овај тип складишта није подржан за анализу слика",
|
||||||
|
"No people found" : "Нема нађених особа",
|
||||||
|
"This image is not yet analyzed" : "Ова слика још није анализирана",
|
||||||
|
"Search for persons in the photos of this directory" : "Тражи особе у сликама у овом директоријуму",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "Слике које нису у галерији се такође игноришу",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "Погледајте <a target=\"_blank\" href=\"{docsLink}\">документацију ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "Отворите <a target=\"_blank\" href=\"{settingsLink}\">поставке</a> да је укључите",
|
||||||
|
"Open Documentation" : "Отвори документацију",
|
||||||
|
"Temporary files" : "Привремени фајлови",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "Привремени фајлови се користе да обезбеде хомогеност између свих слика за време анализе.",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "Мале слике омогућују брзу анализу, али на њима се тешко препознају мала лица. Велике слике побољшавају резултат, али је анализа спорија.",
|
||||||
|
"Restore" : "Поврати",
|
||||||
|
"Minimum confidence" : "Минимално поуздање",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "Минимално поуздање колико поуздано препознавање лица треба да буде да покушамо његово груписање. Нејасна или непоравната лица имају поуздање ближе вредности 0.0, а најбоље слике су ближе вредности 1.0.",
|
||||||
|
"Configuration information" : "Информације о поставкама",
|
||||||
|
"Current status" : "Тренутно стање",
|
||||||
|
"Stopped" : "Заустављено"
|
||||||
|
},"pluralForm" :"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"
|
||||||
|
}
|
||||||
89
facerecognition/l10n/zh_CN.js
Normal file
89
facerecognition/l10n/zh_CN.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "分析完成",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["分析了 %n 张图片"],
|
||||||
|
"Analyzing images" : "分析图片",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["检测到 %n 张图片"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["队列中有 %n 张图片"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "完成时间大约 {estimatedFinalize} 后。",
|
||||||
|
"The analysis is not started yet" : "分析尚未开始",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "更改已保存。 这将在接下来的分析种被考虑在内。",
|
||||||
|
"The change could not be applied." : "无法应用更改。",
|
||||||
|
"Hide person" : "隐藏此人",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "你仍然可以在照片中看到那个人,但指定的名字将只针对那张照片。",
|
||||||
|
"Cancel" : "取消",
|
||||||
|
"Hide" : "隐藏",
|
||||||
|
"Rename person" : "重命名此人",
|
||||||
|
"Please enter a name to rename the person" : "请输入姓名以重命名此人",
|
||||||
|
"Rename" : "重命名",
|
||||||
|
"This person is not {name}" : "这个人不是 {name}",
|
||||||
|
"Optionally you can assign the correct name" : "可选,你可以指定正确的名称",
|
||||||
|
"Please assign a name to this person." : "请给这个人命名。",
|
||||||
|
"Save" : "保存",
|
||||||
|
"Ignore" : "忽略",
|
||||||
|
"There was an error trying to show your friends" : "尝试向您展示亲友时出错",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "启用了分析功能,请耐心等待,您很快就会在这里看到您的亲友。",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "分析被禁用。 所有用于面部识别的信息将很快被删除。",
|
||||||
|
"There was an error renaming this person" : "重命名此人时出错",
|
||||||
|
"There was an error ignoring this person" : "忽略此人时出错",
|
||||||
|
"Face Recognition" : "面部识别",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "您可以在这里看到被识别的亲友的照片",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "分析我的图像,并用相似的面部将我的亲友分组",
|
||||||
|
"Looking for your recognized friends" : "寻找您已识别的亲友",
|
||||||
|
"The analysis is disabled" : "识别被禁用",
|
||||||
|
"Enable it to find your loved ones" : "启用它能够找到您的亲友",
|
||||||
|
"Hide it" : "隐藏它",
|
||||||
|
"Your friends have not been recognized yet" : "您的亲友尚未被识别",
|
||||||
|
"Please, be patient" : "请耐心点",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "您将丢失所有分析的信息,如果重新启用它,则将从头开始。",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "您要停用按面部分组吗?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "尝试查找亲友的照片时出现错误",
|
||||||
|
"An error occurred while hiding this person" : "隐藏此人时发生了一个错误",
|
||||||
|
"There was an error renaming this cluster of faces" : "重命名这组面孔时出错",
|
||||||
|
"An error occurred while hiding this group of faces" : "在隐藏这组面孔时发生了一个错误",
|
||||||
|
"_%n image_::_%n images_" : ["%n 张图片"],
|
||||||
|
"You must be administrator to configure this feature" : "您必须是管理员才能配置此功能",
|
||||||
|
"The format seems to be incorrect." : "格式似乎不正确。",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "您似乎尚未设置任何分类标准",
|
||||||
|
"The minimum recommended area is %s" : "推荐的最小范围是 %s",
|
||||||
|
"The maximum recommended area is %s" : "推荐的最大范围是 %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "这个模型推荐的范围不能大于 %s",
|
||||||
|
"It seems you don't have any model installed." : "您似乎没有安装任何模型",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "看来,你还是要为图像处理配置指定的内存。",
|
||||||
|
"Not installed" : "未安裝",
|
||||||
|
"Not configured." : "未配置。",
|
||||||
|
"A face recognition app" : "面部识别应用",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**在您的云端中检测并分组您所收集的人的面孔**\n\n⚠️ 此应用至少需要 1GB 内存才能运行! 有关详细信息,请参见[需求](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations)。\n\n⚠️ 此应用的设置需要访问终端,甚至需要手动安装其他软件,可能造成系统不够纯净。 有关详细信息,请参见[安装](https://github.com/matiasdelellis/facerecognition/wiki/Installation)。\n\n- **😏从图像中检测面部:** 使用人脸识别应用检测任意图像中的任何人脸!\n- **👪人脸分组:** 根据相似度将检测到的人脸分组在一起,之后人脸识别应用可以识别身份!\n- **🔒内置隐私:** 不会有数据从云端泄露。 默认值始终处于关闭状态,并且每个用户都可以启用 / 禁用面部检测。 如果需要,可以将任何目录中的图像从人脸检测中排除。\n- **⚙️ AI 的力量:** 人脸识别应用通过 [DLib](http://dlib.net/) 库的广泛使用,充分利用了 AI 的力量并已经构建了神经网络模型。\n- **🚀建立自己的东西:** 人脸识别应用只是一个基本构建块。 通过人脸识别 API,您可以构建高级方案——自动向图像添加标签,连接联系人和身份,共享特定人员的图像...我们希望听到您的想法!",
|
||||||
|
"Facial recognition is disabled" : "面部识别已被禁用",
|
||||||
|
"Facial recognition is disabled for this folder" : "此文件夹的面部识别功能已被禁用",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "存储类型不支持分析您的照片",
|
||||||
|
"No people found" : "没有发现任何人",
|
||||||
|
"This image is not yet analyzed" : "这张图片还没有被分析",
|
||||||
|
"Search for persons in the photos of this directory" : "强制在此目录的图像中搜索面部",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "已设置为不在图库中的图像也将被检索",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "查看 <a target=\"_blank\" href=\"{docsLink}\">文档 ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "打开 <a target=\"_blank\" href=\"{settingsLink}\">设置 ↗</a> 以启用它",
|
||||||
|
"Open Documentation" : "打开文档",
|
||||||
|
"Temporary files" : "临时文件",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "在分析过程中,使用临时文件用来确保所有图片的相似性",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "较小的图片可以加速分析,但是你可能会丢失照片中的比较小的面孔。大一点的图片有利于改善结果,但是分析过程可能较慢。",
|
||||||
|
"Smaller images" : "较小的图片",
|
||||||
|
"Larger images" : "较大的图片",
|
||||||
|
"Restore" : "恢复",
|
||||||
|
"Clustering threshold" : "分组阈值",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "将确定为相似面孔的人分组,为了获得这些面孔,必须对发现的所有面孔进行比较。当他们被比较时,一个阈值被用来确定他们是否应该被分组。",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "一个小的阈值只会将非常相似的面孔分组,但最初你会有很多分组需要命名。一个较大的阈值可以更灵活地对人脸进行分组,获得较少的分组,但会将相似的人混淆。",
|
||||||
|
"Small threshold" : "小阈值",
|
||||||
|
"Higher threshold" : "较高的阈值",
|
||||||
|
"Minimum confidence" : "最低置信度",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "最小置信度决定尝试将其分组的人脸检测的可靠程度。模糊或未对齐的脸部的置信度接近 0.0,最佳图像的置信度接近 1.0。",
|
||||||
|
"Lower minimum confidence" : "较低的最低置信度",
|
||||||
|
"Higher minimum confidence" : "较高的最低信任度",
|
||||||
|
"Configuration information" : "配置信息",
|
||||||
|
"Current model:" : "当前模型:",
|
||||||
|
"Maximum memory assigned for image processing:" : "为图像处理分配的最大内存:",
|
||||||
|
"Current status" : "当前状态",
|
||||||
|
"Stopped" : "已停止"
|
||||||
|
},
|
||||||
|
"nplurals=1; plural=0;");
|
||||||
87
facerecognition/l10n/zh_CN.json
Normal file
87
facerecognition/l10n/zh_CN.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "分析完成",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["分析了 %n 张图片"],
|
||||||
|
"Analyzing images" : "分析图片",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["检测到 %n 张图片"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["队列中有 %n 张图片"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "完成时间大约 {estimatedFinalize} 后。",
|
||||||
|
"The analysis is not started yet" : "分析尚未开始",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "更改已保存。 这将在接下来的分析种被考虑在内。",
|
||||||
|
"The change could not be applied." : "无法应用更改。",
|
||||||
|
"Hide person" : "隐藏此人",
|
||||||
|
"You can still see that person in the photos, but assigning a name will only be for that photo." : "你仍然可以在照片中看到那个人,但指定的名字将只针对那张照片。",
|
||||||
|
"Cancel" : "取消",
|
||||||
|
"Hide" : "隐藏",
|
||||||
|
"Rename person" : "重命名此人",
|
||||||
|
"Please enter a name to rename the person" : "请输入姓名以重命名此人",
|
||||||
|
"Rename" : "重命名",
|
||||||
|
"This person is not {name}" : "这个人不是 {name}",
|
||||||
|
"Optionally you can assign the correct name" : "可选,你可以指定正确的名称",
|
||||||
|
"Please assign a name to this person." : "请给这个人命名。",
|
||||||
|
"Save" : "保存",
|
||||||
|
"Ignore" : "忽略",
|
||||||
|
"There was an error trying to show your friends" : "尝试向您展示亲友时出错",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "启用了分析功能,请耐心等待,您很快就会在这里看到您的亲友。",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "分析被禁用。 所有用于面部识别的信息将很快被删除。",
|
||||||
|
"There was an error renaming this person" : "重命名此人时出错",
|
||||||
|
"There was an error ignoring this person" : "忽略此人时出错",
|
||||||
|
"Face Recognition" : "面部识别",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "您可以在这里看到被识别的亲友的照片",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "分析我的图像,并用相似的面部将我的亲友分组",
|
||||||
|
"Looking for your recognized friends" : "寻找您已识别的亲友",
|
||||||
|
"The analysis is disabled" : "识别被禁用",
|
||||||
|
"Enable it to find your loved ones" : "启用它能够找到您的亲友",
|
||||||
|
"Hide it" : "隐藏它",
|
||||||
|
"Your friends have not been recognized yet" : "您的亲友尚未被识别",
|
||||||
|
"Please, be patient" : "请耐心点",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "您将丢失所有分析的信息,如果重新启用它,则将从头开始。",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "您要停用按面部分组吗?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "尝试查找亲友的照片时出现错误",
|
||||||
|
"An error occurred while hiding this person" : "隐藏此人时发生了一个错误",
|
||||||
|
"There was an error renaming this cluster of faces" : "重命名这组面孔时出错",
|
||||||
|
"An error occurred while hiding this group of faces" : "在隐藏这组面孔时发生了一个错误",
|
||||||
|
"_%n image_::_%n images_" : ["%n 张图片"],
|
||||||
|
"You must be administrator to configure this feature" : "您必须是管理员才能配置此功能",
|
||||||
|
"The format seems to be incorrect." : "格式似乎不正确。",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "您似乎尚未设置任何分类标准",
|
||||||
|
"The minimum recommended area is %s" : "推荐的最小范围是 %s",
|
||||||
|
"The maximum recommended area is %s" : "推荐的最大范围是 %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "这个模型推荐的范围不能大于 %s",
|
||||||
|
"It seems you don't have any model installed." : "您似乎没有安装任何模型",
|
||||||
|
"Seems that you still have to configure the assigned memory for image processing." : "看来,你还是要为图像处理配置指定的内存。",
|
||||||
|
"Not installed" : "未安裝",
|
||||||
|
"Not configured." : "未配置。",
|
||||||
|
"A face recognition app" : "面部识别应用",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**在您的云端中检测并分组您所收集的人的面孔**\n\n⚠️ 此应用至少需要 1GB 内存才能运行! 有关详细信息,请参见[需求](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations)。\n\n⚠️ 此应用的设置需要访问终端,甚至需要手动安装其他软件,可能造成系统不够纯净。 有关详细信息,请参见[安装](https://github.com/matiasdelellis/facerecognition/wiki/Installation)。\n\n- **😏从图像中检测面部:** 使用人脸识别应用检测任意图像中的任何人脸!\n- **👪人脸分组:** 根据相似度将检测到的人脸分组在一起,之后人脸识别应用可以识别身份!\n- **🔒内置隐私:** 不会有数据从云端泄露。 默认值始终处于关闭状态,并且每个用户都可以启用 / 禁用面部检测。 如果需要,可以将任何目录中的图像从人脸检测中排除。\n- **⚙️ AI 的力量:** 人脸识别应用通过 [DLib](http://dlib.net/) 库的广泛使用,充分利用了 AI 的力量并已经构建了神经网络模型。\n- **🚀建立自己的东西:** 人脸识别应用只是一个基本构建块。 通过人脸识别 API,您可以构建高级方案——自动向图像添加标签,连接联系人和身份,共享特定人员的图像...我们希望听到您的想法!",
|
||||||
|
"Facial recognition is disabled" : "面部识别已被禁用",
|
||||||
|
"Facial recognition is disabled for this folder" : "此文件夹的面部识别功能已被禁用",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "存储类型不支持分析您的照片",
|
||||||
|
"No people found" : "没有发现任何人",
|
||||||
|
"This image is not yet analyzed" : "这张图片还没有被分析",
|
||||||
|
"Search for persons in the photos of this directory" : "强制在此目录的图像中搜索面部",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "已设置为不在图库中的图像也将被检索",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "查看 <a target=\"_blank\" href=\"{docsLink}\">文档 ↗</a>.",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "打开 <a target=\"_blank\" href=\"{settingsLink}\">设置 ↗</a> 以启用它",
|
||||||
|
"Open Documentation" : "打开文档",
|
||||||
|
"Temporary files" : "临时文件",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "在分析过程中,使用临时文件用来确保所有图片的相似性",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "较小的图片可以加速分析,但是你可能会丢失照片中的比较小的面孔。大一点的图片有利于改善结果,但是分析过程可能较慢。",
|
||||||
|
"Smaller images" : "较小的图片",
|
||||||
|
"Larger images" : "较大的图片",
|
||||||
|
"Restore" : "恢复",
|
||||||
|
"Clustering threshold" : "分组阈值",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "将确定为相似面孔的人分组,为了获得这些面孔,必须对发现的所有面孔进行比较。当他们被比较时,一个阈值被用来确定他们是否应该被分组。",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "一个小的阈值只会将非常相似的面孔分组,但最初你会有很多分组需要命名。一个较大的阈值可以更灵活地对人脸进行分组,获得较少的分组,但会将相似的人混淆。",
|
||||||
|
"Small threshold" : "小阈值",
|
||||||
|
"Higher threshold" : "较高的阈值",
|
||||||
|
"Minimum confidence" : "最低置信度",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "最小置信度决定尝试将其分组的人脸检测的可靠程度。模糊或未对齐的脸部的置信度接近 0.0,最佳图像的置信度接近 1.0。",
|
||||||
|
"Lower minimum confidence" : "较低的最低置信度",
|
||||||
|
"Higher minimum confidence" : "较高的最低信任度",
|
||||||
|
"Configuration information" : "配置信息",
|
||||||
|
"Current model:" : "当前模型:",
|
||||||
|
"Maximum memory assigned for image processing:" : "为图像处理分配的最大内存:",
|
||||||
|
"Current status" : "当前状态",
|
||||||
|
"Stopped" : "已停止"
|
||||||
|
},"pluralForm" :"nplurals=1; plural=0;"
|
||||||
|
}
|
||||||
76
facerecognition/l10n/zh_TW.js
Normal file
76
facerecognition/l10n/zh_TW.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
OC.L10N.register(
|
||||||
|
"facerecognition",
|
||||||
|
{
|
||||||
|
"The analysis is finished" : "分析完成",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["已分析 %n 張圖片"],
|
||||||
|
"Analyzing images" : "正在分析圖片",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["已偵測 %n 張圖片"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["有 %n 張圖片在佇列中"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "大約以 {estimatedFinalize} 結尾",
|
||||||
|
"The analysis is not started yet" : "分析尚未開始",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "變更已儲存。將會在下一次分析中計入。",
|
||||||
|
"The change could not be applied." : "無法套用變更。",
|
||||||
|
"Cancel" : "取消",
|
||||||
|
"Rename person" : "重新命名人",
|
||||||
|
"Please enter a name to rename the person" : "請輸入名稱以重新命名此人",
|
||||||
|
"Rename" : "重新命名",
|
||||||
|
"Please assign a name to this person." : "請為此人指定一個名字。",
|
||||||
|
"Save" : "儲存",
|
||||||
|
"There was an error trying to show your friends" : "嘗試顯示您的朋友時發生錯誤",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "已啟用分析,請耐心等待,您很快就會在此看到您的朋友。",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "已停用分析。所有臉部識別找到的資料很快都會被移除。",
|
||||||
|
"There was an error renaming this person" : "重新命名此人時發生錯誤",
|
||||||
|
"Face Recognition" : "臉部識別",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "在這裡,您可以看到被識別出來的朋友照片",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "分析我的圖片,並用相似的臉部將我的親人們分組",
|
||||||
|
"Looking for your recognized friends" : "尋找您已識別的朋友",
|
||||||
|
"The analysis is disabled" : "已停用分析",
|
||||||
|
"Enable it to find your loved ones" : "啟用它來尋找您的親人",
|
||||||
|
"Your friends have not been recognized yet" : "您的朋友還沒被識別",
|
||||||
|
"Please, be patient" : "請稍候",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "您將會遺失所有已分析的資訊,如果您重新啟用它,將會從頭開始。",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "您想要停用按臉部分組嗎?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "嘗試尋找您朋友的照片時發生錯誤",
|
||||||
|
"There was an error renaming this cluster of faces" : "重新命名這組臉孔時發生錯誤",
|
||||||
|
"_%n image_::_%n images_" : ["%n 張圖片"],
|
||||||
|
"You must be administrator to configure this feature" : "您必須是管理員才能設定此功能",
|
||||||
|
"The format seems to be incorrect." : "格式似乎不正確。",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "您似乎尚未設定任何分析模型",
|
||||||
|
"The minimum recommended area is %s" : "最小推薦面積為 %s",
|
||||||
|
"The maximum recommended area is %s" : "最大推薦面積為 %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "模型建議的面積不大於 %s",
|
||||||
|
"It seems you don't have any model installed." : "看來您沒有安裝任何模型。",
|
||||||
|
"Not installed" : "未安裝",
|
||||||
|
"A face recognition app" : "臉部識別應用程式",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**在您的雲端上偵測並為您親人的臉部分組**\n\n⚠️ 此應用程式最少需要 1GB 的記憶體才能運作!請檢視[需求](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations)以取得詳細資訊。\n\n⚠️ 安裝此應用程式需要使用終端機,並弄髒您的手安裝其他軟體。請檢視[安裝](https://github.com/matiasdelellis/facerecognition/wiki/Installation)以取得詳細資訊。\n\n- **😏 從圖片中偵測人臉:** 使用臉部識別應用程式來偵測您任何圖片中的任何臉孔!\n- **👪 將人們的臉孔分組:** 根據臉部的相似程度將臉孔分組,然後臉部識別應用程式就可以將其分組!\n- **🔒 內建的隱私:** 沒有資料會離開您的雲端。預設是關閉的,每個使用者都可以啟用/停用臉部偵測。如果需要的話,每個目錄的圖片都可以從臉部偵測排除。\n- **⚙️ AI 的力量:** 臉部識別應用程式透過廣泛使用 [DLib](http://dlib.net/) 函式庫來利用 AI 的功能與已經建立的神經網路模型。\n- **🚀 建構您自己的東西:** 臉部識別應用程式只是一個基本的建構區塊。透過臉部識別 API,您可以建構您自己的進階應用場景,自訂將標籤新增到圖片中、分享特定人的圖片……。我們想要聽到您的想法!",
|
||||||
|
"Facial recognition is disabled" : "臉部識別已停用",
|
||||||
|
"Facial recognition is disabled for this folder" : "此資料夾的臉部識別已被停用",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "不支援使用儲存類型來分析您的照片",
|
||||||
|
"No people found" : "找不到人",
|
||||||
|
"This image is not yet analyzed" : "此圖片尚未分析",
|
||||||
|
"Search for persons in the photos of this directory" : "在此目錄中的照片搜尋人",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "不在相簿中的照片將會被忽略",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "檢視<a target=\"_blank\" href=\"{docsLink}\">文件 ↗</a>。",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "開啟<a target=\"_blank\" href=\"{settingsLink}\">設定 ↗</a>以啟用它",
|
||||||
|
"Open Documentation" : "開啟文件",
|
||||||
|
"Temporary files" : "暫時檔案",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "在分析過程中,暫時檔案用來確保所有圖片間的同質性。",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "小圖片可以快速分析,但您可能會遺失您照片的最小照片。較大的圖片可以改善結果,但分析會比較慢。",
|
||||||
|
"Smaller images" : "較小的圖片",
|
||||||
|
"Larger images" : "較大的圖片",
|
||||||
|
"Restore" : "恢復",
|
||||||
|
"Clustering threshold" : "群組閾值",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "將人們以相似的臉孔分組,但必須比較所有找到的臉孔才能確定。比較它們時,將會使用閾值來決定是否要分成同一組。",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "較小的閾值只會將非常相似的臉孔分成一組,但ㄧ開始您會有很多需要命名的組別。較大的閾值可以更靈活地將臉孔分組,但可能會被相似的人迷惑。",
|
||||||
|
"Small threshold" : "小閾值",
|
||||||
|
"Higher threshold" : "較高的閾值",
|
||||||
|
"Minimum confidence" : "最低信賴程度",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "最低信賴程度決定了對臉部偵測所必須具備的可靠程度。模糊或未對齊的可靠程度趨近於 0.0,最佳圖片則趨近於 1.0。",
|
||||||
|
"Lower minimum confidence" : "較低的最低信賴程度",
|
||||||
|
"Higher minimum confidence" : "較高的最低信賴程度",
|
||||||
|
"Configuration information" : "設定資訊",
|
||||||
|
"Current model:" : "目前的模型:",
|
||||||
|
"Current status" : "目前的狀態",
|
||||||
|
"Stopped" : "已停止"
|
||||||
|
},
|
||||||
|
"nplurals=1; plural=0;");
|
||||||
74
facerecognition/l10n/zh_TW.json
Normal file
74
facerecognition/l10n/zh_TW.json
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
{ "translations": {
|
||||||
|
"The analysis is finished" : "分析完成",
|
||||||
|
"_%n image was analyzed_::_%n images were analyzed_" : ["已分析 %n 張圖片"],
|
||||||
|
"Analyzing images" : "正在分析圖片",
|
||||||
|
"_%n image detected_::_%n images detected_" : ["已偵測 %n 張圖片"],
|
||||||
|
"_%n image in queue_::_%n images in queue_" : ["有 %n 張圖片在佇列中"],
|
||||||
|
"Ends approximately {estimatedFinalize}" : "大約以 {estimatedFinalize} 結尾",
|
||||||
|
"The analysis is not started yet" : "分析尚未開始",
|
||||||
|
"The changes were saved. It will be taken into account in the next analysis." : "變更已儲存。將會在下一次分析中計入。",
|
||||||
|
"The change could not be applied." : "無法套用變更。",
|
||||||
|
"Cancel" : "取消",
|
||||||
|
"Rename person" : "重新命名人",
|
||||||
|
"Please enter a name to rename the person" : "請輸入名稱以重新命名此人",
|
||||||
|
"Rename" : "重新命名",
|
||||||
|
"Please assign a name to this person." : "請為此人指定一個名字。",
|
||||||
|
"Save" : "儲存",
|
||||||
|
"There was an error trying to show your friends" : "嘗試顯示您的朋友時發生錯誤",
|
||||||
|
"The analysis is enabled, please be patient, you will soon see your friends here." : "已啟用分析,請耐心等待,您很快就會在此看到您的朋友。",
|
||||||
|
"The analysis is disabled. Soon all the information found for facial recognition will be removed." : "已停用分析。所有臉部識別找到的資料很快都會被移除。",
|
||||||
|
"There was an error renaming this person" : "重新命名此人時發生錯誤",
|
||||||
|
"Face Recognition" : "臉部識別",
|
||||||
|
"Here you can see photos of your friends that are recognized" : "在這裡,您可以看到被識別出來的朋友照片",
|
||||||
|
"Analyze my images and group my loved ones with similar faces" : "分析我的圖片,並用相似的臉部將我的親人們分組",
|
||||||
|
"Looking for your recognized friends" : "尋找您已識別的朋友",
|
||||||
|
"The analysis is disabled" : "已停用分析",
|
||||||
|
"Enable it to find your loved ones" : "啟用它來尋找您的親人",
|
||||||
|
"Your friends have not been recognized yet" : "您的朋友還沒被識別",
|
||||||
|
"Please, be patient" : "請稍候",
|
||||||
|
"You will lose all the information analyzed, and if you re-enable it, you will start from scratch." : "您將會遺失所有已分析的資訊,如果您重新啟用它,將會從頭開始。",
|
||||||
|
"Do you want to deactivate the grouping by faces?" : "您想要停用按臉部分組嗎?",
|
||||||
|
"There was an error when trying to find photos of your friend" : "嘗試尋找您朋友的照片時發生錯誤",
|
||||||
|
"There was an error renaming this cluster of faces" : "重新命名這組臉孔時發生錯誤",
|
||||||
|
"_%n image_::_%n images_" : ["%n 張圖片"],
|
||||||
|
"You must be administrator to configure this feature" : "您必須是管理員才能設定此功能",
|
||||||
|
"The format seems to be incorrect." : "格式似乎不正確。",
|
||||||
|
"Seems you haven't set up any analysis model yet" : "您似乎尚未設定任何分析模型",
|
||||||
|
"The minimum recommended area is %s" : "最小推薦面積為 %s",
|
||||||
|
"The maximum recommended area is %s" : "最大推薦面積為 %s",
|
||||||
|
"The model does not recommend an area greater than %s" : "模型建議的面積不大於 %s",
|
||||||
|
"It seems you don't have any model installed." : "看來您沒有安裝任何模型。",
|
||||||
|
"Not installed" : "未安裝",
|
||||||
|
"A face recognition app" : "臉部識別應用程式",
|
||||||
|
"**Detect and group faces of your loved one in your cloud**\n\n⚠️ This application requires minimum of 1GB of RAM memory to work! See [Requirements](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations) for details.\n\n⚠️ Setup of this app requires access to terminal and even getting your hands dirty with installation of additional software. See [Installation](https://github.com/matiasdelellis/facerecognition/wiki/Installation) for details.\n\n- **😏 Detect faces from images:** Use FaceRecognition app to detect _any_ face in _any_ of your images!\n- **👪 Group faces to persons:** Detected faces are grouped together based on similarity and then FaceRecognition app can recognize persons!\n- **🔒 Built-in privacy:** No data is leaving your cloud. Defaults are always off and each user controls enabling/disabling face detection. Images from every directory can be excluded from face detection, if needed.\n- **⚙️ Power of AI:** FaceRecognition app leverages power of AI and already built neural network models through extensive usage of [DLib](http://dlib.net/) library.\n- **🚀 Build your own thing:** FaceRecognition app is just a basic building block. Through FaceRecognition API, you can build your advanced scenarios - automatically add tags to images, connect contacts and persons, share images from specific person… We want to hear your ideas!" : "**在您的雲端上偵測並為您親人的臉部分組**\n\n⚠️ 此應用程式最少需要 1GB 的記憶體才能運作!請檢視[需求](https://github.com/matiasdelellis/facerecognition/wiki/Requirements-and-Limitations)以取得詳細資訊。\n\n⚠️ 安裝此應用程式需要使用終端機,並弄髒您的手安裝其他軟體。請檢視[安裝](https://github.com/matiasdelellis/facerecognition/wiki/Installation)以取得詳細資訊。\n\n- **😏 從圖片中偵測人臉:** 使用臉部識別應用程式來偵測您任何圖片中的任何臉孔!\n- **👪 將人們的臉孔分組:** 根據臉部的相似程度將臉孔分組,然後臉部識別應用程式就可以將其分組!\n- **🔒 內建的隱私:** 沒有資料會離開您的雲端。預設是關閉的,每個使用者都可以啟用/停用臉部偵測。如果需要的話,每個目錄的圖片都可以從臉部偵測排除。\n- **⚙️ AI 的力量:** 臉部識別應用程式透過廣泛使用 [DLib](http://dlib.net/) 函式庫來利用 AI 的功能與已經建立的神經網路模型。\n- **🚀 建構您自己的東西:** 臉部識別應用程式只是一個基本的建構區塊。透過臉部識別 API,您可以建構您自己的進階應用場景,自訂將標籤新增到圖片中、分享特定人的圖片……。我們想要聽到您的想法!",
|
||||||
|
"Facial recognition is disabled" : "臉部識別已停用",
|
||||||
|
"Facial recognition is disabled for this folder" : "此資料夾的臉部識別已被停用",
|
||||||
|
"The type of storage is not supported to analyze your photos" : "不支援使用儲存類型來分析您的照片",
|
||||||
|
"No people found" : "找不到人",
|
||||||
|
"This image is not yet analyzed" : "此圖片尚未分析",
|
||||||
|
"Search for persons in the photos of this directory" : "在此目錄中的照片搜尋人",
|
||||||
|
"Photos that are not in the gallery are also ignored" : "不在相簿中的照片將會被忽略",
|
||||||
|
"See <a target=\"_blank\" href=\"{docsLink}\">documentation ↗</a>." : "檢視<a target=\"_blank\" href=\"{docsLink}\">文件 ↗</a>。",
|
||||||
|
"Open <a target=\"_blank\" href=\"{settingsLink}\">settings ↗</a> to enable it" : "開啟<a target=\"_blank\" href=\"{settingsLink}\">設定 ↗</a>以啟用它",
|
||||||
|
"Open Documentation" : "開啟文件",
|
||||||
|
"Temporary files" : "暫時檔案",
|
||||||
|
"During analysis, temporary files are used to ensure homogeneity between all images." : "在分析過程中,暫時檔案用來確保所有圖片間的同質性。",
|
||||||
|
"Small images allow a quick analysis, but you can lose the smallest faces of your photos. Large images can improve the results, but the analysis will be slower." : "小圖片可以快速分析,但您可能會遺失您照片的最小照片。較大的圖片可以改善結果,但分析會比較慢。",
|
||||||
|
"Smaller images" : "較小的圖片",
|
||||||
|
"Larger images" : "較大的圖片",
|
||||||
|
"Restore" : "恢復",
|
||||||
|
"Clustering threshold" : "群組閾值",
|
||||||
|
"Persons are determined as groups of similar faces and to obtain them, all the faces found must be compared. When they are compared, a threshold is used to determine if they should be grouped." : "將人們以相似的臉孔分組,但必須比較所有找到的臉孔才能確定。比較它們時,將會使用閾值來決定是否要分成同一組。",
|
||||||
|
"A small threshold will only group very similar faces, but initially you will have many groups to name. A larger threshold is more flexible to group the faces and obtaining fewer groups, but being able to confuse similar persons." : "較小的閾值只會將非常相似的臉孔分成一組,但ㄧ開始您會有很多需要命名的組別。較大的閾值可以更靈活地將臉孔分組,但可能會被相似的人迷惑。",
|
||||||
|
"Small threshold" : "小閾值",
|
||||||
|
"Higher threshold" : "較高的閾值",
|
||||||
|
"Minimum confidence" : "最低信賴程度",
|
||||||
|
"The minimum confidence determines how reliable must be a face detection to try to group it. Blurred or misaligned faces would have a confidence closer to 0.0, and the best images close to 1.0." : "最低信賴程度決定了對臉部偵測所必須具備的可靠程度。模糊或未對齊的可靠程度趨近於 0.0,最佳圖片則趨近於 1.0。",
|
||||||
|
"Lower minimum confidence" : "較低的最低信賴程度",
|
||||||
|
"Higher minimum confidence" : "較高的最低信賴程度",
|
||||||
|
"Configuration information" : "設定資訊",
|
||||||
|
"Current model:" : "目前的模型:",
|
||||||
|
"Current status" : "目前的狀態",
|
||||||
|
"Stopped" : "已停止"
|
||||||
|
},"pluralForm" :"nplurals=1; plural=0;"
|
||||||
|
}
|
||||||
181
facerecognition/lib/Album/AlbumMapper.php
Normal file
181
facerecognition/lib/Album/AlbumMapper.php
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Album;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Utility\ITimeFactory;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\Files\IMimeTypeLoader;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\IGroup;
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
use OCP\IGroupManager;
|
||||||
|
|
||||||
|
class AlbumMapper {
|
||||||
|
|
||||||
|
private IDBConnection $connection;
|
||||||
|
private ITimeFactory $timeFactory;
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $connection,
|
||||||
|
ITimeFactory $timeFactory
|
||||||
|
) {
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->timeFactory = $timeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(string $userId, string $name, string $location = 'Face Recognition'): int {
|
||||||
|
$created = $this->timeFactory->getTime();
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->insert("photos_albums")
|
||||||
|
->values([
|
||||||
|
'user' => $query->createNamedParameter($userId),
|
||||||
|
'name' => $query->createNamedParameter($name),
|
||||||
|
'location' => $query->createNamedParameter($location),
|
||||||
|
'created' => $query->createNamedParameter($created, IQueryBuilder::PARAM_INT),
|
||||||
|
'last_added_photo' => $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT),
|
||||||
|
]);
|
||||||
|
$query->executeStatement();
|
||||||
|
|
||||||
|
return $query->getLastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $userId, string $name, string $location = 'Face Recognition'): int {
|
||||||
|
$qb = $this->connection->getQueryBuilder();
|
||||||
|
$qb->select('album_id')
|
||||||
|
->from('photos_albums')
|
||||||
|
->where($qb->expr()->eq('name', $qb->createNamedParameter($name)))
|
||||||
|
->andWhere($qb->expr()->eq('location', $qb->createNamedParameter($location)))
|
||||||
|
->andWhere($qb->expr()->eq('user', $qb->createNamedParameter($userId)));
|
||||||
|
|
||||||
|
$id = $qb->executeQuery()->fetchOne();
|
||||||
|
if ($id === false) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return (int)$id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll(string $userId, string $location = 'Face Recognition'): array {
|
||||||
|
$qb = $this->connection->getQueryBuilder();
|
||||||
|
$qb->select('name')
|
||||||
|
->from('photos_albums')
|
||||||
|
->where($qb->expr()->eq('location', $qb->createNamedParameter($location)))
|
||||||
|
->andWhere($qb->expr()->eq('user', $qb->createNamedParameter($userId)));
|
||||||
|
$rows = $qb->executeQuery()->fetchAll();
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$result[] = (string)$row['name'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(int $albumId): void {
|
||||||
|
$this->connection->beginTransaction();
|
||||||
|
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->delete("photos_albums")
|
||||||
|
->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));
|
||||||
|
$query->executeStatement();
|
||||||
|
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->delete("photos_albums_files")
|
||||||
|
->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));
|
||||||
|
$query->executeStatement();
|
||||||
|
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->delete("photos_albums_collabs")
|
||||||
|
->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));
|
||||||
|
$query->executeStatement();
|
||||||
|
|
||||||
|
$this->connection->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $albumId
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getFiles(int $albumId): array {
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select('file_id')
|
||||||
|
->from('photos_albums_files')
|
||||||
|
->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId)));
|
||||||
|
$rows = $query->executeQuery()->fetchAll();
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$result[] = (int)$row['file_id'];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFile(int $albumId, int $fileId, string $owner): void {
|
||||||
|
$added = $this->timeFactory->getTime();
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->insert('photos_albums_files')
|
||||||
|
->values([
|
||||||
|
'album_id' => $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT),
|
||||||
|
'file_id' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
|
||||||
|
'added' => $query->createNamedParameter($added, IQueryBuilder::PARAM_INT),
|
||||||
|
'owner' => $query->createNamedParameter($owner),
|
||||||
|
]);
|
||||||
|
$query->executeStatement();
|
||||||
|
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->update('photos_albums')
|
||||||
|
->set('last_added_photo', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))
|
||||||
|
->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));
|
||||||
|
$query->executeStatement();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeFile(int $albumId, int $fileId): void {
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->delete('photos_albums_files')
|
||||||
|
->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($query->expr()->eq('file_id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
|
||||||
|
$query->executeStatement();
|
||||||
|
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->update('photos_albums')
|
||||||
|
->set('last_added_photo', $query->createNamedParameter($this->getLastAdded($albumId), IQueryBuilder::PARAM_INT))
|
||||||
|
->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)));
|
||||||
|
$query->executeStatement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLastAdded(int $albumId): int {
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select('file_id')
|
||||||
|
->from('photos_albums_files')
|
||||||
|
->where($query->expr()->eq('album_id', $query->createNamedParameter($albumId, IQueryBuilder::PARAM_INT)))
|
||||||
|
->orderBy('added', 'DESC')
|
||||||
|
->setMaxResults(1);
|
||||||
|
$id = $query->executeQuery()->fetchOne();
|
||||||
|
if ($id === false) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return (int)$id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
81
facerecognition/lib/AppInfo/Application.php
Normal file
81
facerecognition/lib/AppInfo/Application.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl>
|
||||||
|
* @copyright Copyright (c) 2017-2022 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2020 Xiangbin Li >dassio@icloud.com>
|
||||||
|
*
|
||||||
|
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\AppInfo;
|
||||||
|
|
||||||
|
use OCP\AppFramework\App;
|
||||||
|
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||||
|
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||||
|
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||||
|
|
||||||
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
|
|
||||||
|
use OCA\Files\Event\LoadSidebar;
|
||||||
|
use OCP\Files\Events\Node\NodeDeletedEvent;
|
||||||
|
use OCP\Files\Events\Node\NodeWrittenEvent;
|
||||||
|
use OCP\User\Events\UserDeletedEvent;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Listener\LoadSidebarListener;
|
||||||
|
use OCA\FaceRecognition\Listener\PostDeleteListener;
|
||||||
|
use OCA\FaceRecognition\Listener\PostWriteListener;
|
||||||
|
use OCA\FaceRecognition\Listener\UserDeletedListener;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Search\PersonSearchProvider;
|
||||||
|
|
||||||
|
class Application extends App implements IBootstrap {
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
public const APP_NAME = 'facerecognition';
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
public const API_VERSIONS = ['v1', '2.0'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application constructor.
|
||||||
|
*
|
||||||
|
* @param array $urlParams
|
||||||
|
*/
|
||||||
|
public function __construct(array $urlParams = []) {
|
||||||
|
parent::__construct(self::APP_NAME, $urlParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(IRegistrationContext $context): void {
|
||||||
|
$context->registerSearchProvider(PersonSearchProvider::class);
|
||||||
|
|
||||||
|
$context->registerCapability(Capabilities::class);
|
||||||
|
|
||||||
|
$context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class);
|
||||||
|
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
|
||||||
|
|
||||||
|
$context->registerEventListener(NodeWrittenEvent::class, PostWriteListener::class);
|
||||||
|
$context->registerEventListener(NodeDeletedEvent::class, PostDeleteListener::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(IBootContext $context): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
facerecognition/lib/AppInfo/Capabilities.php
Normal file
60
facerecognition/lib/AppInfo/Capabilities.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/*
|
||||||
|
* @copyright 2022 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author 2022 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\AppInfo;
|
||||||
|
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\IConfig;
|
||||||
|
|
||||||
|
use OCP\Capabilities\ICapability;
|
||||||
|
|
||||||
|
class Capabilities implements ICapability {
|
||||||
|
|
||||||
|
/** @var IAppManager */
|
||||||
|
private $appManager;
|
||||||
|
|
||||||
|
/** @var IConfig */
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct(IAppManager $appManager,
|
||||||
|
IConfig $config,
|
||||||
|
$userId) {
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
$this->config = $config;
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCapabilities() {
|
||||||
|
return [
|
||||||
|
Application::APP_NAME => [
|
||||||
|
'version' => $this->appManager->getAppVersion(Application::APP_NAME),
|
||||||
|
'apiVersions' => Application::API_VERSIONS,
|
||||||
|
'enabled' => boolval($this->config->getUserValue($this->userId, Application::APP_NAME, 'enabled', false)),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
181
facerecognition/lib/BackgroundJob/BackgroundService.php
Normal file
181
facerecognition/lib/BackgroundJob/BackgroundService.php
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob;
|
||||||
|
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\AppInfo\Application;
|
||||||
|
use OCA\FaceRecognition\Helper\Requirements;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\Tasks\AddMissingImagesTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\Tasks\CheckCronTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\Tasks\CheckRequirementsTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\Tasks\CreateClustersTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\Tasks\DisabledUserRemovalTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\Tasks\EnumerateImagesMissingFacesTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\Tasks\ImageProcessingTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\Tasks\StaleImagesRemovalTask;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background service. Both command and cron job are calling this service for long-running background operations.
|
||||||
|
* Background processing for face recognition is comprised of several steps, called tasks. Each task is independent,
|
||||||
|
* idempotent, DI-aware logic unit that yields. Since tasks are non-preemptive, they should yield from time to time,
|
||||||
|
* so we son't end up working for more than given timeout.
|
||||||
|
*
|
||||||
|
* Tasks can be seen as normal sequential functions, but they are easier to work with,
|
||||||
|
* reason about them and test them independently. Other than that, they are really glorified functions.
|
||||||
|
*/
|
||||||
|
class BackgroundService {
|
||||||
|
|
||||||
|
/** @var Application $application */
|
||||||
|
private $application;
|
||||||
|
|
||||||
|
/** @var FaceRecognitionContext $context */
|
||||||
|
private $context;
|
||||||
|
|
||||||
|
public function __construct(Application $application, FaceRecognitionContext $context) {
|
||||||
|
$this->application = $application;
|
||||||
|
$this->context = $context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLogger(OutputInterface $logger): void {
|
||||||
|
if (!is_null($this->context->logger)) {
|
||||||
|
// If you get this exception, it means you already initialized context->logger. Double-check your flow.
|
||||||
|
throw new \LogicException('You cannot call setLogger after you set it once');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->context->logger = new FaceRecognitionLogger($logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts background tasks sequentially.
|
||||||
|
*
|
||||||
|
* @param int $timeout Maximum allowed time (in seconds) to execute
|
||||||
|
* @param bool $verbose Whether to be more verbose
|
||||||
|
* @param IUser|null $user ID of user to execute background operations for
|
||||||
|
* @param int|null $maxImageArea Max image area (in pixels^2) to be fed to neural network when doing face detection
|
||||||
|
* @param string $runMode The command execution mode
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function execute(int $timeout, bool $verbose, IUser $user = null, int $maxImageArea = null, string $runMode) {
|
||||||
|
// Put to context all the stuff we are figuring only now
|
||||||
|
//
|
||||||
|
$this->context->user = $user;
|
||||||
|
$this->context->verbose = $verbose;
|
||||||
|
$this->context->setRunningThroughCommand();
|
||||||
|
$this->context->propertyBag['max_image_area'] = $maxImageArea;
|
||||||
|
$this->context->propertyBag['run_mode'] = $runMode;
|
||||||
|
|
||||||
|
// Here we are defining all the tasks that will get executed.
|
||||||
|
//
|
||||||
|
$task_classes = [
|
||||||
|
CheckRequirementsTask::class,
|
||||||
|
CheckCronTask::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
switch ($runMode)
|
||||||
|
{
|
||||||
|
case 'sync-mode':
|
||||||
|
$task_classes[] = DisabledUserRemovalTask::class;
|
||||||
|
$task_classes[] = StaleImagesRemovalTask::class;
|
||||||
|
$task_classes[] = AddMissingImagesTask::class;
|
||||||
|
break;
|
||||||
|
case 'analyze-mode':
|
||||||
|
$task_classes[] = EnumerateImagesMissingFacesTask::class;
|
||||||
|
$task_classes[] = ImageProcessingTask::class;
|
||||||
|
break;
|
||||||
|
case 'cluster-mode':
|
||||||
|
$task_classes[] = CreateClustersTask::class;
|
||||||
|
break;
|
||||||
|
case 'defer-mode':
|
||||||
|
$task_classes[] = DisabledUserRemovalTask::class;
|
||||||
|
$task_classes[] = StaleImagesRemovalTask::class;
|
||||||
|
$task_classes[] = AddMissingImagesTask::class;
|
||||||
|
$task_classes[] = EnumerateImagesMissingFacesTask::class;
|
||||||
|
$task_classes[] = ImageProcessingTask::class;
|
||||||
|
$task_classes[] = CreateClustersTask::class;
|
||||||
|
break;
|
||||||
|
case 'default-mode':
|
||||||
|
default:
|
||||||
|
$task_classes[] = DisabledUserRemovalTask::class;
|
||||||
|
$task_classes[] = StaleImagesRemovalTask::class;
|
||||||
|
$task_classes[] = CreateClustersTask::class;
|
||||||
|
$task_classes[] = AddMissingImagesTask::class;
|
||||||
|
$task_classes[] = EnumerateImagesMissingFacesTask::class;
|
||||||
|
$task_classes[] = ImageProcessingTask::class;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main logic to iterate over all tasks and executes them.
|
||||||
|
//
|
||||||
|
$startTime = time();
|
||||||
|
for ($i=0, $task_classes_count = count($task_classes); $i < $task_classes_count; $i++) {
|
||||||
|
$task_class = $task_classes[$i];
|
||||||
|
$task = $this->application->getContainer()->query($task_class);
|
||||||
|
$this->context->logger->logInfo(sprintf("%d/%d - Executing task %s (%s)",
|
||||||
|
$i+1, count($task_classes), (new \ReflectionClass($task_class))->getShortName(), $task->description()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$generator = $task->execute($this->context);
|
||||||
|
// $generator can be either actual Generator or return value of boolean.
|
||||||
|
// If it is Generator object, that means execute() had some yields.
|
||||||
|
// Iterate through those yields and we will get end result through getReturn().
|
||||||
|
if ($generator instanceof \Generator) {
|
||||||
|
foreach ($generator as $_) {
|
||||||
|
$currentTime = time();
|
||||||
|
if (($timeout > 0) && ($currentTime - $startTime > $timeout)) {
|
||||||
|
$this->context->logger->logInfo("Time out. Quitting...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->context->verbose) {
|
||||||
|
$this->context->logger->logDebug('yielding');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$shouldContinue = ($generator instanceof \Generator) ? $generator->getReturn() : $generator;
|
||||||
|
if (!$shouldContinue) {
|
||||||
|
$this->context->logger->logInfo(
|
||||||
|
sprintf("Task %s signalled we should not continue, bailing out",
|
||||||
|
(new \ReflectionClass($task_class))->getShortName()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Any exception is fatal, and we should quit background job
|
||||||
|
//
|
||||||
|
$this->context->logger->logInfo("Error during background task execution");
|
||||||
|
$this->context->logger->logInfo("If error is not transient, this means that core component of face recognition is not working properly");
|
||||||
|
$this->context->logger->logInfo("and that quantity and quality of detected faces and person will be low or suboptimal.");
|
||||||
|
$this->context->logger->logInfo("You probably want to file an issue (please include exception below) to: https://github.com/matiasdelellis/facerecognition/issues");
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that each face recognition background task should implement
|
||||||
|
*/
|
||||||
|
interface IFaceRecognitionBackgroundTask {
|
||||||
|
/**
|
||||||
|
* Returns task's description.
|
||||||
|
*
|
||||||
|
* @return string Description of what task do
|
||||||
|
*/
|
||||||
|
public function description();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes task.
|
||||||
|
*
|
||||||
|
* @param FaceRecognitionContext $context Face recognition context
|
||||||
|
* @return \Generator|bool Since we are yielding, return type is either Generator, or boolean (actual return).
|
||||||
|
* Return value specifies should we continue execution. True if we should continue, false if we should bail out.
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract implementation for background task, serves as a helper for common functions.
|
||||||
|
*/
|
||||||
|
abstract class FaceRecognitionBackgroundTask implements IFaceRecognitionBackgroundTask {
|
||||||
|
|
||||||
|
/** @var FaceRecognitionContext $context */
|
||||||
|
protected $context;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets context for a given task, so it can be accessed in task (without a need to dragging it around from execute() method).
|
||||||
|
* Currently public, because of tests (ideally it should be protected).
|
||||||
|
*
|
||||||
|
* @param FaceRecognitionContext $context Context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setContext(FaceRecognitionContext $context): void {
|
||||||
|
$this->context = $context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for info logging. It using this log call, it will indent log messages,
|
||||||
|
* so there is nice visual that those messages belongs to particular task.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function logInfo(string $message): void {
|
||||||
|
$this->context->logger->logInfo("\t" . $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for debug logging. It using this log call, it will indent log messages,
|
||||||
|
* so there is nice visual that those messages belongs to particular task.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function logDebug(string $message): void {
|
||||||
|
if ($this->context->verbose) {
|
||||||
|
$this->context->logger->logDebug("\t" . $message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
facerecognition/lib/BackgroundJob/FaceRecognitionContext.php
Normal file
92
facerecognition/lib/BackgroundJob/FaceRecognitionContext.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob;
|
||||||
|
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionLogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple class holding all information that tasks might need, so they can do their job.
|
||||||
|
* It can also serve as a temporary storage of information flowing from one task to another.
|
||||||
|
*/
|
||||||
|
class FaceRecognitionContext {
|
||||||
|
/** @var IUserManager */
|
||||||
|
public $userManager;
|
||||||
|
|
||||||
|
/** @var IConfig */
|
||||||
|
public $config;
|
||||||
|
|
||||||
|
/** @var FaceRecognitionLogger */
|
||||||
|
public $logger;
|
||||||
|
|
||||||
|
/** @var IUser|null */
|
||||||
|
public $user;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
public $verbose;
|
||||||
|
|
||||||
|
/** @var array Associative array that can hold various data from tasks */
|
||||||
|
public $propertyBag = [];
|
||||||
|
|
||||||
|
/** @var bool True if we are running from command, false if we are running as background job */
|
||||||
|
private $isRunningThroughCommand;
|
||||||
|
|
||||||
|
public function __construct(IUserManager $userManager,
|
||||||
|
IConfig $config) {
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->config = $config;
|
||||||
|
$this->isRunningThroughCommand = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEligibleUsers(): array {
|
||||||
|
$eligable_users = [];
|
||||||
|
if (!is_null($this->user)) {
|
||||||
|
$eligable_users[] = $this->user->getUID();
|
||||||
|
} else {
|
||||||
|
$this->userManager->callForSeenUsers(function (IUser $user) use (&$eligable_users) {
|
||||||
|
$eligable_users[] = $user->getUID();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return $eligable_users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRunningInSyncMode(): bool {
|
||||||
|
if ((array_key_exists('run_mode', $this->propertyBag)) &&
|
||||||
|
(!is_null($this->propertyBag['run_mode']))) {
|
||||||
|
return ($this->propertyBag['run_mode'] === 'sync-mode');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRunningThroughCommand(): bool {
|
||||||
|
return $this->isRunningThroughCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRunningThroughCommand(): void {
|
||||||
|
$this->isRunningThroughCommand = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
facerecognition/lib/BackgroundJob/FaceRecognitionLogger.php
Normal file
80
facerecognition/lib/BackgroundJob/FaceRecognitionLogger.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger class that encapsulates logging for background tasks. Reason for this
|
||||||
|
* class is because background processing can be called as either command, as
|
||||||
|
* well as background job, so we need a way to unify logging for both.
|
||||||
|
*/
|
||||||
|
class FaceRecognitionLogger {
|
||||||
|
/** @var LoggerInterface */
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
/** @var OutputInterface */
|
||||||
|
private $output;
|
||||||
|
|
||||||
|
public function __construct($logger) {
|
||||||
|
if (method_exists($logger, 'info') && method_exists($logger, 'debug')) {
|
||||||
|
$this->logger = $logger;
|
||||||
|
} else if ($logger instanceof OutputInterface) {
|
||||||
|
$this->output = $logger;
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException("Logger must be either instance of LoggerInterface or OutputInterface");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns logger, if it is set
|
||||||
|
*
|
||||||
|
* @return LoggerInterface|null Logger, if it is set.
|
||||||
|
*/
|
||||||
|
public function getLogger() {
|
||||||
|
return $this->logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logInfo(string $message): void {
|
||||||
|
if (!is_null($this->logger)) {
|
||||||
|
$this->logger->info($message);
|
||||||
|
} else if (!is_null($this->output)) {
|
||||||
|
$this->output->writeln($message);
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException("There are no configured loggers. Please file an issue at https://github.com/matiasdelellis/facerecognition/issues");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logDebug(string $message): void {
|
||||||
|
if (!is_null($this->logger)) {
|
||||||
|
$this->logger->debug($message);
|
||||||
|
} else if (!is_null($this->output)) {
|
||||||
|
$this->output->writeln($message);
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException("There are no configured loggers. Please file an issue at https://github.com/matiasdelellis/facerecognition/issues");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
154
facerecognition/lib/BackgroundJob/Tasks/AddMissingImagesTask.php
Normal file
154
facerecognition/lib/BackgroundJob/Tasks/AddMissingImagesTask.php
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
|
||||||
|
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use OCP\Files\File;
|
||||||
|
use OCP\Files\Folder;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\FileService;
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task that, for each user, crawls for all images in filesystem and insert them in database.
|
||||||
|
* This is job that normally does file watcher, but this should be done at least once,
|
||||||
|
* after app is installed (or re-enabled).
|
||||||
|
*/
|
||||||
|
class AddMissingImagesTask extends FaceRecognitionBackgroundTask {
|
||||||
|
const FULL_IMAGE_SCAN_DONE_KEY = "full_image_scan_done";
|
||||||
|
|
||||||
|
/** @var ImageMapper Image mapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var FileService */
|
||||||
|
private $fileService;
|
||||||
|
|
||||||
|
/** @var SettingsService Settings service */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ImageMapper $imageMapper Image mapper
|
||||||
|
* @param FileService $fileService File Service
|
||||||
|
* @param SettingsService $settingsService Settings Service
|
||||||
|
*/
|
||||||
|
public function __construct(ImageMapper $imageMapper,
|
||||||
|
FileService $fileService,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function description() {
|
||||||
|
return "Crawl for missing images for each user and insert them in DB";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context) {
|
||||||
|
$this->setContext($context);
|
||||||
|
|
||||||
|
// Check if we are called for one user only, or for all user in instance.
|
||||||
|
$insertedImages = 0;
|
||||||
|
$eligable_users = $this->context->getEligibleUsers();
|
||||||
|
foreach($eligable_users as $user) {
|
||||||
|
if (!$this->settingsService->getUserEnabled($user)) {
|
||||||
|
// Completely skip this task for this user, seems that disable analysis
|
||||||
|
$this->logInfo('Skipping image scan for user ' . $user . ' that has disabled the analysis');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->context->isRunningInSyncMode() &&
|
||||||
|
$this->settingsService->getUserFullScanDone($user)) {
|
||||||
|
// Completely skip this task for this user, seems that we already did full scan for him
|
||||||
|
$this->logDebug('Skipping full image scan for user ' . $user);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$insertedImages += $this->addMissingImagesForUser($user, $this->settingsService->getCurrentFaceModel());
|
||||||
|
$this->settingsService->setUserFullScanDone(true, $user);
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->context->propertyBag['AddMissingImagesTask_insertedImages'] = $insertedImages;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crawl filesystem for a given user
|
||||||
|
*
|
||||||
|
* @param string $userId ID of the user for which to crawl images for
|
||||||
|
* @param int $model Used model
|
||||||
|
* @return int Number of missing images found
|
||||||
|
*/
|
||||||
|
private function addMissingImagesForUser(string $userId, int $model): int {
|
||||||
|
$this->logInfo(sprintf('Finding missing images for user %s', $userId));
|
||||||
|
$this->fileService->setupFS($userId);
|
||||||
|
|
||||||
|
$userFolder = $this->fileService->getUserFolder($userId);
|
||||||
|
return $this->parseUserFolder($userId, $model, $userFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively crawls given folder for a given user
|
||||||
|
*
|
||||||
|
* @param int $model Used model
|
||||||
|
* @param Folder $folder Folder to recursively search images in
|
||||||
|
* @return int Number of missing images found
|
||||||
|
*/
|
||||||
|
private function parseUserFolder(string $userId, int $model, Folder $folder): int {
|
||||||
|
$insertedImages = 0;
|
||||||
|
$nodes = $this->fileService->getPicturesFromFolder($folder);
|
||||||
|
foreach ($nodes as $file) {
|
||||||
|
$this->logDebug('Found ' . $file->getPath());
|
||||||
|
|
||||||
|
$image = new Image();
|
||||||
|
$image->setUser($userId);
|
||||||
|
$image->setFile($file->getId());
|
||||||
|
$image->setModel($model);
|
||||||
|
// todo: this check/insert logic for each image is so inefficient it hurts my mind
|
||||||
|
if ($this->imageMapper->imageExists($image) === null) {
|
||||||
|
// todo: can we have larger transaction with bulk insert?
|
||||||
|
$this->imageMapper->insert($image);
|
||||||
|
$insertedImages++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $insertedImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
59
facerecognition/lib/BackgroundJob/Tasks/CheckCronTask.php
Normal file
59
facerecognition/lib/BackgroundJob/Tasks/CheckCronTask.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that we are started either through command, or from cron/webcron (but do not allow ajax mode)
|
||||||
|
*/
|
||||||
|
class CheckCronTask extends FaceRecognitionBackgroundTask {
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function description() {
|
||||||
|
return "Check that service is started from either cron or from command";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context) {
|
||||||
|
$this->setContext($context);
|
||||||
|
|
||||||
|
$isCommand = $context->isRunningThroughCommand();
|
||||||
|
$isBackgroundJobModeAjax = $context->config->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
|
||||||
|
if ($isCommand === false && $isBackgroundJobModeAjax === false) {
|
||||||
|
$message =
|
||||||
|
"Face recognition background service can only run with cron/webcron.\n" .
|
||||||
|
"For details, take a look at " .
|
||||||
|
"https://docs.nextcloud.com/server/14/admin_manual/configuration_server/background_jobs_configuration.html";
|
||||||
|
$this->logInfo($message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
|
||||||
|
|
||||||
|
use OCP\Util as OCP_Util;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\Imaginary;
|
||||||
|
use OCA\FaceRecognition\Helper\MemoryLimits;
|
||||||
|
use OCA\FaceRecognition\Helper\Requirements;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Model\IModel;
|
||||||
|
use OCA\FaceRecognition\Model\ModelManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Model\Exceptions\UnavailableException;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check all requirements before we start engaging in lengthy background task.
|
||||||
|
*/
|
||||||
|
class CheckRequirementsTask extends FaceRecognitionBackgroundTask {
|
||||||
|
|
||||||
|
/** @var ModelManager Model Manader */
|
||||||
|
private $modelManager;
|
||||||
|
|
||||||
|
/** @var SettingsService Settings service */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var Imaginary imaginary helper */
|
||||||
|
private $imaginaryHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ModelManager $modelManager Model Manager
|
||||||
|
* @param SettingsService $settingsService Settings service
|
||||||
|
* @param Imaginary $imaginaryHelper imaginary helper
|
||||||
|
*/
|
||||||
|
public function __construct(ModelManager $modelManager,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
Imaginary $imaginaryHelper)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->modelManager = $modelManager;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->imaginaryHelper = $imaginaryHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function description() {
|
||||||
|
return "Check all requirements";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context) {
|
||||||
|
$this->setContext($context);
|
||||||
|
|
||||||
|
$system = php_uname("s");
|
||||||
|
$this->logDebug("System: " . $system);
|
||||||
|
|
||||||
|
$systemMemory = MemoryLimits::getSystemMemory();
|
||||||
|
$this->logDebug("System memory: " . ($systemMemory > 0 ? $systemMemory : "Unknown"));
|
||||||
|
|
||||||
|
$phpMemory = MemoryLimits::getPhpMemory();
|
||||||
|
$this->logDebug("PHP Memory Limit: " . ($phpMemory > 0 ? $phpMemory : "Unknown"));
|
||||||
|
|
||||||
|
$this->logDebug("Clustering backend: " . (Requirements::pdlibLoaded() ? "pdlib" : "PHP (Not recommended.)"));
|
||||||
|
|
||||||
|
if ($this->imaginaryHelper->isEnabled()) {
|
||||||
|
$this->logDebug("Image Backend: Imaginary");
|
||||||
|
$version = $this->imaginaryHelper->getVersion();
|
||||||
|
if ($version) {
|
||||||
|
$this->logDebug("Imaginary version: " . $version);
|
||||||
|
} else {
|
||||||
|
$imaginaryUrl = $this->imaginaryHelper->getUrl();
|
||||||
|
$error_message =
|
||||||
|
"An Imaginary service (" . $imaginaryUrl . ") was configured to manage temporary images, but it is inaccessible." .
|
||||||
|
"Check out the service, or set the 'preview_imaginary_url' key appropriately.";
|
||||||
|
$this->logInfo($error_message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->logDebug("Image Backend: Imagick");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Requirements::hasEnoughMemory()) {
|
||||||
|
$error_message =
|
||||||
|
"Your system does not meet the minimum of memory requirements.\n" .
|
||||||
|
"Face recognition application requires at least " . OCP_Util::humanFileSize(SettingsService::MINIMUM_SYSTEM_MEMORY_REQUIREMENTS) . " of system memory.\n" .
|
||||||
|
"See https://github.com/matiasdelellis/facerecognition/wiki/Performance-analysis-of-DLib%E2%80%99s-CNN-face-detection for more details\n\n" .
|
||||||
|
"Fill an issue here if that doesn't help: https://github.com/matiasdelellis/facerecognition/issues";
|
||||||
|
$this->logInfo($error_message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = $this->modelManager->getCurrentModel();
|
||||||
|
if (is_null($model)) {
|
||||||
|
$error_message =
|
||||||
|
"Seems there are no installed models.\n" .
|
||||||
|
"Please read the documentation about this: https://github.com/matiasdelellis/facerecognition/wiki/Models#install-models\n" .
|
||||||
|
"and install them with the 'occ face:setup --model MODEL_ID' command.\n\n" .
|
||||||
|
"Fill an issue here if that doesn't help: https://github.com/matiasdelellis/facerecognition/issues";
|
||||||
|
$this->logInfo($error_message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$model_message = '';
|
||||||
|
if (!$model->meetDependencies($model_message)) {
|
||||||
|
$error_message =
|
||||||
|
"Seems that don't meet the dependencies to use the model " . $model->getId() . ": " . $model->getName() . "\n".
|
||||||
|
"Resume: " . $model_message . "\n" .
|
||||||
|
"Please read the documentation for this model to continue: " . $model->getDocumentation() . "\n\n" .
|
||||||
|
"Fill an issue here if that doesn't help: https://github.com/matiasdelellis/facerecognition/issues";
|
||||||
|
$this->logInfo($error_message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$imageArea = $this->settingsService->getAnalysisImageArea();
|
||||||
|
if ($imageArea < 0) {
|
||||||
|
$error_message =
|
||||||
|
"Seems that still don't configured the image area used for temporary files.\n" .
|
||||||
|
"Please read the documentation about this: https://github.com/matiasdelellis/facerecognition/wiki/Settings#temporary-files\n" .
|
||||||
|
"and then configure it in the admin panel to continue\n\n" .
|
||||||
|
"Fill an issue here if that doesn't help: https://github.com/matiasdelellis/facerecognition/issues";
|
||||||
|
$this->logInfo($error_message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$model->open();
|
||||||
|
} catch (UnavailableException $e) {
|
||||||
|
$this->logInfo("Error accessing the model to get required information: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxImageArea = $model->getMaximumArea();
|
||||||
|
if ($imageArea > $maxImageArea) {
|
||||||
|
$error_message =
|
||||||
|
"There are inconsistencies between the configured image area (" . $imageArea. " pixels^2) which is\n" .
|
||||||
|
"greater that the maximum allowed by the model (". $maxImageArea . " pixels^2).\n" .
|
||||||
|
"Please check and fix it in the admin panel to continue.\n\n" .
|
||||||
|
"Fill an issue here if that doesn't help: https://github.com/matiasdelellis/facerecognition/issues";
|
||||||
|
$this->logInfo($error_message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
429
facerecognition/lib/BackgroundJob/Tasks/CreateClustersTask.php
Normal file
429
facerecognition/lib/BackgroundJob/Tasks/CreateClustersTask.php
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2023 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
|
||||||
|
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\Euclidean;
|
||||||
|
use OCA\FaceRecognition\Helper\Requirements;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Clusterer\ChineseWhispers;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
/**
|
||||||
|
* Taks that, for each user, creates person clusters for each.
|
||||||
|
*/
|
||||||
|
class CreateClustersTask extends FaceRecognitionBackgroundTask {
|
||||||
|
/** @var PersonMapper Person mapper*/
|
||||||
|
private $personMapper;
|
||||||
|
|
||||||
|
/** @var ImageMapper Image mapper*/
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var FaceMapper Face mapper*/
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService Settings service*/
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PersonMapper $personMapper
|
||||||
|
* @param ImageMapper $imageMapper
|
||||||
|
* @param FaceMapper $faceMapper
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
*/
|
||||||
|
public function __construct(PersonMapper $personMapper,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->personMapper = $personMapper;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function description() {
|
||||||
|
return "Create new persons or update existing persons";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context) {
|
||||||
|
$this->setContext($context);
|
||||||
|
$eligable_users = $this->context->getEligibleUsers();
|
||||||
|
foreach($eligable_users as $user) {
|
||||||
|
$this->createClusterIfNeeded($user);
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function createClusterIfNeeded(string $userId) {
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
// Depending on whether we already have clusters, decide if we should create/recreate them.
|
||||||
|
//
|
||||||
|
$hasPersons = $this->personMapper->countPersons($userId, $modelId) > 0;
|
||||||
|
if ($hasPersons) {
|
||||||
|
$forceRecreate = $this->needRecreateBySettings($userId);
|
||||||
|
$haveEnoughFaces = $this->hasNewFacesToRecreate($userId, $modelId);
|
||||||
|
$haveStaled = $this->hasStalePersonsToRecreate($userId, $modelId);
|
||||||
|
|
||||||
|
if ($forceRecreate) {
|
||||||
|
$this->logInfo('Clusters already exist, but there was some change that requires recreating the clusters');
|
||||||
|
}
|
||||||
|
else if ($haveEnoughFaces || $haveStaled) {
|
||||||
|
$this->logInfo('Face clustering will be recreated with new information or changes');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If there is no invalid persons, and there is no recent new faces, no need to recreate cluster
|
||||||
|
$this->logInfo('Clusters already exist, estimated there is no need to recreate them');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// User should not be able to use this directly, used in tests
|
||||||
|
$forceTestCreation = $this->settingsService->_getForceCreateClusters($userId);
|
||||||
|
$needCreate = $this->needCreateFirstTime($userId, $modelId);
|
||||||
|
|
||||||
|
if ($forceTestCreation) {
|
||||||
|
$this->logInfo('Force the creation of clusters for testing');
|
||||||
|
}
|
||||||
|
else if ($needCreate) {
|
||||||
|
$this->logInfo('Face clustering will be created for the first time.');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->logInfo(
|
||||||
|
'Skipping cluster creation, not enough data (yet) collected. ' .
|
||||||
|
'For cluster creation, you need either one of the following:');
|
||||||
|
$this->logInfo('* have 1000 faces already processed');
|
||||||
|
$this->logInfo('* or you need to have 95% of you images processed');
|
||||||
|
$this->logInfo('Use stats command to track progress');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok. If we are here, the clusters must be recreated.
|
||||||
|
//
|
||||||
|
|
||||||
|
$min_face_size = $this->settingsService->getMinimumFaceSize();
|
||||||
|
$min_confidence = $this->settingsService->getMinimumConfidence();
|
||||||
|
|
||||||
|
$faces = array_merge(
|
||||||
|
$this->faceMapper->getGroupableFaces($userId, $modelId, $min_face_size, $min_confidence),
|
||||||
|
$this->faceMapper->getNonGroupableFaces($userId, $modelId, $min_face_size, $min_confidence)
|
||||||
|
);
|
||||||
|
|
||||||
|
$facesCount = count($faces);
|
||||||
|
$this->logInfo('There are ' . $facesCount . ' faces for clustering');
|
||||||
|
|
||||||
|
$noSlices = 1;
|
||||||
|
$sliceSize = $facesCount;
|
||||||
|
|
||||||
|
$defaultSlice = $this->settingsService->getClusterigBatchSize();
|
||||||
|
if ($defaultSlice > 0) {
|
||||||
|
// The minimum batch size is 20000 faces
|
||||||
|
$defaultSlice = max($defaultSlice, 2000);
|
||||||
|
// The maximun batch size is the faces count.
|
||||||
|
$defaultSlice = min($defaultSlice, $facesCount);
|
||||||
|
$noSlices = intval($facesCount / $defaultSlice) + 1;
|
||||||
|
$sliceSize = ceil($facesCount / $noSlices);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logDebug('We will cluster with ' . $noSlices . ' batch(es) of ' . $sliceSize . ' faces');
|
||||||
|
|
||||||
|
$newClusters = [];
|
||||||
|
for ($i = 0; $i < $noSlices ; $i++) {
|
||||||
|
$facesSliced = array_slice($faces, $i * $sliceSize, $sliceSize);
|
||||||
|
$newClusters = array_merge($newClusters, $this->getNewClusters($facesSliced));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cluster is associative array where key is person ID.
|
||||||
|
// Value is array of face IDs. For old clusters, person IDs are some existing person IDs,
|
||||||
|
// and for new clusters is whatever chinese whispers decides to identify them.
|
||||||
|
//
|
||||||
|
$currentClusters = $this->getCurrentClusters($faces);
|
||||||
|
|
||||||
|
$this->logInfo(count($newClusters) . ' clusters found after clustering');
|
||||||
|
|
||||||
|
// New merge
|
||||||
|
$mergedClusters = $this->mergeClusters($currentClusters, $newClusters);
|
||||||
|
|
||||||
|
$this->personMapper->mergeClusterToDatabase($userId, $currentClusters, $mergedClusters);
|
||||||
|
|
||||||
|
// Remove all orphaned persons (those without any faces)
|
||||||
|
// NOTE: we will do this for all models, not just for current one, but this is not problem.
|
||||||
|
$orphansDeleted = $this->personMapper->deleteOrphaned($userId);
|
||||||
|
if ($orphansDeleted > 0) {
|
||||||
|
$this->logInfo('Deleted ' . $orphansDeleted . ' persons without faces');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevents not create/recreate the clusters unnecessarily.
|
||||||
|
|
||||||
|
$this->settingsService->setNeedRecreateClusters(false, $userId);
|
||||||
|
$this->settingsService->_setForceCreateClusters(false, $userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate whether we want to recreate clusters. We want to recreate clusters/persons if:
|
||||||
|
* - Some cluster/person is invalidated (is_valid is false for someone)
|
||||||
|
* - This means some image that belonged to this user is changed, deleted etc.
|
||||||
|
* - There are some new faces. Now, we don't want to jump the gun here. We want to either have:
|
||||||
|
* - more than 25 new faces, or
|
||||||
|
* - less than 25 new faces, but they are older than 2h
|
||||||
|
*
|
||||||
|
* (basically, we want to avoid recreating cluster for each new face being uploaded,
|
||||||
|
* however, we don't want to wait too much as clusters could be changed a lot)
|
||||||
|
*/
|
||||||
|
private function hasNewFacesToRecreate(string $userId, int $modelId): bool {
|
||||||
|
//
|
||||||
|
$facesWithoutPersons = $this->faceMapper->countFaces($userId, $modelId, true);
|
||||||
|
$this->logDebug(sprintf('Found %d faces without associated persons for user %s and model %d',
|
||||||
|
$facesWithoutPersons, $userId, $modelId));
|
||||||
|
|
||||||
|
// todo: get rid of magic numbers (move to config)
|
||||||
|
if ($facesWithoutPersons === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ($facesWithoutPersons >= 25)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// We have some faces, but not that many, let's see when oldest one is generated.
|
||||||
|
$oldestFace = $this->faceMapper->getOldestCreatedFaceWithoutPerson($userId, $modelId);
|
||||||
|
$oldestFaceTimestamp = $oldestFace->creationTime->getTimestamp();
|
||||||
|
$currentTimestamp = (new \DateTime())->getTimestamp();
|
||||||
|
$this->logDebug(sprintf('Oldest face without persons for user %s and model %d is from %s',
|
||||||
|
$userId, $modelId, $oldestFace->creationTime->format('Y-m-d H:i:s')));
|
||||||
|
|
||||||
|
// todo: get rid of magic numbers (move to config)
|
||||||
|
if ($currentTimestamp - $oldestFaceTimestamp > 2 * 60 * 60)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasStalePersonsToRecreate(string $userId, int $modelId): bool {
|
||||||
|
return $this->personMapper->countClusters($userId, $modelId, true) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function needRecreateBySettings(string $userId): bool {
|
||||||
|
return $this->settingsService->getNeedRecreateClusters($userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function needCreateFirstTime(string $userId, int $modelId): bool {
|
||||||
|
// User should not be able to use this directly, used in tests
|
||||||
|
if ($this->settingsService->_getForceCreateClusters($userId))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
$imageCount = $this->imageMapper->countUserImages($userId, $modelId);
|
||||||
|
if ($imageCount === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$imageProcessed = $this->imageMapper->countUserImages($userId, $modelId, true);
|
||||||
|
if ($imageProcessed === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// These are basic criteria without which we should not even consider creating clusters.
|
||||||
|
// These clusters will be small and not "stable" enough and we should better wait for more images to come.
|
||||||
|
// todo: get rid of magic numbers (move to config)
|
||||||
|
$facesCount = $this->faceMapper->countFaces($userId, $modelId);
|
||||||
|
if ($facesCount > 1000)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
$percentImagesProcessed = $imageProcessed / floatval($imageCount);
|
||||||
|
if ($percentImagesProcessed > 0.95)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCurrentClusters(array $faces): array {
|
||||||
|
$chineseClusters = array();
|
||||||
|
foreach($faces as $face) {
|
||||||
|
if ($face->person !== null) {
|
||||||
|
if (!isset($chineseClusters[$face->person])) {
|
||||||
|
$chineseClusters[$face->person] = array();
|
||||||
|
}
|
||||||
|
$chineseClusters[$face->person][] = $face->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $chineseClusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getNewClusters(array $faces): array {
|
||||||
|
// Clustering parameters
|
||||||
|
$sensitivity = $this->settingsService->getSensitivity();
|
||||||
|
|
||||||
|
if (Requirements::pdlibLoaded()) {
|
||||||
|
// Create edges (neighbors) for Chinese Whispers
|
||||||
|
$edges = array();
|
||||||
|
$faces_count = count($faces);
|
||||||
|
for ($i = 0; $i < $faces_count; $i++) {
|
||||||
|
$face1 = $faces[$i];
|
||||||
|
if (!isset($face1->descriptor)) {
|
||||||
|
$edges[] = array($i, $i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for ($j = $i; $j < $faces_count; $j++) {
|
||||||
|
$face2 = $faces[$j];
|
||||||
|
if (!isset($face2->descriptor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$distance = dlib_vector_length($face1->descriptor, $face2->descriptor);
|
||||||
|
if ($distance < $sensitivity) {
|
||||||
|
$edges[] = array($i, $j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the edges get the list of labels (found clusters) for each face.
|
||||||
|
$newChineseClustersByIndex = dlib_chinese_whispers($edges);
|
||||||
|
} else {
|
||||||
|
// Create edges (neighbors) for Chinese Whispers
|
||||||
|
$edges = array();
|
||||||
|
$faces_count = count($faces);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $faces_count; $i++) {
|
||||||
|
$face1 = $faces[$i];
|
||||||
|
if (!isset($face1->descriptor)) {
|
||||||
|
$edges[] = array($i, $i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for ($j = $i; $j < $faces_count; $j++) {
|
||||||
|
$face2 = $faces[$j];
|
||||||
|
if (!isset($face2->descriptor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$distance = Euclidean::distance($face1->descriptor, $face2->descriptor);
|
||||||
|
if ($distance < $sensitivity) {
|
||||||
|
$edges[] = array($i, $j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The clustering algorithm actually expects ordered lists.
|
||||||
|
$oedges = [];
|
||||||
|
ChineseWhispers::convert_unordered_to_ordered($edges, $oedges);
|
||||||
|
usort($oedges, function($a, $b) {
|
||||||
|
if ($a[0] === $b[0]) return $a[1] - $b[1];
|
||||||
|
return $a[0] - $b[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Given the edges get the list of labels (found clusters) for each face.
|
||||||
|
$newChineseClustersByIndex = [];
|
||||||
|
ChineseWhispers::predict($oedges, $newChineseClustersByIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newClusters = array();
|
||||||
|
for ($i = 0, $c = count($newChineseClustersByIndex); $i < $c; $i++) {
|
||||||
|
if (!isset($newClusters[$newChineseClustersByIndex[$i]])) {
|
||||||
|
$newClusters[$newChineseClustersByIndex[$i]] = array();
|
||||||
|
}
|
||||||
|
$newClusters[$newChineseClustersByIndex[$i]][] = $faces[$i]->id;
|
||||||
|
}
|
||||||
|
return $newClusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* todo: only reason this is public is because of tests. Go figure it out better.
|
||||||
|
*/
|
||||||
|
public function mergeClusters(array $oldCluster, array $newCluster): array {
|
||||||
|
// Create map of face transitions
|
||||||
|
$transitions = array();
|
||||||
|
foreach ($newCluster as $newPerson=>$newFaces) {
|
||||||
|
foreach ($newFaces as $newFace) {
|
||||||
|
$oldPersonFound = null;
|
||||||
|
foreach ($oldCluster as $oldPerson => $oldFaces) {
|
||||||
|
if (in_array($newFace, $oldFaces)) {
|
||||||
|
$oldPersonFound = $oldPerson;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$transitions[$newFace] = array($oldPersonFound, $newPerson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Count transitions
|
||||||
|
$transitionCount = array();
|
||||||
|
foreach ($transitions as $transition) {
|
||||||
|
$key = $transition[0] . ':' . $transition[1];
|
||||||
|
if (array_key_exists($key, $transitionCount)) {
|
||||||
|
$transitionCount[$key]++;
|
||||||
|
} else {
|
||||||
|
$transitionCount[$key] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create map of new person -> old person transitions
|
||||||
|
$newOldPersonMapping = array();
|
||||||
|
$oldPersonProcessed = array(); // store this, so we don't waste cycles for in_array()
|
||||||
|
arsort($transitionCount);
|
||||||
|
foreach ($transitionCount as $transitionKey => $count) {
|
||||||
|
$transition = explode(":", $transitionKey);
|
||||||
|
$oldPerson = intval($transition[0]);
|
||||||
|
$newPerson = intval($transition[1]);
|
||||||
|
if (!array_key_exists($newPerson, $newOldPersonMapping)) {
|
||||||
|
if (($oldPerson === 0) || (!array_key_exists($oldPerson, $oldPersonProcessed))) {
|
||||||
|
$newOldPersonMapping[$newPerson] = $oldPerson;
|
||||||
|
$oldPersonProcessed[$oldPerson] = 0;
|
||||||
|
} else {
|
||||||
|
$newOldPersonMapping[$newPerson] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Starting with new cluster, convert all new person IDs with old person IDs
|
||||||
|
$maxOldPersonId = 1;
|
||||||
|
if (count($oldCluster) > 0) {
|
||||||
|
$maxOldPersonId = (int) max(array_keys($oldCluster)) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
foreach ($newCluster as $newPerson => $newFaces) {
|
||||||
|
$oldPerson = $newOldPersonMapping[$newPerson];
|
||||||
|
if ($oldPerson === 0) {
|
||||||
|
$result[$maxOldPersonId] = $newFaces;
|
||||||
|
$maxOldPersonId++;
|
||||||
|
} else {
|
||||||
|
$result[$oldPerson] = $newFaces;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
|
||||||
|
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\FaceManagementService;
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task that, for each user, check if disabled the analysis,
|
||||||
|
* and if necessary remove data from this application
|
||||||
|
*/
|
||||||
|
class DisabledUserRemovalTask extends FaceRecognitionBackgroundTask {
|
||||||
|
|
||||||
|
/** @var ImageMapper Image mapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var FaceManagementService */
|
||||||
|
private $faceManagementService;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ImageMapper $imageMapper Image mapper
|
||||||
|
* @param FaceManagementService $faceManagementService
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
*/
|
||||||
|
public function __construct (ImageMapper $imageMapper,
|
||||||
|
FaceManagementService $faceManagementService,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->faceManagementService = $faceManagementService;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function description() {
|
||||||
|
return "Purge all the information of a user when disable the analysis.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context) {
|
||||||
|
$this->setContext($context);
|
||||||
|
|
||||||
|
// Check if we are called for one user only, or for all user in instance.
|
||||||
|
$eligable_users = $this->context->getEligibleUsers();
|
||||||
|
|
||||||
|
// Reset user datas if needed.
|
||||||
|
foreach($eligable_users as $userId) {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($userId);
|
||||||
|
$imageCount = $this->imageMapper->countUserImages($userId, $this->settingsService->getCurrentFaceModel());
|
||||||
|
if (!$userEnabled && $imageCount > 0) {
|
||||||
|
// TODO: Check that the user really has information to remove.
|
||||||
|
$this->logInfo(sprintf('Removing data from user %s that disable analysis', $userId));
|
||||||
|
$this->faceManagementService->resetAllForUser($userId);
|
||||||
|
}
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task that gets all images (from database) that don't yet have faces found (e.g. they are not processed).
|
||||||
|
* Shuffles found images and outputs them to context->propertyBag.
|
||||||
|
*/
|
||||||
|
class EnumerateImagesMissingFacesTask extends FaceRecognitionBackgroundTask {
|
||||||
|
|
||||||
|
/** @var SettingsService Settings service */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var ImageMapper Image mapper*/
|
||||||
|
protected $imageMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SettingsService $settingsService Settings service
|
||||||
|
* @param ImageMapper $imageMapper Image mapper
|
||||||
|
*/
|
||||||
|
public function __construct(SettingsService $settingsService,
|
||||||
|
ImageMapper $imageMapper)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function description() {
|
||||||
|
return "Find all images which don't have faces generated for them";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context) {
|
||||||
|
$this->setContext($context);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$images = $this->imageMapper->findImagesWithoutFaces($this->context->user, $modelId);
|
||||||
|
yield;
|
||||||
|
|
||||||
|
shuffle($images);
|
||||||
|
$this->context->propertyBag['images'] = $images;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
304
facerecognition/lib/BackgroundJob/Tasks/ImageProcessingTask.php
Normal file
304
facerecognition/lib/BackgroundJob/Tasks/ImageProcessingTask.php
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
|
||||||
|
|
||||||
|
use OCP\Image as OCP_Image;
|
||||||
|
|
||||||
|
use OCP\Files\File;
|
||||||
|
use OCP\Files\Folder;
|
||||||
|
use OCP\Lock\ILockingProvider;
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Face;
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\TempImage;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Model\IModel;
|
||||||
|
use OCA\FaceRecognition\Model\ModelManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\FileService;
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taks that get all images that are still not processed and processes them.
|
||||||
|
* Processing image means that each image is prepared, faces extracted form it,
|
||||||
|
* and for each found face - face descriptor is extracted.
|
||||||
|
*/
|
||||||
|
class ImageProcessingTask extends FaceRecognitionBackgroundTask {
|
||||||
|
|
||||||
|
/** @var ImageMapper Image mapper*/
|
||||||
|
protected $imageMapper;
|
||||||
|
|
||||||
|
/** @var FileService */
|
||||||
|
protected $fileService;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
protected $settingsService;
|
||||||
|
|
||||||
|
/** @var ModelManager $modelManager */
|
||||||
|
protected $modelManager;
|
||||||
|
|
||||||
|
/** @var ILockingProvider $lockingProvider */
|
||||||
|
protected ILockingProvider $lockingProvider;
|
||||||
|
|
||||||
|
/** @var IModel $model */
|
||||||
|
private $model;
|
||||||
|
|
||||||
|
/** @var int|null $maxImageAreaCached Maximum image area (cached, so it is not recalculated for each image) */
|
||||||
|
private $maxImageAreaCached;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ImageMapper $imageMapper Image mapper
|
||||||
|
* @param FileService $fileService
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
* @param ModelManager $modelManager Model manager
|
||||||
|
* @param ILockingProvider $lockingProvider
|
||||||
|
*/
|
||||||
|
public function __construct(ImageMapper $imageMapper,
|
||||||
|
FileService $fileService,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
ModelManager $modelManager,
|
||||||
|
ILockingProvider $lockingProvider)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->modelManager = $modelManager;
|
||||||
|
$this->lockingProvider = $lockingProvider;
|
||||||
|
|
||||||
|
$this->model = null;
|
||||||
|
$this->maxImageAreaCached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function description() {
|
||||||
|
return "Process all images to extract faces";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context) {
|
||||||
|
$this->setContext($context);
|
||||||
|
|
||||||
|
$this->logInfo('NOTE: Starting face recognition. If you experience random crashes after this point, please look FAQ at https://github.com/matiasdelellis/facerecognition/wiki/FAQ');
|
||||||
|
|
||||||
|
// Get current model.
|
||||||
|
$this->model = $this->modelManager->getCurrentModel();
|
||||||
|
|
||||||
|
// Open model.
|
||||||
|
$this->model->open();
|
||||||
|
|
||||||
|
$images = $context->propertyBag['images'];
|
||||||
|
foreach($images as $image) {
|
||||||
|
yield;
|
||||||
|
|
||||||
|
$startMillis = round(microtime(true) * 1000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get a image lock
|
||||||
|
$lockKey = 'facerecognition/' . $image->getId();
|
||||||
|
$lockType = ILockingProvider::LOCK_EXCLUSIVE;
|
||||||
|
$this->lockingProvider->acquireLock($lockKey, $lockType);
|
||||||
|
|
||||||
|
$dbImage = $this->imageMapper->find($image->getUser(), $image->getId());
|
||||||
|
if ($dbImage->getIsProcessed()) {
|
||||||
|
$this->logInfo('Faces found: 0. Image will be skipped since it was already processed.');
|
||||||
|
// Release lock of file.
|
||||||
|
$this->lockingProvider->releaseLock($lockKey, $lockType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Get an temp Image to process this image.
|
||||||
|
$tempImage = $this->getTempImage($image);
|
||||||
|
|
||||||
|
if (is_null($tempImage)) {
|
||||||
|
// If we cannot find a file probably it was deleted out of our control and we must clean our tables.
|
||||||
|
$this->settingsService->setNeedRemoveStaleImages(true, $image->user);
|
||||||
|
$this->logInfo('File with ID ' . $image->file . ' doesn\'t exist anymore, skipping it');
|
||||||
|
// Release lock of file.
|
||||||
|
$this->lockingProvider->releaseLock($lockKey, $lockType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tempImage->getSkipped() === true) {
|
||||||
|
$this->logInfo('Faces found: 0 (image will be skipped because it is too small)');
|
||||||
|
$this->imageMapper->imageProcessed($image, array(), 0);
|
||||||
|
// Release lock of file.
|
||||||
|
$this->lockingProvider->releaseLock($lockKey, $lockType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get faces in the temporary image
|
||||||
|
$tempImagePath = $tempImage->getTempPath();
|
||||||
|
$rawFaces = $this->model->detectFaces($tempImagePath);
|
||||||
|
|
||||||
|
$this->logInfo('Faces found: ' . count($rawFaces));
|
||||||
|
|
||||||
|
$faces = array();
|
||||||
|
foreach ($rawFaces as $rawFace) {
|
||||||
|
// Normalize face and landmarks from model to original size
|
||||||
|
$normFace = $this->getNormalizedFace($rawFace, $tempImage->getRatio());
|
||||||
|
// Convert from dictionary of face to our Face Db Entity.
|
||||||
|
$face = Face::fromModel($image->getId(), $normFace);
|
||||||
|
// Save the normalized Face to insert on database later.
|
||||||
|
$faces[] = $face;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save new faces fo database
|
||||||
|
$endMillis = round(microtime(true) * 1000);
|
||||||
|
$duration = (int) max($endMillis - $startMillis, 0);
|
||||||
|
$this->imageMapper->imageProcessed($image, $faces, $duration);
|
||||||
|
|
||||||
|
// Release lock of file.
|
||||||
|
$this->lockingProvider->releaseLock($lockKey, $lockType);
|
||||||
|
} catch (\OCP\Lock\LockedException $e) {
|
||||||
|
$this->logInfo('Faces found: 0. Image will be skipped because it is locked');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if ($e->getMessage() === "std::bad_alloc") {
|
||||||
|
throw new \RuntimeException("Not enough memory to run face recognition! Please look FAQ at https://github.com/matiasdelellis/facerecognition/wiki/FAQ");
|
||||||
|
}
|
||||||
|
$this->logInfo('Faces found: 0. Image will be skipped because of the following error: ' . $e->getMessage());
|
||||||
|
$this->logDebug((string) $e);
|
||||||
|
|
||||||
|
// Save an empty entry so it can be analyzed again later
|
||||||
|
$this->imageMapper->imageProcessed($image, array(), 0, $e);
|
||||||
|
} finally {
|
||||||
|
// Clean temporary image.
|
||||||
|
if (isset($tempImage)) {
|
||||||
|
$tempImage->clean();
|
||||||
|
}
|
||||||
|
// If there are temporary files from external files, they must also be cleaned.
|
||||||
|
$this->fileService->clean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an image, build a temporary image to perform the analysis
|
||||||
|
*
|
||||||
|
* return TempImage|null
|
||||||
|
*/
|
||||||
|
private function getTempImage(Image $image): ?TempImage {
|
||||||
|
// todo: check if this hits I/O (database, disk...), consider having lazy caching to return user folder from user
|
||||||
|
$file = $this->fileService->getFileById($image->getFile(), $image->getUser());
|
||||||
|
if (empty($file)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->fileService->isAllowedNode($file)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$imagePath = $this->fileService->getLocalFile($file);
|
||||||
|
if ($imagePath === null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$this->logInfo('Processing image ' . $imagePath);
|
||||||
|
|
||||||
|
$tempImage = new TempImage($imagePath,
|
||||||
|
$this->model->getPreferredMimeType(),
|
||||||
|
$this->getMaxImageArea(),
|
||||||
|
$this->settingsService->getMinimumImageSize());
|
||||||
|
|
||||||
|
return $tempImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains max image area lazily (from cache, or calculates it and puts it to cache)
|
||||||
|
*
|
||||||
|
* @return int Max image area (in pixels^2)
|
||||||
|
*/
|
||||||
|
private function getMaxImageArea(): int {
|
||||||
|
// First check if is cached
|
||||||
|
//
|
||||||
|
if (!is_null($this->maxImageAreaCached)) {
|
||||||
|
return $this->maxImageAreaCached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get this setting on main app_config.
|
||||||
|
// Note that this option has lower and upper limits and validations
|
||||||
|
$this->maxImageAreaCached = $this->settingsService->getAnalysisImageArea();
|
||||||
|
|
||||||
|
// Check if admin override it in config and it is valid value
|
||||||
|
//
|
||||||
|
$maxImageArea = $this->settingsService->getMaximumImageArea();
|
||||||
|
if ($maxImageArea > 0) {
|
||||||
|
$this->maxImageAreaCached = $maxImageArea;
|
||||||
|
}
|
||||||
|
// Also check if we are provided value from command line.
|
||||||
|
//
|
||||||
|
if ((array_key_exists('max_image_area', $this->context->propertyBag)) &&
|
||||||
|
(!is_null($this->context->propertyBag['max_image_area']))) {
|
||||||
|
$this->maxImageAreaCached = $this->context->propertyBag['max_image_area'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->maxImageAreaCached;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method, to normalize face sizes back to original dimensions, based on ratio
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function getNormalizedFace(array $rawFace, float $ratio): array {
|
||||||
|
$face = [];
|
||||||
|
$face['left'] = intval(round($rawFace['left']*$ratio));
|
||||||
|
$face['right'] = intval(round($rawFace['right']*$ratio));
|
||||||
|
$face['top'] = intval(round($rawFace['top']*$ratio));
|
||||||
|
$face['bottom'] = intval(round($rawFace['bottom']*$ratio));
|
||||||
|
$face['detection_confidence'] = $rawFace['detection_confidence'];
|
||||||
|
$face['landmarks'] = $this->getNormalizedLandmarks($rawFace['landmarks'], $ratio);
|
||||||
|
$face['descriptor'] = $rawFace['descriptor'];
|
||||||
|
return $face;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method, to normalize landmarks sizes back to original dimensions, based on ratio
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function getNormalizedLandmarks(array $rawLandmarks, float $ratio): array {
|
||||||
|
$landmarks = [];
|
||||||
|
foreach ($rawLandmarks as $rawLandmark) {
|
||||||
|
$landmark = [];
|
||||||
|
$landmark['x'] = intval(round($rawLandmark['x']*$ratio));
|
||||||
|
$landmark['y'] = intval(round($rawLandmark['y']*$ratio));
|
||||||
|
$landmarks[] = $landmark;
|
||||||
|
}
|
||||||
|
return $landmarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,208 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\BackgroundJob\Tasks;
|
||||||
|
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use OCP\Files\File;
|
||||||
|
use OCP\Files\Folder;
|
||||||
|
use OCP\Files\Node;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionBackgroundTask;
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\FileService;
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task that, for each user, crawls for all images in database,
|
||||||
|
* checks if they actually exist and removes them if they don't.
|
||||||
|
* It should be executed rarely.
|
||||||
|
*/
|
||||||
|
class StaleImagesRemovalTask extends FaceRecognitionBackgroundTask {
|
||||||
|
|
||||||
|
/** @var ImageMapper Image mapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var FaceMapper Face mapper */
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
/** @var PersonMapper Person mapper */
|
||||||
|
private $personMapper;
|
||||||
|
|
||||||
|
/** @var FileService File service*/
|
||||||
|
private $fileService;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ImageMapper $imageMapper Image mapper
|
||||||
|
* @param FaceMapper $faceMapper Face mapper
|
||||||
|
* @param PersonMapper $personMapper Person mapper
|
||||||
|
* @param FileService $fileService File Service
|
||||||
|
* @param SettingsService $settingsService Settings Service
|
||||||
|
*/
|
||||||
|
public function __construct(ImageMapper $imageMapper,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
PersonMapper $personMapper,
|
||||||
|
FileService $fileService,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->personMapper = $personMapper;
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function description() {
|
||||||
|
return "Crawl for stale images (either missing in filesystem or under .nomedia) and remove them from DB";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function execute(FaceRecognitionContext $context) {
|
||||||
|
$this->setContext($context);
|
||||||
|
|
||||||
|
$staleRemovedImages = 0;
|
||||||
|
|
||||||
|
$eligable_users = $this->context->getEligibleUsers();
|
||||||
|
foreach($eligable_users as $user) {
|
||||||
|
if (!$this->context->isRunningInSyncMode() &&
|
||||||
|
!$this->settingsService->getNeedRemoveStaleImages($user)) {
|
||||||
|
// Completely skip this task for this user, seems that we already did full scan for him
|
||||||
|
$this->logDebug(sprintf('Skipping stale images removal for user %s as there is no need for it', $user));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since method below can take long time, it is generator itself
|
||||||
|
$generator = $this->staleImagesRemovalForUser($user, $this->settingsService->getCurrentFaceModel());
|
||||||
|
foreach ($generator as $_) {
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
$staleRemovedImages += $generator->getReturn();
|
||||||
|
|
||||||
|
$this->settingsService->setNeedRemoveStaleImages(false, $user);
|
||||||
|
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Dont remove, it is used within the Integration tests
|
||||||
|
$this->context->propertyBag['StaleImagesRemovalTask_staleRemovedImages'] = $staleRemovedImages;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all images in database for a given user. For each image, check if it
|
||||||
|
* actually present in filesystem (and there is no .nomedia for it) and removes
|
||||||
|
* it from database if it is not present.
|
||||||
|
*
|
||||||
|
* @param string $userId ID of the user for which to remove stale images for
|
||||||
|
* @param int $model Used model
|
||||||
|
* @return \Generator|int Returns generator during yielding and finally returns int,
|
||||||
|
* which represent number of stale images removed
|
||||||
|
*/
|
||||||
|
private function staleImagesRemovalForUser(string $userId, int $model) {
|
||||||
|
|
||||||
|
$this->fileService->setupFS($userId);
|
||||||
|
|
||||||
|
$this->logDebug(sprintf('Getting all images for user %s', $userId));
|
||||||
|
$allImages = $this->imageMapper->findImages($userId, $model);
|
||||||
|
$this->logDebug(sprintf('Found %d images for user %s', count($allImages), $userId));
|
||||||
|
yield;
|
||||||
|
|
||||||
|
// Find if we stopped somewhere abruptly before. If we are, we need to start from that point.
|
||||||
|
// If there is value, we start from beggining. Important is that:
|
||||||
|
// * There needs to be some (any!) ordering here, we used "id" for ordering key
|
||||||
|
// * New images will be processed, or some might be checked more than once, and that is OK
|
||||||
|
// Important part is that we make continuous progess.
|
||||||
|
|
||||||
|
$lastChecked = $this->settingsService->getLastStaleImageChecked($userId);
|
||||||
|
$this->logDebug(sprintf('Last checked image id for user %s is %d', $userId, $lastChecked));
|
||||||
|
yield;
|
||||||
|
|
||||||
|
// Now filter by those above last checked and sort remaining images
|
||||||
|
$allImages = array_filter($allImages, function ($i) use($lastChecked) {
|
||||||
|
return $i->id > $lastChecked;
|
||||||
|
});
|
||||||
|
usort($allImages, function ($i1, $i2) {
|
||||||
|
return $i1->id <=> $i2->id;
|
||||||
|
});
|
||||||
|
$this->logDebug(sprintf(
|
||||||
|
'After filtering and sorting, there is %d remaining stale images to check for user %s',
|
||||||
|
count($allImages), $userId));
|
||||||
|
yield;
|
||||||
|
|
||||||
|
// Now iterate and check remaining images
|
||||||
|
$processed = 0;
|
||||||
|
$imagesRemoved = 0;
|
||||||
|
foreach ($allImages as $image) {
|
||||||
|
$file = $this->fileService->getFileById($image->getFile(), $userId);
|
||||||
|
|
||||||
|
// Delete image doesn't exist anymore in filesystem or it is under .nomedia
|
||||||
|
if (($file === null) || (!$this->fileService->isAllowedNode($file)) ||
|
||||||
|
($this->fileService->isUnderNoDetection($file))) {
|
||||||
|
$this->deleteImage($image, $userId);
|
||||||
|
$imagesRemoved++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember last processed image
|
||||||
|
$this->settingsService->setLastStaleImageChecked($image->id, $userId);
|
||||||
|
|
||||||
|
// Yield from time to time
|
||||||
|
$processed++;
|
||||||
|
if ($processed % 10 === 0) {
|
||||||
|
$this->logDebug(sprintf('Processed %d/%d stale images for user %s', $processed, count($allImages), $userId));
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove this value when we are done, so next cleanup can start from 0
|
||||||
|
$this->settingsService->setLastStaleImageChecked(0, $userId);
|
||||||
|
|
||||||
|
return $imagesRemoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteImage(Image $image, string $userId): void {
|
||||||
|
$this->logInfo(sprintf('Removing stale image %d for user %s', $image->id, $userId));
|
||||||
|
// note that invalidatePersons depends on existence of faces for a given image,
|
||||||
|
// and we must invalidate before we delete faces!
|
||||||
|
// TODO: this is same method as in Watcher, find where to unify them.
|
||||||
|
$this->personMapper->invalidatePersons($image->id);
|
||||||
|
$this->faceMapper->removeFromImage($image->id);
|
||||||
|
$this->imageMapper->delete($image);
|
||||||
|
}
|
||||||
|
}
|
||||||
159
facerecognition/lib/Clusterer/ChineseWhispers.php
Normal file
159
facerecognition/lib/Clusterer/ChineseWhispers.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2023, Matias De lellis
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This code is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License, version 3,
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Clusterer;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements the graph clustering algorithm described in the
|
||||||
|
* paper: Chinese Whispers - an Efficient Graph Clustering Algorithm and its
|
||||||
|
* Application to Natural Language Processing Problems by Chris Biemann.
|
||||||
|
*
|
||||||
|
* In particular, it tries to be a shameless copy of the original dlib
|
||||||
|
* implementation.
|
||||||
|
* - https://github.com/davisking/dlib/blob/master/dlib/clustering/chinese_whispers.h
|
||||||
|
*/
|
||||||
|
class ChineseWhispers {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cluster the dataset by assigning a label to each sample.from the edges
|
||||||
|
*/
|
||||||
|
static public function predict(array &$edges, array &$labels, int $num_iterations = 100)
|
||||||
|
{
|
||||||
|
// To improve the stability of the clusters, we must
|
||||||
|
// iterate the neighbors in a pseudo-random way.
|
||||||
|
mt_srand(2023);
|
||||||
|
|
||||||
|
$labels = [];
|
||||||
|
if (count($edges) == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
$neighbors = [];
|
||||||
|
self::find_neighbor_ranges($edges, $neighbors);
|
||||||
|
|
||||||
|
// Initialize the labels, each node gets a different label.
|
||||||
|
for ($i = 0; $i < count($neighbors); ++$i)
|
||||||
|
$labels[$i] = $i;
|
||||||
|
|
||||||
|
for ($iter = 0; $iter < count($neighbors)*$num_iterations; ++$iter)
|
||||||
|
{
|
||||||
|
// Pick a random node.
|
||||||
|
$idx = mt_rand()%count($neighbors);
|
||||||
|
|
||||||
|
// Count how many times each label happens amongst our neighbors.
|
||||||
|
$labels_to_counts = [];
|
||||||
|
$end = $neighbors[$idx][1];
|
||||||
|
|
||||||
|
for ($i = $neighbors[$idx][0]; $i != $end; ++$i)
|
||||||
|
{
|
||||||
|
$iLabelFirst = $edges[$i][1];
|
||||||
|
$iLabel = $labels[$iLabelFirst];
|
||||||
|
if (isset($labels_to_counts[$iLabel]))
|
||||||
|
$labels_to_counts[$iLabel]++;
|
||||||
|
else
|
||||||
|
$labels_to_counts[$iLabel] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the most common label
|
||||||
|
// std::map<unsigned long, double>::iterator i;
|
||||||
|
$best_score = PHP_INT_MIN;
|
||||||
|
$best_label = $labels[$idx];
|
||||||
|
foreach ($labels_to_counts as $key => $value)
|
||||||
|
{
|
||||||
|
if ($value > $best_score)
|
||||||
|
{
|
||||||
|
$best_score = $value;
|
||||||
|
$best_label = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$labels[$idx] = $best_label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap the labels into a contiguous range. First we find the
|
||||||
|
// mapping.
|
||||||
|
$label_remap = [];
|
||||||
|
for ($i = 0; $i < count($labels); ++$i)
|
||||||
|
{
|
||||||
|
$next_id = count($label_remap);
|
||||||
|
if (!isset($label_remap[$labels[$i]]))
|
||||||
|
$label_remap[$labels[$i]] = $next_id;
|
||||||
|
}
|
||||||
|
// now apply the mapping to all the labels.
|
||||||
|
for ($i = 0; $i < count($labels); ++$i)
|
||||||
|
{
|
||||||
|
$labels[$i] = $label_remap[$labels[$i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($label_remap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function find_neighbor_ranges (&$edges, &$neighbors) {
|
||||||
|
// setup neighbors so that [neighbors[i].first, neighbors[i].second) is the range
|
||||||
|
// within edges that contains all node i's edges.
|
||||||
|
$num_nodes = self::max_index_plus_one($edges);
|
||||||
|
for ($i = 0; $i < $num_nodes; ++$i) $neighbors[$i] = [0, 0];
|
||||||
|
$cur_node = 0;
|
||||||
|
$start_idx = 0;
|
||||||
|
for ($i = 0; $i < count($edges); ++$i)
|
||||||
|
{
|
||||||
|
if ($edges[$i][0] != $cur_node)
|
||||||
|
{
|
||||||
|
$neighbors[$cur_node] = [$start_idx, $i];
|
||||||
|
$start_idx = $i;
|
||||||
|
$cur_node = $edges[$i][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($neighbors) !== 0)
|
||||||
|
$neighbors[$cur_node] = [$start_idx, count($edges)];
|
||||||
|
}
|
||||||
|
|
||||||
|
static function max_index_plus_one ($pairs): int {
|
||||||
|
if (count($pairs) === 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$max_idx = 0;
|
||||||
|
for ($i = 0; $i < count($pairs); ++$i)
|
||||||
|
{
|
||||||
|
if ($pairs[$i][0] > $max_idx)
|
||||||
|
$max_idx = $pairs[$i][0];
|
||||||
|
if ($pairs[$i][1] > $max_idx)
|
||||||
|
$max_idx = $pairs[$i][1];
|
||||||
|
}
|
||||||
|
return $max_idx + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function convert_unordered_to_ordered (&$edges, &$out_edges)
|
||||||
|
{
|
||||||
|
$out_edges = [];
|
||||||
|
for ($i = 0; $i < count($edges); ++$i)
|
||||||
|
{
|
||||||
|
$out_edges[] = [$edges[$i][0], $edges[$i][1]];
|
||||||
|
if ($edges[$i][0] != $edges[$i][1])
|
||||||
|
$out_edges[] = [$edges[$i][1], $edges[$i][0]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
202
facerecognition/lib/Command/BackgroundCommand.php
Normal file
202
facerecognition/lib/Command/BackgroundCommand.php
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Command;
|
||||||
|
|
||||||
|
use OCP\Files\IRootFolder;
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\CommandLock;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\BackgroundJob\BackgroundService;
|
||||||
|
|
||||||
|
class BackgroundCommand extends Command {
|
||||||
|
|
||||||
|
/** @var BackgroundService */
|
||||||
|
protected $backgroundService;
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
protected $userManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BackgroundService $backgroundService
|
||||||
|
* @param IUserManager $userManager
|
||||||
|
*/
|
||||||
|
public function __construct(BackgroundService $backgroundService,
|
||||||
|
IUserManager $userManager) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->backgroundService = $backgroundService;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('face:background_job')
|
||||||
|
->setDescription('Equivalent of cron job to analyze images, extract faces and create clusters from found faces')
|
||||||
|
->addOption(
|
||||||
|
'user_id',
|
||||||
|
'u',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Analyze faces for the given user only. If not given, analyzes images for all users.',
|
||||||
|
null
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'max_image_area',
|
||||||
|
'M',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Caps maximum area (in pixels^2) of the image to be fed to neural network, effectively lowering needed memory. ' .
|
||||||
|
'Use this if face detection crashes randomly.'
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'sync-mode',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Execute all actions related to synchronizing the files. New users, shared or deleted files, etc.'
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'analyze-mode',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Execute only the action of analyzing the images to obtain the faces and their descriptors.'
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'cluster-mode',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Execute only the action of face clustering to get the people.'
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'defer-clustering',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Defer the face clustering at the end of the analysis to get persons in a simple execution of the command.'
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'timeout',
|
||||||
|
't',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Sets timeout in seconds for this command. Default is without timeout, e.g. command runs indefinitely.',
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||||
|
$this->backgroundService->setLogger($output);
|
||||||
|
|
||||||
|
// Extract user, if any
|
||||||
|
//
|
||||||
|
$userId = $input->getOption('user_id');
|
||||||
|
$user = null;
|
||||||
|
|
||||||
|
if (!is_null($userId)) {
|
||||||
|
$user = $this->userManager->get($userId);
|
||||||
|
if ($user === null) {
|
||||||
|
throw new \InvalidArgumentException("User with id <$userId> in unknown.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract timeout
|
||||||
|
//
|
||||||
|
$timeout = $input->getOption('timeout');
|
||||||
|
if (!is_null($timeout)) {
|
||||||
|
if ($timeout < 0) {
|
||||||
|
throw new \InvalidArgumentException("Timeout must be positive value in seconds.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$timeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract max image area
|
||||||
|
//
|
||||||
|
$maxImageArea = $input->getOption('max_image_area');
|
||||||
|
if (!is_null($maxImageArea)) {
|
||||||
|
$maxImageArea = intval($maxImageArea);
|
||||||
|
|
||||||
|
if ($maxImageArea === 0) {
|
||||||
|
throw new \InvalidArgumentException("Max image area must be positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($maxImageArea < 0) {
|
||||||
|
throw new \InvalidArgumentException("Max image area must be positive value.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract mode from options
|
||||||
|
//
|
||||||
|
$mode = 'default-mode';
|
||||||
|
if ($input->getOption('sync-mode')) {
|
||||||
|
$mode = 'sync-mode';
|
||||||
|
} else if ($input->getOption('analyze-mode')) {
|
||||||
|
$mode = 'analyze-mode';
|
||||||
|
} else if ($input->getOption('cluster-mode')) {
|
||||||
|
$mode = 'cluster-mode';
|
||||||
|
} else if ($input->getOption('defer-clustering')) {
|
||||||
|
$mode = 'defer-mode';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract verbosity (for command, we don't need this, but execute asks for it, if running from cron job).
|
||||||
|
//
|
||||||
|
$verbose = $input->getOption('verbose');
|
||||||
|
|
||||||
|
// In image analysis mode it run in parallel.
|
||||||
|
// In any other case acquire lock so that only one background task can run
|
||||||
|
//
|
||||||
|
$globalLock = ($mode != 'analyze-mode');
|
||||||
|
if ($globalLock) {
|
||||||
|
$lock = CommandLock::Lock('face:background_job');
|
||||||
|
if (!$lock) {
|
||||||
|
$output->writeln("Another task ('". CommandLock::IsLockedBy(). "') is already running that prevents it from continuing.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main thing
|
||||||
|
//
|
||||||
|
$this->backgroundService->execute($timeout, $verbose, $user, $maxImageArea, $mode);
|
||||||
|
|
||||||
|
// Release obtained lock
|
||||||
|
//
|
||||||
|
if ($globalLock) {
|
||||||
|
CommandLock::Unlock($lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
131
facerecognition/lib/Command/DumpCommand.php
Normal file
131
facerecognition/lib/Command/DumpCommand.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
class DumpCommand extends Command {
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
protected $userManager;
|
||||||
|
|
||||||
|
/** @var PersonMapper */
|
||||||
|
protected $personMapper;
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
protected $faceMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IUserManager $userManager
|
||||||
|
* @param ImageMapper $imageMapper
|
||||||
|
* @param FaceMapper $faceMapper
|
||||||
|
* @param PersonMapper $personMapper
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
*/
|
||||||
|
public function __construct(IUserManager $userManager,
|
||||||
|
PersonMapper $personMapper,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->personMapper = $personMapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('face:dump')
|
||||||
|
->setDescription('Get a summary of statistics images, faces and persons')
|
||||||
|
->addOption(
|
||||||
|
'user_id',
|
||||||
|
'u',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Get stats for a given user only. If not given, get stats for all users.',
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||||
|
$users = array();
|
||||||
|
|
||||||
|
$userId = $input->getOption('user_id');
|
||||||
|
if (!is_null($userId)) {
|
||||||
|
if ($this->userManager->get($userId) === null) {
|
||||||
|
$output->writeln("User with id <$userId> in unknown.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$users[] = $userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->userManager->callForAllUsers(function (IUser $iUser) use (&$users) {
|
||||||
|
$users[] = $iUser->getUID();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->printCSVStats($output, $users);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printCSVStats(OutputInterface $output, array $users): void {
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
foreach ($users as $userId) {
|
||||||
|
$clusters = $this->personMapper->findAll($userId, $modelId);
|
||||||
|
$output->writeln('id,size');
|
||||||
|
foreach ($clusters as $cluster) {
|
||||||
|
$faces = $this->faceMapper->findFromCluster($userId, $cluster->getId(), $modelId);
|
||||||
|
$output->writeln($cluster->getId().','.count($faces));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
324
facerecognition/lib/Command/MigrateCommand.php
Normal file
324
facerecognition/lib/Command/MigrateCommand.php
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2019, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Command;
|
||||||
|
|
||||||
|
use OCP\IUserManager;
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Helper\ProgressBar;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Face;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Model\IModel;
|
||||||
|
use OCA\FaceRecognition\Model\ModelManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\FaceManagementService;
|
||||||
|
use OCA\FaceRecognition\Service\FileService;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\CommandLock;
|
||||||
|
|
||||||
|
use OCP\Image as OCP_Image;
|
||||||
|
|
||||||
|
class MigrateCommand extends Command {
|
||||||
|
|
||||||
|
/** @var FaceManagementService */
|
||||||
|
protected $faceManagementService;
|
||||||
|
|
||||||
|
/** @var FileService */
|
||||||
|
protected $fileService;
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
protected $userManager;
|
||||||
|
|
||||||
|
/** @var ModelManager */
|
||||||
|
protected $modelManager;
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
protected $faceMapper;
|
||||||
|
|
||||||
|
/** @var ImageMapper Image mapper*/
|
||||||
|
protected $imageMapper;
|
||||||
|
|
||||||
|
/** @var OutputInterface $output */
|
||||||
|
protected $output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FaceManagementService $faceManagementService
|
||||||
|
* @param IUserManager $userManager
|
||||||
|
*/
|
||||||
|
public function __construct(FaceManagementService $faceManagementService,
|
||||||
|
FileService $fileService,
|
||||||
|
IUserManager $userManager,
|
||||||
|
ModelManager $modelManager,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
ImageMapper $imageMapper)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->faceManagementService = $faceManagementService;
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->modelManager = $modelManager;
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('face:migrate')
|
||||||
|
->setDescription(
|
||||||
|
'Migrate the faces found in a model and analyze with the current model.')
|
||||||
|
->addOption(
|
||||||
|
'model_id',
|
||||||
|
'm',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'The identifier number of the model to migrate',
|
||||||
|
null
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'user_id',
|
||||||
|
'u',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Migrate data for a given user only. If not given, migrate everything for all users.',
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
$this->output = $output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the considerations of the models to migrate.
|
||||||
|
*/
|
||||||
|
$modelId = $input->getOption('model_id');
|
||||||
|
if (is_null($modelId)) {
|
||||||
|
$output->writeln("You must indicate the ID of the model to migrate");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = $this->modelManager->getModel($modelId);
|
||||||
|
if (is_null($model)) {
|
||||||
|
$output->writeln("Invalid model Id");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$model->isInstalled()) {
|
||||||
|
$output->writeln("The model <$modelId> is not installed");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentModel = $this->modelManager->getCurrentModel();
|
||||||
|
$currentModelId = (!is_null($currentModel)) ? $currentModel->getId() : -1;
|
||||||
|
|
||||||
|
if ($currentModelId === $modelId) {
|
||||||
|
$output->writeln("The proposed model <$modelId> to migrate must be other than the current one <$currentModelId>");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the user if it is provided.
|
||||||
|
*/
|
||||||
|
$userId = $input->getOption('user_id');
|
||||||
|
if ($userId !== null) {
|
||||||
|
$user = $this->userManager->get($userId);
|
||||||
|
if ($user === null) {
|
||||||
|
$output->writeln("User with id <$userId> is unknown.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user to migrate
|
||||||
|
*/
|
||||||
|
$userIds = $this->getEligiblesUserId($userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that any user have data in the current model
|
||||||
|
*/
|
||||||
|
foreach ($userIds as $mUserId) {
|
||||||
|
if ($this->faceManagementService->hasDataForUser($mUserId, $currentModelId)) {
|
||||||
|
$output->writeln("The user <$mUserId> in current model <$currentModelId> already has data. You cannot migrate to a used model.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get lock to avoid potential errors.
|
||||||
|
//
|
||||||
|
$lock = CommandLock::Lock("face:migrate");
|
||||||
|
if (!$lock) {
|
||||||
|
$output->writeln("Another command ('". CommandLock::IsLockedBy(). "') is already running that prevents it from continuing.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the model and migrate to users.
|
||||||
|
*/
|
||||||
|
$currentModel->open();
|
||||||
|
|
||||||
|
foreach ($userIds as $mUserId) {
|
||||||
|
$this->migrateUser($currentModel, $modelId, $mUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("The faces migration is done. Remember that you must recreate the clusters with the background_job command");
|
||||||
|
|
||||||
|
// Release obtained lock
|
||||||
|
//
|
||||||
|
CommandLock::Unlock($lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function migrateUser(IModel $currentModel, int $oldModelId, $userId) {
|
||||||
|
if (!$this->faceManagementService->hasDataForUser($userId, $oldModelId)) {
|
||||||
|
$this->output->writeln("User <$userId> has no data in model <$oldModelId> to migrate.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->output->writeln("Will be migrated <$userId> from model <$oldModelId>");
|
||||||
|
|
||||||
|
$currentModelId = $currentModel->getId();
|
||||||
|
$oldImages = $this->imageMapper->findAll($userId, $oldModelId);
|
||||||
|
|
||||||
|
$progressBar = new ProgressBar($this->output, count($oldImages));
|
||||||
|
$progressBar->start();
|
||||||
|
|
||||||
|
foreach ($oldImages as $oldImage) {
|
||||||
|
$newImage = $this->migrateImage($oldImage, $userId, $currentModelId);
|
||||||
|
$oldFaces = $this->faceMapper->findFromFile($userId, $oldModelId, $newImage->getFile());
|
||||||
|
if (count($oldFaces) > 0) {
|
||||||
|
$filePath = $this->getImageFilePath($newImage);
|
||||||
|
if ($filePath === null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach ($oldFaces as $oldFace) {
|
||||||
|
$this->migrateFace($currentModel, $oldFace, $newImage, $filePath);
|
||||||
|
}
|
||||||
|
$this->fileService->clean();
|
||||||
|
}
|
||||||
|
$progressBar->advance(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$progressBar->finish();
|
||||||
|
|
||||||
|
$this->output->writeln("Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function migrateImage($oldImage, string $userId, int $modelId): Image {
|
||||||
|
$image = new Image();
|
||||||
|
|
||||||
|
$image->setUser($userId);
|
||||||
|
$image->setFile($oldImage->getFile());
|
||||||
|
$image->setModel($modelId);
|
||||||
|
$image->setIsProcessed($oldImage->getIsProcessed());
|
||||||
|
$image->setError($oldImage->getError());
|
||||||
|
$image->setLastProcessedTime($oldImage->getLastProcessedTime());
|
||||||
|
$image->setProcessingDuration($oldImage->getProcessingDuration());
|
||||||
|
|
||||||
|
return $this->imageMapper->insert($image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filePath
|
||||||
|
*/
|
||||||
|
private function migrateFace(IModel $model, Face $oldFace, Image $image, string $filePath): void {
|
||||||
|
// Get the rectangle and the confidence of the original face.
|
||||||
|
$faceRect = $this->getFaceRect($oldFace);
|
||||||
|
|
||||||
|
// Get the landmarks and descriptor with the new model.
|
||||||
|
$faceAssoc = $model->compute($filePath, $faceRect);
|
||||||
|
|
||||||
|
// Convert the assoc array into an Face object and insert on database.
|
||||||
|
$face = Face::fromModel($image->getId(), $faceAssoc);
|
||||||
|
$this->faceMapper->insertFace($face);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getImageFilePath(Image $image): ?string {
|
||||||
|
$file = $this->fileService->getFileById($image->getFile(), $image->getUser());
|
||||||
|
if (empty($file)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$localPath = $this->fileService->getLocalFile($file);
|
||||||
|
|
||||||
|
$image = new OCP_Image();
|
||||||
|
$image->loadFromFile($localPath);
|
||||||
|
if ($image->getOrientation() > 1) {
|
||||||
|
$tempPath = $this->fileService->getTemporaryFile();
|
||||||
|
$image->fixOrientation();
|
||||||
|
$image->save($tempPath, 'image/png');
|
||||||
|
return $tempPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFaceRect(Face $face): array {
|
||||||
|
$rect = [];
|
||||||
|
$rect['left'] = (int)$face->getX();
|
||||||
|
$rect['right'] = (int)$face->getX() + $face->getWidth();
|
||||||
|
$rect['top'] = (int)$face->getY();
|
||||||
|
$rect['bottom'] = (int)$face->getY() + $face->getHeight();
|
||||||
|
$rect['detection_confidence'] = $face->getConfidence();
|
||||||
|
return $rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array with the eligibles users taking into account the user argument,
|
||||||
|
* or all users.
|
||||||
|
*/
|
||||||
|
private function getEligiblesUserId(string $userId = null): array {
|
||||||
|
$eligible_users = array();
|
||||||
|
if (is_null($userId)) {
|
||||||
|
$this->userManager->callForAllUsers(function (IUser $user) use (&$eligible_users) {
|
||||||
|
$eligible_users[] = $user->getUID();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$eligible_users[] = $userId;
|
||||||
|
}
|
||||||
|
return $eligible_users;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
140
facerecognition/lib/Command/ProgressCommand.php
Normal file
140
facerecognition/lib/Command/ProgressCommand.php
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use OCP\IDateTimeFormatter;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
class ProgressCommand extends Command {
|
||||||
|
|
||||||
|
/** @var IDateTimeFormatter */
|
||||||
|
protected $dateTimeFormatter;
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
protected $imageMapper;
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
protected $faceMapper;
|
||||||
|
|
||||||
|
/** @var PersonMapper */
|
||||||
|
protected $personMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IDateTimeFormatter $dateTimeFormatter
|
||||||
|
* @param ImageMapper $imageMapper
|
||||||
|
* @param FaceMapper $faceMapper
|
||||||
|
* @param PersonMapper $personMapper
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
*/
|
||||||
|
public function __construct(IDateTimeFormatter $dateTimeFormatter,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
PersonMapper $personMapper,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->dateTimeFormatter = $dateTimeFormatter;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->personMapper = $personMapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('face:progress')
|
||||||
|
->setDescription('Get the progress of the analysis and an estimated time')
|
||||||
|
->addOption(
|
||||||
|
'json',
|
||||||
|
'j',
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Print in a json format, useful to analyze it with another tool.',
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$totalImages = $this->imageMapper->countImages($modelId);
|
||||||
|
$processedImages = $this->imageMapper->countProcessedImages($modelId);
|
||||||
|
|
||||||
|
$remainingImages = $totalImages - $processedImages;
|
||||||
|
|
||||||
|
$avgProcessingTime = $this->imageMapper->avgProcessingDuration($modelId);
|
||||||
|
$estimatedSeconds = (int) ($remainingImages * $avgProcessingTime/1000);
|
||||||
|
|
||||||
|
if ($input->getOption('json')) {
|
||||||
|
$this->printJsonProgress($output, $totalImages, $remainingImages, $estimatedSeconds);
|
||||||
|
} else {
|
||||||
|
$this->printTabledProgress($output, $totalImages, $remainingImages, $estimatedSeconds);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printTabledProgress(OutputInterface $output, $totalImages, $remainingImages, $estimatedSeconds): void {
|
||||||
|
if ($estimatedSeconds) {
|
||||||
|
$estimatedTime = $this->dateTimeFormatter->formatTimeSpan((time() + $estimatedSeconds));
|
||||||
|
} else {
|
||||||
|
$estimatedTime = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = new Table($output);
|
||||||
|
$table
|
||||||
|
->setHeaders(['Images', 'Remaining', 'ETA'])
|
||||||
|
->setRows([[strval($totalImages), strval($remainingImages), $estimatedTime]]);
|
||||||
|
$table->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printJsonProgress(OutputInterface $output, $totalImages, $remainingImages, $estimatedSeconds): void {
|
||||||
|
$stats[] = array(
|
||||||
|
'images' => $totalImages,
|
||||||
|
'remaining' => $remainingImages,
|
||||||
|
'eta' => $estimatedSeconds
|
||||||
|
);
|
||||||
|
$output->writeln(json_encode($stats));
|
||||||
|
}
|
||||||
|
}
|
||||||
233
facerecognition/lib/Command/ResetCommand.php
Normal file
233
facerecognition/lib/Command/ResetCommand.php
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2019, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Command;
|
||||||
|
|
||||||
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\CommandLock;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\FaceManagementService;
|
||||||
|
|
||||||
|
class ResetCommand extends Command {
|
||||||
|
|
||||||
|
/** @var FaceManagementService */
|
||||||
|
protected $faceManagementService;
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
protected $userManager;
|
||||||
|
|
||||||
|
/** @var InputInterface */
|
||||||
|
protected $input;
|
||||||
|
|
||||||
|
/** @var OutputInterface */
|
||||||
|
protected $output;
|
||||||
|
|
||||||
|
/** @var QuestionHelper */
|
||||||
|
protected $questionHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FaceManagementService $faceManagementService
|
||||||
|
* @param IUserManager $userManager
|
||||||
|
* @param QuestionHelper $questionHelper
|
||||||
|
*/
|
||||||
|
public function __construct(FaceManagementService $faceManagementService,
|
||||||
|
IUserManager $userManager,
|
||||||
|
QuestionHelper $questionHelper) {
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->faceManagementService = $faceManagementService;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->questionHelper = $questionHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('face:reset')
|
||||||
|
->setDescription(
|
||||||
|
'Resets and deletes everything. Good for starting over. ' .
|
||||||
|
'BEWARE: Next runs of face:background_job will re-analyze all images.')
|
||||||
|
->addOption(
|
||||||
|
'user_id',
|
||||||
|
'u',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Resets data for a given user only. If not given, resets everything for all users.',
|
||||||
|
null
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'all',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Reset everything.',
|
||||||
|
null
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'model',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Reset current model.',
|
||||||
|
null
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'image-errors',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Reset errors in images to re-analyze again',
|
||||||
|
null
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'clustering',
|
||||||
|
null,
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Just reset the clustering.',
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||||
|
// Used to questions.
|
||||||
|
//
|
||||||
|
$this->input = $input;
|
||||||
|
$this->output = $output;
|
||||||
|
|
||||||
|
// Extract user, if any
|
||||||
|
//
|
||||||
|
$userId = $input->getOption('user_id');
|
||||||
|
$user = null;
|
||||||
|
|
||||||
|
if (!is_null($userId)) {
|
||||||
|
$user = $this->userManager->get($userId);
|
||||||
|
if ($user === null) {
|
||||||
|
$output->writeln("User with id <$userId> in unknown.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get lock to avoid potential errors.
|
||||||
|
//
|
||||||
|
$lock = CommandLock::Lock('face:reset');
|
||||||
|
if (!$lock) {
|
||||||
|
$output->writeln("Another command ('". CommandLock::IsLockedBy(). "') is already running that prevents it from continuing.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main thing
|
||||||
|
//
|
||||||
|
$ret = 0;
|
||||||
|
if ($input->getOption('all')) {
|
||||||
|
if ($this->confirmate()) {
|
||||||
|
$this->resetAll($user);
|
||||||
|
$output->writeln('Reset successfully done');
|
||||||
|
} else {
|
||||||
|
$output->writeln('Aborted');
|
||||||
|
$ret = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($input->getOption('model')) {
|
||||||
|
if ($this->confirmate()) {
|
||||||
|
$this->resetModel($user);
|
||||||
|
$output->writeln('Reset model successfully done');
|
||||||
|
} else {
|
||||||
|
$output->writeln('Aborted');
|
||||||
|
$ret = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($input->getOption('image-errors')) {
|
||||||
|
if ($this->confirmate()) {
|
||||||
|
$this->resetImageErrors($user);
|
||||||
|
$output->writeln('Reset image errors done');
|
||||||
|
} else {
|
||||||
|
$output->writeln('Aborted');
|
||||||
|
$ret = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($input->getOption('clustering')) {
|
||||||
|
if ($this->confirmate()) {
|
||||||
|
$this->resetClusters($user);
|
||||||
|
$output->writeln('Reset clustering done');
|
||||||
|
} else {
|
||||||
|
$output->writeln('Aborted');
|
||||||
|
$ret = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$output->writeln('You must specify what you want to reset');
|
||||||
|
$ret = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release obtained lock
|
||||||
|
//
|
||||||
|
CommandLock::Unlock($lock);
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function confirmate() {
|
||||||
|
$question = new ConfirmationQuestion('Warning: This command is not reversible. Do you want to continue? [y/N]', false);
|
||||||
|
return $this->questionHelper->ask($this->input, $this->output, $question);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \OCP\IUser|null $user
|
||||||
|
*/
|
||||||
|
private function resetClusters(?\OCP\IUser $user): void {
|
||||||
|
$this->faceManagementService->resetClusters($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \OCP\IUser|null $user
|
||||||
|
*/
|
||||||
|
private function resetImageErrors(?\OCP\IUser $user): void {
|
||||||
|
$this->faceManagementService->resetImageErrors($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \OCP\IUser|null $user
|
||||||
|
*/
|
||||||
|
private function resetAll(?\OCP\IUser $user): void {
|
||||||
|
$this->faceManagementService->resetAll($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \OCP\IUser|null $user
|
||||||
|
*/
|
||||||
|
private function resetModel(?\OCP\IUser $user): void {
|
||||||
|
$this->faceManagementService->resetModel($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
234
facerecognition/lib/Command/SetupCommand.php
Normal file
234
facerecognition/lib/Command/SetupCommand.php
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Model\IModel;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Model\ModelManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\CommandLock;
|
||||||
|
use OCA\FaceRecognition\Helper\MemoryLimits;
|
||||||
|
|
||||||
|
use OCP\Util as OCP_Util;
|
||||||
|
|
||||||
|
class SetupCommand extends Command {
|
||||||
|
|
||||||
|
/** @var ModelManager */
|
||||||
|
protected $modelManager;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var OutputInterface */
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ModelManager $modelManager
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
*/
|
||||||
|
public function __construct(ModelManager $modelManager,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->modelManager = $modelManager;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('face:setup')
|
||||||
|
->setDescription('Basic application settings, such as maximum memory, and the model used.')
|
||||||
|
->addOption(
|
||||||
|
'memory',
|
||||||
|
'M',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'The maximum memory assigned for image processing',
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
->addOption(
|
||||||
|
'model',
|
||||||
|
'm',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'The identifier number of the model to install',
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||||
|
$this->logger = $output;
|
||||||
|
|
||||||
|
$assignMemory = $input->getOption('memory');
|
||||||
|
$modelId = $input->getOption('model');
|
||||||
|
|
||||||
|
if ($assignMemory < 0 && $modelId < 0) {
|
||||||
|
$this->dumpCurrentSetup();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get lock to avoid potential errors.
|
||||||
|
//
|
||||||
|
$lock = CommandLock::Lock("face:setup");
|
||||||
|
if (!$lock) {
|
||||||
|
$output->writeln("Another command ('". CommandLock::IsLockedBy(). "') is already running that prevents it from continuing.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($assignMemory > 0) {
|
||||||
|
$ret = $this->setupAssignedMemory(OCP_Util::computerFileSize($assignMemory));
|
||||||
|
if ($ret > 0) {
|
||||||
|
CommandLock::Unlock($lock);
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($modelId > 0) {
|
||||||
|
$ret = $this->setupModel($modelId);
|
||||||
|
if ($ret > 0) {
|
||||||
|
CommandLock::Unlock($lock);
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release obtained lock
|
||||||
|
//
|
||||||
|
CommandLock::Unlock($lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setupAssignedMemory ($assignMemory): int {
|
||||||
|
$systemMemory = MemoryLimits::getSystemMemory();
|
||||||
|
$this->logger->writeln("System memory: " . ($systemMemory > 0 ? $this->getHumanMemory($systemMemory) : "Unknown"));
|
||||||
|
$phpMemory = MemoryLimits::getPhpMemory();
|
||||||
|
$this->logger->writeln("Memory assigned to PHP: " . ($phpMemory > 0 ? $this->getHumanMemory($phpMemory) : "Unlimited"));
|
||||||
|
|
||||||
|
$this->logger->writeln("");
|
||||||
|
$availableMemory = MemoryLimits::getAvailableMemory();
|
||||||
|
$this->logger->writeln("Minimum value to assign to image processing.: " . $this->getHumanMemory(SettingsService::MINIMUM_ASSIGNED_MEMORY));
|
||||||
|
$this->logger->writeln("Maximum value to assign to image processing.: " . ($availableMemory > 0 ? $this->getHumanMemory($availableMemory) : "Unknown"));
|
||||||
|
|
||||||
|
$this->logger->writeln("");
|
||||||
|
if ($assignMemory > $availableMemory) {
|
||||||
|
$this->logger->writeln("Cannot assign more memory than the maximum...");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($assignMemory < SettingsService::MINIMUM_ASSIGNED_MEMORY) {
|
||||||
|
$this->logger->writeln("Cannot assign less memory than the minimum...");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->settingsService->setAssignedMemory ($assignMemory);
|
||||||
|
$this->logger->writeln("Maximum memory assigned for image processing: " . $this->getHumanMemory($assignMemory));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setupModel (int $modelId): int {
|
||||||
|
$this->logger->writeln("");
|
||||||
|
|
||||||
|
$model = $this->modelManager->getModel($modelId);
|
||||||
|
if (is_null($model)) {
|
||||||
|
$this->logger->writeln('Invalid model Id');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$modelDescription = $model->getId() . ' (' . $model->getName(). ')';
|
||||||
|
|
||||||
|
$error_message = "";
|
||||||
|
if (!$model->meetDependencies($error_message)) {
|
||||||
|
$this->logger->writeln('You do not meet the dependencies to install the model ' . $modelDescription);
|
||||||
|
$this->logger->writeln('Summary: ' . $error_message);
|
||||||
|
$this->logger->writeln('Please read the documentation for this model to continue: ' .$model->getDocumentation());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($model->isInstalled()) {
|
||||||
|
$this->logger->writeln('The files of model ' . $modelDescription . ' are already installed');
|
||||||
|
$this->modelManager->setDefault($modelId);
|
||||||
|
$this->logger->writeln('The model ' . $modelDescription . ' was configured as default');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->writeln('The model ' . $modelDescription . ' will be installed');
|
||||||
|
$model->install();
|
||||||
|
$this->logger->writeln('Install model ' . $modelDescription . ' successfully done');
|
||||||
|
|
||||||
|
$this->modelManager->setDefault($modelId);
|
||||||
|
$this->logger->writeln('The model ' . $modelDescription . ' was configured as default');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function dumpCurrentSetup (): void {
|
||||||
|
$this->logger->writeln("Current setup:");
|
||||||
|
$this->logger->writeln('');
|
||||||
|
$this->logger->writeln("Minimum value to assign to image processing.: " . $this->getHumanMemory(SettingsService::MINIMUM_ASSIGNED_MEMORY));
|
||||||
|
$availableMemory = MemoryLimits::getAvailableMemory();
|
||||||
|
$this->logger->writeln("Maximum value to assign to image processing.: " . ($availableMemory > 0 ? $this->getHumanMemory($availableMemory) : "Unknown"));
|
||||||
|
$assignedMemory = $this->settingsService->getAssignedMemory();
|
||||||
|
$this->logger->writeln("Maximum memory assigned for image processing: " . ($assignedMemory > 0 ? $this->getHumanMemory($assignedMemory) : "Pending configuration"));
|
||||||
|
|
||||||
|
$this->logger->writeln('');
|
||||||
|
$this->logger->writeln("Available models:");
|
||||||
|
$table = new Table($this->logger);
|
||||||
|
$table->setHeaders(['Id', 'Enabled', 'Name', 'Description']);
|
||||||
|
|
||||||
|
$currentModel = $this->modelManager->getCurrentModel();
|
||||||
|
$modelId = (!is_null($currentModel)) ? $currentModel->getId() : -1;
|
||||||
|
|
||||||
|
$models = $this->modelManager->getAllModels();
|
||||||
|
foreach ($models as $model) {
|
||||||
|
$table->addRow([
|
||||||
|
$model->getId(),
|
||||||
|
($model->getId() === $modelId) ? '*' : '',
|
||||||
|
$model->getName(),
|
||||||
|
$model->getDescription()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$table->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getHumanMemory ($memory): string {
|
||||||
|
return OCP_Util::humanFileSize($memory) . " (" . intval($memory) . "B)";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
175
facerecognition/lib/Command/StatsCommand.php
Normal file
175
facerecognition/lib/Command/StatsCommand.php
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
class StatsCommand extends Command {
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
protected $userManager;
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
protected $imageMapper;
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
protected $faceMapper;
|
||||||
|
|
||||||
|
/** @var PersonMapper */
|
||||||
|
protected $personMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IUserManager $userManager
|
||||||
|
* @param ImageMapper $imageMapper
|
||||||
|
* @param FaceMapper $faceMapper
|
||||||
|
* @param PersonMapper $personMapper
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
*/
|
||||||
|
public function __construct(IUserManager $userManager,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
PersonMapper $personMapper,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->personMapper = $personMapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('face:stats')
|
||||||
|
->setDescription('Get a summary of statistics images, faces and persons')
|
||||||
|
->addOption(
|
||||||
|
'user_id',
|
||||||
|
'u',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Get stats for a given user only. If not given, get stats for all users.',
|
||||||
|
null
|
||||||
|
)->addOption(
|
||||||
|
'json',
|
||||||
|
'j',
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'Print in a json format, useful to analyze it with another tool.',
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||||
|
$users = array();
|
||||||
|
|
||||||
|
$userId = $input->getOption('user_id');
|
||||||
|
if (!is_null($userId)) {
|
||||||
|
if ($this->userManager->get($userId) === null) {
|
||||||
|
$output->writeln("User with id <$userId> in unknown.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$users[] = $userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->userManager->callForAllUsers(function (IUser $iUser) use (&$users) {
|
||||||
|
$users[] = $iUser->getUID();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('json')) {
|
||||||
|
$this->printJsonStats($output, $users);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->printTabledStats($output, $users);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printTabledStats(OutputInterface $output, array $users): void {
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$stats = array();
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$stats[] = [
|
||||||
|
$user,
|
||||||
|
$this->imageMapper->countUserImages($user, $modelId),
|
||||||
|
$this->imageMapper->countUserImages($user, $modelId, true),
|
||||||
|
$this->faceMapper->countFaces($user, $modelId),
|
||||||
|
$this->personMapper->countClusters($user, $modelId),
|
||||||
|
$this->personMapper->countPersons($user, $modelId)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = new Table($output);
|
||||||
|
$table->setHeaders(['User', 'Images', 'Processed', 'Faces', 'Clusters', 'Persons'])->setRows($stats);
|
||||||
|
$table->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printJsonStats(OutputInterface $output, array $users): void {
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$stats = array();
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$stats[] = array(
|
||||||
|
'user' => $user,
|
||||||
|
'images' => $this->imageMapper->countUserImages($user, $modelId),
|
||||||
|
'processed'=> $this->imageMapper->countUserImages($user, $modelId, true),
|
||||||
|
'faces' => $this->faceMapper->countFaces($user, $modelId),
|
||||||
|
'clusters' => $this->personMapper->countClusters($user, $modelId),
|
||||||
|
'persons' => $this->personMapper->countPersons($user, $modelId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln(json_encode($stats));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
201
facerecognition/lib/Command/SyncAlbumsCommand.php
Normal file
201
facerecognition/lib/Command/SyncAlbumsCommand.php
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\PhotoAlbums;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
class SyncAlbumsCommand extends Command {
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
protected $userManager;
|
||||||
|
|
||||||
|
/** @var PersonMapper Person mapper*/
|
||||||
|
private $personMapper;
|
||||||
|
|
||||||
|
/** @var IAppManager */
|
||||||
|
private $appManager;
|
||||||
|
|
||||||
|
/** @var PhotoAlbums */
|
||||||
|
protected $photoAlbums;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IUserManager $userManager
|
||||||
|
* @param PersonMapper $personMapper
|
||||||
|
* @param PhotoAlbums $photoAlbums
|
||||||
|
* @param SettingsService $settingsService
|
||||||
|
* @param IAppManager $appManager
|
||||||
|
*/
|
||||||
|
public function __construct(IUserManager $userManager,
|
||||||
|
PersonMapper $personMapper,
|
||||||
|
IAppManager $appManager,
|
||||||
|
PhotoAlbums $photoAlbums,
|
||||||
|
SettingsService $settingsService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
$this->personMapper = $personMapper;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->photoAlbums = $photoAlbums;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function configure() {
|
||||||
|
$this
|
||||||
|
->setName('face:sync-albums')
|
||||||
|
->setDescription('Synchronize the people found with the photo albums')
|
||||||
|
->addOption(
|
||||||
|
'user_id',
|
||||||
|
'u',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Sync albums for a given user only. If not given, sync albums for all users.',
|
||||||
|
null
|
||||||
|
)->addOption(
|
||||||
|
'list_person',
|
||||||
|
'l',
|
||||||
|
InputOption::VALUE_NONE,
|
||||||
|
'List all persons defined for the given user_id.',
|
||||||
|
null
|
||||||
|
)->addOption(
|
||||||
|
'person_name',
|
||||||
|
'p',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Sync albums for a given user and person name(s) (separate using comma). If not used, sync albums for all persons defined by the user.',
|
||||||
|
null
|
||||||
|
)->addOption(
|
||||||
|
'mode',
|
||||||
|
'm',
|
||||||
|
InputOption::VALUE_REQUIRED,
|
||||||
|
'Album creation mode. Use "album-per-person" to create one album for each given person via person_name parameter. Use "album-combined" to create one album for all person names given via person_name parameter.',
|
||||||
|
'album-per-person'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||||
|
if (!$this->appManager->isEnabledForUser('photos')) {
|
||||||
|
$output->writeln('The photos app is disabled.');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$users = array();
|
||||||
|
$userId = $input->getOption('user_id');
|
||||||
|
$person_name = $input->getOption('person_name');
|
||||||
|
$mode = $input->getOption('mode');
|
||||||
|
|
||||||
|
if (!is_null($userId)) {
|
||||||
|
if ($this->userManager->get($userId) === null) {
|
||||||
|
$output->writeln("User with id <$userId> in unknown.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$users[] = $userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->userManager->callForAllUsers(function (IUser $iUser) use (&$users) {
|
||||||
|
$users[] = $iUser->getUID();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('list_person')) {
|
||||||
|
if (is_null($userId)) {
|
||||||
|
$output->writeln("List option requires option user_id!");
|
||||||
|
return 1;
|
||||||
|
} else{
|
||||||
|
$output->writeln("List of defined persons for the user <$userId> :");
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
$distintNames = $this->personMapper->findDistinctNames($userId, $modelId);
|
||||||
|
foreach ($distintNames as $key=>$distintName) {
|
||||||
|
if ($key > 0 ){
|
||||||
|
$output->write(", ");
|
||||||
|
}
|
||||||
|
$output->write($distintName->getName());
|
||||||
|
}
|
||||||
|
$output->writeln("");
|
||||||
|
$output->writeln("Done.");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($users as $userId) {
|
||||||
|
if (!is_null($person_name)) {
|
||||||
|
if (is_null($userId)) {
|
||||||
|
$output->writeln("Person_name option requires option user_id!");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
$output->writeln("Synchronizing albums for the user <$userId> and person_name <$person_name> using mode <$mode>... ");
|
||||||
|
if ($mode === "album-per-person") {
|
||||||
|
$personList = explode(",", $person_name);
|
||||||
|
foreach ($personList as $person) {
|
||||||
|
$this->photoAlbums->syncUserPersonNamesSelected($userId, $person, $output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($mode === "album-combined") {
|
||||||
|
$personList = explode(",", $person_name);
|
||||||
|
if (count($personList) < 2) {
|
||||||
|
$output->writeln("Note parameter mode <$mode> requires at least two persons separated using coma.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
$this->photoAlbums->syncUserPersonNamesCombinedAlbum($userId, $personList, $output);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$output->writeln("Error: invalid value for parameter mode <$mode>. ");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
$output->writeln("Done.");
|
||||||
|
} else {
|
||||||
|
$output->write("Synchronizing albums for the user <$userId>... ");
|
||||||
|
$this->photoAlbums->syncUser($userId);
|
||||||
|
$output->writeln("Done.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
427
facerecognition/lib/Controller/ApiController.php
Normal file
427
facerecognition/lib/Controller/ApiController.php
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Ming Tsang <nkming2@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2022 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Ming Tsang <nkming2@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Files\File;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\ApiController as NCApiController;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Face;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Person;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
use OCA\FaceRecognition\Service\UrlService;
|
||||||
|
|
||||||
|
class ApiController extends NcApiController {
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var PersonMapper */
|
||||||
|
private $personMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var UrlService */
|
||||||
|
private $urlService;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
$AppName,
|
||||||
|
IRequest $request,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
PersonMapper $personmapper,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
UrlService $urlService,
|
||||||
|
$UserId)
|
||||||
|
{
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->personMapper = $personmapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->urlService = $urlService;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API V1
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all named persons
|
||||||
|
*
|
||||||
|
* - Endpoint: /persons
|
||||||
|
* - Method: GET
|
||||||
|
* - Response: Array of persons
|
||||||
|
* - Person:
|
||||||
|
* - name: Name of the person
|
||||||
|
* - thumbFaceId: Face representing this person
|
||||||
|
* - count: Number of images associated to this person
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getPersons(): JSONResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new JSONResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$personsNames = $this->personMapper->findDistinctNames($this->userId, $modelId);
|
||||||
|
foreach ($personsNames as $personNamed) {
|
||||||
|
$facesCount = 0;
|
||||||
|
$thumbFaceId = null;
|
||||||
|
$persons = $this->personMapper->findByName($this->userId, $modelId, $personNamed->getName());
|
||||||
|
foreach ($persons as $person) {
|
||||||
|
$personFaces = $this->faceMapper->findFromCluster($this->userId, $person->getId(), $modelId);
|
||||||
|
if (is_null($thumbFaceId)) {
|
||||||
|
$thumbFaceId = $personFaces[0]->getId();
|
||||||
|
}
|
||||||
|
$facesCount += count($personFaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
$respPerson = [];
|
||||||
|
$respPerson['name'] = $personNamed->getName();
|
||||||
|
$respPerson['thumbFaceId'] = $thumbFaceId;
|
||||||
|
$respPerson['count'] = $facesCount;
|
||||||
|
|
||||||
|
$resp[] = $respPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all faces associated to a person
|
||||||
|
*
|
||||||
|
* - Endpoint: /person/<name>/faces
|
||||||
|
* - Method: GET
|
||||||
|
* - URL Arguments: name - (string) name of the person
|
||||||
|
* - Response: Array of faces
|
||||||
|
* - Face:
|
||||||
|
* - id: Face ID
|
||||||
|
* - fileId: The file where this face was found
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getFacesByPerson(string $name): JSONResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new JSONResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$clusters = $this->personMapper->findByName($this->userId, $modelId, $name);
|
||||||
|
foreach ($clusters as $cluster) {
|
||||||
|
$faces = $this->faceMapper->findFromCluster($this->userId, $cluster->getId(), $modelId);
|
||||||
|
foreach ($faces as $face) {
|
||||||
|
$image = $this->imageMapper->find($this->userId, $face->getImage());
|
||||||
|
|
||||||
|
$respFace = [];
|
||||||
|
$respFace['id'] = $face->getId();
|
||||||
|
$respFace['fileId'] = $image->getFile();
|
||||||
|
|
||||||
|
$resp[] = $respFace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API V2
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getPersonsV2($thumb_size = 128): JSONResponse {
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
$list = [];
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
$personsNames = $this->personMapper->findDistinctNames($this->userId, $modelId);
|
||||||
|
foreach ($personsNames as $personNamed) {
|
||||||
|
$name = $personNamed->getName();
|
||||||
|
$personFace = current($this->faceMapper->findFromPerson($this->userId, $name, $modelId, 1));
|
||||||
|
|
||||||
|
$person = [];
|
||||||
|
$person['name'] = $name;
|
||||||
|
$person['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), $thumb_size);
|
||||||
|
$person['count'] = $this->imageMapper->countFromPerson($this->userId, $modelId, $name);
|
||||||
|
|
||||||
|
$list[] = $person;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getPerson(string $personName, $thumb_size = 128): JSONResponse {
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
if (empty($personName))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
$resp = [];
|
||||||
|
$resp['name'] = $personName;
|
||||||
|
$resp['thumbUrl'] = null;
|
||||||
|
$resp['images'] = array();
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$personFace = current($this->faceMapper->findFromPerson($this->userId, $personName, $modelId, 1));
|
||||||
|
$resp['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), $thumb_size);
|
||||||
|
|
||||||
|
$images = $this->imageMapper->findFromPerson($this->userId, $modelId, $personName);
|
||||||
|
foreach ($images as $image) {
|
||||||
|
$node = $this->urlService->getFileNode($image->getFile());
|
||||||
|
if ($node === null) continue;
|
||||||
|
|
||||||
|
$photo = [];
|
||||||
|
$photo['basename'] = $this->urlService->getBasename($node);
|
||||||
|
$photo['filename'] = $this->urlService->getFilename($node);
|
||||||
|
$photo['mimetype'] = $this->urlService->getMimetype($node);
|
||||||
|
$photo['fileUrl'] = $this->urlService->getRedirectToFileUrl($node);
|
||||||
|
$photo['thumbUrl'] = $this->urlService->getPreviewUrl($node, 256);
|
||||||
|
|
||||||
|
$resp['images'][] = $photo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse($resp, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function updatePerson(string $personName, $name = null, $visible = null): JSONResponse {
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
if (empty($personName))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$clusters = $this->personMapper->findByName($this->userId, $modelId, $personName);
|
||||||
|
if (empty($clusters))
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
|
||||||
|
if (!is_null($name)) {
|
||||||
|
foreach ($clusters as $person) {
|
||||||
|
$person->setName($name);
|
||||||
|
$this->personMapper->update($person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// When change visibility it has a special treatment
|
||||||
|
if (!is_null($visible)) {
|
||||||
|
foreach ($clusters as $person) {
|
||||||
|
$person->setIsVisible($visible);
|
||||||
|
$person->setName($visible ? $name : null);
|
||||||
|
$this->personMapper->update($person);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: What should response?
|
||||||
|
if (is_null($name) || (!is_null($visible) && !$visible))
|
||||||
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
|
else
|
||||||
|
return $this->getPerson($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function updateCluster(int $clusterId, $name = null, $visible = null): JSONResponse {
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
$cluster = [];
|
||||||
|
if (!is_null($name)) {
|
||||||
|
$cluster = $this->personMapper->find($this->userId, $clusterId);
|
||||||
|
$cluster->setName($name);
|
||||||
|
$cluster = $this->personMapper->update($cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($visible)) {
|
||||||
|
$cluster = $this->personMapper->find($this->userId, $clusterId);
|
||||||
|
$cluster->setIsVisible($visible);
|
||||||
|
$cluster->setName($visible ? $name : null);
|
||||||
|
$cluster = $this->personMapper->update($cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: What should response?
|
||||||
|
return new JSONResponse($cluster, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function discoverPerson($minimum_count = NULL, $max_previews = 40, $thumb_size = 128): JSONResponse {
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
$discoveries = [];
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
if (is_null($minimum_count))
|
||||||
|
$minimum_count = $this->settingsService->getMinimumFacesInCluster();
|
||||||
|
|
||||||
|
$clusters = $this->personMapper->findUnassigned($this->userId, $modelId);
|
||||||
|
foreach ($clusters as $cluster) {
|
||||||
|
$clusterSize = $this->personMapper->countClusterFaces($cluster->getId());
|
||||||
|
if ($clusterSize < $minimum_count)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$personFaces = $this->faceMapper->findFromCluster($this->userId, $cluster->getId(), $modelId, $max_previews);
|
||||||
|
|
||||||
|
$faces = [];
|
||||||
|
foreach ($personFaces as $personFace) {
|
||||||
|
$image = $this->imageMapper->find($this->userId, $personFace->getImage());
|
||||||
|
|
||||||
|
$file = $this->urlService->getFileNode($image->getFile());
|
||||||
|
if ($file === null) continue;
|
||||||
|
|
||||||
|
$face = [];
|
||||||
|
$face['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), $thumb_size);
|
||||||
|
$face['fileUrl'] = $this->urlService->getRedirectToFileUrl($file);
|
||||||
|
|
||||||
|
$faces[] = $face;
|
||||||
|
}
|
||||||
|
|
||||||
|
$discovery = [];
|
||||||
|
$discovery['id'] = $cluster->getId();
|
||||||
|
$discovery['count'] = $clusterSize;
|
||||||
|
$discovery['faces'] = $faces;
|
||||||
|
|
||||||
|
$discoveries[] = $discovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
usort($discoveries, function ($a, $b) {
|
||||||
|
return $b['count'] <=> $a['count'];
|
||||||
|
});
|
||||||
|
|
||||||
|
return new JSONResponse($discoveries, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function autocomplete(string $query, $thumb_size = 128): JSONResponse {
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
if (strlen($query) < 3)
|
||||||
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
|
|
||||||
|
$resp = [];
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
$persons = $this->personMapper->findPersonsLike($this->userId, $modelId, $query);
|
||||||
|
foreach ($persons as $person) {
|
||||||
|
$name = [];
|
||||||
|
$name['name'] = $person->getName();
|
||||||
|
$name['value'] = $person->getName();
|
||||||
|
|
||||||
|
$personFace = current($this->faceMapper->findFromPerson($this->userId, $person->getName(), $modelId, 1));
|
||||||
|
$name['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), $thumb_size);
|
||||||
|
|
||||||
|
$resp[] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse($resp, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function detachFace(int $faceId, $name = null): JSONResponse {
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId))
|
||||||
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
$face = $this->faceMapper->find($faceId);
|
||||||
|
$person = $this->personMapper->detachFace($face->getPerson(), $faceId, $name);
|
||||||
|
return new JSONResponse($person, Http::STATUS_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
306
facerecognition/lib/Controller/ClusterController.php
Normal file
306
facerecognition/lib/Controller/ClusterController.php
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2018-2024 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Files\File;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Face;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Person;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
use OCA\FaceRecognition\Service\UrlService;
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterController extends Controller {
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var PersonMapper */
|
||||||
|
private $personMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var UrlService */
|
||||||
|
private $urlService;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct($AppName,
|
||||||
|
IRequest $request,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
PersonMapper $personmapper,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
UrlService $urlService,
|
||||||
|
$UserId)
|
||||||
|
{
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->personMapper = $personmapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->urlService = $urlService;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function find(int $id): DataResponse {
|
||||||
|
$person = $this->personMapper->find($this->userId, $id);
|
||||||
|
|
||||||
|
$resp = [];
|
||||||
|
$faces = [];
|
||||||
|
$personFaces = $this->faceMapper->findFromCluster($this->userId, $person->getId(), $this->settingsService->getCurrentFaceModel());
|
||||||
|
foreach ($personFaces as $personFace) {
|
||||||
|
$image = $this->imageMapper->find($this->userId, $personFace->getImage());
|
||||||
|
|
||||||
|
$file = $this->urlService->getFileNode($image->getFile());
|
||||||
|
if ($file === null) continue;
|
||||||
|
|
||||||
|
$face = [];
|
||||||
|
$face['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), 50);
|
||||||
|
$face['fileUrl'] = $this->urlService->getRedirectToFileUrl($file);
|
||||||
|
$faces[] = $face;
|
||||||
|
}
|
||||||
|
$resp['name'] = $person->getName();
|
||||||
|
$resp['id'] = $person->getId();
|
||||||
|
$resp['faces'] = $faces;
|
||||||
|
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function findByName(string $personName): DataResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
$resp['clusters'] = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new DataResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$persons = $this->personMapper->findByName($this->userId, $modelId, $personName);
|
||||||
|
foreach ($persons as $person) {
|
||||||
|
$personFaces = $this->faceMapper->findFromCluster($this->userId, $person->getId(), $modelId);
|
||||||
|
|
||||||
|
$faces = [];
|
||||||
|
foreach ($personFaces as $personFace) {
|
||||||
|
$image = $this->imageMapper->find($this->userId, $personFace->getImage());
|
||||||
|
|
||||||
|
$file = $this->urlService->getFileNode($image->getFile());
|
||||||
|
if ($file === null) continue;
|
||||||
|
|
||||||
|
$face = [];
|
||||||
|
$face['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), 50);
|
||||||
|
$face['fileUrl'] = $this->urlService->getRedirectToFileUrl($file);
|
||||||
|
$faces[] = $face;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cluster = [];
|
||||||
|
$cluster['name'] = $person->getName();
|
||||||
|
$cluster['count'] = count($personFaces);
|
||||||
|
$cluster['id'] = $person->getId();
|
||||||
|
$cluster['faces'] = $faces;
|
||||||
|
$resp['clusters'][] = $cluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function findUnassigned(): DataResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
$resp['enabled'] = $userEnabled;
|
||||||
|
$resp['clusters'] = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new DataResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
$minClusterSize = $this->settingsService->getMinimumFacesInCluster();
|
||||||
|
|
||||||
|
$clusters = $this->personMapper->findUnassigned($this->userId, $modelId);
|
||||||
|
foreach ($clusters as $cluster) {
|
||||||
|
$clusterSize = $this->personMapper->countClusterFaces($cluster->getId());
|
||||||
|
if ($clusterSize < $minClusterSize)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$personFaces = $this->faceMapper->findFromCluster($this->userId, $cluster->getId(), $modelId, 40);
|
||||||
|
$faces = [];
|
||||||
|
foreach ($personFaces as $personFace) {
|
||||||
|
$image = $this->imageMapper->find($this->userId, $personFace->getImage());
|
||||||
|
|
||||||
|
$file = $this->urlService->getFileNode($image->getFile());
|
||||||
|
if ($file === null) continue;
|
||||||
|
|
||||||
|
$face = [];
|
||||||
|
$face['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), 50);
|
||||||
|
$face['fileUrl'] = $this->urlService->getRedirectToFileUrl($file);
|
||||||
|
|
||||||
|
$faces[] = $face;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry = [];
|
||||||
|
$entry['count'] = $clusterSize;
|
||||||
|
$entry['id'] = $cluster->getId();
|
||||||
|
$entry['faces'] = $faces;
|
||||||
|
$resp['clusters'][] = $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
Public function findIgnored(): DataResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
$resp['enabled'] = $userEnabled;
|
||||||
|
$resp['clusters'] = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new DataResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
$minClusterSize = $this->settingsService->getMinimumFacesInCluster();
|
||||||
|
|
||||||
|
$clusters = $this->personMapper->findIgnored($this->userId, $modelId);
|
||||||
|
foreach ($clusters as $cluster) {
|
||||||
|
$clusterSize = $this->personMapper->countClusterFaces($cluster->getId());
|
||||||
|
if ($clusterSize < $minClusterSize)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$personFaces = $this->faceMapper->findFromCluster($this->userId, $cluster->getId(), $modelId, 40);
|
||||||
|
$faces = [];
|
||||||
|
foreach ($personFaces as $personFace) {
|
||||||
|
$image = $this->imageMapper->find($this->userId, $personFace->getImage());
|
||||||
|
|
||||||
|
$file = $this->urlService->getFileNode($image->getFile());
|
||||||
|
if ($file === null) continue;
|
||||||
|
|
||||||
|
$face = [];
|
||||||
|
$face['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), 50);
|
||||||
|
$face['fileUrl'] = $this->urlService->getRedirectToFileUrl($file);
|
||||||
|
|
||||||
|
$faces[] = $face;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry = [];
|
||||||
|
$entry['count'] = $clusterSize;
|
||||||
|
$entry['id'] = $cluster->getId();
|
||||||
|
$entry['faces'] = $faces;
|
||||||
|
$resp['clusters'][] = $entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @param bool $visible
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function setVisibility (int $id, bool $visible): DataResponse {
|
||||||
|
$resp = array();
|
||||||
|
$this->personMapper->setVisibility($id, $visible);
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @param int $id if of cluster
|
||||||
|
* @param int $face id of face.
|
||||||
|
* @param string|null $name optional name to rename it.
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function detachFace (int $id, int $face, $name = null): DataResponse {
|
||||||
|
$person = $this->personMapper->detachFace($id, $face, $name);
|
||||||
|
return new DataResponse($person);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @param int $id of cluster
|
||||||
|
* @param string $name to rename them.
|
||||||
|
* @param int|null $face_id optional face id if you just want to name that face
|
||||||
|
*
|
||||||
|
* @return DataResponse new person with that update.
|
||||||
|
*/
|
||||||
|
public function updateName($id, $name, $face_id = null): DataResponse {
|
||||||
|
if (is_null($face_id)) {
|
||||||
|
$person = $this->personMapper->find($this->userId, $id);
|
||||||
|
$person->setName($name);
|
||||||
|
$this->personMapper->update($person);
|
||||||
|
} else {
|
||||||
|
$person = $this->personMapper->detachFace($id, $face_id, $name);
|
||||||
|
}
|
||||||
|
return new DataResponse($person);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
234
facerecognition/lib/Controller/FaceController.php
Normal file
234
facerecognition/lib/Controller/FaceController.php
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2018-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Controller;
|
||||||
|
|
||||||
|
use OCP\Image as OCP_Image;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Files\IRootFolder;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Face;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
class FaceController extends Controller {
|
||||||
|
|
||||||
|
/** @var IRootFolder */
|
||||||
|
private $rootFolder;
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct($AppName,
|
||||||
|
IRequest $request,
|
||||||
|
IRootFolder $rootFolder,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
$UserId)
|
||||||
|
{
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
|
||||||
|
$this->rootFolder = $rootFolder;
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return DataDisplayResponse|JSONResponse
|
||||||
|
*/
|
||||||
|
public function getThumb ($id, $size) {
|
||||||
|
$face = $this->faceMapper->find($id);
|
||||||
|
if ($face === null) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$image = $this->imageMapper->find($this->userId, $face->getImage());
|
||||||
|
if ($image === null) {
|
||||||
|
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileId = $image->getFile();
|
||||||
|
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||||
|
$nodes = $userFolder->getById($fileId);
|
||||||
|
$file = $nodes[0];
|
||||||
|
|
||||||
|
$ownerView = new \OC\Files\View('/'. $this->userId . '/files');
|
||||||
|
$path = $userFolder->getRelativePath($file->getPath());
|
||||||
|
|
||||||
|
$img = new OCP_Image();
|
||||||
|
$fileName = $ownerView->getLocalFile($path);
|
||||||
|
$img->loadFromFile($fileName);
|
||||||
|
$img->fixOrientation();
|
||||||
|
|
||||||
|
$x = $face->getX();
|
||||||
|
$y = $face->getY();
|
||||||
|
$w = $face->getWidth();
|
||||||
|
$h = $face->getHeight();
|
||||||
|
|
||||||
|
$padding = $h*0.35;
|
||||||
|
$x -= $padding;
|
||||||
|
$y -= $padding;
|
||||||
|
$w += $padding*2;
|
||||||
|
$h += $padding*2;
|
||||||
|
|
||||||
|
if ($this->settingsService->getObfuscateFaces()) {
|
||||||
|
$this->hipsterize($img, $face);
|
||||||
|
}
|
||||||
|
|
||||||
|
$img->crop($x, $y, $w, $h);
|
||||||
|
$img->scaleDownToFit($size, $size);
|
||||||
|
|
||||||
|
$resp = new DataDisplayResponse($img->data(), Http::STATUS_OK, ['Content-Type' => $img->mimeType()]);
|
||||||
|
$resp->setETag((string)crc32($img->data()));
|
||||||
|
$resp->cacheFor(7 * 24 * 60 * 60);
|
||||||
|
$resp->setLastModified(new \DateTime('now', new \DateTimeZone('GMT')));
|
||||||
|
|
||||||
|
return $resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return DataDisplayResponse|JSONResponse
|
||||||
|
*/
|
||||||
|
public function getPersonThumb (string $name, int $size) {
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
$personFace = current($this->faceMapper->findFromPerson($this->userId, $name, $modelId, 1));
|
||||||
|
return $this->getThumb($personFace->getId(), $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function hipsterize(OCP_Image &$image, Entity &$face) {
|
||||||
|
$imgResource = $image->resource();
|
||||||
|
|
||||||
|
$landmarks = json_decode($face->getLandmarks(), true);
|
||||||
|
if (count($landmarks) === 5) {
|
||||||
|
$eyesX1 = $landmarks[2]['x'];
|
||||||
|
$eyesY1 = $landmarks[2]['y'];
|
||||||
|
|
||||||
|
$eyesX2 = $landmarks[0]['x'];
|
||||||
|
$eyesY2 = $landmarks[0]['y'];
|
||||||
|
|
||||||
|
$eyesXC = ($eyesX2 + $eyesX1)/2;
|
||||||
|
$eyesYC = ($eyesY2 + $eyesY1)/2;
|
||||||
|
|
||||||
|
$mustacheXC = $landmarks[4]['x'];
|
||||||
|
$mustacheYC = $landmarks[4]['y'];
|
||||||
|
}
|
||||||
|
else if (count($landmarks) === 68) {
|
||||||
|
$eyesX1 = $landmarks[36]['x'];
|
||||||
|
$eyesY1 = $landmarks[36]['y'];
|
||||||
|
$eyesX2 = $landmarks[45]['x'];
|
||||||
|
$eyesY2 = $landmarks[45]['y'];
|
||||||
|
|
||||||
|
$eyesXC = ($eyesX2 + $eyesX1)/2;
|
||||||
|
$eyesYC = ($eyesY2 + $eyesY1)/2;
|
||||||
|
|
||||||
|
$mustacheXC = $landmarks[52]['x'];
|
||||||
|
$mustacheYC = $landmarks[52]['y'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$eyesW = $eyesX2 - $eyesX1;
|
||||||
|
$eyesH = $eyesY2 - $eyesY1;
|
||||||
|
|
||||||
|
$eyesL = sqrt(pow($eyesW, 2) + pow($eyesH, 2));
|
||||||
|
$angle = rad2deg(atan(-$eyesH/$eyesW));
|
||||||
|
|
||||||
|
$glassesGd = imagecreatefrompng(\OC_App::getAppPath('facerecognition') . '/img/glasses.png');
|
||||||
|
if ($glassesGd === false) return;
|
||||||
|
|
||||||
|
$fillColor = imagecolorallocatealpha($glassesGd, 0, 0, 0, 127);
|
||||||
|
$glassesGd = imagerotate($glassesGd, $angle, $fillColor);
|
||||||
|
if ($glassesGd === false) return;
|
||||||
|
|
||||||
|
$glassesW = imagesx($glassesGd);
|
||||||
|
$glassesH = imagesy($glassesGd);
|
||||||
|
|
||||||
|
$glassesRatio = $eyesL/$glassesW*1.5;
|
||||||
|
|
||||||
|
$glassesDestX = intval($eyesXC - $glassesW * $glassesRatio / 2);
|
||||||
|
$glassesDestY = intval($eyesYC - $glassesH * $glassesRatio / 2);
|
||||||
|
$glassesDestW = intval($glassesW * $glassesRatio);
|
||||||
|
$glassesDestH = intval($glassesH * $glassesRatio);
|
||||||
|
|
||||||
|
imagecopyresized($imgResource, $glassesGd, $glassesDestX, $glassesDestY, 0, 0, $glassesDestW, $glassesDestH, $glassesW, $glassesH);
|
||||||
|
|
||||||
|
$mustacheGd = imagecreatefrompng(\OC_App::getAppPath('facerecognition') . '/img/mustache.png');
|
||||||
|
if ($mustacheGd === false) return;
|
||||||
|
|
||||||
|
$fillColor = imagecolorallocatealpha($mustacheGd, 0, 0, 0, 127);
|
||||||
|
$mustacheGd = imagerotate($mustacheGd, $angle, $fillColor);
|
||||||
|
if ($mustacheGd === false) return;
|
||||||
|
|
||||||
|
$mustacheW = imagesx($mustacheGd);
|
||||||
|
$mustacheH = imagesy($mustacheGd);
|
||||||
|
|
||||||
|
$mustacheRatio = $eyesL/$glassesW*1.1;
|
||||||
|
|
||||||
|
$mustacheDestX = intval($mustacheXC - $mustacheW * $mustacheRatio / 2);
|
||||||
|
$mustacheDestY = intval($mustacheYC - $mustacheH * $mustacheRatio / 2);
|
||||||
|
$mustacheDestW = intval($mustacheW * $mustacheRatio);
|
||||||
|
$mustacheDestH = intval($mustacheH * $mustacheRatio);
|
||||||
|
|
||||||
|
imagecopyresized($imgResource, $mustacheGd, $mustacheDestX, $mustacheDestY, 0, 0, $mustacheDestW, $mustacheDestH, $mustacheW, $mustacheH);
|
||||||
|
|
||||||
|
$image->setResource($imgResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
166
facerecognition/lib/Controller/FileController.php
Normal file
166
facerecognition/lib/Controller/FileController.php
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<?php
|
||||||
|
namespace OCA\FaceRecognition\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Face;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Person;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\FileService;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\UrlService;
|
||||||
|
|
||||||
|
class FileController extends Controller {
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var PersonMapper */
|
||||||
|
private $personMapper;
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
/** @var FileService */
|
||||||
|
private $fileService;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var UrlService */
|
||||||
|
private $urlService;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct($AppName,
|
||||||
|
IRequest $request,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
PersonMapper $personMapper,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
FileService $fileService,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
UrlService $urlService,
|
||||||
|
$UserId)
|
||||||
|
{
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->personMapper = $personMapper;
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->fileService = $fileService;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->urlService = $urlService;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Get persons on file.
|
||||||
|
*
|
||||||
|
* @param string $fullpath of the file to get persons
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getPersonsFromPath(string $fullpath) {
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId)) {
|
||||||
|
$resp['enabled'] = false;
|
||||||
|
return new JSONResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $this->fileService->getFileByPath($fullpath);
|
||||||
|
|
||||||
|
$fileId = $file->getId();
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$image = $this->imageMapper->findFromFile($this->userId, $modelId, $fileId);
|
||||||
|
|
||||||
|
$resp['enabled'] = true;
|
||||||
|
$resp['is_allowed'] = $this->fileService->isAllowedNode($file);
|
||||||
|
$resp['parent_detection'] = !$this->fileService->isUnderNoDetection($file);
|
||||||
|
$resp['image_id'] = $image ? $image->getId() : 0;
|
||||||
|
$resp['is_processed'] = $image ? $image->getIsProcessed() : false;
|
||||||
|
$resp['error'] = $image ? $image->getError() : null;
|
||||||
|
$resp['persons'] = array();
|
||||||
|
|
||||||
|
$faces = $this->faceMapper->findFromFile($this->userId, $modelId, $fileId);
|
||||||
|
foreach ($faces as $face) {
|
||||||
|
// When there are faces but still dont have person, the process is not completed yet.
|
||||||
|
// See issue https://github.com/matiasdelellis/facerecognition/issues/255
|
||||||
|
if (!$face->getPerson()) {
|
||||||
|
$resp['is_processed'] = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$person = $this->personMapper->find($this->userId, $face->getPerson());
|
||||||
|
$personName = $person->getName();
|
||||||
|
|
||||||
|
$facePerson = array();
|
||||||
|
$facePerson['name'] = $personName;
|
||||||
|
$facePerson['person_id'] = $person->getId();
|
||||||
|
$facePerson['person_visible'] = $person->getIsVisible();
|
||||||
|
$facePerson['face_id'] = $face->getId();
|
||||||
|
$facePerson['thumb_url'] = $this->urlService->getThumbUrl($face->getId(), 50);
|
||||||
|
$facePerson['photos_url'] = $personName ? $this->urlService->getRedirectToPersonUrl($personName) : null;
|
||||||
|
|
||||||
|
$resp['persons'][] = $facePerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JSONResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Get if folder if folder is enabled
|
||||||
|
*
|
||||||
|
* @param string $fullpath of the folder
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getFolderOptions(string $fullpath) {
|
||||||
|
$resp = array();
|
||||||
|
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId)) {
|
||||||
|
$resp['enabled'] = false;
|
||||||
|
return new JSONResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
$folder = $this->fileService->getFileByPath($fullpath);
|
||||||
|
|
||||||
|
$resp['enabled'] = 'true';
|
||||||
|
$resp['is_allowed'] = $this->fileService->isAllowedNode($folder);
|
||||||
|
$resp['parent_detection'] = !$this->fileService->isUnderNoDetection($folder);
|
||||||
|
$resp['descendant_detection'] = $this->fileService->getDescendantDetection($folder);
|
||||||
|
|
||||||
|
return new JSONResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* Apply option to folder to enabled or disable it.
|
||||||
|
*
|
||||||
|
* @param string $fullpath of the folder.
|
||||||
|
* @param bool $detection
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function setFolderOptions(string $fullpath, bool $detection) {
|
||||||
|
$folder = $this->fileService->getFileByPath($fullpath);
|
||||||
|
$this->fileService->setDescendantDetection($folder, $detection);
|
||||||
|
|
||||||
|
return $this->getFolderOptions($fullpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
182
facerecognition/lib/Controller/OcsApiController.php
Normal file
182
facerecognition/lib/Controller/OcsApiController.php
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2021 Ming Tsang <nkming2@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Ming Tsang <nkming2@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Files\File;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||||
|
use OCP\AppFramework\OCSController;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Face;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Person;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
use OCA\FaceRecognition\Service\UrlService;
|
||||||
|
|
||||||
|
class OcsApiController extends OCSController {
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var PersonMapper */
|
||||||
|
private $personMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var UrlService */
|
||||||
|
private $urlService;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
$AppName,
|
||||||
|
IRequest $request,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
PersonMapper $personmapper,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
UrlService $urlService,
|
||||||
|
$UserId)
|
||||||
|
{
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->personMapper = $personmapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->urlService = $urlService;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API V1
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all named persons
|
||||||
|
*
|
||||||
|
* - Endpoint: /persons
|
||||||
|
* - Method: GET
|
||||||
|
* - Response: Array of persons
|
||||||
|
* - Person:
|
||||||
|
* - name: Name of the person
|
||||||
|
* - thumbFaceId: Face representing this person
|
||||||
|
* - count: Number of images associated to this person
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function getPersonsV1(): DataResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new DataResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$personsNames = $this->personMapper->findDistinctNames($this->userId, $modelId);
|
||||||
|
foreach ($personsNames as $personNamed) {
|
||||||
|
$facesCount = 0;
|
||||||
|
$thumbFaceId = null;
|
||||||
|
$persons = $this->personMapper->findByName($this->userId, $modelId, $personNamed->getName());
|
||||||
|
foreach ($persons as $person) {
|
||||||
|
$personFaces = $this->faceMapper->findFromCluster($this->userId, $person->getId(), $modelId);
|
||||||
|
if (is_null($thumbFaceId)) {
|
||||||
|
$thumbFaceId = $personFaces[0]->getId();
|
||||||
|
}
|
||||||
|
$facesCount += count($personFaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
$respPerson = [];
|
||||||
|
$respPerson['name'] = $personNamed->getName();
|
||||||
|
$respPerson['thumbFaceId'] = $thumbFaceId;
|
||||||
|
$respPerson['count'] = $facesCount;
|
||||||
|
|
||||||
|
$resp[] = $respPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all faces associated to a person
|
||||||
|
*
|
||||||
|
* - Endpoint: /person/<name>/faces
|
||||||
|
* - Method: GET
|
||||||
|
* - URL Arguments: name - (string) name of the person
|
||||||
|
* - Response: Array of faces
|
||||||
|
* - Face:
|
||||||
|
* - id: Face ID
|
||||||
|
* - fileId: The file where this face was found
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function getFacesByPerson(string $name): DataResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new DataResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$clusters = $this->personMapper->findByName($this->userId, $modelId, $name);
|
||||||
|
foreach ($clusters as $cluster) {
|
||||||
|
$faces = $this->faceMapper->findFromCluster($this->userId, $cluster->getId(), $modelId);
|
||||||
|
foreach ($faces as $face) {
|
||||||
|
$image = $this->imageMapper->find($this->userId, $face->getImage());
|
||||||
|
|
||||||
|
$respFace = [];
|
||||||
|
$respFace['id'] = $face->getId();
|
||||||
|
$respFace['fileId'] = $image->getFile();
|
||||||
|
|
||||||
|
$resp[] = $respFace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
216
facerecognition/lib/Controller/PersonController.php
Normal file
216
facerecognition/lib/Controller/PersonController.php
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2018-2024 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Files\File;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Face;
|
||||||
|
use OCA\FaceRecognition\Db\FaceMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Image;
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\Person;
|
||||||
|
use OCA\FaceRecognition\Db\PersonMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
use OCA\FaceRecognition\Service\UrlService;
|
||||||
|
|
||||||
|
|
||||||
|
class PersonController extends Controller {
|
||||||
|
|
||||||
|
/** @var FaceMapper */
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var PersonMapper */
|
||||||
|
private $personMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var UrlService */
|
||||||
|
private $urlService;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct($AppName,
|
||||||
|
IRequest $request,
|
||||||
|
FaceMapper $faceMapper,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
PersonMapper $personmapper,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
UrlService $urlService,
|
||||||
|
$UserId)
|
||||||
|
{
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->personMapper = $personmapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->urlService = $urlService;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function index(): DataResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
$resp['persons'] = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new DataResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$personsNames = $this->personMapper->findDistinctNames($this->userId, $modelId);
|
||||||
|
foreach ($personsNames as $personNamed) {
|
||||||
|
$name = $personNamed->getName();
|
||||||
|
$personFace = current($this->faceMapper->findFromPerson($this->userId, $name, $modelId, 1));
|
||||||
|
|
||||||
|
$person = [];
|
||||||
|
$person['name'] = $name;
|
||||||
|
$person['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), 128);
|
||||||
|
$person['count'] = $this->imageMapper->countFromPerson($this->userId, $modelId, $name);
|
||||||
|
|
||||||
|
$resp['persons'][] = $person;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function find(string $personName): DataResponse {
|
||||||
|
$userEnabled = $this->settingsService->getUserEnabled($this->userId);
|
||||||
|
|
||||||
|
$resp = array();
|
||||||
|
$resp['name'] = $personName;
|
||||||
|
$resp['thumbUrl'] = null;
|
||||||
|
$resp['images'] = array();
|
||||||
|
|
||||||
|
if (!$userEnabled)
|
||||||
|
return new DataResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$personFace = current($this->faceMapper->findFromPerson($this->userId, $personName, $modelId, 1));
|
||||||
|
$resp['thumbUrl'] = $this->urlService->getThumbUrl($personFace->getId(), 128);
|
||||||
|
|
||||||
|
$images = $this->imageMapper->findFromPerson($this->userId, $modelId, $personName);
|
||||||
|
foreach ($images as $image) {
|
||||||
|
$node = $this->urlService->getFileNode($image->getFile());
|
||||||
|
if ($node === null) continue;
|
||||||
|
|
||||||
|
$photo = [];
|
||||||
|
$photo['basename'] = $this->urlService->getBasename($node);
|
||||||
|
$photo['filename'] = $this->urlService->getFilename($node);
|
||||||
|
$photo['mimetype'] = $this->urlService->getMimetype($node);
|
||||||
|
$photo['fileUrl'] = $this->urlService->getRedirectToFileUrl($node);
|
||||||
|
$photo['thumbUrl'] = $this->urlService->getPreviewUrl($node, 256);
|
||||||
|
|
||||||
|
$resp['images'][] = $photo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @param string $personName
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function updateName($personName, $name): DataResponse {
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
$clusters = $this->personMapper->findByName($this->userId, $modelId, $personName);
|
||||||
|
foreach ($clusters as $person) {
|
||||||
|
$person->setName($name);
|
||||||
|
$this->personMapper->update($person);
|
||||||
|
}
|
||||||
|
return $this->find($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @param string $personName
|
||||||
|
* @param bool $visible
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function setVisibility ($personName, bool $visible): DataResponse {
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
$clusters = $this->personMapper->findByName($this->userId, $modelId, $personName);
|
||||||
|
foreach ($clusters as $cluster) {
|
||||||
|
$this->personMapper->setVisibility($cluster->getId(), $visible);
|
||||||
|
}
|
||||||
|
return $this->find($personName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @return DataResponse
|
||||||
|
*/
|
||||||
|
public function autocomplete(string $query): DataResponse {
|
||||||
|
$resp = array();
|
||||||
|
|
||||||
|
if (!$this->settingsService->getUserEnabled($this->userId))
|
||||||
|
return new DataResponse($resp);
|
||||||
|
|
||||||
|
$modelId = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$persons = $this->personMapper->findPersonsLike($this->userId, $modelId, $query);
|
||||||
|
foreach ($persons as $person) {
|
||||||
|
$name = [];
|
||||||
|
$name['name'] = $person->getName();
|
||||||
|
$name['value'] = $person->getName();
|
||||||
|
$resp[] = $name;
|
||||||
|
}
|
||||||
|
return new DataResponse($resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
88
facerecognition/lib/Controller/ProcessController.php
Normal file
88
facerecognition/lib/Controller/ProcessController.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Db\ImageMapper;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
class ProcessController extends Controller {
|
||||||
|
|
||||||
|
/** @var ImageMapper */
|
||||||
|
private $imageMapper;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct($AppName,
|
||||||
|
IRequest $request,
|
||||||
|
ImageMapper $imageMapper,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
$UserId)
|
||||||
|
{
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
|
||||||
|
$this->imageMapper = $imageMapper;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just print a global status of the analysis.
|
||||||
|
*
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function index(): JSONResponse {
|
||||||
|
|
||||||
|
$model = $this->settingsService->getCurrentFaceModel();
|
||||||
|
|
||||||
|
$totalImages = $this->imageMapper->countImages($model);
|
||||||
|
$processedImages = $this->imageMapper->countProcessedImages($model);
|
||||||
|
$avgProcessingTime = $this->imageMapper->avgProcessingDuration($model);
|
||||||
|
|
||||||
|
// TODO: How to know the real state of the process?
|
||||||
|
$status = ($processedImages > 0);
|
||||||
|
|
||||||
|
$estimatedTime = ($totalImages - $processedImages) * $avgProcessingTime/1000;
|
||||||
|
|
||||||
|
$estimatedFinalize = $estimatedTime;
|
||||||
|
|
||||||
|
$params = array(
|
||||||
|
'status' => $status,
|
||||||
|
'estimatedFinalize' => $estimatedFinalize,
|
||||||
|
'totalImages' => $totalImages,
|
||||||
|
'processedImages' => $processedImages
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JSONResponse($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
264
facerecognition/lib/Controller/SettingsController.php
Normal file
264
facerecognition/lib/Controller/SettingsController.php
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019-2020 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Controller;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\IL10N;
|
||||||
|
|
||||||
|
use OCP\Util as OCP_Util;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Helper\MemoryLimits;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Model\IModel;
|
||||||
|
use OCA\FaceRecognition\Model\ModelManager;
|
||||||
|
|
||||||
|
use OCA\FaceRecognition\Service\SettingsService;
|
||||||
|
|
||||||
|
class SettingsController extends Controller {
|
||||||
|
|
||||||
|
/** @var ModelManager */
|
||||||
|
private $modelManager;
|
||||||
|
|
||||||
|
/** @var SettingsService */
|
||||||
|
private $settingsService;
|
||||||
|
|
||||||
|
/** @var \OCP\IL10N */
|
||||||
|
protected $l10n;
|
||||||
|
|
||||||
|
/** @var IUserManager */
|
||||||
|
private $userManager;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
const STATE_OK = 0;
|
||||||
|
const STATE_FALSE = 1;
|
||||||
|
const STATE_SUCCESS = 2;
|
||||||
|
const STATE_ERROR = 3;
|
||||||
|
|
||||||
|
public function __construct ($appName,
|
||||||
|
IRequest $request,
|
||||||
|
ModelManager $modelManager,
|
||||||
|
SettingsService $settingsService,
|
||||||
|
IL10N $l10n,
|
||||||
|
IUserManager $userManager,
|
||||||
|
$userId)
|
||||||
|
{
|
||||||
|
parent::__construct($appName, $request);
|
||||||
|
|
||||||
|
$this->appName = $appName;
|
||||||
|
$this->modelManager = $modelManager;
|
||||||
|
$this->settingsService = $settingsService;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @param $type
|
||||||
|
* @param $value
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function setUserValue($type, $value) {
|
||||||
|
$status = self::STATE_SUCCESS;
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case SettingsService::USER_ENABLED_KEY:
|
||||||
|
$enabled = ($value === 'true');
|
||||||
|
$this->settingsService->setUserEnabled($enabled);
|
||||||
|
if ($enabled) {
|
||||||
|
$this->settingsService->setUserFullScanDone(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$status = self::STATE_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
$result = [
|
||||||
|
'status' => $status,
|
||||||
|
'value' => $value
|
||||||
|
];
|
||||||
|
|
||||||
|
return new JSONResponse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @param $type
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getUserValue($type) {
|
||||||
|
$status = self::STATE_OK;
|
||||||
|
$value ='nodata';
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case SettingsService::USER_ENABLED_KEY:
|
||||||
|
$value = $this->settingsService->getUserEnabled();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$status = self::STATE_FALSE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'status' => $status,
|
||||||
|
'value' => $value
|
||||||
|
];
|
||||||
|
|
||||||
|
return new JSONResponse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $type
|
||||||
|
* @param $value
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function setAppValue($type, $value) {
|
||||||
|
$status = self::STATE_SUCCESS;
|
||||||
|
$message = "";
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case SettingsService::ANALYSIS_IMAGE_AREA_KEY:
|
||||||
|
if (!is_numeric ($value)) {
|
||||||
|
$status = self::STATE_ERROR;
|
||||||
|
$message = $this->l10n->t("The format seems to be incorrect.");
|
||||||
|
$value = '-1';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$model = $this->modelManager->getCurrentModel();
|
||||||
|
if (is_null($model)) {
|
||||||
|
$status = self::STATE_ERROR;
|
||||||
|
$message = $this->l10n->t("Seems you haven't set up any analysis model yet");
|
||||||
|
$value = '-1';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Apply prudent limits.
|
||||||
|
if ($value > 0 && $value < SettingsService::MINIMUM_ANALYSIS_IMAGE_AREA) {
|
||||||
|
$value = SettingsService::MINIMUM_ANALYSIS_IMAGE_AREA;
|
||||||
|
$message = $this->l10n->t("The minimum recommended area is %s", $this->getFourByThreeRelation($value));
|
||||||
|
$status = self::STATE_ERROR;
|
||||||
|
} else if ($value > SettingsService::MAXIMUM_ANALYSIS_IMAGE_AREA) {
|
||||||
|
$value = SettingsService::MAXIMUM_ANALYSIS_IMAGE_AREA;
|
||||||
|
$message = $this->l10n->t("The maximum recommended area is %s", $this->getFourByThreeRelation($value));
|
||||||
|
$status = self::STATE_ERROR;
|
||||||
|
}
|
||||||
|
$model->open();
|
||||||
|
$maxImageArea = $model->getMaximumArea();
|
||||||
|
if ($value > $maxImageArea) {
|
||||||
|
$value = $maxImageArea;
|
||||||
|
$message = $this->l10n->t("The model does not recommend an area greater than %s", $this->getFourByThreeRelation($value));
|
||||||
|
$status = self::STATE_ERROR;
|
||||||
|
}
|
||||||
|
// If any validation error saves the value
|
||||||
|
if ($status !== self::STATE_ERROR) {
|
||||||
|
$message = $this->l10n->t("The changes were saved. It will be taken into account in the next analysis.");
|
||||||
|
$this->settingsService->setAnalysisImageArea((int) $value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SettingsService::SENSITIVITY_KEY:
|
||||||
|
$this->settingsService->setSensitivity($value);
|
||||||
|
$this->userManager->callForSeenUsers(function(IUser $user) {
|
||||||
|
$this->settingsService->setNeedRecreateClusters(true, $user->getUID());
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SettingsService::MINIMUM_CONFIDENCE_KEY:
|
||||||
|
$this->settingsService->setMinimumConfidence($value);
|
||||||
|
$this->userManager->callForSeenUsers(function(IUser $user) {
|
||||||
|
$this->settingsService->setNeedRecreateClusters(true, $user->getUID());
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SettingsService::MINIMUM_FACES_IN_CLUSTER_KEY:
|
||||||
|
$this->settingsService->setMinimumFacesInCluster($value);
|
||||||
|
break;
|
||||||
|
case SettingsService::OBFUSCATE_FACE_THUMBS_KEY:
|
||||||
|
$this->settingsService->setObfuscateFaces(!$this->settingsService->getObfuscateFaces());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$status = self::STATE_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
$result = [
|
||||||
|
'status' => $status,
|
||||||
|
'message' => $message,
|
||||||
|
'value' => $value
|
||||||
|
];
|
||||||
|
|
||||||
|
return new JSONResponse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $type
|
||||||
|
* @return JSONResponse
|
||||||
|
*/
|
||||||
|
public function getAppValue($type) {
|
||||||
|
$status = self::STATE_OK;
|
||||||
|
$value = 'nodata';
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case SettingsService::SENSITIVITY_KEY:
|
||||||
|
$value = $this->settingsService->getSensitivity();
|
||||||
|
break;
|
||||||
|
case SettingsService::MINIMUM_CONFIDENCE_KEY:
|
||||||
|
$value = $this->settingsService->getMinimumConfidence();
|
||||||
|
break;
|
||||||
|
case SettingsService::MINIMUM_FACES_IN_CLUSTER_KEY:
|
||||||
|
$value = $this->settingsService->getMinimumFacesInCluster();
|
||||||
|
break;
|
||||||
|
case SettingsService::ANALYSIS_IMAGE_AREA_KEY:
|
||||||
|
$value = $this->settingsService->getAnalysisImageArea();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
$result = [
|
||||||
|
'status' => $status,
|
||||||
|
'value' => $value
|
||||||
|
];
|
||||||
|
|
||||||
|
return new JSONResponse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an approximate image size with 4x3 ratio
|
||||||
|
* @param int $area area in pixels^2
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getFourByThreeRelation(int $area): string {
|
||||||
|
$width = intval(sqrt($area * 4 / 3));
|
||||||
|
$height = intval($width * 3 / 4);
|
||||||
|
return $width . 'x' . $height . ' (4x3)';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
199
facerecognition/lib/Db/Face.php
Normal file
199
facerecognition/lib/Db/Face.php
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2021 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Db;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Face represents one found face from one image.
|
||||||
|
*
|
||||||
|
* @method int getImage()
|
||||||
|
* @method int getPerson()
|
||||||
|
* @method int getX()
|
||||||
|
* @method int getY()
|
||||||
|
* @method int getWidth()
|
||||||
|
* @method int getHeight()
|
||||||
|
* @method float getConfidence()
|
||||||
|
* @method void setImage(int $image)
|
||||||
|
* @method void setPerson(int $person)
|
||||||
|
* @method void setX(int $x)
|
||||||
|
* @method void setY(int $y)
|
||||||
|
* @method void setWidth(int $width)
|
||||||
|
* @method void setHeight(int $height)
|
||||||
|
* @method void setConfidence(float $confidence)
|
||||||
|
*/
|
||||||
|
class Face extends Entity implements JsonSerializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image from this face originated from.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* */
|
||||||
|
public $image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Person (cluster) that this face belongs to
|
||||||
|
*
|
||||||
|
* @var int|null
|
||||||
|
* */
|
||||||
|
public $person;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Left border of bounding rectangle for this face
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* */
|
||||||
|
public $x;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top border of bounding rectangle for this face
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* */
|
||||||
|
public $y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Width of this face from the left border
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* */
|
||||||
|
public $width;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Height of this face from top border
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* */
|
||||||
|
public $height;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confidence of face detection obtained from the model
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* */
|
||||||
|
public $confidence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If it can be grouped according to the configurations
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
**/
|
||||||
|
public $isGroupable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* landmarks for this face.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* */
|
||||||
|
public $landmarks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 128D face descriptor for this face.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* */
|
||||||
|
public $descriptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time when this face was found
|
||||||
|
*
|
||||||
|
* @var \DateTime
|
||||||
|
* */
|
||||||
|
public $creationTime;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->addType('id', 'integer');
|
||||||
|
$this->addType('image', 'integer');
|
||||||
|
$this->addType('person', 'integer');
|
||||||
|
$this->addType('isGroupable', 'bool');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to create Face from face structure that is returned as output of the model.
|
||||||
|
*
|
||||||
|
* @param int $image Image Id
|
||||||
|
* @param array $faceFromModel Face obtained from DNN model
|
||||||
|
* @return Face Created face
|
||||||
|
*/
|
||||||
|
public static function fromModel(int $imageId, array $faceFromModel): Face {
|
||||||
|
$face = new Face();
|
||||||
|
$face->image = $imageId;
|
||||||
|
$face->person = null;
|
||||||
|
$face->x = $faceFromModel['left'];
|
||||||
|
$face->y = $faceFromModel['top'];
|
||||||
|
$face->width = $faceFromModel['right'] - $faceFromModel['left'];
|
||||||
|
$face->height = $faceFromModel['bottom'] - $faceFromModel['top'];
|
||||||
|
$face->confidence = $faceFromModel['detection_confidence'];
|
||||||
|
$face->landmarks = isset($faceFromModel['landmarks']) ? $faceFromModel['landmarks'] : [];
|
||||||
|
$face->descriptor = isset($faceFromModel['descriptor']) ? $faceFromModel['descriptor'] : [];
|
||||||
|
$face->setCreationTime(new \DateTime());
|
||||||
|
return $face;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'image' => $this->image,
|
||||||
|
'person' => $this->person,
|
||||||
|
'x' => $this->x,
|
||||||
|
'y' => $this->y,
|
||||||
|
'width' => $this->width,
|
||||||
|
'height' => $this->height,
|
||||||
|
'confidence' => $this->confidence,
|
||||||
|
'is_groupable' => $this->isGroupable,
|
||||||
|
'landmarks' => $this->landmarks,
|
||||||
|
'descriptor' => $this->descriptor,
|
||||||
|
'creation_time' => $this->creationTime
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLandmarks(): string {
|
||||||
|
return json_encode($this->landmarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLandmarks($landmarks): void {
|
||||||
|
$this->landmarks = json_decode($landmarks);
|
||||||
|
$this->markFieldUpdated('landmarks');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescriptor(): string {
|
||||||
|
return json_encode($this->descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDescriptor($descriptor): void {
|
||||||
|
$this->descriptor = json_decode($descriptor);
|
||||||
|
$this->markFieldUpdated('descriptor');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreationTime($creationTime): void {
|
||||||
|
if (is_a($creationTime, 'DateTime')) {
|
||||||
|
$this->creationTime = $creationTime;
|
||||||
|
} else {
|
||||||
|
$this->creationTime = new \DateTime($creationTime);
|
||||||
|
}
|
||||||
|
$this->markFieldUpdated('creationTime');
|
||||||
|
}
|
||||||
|
}
|
||||||
365
facerecognition/lib/Db/FaceMapper.php
Normal file
365
facerecognition/lib/Db/FaceMapper.php
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018-2019, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Db;
|
||||||
|
|
||||||
|
use OC\DB\QueryBuilder\Literal;
|
||||||
|
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
|
||||||
|
class FaceMapper extends QBMapper {
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $db) {
|
||||||
|
parent::__construct($db, 'facerecog_faces', '\OCA\FaceRecognition\Db\Face');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find (int $faceId): ?Face {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'image', 'person', 'x', 'y', 'width', 'height', 'landmarks', 'descriptor', 'confidence')
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->andWhere($qb->expr()->eq('id', $qb->createNamedParameter($faceId)));
|
||||||
|
try {
|
||||||
|
return $this->findEntity($qb);
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on a given fileId, takes all faces that belong to that file
|
||||||
|
* and return an array with that.
|
||||||
|
*
|
||||||
|
* @param string $userId ID of the user that faces belong to
|
||||||
|
* @param int $modelId ID of the model that faces belgon to
|
||||||
|
* @param int $fileId ID of file for which to search faces.
|
||||||
|
*
|
||||||
|
* @return Face[]
|
||||||
|
*/
|
||||||
|
public function findFromFile(string $userId, int $modelId, int $fileId): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('f.id', 'x', 'y', 'width', 'height', 'person', 'confidence', 'creation_time')
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model_id')))
|
||||||
|
->andWhere($qb->expr()->eq('file', $qb->createParameter('file_id')))
|
||||||
|
->setParameter('user_id', $userId)
|
||||||
|
->setParameter('model_id', $modelId)
|
||||||
|
->setParameter('file_id', $fileId)
|
||||||
|
->orderBy('confidence', 'DESC');
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts all the faces that belong to images of a given user, created using given model
|
||||||
|
*
|
||||||
|
* @param string $userId User to which faces and associated images belongs to
|
||||||
|
* @param int $model Model ID
|
||||||
|
* @param bool $onlyWithoutPersons True if we need to count only faces which are not having person associated for it.
|
||||||
|
* If false, all faces are counted.
|
||||||
|
*/
|
||||||
|
public function countFaces(string $userId, int $model, bool $onlyWithoutPersons=false): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb = $qb
|
||||||
|
->select($qb->createFunction('COUNT(' . $qb->getColumnName('f.id') . ')'))
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('user', $qb->createParameter('user')))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model')));
|
||||||
|
if ($onlyWithoutPersons) {
|
||||||
|
$qb = $qb->andWhere($qb->expr()->isNull('person'));
|
||||||
|
}
|
||||||
|
$query = $qb
|
||||||
|
->setParameter('user', $userId)
|
||||||
|
->setParameter('model', $model);
|
||||||
|
$resultStatement = $query->execute();
|
||||||
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM);
|
||||||
|
$resultStatement->closeCursor();
|
||||||
|
|
||||||
|
return (int)$data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets oldest created face from database, for a given user and model, that is not associated with a person.
|
||||||
|
*
|
||||||
|
* @param string $userId User to which faces and associated images belongs to
|
||||||
|
* @param int $model Model ID
|
||||||
|
*
|
||||||
|
* @return Face Oldest face, if any is found
|
||||||
|
* @throws DoesNotExistException If there is no faces in database without person for a given user and model.
|
||||||
|
*/
|
||||||
|
public function getOldestCreatedFaceWithoutPerson(string $userId, int $model) {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb
|
||||||
|
->select('f.id', 'f.creation_time')
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)))
|
||||||
|
->andWhere($qb->expr()->isNull('person'))
|
||||||
|
->orderBy('f.creation_time', 'ASC');
|
||||||
|
$cursor = $qb->execute();
|
||||||
|
$row = $cursor->fetch();
|
||||||
|
if($row === false) {
|
||||||
|
$cursor->closeCursor();
|
||||||
|
throw new DoesNotExistException("No faces found and we should have at least one");
|
||||||
|
}
|
||||||
|
$face = $this->mapRowToEntity($row);
|
||||||
|
$cursor->closeCursor();
|
||||||
|
return $face;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFaces(string $userId, int $model): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('f.id', 'f.person', 'f.x', 'f.y', 'f.width', 'f.height', 'f.confidence', 'f.descriptor', 'f.is_groupable')
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('user', $qb->createParameter('user')))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
|
||||||
|
->setParameter('user', $userId)
|
||||||
|
->setParameter('model', $model);
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupableFaces(string $userId, int $model, int $minSize, float $minConfidence): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('f.id', 'f.person', 'f.descriptor')
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('user', $qb->createParameter('user')))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
|
||||||
|
->andWhere($qb->expr()->gte('width', $qb->createParameter('min_size')))
|
||||||
|
->andWhere($qb->expr()->gte('height', $qb->createParameter('min_size')))
|
||||||
|
->andWhere($qb->expr()->gte('confidence', $qb->createParameter('min_confidence')))
|
||||||
|
->andWhere($qb->expr()->eq('is_groupable', $qb->createParameter('is_groupable')))
|
||||||
|
->setParameter('user', $userId)
|
||||||
|
->setParameter('model', $model)
|
||||||
|
->setParameter('min_size', $minSize)
|
||||||
|
->setParameter('min_confidence', $minConfidence)
|
||||||
|
->setParameter('is_groupable', true, IQueryBuilder::PARAM_BOOL);
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNonGroupableFaces(string $userId, int $model, int $minSize, float $minConfidence): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('f.id', 'f.person')
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('user', $qb->createParameter('user')))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
|
||||||
|
->andWhere($qb->expr()->orX(
|
||||||
|
$qb->expr()->lt('width', $qb->createParameter('min_size')),
|
||||||
|
$qb->expr()->lt('height', $qb->createParameter('min_size')),
|
||||||
|
$qb->expr()->lt('confidence', $qb->createParameter('min_confidence')),
|
||||||
|
$qb->expr()->eq('is_groupable', $qb->createParameter('is_groupable'))
|
||||||
|
))
|
||||||
|
->setParameter('user', $userId)
|
||||||
|
->setParameter('model', $model)
|
||||||
|
->setParameter('min_size', $minSize)
|
||||||
|
->setParameter('min_confidence', $minConfidence)
|
||||||
|
->setParameter('is_groupable', false, IQueryBuilder::PARAM_BOOL);
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int|null $limit
|
||||||
|
*/
|
||||||
|
public function findFromCluster(string $userId, int $clusterId, int $model, ?int $limit = null, $offset = null): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('f.id', 'f.image', 'f.person')
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('person', $qb->createNamedParameter($clusterId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)));
|
||||||
|
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
|
||||||
|
$faces = $this->findEntities($qb);
|
||||||
|
return $faces;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int|null $limit
|
||||||
|
*/
|
||||||
|
public function findFromPerson(string $userId, string $personId, int $model, ?int $limit = null, $offset = null): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('f.id')
|
||||||
|
->from($this->getTableName(), 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->innerJoin('f', 'facerecog_persons' ,'p', $qb->expr()->eq('f.person', 'p.id'))
|
||||||
|
->where($qb->expr()->eq('p.user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('name', $qb->createNamedParameter($personId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)))
|
||||||
|
->orderBy('i.file', 'DESC');
|
||||||
|
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
|
||||||
|
$faces = $this->findEntities($qb);
|
||||||
|
|
||||||
|
return $faces;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all faces contained in one image
|
||||||
|
* Note that this is independent of any Model
|
||||||
|
*
|
||||||
|
* @param int $imageId Image for which to find all faces for
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function findByImage(int $imageId): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'image', 'person')
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('image', $qb->createNamedParameter($imageId)));
|
||||||
|
$faces = $this->findEntities($qb);
|
||||||
|
return $faces;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all faces contained in one image.
|
||||||
|
* Note that this is independent of any Model
|
||||||
|
*
|
||||||
|
* @param int $imageId Image for which to delete faces for
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeFromImage(int $imageId): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('image', $qb->createNamedParameter($imageId)))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all faces from that user.
|
||||||
|
*
|
||||||
|
* @param string $userId User to drop faces from table.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteUserFaces(string $userId): void {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'));
|
||||||
|
$sub->from('facerecog_images', 'i')
|
||||||
|
->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
|
||||||
|
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->setParameter('user', $userId)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all faces from that user and model
|
||||||
|
*
|
||||||
|
* @param string $userId User to drop faces from table.
|
||||||
|
* @param int $modelId model to drop faces from table.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteUserModel(string $userId, $modelId): void {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'));
|
||||||
|
$sub->from('facerecog_images', 'i')
|
||||||
|
->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
|
||||||
|
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')))
|
||||||
|
->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->setParameter('user', $userId)
|
||||||
|
->setParameter('model', $modelId)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unset relation beetwen faces and persons from that user in order to reset clustering
|
||||||
|
*
|
||||||
|
* @param string $userId User to drop fo unset relation.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function unsetPersonsRelationForUser(string $userId, int $model): void {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'));
|
||||||
|
$sub->from('facerecog_images', 'i')
|
||||||
|
->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
|
||||||
|
->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model')))
|
||||||
|
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update($this->getTableName())
|
||||||
|
->set("person", $qb->createNamedParameter(null))
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->setParameter('model', $model)
|
||||||
|
->setParameter('user', $userId)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert one face to database.
|
||||||
|
* Note: only reason we are not using (idiomatic) QBMapper method is
|
||||||
|
* because "QueryBuilder::PARAM_DATE" cannot be set there
|
||||||
|
*
|
||||||
|
* @param Face $face Face to insert
|
||||||
|
* @param IDBConnection $db Existing connection, if we need to reuse it. Null if we commit immediatelly.
|
||||||
|
*
|
||||||
|
* @return Face
|
||||||
|
*/
|
||||||
|
public function insertFace(Face $face, IDBConnection $db = null): Face {
|
||||||
|
if ($db !== null) {
|
||||||
|
$qb = $db->getQueryBuilder();
|
||||||
|
} else {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb->insert($this->getTableName())
|
||||||
|
->values([
|
||||||
|
'image' => $qb->createNamedParameter($face->image),
|
||||||
|
'person' => $qb->createNamedParameter($face->person),
|
||||||
|
'x' => $qb->createNamedParameter($face->x),
|
||||||
|
'y' => $qb->createNamedParameter($face->y),
|
||||||
|
'width' => $qb->createNamedParameter($face->width),
|
||||||
|
'height' => $qb->createNamedParameter($face->height),
|
||||||
|
'confidence' => $qb->createNamedParameter($face->confidence),
|
||||||
|
'landmarks' => $qb->createNamedParameter(json_encode($face->landmarks)),
|
||||||
|
'descriptor' => $qb->createNamedParameter(json_encode($face->descriptor)),
|
||||||
|
'creation_time' => $qb->createNamedParameter($face->creationTime, IQueryBuilder::PARAM_DATE),
|
||||||
|
])
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$face->setId($qb->getLastInsertId());
|
||||||
|
|
||||||
|
return $face;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
facerecognition/lib/Db/FaceModel.php
Normal file
47
facerecognition/lib/Db/FaceModel.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Db;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent one possible DNN model that faces can be detected, extracted and face descriptors taken.
|
||||||
|
* Logically encapsulates files from one DNN model used to do stuff from above.
|
||||||
|
*/
|
||||||
|
class FaceModel extends Entity implements JsonSerializable {
|
||||||
|
|
||||||
|
protected $uid;
|
||||||
|
protected $name;
|
||||||
|
protected $description;
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'description' => $this->description
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
137
facerecognition/lib/Db/Image.php
Normal file
137
facerecognition/lib/Db/Image.php
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2018, 2020-2021 Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Db;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image represent one image file for one user.
|
||||||
|
*
|
||||||
|
* @method string getUser()
|
||||||
|
* @method void setUser(string $user)
|
||||||
|
*
|
||||||
|
* @method integer getFile()
|
||||||
|
* @method void setFile(integer $file)
|
||||||
|
*
|
||||||
|
* @method integer getModel()
|
||||||
|
* @method void setModel(integer $model)
|
||||||
|
*
|
||||||
|
* @method string|null getError()
|
||||||
|
* @method void setError(string $error)
|
||||||
|
*
|
||||||
|
* @method bool getIsProcessed()
|
||||||
|
* @method void setIsProcessed($isProcessed)
|
||||||
|
*
|
||||||
|
* @method void setLastProcessedTime($lastProcessedTime)
|
||||||
|
*
|
||||||
|
* @method void setProcessingDuration(int $processingDuration)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Image extends Entity implements JsonSerializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User this image belongs to.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* */
|
||||||
|
public $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File that this image refer to.
|
||||||
|
* todo: add proper getters in whole class
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
public $file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Face model that processed this image.
|
||||||
|
*
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
protected $model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this image is processed or not. Needed because image doesn't have to have any faces on it,
|
||||||
|
* yet we still need to know if it is being processed or not.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $isProcessed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of error that happened during image processing.
|
||||||
|
* If it exist, image processing should be skipped even if $is_processed is false.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp when this image was last processed.
|
||||||
|
*
|
||||||
|
* @var \DateTime|null
|
||||||
|
*/
|
||||||
|
protected $lastProcessedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration (in ms) it took to completely process this image. Should serve as a way to give estimates to user.
|
||||||
|
*
|
||||||
|
* @var integer|null
|
||||||
|
*/
|
||||||
|
protected $processingDuration;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->addType('id', 'integer');
|
||||||
|
$this->addType('user', 'string');
|
||||||
|
$this->addType('file', 'integer');
|
||||||
|
$this->addType('model', 'integer');
|
||||||
|
$this->addType('isProcessed', 'bool');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'user' => $this->user,
|
||||||
|
'file' => $this->file,
|
||||||
|
'model' => $this->model,
|
||||||
|
'is_processed' => $this->isProcessed,
|
||||||
|
'error' => $this->error,
|
||||||
|
'last_processed_time' => $this->lastProcessedTime,
|
||||||
|
'processing_duration' => $this->processingDuration
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsProcessed($isProcessed): void {
|
||||||
|
if (is_bool($isProcessed)) {
|
||||||
|
$this->isProcessed = $isProcessed;
|
||||||
|
} else {
|
||||||
|
$this->isProcessed = filter_var($isProcessed, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
}
|
||||||
|
$this->markFieldUpdated('isProcessed');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
382
facerecognition/lib/Db/ImageMapper.php
Normal file
382
facerecognition/lib/Db/ImageMapper.php
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017-2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018-2019, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Db;
|
||||||
|
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
|
||||||
|
class ImageMapper extends QBMapper {
|
||||||
|
/** @var FaceMapper Face mapper*/
|
||||||
|
private $faceMapper;
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $db, FaceMapper $faceMapper) {
|
||||||
|
parent::__construct($db, 'facerecog_images', '\OCA\FaceRecognition\Db\Image');
|
||||||
|
$this->faceMapper = $faceMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId Id of user
|
||||||
|
* @param int $imageId Id of Image to get
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function find(string $userId, int $imageId): ?Image {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'file', 'is_processed', 'error', 'last_processed_time', 'processing_duration')
|
||||||
|
->from($this->getTableName(), 'i')
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('id', $qb->createNamedParameter($imageId)));
|
||||||
|
try {
|
||||||
|
return $this->findEntity($qb);
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId Id of user
|
||||||
|
* @param int $modelId Id of model to get
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function findAll(string $userId, int $modelId): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'file', 'is_processed', 'error', 'last_processed_time', 'processing_duration')
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)));
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId Id of user
|
||||||
|
* @param int $modelId Id of model
|
||||||
|
* @param int $fileId Id of file to get Image
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function findFromFile(string $userId, int $modelId, int $fileId): ?Image {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'is_processed', 'error')
|
||||||
|
->from($this->getTableName(), 'i')
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->andwhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)))
|
||||||
|
->andWhere($qb->expr()->eq('file', $qb->createNamedParameter($fileId)));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $this->findEntity($qb);
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function imageExists(Image $image): ?int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$query = $qb
|
||||||
|
->select(['id'])
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('user', $qb->createParameter('user')))
|
||||||
|
->andWhere($qb->expr()->eq('file', $qb->createParameter('file')))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
|
||||||
|
->setParameter('user', $image->getUser())
|
||||||
|
->setParameter('file', $image->getFile())
|
||||||
|
->setParameter('model', $image->getModel());
|
||||||
|
$resultStatement = $query->execute();
|
||||||
|
$row = $resultStatement->fetch();
|
||||||
|
$resultStatement->closeCursor();
|
||||||
|
return $row ? (int)$row['id'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countImages(int $model): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$query = $qb
|
||||||
|
->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('model', $qb->createParameter('model')))
|
||||||
|
->setParameter('model', $model);
|
||||||
|
$resultStatement = $query->execute();
|
||||||
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM);
|
||||||
|
$resultStatement->closeCursor();
|
||||||
|
|
||||||
|
return (int)$data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countProcessedImages(int $model): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$query = $qb
|
||||||
|
->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('model', $qb->createParameter('model')))
|
||||||
|
->andWhere($qb->expr()->eq('is_processed', $qb->createParameter('is_processed')))
|
||||||
|
->setParameter('model', $model)
|
||||||
|
->setParameter('is_processed', True);
|
||||||
|
$resultStatement = $query->execute();
|
||||||
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM);
|
||||||
|
$resultStatement->closeCursor();
|
||||||
|
|
||||||
|
return (int)$data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function avgProcessingDuration(int $model): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$query = $qb
|
||||||
|
->select($qb->createFunction('AVG(' . $qb->getColumnName('processing_duration') . ')'))
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('model', $qb->createParameter('model')))
|
||||||
|
->andWhere($qb->expr()->eq('is_processed', $qb->createParameter('is_processed')))
|
||||||
|
->setParameter('model', $model)
|
||||||
|
->setParameter('is_processed', True);
|
||||||
|
$resultStatement = $query->execute();
|
||||||
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM);
|
||||||
|
$resultStatement->closeCursor();
|
||||||
|
|
||||||
|
return (int)$data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countUserImages(string $userId, int $model, bool $processed = false): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$query = $qb
|
||||||
|
->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('user', $qb->createParameter('user')))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
|
||||||
|
->setParameter('user', $userId)
|
||||||
|
->setParameter('model', $model);
|
||||||
|
|
||||||
|
if ($processed) {
|
||||||
|
$query->andWhere($qb->expr()->eq('is_processed', $qb->createParameter('is_processed')))
|
||||||
|
->setParameter('is_processed', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultStatement = $query->execute();
|
||||||
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM);
|
||||||
|
$resultStatement->closeCursor();
|
||||||
|
|
||||||
|
return (int)$data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IUser|null $user User for which to get images for. If not given, all images from instance are returned.
|
||||||
|
* @param int $modelId Model Id to get images for.
|
||||||
|
*/
|
||||||
|
public function findImagesWithoutFaces(IUser $user = null, int $modelId): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb
|
||||||
|
->select(['id', 'user', 'file', 'model'])
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('is_processed', $qb->createParameter('is_processed')))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)))
|
||||||
|
->setParameter('is_processed', false, IQueryBuilder::PARAM_BOOL);
|
||||||
|
if (!is_null($user)) {
|
||||||
|
$qb->andWhere($qb->expr()->eq('user', $qb->createNamedParameter($user->getUID())));
|
||||||
|
}
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findImages(string $userId, int $model): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('i.id', 'i.file')
|
||||||
|
->from($this->getTableName(), 'i')
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)));
|
||||||
|
|
||||||
|
$images = $this->findEntities($qb);
|
||||||
|
return $images;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findFromPersonLike(string $userId, int $model, string $name, $offset = null, $limit = null): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('i.id', 'i.file')
|
||||||
|
->from($this->getTableName(), 'i')
|
||||||
|
->innerJoin('i', 'facerecog_faces', 'f', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->innerJoin('i', 'facerecog_persons', 'p', $qb->expr()->eq('f.person', 'p.id'))
|
||||||
|
->where($qb->expr()->eq('p.user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)))
|
||||||
|
->andWhere($qb->expr()->eq('is_processed', $qb->createNamedParameter(True)))
|
||||||
|
->andWhere($qb->expr()->like($qb->func()->lower('p.name'), $qb->createParameter('query')));
|
||||||
|
|
||||||
|
$query = '%' . $this->db->escapeLikeParameter(strtolower($name)) . '%';
|
||||||
|
$qb->setParameter('query', $query);
|
||||||
|
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findFromPerson(string $userId, int $modelId, string $name, $offset = null, $limit = null): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('i.file')
|
||||||
|
->from($this->getTableName(), 'i')
|
||||||
|
->innerJoin('i', 'facerecog_faces', 'f', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->innerJoin('f', 'facerecog_persons', 'p', $qb->expr()->eq('f.person', 'p.id'))
|
||||||
|
->where($qb->expr()->eq('p.user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)))
|
||||||
|
->andWhere($qb->expr()->eq('is_processed', $qb->createNamedParameter(True)))
|
||||||
|
->andWhere($qb->expr()->eq('p.name', $qb->createNamedParameter($name)))
|
||||||
|
->orderBy('i.file', 'DESC');
|
||||||
|
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countFromPerson(string $userId, int $modelId, string $name): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select($qb->func()->count('*'))
|
||||||
|
->from($this->getTableName(), 'i')
|
||||||
|
->innerJoin('i', 'facerecog_faces', 'f', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->innerJoin('f', 'facerecog_persons', 'p', $qb->expr()->eq('f.person', 'p.id'))
|
||||||
|
->where($qb->expr()->eq('p.user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)))
|
||||||
|
->andWhere($qb->expr()->eq('is_processed', $qb->createNamedParameter(True)))
|
||||||
|
->andWhere($qb->expr()->eq('p.name', $qb->createNamedParameter($name)));
|
||||||
|
|
||||||
|
$result = $qb->executeQuery();
|
||||||
|
$column = (int)$result->fetchOne();
|
||||||
|
$result->closeCursor();
|
||||||
|
|
||||||
|
return $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes to DB that image has been processed. Previously found faces are deleted and new ones are inserted.
|
||||||
|
* If there is exception, its stack trace is also updated.
|
||||||
|
*
|
||||||
|
* @param Image $image Image to be updated
|
||||||
|
* @param Face[] $faces Faces to insert
|
||||||
|
* @param int $duration Processing time, in milliseconds
|
||||||
|
* @param \Exception|null $e Any exception that happened during image processing
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function imageProcessed(Image $image, array $faces, int $duration, \Exception $e = null): void {
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
try {
|
||||||
|
// Update image itself
|
||||||
|
//
|
||||||
|
$error = null;
|
||||||
|
if ($e !== null) {
|
||||||
|
$error = substr($e->getMessage(), 0, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update($this->getTableName())
|
||||||
|
->set("is_processed", $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))
|
||||||
|
->set("error", $qb->createNamedParameter($error))
|
||||||
|
->set("last_processed_time", $qb->createNamedParameter(new \DateTime(), IQueryBuilder::PARAM_DATE))
|
||||||
|
->set("processing_duration", $qb->createNamedParameter($duration))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($image->id)))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
// Delete all previous faces
|
||||||
|
//
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete('facerecog_faces')
|
||||||
|
->where($qb->expr()->eq('image', $qb->createNamedParameter($image->id)))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
// Insert all faces
|
||||||
|
//
|
||||||
|
foreach ($faces as $face) {
|
||||||
|
$this->faceMapper->insertFace($face, $this->db);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets image by deleting all associated faces and prepares it to be processed again
|
||||||
|
*
|
||||||
|
* @param Image $image Image to reset
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function resetImage(Image $image): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update($this->getTableName())
|
||||||
|
->set("is_processed", $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))
|
||||||
|
->set("error", $qb->createNamedParameter(null))
|
||||||
|
->set("last_processed_time", $qb->createNamedParameter(null))
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($image->getUser())))
|
||||||
|
->andWhere($qb->expr()->eq('file', $qb->createNamedParameter($image->getFile())))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($image->getModel())))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all image with error from that user and prepares it to be processed again
|
||||||
|
*
|
||||||
|
* @param string $userId User to reset errors
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function resetErrors(string $userId): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update($this->getTableName())
|
||||||
|
->set("is_processed", $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))
|
||||||
|
->set("error", $qb->createNamedParameter(null))
|
||||||
|
->set("last_processed_time", $qb->createNamedParameter(null))
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->isNotNull('error'))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all images from that user.
|
||||||
|
*
|
||||||
|
* @param string $userId User to drop images from table.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteUserImages(string $userId): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all images from that user and Model
|
||||||
|
*
|
||||||
|
* @param string $userId User to drop images from table.
|
||||||
|
* @param int $modelId model to drop images from table.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteUserModel(string $userId, $modelId): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
118
facerecognition/lib/Db/Person.php
Normal file
118
facerecognition/lib/Db/Person.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020-2021, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Db;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Person represent one cluster, set of faces. It belongs to $user_id.
|
||||||
|
*
|
||||||
|
* @method string getUser()
|
||||||
|
* @method string getName()
|
||||||
|
* @method void setName(string $name)
|
||||||
|
* @method bool getIsVisible()
|
||||||
|
*/
|
||||||
|
class Person extends Entity implements JsonSerializable {
|
||||||
|
/**
|
||||||
|
* User this person belongs to
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* */
|
||||||
|
protected $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name for this person/cluster. Must exists, even if linked user is set.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this person is visible/relevant to user.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $isVisible;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this person is still valid
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $isValid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last timestamp when this person/cluster was created, or when it was refreshed
|
||||||
|
*
|
||||||
|
* @var \DateTime|null
|
||||||
|
*/
|
||||||
|
protected $lastGenerationTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Foreign key to other user that this person belongs to (if it is on same Nextcloud instance).
|
||||||
|
* It is set by owner of this cluster. It is optional.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $linkedUser;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->addType('id', 'integer');
|
||||||
|
$this->addType('user', 'string');
|
||||||
|
$this->addType('isVisible', 'bool');
|
||||||
|
$this->addType('isValid', 'bool');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'user' => $this->user,
|
||||||
|
'name' => $this->name,
|
||||||
|
'is_visible' => $this->isVisible,
|
||||||
|
'is_valid' => $this->isValid,
|
||||||
|
'last_generation_time' => $this->lastGenerationTime,
|
||||||
|
'linked_user' => $this->linkedUser
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsVisible($isVisible): void {
|
||||||
|
if (is_bool($isVisible)) {
|
||||||
|
$this->isVisible = $isVisible;
|
||||||
|
} else {
|
||||||
|
$this->isVisible = filter_var($isVisible, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
}
|
||||||
|
$this->markFieldUpdated('isVisible');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsValid($isValid): void {
|
||||||
|
if (is_bool($isValid)) {
|
||||||
|
$this->isValid = $isValid;
|
||||||
|
} else {
|
||||||
|
$this->isValid = filter_var($isValid, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
}
|
||||||
|
$this->markFieldUpdated('isValid');
|
||||||
|
}
|
||||||
|
}
|
||||||
645
facerecognition/lib/Db/PersonMapper.php
Normal file
645
facerecognition/lib/Db/PersonMapper.php
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2018-2021, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018-2019, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Db;
|
||||||
|
|
||||||
|
use OC\DB\QueryBuilder\Literal;
|
||||||
|
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
|
||||||
|
class PersonMapper extends QBMapper {
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $db) {
|
||||||
|
parent::__construct($db, 'facerecog_persons', '\OCA\FaceRecognition\Db\Person');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
* @param int $personId ID of the person
|
||||||
|
*
|
||||||
|
* @return Person
|
||||||
|
*/
|
||||||
|
public function find(string $userId, int $personId): Person {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'name', 'is_visible')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
|
||||||
|
->andWhere($qb->expr()->eq('user', $qb->createNamedParameter($userId)));
|
||||||
|
return $this->findEntity($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
* @param int $modelId ID of the model
|
||||||
|
* @param string $personName name of the person to find
|
||||||
|
* @return Person[]
|
||||||
|
*/
|
||||||
|
public function findByName(string $userId, int $modelId, string $personName): array {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'))
|
||||||
|
->from('facerecog_faces', 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($sub->expr()->eq('p.id', 'f.person'))
|
||||||
|
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
|
||||||
|
->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')))
|
||||||
|
->andWhere($sub->expr()->eq('p.name', $sub->createParameter('person_name')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'name', 'is_valid')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->setParameter('user_id', $userId)
|
||||||
|
->setParameter('model_id', $modelId)
|
||||||
|
->setParameter('person_name', $personName);
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
* @param int $modelId ID of the model
|
||||||
|
* @return Person[]
|
||||||
|
*/
|
||||||
|
public function findUnassigned(string $userId, int $modelId): array {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'))
|
||||||
|
->from('facerecog_faces', 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($sub->expr()->eq('p.id', 'f.person'))
|
||||||
|
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
|
||||||
|
->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'is_valid')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
|
||||||
|
->andWhere($qb->expr()->eq('is_visible', $qb->createParameter('is_visible')))
|
||||||
|
->andWhere($qb->expr()->isNull('name'))
|
||||||
|
->setParameter('user_id', $userId)
|
||||||
|
->setParameter('model_id', $modelId)
|
||||||
|
->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
|
||||||
|
->setParameter('is_visible', true, IQueryBuilder::PARAM_BOOL);
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
* @param int $modelId ID of the model
|
||||||
|
* @return Person[]
|
||||||
|
*/
|
||||||
|
public function findIgnored(string $userId, int $modelId): array {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'))
|
||||||
|
->from('facerecog_faces', 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($sub->expr()->eq('p.id', 'f.person'))
|
||||||
|
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
|
||||||
|
->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'is_valid')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
|
||||||
|
->andWhere($qb->expr()->eq('is_visible', $qb->createParameter('is_visible')))
|
||||||
|
->andWhere($qb->expr()->isNull('name'))
|
||||||
|
->setParameter('user_id', $userId)
|
||||||
|
->setParameter('model_id', $modelId)
|
||||||
|
->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
|
||||||
|
->setParameter('is_visible', false, IQueryBuilder::PARAM_BOOL);
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
* @param int $modelId ID of the model
|
||||||
|
* @return Person[]
|
||||||
|
*/
|
||||||
|
public function findAll(string $userId, int $modelId): array {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'))
|
||||||
|
->from('facerecog_faces', 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($sub->expr()->eq('p.id', 'f.person'))
|
||||||
|
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
|
||||||
|
->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'name', 'is_valid')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->setParameter('user_id', $userId)
|
||||||
|
->setParameter('model_id', $modelId);
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
*
|
||||||
|
* @return Person[]
|
||||||
|
*/
|
||||||
|
public function findDistinctNames(string $userId, int $modelId): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->selectDistinct('name')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->innerJoin('p', 'facerecog_faces' , 'f', $qb->expr()->eq('f.person', 'p.id'))
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
|
||||||
|
->andWhere($qb->expr()->eq('i.model', $qb->createParameter('model_id')))
|
||||||
|
->andwhere($qb->expr()->isNotNull('p.name'))
|
||||||
|
->setParameter('user_id', $userId)
|
||||||
|
->setParameter('model_id', $modelId);
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
*
|
||||||
|
* @return Person[]
|
||||||
|
*/
|
||||||
|
public function findDistinctNamesSelected(string $userId, int $modelId, $faceNames): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->selectDistinct('name')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->innerJoin('p', 'facerecog_faces' , 'f', $qb->expr()->eq('f.person', 'p.id'))
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
|
||||||
|
->andWhere($qb->expr()->eq('i.model', $qb->createParameter('model_id')))
|
||||||
|
->andwhere($qb->expr()->isNotNull('p.name'))
|
||||||
|
->andWhere($qb->expr()->eq('p.name', $qb->createParameter('faceNames')))
|
||||||
|
->setParameter('user_id', $userId)
|
||||||
|
->setParameter('model_id', $modelId)
|
||||||
|
->setParameter('faceNames', $faceNames);
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search Person by name
|
||||||
|
*
|
||||||
|
* @param int|null $offset
|
||||||
|
* @param int|null $limit
|
||||||
|
*/
|
||||||
|
public function findPersonsLike(string $userId, int $modelId, string $name, ?int $offset = null, ?int $limit = null): array {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->selectDistinct('p.name')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->innerJoin('p', 'facerecog_faces', 'f', $qb->expr()->eq('f.person', 'p.id'))
|
||||||
|
->innerJoin('p', 'facerecog_images', 'i', $qb->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($qb->expr()->eq('p.user', $qb->createNamedParameter($userId)))
|
||||||
|
->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($modelId)))
|
||||||
|
->andWhere($qb->expr()->eq('is_processed', $qb->createNamedParameter(True)))
|
||||||
|
->andWhere($qb->expr()->like($qb->func()->lower('p.name'), $qb->createParameter('query')));
|
||||||
|
|
||||||
|
$query = '%' . $this->db->escapeLikeParameter(strtolower($name)) . '%';
|
||||||
|
$qb->setParameter('query', $query);
|
||||||
|
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns count of persons found for a given user.
|
||||||
|
*
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
* @param int $modelId ID of the model
|
||||||
|
* @return int Count of persons
|
||||||
|
*/
|
||||||
|
public function countPersons(string $userId, int $modelId): int {
|
||||||
|
return count($this->findDistinctNames($userId, $modelId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns count of clusters found for a given user.
|
||||||
|
*
|
||||||
|
* @param string $userId ID of the user
|
||||||
|
* @param int $modelId ID of the model
|
||||||
|
* @param bool $onlyInvalid True if client wants count of invalid clusters only,
|
||||||
|
* false if client want count of all clusters
|
||||||
|
* @return int Count of clusters
|
||||||
|
*/
|
||||||
|
public function countClusters(string $userId, int $modelId, bool $onlyInvalid=false): int {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'))
|
||||||
|
->from('facerecog_faces', 'f')
|
||||||
|
->innerJoin('f', 'facerecog_images' ,'i', $sub->expr()->eq('f.image', 'i.id'))
|
||||||
|
->where($sub->expr()->eq('p.id', 'f.person'))
|
||||||
|
->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user_id')))
|
||||||
|
->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model_id')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')');
|
||||||
|
|
||||||
|
if ($onlyInvalid) {
|
||||||
|
$qb = $qb
|
||||||
|
->andWhere($qb->expr()->eq('is_valid', $qb->createParameter('is_valid')))
|
||||||
|
->setParameter('is_valid', false, IQueryBuilder::PARAM_BOOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb = $qb
|
||||||
|
->setParameter('user_id', $userId)
|
||||||
|
->setParameter('model_id', $modelId);
|
||||||
|
|
||||||
|
$resultStatement = $qb->execute();
|
||||||
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM);
|
||||||
|
$resultStatement->closeCursor();
|
||||||
|
|
||||||
|
return (int)$data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on a given image, takes all faces that belong to that image
|
||||||
|
* and invalidates all person that those faces belongs to.
|
||||||
|
*
|
||||||
|
* @param int $imageId ID of image for which to invalidate persons for
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function invalidatePersons(int $imageId): void {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$tableNameWithPrefixWithoutQuotes = trim($sub->getTableName($this->getTableName()), '`');
|
||||||
|
$sub->select(new Literal('1'));
|
||||||
|
$sub->from('facerecog_images', 'i')
|
||||||
|
->innerJoin('i', 'facerecog_faces' ,'f', $sub->expr()->eq('i.id', 'f.image'))
|
||||||
|
->where($sub->expr()->eq($tableNameWithPrefixWithoutQuotes . '.id', 'f.person'))
|
||||||
|
->andWhere($sub->expr()->eq('i.id', $sub->createParameter('image_id')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update($this->getTableName())
|
||||||
|
->set("is_valid", $qb->createParameter('is_valid'))
|
||||||
|
->where('EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->setParameter('image_id', $imageId)
|
||||||
|
->setParameter('is_valid', false, IQueryBuilder::PARAM_BOOL)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on current clusters and new clusters, do database reconciliation.
|
||||||
|
* It tries to do that in minimal number of SQL queries. Operation is atomic.
|
||||||
|
*
|
||||||
|
* Clusters are array, where keys are ID of persons, and values are indexed arrays
|
||||||
|
* with values that are ID of the faces for those persons.
|
||||||
|
*
|
||||||
|
* @param string $userId ID of the user that clusters belong to
|
||||||
|
* @param array $currentClusters Current clusters
|
||||||
|
* @param array $newClusters New clusters
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function mergeClusterToDatabase(string $userId, $currentClusters, $newClusters): void {
|
||||||
|
$this->db->beginTransaction();
|
||||||
|
$currentDateTime = new \DateTime();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Delete clusters that do not exist anymore
|
||||||
|
foreach($currentClusters as $oldPerson => $oldFaces) {
|
||||||
|
if (array_key_exists($oldPerson, $newClusters)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK, we bumped into cluster that existed and now it does not exist.
|
||||||
|
// We need to remove all references to it and to delete it.
|
||||||
|
foreach ($oldFaces as $oldFace) {
|
||||||
|
$this->updateFace($oldFace, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: this is not very cool. What if user had associated linked user to this. And all lost?
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
// todo: for extra safety, we should probably add here additional condition, where (user=$userId)
|
||||||
|
$qb
|
||||||
|
->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($oldPerson)))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify existing clusters
|
||||||
|
foreach($newClusters as $newPerson=>$newFaces) {
|
||||||
|
if (!array_key_exists($newPerson, $currentClusters)) {
|
||||||
|
// This cluster didn't exist, there is nothing to modify
|
||||||
|
// It will be processed during cluster adding operation
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldFaces = $currentClusters[$newPerson];
|
||||||
|
if ($newFaces === $oldFaces) {
|
||||||
|
// Set cluster as valid now
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb
|
||||||
|
->update($this->getTableName())
|
||||||
|
->set("is_valid", $qb->createParameter('is_valid'))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($newPerson)))
|
||||||
|
->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
|
||||||
|
->execute();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK, set of faces do differ. Now, we could potentially go into finer grain details
|
||||||
|
// and add/remove each individual face, but this seems too detailed. Enough is to
|
||||||
|
// reset all existing faces to null and to add new faces to new person. That should
|
||||||
|
// take care of both faces that are removed from cluster, as well as for newly added
|
||||||
|
// faces to this cluster.
|
||||||
|
|
||||||
|
// First remove all old faces from any cluster (reset them to null)
|
||||||
|
foreach ($oldFaces as $oldFace) {
|
||||||
|
// Reset face to null only if it wasn't moved to other cluster!
|
||||||
|
// (if face is just moved to other cluster, do not reset to null, as some other
|
||||||
|
// pass for some other cluster will eventually update it to proper cluster)
|
||||||
|
if ($this->isFaceInClusters($oldFace, $newClusters) === false) {
|
||||||
|
$this->updateFace($oldFace, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then set all new faces to belong to this cluster
|
||||||
|
foreach ($newFaces as $newFace) {
|
||||||
|
$this->updateFace($newFace, $newPerson);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cluster as valid now
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb
|
||||||
|
->update($this->getTableName())
|
||||||
|
->set("is_valid", $qb->createParameter('is_valid'))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($newPerson)))
|
||||||
|
->setParameter('is_valid', true, IQueryBuilder::PARAM_BOOL)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new clusters
|
||||||
|
foreach($newClusters as $newPerson=>$newFaces) {
|
||||||
|
if (array_key_exists($newPerson, $currentClusters)) {
|
||||||
|
// This cluster already existed, nothing to add
|
||||||
|
// It was already processed during modify cluster operation
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new cluster and add all faces to it
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb
|
||||||
|
->insert($this->getTableName())
|
||||||
|
->values([
|
||||||
|
'user' => $qb->createNamedParameter($userId),
|
||||||
|
'is_valid' => $qb->createNamedParameter(true),
|
||||||
|
'last_generation_time' => $qb->createNamedParameter($currentDateTime, IQueryBuilder::PARAM_DATE),
|
||||||
|
'linked_user' => $qb->createNamedParameter(null)])
|
||||||
|
->execute();
|
||||||
|
$insertedPersonId = $qb->getLastInsertId();
|
||||||
|
foreach ($newFaces as $newFace) {
|
||||||
|
$this->updateFace($newFace, $insertedPersonId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->commit();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->db->rollBack();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all persons from that user.
|
||||||
|
*
|
||||||
|
* @param string $userId User to drop persons from a table.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteUserPersons(string $userId): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all persons from that user and model
|
||||||
|
*
|
||||||
|
* @param string $userId ID of user for drop from table
|
||||||
|
* @param int $modelId
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteUserModel(string $userId, int $modelId): void {
|
||||||
|
//TODO: Make it atomic
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('id', $qb->createParameter('person')));
|
||||||
|
|
||||||
|
$persons = $this->findAll($userId, $modelId);
|
||||||
|
foreach ($persons as $person) {
|
||||||
|
$qb->setParameter('person', $person->getId())->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes person if it is empty (have no faces associated to it)
|
||||||
|
*
|
||||||
|
* @param int $personId Person to check if it should be deleted
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeIfEmpty(int $personId): void {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'));
|
||||||
|
$sub->from('facerecog_faces', 'f')
|
||||||
|
->where($sub->expr()->eq('f.person', $sub->createParameter('person')));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('id', $qb->createParameter('person')))
|
||||||
|
->andWhere('NOT EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->setParameter('person', $personId)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all persons that have no faces associated to them
|
||||||
|
*
|
||||||
|
* @param string $userId ID of user for which we are deleting orphaned persons
|
||||||
|
*/
|
||||||
|
public function deleteOrphaned(string $userId): int {
|
||||||
|
$sub = $this->db->getQueryBuilder();
|
||||||
|
$sub->select(new Literal('1'));
|
||||||
|
$sub->from('facerecog_faces', 'f')
|
||||||
|
->where($sub->expr()->eq('f.person', 'p.id'));
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('p.id')
|
||||||
|
->from($this->getTableName(), 'p')
|
||||||
|
->where($qb->expr()->eq('p.user', $qb->createParameter('user')))
|
||||||
|
->andWhere('NOT EXISTS (' . $sub->getSQL() . ')')
|
||||||
|
->setParameter('user', $userId);
|
||||||
|
$orphanedPersons = $this->findEntities($qb);
|
||||||
|
|
||||||
|
$orphaned = 0;
|
||||||
|
foreach ($orphanedPersons as $person) {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$orphaned += $qb->delete($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($person->id)))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
return $orphaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mark the cluster as hidden or visible to user.
|
||||||
|
*
|
||||||
|
* @param int $personId ID of the person
|
||||||
|
* @param bool $visible visibility of the person
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setVisibility (int $personId, bool $visible): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
if ($visible) {
|
||||||
|
$qb->update($this->getTableName())
|
||||||
|
->set('is_visible', $qb->createNamedParameter(1))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
|
||||||
|
->execute();
|
||||||
|
} else {
|
||||||
|
$qb->update($this->getTableName())
|
||||||
|
->set('is_visible', $qb->createNamedParameter(0))
|
||||||
|
->set('name', $qb->createNamedParameter(null))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mark the cluster as hidden or visible to user.
|
||||||
|
*
|
||||||
|
* @param int $personId ID of the person
|
||||||
|
* @param int $faceId visibility of the person
|
||||||
|
* @param string|null $name optional name to rename them.
|
||||||
|
*
|
||||||
|
* @return Person
|
||||||
|
*/
|
||||||
|
public function detachFace(int $personId, int $faceId, $name = null): Person {
|
||||||
|
// Mark the face as non groupable.
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update('facerecog_faces')
|
||||||
|
->set('is_groupable', $qb->createParameter('is_groupable'))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
|
||||||
|
->setParameter('is_groupable', false, IQueryBuilder::PARAM_BOOL)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if ($this->countClusterFaces($personId) === 1) {
|
||||||
|
// If cluster is an single face just rename it.
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update($this->getTableName())
|
||||||
|
->set('name', $qb->createNamedParameter($name))
|
||||||
|
->set('is_visible', $qb->createNamedParameter(true))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)))
|
||||||
|
->execute();
|
||||||
|
} else {
|
||||||
|
// If there are other faces, must create a new person for that face.
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('user')
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)));
|
||||||
|
$oldPerson = $this->findEntity($qb);
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->insert($this->getTableName())->values([
|
||||||
|
'user' => $qb->createNamedParameter($oldPerson->getUser()),
|
||||||
|
'name' => $qb->createNamedParameter($name),
|
||||||
|
'is_valid' => $qb->createNamedParameter(true),
|
||||||
|
'last_generation_time' => $qb->createNamedParameter(new \DateTime(), IQueryBuilder::PARAM_DATE),
|
||||||
|
'linked_user' => $qb->createNamedParameter(null),
|
||||||
|
'is_visible' => $qb->createNamedParameter(true)
|
||||||
|
])->execute();
|
||||||
|
|
||||||
|
$personId = $qb->getLastInsertId();
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update('facerecog_faces')
|
||||||
|
->set('person', $qb->createParameter('person'))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
|
||||||
|
->setParameter('person', $personId)
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'name', 'is_valid', 'is_visible')
|
||||||
|
->from($this->getTableName())
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($personId)));
|
||||||
|
return $this->findEntity($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countClusterFaces(int $personId): int {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$query = $qb
|
||||||
|
->select($qb->createFunction('COUNT(' . $qb->getColumnName('id') . ')'))
|
||||||
|
->from('facerecog_faces')
|
||||||
|
->where($qb->expr()->eq('person', $qb->createParameter('person')))
|
||||||
|
->setParameter('person', $personId);
|
||||||
|
$resultStatement = $query->execute();
|
||||||
|
$data = $resultStatement->fetch(\PDO::FETCH_NUM);
|
||||||
|
$resultStatement->closeCursor();
|
||||||
|
|
||||||
|
return (int)$data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates one face with $faceId to database to person ID $personId.
|
||||||
|
*
|
||||||
|
* @param int $faceId ID of the face
|
||||||
|
* @param int|null $personId ID of the person
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function updateFace(int $faceId, $personId): void {
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->update('facerecog_faces')
|
||||||
|
->set("person", $qb->createNamedParameter($personId))
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)))
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if face with a given ID is in any cluster.
|
||||||
|
*
|
||||||
|
* @param int $faceId ID of the face to check
|
||||||
|
* @param array $cluster All clusters to check into
|
||||||
|
*
|
||||||
|
* @return bool True if face is found in any cluster, false otherwise.
|
||||||
|
*/
|
||||||
|
private function isFaceInClusters(int $faceId, array $clusters): bool {
|
||||||
|
foreach ($clusters as $_=>$faces) {
|
||||||
|
if (in_array($faceId, $faces)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
60
facerecognition/lib/Helper/CommandLock.php
Normal file
60
facerecognition/lib/Helper/CommandLock.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2017, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
* @copyright Copyright (c) 2018, Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @author Branko Kokanovic <branko@kokanovic.org>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
namespace OCA\FaceRecognition\Helper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tasks that do flock over file and acts as a global mutex,
|
||||||
|
* so we don't run more than one background task in parallel.
|
||||||
|
*/
|
||||||
|
class CommandLock {
|
||||||
|
|
||||||
|
private static function LockFile(): string {
|
||||||
|
return sys_get_temp_dir() . '/' . 'nextcloud_face_recognition_lock.pid';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function IsLockedBy(): string {
|
||||||
|
$fp = fopen(self::LockFile(), 'r');
|
||||||
|
$lockDescription = fread($fp, filesize(self::LockFile()));
|
||||||
|
//fclose($fp);
|
||||||
|
return $lockDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function lock(string $lockDescription) {
|
||||||
|
$fp = fopen(self::LockFile(), 'c');
|
||||||
|
if (!$fp || !flock($fp, LOCK_EX | LOCK_NB, $eWouldBlock) || $eWouldBlock) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
fwrite($fp, $lockDescription);
|
||||||
|
return $fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unlock($lockFile): void {
|
||||||
|
flock($lockFile, LOCK_UN);
|
||||||
|
unlink(self::LockFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
facerecognition/lib/Helper/Euclidean.php
Normal file
33
facerecognition/lib/Helper/Euclidean.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Helper;
|
||||||
|
|
||||||
|
class Euclidean
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Euclidean distance metric between two vectors
|
||||||
|
*
|
||||||
|
* The euclidean distance between two vectors (vector1, vector2) is defined as
|
||||||
|
* D = SQRT(SUM((vector1(i) - vector2(i))^2)) (i = 0..k)
|
||||||
|
*
|
||||||
|
* Refs:
|
||||||
|
* - http://mathworld.wolfram.com/EuclideanMetric.html
|
||||||
|
* - http://en.wikipedia.org/wiki/Euclidean_distance
|
||||||
|
*
|
||||||
|
* @param array $vector1 first vector
|
||||||
|
* @param array $vector2 second vector
|
||||||
|
*
|
||||||
|
* @return float The Euclidean distance between vector1 and vector2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static function distance(array $vector1, array $vector2): float
|
||||||
|
{
|
||||||
|
$n = count($vector1);
|
||||||
|
$sum = 0;
|
||||||
|
for ($i = 0; $i < $n; $i++) {
|
||||||
|
$sum += ($vector1[$i] - $vector2[$i]) * ($vector1[$i] - $vector2[$i]);
|
||||||
|
}
|
||||||
|
return sqrt($sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
facerecognition/lib/Helper/FaceRect.php
Normal file
64
facerecognition/lib/Helper/FaceRect.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020, Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Helper;
|
||||||
|
|
||||||
|
class FaceRect {
|
||||||
|
|
||||||
|
public static function overlapPercent(array $rectA, array $rectB): float {
|
||||||
|
// Firts face rect
|
||||||
|
$leftA = $rectA['left'];
|
||||||
|
$rightA = $rectA['right'];
|
||||||
|
$topA = $rectA['top'];
|
||||||
|
$bottomA = $rectA['bottom'];
|
||||||
|
|
||||||
|
// Face rect to compare
|
||||||
|
$leftB = $rectB['left'];
|
||||||
|
$rightB = $rectB['right'];
|
||||||
|
$topB = $rectB['top'];
|
||||||
|
$bottomB = $rectB['bottom'];
|
||||||
|
|
||||||
|
// If one rectangle is on left side of other
|
||||||
|
if ($leftA >= $rightB || $leftB >= $rightA)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
// If one rectangle is above other
|
||||||
|
if ($topA >= $bottomB || $topB >= $bottomA)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
// Overlap area.
|
||||||
|
$leftO = max($leftA, $leftB);
|
||||||
|
$rightO = min($rightA, $rightB);
|
||||||
|
$topO = max($topA, $topB);
|
||||||
|
$bottomO = min($bottomA, $bottomB);
|
||||||
|
|
||||||
|
// Calculate the areas of all the rectangles
|
||||||
|
$areaA = ($rightA - $leftA) * ($bottomA - $topA);
|
||||||
|
$areaB = ($rightB - $leftB) * ($bottomB - $topB);
|
||||||
|
$overlapArea = ($rightO - $leftO) * ($bottomO - $topO);
|
||||||
|
|
||||||
|
// Calculate and return the overlay percent.
|
||||||
|
return floatval($overlapArea / ($areaA + $areaB - $overlapArea));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
208
facerecognition/lib/Helper/Imaginary.php
Normal file
208
facerecognition/lib/Helper/Imaginary.php
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022-2023, Matias De lellis
|
||||||
|
*
|
||||||
|
* @author Matias De lellis <mati86dl@gmail.com>
|
||||||
|
*
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This code is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License, version 3,
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\FaceRecognition\Helper;
|
||||||
|
|
||||||
|
use OCP\Files\File;
|
||||||
|
use OCP\Http\Client\IClientService;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IImage;
|
||||||
|
|
||||||
|
use OC\StreamImage;
|
||||||
|
|
||||||
|
class Imaginary {
|
||||||
|
|
||||||
|
/** @var IConfig */
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
/** @var IClientService */
|
||||||
|
private $service;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->config = \OC::$server->get(IConfig::class);
|
||||||
|
$this->service = \OC::$server->get(IClientService::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEnabled(): bool {
|
||||||
|
$imaginaryUrl = $this->config->getSystemValueString('preview_imaginary_url', 'invalid');
|
||||||
|
return ($imaginaryUrl !== 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUrl(): ?string {
|
||||||
|
$imaginaryUrl = $this->config->getSystemValueString('preview_imaginary_url', 'invalid');
|
||||||
|
if ($imaginaryUrl === 'invalid')
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return rtrim($imaginaryUrl, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasKey(): bool {
|
||||||
|
$imaginaryKey = $this->config->getSystemValueString('preview_imaginary_key', 'invalid');
|
||||||
|
return ($imaginaryKey !== 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey(): ?string {
|
||||||
|
$imaginaryKey = $this->config->getSystemValueString('preview_imaginary_key', 'invalid');
|
||||||
|
if ($imaginaryKey === 'invalid')
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return $imaginaryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string imaginary version
|
||||||
|
*/
|
||||||
|
public function getVersion(): ?string {
|
||||||
|
$imaginaryUrl = $this->getUrl();
|
||||||
|
if (!$imaginaryUrl) {
|
||||||
|
throw new \RuntimeException('Try to use imaginary without valid url');
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpClient = $this->service->newClient();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$options = [];
|
||||||
|
if ($this->hasKey()) {
|
||||||
|
$options['query'] = [
|
||||||
|
'key' => $this->getKey(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$response = $httpClient->get($imaginaryUrl . '/', $options);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response->getStatusCode() !== 200) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = json_decode($response->getBody(), true);
|
||||||
|
|
||||||
|
return $info['imaginary'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array Returns the array with the size of image.
|
||||||
|
*/
|
||||||
|
public function getInfo(string $filepath): array {
|
||||||
|
$imaginaryUrl = $this->getUrl();
|
||||||
|
if (!$imaginaryUrl) {
|
||||||
|
throw new \RuntimeException('Try to use imaginary without valid url');
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpClient = $this->service->newClient();
|
||||||
|
|
||||||
|
$options = [];
|
||||||
|
$options['multipart'] = [[
|
||||||
|
'name' => 'file',
|
||||||
|
'contents' => file_get_contents($filepath),
|
||||||
|
'filename' => basename($filepath),
|
||||||
|
]];
|
||||||
|
|
||||||
|
if ($this->hasKey()) {
|
||||||
|
$options['query'] = [
|
||||||
|
'key' => $this->getKey(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $httpClient->post($imaginaryUrl . '/info', $options);
|
||||||
|
|
||||||
|
if ($response->getStatusCode() !== 200) {
|
||||||
|
throw new \RuntimeException('Error getting image information in Imaginary: ' . json_decode($response->getBody())['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = json_decode($response->getBody(), true);
|
||||||
|
|
||||||
|
$type = $info['type'];
|
||||||
|
//NOTE: Imaginary has problems rorating heic images. Issue #662
|
||||||
|
$autorotate = ($info['orientation'] > 4 && $type != 'heif');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => $type,
|
||||||
|
'autorotate' => $autorotate,
|
||||||
|
// Rotates the size, since it is important and Imaginary do not do that.
|
||||||
|
'width' => $autorotate ? $info['height'] : $info['width'],
|
||||||
|
'height' => $autorotate ? $info['width'] : $info['height']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|resource Returns the resized image
|
||||||
|
*/
|
||||||
|
public function getResized(string $filepath, int $width, int $height, bool $autorotate, string $mimeType) {
|
||||||
|
|
||||||
|
$imaginaryUrl = $this->getUrl();
|
||||||
|
if (!$imaginaryUrl) {
|
||||||
|
throw new \RuntimeException('Try to use imaginary without valid url');
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpClient = $this->service->newClient();
|
||||||
|
|
||||||
|
switch ($mimeType) {
|
||||||
|
case 'image/png':
|
||||||
|
$type = 'png';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$type = 'jpeg';
|
||||||
|
}
|
||||||
|
|
||||||
|
$operations = [];
|
||||||
|
|
||||||
|
if ($autorotate) {
|
||||||
|
$operations[] = [
|
||||||
|
'operation' => 'autorotate',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$operations[] = [
|
||||||
|
'operation' => 'resize',
|
||||||
|
'params' => [
|
||||||
|
'width' => $width,
|
||||||
|
'height' => $height,
|
||||||
|
'stripmeta' => 'true',
|
||||||
|
'type' => $type,
|
||||||
|
'norotation' => 'true',
|
||||||
|
'force' => 'true'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$query = [];
|
||||||
|
$query['operations'] = json_encode($operations);
|
||||||
|
if ($this->hasKey()) {
|
||||||
|
$query['key'] = $this->getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $httpClient->post(
|
||||||
|
$imaginaryUrl . '/pipeline', [
|
||||||
|
'query' => $query,
|
||||||
|
'body' => file_get_contents($filepath),
|
||||||
|
'nextcloud' => ['allow_local_address' => true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->getStatusCode() !== 200) {
|
||||||
|
throw new \RuntimeException('Error generating temporary image in Imaginary: ' . json_decode($response->getBody())['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user