Compare commits

..

7 Commits

Author SHA1 Message Date
Rhet Turnbull
05f111a287 Added exception handling/capture for convert-to-jpeg, issue #322 2021-01-03 09:36:26 -08:00
Rhet Turnbull
83915c65ab Add @synox as a contributor 2021-01-03 09:24:45 -08:00
Rhet Turnbull
22f44f7f40 Merge pull request #326 from synox/master
Make readme easier for beginners, thanks to @synox
2021-01-03 09:21:20 -08:00
Aravindo Wingeier
02ef0f9a25 doc simplify readme 2021-01-03 18:04:51 +01:00
Rhet Turnbull
6347d94dfb Updated CHANGELOG.md 2021-01-03 08:47:16 -08:00
Rhet Turnbull
a32c102d62 Updated CHANGELOG.md 2021-01-03 08:46:36 -08:00
Aravindo Wingeier
38842ff924 Cleanup up the readme
simplify as much as possible
2021-01-03 17:31:42 +01:00
13 changed files with 181 additions and 42 deletions

View File

@@ -118,6 +118,15 @@
"contributions": [
"doc"
]
},
{
"login": "synox",
"name": "Aravindo Wingeier",
"avatar_url": "https://avatars2.githubusercontent.com/u/2250964?v=4",
"profile": "https://github.com/synox",
"contributions": [
"doc"
]
}
],
"contributorsPerLine": 7

View File

@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.39.5](https://github.com/RhetTbull/osxphotos/compare/v0.39.3...v0.39.5)
> 3 January 2021
- Implemented text replacement for templates, issue #316 [`478715a`](https://github.com/RhetTbull/osxphotos/commit/478715a363f5009e4a38148e832bf0ad3c4cc4f8)
#### [v0.39.3](https://github.com/RhetTbull/osxphotos/compare/v0.39.2...v0.39.3)
> 31 December 2020

View File

@@ -3,7 +3,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python package](https://github.com/RhetTbull/osxphotos/workflows/Python%20package/badge.svg)](https://github.com/RhetTbull/osxphotos/workflows/Python%20package/badge.svg)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
- [OSXPhotos](#osxphotos)
@@ -40,31 +40,29 @@
## What is osxphotos?
OSXPhotos provides the ability to interact with and query Apple's Photos.app library database on MacOS. Using this package you can query the Photos database for information about the photos stored in a Photos library on your Mac--for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
OSXPhotos provides the ability to interact with and query Apple's Photos.app library on macOS. You can query the Photos library database -- for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
## Supported operating systems
Only works on MacOS (aka Mac OS X). Tested on MacOS 10.12.6 / Photos 2.0, 10.13.6 / Photos 3.0, MacOS 10.14.5, 10.14.6 / Photos 4.0, MacOS 10.15.1 - 10.15.7 / Photos 5.0.
Only works on macOS (aka Mac OS X). Tested on macOS Sierra (10.12.6) until macOS Catalina (10.15.7).
Beta support for MacOS 10.16/MacOS 11 Big Sur Beta / Photos 6.0. Not tested on M1 / Apple silicon Macs.
| macOS Version | macOS name | Photos.app version |
| ----------------- |------------|:-------------------|
| 10.16 | Big Sur | 6.0 ⚠️ Beta support, not tested on M1 / Apple silicon Macs. |
| 10.15.1 - 10.15.7 | Catalina | 5.0 ✅ |
| 10.14.5, 10.14.6 | Mojave | 4.0 ✅ |
| 10.13.6 | High Sierra| 3.0 ✅ |
| 10.12.6 | Sierra | 2.0 ✅ |
Requires python >= 3.7.
This package will read Photos databases for any supported version on any supported macOS version. E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine running macOS 10.12 and vice versa.
This package will read Photos databases for any supported version on any supported OS version. E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine running MacOS 10.12 and vice versa.
Requires python >= `3.7`.
## Installation instructions
OSXPhotos uses setuptools, thus simply run:
python3 setup.py install
You can also install directly from [pypi](https://pypi.org/project/osxphotos/):
pip install osxphotos
I recommend you create a [virtual environment](https://docs.python.org/3/tutorial/venv.html) before installing osxphotos.
## Installation
If you are new to python, I recommend you to install using pipx. See other advanced options below.
### Installation using pipx
If you aren't familiar with installing python applications, I recommend you install `osxphotos` with [pipx](https://github.com/pipxproject/pipx). If you use `pipx`, you will not need to create a virtual environment as `pipx` takes care of this. The easiest way to do this on a Mac is to use [homebrew](https://brew.sh/):
- Open `Terminal` (search for `Terminal` in Spotlight or look in `Applications/Utilities`)
@@ -73,7 +71,21 @@ If you aren't familiar with installing python applications, I recommend you inst
- Then type this: `pipx install osxphotos`
- Now you should be able to run `osxphotos` by typing: `osxphotos`
**WARNING** The git repo for this project is very large (> 1GB) because it contains multiple Photos libraries used for testing on different versions of MacOS. If you just want to use the osxphotos package in your own code, I recommend you install the latest version from [PyPI](https://pypi.org/project/osxphotos/) which does not include all the test libraries. If you just want to use the command line utility, you can download a pre-built executable of the latest [release](https://github.com/RhetTbull/osxphotos/releases) or you can install via `pip` which also installs the command line app. If you aren't comfortable with running python on your Mac, start with the pre-built executable or `pipx` as described above.
### Installation using pip
You can also install directly from [pypi](https://pypi.org/project/osxphotos/):
pip install osxphotos
### Installation from git repository
OSXPhotos uses setuptools, thus simply run:
git clone https://github.com/RhetTbull/osxphotos.git
cd osxphotos
python3 setup.py install
I recommend you create a [virtual environment](https://docs.python.org/3/tutorial/venv.html) before installing osxphotos.
**WARNING** The git repo for this project is very large (> 1GB) because it contains multiple Photos libraries used for testing on different versions of macOS. If you just want to use the osxphotos package in your own code, I recommend you install the latest version from [PyPI](https://pypi.org/project/osxphotos/) which does not include all the test libraries. If you just want to use the command line utility, you can download a pre-built executable of the latest [release](https://github.com/RhetTbull/osxphotos/releases) or you can install via `pip` which also installs the command line app. If you aren't comfortable with running python on your Mac, start with the pre-built executable or `pipx` as described above.
## Command Line Usage
@@ -446,6 +458,13 @@ Options:
do not include an extension in the FILENAME
template. See below for additional details
on templating system.
--strip Optionally strip leading and trailing
whitespace from any rendered templates. For
example, if --filename template is "{title,}
{original_name}" and image has no title,
resulting file would have a leading space
but if used with --strip, this will be
removed.
--edited-suffix SUFFIX Optional suffix template for naming edited
photos. Default name for edited photos is
in form 'photoname_edited.ext'. For example,
@@ -885,6 +904,19 @@ Substitution Description
e.g. 'Summer'; (Photos 5+ only, applied
automatically by Photos' image
categorization algorithms).
{exif.camera_make} Camera make from original photo's EXIF
inormation as imported by Photos, e.g.
'Apple'
{exif.camera_model} Camera model from original photo's EXIF
inormation as imported by Photos, e.g.
'iPhone 6s'
{exif.lens_model} Lens model from original photo's EXIF
inormation as imported by Photos, e.g.
'iPhone 6s back camera 4.15mm f/2.2'
{uuid} Photo's internal universally unique
identifier (UUID) for the photo, a
36-character string unique to the photo,
e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
The following substitutions may result in multiple values. Thus if specified
for --directory these could result in multiple copies of a photo being being
@@ -1778,7 +1810,7 @@ If overwrite=False and increment=False, export will fail if destination file alr
#### <a name="rendertemplate">`render_template()`</a>
`render_template(template_str, none_str = "_", path_sep = None, expand_inplace = False, inplace_sep = None, filename=False, dirname=False)`
`render_template(template_str, none_str = "_", path_sep = None, expand_inplace = False, inplace_sep = None, filename=False, dirname=False, strip=False)`
Render template string for photo. none_str is used if template substitution results in None value and no default specified.
@@ -1789,6 +1821,7 @@ Render template string for photo. none_str is used if template substitution res
- `inplace_sep`: optional string to use as separator between multi-valued keywords with expand_inplace; default is ','
- `filename`: if True, template output will be sanitized to produce valid file name
- `dirname`: if True, template output will be sanitized to produce valid directory name
- `strip`: if True, leading/trailign whitespace will be stripped from rendered template strings
Returns a tuple of (rendered, unmatched) where rendered is a list of rendered strings with all substitutions made and unmatched is a list of any strings that resembled a template substitution but did not match a known substitution. E.g. if template contained "{foo}", unmatched would be ["foo"].
@@ -2401,6 +2434,10 @@ The following template field substitutions are availabe for use with `PhotoInfo.
|{place.address.country}|Country name of the postal address, e.g. 'United States'|
|{place.address.country_code}|ISO country code of the postal address, e.g. 'US'|
|{searchinfo.season}|Season of the year associated with a photo, e.g. 'Summer'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).|
|{exif.camera_make}|Camera make from original photo's EXIF inormation as imported by Photos, e.g. 'Apple'|
|{exif.camera_model}|Camera model from original photo's EXIF inormation as imported by Photos, e.g. 'iPhone 6s'|
|{exif.lens_model}|Lens model from original photo's EXIF inormation as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'|
|{uuid}|Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
|{keyword}|Keyword(s) assigned to photo|
@@ -2536,6 +2573,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/hhoeck"><img src="https://avatars1.githubusercontent.com/u/6313998?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Horst Höck</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=hhoeck" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/jstrine"><img src="https://avatars1.githubusercontent.com/u/33943447?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonathan Strine</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=jstrine" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/finestream"><img src="https://avatars1.githubusercontent.com/u/16638513?v=4?s=100" width="100px;" alt=""/><br /><sub><b>finestream</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=finestream" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/synox"><img src="https://avatars2.githubusercontent.com/u/2250964?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aravindo Wingeier</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=synox" title="Documentation">📖</a></td>
</tr>
</table>

View File

@@ -1586,6 +1586,14 @@ def query(
"File extension will be added automatically--do not include an extension in the FILENAME template. "
"See below for additional details on templating system.",
)
@click.option(
"--strip",
is_flag=True,
help="Optionally strip leading and trailing whitespace from any rendered templates. "
'For example, if --filename template is "{title,} {original_name}" and image has no '
"title, resulting file would have a leading space but if used with --strip, this will "
"be removed.",
)
@click.option(
"--edited-suffix",
metavar="SUFFIX",
@@ -1749,6 +1757,7 @@ def export(
has_raw,
directory,
filename_template,
strip,
edited_suffix,
original_suffix,
place,
@@ -1887,6 +1896,7 @@ def export(
has_raw = cfg.has_raw
directory = cfg.directory
filename_template = cfg.filename_template
strip = cfg.strip
edited_suffix = cfg.edited_suffix
original_suffix = cfg.original_suffix
place = cfg.place
@@ -2252,6 +2262,7 @@ def export(
ignore_date_modified=ignore_date_modified,
use_photokit=use_photokit,
exiftool_option=exiftool_option,
strip=strip,
)
results += export_results
@@ -2276,13 +2287,14 @@ def export(
person_keyword=person_keyword,
exiftool_merge_keywords=exiftool_merge_keywords,
finder_tag_template=finder_tag_template,
strip=strip,
)
results.xattr_written.extend(tags_written)
results.xattr_skipped.extend(tags_skipped)
if xattr_template:
xattr_written, xattr_skipped = write_extended_attributes(
p, photo_files, xattr_template
p, photo_files, xattr_template, strip=strip
)
results.xattr_written.extend(xattr_written)
results.xattr_skipped.extend(xattr_skipped)
@@ -2822,6 +2834,7 @@ def export_photo(
ignore_date_modified=False,
use_photokit=False,
exiftool_option=None,
strip=False,
):
"""Helper function for export that does the actual export
@@ -2912,12 +2925,14 @@ def export_photo(
if photo.hasadjustments and photo.path_edited is None:
missing_edited = True
filenames = get_filenames_from_template(photo, filename_template, original_name)
filenames = get_filenames_from_template(
photo, filename_template, original_name, strip=strip
)
for filename in filenames:
if original_suffix:
try:
rendered_suffix, unmatched = photo.render_template(
original_suffix, filename=True
original_suffix, filename=True, strip=strip
)
except ValueError:
raise click.BadOptionUsage(
@@ -2950,7 +2965,7 @@ def export_photo(
)
dest_paths = get_dirnames_from_template(
photo, directory, export_by_date, dest, dry_run
photo, directory, export_by_date, dest, dry_run, strip=strip
)
sidecar = [s.lower() for s in sidecar]
@@ -3062,7 +3077,7 @@ def export_photo(
if edited_suffix:
try:
rendered_suffix, unmatched = photo.render_template(
edited_suffix, filename=True
edited_suffix, filename=True, strip=strip
)
except ValueError:
raise click.BadOptionUsage(
@@ -3167,7 +3182,7 @@ def export_photo(
return results
def get_filenames_from_template(photo, filename_template, original_name):
def get_filenames_from_template(photo, filename_template, original_name, strip=False):
"""get list of export filenames for a photo
Args:
@@ -3185,7 +3200,7 @@ def get_filenames_from_template(photo, filename_template, original_name):
photo_ext = pathlib.Path(photo.original_filename).suffix
try:
filenames, unmatched = photo.render_template(
filename_template, path_sep="_", filename=True
filename_template, path_sep="_", filename=True, strip=strip
)
except ValueError:
raise click.BadOptionUsage(
@@ -3208,7 +3223,9 @@ def get_filenames_from_template(photo, filename_template, original_name):
return filenames
def get_dirnames_from_template(photo, directory, export_by_date, dest, dry_run):
def get_dirnames_from_template(
photo, directory, export_by_date, dest, dry_run, strip=False
):
"""get list of directories to export a photo into, creates directories if they don't exist
Args:
@@ -3236,7 +3253,9 @@ def get_dirnames_from_template(photo, directory, export_by_date, dest, dry_run):
elif directory:
# got a directory template, render it and check results are valid
try:
dirnames, unmatched = photo.render_template(directory, dirname=True)
dirnames, unmatched = photo.render_template(
directory, dirname=True, strip=strip
)
except ValueError:
raise click.BadOptionUsage("directory", f"Invalid template '{directory}'")
if not dirnames or unmatched:
@@ -3498,6 +3517,7 @@ def write_finder_tags(
person_keyword=None,
exiftool_merge_keywords=None,
finder_tag_template=None,
strip=False,
):
"""Write Finder tags (extended attributes) to files; only writes attributes if attributes on file differ from what would be written
@@ -3537,7 +3557,10 @@ def write_finder_tags(
for template_str in finder_tag_template:
try:
rendered, unmatched = photo.render_template(
template_str, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/"
template_str,
none_str=_OSXPHOTOS_NONE_SENTINEL,
path_sep="/",
strip=strip,
)
except ValueError:
raise click.BadOptionUsage(
@@ -3575,7 +3598,7 @@ def write_finder_tags(
return (written, skipped)
def write_extended_attributes(photo, files, xattr_template):
def write_extended_attributes(photo, files, xattr_template, strip=False):
""" Writes extended attributes to exported files
Args:
@@ -3590,7 +3613,10 @@ def write_extended_attributes(photo, files, xattr_template):
for xattr, template_str in xattr_template:
try:
rendered, unmatched = photo.render_template(
template_str, none_str=_OSXPHOTOS_NONE_SENTINEL, path_sep="/"
template_str,
none_str=_OSXPHOTOS_NONE_SENTINEL,
path_sep="/",
strip=strip,
)
except ValueError:
raise click.BadOptionUsage(

View File

@@ -1,5 +1,5 @@
""" version info """
__version__ = "0.39.4"
__version__ = "0.39.6"

View File

@@ -4,7 +4,6 @@
# reference: https://stackoverflow.com/questions/59330149/coreimage-ciimage-write-jpg-is-shifting-colors-macos/59334308#59334308
import logging
import pathlib
import Metal
@@ -16,6 +15,11 @@ from Foundation import NSDictionary
from wurlitzer import pipes
class ImageConversionError(Exception):
"""Base class for exceptions in this module. """
pass
class ImageConverter:
""" Convert images to jpeg. This class is a singleton
which will re-use the Core Image CIContext to avoid
@@ -60,6 +64,7 @@ class ImageConverter:
Raises:
ValueError if compression quality not in range 0.0 to 1.0
FileNotFoundError if input_path doesn't exist
ImageConversionError if error during conversion
"""
# accept input_path or output_path as pathlib.Path
@@ -89,8 +94,7 @@ class ImageConverter:
input_image = Quartz.CIImage.imageWithContentsOfURL_(input_url)
if input_image is None:
logging.debug(f"Could not create CIImage for {input_path}")
return False
raise ImageConversionError(f"Could not create CIImage for {input_path}")
output_colorspace = input_image.colorSpace() or Quartz.CGColorSpaceCreateWithName(
Quartz.CoreGraphics.kCGColorSpaceSRGB
@@ -105,8 +109,7 @@ class ImageConverter:
if not error:
return True
else:
logging.debug(
raise ImageConversionError(
"Error converting file {input_path} to jpeg at {output_path}: {error}"
)
return False

View File

@@ -832,6 +832,7 @@ class PhotoInfo:
inplace_sep=None,
filename=False,
dirname=False,
strip=False,
):
"""Renders a template string for PhotoInfo instance using PhotoTemplate
@@ -846,6 +847,7 @@ class PhotoInfo:
with expand_inplace; default is ','
filename: if True, template output will be sanitized to produce valid file name
dirname: if True, template output will be sanitized to produce valid directory name
strip: if True, strips leading/trailing white space from resulting template
Returns:
([rendered_strings], [unmatched]): tuple of list of rendered strings and list of unmatched template values
@@ -859,6 +861,7 @@ class PhotoInfo:
inplace_sep=inplace_sep,
filename=filename,
dirname=dirname,
strip=strip,
)
@property

View File

@@ -118,6 +118,10 @@ TEMPLATE_SUBSTITUTIONS = {
"{place.address.country}": "Country name of the postal address, e.g. 'United States'",
"{place.address.country_code}": "ISO country code of the postal address, e.g. 'US'",
"{searchinfo.season}": "Season of the year associated with a photo, e.g. 'Summer'; (Photos 5+ only, applied automatically by Photos' image categorization algorithms).",
"{exif.camera_make}": "Camera make from original photo's EXIF inormation as imported by Photos, e.g. 'Apple'",
"{exif.camera_model}": "Camera model from original photo's EXIF inormation as imported by Photos, e.g. 'iPhone 6s'",
"{exif.lens_model}": "Lens model from original photo's EXIF inormation as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'",
"{uuid}": "Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'",
}
# Permitted multi-value substitutions (each of these returns None or 1 or more values)
@@ -251,6 +255,7 @@ class PhotoTemplate:
inplace_sep=None,
filename=False,
dirname=False,
strip=False,
):
""" Render a filename or directory template
@@ -264,6 +269,7 @@ class PhotoTemplate:
with expand_inplace; default is ','
filename: if True, template output will be sanitized to produce valid file name
dirname: if True, template output will be sanitized to produce valid directory name
strip: if True, strips leading/trailing whitespace from rendered templates
Returns:
([rendered_strings], [unmatched]): tuple of list of rendered strings and list of unmatched template values
@@ -364,6 +370,11 @@ class PhotoTemplate:
sanitize_filename(rendered_str) for rendered_str in rendered_strings
]
if strip:
rendered_strings = [
rendered_str.strip() for rendered_str in rendered_strings
]
return rendered_strings, unmatched
def _render_multi_valued_templates(
@@ -890,6 +901,14 @@ class PhotoTemplate:
)
elif field == "searchinfo.season":
value = self.photo.search_info.season if self.photo.search_info else None
elif field == "exif.camera_make":
value = self.photo.exif_info.camera_make if self.photo.exif_info else None
elif field == "exif.camera_model":
value = self.photo.exif_info.camera_model if self.photo.exif_info else None
elif field == "exif.lens_model":
value = self.photo.exif_info.lens_model if self.photo.exif_info else None
elif field == "uuid":
value = self.photo.uuid
else:
# if here, didn't get a match
raise ValueError(f"Unhandled template value: {field}")

View File

@@ -51,7 +51,7 @@ with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f:
setup(
name="osxphotos",
version=about["__version__"],
description="Manipulate (read-only) Apple's Photos app library on Mac OS X",
description="Export photos from Apple's macOS Photos app and query the Photos library database to access metadata about images.",
long_description=about["long_description"],
long_description_content_type="text/markdown",
author="Rhet Turnbull",

File diff suppressed because one or more lines are too long

View File

@@ -2880,7 +2880,6 @@ def test_export_filename_template_1():
],
)
assert result.exit_code == 0
workdir = os.getcwd()
files = glob.glob("*.*")
assert sorted(files) == sorted(CLI_EXPORTED_FILENAME_TEMPLATE_FILENAMES1)
@@ -2915,6 +2914,37 @@ def test_export_filename_template_2():
assert sorted(files) == sorted(CLI_EXPORTED_FILENAME_TEMPLATE_FILENAMES2)
def test_export_filename_template_strip():
""" export photos using filename template with --strip """
import glob
import locale
import os
import os.path
import osxphotos
from osxphotos.__main__ import export
locale.setlocale(locale.LC_ALL, "en_US")
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--filename",
"{searchinfo.venue,} {created.year}-{original_name}",
"--strip",
],
)
assert result.exit_code == 0
files = glob.glob("*.*")
assert sorted(files) == sorted(CLI_EXPORTED_FILENAME_TEMPLATE_FILENAMES1)
def test_export_filename_template_pathsep_in_name_1():
""" export photos using filename template with folder_album and "/" in album name """
import locale

View File

@@ -89,14 +89,15 @@ def test_image_converter_bad_file():
""" Try to convert a file that's not an image """
import pathlib
import tempfile
from osxphotos.imageconverter import ImageConverter
from osxphotos.imageconverter import ImageConverter, ImageConversionError
converter = ImageConverter()
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
with tempdir:
imgfile = pathlib.Path(TEST_NOT_AN_IMAGE)
outfile = pathlib.Path(tempdir.name) / f"{imgfile.stem}.jpeg"
assert not converter.write_jpeg(imgfile, outfile)
with pytest.raises(ImageConversionError):
converter.write_jpeg(imgfile, outfile)
def test_image_converter_missing_file():

View File

@@ -136,6 +136,10 @@ TEMPLATE_VALUES = {
"{place.address.postal_code}": "20009",
"{place.address.country}": "United States",
"{place.address.country_code}": "US",
"{uuid}": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
"{exif.camera_make}": "Apple",
"{exif.camera_model}": "iPhone 6s",
"{exif.lens_model}": "iPhone 6s back camera 4.15mm f/2.2",
}