Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
RKrahl committed Sep 24, 2023
2 parents 52b800b + 7e771a3 commit 922f5b2
Show file tree
Hide file tree
Showing 24 changed files with 138 additions and 28 deletions.
1 change: 1 addition & 0 deletions .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ jobs:
- '3.9'
- '3.10'
- '3.11'
- '3.12.0-beta - 3.12.0'
os: [ubuntu-latest]
include:
- python-version: '3.6'
Expand Down
20 changes: 20 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@ Changelog
=========


0.10.1 (2023-09-24)
~~~~~~~~~~~~~~~~~~~

Internal changes
----------------

+ `#60`_: Review the internal representation of the `Orientation` EXIF
tag.

Bug fixes and minor changes
---------------------------

+ `#59`_, `#61`_: Fix wrong orientation of thumbnails in the overview
window with vignette 5.0 and newer.

.. _#59: https://github.com/RKrahl/photoidx/issues/59
.. _#60: https://github.com/RKrahl/photoidx/pull/60
.. _#61: https://github.com/RKrahl/photoidx/pull/61


0.10.0 (2022-12-29)
~~~~~~~~~~~~~~~~~~~

Expand Down
12 changes: 8 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Required library packages:

+ `setuptools`_

+ `packaging`_

+ `PyYAML`_

+ `ExifRead`_ >= 2.2.0
Expand All @@ -44,9 +46,10 @@ Optional library packages:
vignette is not available, everything will still work, but
displaying the overview window may be significantly slower.

+ vignette needs at least one thumbnail backend, either `Pillow`_ or
`PyQt5`_. If no suitable backend is found, vignette will be
disabled in photoidx.
+ vignette needs at least one thumbnail backend, for instance
`Pillow`_ >= 6.0 or `PyQt5`_, see the vignette documentation for
details. If no suitable backend is found, vignette will be disabled
in photoidx.

+ `setuptools_scm`_

Expand Down Expand Up @@ -128,7 +131,7 @@ created during the release.
Copyright and License
---------------------

Copyright 2015–2022 Rolf Krahl
Copyright 2015–2023 Rolf Krahl

Licensed under the `Apache License`_, Version 2.0 (the "License"); you
may not use this package except in compliance with the License.
Expand All @@ -141,6 +144,7 @@ permissions and limitations under the License.


.. _setuptools: https://github.com/pypa/setuptools/
.. _packaging: https://github.com/pypa/packaging/
.. _PyYAML: https://github.com/yaml/pyyaml
.. _ExifRead: https://github.com/ianare/exif-py
.. _PySide2: https://www.pyside.org/
Expand Down
43 changes: 31 additions & 12 deletions photoidx/exif.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@
# Some helper classes for exif attributes, having customized string
# representations.

class Orientation(int):
"""EXIF orientation value.
Supports int and str representation.
"""
OrientationLabels = {
1: 'Horizontal (normal)',
2: 'Mirror horizontal',
3: 'Rotate 180',
4: 'Mirror vertical',
5: 'Mirror horizontal and rotate 270 CW',
6: 'Rotate 90 CW',
7: 'Mirror horizontal and rotate 90 CW',
8: 'Rotate 270 CW',
}
def __new__(cls, o):
if o is None:
return None
elif isinstance(o, str):
for i, l in cls.OrientationLabels.items():
if o == l:
o = i
break
else:
raise ValueError("invalid Orientation value %r" % o)
elif int(o) not in cls.OrientationLabels:
raise ValueError("invalid Orientation value %r" % o)
return super().__new__(cls, o)
def __str__(self):
return self.OrientationLabels[self]

class ExposureTime(fractions.Fraction):
def __str__(self):
if self.denominator == 1:
Expand All @@ -35,17 +65,6 @@ def __str__(self):

class Exif(object):

OrientationXlate = {
1: 'Horizontal (normal)',
2: 'Mirror horizontal',
3: 'Rotate 180',
4: 'Mirror vertical',
5: 'Mirror horizontal and rotate 270 CW',
6: 'Rotate 90 CW',
7: 'Mirror horizontal and rotate 90 CW',
8: 'Rotate 270 CW',
}

def __init__(self, path):
with path.open("rb") as f:
self._tags = exifread.process_file(f)
Expand All @@ -68,7 +87,7 @@ def orientation(self):
except (AttributeError, KeyError):
return None
else:
return self.OrientationXlate[int(orientation)]
return Orientation(int(orientation))

@property
def gpsPosition(self):
Expand Down
6 changes: 3 additions & 3 deletions photoidx/idxitem.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import hashlib
from pathlib import Path
from photoidx.exif import Exif
from photoidx.exif import Orientation, Exif
from photoidx.geo import GeoPosition


Expand Down Expand Up @@ -40,7 +40,7 @@ def __init__(self, data=None, filename=None, basedir=None, hashalg=['md5']):
# legacy: 'createDate' used to be 'createdate' in old
# index file format.
self.createDate = data['createdate']
self.orientation = data.get('orientation')
self.orientation = Orientation(data.get('orientation'))
self.gpsPosition = data.get('gpsPosition')
tags = data.get('tags', [])
self.tags = set(filter(lambda t: not t.startswith('pidx:'), tags))
Expand Down Expand Up @@ -69,7 +69,7 @@ def as_dict(self):
'filename': str(self.filename),
'checksum': self.checksum,
'createDate': self.createDate,
'orientation': self.orientation,
'orientation': str(self.orientation) if self.orientation else None,
'gpsPosition': self.gpsPosition,
'tags': sorted(tags),
}
Expand Down
55 changes: 48 additions & 7 deletions photoidx/qt/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import logging
import re
from packaging.version import Version
from PySide2 import QtCore, QtGui, QtWidgets
try:
import vignette
Expand Down Expand Up @@ -33,11 +34,46 @@
"no suitable thumbnailer backend available")
vignette = None

_thumbs_oriented = (vignette and
Version(vignette.__version__) >= Version('5.0.0'))

class ImageNotFoundError(Exception):
pass


_OrientationTransform = {
# 1: 'Horizontal (normal)'
1: ( 1, 0,
0, 1 ),
# 2: 'Mirror horizontal'
2: (-1, 0,
0, 1 ),
# 3: 'Rotate 180'
3: (-1, 0,
0, -1),
# 4: 'Mirror vertical'
4: ( 1, 0,
0, -1 ),
# 5: 'Mirror horizontal and rotate 270 CW'
5: ( 0, 1,
1, 0 ),
# 6: 'Rotate 90 CW'
6: ( 0, 1,
-1, 0 ),
# 7: 'Mirror horizontal and rotate 90 CW'
7: ( 0, -1,
-1, 0 ),
# 8: 'Rotate 270 CW'
8: ( 0, -1,
1, 0 ),
}
def _get_transform(orientation):
if orientation is not None:
return QtGui.QMatrix(*_OrientationTransform[orientation], 0, 0)
else:
return QtGui.QMatrix()


class Image(object):

ThumbnailSize = QtCore.QSize(128, 128)
Expand All @@ -46,11 +82,12 @@ def __init__(self, basedir, item):
self.item = item
self.fileName = basedir / item.filename
self.name = item.name or self.fileName.name
self.transform = QtGui.QMatrix()
if self.item.orientation:
m = re.match(r"Rotate (\d+) CW", self.item.orientation)
if m:
self.rotate(int(m.group(1)))
self.init_transform = _get_transform(self.item.orientation)
self.post_transform = QtGui.QMatrix()

@property
def transform(self):
return self.post_transform * self.init_transform

def getPixmap(self):
image = QtGui.QImage(str(self.fileName))
Expand All @@ -73,8 +110,12 @@ def getThumbPixmap(self):
pixmap = pixmap.scaled(self.ThumbnailSize,
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation)
pixmap = pixmap.transformed(self.transform)
if _thumbs_oriented:
transform = self.post_transform
else:
transform = self.transform
pixmap = pixmap.transformed(transform)
return pixmap

def rotate(self, a):
self.transform.rotate(a)
self.post_transform.rotate(a)
2 changes: 1 addition & 1 deletion photoidx/qt/imageInfoDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def setinfo(self, item):
else:
self.createDate.setText(None)
if item.orientation:
self.orientation.setText(item.orientation)
self.orientation.setText(str(item.orientation))
else:
self.orientation.setText(None)
pos = item.gpsPosition
Expand Down
1 change: 1 addition & 0 deletions python-photoidx.spec
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Summary: Tools for managing photo collections
Provides: python3-photo-qt = %{version}-%{release}
Obsoletes: python3-photo-qt < %{version}-%{release}
Requires: python3-%{distname} = %{version}
Requires: python3-packaging
Requires: python3-pyside2
Recommends: python3-vignette >= 4.3.0

Expand Down
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,15 @@ def run(self):
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
project_urls = dict(
Source="https://github.com/RKrahl/photoidx",
Download="https://github.com/RKrahl/photoidx/releases/latest"
),
packages = ["photoidx", "photoidx.qt"],
scripts = ["scripts/photo-idx.py", "scripts/imageview.py"],
python_requires = ">=3.6",
install_requires = ["PyYAML", "ExifRead >= 2.2.0"],
install_requires = ["packaging", "PyYAML", "ExifRead >= 2.2.0"],
cmdclass = dict(cmdclass, build_py=build_py, sdist=sdist, meta=meta),
)
Binary file added tests/data/dsc_1190-o1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/dsc_1190-o2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/dsc_1190-o3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/dsc_1190-o4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/dsc_1190-o5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/dsc_1190-o6.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/dsc_1190-o7.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/data/dsc_1190-o8.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions tests/test_01_exif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Use the photoidx.exif module to read EXIF tags from image files.
For the time being, only consider the Orientation tag. More tests
considering other tags may be added later.
"""

from pathlib import Path
import pytest
import photoidx.exif
from conftest import gettestdata

@pytest.mark.parametrize("o", range(1, 9))
def test_exif_orientation(o):
testimg = "dsc_1190-o%d.jpg" % o
filename = Path(gettestdata(testimg))
exifdata = photoidx.exif.Exif(filename)
orientation = exifdata.orientation
assert orientation == o
assert str(orientation) == photoidx.exif.Orientation.OrientationLabels[o]
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 922f5b2

Please sign in to comment.