From a2fd02e0cd2c49aa6bccc91f351a7b9d0399c9d0 Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Fri, 20 Dec 2024 10:57:53 +0000 Subject: [PATCH 01/11] Updated regions tab documentation --- src/doc/manual/regions.rst | 8 +++++-- src/lang/ca/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/cs/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/de/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/es/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/fr/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/it/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/ko/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/nb/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/pl/LC_MESSAGES/documentation.po | 23 ++++++++++++++++---- src/lang/templates/gettext/documentation.pot | 9 +++++--- 11 files changed, 183 insertions(+), 41 deletions(-) diff --git a/src/doc/manual/regions.rst b/src/doc/manual/regions.rst index c0c018fd..5036de09 100644 --- a/src/doc/manual/regions.rst +++ b/src/doc/manual/regions.rst @@ -49,6 +49,12 @@ The entire region can be dragged by clicking within it. Positioning the region is made easier if you zoom in by holding down the ``Ctrl`` key while scrolling with the mouse scroll wheel. (The zoom can also be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.) +After creating a region you can add metadata about the it. +The ``Region name`` and ``Identifier`` fields are unique labels for the region, but I haven't found a use for them. + +The ``Boundary unit`` specifies whether the vertex positions are stored as absolute pixel values or relative proportions of the image width and height. +Don't use pixel values unless you have some other software that requires them. + .. image:: ../images/screenshot_276.png The most important metadata for a region is probably its "role_". @@ -56,8 +62,6 @@ This is chosen from a "controlled vocabulary" defined by the IPTC. Photini shows the IPTC names and definitions (as "tooltips") in a drop down menu when you click on the ``Role`` entry. You can select one or more roles from the list. -Other, less useful, metadata includes a name and identifier for the region. - .. image:: ../images/screenshot_277.png The `content type`_ is another controlled vocabulary that allows you to say what's special about the selected area. diff --git a/src/lang/ca/LC_MESSAGES/documentation.po b/src/lang/ca/LC_MESSAGES/documentation.po index 01d68d37..a9ecc3ef 100644 --- a/src/lang/ca/LC_MESSAGES/documentation.po +++ b/src/lang/ca/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Joan , 2023\n" "Language: ca\n" @@ -1920,6 +1920,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -1928,9 +1941,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5830,3 +5840,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/cs/LC_MESSAGES/documentation.po b/src/lang/cs/LC_MESSAGES/documentation.po index fe018779..8ad075a9 100644 --- a/src/lang/cs/LC_MESSAGES/documentation.po +++ b/src/lang/cs/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: fri, 2023\n" "Language: cs@qtfiletype\n" @@ -1917,6 +1917,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -1925,9 +1938,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5799,3 +5809,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/de/LC_MESSAGES/documentation.po b/src/lang/de/LC_MESSAGES/documentation.po index d6a79651..d3393372 100644 --- a/src/lang/de/LC_MESSAGES/documentation.po +++ b/src/lang/de/LC_MESSAGES/documentation.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: de\n" @@ -1914,6 +1914,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -1922,9 +1935,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5827,3 +5837,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/es/LC_MESSAGES/documentation.po b/src/lang/es/LC_MESSAGES/documentation.po index 573e8021..f2271f20 100644 --- a/src/lang/es/LC_MESSAGES/documentation.po +++ b/src/lang/es/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: es@qtfiletype\n" @@ -1910,6 +1910,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -1918,9 +1931,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5814,3 +5824,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/fr/LC_MESSAGES/documentation.po b/src/lang/fr/LC_MESSAGES/documentation.po index 52e9f633..89754420 100644 --- a/src/lang/fr/LC_MESSAGES/documentation.po +++ b/src/lang/fr/LC_MESSAGES/documentation.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: fr@qtfiletype\n" @@ -1904,6 +1904,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -1912,9 +1925,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5814,3 +5824,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/it/LC_MESSAGES/documentation.po b/src/lang/it/LC_MESSAGES/documentation.po index 899781a5..bd442bfd 100644 --- a/src/lang/it/LC_MESSAGES/documentation.po +++ b/src/lang/it/LC_MESSAGES/documentation.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: alessio Cadore , 2023\n" "Language: it@qtfiletype\n" @@ -1903,6 +1903,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -1911,9 +1924,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5813,3 +5823,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/ko/LC_MESSAGES/documentation.po b/src/lang/ko/LC_MESSAGES/documentation.po index 0d670e46..268bf543 100644 --- a/src/lang/ko/LC_MESSAGES/documentation.po +++ b/src/lang/ko/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: ko\n" @@ -2290,6 +2290,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -2298,9 +2311,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5817,3 +5827,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/nb/LC_MESSAGES/documentation.po b/src/lang/nb/LC_MESSAGES/documentation.po index 9d047f97..a4f4920d 100644 --- a/src/lang/nb/LC_MESSAGES/documentation.po +++ b/src/lang/nb/LC_MESSAGES/documentation.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: nb\n" @@ -1904,6 +1904,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -1912,9 +1925,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5814,3 +5824,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/pl/LC_MESSAGES/documentation.po b/src/lang/pl/LC_MESSAGES/documentation.po index 234a14b5..4eb59cb6 100644 --- a/src/lang/pl/LC_MESSAGES/documentation.po +++ b/src/lang/pl/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Dawid Głaz, 2023\n" "Language: pl@qtfiletype\n" @@ -1906,6 +1906,19 @@ msgid "" " be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" +msgid "" +"After creating a region you can add metadata about the it. The ``Region " +"name`` and ``Identifier`` fields are unique labels for the region, but I " +"haven't found a use for them." +msgstr "" + +msgid "" +"The ``Boundary unit`` specifies whether the vertex positions are stored as " +"absolute pixel values or relative proportions of the image width and height." +" Don't use pixel values unless you have some other software that requires " +"them." +msgstr "" + msgid "" "The most important metadata for a region is probably its \"role_\". This is " "chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows " @@ -1914,9 +1927,6 @@ msgid "" "list." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." -msgstr "" - msgid "" "The `content type`_ is another controlled vocabulary that allows you to say " "what's special about the selected area. The upper part shows the IPTC " @@ -5816,3 +5826,8 @@ msgstr "" #~ " metadata." #~ msgstr "" +#~ msgid "" +#~ "Other, less useful, metadata includes a " +#~ "name and identifier for the region." +#~ msgstr "" + diff --git a/src/lang/templates/gettext/documentation.pot b/src/lang/templates/gettext/documentation.pot index 68cfc77c..2df27027 100644 --- a/src/lang/templates/gettext/documentation.pot +++ b/src/lang/templates/gettext/documentation.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2024.11.1.post38\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-19 16:57+0000\n" +"POT-Creation-Date: 2024-12-20 10:57+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1012,10 +1012,13 @@ msgstr "" msgid "Positioning the region is made easier if you zoom in by holding down the ``Ctrl`` key while scrolling with the mouse scroll wheel. (The zoom can also be adjusted with ``Ctrl-Plus`` and ``Ctrl-Minus`` key combinations.)" msgstr "" -msgid "The most important metadata for a region is probably its \"role_\". This is chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows the IPTC names and definitions (as \"tooltips\") in a drop down menu when you click on the ``Role`` entry. You can select one or more roles from the list." +msgid "After creating a region you can add metadata about the it. The ``Region name`` and ``Identifier`` fields are unique labels for the region, but I haven't found a use for them." +msgstr "" + +msgid "The ``Boundary unit`` specifies whether the vertex positions are stored as absolute pixel values or relative proportions of the image width and height. Don't use pixel values unless you have some other software that requires them." msgstr "" -msgid "Other, less useful, metadata includes a name and identifier for the region." +msgid "The most important metadata for a region is probably its \"role_\". This is chosen from a \"controlled vocabulary\" defined by the IPTC. Photini shows the IPTC names and definitions (as \"tooltips\") in a drop down menu when you click on the ``Role`` entry. You can select one or more roles from the list." msgstr "" msgid "The `content type`_ is another controlled vocabulary that allows you to say what's special about the selected area. The upper part shows the IPTC controlled vocabulary. The most useful of these is probably ``human``. The lower part shows MWG \"types\". These are primarily intended for use by automatic systems such as face detectors and camera autofocus." From 027035bbed8c6720d46cef5ecdc68895f4791c6f Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Fri, 20 Dec 2024 14:44:05 +0000 Subject: [PATCH 02/11] Update tag reference documentation --- src/doc/manual/tags.rst | 8 +++++++- src/lang/ca/LC_MESSAGES/documentation.po | 13 +++++++++++-- src/lang/cs/LC_MESSAGES/documentation.po | 13 +++++++++++-- src/lang/de/LC_MESSAGES/documentation.po | 13 +++++++++++-- src/lang/es/LC_MESSAGES/documentation.po | 13 +++++++++++-- src/lang/fr/LC_MESSAGES/documentation.po | 13 +++++++++++-- src/lang/it/LC_MESSAGES/documentation.po | 13 +++++++++++-- src/lang/ko/LC_MESSAGES/documentation.po | 14 ++++++++++++-- src/lang/nb/LC_MESSAGES/documentation.po | 13 +++++++++++-- src/lang/pl/LC_MESSAGES/documentation.po | 13 +++++++++++-- src/lang/templates/gettext/documentation.pot | 12 +++++++++--- 11 files changed, 116 insertions(+), 22 deletions(-) diff --git a/src/doc/manual/tags.rst b/src/doc/manual/tags.rst index 6aebc99b..8a3a26d0 100644 --- a/src/doc/manual/tags.rst +++ b/src/doc/manual/tags.rst @@ -51,6 +51,10 @@ You may find this useful when deciding what to write in those fields. - - Xmp.iptc.ExtDescrAccessibility - + * - `Person(s) shown`_ + - + - Xmp.iptcExt.PersonInImage + - * - Rating_ - - Xmp.xmp.Rating @@ -133,7 +137,7 @@ You may find this useful when deciding what to write in those fields. - * - `Image Regions`_ - - - Xmp.iptcExt.ImageRegion + - Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo - * - Latitude_, longitude_ - Exif.GPSInfo.GPSLatitude Exif.GPSInfo.GPSLatitudeRef Exif.GPSInfo.GPSLongitude Exif.GPSInfo.GPSLongitudeRef @@ -324,6 +328,8 @@ It is applied to the Date / time Taken, Date / time Digitised and Date / time Mo http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata#gps-latitude .. _longitude: http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata#gps-longitude +.. _Person(s) shown: + http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata#person-shown-in-the-image .. _Rating: http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata#image-rating .. _Rights\: Usage Terms: diff --git a/src/lang/ca/LC_MESSAGES/documentation.po b/src/lang/ca/LC_MESSAGES/documentation.po index a9ecc3ef..6866da34 100644 --- a/src/lang/ca/LC_MESSAGES/documentation.po +++ b/src/lang/ca/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Joan , 2023\n" "Language: ca\n" @@ -2081,6 +2081,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -2248,7 +2254,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5845,3 +5851,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/cs/LC_MESSAGES/documentation.po b/src/lang/cs/LC_MESSAGES/documentation.po index 8ad075a9..06bd2a7e 100644 --- a/src/lang/cs/LC_MESSAGES/documentation.po +++ b/src/lang/cs/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: fri, 2023\n" "Language: cs@qtfiletype\n" @@ -2078,6 +2078,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -2245,7 +2251,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5814,3 +5820,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/de/LC_MESSAGES/documentation.po b/src/lang/de/LC_MESSAGES/documentation.po index d3393372..ac9cc9bb 100644 --- a/src/lang/de/LC_MESSAGES/documentation.po +++ b/src/lang/de/LC_MESSAGES/documentation.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: de\n" @@ -2075,6 +2075,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -2242,7 +2248,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5842,3 +5848,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/es/LC_MESSAGES/documentation.po b/src/lang/es/LC_MESSAGES/documentation.po index f2271f20..8c77e5c6 100644 --- a/src/lang/es/LC_MESSAGES/documentation.po +++ b/src/lang/es/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: es@qtfiletype\n" @@ -2071,6 +2071,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -2238,7 +2244,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5829,3 +5835,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/fr/LC_MESSAGES/documentation.po b/src/lang/fr/LC_MESSAGES/documentation.po index 89754420..ddbac402 100644 --- a/src/lang/fr/LC_MESSAGES/documentation.po +++ b/src/lang/fr/LC_MESSAGES/documentation.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: fr@qtfiletype\n" @@ -2065,6 +2065,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -2232,7 +2238,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5829,3 +5835,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/it/LC_MESSAGES/documentation.po b/src/lang/it/LC_MESSAGES/documentation.po index bd442bfd..e840c2ca 100644 --- a/src/lang/it/LC_MESSAGES/documentation.po +++ b/src/lang/it/LC_MESSAGES/documentation.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: alessio Cadore , 2023\n" "Language: it@qtfiletype\n" @@ -2064,6 +2064,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -2231,7 +2237,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5828,3 +5834,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/ko/LC_MESSAGES/documentation.po b/src/lang/ko/LC_MESSAGES/documentation.po index 268bf543..53edbea5 100644 --- a/src/lang/ko/LC_MESSAGES/documentation.po +++ b/src/lang/ko/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: ko\n" @@ -2462,6 +2462,13 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +#, fuzzy +msgid "Xmp.iptcExt.PersonInImage" +msgstr "Xmp.iptcExt.LocationShown" + msgid "Rating_" msgstr "Rating_" @@ -2631,7 +2638,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5832,3 +5839,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/nb/LC_MESSAGES/documentation.po b/src/lang/nb/LC_MESSAGES/documentation.po index a4f4920d..d0375f90 100644 --- a/src/lang/nb/LC_MESSAGES/documentation.po +++ b/src/lang/nb/LC_MESSAGES/documentation.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Jim Easterbrook , 2023\n" "Language: nb\n" @@ -2065,6 +2065,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -2232,7 +2238,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5829,3 +5835,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/pl/LC_MESSAGES/documentation.po b/src/lang/pl/LC_MESSAGES/documentation.po index 4eb59cb6..723838e8 100644 --- a/src/lang/pl/LC_MESSAGES/documentation.po +++ b/src/lang/pl/LC_MESSAGES/documentation.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: Photini 2023.4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: 2023-04-19 08:54+0000\n" "Last-Translator: Dawid Głaz, 2023\n" "Language: pl@qtfiletype\n" @@ -2067,6 +2067,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -2234,7 +2240,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" @@ -5831,3 +5837,6 @@ msgstr "" #~ "name and identifier for the region." #~ msgstr "" +#~ msgid "Xmp.iptcExt.ImageRegion" +#~ msgstr "" + diff --git a/src/lang/templates/gettext/documentation.pot b/src/lang/templates/gettext/documentation.pot index 2df27027..a452a152 100644 --- a/src/lang/templates/gettext/documentation.pot +++ b/src/lang/templates/gettext/documentation.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Photini 2024.11.1.post38\n" +"Project-Id-Version: Photini 2024.11.1.post43\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-20 10:57+0000\n" +"POT-Creation-Date: 2024-12-20 14:43+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1120,6 +1120,12 @@ msgstr "" msgid "Xmp.iptc.ExtDescrAccessibility" msgstr "" +msgid "`Person(s) shown`_" +msgstr "" + +msgid "Xmp.iptcExt.PersonInImage" +msgstr "" + msgid "Rating_" msgstr "" @@ -1285,7 +1291,7 @@ msgstr "" msgid "`Image Regions`_" msgstr "" -msgid "Xmp.iptcExt.ImageRegion" +msgid "Xmp.iptcExt.ImageRegion Xmp.mwg-rs.Regions Xmp.MP.RegionInfo" msgstr "" msgid "Latitude_, longitude_" From 10090b15286d174df7b142ca711354cda1158b0a Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Sat, 21 Dec 2024 12:52:46 +0000 Subject: [PATCH 03/11] More intelligent merging of image region data --- src/photini/metadata.py | 3 +- src/photini/types.py | 62 +++++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/photini/metadata.py b/src/photini/metadata.py index d5ed775c..baea0a2a 100644 --- a/src/photini/metadata.py +++ b/src/photini/metadata.py @@ -719,7 +719,8 @@ def __init__(self, path, notify=None): if extras: value = list(value) + extras values[0] = (tag, self._data_type[name](value)) - logger.info('%s: merged people in regions', tag) + logger.info('%s(%s): merged image_region people', + os.path.basename(self._path), name) # merge in camera timezone if (name in ('date_digitised', 'date_modified', 'date_taken') and self.timezone): diff --git a/src/photini/types.py b/src/photini/types.py index cc0cf63c..dd3ccb89 100644 --- a/src/photini/types.py +++ b/src/photini/types.py @@ -1882,6 +1882,7 @@ class ImageRegionItem(MD_Structure): 'Iptc4xmpExt:PersonInImage': MD_MultiString, 'Iptc4xmpExt:OrganisationInImageName': MD_MultiString, 'mwg-rs:BarCodeValue': MD_String, + 'mwg-rs:Description': MD_String, 'mwg-rs:Name': MD_String, 'mwg-rs:Title': MD_String, 'photoshop:CaptionWriter': MD_String, @@ -1891,6 +1892,26 @@ class ImageRegionItem(MD_Structure): 'xmpRights:UsageTerms': MD_LangAlt, } + def merge(self, info, tag, other): + result = super(ImageRegionItem, self).merge(info, tag, other) + for old_key, new_key in (('mwg-rs:Name', 'dc:description'), + ('mwg-rs:Description', 'dc:description')): + if result[old_key]: + value = result[old_key] + del result[old_key] + if old_key == 'mwg-rs:Name' and ( + result.has_type('imgregtype:human') + or result.has_type('', uid='mwg-rs:Type Face')): + new_key = 'Iptc4xmpExt:PersonInImage' + value = [value] + if result[new_key]: + result[new_key] = result[new_key].merge( + info, '{}[{}]'.format(tag, new_key), value) + else: + result[new_key] = value + self.log_merged(info, '{}[{}]'.format(tag, new_key), value) + return self.__class__(result) + @staticmethod def ctype_IPTC_to_MWG(file_value): if 'Iptc4xmpExt:rCtype' not in file_value: @@ -1971,7 +1992,7 @@ def to_MWG(self, dims): return None region['mwg-rs:Area'] = area elif (key == 'Iptc4xmpExt:PersonInImage' - and not file_value['mwg-rs:Name']): + and file_value['mwg-rs:Name'] not in (value, None)): region['mwg-rs:Name'] = str(value) elif key == 'dc:description': region['mwg-rs:Description'] = value.best_match() @@ -2003,7 +2024,7 @@ def to_MP(self, dims): return region @classmethod - def from_MWG(cls, file_value, scale_diameter): + def from_MWG(cls, file_value, dims): if not file_value: return None # convert MWG region data to IPTC format @@ -2026,6 +2047,7 @@ def from_MWG(cls, file_value, scale_diameter): boundary['Iptc4xmpExt:rbH'] = h elif 'stArea:d' in area: # circle + scale_diameter = min(dims['stDim:h'] / dims['stDim:w'], 1.0) d = float(area['stArea:d']) * scale_diameter boundary['Iptc4xmpExt:rbShape'] = 'circle' boundary['Iptc4xmpExt:rbX'] = x @@ -2039,25 +2061,16 @@ def from_MWG(cls, file_value, scale_diameter): region = {'Iptc4xmpExt:RegionBoundary': boundary} file_value, ctype = cls.ctype_MWG_to_IPTC(file_value) region['Iptc4xmpExt:rCtype'] = [ctype] + core_region = cls(region) for key, value in file_value.items(): - if key in ('mwg-rs:Area', 'mwg-rs:Extensions', 'rdfs:seeAlso'): - continue - elif (key == 'mwg-rs:Name' and 'Iptc4xmpExt:Name' in ctype - and ctype['Iptc4xmpExt:Name'] == 'Face'): - region['Iptc4xmpExt:PersonInImage'] = [value] - elif key == 'mwg-rs:Description': - region['dc:description'] = value - else: + if key in ('rdfs:seeAlso', 'mwg-rs:Extensions'): + for k, v in value.items(): + region[k] = v + elif key != 'mwg-rs:Area': region[key] = value - region = cls(region) - for key in ('mwg-rs:Extensions', 'rdfs:seeAlso'): - if key in file_value: - for k, v in file_value[key].items(): - if k in region: - region[k] = region[k].merge('info', 'tag', v) - else: - region[k] = v - return cls(region) + # merge core data with full data to convert items + return core_region.merge( + '(image_region)', 'Xmp.mwg-rs.Regions', cls(region)) @classmethod def from_MP(cls, file_value): @@ -2125,9 +2138,11 @@ def has_uid(self, key, uid): return True return False - def has_type(self, qcode): - data = image_region_types[image_region_types_idx[qcode]]['data'] - return self.has_uid('Iptc4xmpExt:rCtype', data['xmp:Identifier'][0]) + def has_type(self, qcode, uid=None): + if not uid: + data = image_region_types[image_region_types_idx[qcode]]['data'] + uid = data['xmp:Identifier'][0] + return self.has_uid('Iptc4xmpExt:rCtype', uid) def has_role(self, qcode): data = image_region_roles[image_region_roles_idx[qcode]]['data'] @@ -2200,10 +2215,9 @@ def from_exiv2(cls, file_value, tag): for x in file_value]} elif tag == 'Xmp.mwg-rs.Regions': dims = AppliedToDimensions(file_value['mwg-rs:AppliedToDimensions']) - scale_diameter = min(dims['stDim:h'] / dims['stDim:w'], 1.0) value = { 'AppliedToDimensions': dims, - 'RegionList': [ImageRegionItem.from_MWG(x, scale_diameter) + 'RegionList': [ImageRegionItem.from_MWG(x, dims) for x in file_value['mwg-rs:RegionList']]} elif tag == 'Xmp.MP.RegionInfo': value = {'RegionList': [ImageRegionItem.from_MP(x) From fe7145125806f6d27ff0f58b1e15f4992b4c6678 Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Sat, 21 Dec 2024 14:41:44 +0000 Subject: [PATCH 04/11] More pedantic choice of what to put in MWG extras --- src/photini/types.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/photini/types.py b/src/photini/types.py index dd3ccb89..c3bdfba7 100644 --- a/src/photini/types.py +++ b/src/photini/types.py @@ -1962,6 +1962,7 @@ def to_MWG(self, dims): file_value, region = self.ctype_IPTC_to_MWG(dict(self)) # convert some IPTC region data to MWG format region['mwg-rs:Extensions'] = {} + region['mwg-rs:Name'] = file_value['mwg-rs:Name'] for key, value in file_value.items(): if not value: continue @@ -1992,13 +1993,17 @@ def to_MWG(self, dims): return None region['mwg-rs:Area'] = area elif (key == 'Iptc4xmpExt:PersonInImage' - and file_value['mwg-rs:Name'] not in (value, None)): - region['mwg-rs:Name'] = str(value) + and not file_value['mwg-rs:Name'] + and 'mwg-rs:Type' in region + and region['mwg-rs:Type'] == 'Face'): + region['mwg-rs:Name'] = str(value) elif key == 'dc:description': region['mwg-rs:Description'] = value.best_match() elif key.startswith('mwg-rs:'): region[key] = value - else: + elif key not in ('Iptc4xmpExt:rCtype', 'Iptc4xmpExt:rRole'): + # MWG docs say extension can be "any additional top + # level XMP property". ctype and role are not top level region['mwg-rs:Extensions'][key] = value return region From e5ce364979a7c96f846fab989e8b6f690d87d78c Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Sun, 22 Dec 2024 13:22:07 +0000 Subject: [PATCH 05/11] Move controlled vocabulary stuff to its own module Also stopped storing MWG type info in IPTC and vice versa. --- src/photini/regions.py | 61 +++----------------- src/photini/types.py | 114 +++++++++++------------------------- src/photini/vocab.py | 128 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 135 deletions(-) create mode 100644 src/photini/vocab.py diff --git a/src/photini/regions.py b/src/photini/regions.py index 441d5e0e..c71fac54 100644 --- a/src/photini/regions.py +++ b/src/photini/regions.py @@ -21,10 +21,10 @@ import os import re -from photini.cv import image_region_types, image_region_roles from photini.pyqt import * from photini.pyqt import set_symbol_font, using_pyside from photini.types import ImageRegionItem, MD_LangAlt +from photini.vocab import IPTCRoleCV, IPTCTypeCV, MWGTypeCV from photini.widgets import LangAltWidget, MultiStringEdit, SingleLineEdit logger = logging.getLogger(__name__) @@ -589,14 +589,14 @@ def draw_boundaries(self, idx, regions): active = n == idx boundary = region['Iptc4xmpExt:RegionBoundary'] if boundary['Iptc4xmpExt:rbShape'] == 'rectangle': - if region.has_role('imgregrole:squareCropping'): + if region.has_role('squareCropping'): constraint = 'square' - elif region.has_role('imgregrole:landscapeCropping'): + elif region.has_role('landscapeCropping'): if self.transform().isRotating(): constraint = 'portrait' else: constraint = 'landscape' - elif region.has_role('imgregrole:portraitCropping'): + elif region.has_role('portraitCropping'): if self.transform().isRotating(): constraint = 'landscape' else: @@ -775,52 +775,6 @@ def set_value(self, value): class RegionForm(QtWidgets.QScrollArea): new_value = QtSignal(int, dict) - MWG_region_types = ( - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Face'}, - 'xmp:Identifier': ('mwg-rs:Type Face',)}, - 'definition': {'en-GB': "Region area for people's faces."}, - 'name': {'en-GB': 'Face'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Pet'}, - 'xmp:Identifier': ('mwg-rs:Type Pet',)}, - 'definition': {'en-GB': "Region area for pets."}, - 'name': {'en-GB': 'Pet'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/EvaluatedUsed'}, - 'xmp:Identifier': ('mwg-rs:Type Focus', - 'mwg-rs:FocusUsage EvaluatedUsed')}, - 'definition': {'en-GB': "Region area for camera auto-focus regions." - "
EvaluatedUsed specifies that the focus point was" - " considered during focusing and was used in the final" - " image."}, - 'name': {'en-GB': 'Focus (EvaluatedUsed)'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/EvaluatedNotUsed'}, - 'xmp:Identifier': ('mwg-rs:Type Focus', - 'mwg-rs:FocusUsage EvaluatedNotUsed')}, - 'definition': {'en-GB': "Region area for camera auto-focus regions." - "
EvaluatedNotUsed specifies that the focus point" - " was considered during focusing but not utilised in" - " the final image."}, - 'name': {'en-GB': 'Focus (EvaluatedNotUsed)'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/NotEvaluatedNotUsed'}, - 'xmp:Identifier': ('mwg-rs:Type Focus' - 'mwg-rs:FocusUsage NotEvaluatedNotUsed')}, - 'definition': {'en-GB': "Region area for camera auto-focus regions." - "
NotEvaluatedNotUsed specifies that a focus point" - " was not evaluated and not used, e.g. a fixed focus" - " point on the camera which was not used in any" - " fashion."}, - 'name': {'en-GB': 'Focus (NotEvaluatedNotUsed)'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'BarCode'}, - 'xmp:Identifier': ('mwg-rs:Type BarCode',)}, - 'definition': {'en-GB': "One dimensional linear or two dimensional" - " matrix optical code."}, - 'name': {'en-GB': 'BarCode'}, - 'note': None}, - ) def __init__(self, idx, *arg, **kw): super(RegionForm, self).__init__(*arg, **kw) @@ -863,7 +817,7 @@ def __init__(self, idx, *arg, **kw): translate('RegionsTab', 'Boundary unit'), self.widgets[key]) # roles key = 'Iptc4xmpExt:rRole' - self.widgets[key] = EntityConceptWidget(key, image_region_roles) + self.widgets[key] = EntityConceptWidget(key, IPTCRoleCV.vocab) self.widgets[key].setToolTip('

{}

'.format(translate( 'RegionsTab', 'Role of this region among all regions of this image' ' or of other images. The value SHOULD be taken from a Controlled' @@ -872,9 +826,8 @@ def __init__(self, idx, *arg, **kw): layout.addRow(translate('RegionsTab', 'Role'), self.widgets[key]) # content types key = 'Iptc4xmpExt:rCtype' - self.widgets[key] = EntityConceptWidget(key, image_region_types) - self.widgets[key].add_menu_items( - self.MWG_region_types, exclusive=True) + self.widgets[key] = EntityConceptWidget(key, IPTCTypeCV.vocab) + self.widgets[key].add_menu_items(MWGTypeCV.vocab, exclusive=True) self.widgets[key].setToolTip('

{}

'.format(translate( 'RegionsTab', 'The semantic type of what is shown inside the' ' region. The value SHOULD be taken from a Controlled' diff --git a/src/photini/types.py b/src/photini/types.py index c3bdfba7..067980ee 100644 --- a/src/photini/types.py +++ b/src/photini/types.py @@ -24,11 +24,10 @@ import pprint import re -from photini.cv import (image_region_roles, image_region_roles_idx, - image_region_types, image_region_types_idx) from photini.exiv2 import MetadataHandler from photini.pyqt import * from photini.pyqt import qt_version_info, using_pyside +from photini.vocab import IPTCRoleCV, IPTCTypeCV, MWGTypeCV logger = logging.getLogger(__name__) @@ -1900,7 +1899,7 @@ def merge(self, info, tag, other): value = result[old_key] del result[old_key] if old_key == 'mwg-rs:Name' and ( - result.has_type('imgregtype:human') + result.has_type('human') or result.has_type('', uid='mwg-rs:Type Face')): new_key = 'Iptc4xmpExt:PersonInImage' value = [value] @@ -1912,58 +1911,21 @@ def merge(self, info, tag, other): self.log_merged(info, '{}[{}]'.format(tag, new_key), value) return self.__class__(result) - @staticmethod - def ctype_IPTC_to_MWG(file_value): - if 'Iptc4xmpExt:rCtype' not in file_value: - return file_value, {} - # move MWG type info from Iptc4xmpExt:rCtype to extra info - ctype_list = [] - extras = {} - for item in file_value['Iptc4xmpExt:rCtype']: - if any(x.startswith('mwg') for x in item['xmp:Identifier']): - extras.update(x.split() for x in item['xmp:Identifier']) - else: - ctype_list.append(item) - file_value['Iptc4xmpExt:rCtype'] = ctype_list - return file_value, extras - def to_IPTC(self): - # move MWG type info from Iptc4xmpExt:rCtype to extra info - file_value, extras = self.ctype_IPTC_to_MWG(dict(self)) - file_value.update(extras) + # remove MWG ctype from Iptc4xmpExt:rCtype + file_value = dict(self) + file_value['Iptc4xmpExt:rCtype'] = MWGTypeCV.clean_file_data( + file_value['Iptc4xmpExt:rCtype']) return file_value - @staticmethod - def ctype_MWG_to_IPTC(file_value): - if 'mwg-rs:Type' not in file_value: - return file_value, {} - # move MWG type info from extra info to Iptc4xmpExt:rCtype - name = file_value['mwg-rs:Type'] - del file_value['mwg-rs:Type'] - identifier = ['mwg-rs:Type ' + name] - if 'mwg-rs:FocusUsage' in file_value: - focus_usage = file_value['mwg-rs:FocusUsage'] - del file_value['mwg-rs:FocusUsage'] - name += '/' + focus_usage - identifier.append('mwg-rs:FocusUsage ' + focus_usage) - return file_value, { - 'Iptc4xmpExt:Name': name, 'xmp:Identifier': identifier} - - @classmethod - def from_IPTC(cls, file_value): - file_value, ctype = cls.ctype_MWG_to_IPTC(file_value) - if ctype: - if 'Iptc4xmpExt:rCtype' not in file_value: - file_value['Iptc4xmpExt:rCtype'] = [] - file_value['Iptc4xmpExt:rCtype'].append(ctype) - return cls(file_value) - def to_MWG(self, dims): - file_value, region = self.ctype_IPTC_to_MWG(dict(self)) # convert some IPTC region data to MWG format - region['mwg-rs:Extensions'] = {} - region['mwg-rs:Name'] = file_value['mwg-rs:Name'] - for key, value in file_value.items(): + region = { + 'mwg-rs:Extensions': {}, + 'mwg-rs:Name': self['mwg-rs:Name'], + } + region.update(MWGTypeCV.to_file_data(self['Iptc4xmpExt:rCtype'])) + for key, value in self.items(): if not value: continue if key == 'Iptc4xmpExt:RegionBoundary': @@ -1993,7 +1955,7 @@ def to_MWG(self, dims): return None region['mwg-rs:Area'] = area elif (key == 'Iptc4xmpExt:PersonInImage' - and not file_value['mwg-rs:Name'] + and not region['mwg-rs:Name'] and 'mwg-rs:Type' in region and region['mwg-rs:Type'] == 'Face'): region['mwg-rs:Name'] = str(value) @@ -2063,15 +2025,16 @@ def from_MWG(cls, file_value, dims): boundary['Iptc4xmpExt:rbShape'] = 'polygon' boundary['Iptc4xmpExt:rbVertices'] = [{ 'Iptc4xmpExt:rbX': x, 'Iptc4xmpExt:rbY': y}] - region = {'Iptc4xmpExt:RegionBoundary': boundary} - file_value, ctype = cls.ctype_MWG_to_IPTC(file_value) - region['Iptc4xmpExt:rCtype'] = [ctype] + region = { + 'Iptc4xmpExt:RegionBoundary': boundary, + 'Iptc4xmpExt:rCtype': [MWGTypeCV.from_file_data(file_value)], + } core_region = cls(region) for key, value in file_value.items(): if key in ('rdfs:seeAlso', 'mwg-rs:Extensions'): for k, v in value.items(): region[k] = v - elif key != 'mwg-rs:Area': + elif key not in ('mwg-rs:Area', 'mwg-rs:Type', 'mwg-rs:FocusUsage'): region[key] = value # merge core data with full data to convert items return core_region.merge( @@ -2131,8 +2094,7 @@ def from_Exif(cls, file_value): region['Iptc4xmpExt:rbUnit'] = 'pixel' return cls({ 'Iptc4xmpExt:RegionBoundary': region, - 'Iptc4xmpExt:rRole': [image_region_roles[ - image_region_roles_idx['imgregrole:mainSubjectArea']]['data']], + 'Iptc4xmpExt:rRole': [IPTCRoleCV.data_for_qcode('mainSubjectArea')], }) def has_uid(self, key, uid): @@ -2145,12 +2107,12 @@ def has_uid(self, key, uid): def has_type(self, qcode, uid=None): if not uid: - data = image_region_types[image_region_types_idx[qcode]]['data'] + data = IPTCTypeCV.data_for_qcode(qcode) uid = data['xmp:Identifier'][0] return self.has_uid('Iptc4xmpExt:rCtype', uid) def has_role(self, qcode): - data = image_region_roles[image_region_roles_idx[qcode]]['data'] + data = IPTCRoleCV.data_for_qcode(qcode) return self.has_uid('Iptc4xmpExt:rRole', data['xmp:Identifier'][0]) def to_Qt(self, image): @@ -2175,10 +2137,10 @@ class RegionList(MD_StructArray): item_type = ImageRegionItem def find(self, other): - if other.has_role('imgregrole:mainSubjectArea'): + if other.has_role('mainSubjectArea'): # only one main subject area region allowed for n, value in enumerate(self): - if value.has_role('imgregrole:mainSubjectArea'): + if value.has_role('mainSubjectArea'): return n return len(self) for n, value in enumerate(self): @@ -2216,8 +2178,7 @@ def from_exiv2(cls, file_value, tag): if not file_value: return cls() if tag == 'Xmp.iptcExt.ImageRegion': - value = {'RegionList': [ImageRegionItem.from_IPTC(x) - for x in file_value]} + value = {'RegionList': [ImageRegionItem(x) for x in file_value]} elif tag == 'Xmp.mwg-rs.Regions': dims = AppliedToDimensions(file_value['mwg-rs:AppliedToDimensions']) value = { @@ -2337,13 +2298,12 @@ def from_notes(self, notes, image, target_size): continue region = { 'Iptc4xmpExt:RegionBoundary': boundary, - 'Iptc4xmpExt:rRole': [image_region_roles[ - image_region_roles_idx['imgregrole:subjectArea']]['data']], + 'Iptc4xmpExt:rRole': [IPTCRoleCV.data_for_qcode('subjectArea')], } if note['is_person']: region['Iptc4xmpExt:PersonInImage'] = [note['content']] - region['Iptc4xmpExt:rCtype'] = [image_region_types[ - image_region_types_idx['imgregtype:human']]['data']] + region['Iptc4xmpExt:rCtype'] = [ + IPTCTypeCV.data_for_qcode('human')] else: region['dc:description'] = {'x-default': note['content']} if note['authorrealname']: @@ -2376,15 +2336,13 @@ def to_notes(self, image, target_size): for region, note in self.to_note_boundary(image, target_size): note['content'] = '' note['is_person'] = False - if region.has_type('imgregtype:human'): + if region.has_type('human'): if 'Iptc4xmpExt:PersonInImage' in region: note['content'] = ', '.join( region['Iptc4xmpExt:PersonInImage']) note['is_person'] = True elif not any(region.has_role(x) for x in ( - 'imgregrole:subjectArea', - 'imgregrole:mainSubjectArea', - 'imgregrole:areaOfInterest')): + 'subjectArea', 'mainSubjectArea', 'areaOfInterest')): continue if 'dc:description' in region and not note['content']: note['content'] = MD_LangAlt( @@ -2405,17 +2363,11 @@ def get_focus(self, image): if transform and transform.isRotating(): portrait_format = not portrait_format if portrait_format: - roles = ('imgregrole:landscapeCropping', - 'imgregrole:squareCropping', - 'imgregrole:recomCropping', - 'imgregrole:cropping', - 'imgregrole:portraitCropping') + roles = ('landscapeCropping', 'squareCropping', 'recomCropping', + 'cropping', 'portraitCropping') else: - roles = ('imgregrole:squareCropping', - 'imgregrole:portraitCropping', - 'imgregrole:landscapeCropping', - 'imgregrole:recomCropping', - 'imgregrole:cropping') + roles = ('squareCropping', 'portraitCropping', 'landscapeCropping', + 'recomCropping', 'cropping') for role in roles: for region in self: if not region.has_role(role): diff --git a/src/photini/vocab.py b/src/photini/vocab.py new file mode 100644 index 00000000..9a144f36 --- /dev/null +++ b/src/photini/vocab.py @@ -0,0 +1,128 @@ +# Photini - a simple photo metadata editor. +# http://github.com/jim-easterbrook/Photini +# Copyright (C) 2024 Jim Easterbrook jim@jim-easterbrook.me.uk +# +# This file is part of Photini. +# +# Photini is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# Photini 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 General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with Photini. If not, see . + +# Stuff to handle "controlled vocabularies" from IPTC and others + +from photini.cv import image_region_types, image_region_roles + + +class IPTCBaseCV(object): + @classmethod + def init_qcode_map(cls, prefix): + prefix += ':' + cls.qcode_map = {} + for item in cls.vocab: + qcode = item['qcode'].replace(prefix, '') + cls.qcode_map[qcode] = item['data'] + + @classmethod + def data_for_qcode(cls, qcode): + if qcode in cls.qcode_map: + return cls.qcode_map[qcode] + return {} + + +class IPTCRoleCV(IPTCBaseCV): + vocab = image_region_roles + + +class IPTCTypeCV(IPTCBaseCV): + vocab = image_region_types + + +IPTCRoleCV.init_qcode_map('imgregrole') +IPTCTypeCV.init_qcode_map('imgregtype') + + +class MWGTypeCV(object): + vocab = ( + {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Face'}, + 'xmp:Identifier': ('mwg-rs:Type Face',)}, + 'definition': {'en-GB': "Region area for people's faces."}, + 'name': {'en-GB': 'Face'}, + 'note': None}, + {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Pet'}, + 'xmp:Identifier': ('mwg-rs:Type Pet',)}, + 'definition': {'en-GB': "Region area for pets."}, + 'name': {'en-GB': 'Pet'}, + 'note': None}, + {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/EvaluatedUsed'}, + 'xmp:Identifier': ('mwg-rs:Type Focus', + 'mwg-rs:FocusUsage EvaluatedUsed')}, + 'definition': {'en-GB': "Region area for camera auto-focus regions." + "
EvaluatedUsed specifies that the focus point was" + " considered during focusing and was used in the final" + " image."}, + 'name': {'en-GB': 'Focus (EvaluatedUsed)'}, + 'note': None}, + {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/EvaluatedNotUsed'}, + 'xmp:Identifier': ('mwg-rs:Type Focus', + 'mwg-rs:FocusUsage EvaluatedNotUsed')}, + 'definition': {'en-GB': "Region area for camera auto-focus regions." + "
EvaluatedNotUsed specifies that the focus point" + " was considered during focusing but not utilised in" + " the final image."}, + 'name': {'en-GB': 'Focus (EvaluatedNotUsed)'}, + 'note': None}, + {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/NotEvaluatedNotUsed'}, + 'xmp:Identifier': ('mwg-rs:Type Focus' + 'mwg-rs:FocusUsage NotEvaluatedNotUsed')}, + 'definition': {'en-GB': "Region area for camera auto-focus regions." + "
NotEvaluatedNotUsed specifies that a focus point" + " was not evaluated and not used, e.g. a fixed focus" + " point on the camera which was not used in any" + " fashion."}, + 'name': {'en-GB': 'Focus (NotEvaluatedNotUsed)'}, + 'note': None}, + {'data': {'Iptc4xmpExt:Name': {'en-GB': 'BarCode'}, + 'xmp:Identifier': ('mwg-rs:Type BarCode',)}, + 'definition': {'en-GB': "One dimensional linear or two dimensional" + " matrix optical code."}, + 'name': {'en-GB': 'BarCode'}, + 'note': None}, + ) + + @classmethod + def clean_file_data(cls, ctype_data): + # remove any MWG ctype from a list of ctypes + result = list(ctype_data) + for item in cls.vocab: + if item['data'] in result: + result.remove(item['data']) + break + return result + + @classmethod + def to_file_data(cls, ctype_data): + for item in cls.vocab: + if item['data'] in ctype_data: + return dict(x.split() for x in item['data']['xmp:Identifier']) + return {} + + @staticmethod + def from_file_data(data): + if 'mwg-rs:Type' not in data: + return {} + name = data['mwg-rs:Type'] + identifier = ['mwg-rs:Type ' + name] + if 'mwg-rs:FocusUsage' in data: + focus_usage = data['mwg-rs:FocusUsage'] + name += '/' + focus_usage + identifier.append('mwg-rs:FocusUsage ' + focus_usage) + return {'Iptc4xmpExt:Name': name, 'xmp:Identifier': tuple(identifier)} From e7f0694dd8a4e339ace4d01e28d10bb9f46e5a9f Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Sun, 22 Dec 2024 13:27:37 +0000 Subject: [PATCH 06/11] Remove redundant indexes from CV downloaded data --- src/photini/cv.py | 31 ------------------------------- utils/download_cvs.py | 5 ----- 2 files changed, 36 deletions(-) diff --git a/src/photini/cv.py b/src/photini/cv.py index f89ae40c..2e3364b7 100644 --- a/src/photini/cv.py +++ b/src/photini/cv.py @@ -108,24 +108,6 @@ 'water'}, 'qcode': 'imgregtype:bodyOfWater'}) -image_region_types_idx = \ -{'imgregtype:animal': 0, - 'imgregtype:artwork': 1, - 'imgregtype:bodyOfWater': 15, - 'imgregtype:building': 10, - 'imgregtype:clothing': 13, - 'imgregtype:dividingLine': 2, - 'imgregtype:food': 12, - 'imgregtype:geoArea': 4, - 'imgregtype:graphic': 5, - 'imgregtype:human': 7, - 'imgregtype:machineCode': 6, - 'imgregtype:plant': 3, - 'imgregtype:product': 8, - 'imgregtype:rockFormation': 14, - 'imgregtype:text': 9, - 'imgregtype:vehicle': 11} - # ©IPTC, International Press Telecommunications Council - https://iptc.org # Date: 2019-12-06T12:00:00+00:00 # Licence: http://creativecommons.org/licenses/by/4.0/ @@ -208,16 +190,3 @@ 'term of a role CV of this business.'}, 'qcode': 'imgregrole:businessUse'}) -image_region_roles_idx = \ -{'imgregrole:areaOfInterest': 9, - 'imgregrole:businessUse': 10, - 'imgregrole:compositeImageItem': 5, - 'imgregrole:copyrightRegion': 6, - 'imgregrole:cropping': 0, - 'imgregrole:landscapeCropping': 2, - 'imgregrole:mainSubjectArea': 8, - 'imgregrole:portraitCropping': 3, - 'imgregrole:recomCropping': 1, - 'imgregrole:squareCropping': 4, - 'imgregrole:subjectArea': 7} - diff --git a/utils/download_cvs.py b/utils/download_cvs.py index ff4ae6aa..db116b73 100644 --- a/utils/download_cvs.py +++ b/utils/download_cvs.py @@ -69,11 +69,6 @@ def main(argv=None): py.write(' = \\\n') pprint(tuple(data[x] for x in uris), stream=py) py.write('\n') - py.write(data_name) - py.write('_idx = \\\n') - pprint(dict((data[x]['qcode'], n) - for n, x in enumerate(uris)), stream=py) - py.write('\n') return 0 From ffe656ed8cbf6b2dfb8c9d5f7740dd8b2e9fc28d Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Sun, 22 Dec 2024 13:53:51 +0000 Subject: [PATCH 07/11] Better inferring of type for region "extra" data --- src/photini/types.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/photini/types.py b/src/photini/types.py index 067980ee..7bad9bd9 100644 --- a/src/photini/types.py +++ b/src/photini/types.py @@ -24,6 +24,8 @@ import pprint import re +import exiv2 + from photini.exiv2 import MetadataHandler from photini.pyqt import * from photini.pyqt import qt_version_info, using_pyside @@ -683,13 +685,22 @@ def __init__(self, value=None): @classmethod def get_type(cls, key, value): if cls.extendable and key not in cls.item_type: - logger.warning('Inferring type for %s', key) - if isinstance(value, (list, tuple)): - cls.item_type[key] = MD_MultiString - elif isinstance(value, dict): - cls.item_type[key] = MD_LangAlt + result = exiv2.XmpProperties.propertyType( + exiv2.XmpKey('Xmp.' + key.replace(':', '.'))) + if result in (exiv2.TypeId.xmpAlt, exiv2.TypeId.xmpBag, + exiv2.TypeId.xmpSeq): + result = MD_MultiString + elif result == exiv2.TypeId.langAlt: + result = MD_LangAlt + elif isinstance(value, (list, tuple, dict)): + logger.warning('Inferring type for %s', key) + if isinstance(value, dict): + result = MD_LangAlt + else: + result = MD_MultiString else: - cls.item_type[key] = MD_String + result = MD_String + cls.item_type[key] = result return cls.item_type[key] @classmethod @@ -1879,16 +1890,9 @@ class ImageRegionItem(MD_Structure): 'Iptc4xmpExt:rCtype': EntityConceptArray, 'Iptc4xmpExt:rRole': EntityConceptArray, 'Iptc4xmpExt:PersonInImage': MD_MultiString, - 'Iptc4xmpExt:OrganisationInImageName': MD_MultiString, - 'mwg-rs:BarCodeValue': MD_String, 'mwg-rs:Description': MD_String, 'mwg-rs:Name': MD_String, - 'mwg-rs:Title': MD_String, - 'photoshop:CaptionWriter': MD_String, - 'dc:creator': MD_MultiString, 'dc:description': MD_LangAlt, - 'dc:subject': MD_MultiString, - 'xmpRights:UsageTerms': MD_LangAlt, } def merge(self, info, tag, other): From 85ed44536f25eade43f65484de669fbad49967f3 Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Sun, 22 Dec 2024 16:59:29 +0000 Subject: [PATCH 08/11] Simplify searching of controlled vocabs --- src/photini/regions.py | 6 +++--- src/photini/types.py | 49 ++++++++++++++++++------------------------ src/photini/vocab.py | 30 +++++++------------------- 3 files changed, 32 insertions(+), 53 deletions(-) diff --git a/src/photini/regions.py b/src/photini/regions.py index c71fac54..7de86141 100644 --- a/src/photini/regions.py +++ b/src/photini/regions.py @@ -589,14 +589,14 @@ def draw_boundaries(self, idx, regions): active = n == idx boundary = region['Iptc4xmpExt:RegionBoundary'] if boundary['Iptc4xmpExt:rbShape'] == 'rectangle': - if region.has_role('squareCropping'): + if region.has_role('square format cropping'): constraint = 'square' - elif region.has_role('landscapeCropping'): + elif region.has_role('landscape format cropping'): if self.transform().isRotating(): constraint = 'portrait' else: constraint = 'landscape' - elif region.has_role('portraitCropping'): + elif region.has_role('portrait format cropping'): if self.transform().isRotating(): constraint = 'landscape' else: diff --git a/src/photini/types.py b/src/photini/types.py index 7bad9bd9..ffa647c2 100644 --- a/src/photini/types.py +++ b/src/photini/types.py @@ -1729,6 +1729,9 @@ def __eq__(self, other): class EntityConceptArray(MD_StructArray): item_type = EntityConcept + def has_name(self, name): + return any(item['Iptc4xmpExt:Name']['en-GB'] == name for item in self) + class RegionBoundaryNumber(MD_Float): def set_decimals(self, decimals): @@ -1903,8 +1906,7 @@ def merge(self, info, tag, other): value = result[old_key] del result[old_key] if old_key == 'mwg-rs:Name' and ( - result.has_type('human') - or result.has_type('', uid='mwg-rs:Type Face')): + result.has_type('human') or result.has_type('Face')): new_key = 'Iptc4xmpExt:PersonInImage' value = [value] if result[new_key]: @@ -2098,26 +2100,15 @@ def from_Exif(cls, file_value): region['Iptc4xmpExt:rbUnit'] = 'pixel' return cls({ 'Iptc4xmpExt:RegionBoundary': region, - 'Iptc4xmpExt:rRole': [IPTCRoleCV.data_for_qcode('mainSubjectArea')], + 'Iptc4xmpExt:rRole': [ + IPTCRoleCV.data_for_name('main subject area')], }) - def has_uid(self, key, uid): - if key not in self: - return False - for item in self[key]: - if 'xmp:Identifier' in item and uid in item['xmp:Identifier']: - return True - return False - - def has_type(self, qcode, uid=None): - if not uid: - data = IPTCTypeCV.data_for_qcode(qcode) - uid = data['xmp:Identifier'][0] - return self.has_uid('Iptc4xmpExt:rCtype', uid) + def has_type(self, name): + return self['Iptc4xmpExt:rCtype'].has_name(name) - def has_role(self, qcode): - data = IPTCRoleCV.data_for_qcode(qcode) - return self.has_uid('Iptc4xmpExt:rRole', data['xmp:Identifier'][0]) + def has_role(self, name): + return self['Iptc4xmpExt:rRole'].has_name(name) def to_Qt(self, image): return self['Iptc4xmpExt:RegionBoundary'].to_Qt(image) @@ -2141,10 +2132,10 @@ class RegionList(MD_StructArray): item_type = ImageRegionItem def find(self, other): - if other.has_role('mainSubjectArea'): + if other.has_role('main subject area'): # only one main subject area region allowed for n, value in enumerate(self): - if value.has_role('mainSubjectArea'): + if value.has_role('main subject area'): return n return len(self) for n, value in enumerate(self): @@ -2302,12 +2293,12 @@ def from_notes(self, notes, image, target_size): continue region = { 'Iptc4xmpExt:RegionBoundary': boundary, - 'Iptc4xmpExt:rRole': [IPTCRoleCV.data_for_qcode('subjectArea')], + 'Iptc4xmpExt:rRole': [IPTCRoleCV.data_for_name('subject area')], } if note['is_person']: region['Iptc4xmpExt:PersonInImage'] = [note['content']] region['Iptc4xmpExt:rCtype'] = [ - IPTCTypeCV.data_for_qcode('human')] + IPTCTypeCV.data_for_name('human')] else: region['dc:description'] = {'x-default': note['content']} if note['authorrealname']: @@ -2346,7 +2337,7 @@ def to_notes(self, image, target_size): region['Iptc4xmpExt:PersonInImage']) note['is_person'] = True elif not any(region.has_role(x) for x in ( - 'subjectArea', 'mainSubjectArea', 'areaOfInterest')): + 'subject area', 'main subject area', 'area of interest')): continue if 'dc:description' in region and not note['content']: note['content'] = MD_LangAlt( @@ -2367,11 +2358,13 @@ def get_focus(self, image): if transform and transform.isRotating(): portrait_format = not portrait_format if portrait_format: - roles = ('landscapeCropping', 'squareCropping', 'recomCropping', - 'cropping', 'portraitCropping') + roles = ('landscape format cropping', 'square format cropping', + 'recommended cropping', 'cropping', + 'portrait format cropping') else: - roles = ('squareCropping', 'portraitCropping', 'landscapeCropping', - 'recomCropping', 'cropping') + roles = ('square format cropping', 'portrait format cropping', + 'landscape format cropping', 'recommended cropping', + 'cropping') for role in roles: for region in self: if not region.has_role(role): diff --git a/src/photini/vocab.py b/src/photini/vocab.py index 9a144f36..835a9c50 100644 --- a/src/photini/vocab.py +++ b/src/photini/vocab.py @@ -24,17 +24,10 @@ class IPTCBaseCV(object): @classmethod - def init_qcode_map(cls, prefix): - prefix += ':' - cls.qcode_map = {} + def data_for_name(cls, name): for item in cls.vocab: - qcode = item['qcode'].replace(prefix, '') - cls.qcode_map[qcode] = item['data'] - - @classmethod - def data_for_qcode(cls, qcode): - if qcode in cls.qcode_map: - return cls.qcode_map[qcode] + if item['data']['Iptc4xmpExt:Name']['en-GB'] == name: + return item['data'] return {} @@ -46,11 +39,7 @@ class IPTCTypeCV(IPTCBaseCV): vocab = image_region_types -IPTCRoleCV.init_qcode_map('imgregrole') -IPTCTypeCV.init_qcode_map('imgregtype') - - -class MWGTypeCV(object): +class MWGTypeCV(IPTCBaseCV): vocab = ( {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Face'}, 'xmp:Identifier': ('mwg-rs:Type Face',)}, @@ -115,14 +104,11 @@ def to_file_data(cls, ctype_data): return dict(x.split() for x in item['data']['xmp:Identifier']) return {} - @staticmethod - def from_file_data(data): + @classmethod + def from_file_data(cls, data): if 'mwg-rs:Type' not in data: return {} name = data['mwg-rs:Type'] - identifier = ['mwg-rs:Type ' + name] if 'mwg-rs:FocusUsage' in data: - focus_usage = data['mwg-rs:FocusUsage'] - name += '/' + focus_usage - identifier.append('mwg-rs:FocusUsage ' + focus_usage) - return {'Iptc4xmpExt:Name': name, 'xmp:Identifier': tuple(identifier)} + name += '/' + data['mwg-rs:FocusUsage'] + return cls.data_for_name(name) From 08ca5789a16ed5762173e5077bbd1cd4fd035b07 Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Mon, 23 Dec 2024 09:44:58 +0000 Subject: [PATCH 09/11] Store controlled vocab in a dict --- src/photini/cv.py | 334 +++++++++++++++++++---------------------- src/photini/regions.py | 16 +- src/photini/types.py | 38 ++--- src/photini/vocab.py | 120 ++++++++------- utils/download_cvs.py | 35 ++--- 5 files changed, 262 insertions(+), 281 deletions(-) diff --git a/src/photini/cv.py b/src/photini/cv.py index 2e3364b7..5320090d 100644 --- a/src/photini/cv.py +++ b/src/photini/cv.py @@ -5,188 +5,160 @@ # Date: 2019-12-06T12:00:00+00:00 # Licence: http://creativecommons.org/licenses/by/4.0/ # http://cv.iptc.org/newscodes/imageregiontype/ -image_region_types = \ -({'data': {'Iptc4xmpExt:Name': {'en-GB': 'animal'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/animal',)}, - 'definition': {'en-GB': 'A living organism different from humans or flora'}, - 'name': {'en-GB': 'animal'}, - 'note': {}, - 'qcode': 'imgregtype:animal'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'artwork'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/artwork',)}, - 'definition': {'en-GB': 'Artistic work'}, - 'name': {'en-GB': 'artwork'}, - 'note': {}, - 'qcode': 'imgregtype:artwork'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'dividing line'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/dividingLine',)}, - 'definition': {'en-GB': 'A line expressing a visual division of the image, ' - 'such as a horizon'}, - 'name': {'en-GB': 'dividing line'}, - 'note': {}, - 'qcode': 'imgregtype:dividingLine'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'plant'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/plant',)}, - 'definition': {'en-GB': 'A living organism different from humans and ' - 'animals'}, - 'name': {'en-GB': 'plant'}, - 'note': {}, - 'qcode': 'imgregtype:plant'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'geographic area'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/geoArea',)}, - 'definition': {'en-GB': 'A named area on the surface of the planet earth'}, - 'name': {'en-GB': 'geographic area'}, - 'note': {'en-GB': 'Specific details of the area can be expressed by other ' - 'metadata'}, - 'qcode': 'imgregtype:geoArea'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'graphic'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/graphic',)}, - 'definition': {'en-GB': 'A graphic representation of information'}, - 'name': {'en-GB': 'graphic'}, - 'note': {}, - 'qcode': 'imgregtype:graphic'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'machine-readable code'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/machineCode',)}, - 'definition': {'en-GB': 'Optical label such as barcode or QR code'}, - 'name': {'en-GB': 'machine-readable code'}, - 'note': {}, - 'qcode': 'imgregtype:machineCode'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'human'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/human',)}, - 'definition': {'en-GB': 'A human being'}, - 'name': {'en-GB': 'human'}, - 'note': {}, - 'qcode': 'imgregtype:human'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'product'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/product',)}, - 'definition': {'en-GB': 'A thing that was produced and can be handed over'}, - 'name': {'en-GB': 'product'}, - 'note': {}, - 'qcode': 'imgregtype:product'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'text'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/text',)}, - 'definition': {'en-GB': 'Human readable script of any language'}, - 'name': {'en-GB': 'text'}, - 'note': {}, - 'qcode': 'imgregtype:text'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'building'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/building',)}, - 'definition': {'en-GB': 'A structure with walls and roof in most cases'}, - 'name': {'en-GB': 'building'}, - 'note': {}, - 'qcode': 'imgregtype:building'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'vehicle'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/vehicle',)}, - 'definition': {'en-GB': 'An object used for transporting something, like ' - 'car, train, ship, plane or bike'}, - 'name': {'en-GB': 'vehicle'}, - 'note': {}, - 'qcode': 'imgregtype:vehicle'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'food'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/food',)}, - 'definition': {'en-GB': 'Substances providing nutrition for a living body'}, - 'name': {'en-GB': 'food'}, - 'note': {}, - 'qcode': 'imgregtype:food'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'clothing'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/clothing',)}, - 'definition': {'en-GB': 'Something worn to cover the body'}, - 'name': {'en-GB': 'clothing'}, - 'note': {}, - 'qcode': 'imgregtype:clothing'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'rock formation'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/rockFormation',)}, - 'definition': {'en-GB': 'A special formation of stone mass'}, - 'name': {'en-GB': 'rock formation'}, - 'note': {}, - 'qcode': 'imgregtype:rockFormation'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'body of water'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/bodyOfWater',)}, - 'definition': {'en-GB': 'A significant accumulation of water'}, - 'name': {'en-GB': 'body of water'}, - 'note': {'en-GB': 'Including a waterfall, a geyser and other phenomena of ' - 'water'}, - 'qcode': 'imgregtype:bodyOfWater'}) - +image_region_types = { +'animal': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'animal'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/animal',)}, + 'definition': {'en-GB': 'A living organism different from humans or flora'}, + 'note': {}}, +'artwork': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'artwork'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/artwork',)}, + 'definition': {'en-GB': 'Artistic work'}, + 'note': {}}, +'dividingLine': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'dividing line'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/dividingLine',)}, + 'definition': {'en-GB': 'A line expressing a visual division of the image, ' + 'such as a horizon'}, + 'note': {}}, +'plant': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'plant'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/plant',)}, + 'definition': {'en-GB': 'A living organism different from humans and animals'}, + 'note': {}}, +'geoArea': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'geographic area'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/geoArea',)}, + 'definition': {'en-GB': 'A named area on the surface of the planet earth'}, + 'note': {'en-GB': 'Specific details of the area can be expressed by other ' + 'metadata'}}, +'graphic': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'graphic'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/graphic',)}, + 'definition': {'en-GB': 'A graphic representation of information'}, + 'note': {}}, +'machineCode': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'machine-readable code'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/machineCode',)}, + 'definition': {'en-GB': 'Optical label such as barcode or QR code'}, + 'note': {}}, +'human': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'human'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/human',)}, + 'definition': {'en-GB': 'A human being'}, + 'note': {}}, +'product': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'product'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/product',)}, + 'definition': {'en-GB': 'A thing that was produced and can be handed over'}, + 'note': {}}, +'text': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'text'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/text',)}, + 'definition': {'en-GB': 'Human readable script of any language'}, + 'note': {}}, +'building': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'building'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/building',)}, + 'definition': {'en-GB': 'A structure with walls and roof in most cases'}, + 'note': {}}, +'vehicle': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'vehicle'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/vehicle',)}, + 'definition': {'en-GB': 'An object used for transporting something, like car, ' + 'train, ship, plane or bike'}, + 'note': {}}, +'food': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'food'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/food',)}, + 'definition': {'en-GB': 'Substances providing nutrition for a living body'}, + 'note': {}}, +'clothing': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'clothing'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/clothing',)}, + 'definition': {'en-GB': 'Something worn to cover the body'}, + 'note': {}}, +'rockFormation': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'rock formation'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/rockFormation',)}, + 'definition': {'en-GB': 'A special formation of stone mass'}, + 'note': {}}, +'bodyOfWater': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'body of water'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregiontype/bodyOfWater',)}, + 'definition': {'en-GB': 'A significant accumulation of water'}, + 'note': {'en-GB': 'Including a waterfall, a geyser and other phenomena of ' + 'water'}}, +} # ©IPTC, International Press Telecommunications Council - https://iptc.org # Date: 2019-12-06T12:00:00+00:00 # Licence: http://creativecommons.org/licenses/by/4.0/ # http://cv.iptc.org/newscodes/imageregionrole/ -image_region_roles = \ -({'data': {'Iptc4xmpExt:Name': {'en-GB': 'cropping'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/cropping',)}, - 'definition': {'en-GB': 'Image region can be used for any cropping'}, - 'name': {'en-GB': 'cropping'}, - 'note': {}, - 'qcode': 'imgregrole:cropping'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'recommended cropping'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/recomCropping',)}, - 'definition': {'en-GB': 'Image region is recommended for cropping'}, - 'name': {'en-GB': 'recommended cropping'}, - 'note': {}, - 'qcode': 'imgregrole:recomCropping'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'landscape format cropping'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/landscapeCropping',)}, - 'definition': {'en-GB': 'Image region suggested for cropping in landscape ' - 'format'}, - 'name': {'en-GB': 'landscape format cropping'}, - 'note': {'en-GB': 'Use for images of non-landscape format'}, - 'qcode': 'imgregrole:landscapeCropping'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'portrait format cropping'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/portraitCropping',)}, - 'definition': {'en-GB': 'Image region suggested for cropping in portrait ' - 'format'}, - 'name': {'en-GB': 'portrait format cropping'}, - 'note': {'en-GB': 'Use for images of non-portrait format'}, - 'qcode': 'imgregrole:portraitCropping'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'square format cropping'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/squareCropping',)}, - 'definition': {'en-GB': 'Image region suggested for cropping in square ' - 'format'}, - 'name': {'en-GB': 'square format cropping'}, - 'note': {}, - 'qcode': 'imgregrole:squareCropping'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'composite image item'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/compositeImageItem',)}, - 'definition': {'en-GB': 'Image region of an item in a composite image'}, - 'name': {'en-GB': 'composite image item'}, - 'note': {}, - 'qcode': 'imgregrole:compositeImageItem'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'copyright region'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/copyrightRegion',)}, - 'definition': {'en-GB': 'Image region with a copyright different from the ' - 'copyright of the whole picture'}, - 'name': {'en-GB': 'copyright region'}, - 'note': {}, - 'qcode': 'imgregrole:copyrightRegion'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'subject area'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/subjectArea',)}, - 'definition': {'en-GB': 'Image region contains a subject in the overall ' - 'scene.'}, - 'name': {'en-GB': 'subject area'}, - 'note': {'en-GB': 'Multiple regions of an image may be set as subject area.'}, - 'qcode': 'imgregrole:subjectArea'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'main subject area'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/mainSubjectArea',)}, - 'definition': {'en-GB': 'Image region contains the main subject in the ' - 'overall scene. Same as the Exif SubjectArea.'}, - 'name': {'en-GB': 'main subject area'}, - 'note': {'en-GB': 'Only a single region of an image may be set as main ' - 'subject area.'}, - 'qcode': 'imgregrole:mainSubjectArea'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'area of interest'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/areaOfInterest',)}, - 'definition': {'en-GB': 'Image region contains a thing of special interest ' - 'to the viewer'}, - 'name': {'en-GB': 'area of interest'}, - 'note': {}, - 'qcode': 'imgregrole:areaOfInterest'}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'business use'}, - 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/businessUse',)}, - 'definition': {'en-GB': 'Image region is dedicated to a specific business ' - 'use'}, - 'name': {'en-GB': 'business use'}, - 'note': {'en-GB': 'In addition a more granular role could be expressed by a ' - 'term of a role CV of this business.'}, - 'qcode': 'imgregrole:businessUse'}) - +image_region_roles = { +'cropping': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'cropping'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/cropping',)}, + 'definition': {'en-GB': 'Image region can be used for any cropping'}, + 'note': {}}, +'recomCropping': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'recommended cropping'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/recomCropping',)}, + 'definition': {'en-GB': 'Image region is recommended for cropping'}, + 'note': {}}, +'landscapeCropping': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'landscape format cropping'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/landscapeCropping',)}, + 'definition': {'en-GB': 'Image region suggested for cropping in landscape ' + 'format'}, + 'note': {'en-GB': 'Use for images of non-landscape format'}}, +'portraitCropping': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'portrait format cropping'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/portraitCropping',)}, + 'definition': {'en-GB': 'Image region suggested for cropping in portrait ' + 'format'}, + 'note': {'en-GB': 'Use for images of non-portrait format'}}, +'squareCropping': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'square format cropping'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/squareCropping',)}, + 'definition': {'en-GB': 'Image region suggested for cropping in square ' + 'format'}, + 'note': {}}, +'compositeImageItem': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'composite image item'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/compositeImageItem',)}, + 'definition': {'en-GB': 'Image region of an item in a composite image'}, + 'note': {}}, +'copyrightRegion': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'copyright region'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/copyrightRegion',)}, + 'definition': {'en-GB': 'Image region with a copyright different from the ' + 'copyright of the whole picture'}, + 'note': {}}, +'subjectArea': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'subject area'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/subjectArea',)}, + 'definition': {'en-GB': 'Image region contains a subject in the overall ' + 'scene.'}, + 'note': {'en-GB': 'Multiple regions of an image may be set as subject area.'}}, +'mainSubjectArea': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'main subject area'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/mainSubjectArea',)}, + 'definition': {'en-GB': 'Image region contains the main subject in the ' + 'overall scene. Same as the Exif SubjectArea.'}, + 'note': {'en-GB': 'Only a single region of an image may be set as main ' + 'subject area.'}}, +'areaOfInterest': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'area of interest'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/areaOfInterest',)}, + 'definition': {'en-GB': 'Image region contains a thing of special interest to ' + 'the viewer'}, + 'note': {}}, +'businessUse': +{'data': {'Iptc4xmpExt:Name': {'en-GB': 'business use'}, + 'xmp:Identifier': ('http://cv.iptc.org/newscodes/imageregionrole/businessUse',)}, + 'definition': {'en-GB': 'Image region is dedicated to a specific business ' + 'use'}, + 'note': {'en-GB': 'In addition a more granular role could be expressed by a ' + 'term of a role CV of this business.'}}, +} diff --git a/src/photini/regions.py b/src/photini/regions.py index 7de86141..7c0ec291 100644 --- a/src/photini/regions.py +++ b/src/photini/regions.py @@ -589,14 +589,14 @@ def draw_boundaries(self, idx, regions): active = n == idx boundary = region['Iptc4xmpExt:RegionBoundary'] if boundary['Iptc4xmpExt:rbShape'] == 'rectangle': - if region.has_role('square format cropping'): + if region.has_role('squareCropping'): constraint = 'square' - elif region.has_role('landscape format cropping'): + elif region.has_role('landscapeCropping'): if self.transform().isRotating(): constraint = 'portrait' else: constraint = 'landscape' - elif region.has_role('portrait format cropping'): + elif region.has_role('portraitCropping'): if self.transform().isRotating(): constraint = 'landscape' else: @@ -668,7 +668,7 @@ def add_menu_items(self, items, add_separator=True, exclusive=False): group = QtWidgets.QActionGroup(self) group.setExclusionPolicy(group.ExclusionPolicy.ExclusiveOptional) for item in items: - label = MD_LangAlt(item['name']).best_match() + label = MD_LangAlt(item['data']['Iptc4xmpExt:Name']).best_match() tip = MD_LangAlt(item['definition']).best_match() if item['note']: tip += ' ({})'.format(MD_LangAlt(item['note']).best_match()) @@ -716,7 +716,6 @@ def set_value(self, value): self.add_menu_items([{ 'data': item, 'definition': None, - 'name': item['Iptc4xmpExt:Name'], 'note': None}], add_separator=False) self._updating = False self.update_display() @@ -817,7 +816,7 @@ def __init__(self, idx, *arg, **kw): translate('RegionsTab', 'Boundary unit'), self.widgets[key]) # roles key = 'Iptc4xmpExt:rRole' - self.widgets[key] = EntityConceptWidget(key, IPTCRoleCV.vocab) + self.widgets[key] = EntityConceptWidget(key, IPTCRoleCV.vocab.values()) self.widgets[key].setToolTip('

{}

'.format(translate( 'RegionsTab', 'Role of this region among all regions of this image' ' or of other images. The value SHOULD be taken from a Controlled' @@ -826,8 +825,9 @@ def __init__(self, idx, *arg, **kw): layout.addRow(translate('RegionsTab', 'Role'), self.widgets[key]) # content types key = 'Iptc4xmpExt:rCtype' - self.widgets[key] = EntityConceptWidget(key, IPTCTypeCV.vocab) - self.widgets[key].add_menu_items(MWGTypeCV.vocab, exclusive=True) + self.widgets[key] = EntityConceptWidget(key, IPTCTypeCV.vocab.values()) + self.widgets[key].add_menu_items( + MWGTypeCV.vocab.values(), exclusive=True) self.widgets[key].setToolTip('

{}

'.format(translate( 'RegionsTab', 'The semantic type of what is shown inside the' ' region. The value SHOULD be taken from a Controlled' diff --git a/src/photini/types.py b/src/photini/types.py index ffa647c2..f54f33ff 100644 --- a/src/photini/types.py +++ b/src/photini/types.py @@ -1729,8 +1729,8 @@ def __eq__(self, other): class EntityConceptArray(MD_StructArray): item_type = EntityConcept - def has_name(self, name): - return any(item['Iptc4xmpExt:Name']['en-GB'] == name for item in self) + def has_data(self, data): + return data in self class RegionBoundaryNumber(MD_Float): @@ -2100,15 +2100,19 @@ def from_Exif(cls, file_value): region['Iptc4xmpExt:rbUnit'] = 'pixel' return cls({ 'Iptc4xmpExt:RegionBoundary': region, - 'Iptc4xmpExt:rRole': [ - IPTCRoleCV.data_for_name('main subject area')], + 'Iptc4xmpExt:rRole': [IPTCRoleCV.data_for_name('mainSubjectArea')], }) - def has_type(self, name): - return self['Iptc4xmpExt:rCtype'].has_name(name) + def has_type(self, label): + if label in MWGTypeCV.vocab: + data = MWGTypeCV.vocab[label]['data'] + else: + data = IPTCTypeCV.vocab[label]['data'] + return self['Iptc4xmpExt:rCtype'].has_data(data) - def has_role(self, name): - return self['Iptc4xmpExt:rRole'].has_name(name) + def has_role(self, label): + return self['Iptc4xmpExt:rRole'].has_data( + IPTCRoleCV.vocab[label]['data']) def to_Qt(self, image): return self['Iptc4xmpExt:RegionBoundary'].to_Qt(image) @@ -2132,10 +2136,10 @@ class RegionList(MD_StructArray): item_type = ImageRegionItem def find(self, other): - if other.has_role('main subject area'): + if other.has_role('mainSubjectArea'): # only one main subject area region allowed for n, value in enumerate(self): - if value.has_role('main subject area'): + if value.has_role('mainSubjectArea'): return n return len(self) for n, value in enumerate(self): @@ -2293,7 +2297,7 @@ def from_notes(self, notes, image, target_size): continue region = { 'Iptc4xmpExt:RegionBoundary': boundary, - 'Iptc4xmpExt:rRole': [IPTCRoleCV.data_for_name('subject area')], + 'Iptc4xmpExt:rRole': [IPTCRoleCV.data_for_name('subjectArea')], } if note['is_person']: region['Iptc4xmpExt:PersonInImage'] = [note['content']] @@ -2337,7 +2341,7 @@ def to_notes(self, image, target_size): region['Iptc4xmpExt:PersonInImage']) note['is_person'] = True elif not any(region.has_role(x) for x in ( - 'subject area', 'main subject area', 'area of interest')): + 'subjectArea', 'mainSubjectArea', 'areaOfInterest')): continue if 'dc:description' in region and not note['content']: note['content'] = MD_LangAlt( @@ -2358,13 +2362,11 @@ def get_focus(self, image): if transform and transform.isRotating(): portrait_format = not portrait_format if portrait_format: - roles = ('landscape format cropping', 'square format cropping', - 'recommended cropping', 'cropping', - 'portrait format cropping') + roles = ('landscapeCropping', 'squareCropping', 'recomCropping', + 'cropping', 'portraitCropping') else: - roles = ('square format cropping', 'portrait format cropping', - 'landscape format cropping', 'recommended cropping', - 'cropping') + roles = ('squareCropping', 'portraitCropping', 'landscapeCropping', + 'recomCropping', 'cropping') for role in roles: for region in self: if not region.has_role(role): diff --git a/src/photini/vocab.py b/src/photini/vocab.py index 835a9c50..8ce39e38 100644 --- a/src/photini/vocab.py +++ b/src/photini/vocab.py @@ -25,10 +25,7 @@ class IPTCBaseCV(object): @classmethod def data_for_name(cls, name): - for item in cls.vocab: - if item['data']['Iptc4xmpExt:Name']['en-GB'] == name: - return item['data'] - return {} + return cls.vocab[name]['data'] class IPTCRoleCV(IPTCBaseCV): @@ -40,58 +37,65 @@ class IPTCTypeCV(IPTCBaseCV): class MWGTypeCV(IPTCBaseCV): - vocab = ( - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Face'}, - 'xmp:Identifier': ('mwg-rs:Type Face',)}, - 'definition': {'en-GB': "Region area for people's faces."}, - 'name': {'en-GB': 'Face'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Pet'}, - 'xmp:Identifier': ('mwg-rs:Type Pet',)}, - 'definition': {'en-GB': "Region area for pets."}, - 'name': {'en-GB': 'Pet'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/EvaluatedUsed'}, - 'xmp:Identifier': ('mwg-rs:Type Focus', - 'mwg-rs:FocusUsage EvaluatedUsed')}, - 'definition': {'en-GB': "Region area for camera auto-focus regions." - "
EvaluatedUsed specifies that the focus point was" - " considered during focusing and was used in the final" - " image."}, - 'name': {'en-GB': 'Focus (EvaluatedUsed)'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/EvaluatedNotUsed'}, - 'xmp:Identifier': ('mwg-rs:Type Focus', - 'mwg-rs:FocusUsage EvaluatedNotUsed')}, - 'definition': {'en-GB': "Region area for camera auto-focus regions." - "
EvaluatedNotUsed specifies that the focus point" - " was considered during focusing but not utilised in" - " the final image."}, - 'name': {'en-GB': 'Focus (EvaluatedNotUsed)'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus/NotEvaluatedNotUsed'}, - 'xmp:Identifier': ('mwg-rs:Type Focus' - 'mwg-rs:FocusUsage NotEvaluatedNotUsed')}, - 'definition': {'en-GB': "Region area for camera auto-focus regions." - "
NotEvaluatedNotUsed specifies that a focus point" - " was not evaluated and not used, e.g. a fixed focus" - " point on the camera which was not used in any" - " fashion."}, - 'name': {'en-GB': 'Focus (NotEvaluatedNotUsed)'}, - 'note': None}, - {'data': {'Iptc4xmpExt:Name': {'en-GB': 'BarCode'}, - 'xmp:Identifier': ('mwg-rs:Type BarCode',)}, - 'definition': {'en-GB': "One dimensional linear or two dimensional" - " matrix optical code."}, - 'name': {'en-GB': 'BarCode'}, - 'note': None}, - ) + vocab = { + 'Face': { + 'data': {'Iptc4xmpExt:Name': {'en-GB': 'Face'}, + 'xmp:Identifier': ('Face',)}, + 'file_data': {'mwg-rs:Type': 'Face'}, + 'definition': {'en-GB': "Region area for people's faces."}, + 'note': None}, + 'Pet': { + 'data': {'Iptc4xmpExt:Name': {'en-GB': 'Pet'}, + 'xmp:Identifier': ('Pet',)}, + 'file_data': {'mwg-rs:Type': 'Pet'}, + 'definition': {'en-GB': "Region area for pets."}, + 'note': None}, + 'FocusEvaluatedUsed': { + 'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus (EvaluatedUsed)'}, + 'xmp:Identifier': ('FocusEvaluatedUsed',)}, + 'file_data': {'mwg-rs:Type': 'Focus', + 'mwg-rs:FocusUsage': 'EvaluatedUsed'}, + 'definition': {'en-GB': "Region area for camera auto-focus regions." + "
EvaluatedUsed specifies that the focus point" + " was considered during focusing and was used in the" + " final image."}, + 'note': None}, + 'FocusEvaluatedNotUsed': { + 'data': {'Iptc4xmpExt:Name': {'en-GB': 'Focus (EvaluatedNotUsed)'}, + 'xmp:Identifier': ('FocusEvaluatedNotUsed',)}, + 'file_data': {'mwg-rs:Type': 'Focus', + 'mwg-rs:FocusUsage': 'EvaluatedNotUsed'}, + 'definition': {'en-GB': "Region area for camera auto-focus regions." + "
EvaluatedNotUsed specifies that the focus" + " point was considered during focusing but not" + " utilised in the final image."}, + 'note': None}, + 'FocusNotEvaluatedNotUsed': { + 'data': { + 'Iptc4xmpExt:Name': {'en-GB': 'Focus (NotEvaluatedNotUsed)'}, + 'xmp:Identifier': ('FocusNotEvaluatedNotUsed',)}, + 'file_data': {'mwg-rs:Type': 'Focus', + 'mwg-rs:FocusUsage': 'NotEvaluatedNotUsed'}, + 'definition': {'en-GB': "Region area for camera auto-focus regions." + "
NotEvaluatedNotUsed specifies that a focus" + " point was not evaluated and not used, e.g. a fixed" + " focus point on the camera which was not used in" + " any fashion."}, + 'note': None}, + 'BarCode': { + 'data': {'Iptc4xmpExt:Name': {'en-GB': 'BarCode'}, + 'xmp:Identifier': ('BarCode',)}, + 'file_data': {'mwg-rs:Type': 'BarCode'}, + 'definition': {'en-GB': "One dimensional linear or two dimensional" + " matrix optical code."}, + 'note': None}, + } @classmethod def clean_file_data(cls, ctype_data): # remove any MWG ctype from a list of ctypes result = list(ctype_data) - for item in cls.vocab: + for item in cls.vocab.values(): if item['data'] in result: result.remove(item['data']) break @@ -99,16 +103,22 @@ def clean_file_data(cls, ctype_data): @classmethod def to_file_data(cls, ctype_data): - for item in cls.vocab: + for item in cls.vocab.values(): if item['data'] in ctype_data: - return dict(x.split() for x in item['data']['xmp:Identifier']) + return item['file_data'] return {} @classmethod def from_file_data(cls, data): if 'mwg-rs:Type' not in data: return {} + label = data['mwg-rs:Type'] + if 'mwg-rs:FocusUsage' in data: + label += data['mwg-rs:FocusUsage'] + if label in cls.vocab: + return cls.vocab[label]['data'] name = data['mwg-rs:Type'] if 'mwg-rs:FocusUsage' in data: - name += '/' + data['mwg-rs:FocusUsage'] - return cls.data_for_name(name) + name = '{} ({})'.format(name, data['mwg-rs:FocusUsage']) + return {'Iptc4xmpExt:Name': {'en-GB': name}, + 'xmp:Identifier': (label,)} diff --git a/utils/download_cvs.py b/utils/download_cvs.py index db116b73..af25cc27 100644 --- a/utils/download_cvs.py +++ b/utils/download_cvs.py @@ -17,7 +17,7 @@ # . import os -from pprint import pprint +import pprint import sys import requests @@ -44,31 +44,28 @@ def main(argv=None): rsp = session.get(url, params=params) rsp.raise_for_status() rsp = rsp.json() - pprint(rsp) + pprint.pprint(rsp) py.write('''# ©{copyrightHolder} # Date: {dateReleased} # Licence: {licenceLink} # {uri} '''.format(**rsp)) - uris = [] - data = {} + py.write(data_name) + py.write(' = {\n') for concept in rsp['conceptSet']: - uri = concept['uri'] - if uri not in uris: - uris.append(uri) - data[uri] = {'name': {}, 'definition': {}, 'note': {}} - data[uri]['name'].update(concept['prefLabel']) - data[uri]['definition'].update(concept['definition']) + key = concept['qcode'].split(':')[1] + value = { + 'data': { + 'Iptc4xmpExt:Name': concept['prefLabel'], + 'xmp:Identifier': (concept['uri'],), + }, + 'definition': concept['definition'], + 'note': {}, + } if 'note' in concept: - data[uri]['note'].update(concept['note']) - data[uri]['qcode'] = concept['qcode'] - data[uri]['data'] = { - 'xmp:Identifier': (concept['uri'],), - 'Iptc4xmpExt:Name': concept['prefLabel']} - py.write(data_name) - py.write(' = \\\n') - pprint(tuple(data[x] for x in uris), stream=py) - py.write('\n') + value['note'].update(concept['note']) + py.write("'{}':\n{},\n".format(key, pprint.pformat(value))) + py.write('}\n') return 0 From e732c028aebb397e0a498fdc2ab723bcc179ffee Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Mon, 23 Dec 2024 11:26:50 +0000 Subject: [PATCH 10/11] Moved 'focus' computation to Pixelfed tab Pixelfed is the only thing that uses it and the 'types' module is way too large. --- src/photini/pixelfed.py | 34 +++++++++++++++++++++++++++++++++- src/photini/types.py | 30 ------------------------------ 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/photini/pixelfed.py b/src/photini/pixelfed.py index df6e498e..8fcf5a9a 100644 --- a/src/photini/pixelfed.py +++ b/src/photini/pixelfed.py @@ -720,6 +720,38 @@ def alt_text(self, image): return '\n\n'.join(description) return '' + def focus(self, image): + md = image.metadata + if not md.image_region: + return None + dims = md.dimensions + portrait_format = dims['height'] > dims['width'] + transform = md.orientation and md.orientation.get_transform() + if transform and transform.isRotating(): + portrait_format = not portrait_format + if portrait_format: + roles = ('landscapeCropping', 'squareCropping', 'recomCropping', + 'cropping', 'portraitCropping') + else: + roles = ('squareCropping', 'portraitCropping', 'landscapeCropping', + 'recomCropping', 'cropping') + for role in roles: + for region in md.image_region: + if not region.has_role(role): + continue + points = region.to_Qt(image) + boundary = region['Iptc4xmpExt:RegionBoundary'] + if boundary['Iptc4xmpExt:rbShape'] == 'rectangle': + centre = (points.at(0) + points.at(1)) / 2.0 + elif boundary['Iptc4xmpExt:rbShape'] == 'circle': + centre = points.at(0) + else: + centre = points.boundingRect().center() + if transform: + centre = transform.map(centre) + return (centre.x() * 2.0) - 1.0, 1.0 - (centre.y() * 2.0) + return None + def get_upload_params(self, image, state): if 'ask_alt_text' not in state: state['ask_alt_text'] = self.user_widget.compose_settings[ @@ -748,7 +780,7 @@ def get_upload_params(self, image, state): state['ask_alt_text'] = False elif result == dialog.StandardButton.Abort: return 'abort' - focus = image.metadata.image_region.get_focus(image) + focus = self.focus(image) if focus: params['media']['focus'] = '{:f},{:f}'.format(*focus) params['license'] = self.widget['license'].get_value() diff --git a/src/photini/types.py b/src/photini/types.py index f54f33ff..9b6dc4ca 100644 --- a/src/photini/types.py +++ b/src/photini/types.py @@ -2353,33 +2353,3 @@ def to_notes(self, image, target_size): continue result.append(note) return result - - def get_focus(self, image): - image_dims = image.metadata.dimensions - portrait_format = image_dims['height'] > image_dims['width'] - transform = (image.metadata.orientation - and image.metadata.orientation.get_transform()) - if transform and transform.isRotating(): - portrait_format = not portrait_format - if portrait_format: - roles = ('landscapeCropping', 'squareCropping', 'recomCropping', - 'cropping', 'portraitCropping') - else: - roles = ('squareCropping', 'portraitCropping', 'landscapeCropping', - 'recomCropping', 'cropping') - for role in roles: - for region in self: - if not region.has_role(role): - continue - points = region.to_Qt(image) - boundary = region['Iptc4xmpExt:RegionBoundary'] - if boundary['Iptc4xmpExt:rbShape'] == 'rectangle': - centre = (points.at(0) + points.at(1)) / 2.0 - elif boundary['Iptc4xmpExt:rbShape'] == 'circle': - centre = points.at(0) - else: - centre = points.boundingRect().center() - if transform: - centre = transform.map(centre) - return (centre.x() * 2.0) - 1.0, 1.0 - (centre.y() * 2.0) - return None From 6e029730e9b1ae79b1e48d274795d7726093e70d Mon Sep 17 00:00:00 2001 From: Jim Easterbrook Date: Mon, 23 Dec 2024 12:53:32 +0000 Subject: [PATCH 11/11] Improved setting 'notes' in Flickr and Ipernity --- src/photini/flickr.py | 20 +++++++++----------- src/photini/types.py | 18 ++++++++---------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/photini/flickr.py b/src/photini/flickr.py index 751db7f4..c35886ce 100644 --- a/src/photini/flickr.py +++ b/src/photini/flickr.py @@ -248,16 +248,19 @@ def set_notes(self, params, photo_id): return 'Failed to delete note' # add new notes for note in params['notes']: - if not note['is_person']: - rsp = self.api_call( - 'flickr.photos.notes.add', post=True, photo_id=photo_id, - note_x=note['x'], note_y=note['y'], note_w=note['w'], - note_h=note['h'], note_text=note['content']) - elif note['content'] == self.user_data['fullname']: + if (note['is_person'] and + note['content'] == self.user_data['fullname']): rsp = self.api_call( 'flickr.photos.people.add', post=True, photo_id=photo_id, person_x=note['x'], person_y=note['y'], person_w=note['w'], person_h=note['h'], user_id=self.user_data['user_nsid']) + if rsp is None: + return 'Failed to add person' + # flickr.photos.people.add doesn't show a box, so do it separately + rsp = self.api_call( + 'flickr.photos.notes.add', post=True, photo_id=photo_id, + note_x=note['x'], note_y=note['y'], note_w=note['w'], + note_h=note['h'], note_text=note['content']) if rsp is None: return 'Failed to add note' return '' @@ -650,11 +653,6 @@ def get_params(self, image, upload_prefs, replace_prefs, photo_id): params['notes'] = [] for note in image.metadata.image_region.to_notes(image, 500): params['notes'].append(note) - if note['is_person']: - # Flickr doesn't show box around person, so add one - note = dict(note) - note['is_person'] = False - params['notes'].append(note) return params def replace_dialog(self, image): diff --git a/src/photini/types.py b/src/photini/types.py index 9b6dc4ca..6201e097 100644 --- a/src/photini/types.py +++ b/src/photini/types.py @@ -2335,20 +2335,18 @@ def to_notes(self, image, target_size): for region, note in self.to_note_boundary(image, target_size): note['content'] = '' note['is_person'] = False - if region.has_type('human'): - if 'Iptc4xmpExt:PersonInImage' in region: - note['content'] = ', '.join( - region['Iptc4xmpExt:PersonInImage']) + if region.has_type('human') or region.has_type('Face'): + note['content'] = ', '.join(region['Iptc4xmpExt:PersonInImage']) note['is_person'] = True elif not any(region.has_role(x) for x in ( 'subjectArea', 'mainSubjectArea', 'areaOfInterest')): continue - if 'dc:description' in region and not note['content']: - note['content'] = MD_LangAlt( - region['dc:description']).best_match() - if 'Iptc4xmpExt:Name' in region and not note['content']: - note['content'] = MD_LangAlt( - region['Iptc4xmpExt:Name']).best_match() + if not note['content']: + note['content'] = region['dc:description'].best_match() + if not note['content']: + note['content'] = ', '.join(region['Iptc4xmpExt:PersonInImage']) + if not note['content']: + note['content'] = region['Iptc4xmpExt:Name'].best_match() if not note['content']: continue result.append(note)