Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddce731a5d | ||
|
|
f662495c46 | ||
|
|
08bc8a9723 | ||
|
|
1fd0fe5ea4 | ||
|
|
fd5976b75c | ||
|
|
088476c591 | ||
|
|
250697c4a2 | ||
|
|
eba796b684 | ||
|
|
965e10e20f | ||
|
|
61f649e59d | ||
|
|
165f9b08f5 | ||
|
|
039118c1aa | ||
|
|
27f779b16c | ||
|
|
eec960861e | ||
|
|
4d924d0826 | ||
|
|
55c088eea2 | ||
|
|
ee2750224a | ||
|
|
db1947dd1e | ||
|
|
248fdbcf02 | ||
|
|
71cb01572d | ||
|
|
51b1058785 | ||
|
|
87701822ae | ||
|
|
b67f11a3bb | ||
|
|
804e13efff | ||
|
|
504b81b720 | ||
|
|
538e8b588e | ||
|
|
c4980fc284 | ||
|
|
a7678df397 | ||
|
|
e6f45f5949 | ||
|
|
f8468c63fd | ||
|
|
0545d5e321 | ||
|
|
5de9d4f90c | ||
|
|
123ebb2cb7 | ||
|
|
2d09d382e9 | ||
|
|
5e676d3507 | ||
|
|
935865dc65 | ||
|
|
193f26bec8 | ||
|
|
7b6a0af314 | ||
|
|
549a9b3572 | ||
|
|
792247b51c | ||
|
|
568d1b36a6 | ||
|
|
d78097ccc0 | ||
|
|
ad9dcd9ed7 | ||
|
|
aba50c5c73 | ||
|
|
8ca7719641 | ||
|
|
5dc2eeaf9a | ||
|
|
658e8ac096 |
@@ -128,7 +128,36 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kradalby",
|
||||
"name": "Kristoffer Dalby",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/98431?v=4",
|
||||
"profile": "https://kradalby.no",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Rott-Apple",
|
||||
"name": "Rott-Apple",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/67875570?v=4",
|
||||
"profile": "https://github.com/Rott-Apple",
|
||||
"contributions": [
|
||||
"research"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "narensankar0529",
|
||||
"name": "narensankar0529",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/74054766?v=4",
|
||||
"profile": "https://github.com/narensankar0529",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"userTesting"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7
|
||||
"contributorsPerLine": 7,
|
||||
"skipCi": true
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ jobs:
|
||||
build:
|
||||
|
||||
runs-on: macOS-latest
|
||||
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
74
CHANGELOG.md
74
CHANGELOG.md
@@ -4,6 +4,80 @@ 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.17](https://github.com/RhetTbull/osxphotos/compare/v0.39.16...v0.39.17)
|
||||
|
||||
> 12 January 2021
|
||||
|
||||
- Fixed test for M1, added about command, closes #315 [`#315`](https://github.com/RhetTbull/osxphotos/issues/315)
|
||||
- Fixed time zone for tests [`165f9b0`](https://github.com/RhetTbull/osxphotos/commit/165f9b08f5056d1f0b2ca7c74cec84d42b635663)
|
||||
- Add @narensankar0529 as a contributor [`039118c`](https://github.com/RhetTbull/osxphotos/commit/039118c1aaa217f46354b351ea36b0729e3e1c35)
|
||||
- Update @narensankar0529 as a contributor [`61f649e`](https://github.com/RhetTbull/osxphotos/commit/61f649e59d53a3e3011602476b72cc64951d38c0)
|
||||
|
||||
#### [v0.39.16](https://github.com/RhetTbull/osxphotos/compare/v0.39.15...v0.39.16)
|
||||
|
||||
> 12 January 2021
|
||||
|
||||
- Added version check for M1 macs [`27f779b`](https://github.com/RhetTbull/osxphotos/commit/27f779b16c850cdbda2691e5fae8cd14405653b3)
|
||||
|
||||
#### [v0.39.15](https://github.com/RhetTbull/osxphotos/compare/v0.39.13...v0.39.15)
|
||||
|
||||
> 11 January 2021
|
||||
|
||||
- Completed implementation of --jpeg-ext, fixed --dry-run, closes #330, #346 [`#330`](https://github.com/RhetTbull/osxphotos/issues/330)
|
||||
- Added --jpeg-ext, implements #330 [`55c088e`](https://github.com/RhetTbull/osxphotos/commit/55c088eea2ddecb14e362221da9e2a7c0f403780)
|
||||
|
||||
#### [v0.39.13](https://github.com/RhetTbull/osxphotos/compare/v0.39.12...v0.39.13)
|
||||
|
||||
> 10 January 2021
|
||||
|
||||
- Fixed leaky memory in PhotoKit, issue #276 [`db1947d`](https://github.com/RhetTbull/osxphotos/commit/db1947dd1e3d47a487eeb68a5ceb5f7098f1df10)
|
||||
|
||||
#### [v0.39.12](https://github.com/RhetTbull/osxphotos/compare/v0.39.11...v0.39.12)
|
||||
|
||||
> 9 January 2021
|
||||
|
||||
- Force cleanup of objects in write_jpeg (fix memory leak) [`#344`](https://github.com/RhetTbull/osxphotos/pull/344)
|
||||
- doc: Recorded screencast and updated of readme [skip ci] [`#328`](https://github.com/RhetTbull/osxphotos/pull/328)
|
||||
- Added PhotoInfo.visible, PhotoInfo.date_trashed, closes #333, #334 [`#333`](https://github.com/RhetTbull/osxphotos/issues/333)
|
||||
- Force cleanup of objects with autorelease pool [`b67f11a`](https://github.com/RhetTbull/osxphotos/commit/b67f11a3bb95c08a39a185b6d884092870e949f2)
|
||||
- Add @Rott-Apple as a contributor [`71cb015`](https://github.com/RhetTbull/osxphotos/commit/71cb01572d2d946df18dd7b36f95b2f2e5b48f86)
|
||||
- Updated README [skip ci] [`804e13e`](https://github.com/RhetTbull/osxphotos/commit/804e13efff921ab51b996493d659b32102807a8a)
|
||||
|
||||
#### [v0.39.11](https://github.com/RhetTbull/osxphotos/compare/v0.39.10...v0.39.11)
|
||||
|
||||
> 8 January 2021
|
||||
|
||||
- All contributors/add kradalby [`#343`](https://github.com/RhetTbull/osxphotos/pull/343)
|
||||
- Ensure keyword list only contains strings, @all-contributors please add @kradalby for code [`#342`](https://github.com/RhetTbull/osxphotos/pull/342)
|
||||
- Added README.rst, closes #331 [`#331`](https://github.com/RhetTbull/osxphotos/issues/331)
|
||||
- Updated tests workflow badge link [`a7678df`](https://github.com/RhetTbull/osxphotos/commit/a7678df3974ff539050f5acb4c94817f525dcd56)
|
||||
- Merge branch 'master' of github.com:RhetTbull/osxphotos [`e6f45f5`](https://github.com/RhetTbull/osxphotos/commit/e6f45f59491d9e805e227af8cbf8ac08ff99fdf0)
|
||||
- Renamed workflow to tests [`f8468c6`](https://github.com/RhetTbull/osxphotos/commit/f8468c63fda930216f73ad5aa8c4aa92edf1adf2)
|
||||
- Merge branch 'master' of github.com:RhetTbull/osxphotos [`5de9d4f`](https://github.com/RhetTbull/osxphotos/commit/5de9d4f90c1102c4fb0099befd6142180f32df3f)
|
||||
- Ensure merge_exif_keywords are str not int [`123ebb2`](https://github.com/RhetTbull/osxphotos/commit/123ebb2cb752bb94291ac2b77e4a327cee996df1)
|
||||
|
||||
#### [v0.39.10](https://github.com/RhetTbull/osxphotos/compare/v0.39.9...v0.39.10)
|
||||
|
||||
> 6 January 2021
|
||||
|
||||
- Refactored ExportResults [`568d1b3`](https://github.com/RhetTbull/osxphotos/commit/568d1b36a631df33317dc00f27126b507c90bf51)
|
||||
- Improved handling of deleted photos, #332 [`792247b`](https://github.com/RhetTbull/osxphotos/commit/792247b51cc2263221ba8c2e741d2ec454c75ca8)
|
||||
- Added error_str to ExportResults [`d78097c`](https://github.com/RhetTbull/osxphotos/commit/d78097ccc0686680baf5fffa91f9e082e44b576e)
|
||||
|
||||
#### [v0.39.9](https://github.com/RhetTbull/osxphotos/compare/v0.39.8...v0.39.9)
|
||||
|
||||
> 4 January 2021
|
||||
|
||||
- Added test for Big Sur 16.0.1 database changes [`7deac58`](https://github.com/RhetTbull/osxphotos/commit/7deac581b1f1fb3dc59885b6e1ab9a63b382408d)
|
||||
- Create terminalizer-demo.yml [`5dc2eea`](https://github.com/RhetTbull/osxphotos/commit/5dc2eeaf9a7265873c81db23bbc86d3023189a26)
|
||||
- doc: Recorded screencast and updated of readme [`658e8ac`](https://github.com/RhetTbull/osxphotos/commit/658e8ac096d141fce48483dbfc1426bea317d806)
|
||||
|
||||
#### [v0.39.8](https://github.com/RhetTbull/osxphotos/compare/v0.39.7...v0.39.8)
|
||||
|
||||
> 3 January 2021
|
||||
|
||||
- Updated README, version [`b93d682`](https://github.com/RhetTbull/osxphotos/commit/b93d6822ac5366c57d9142cba9b809b4ab99ad98)
|
||||
|
||||
#### [v0.39.7](https://github.com/RhetTbull/osxphotos/compare/v0.39.6...v0.39.7)
|
||||
|
||||
> 3 January 2021
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Rhet Turnbull
|
||||
Copyright (c) 2019-2021 Rhet Turnbull
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
271
README.rst
Normal file
271
README.rst
Normal file
@@ -0,0 +1,271 @@
|
||||
.. role:: raw-html-m2r(raw)
|
||||
:format: html
|
||||
|
||||
|
||||
OSXPhotos
|
||||
=========
|
||||
|
||||
What is osxphotos?
|
||||
------------------
|
||||
|
||||
OSXPhotos provides both the ability to interact with and query Apple's Photos.app library on macOS directly from your python code
|
||||
as well as a very flexible command line interface (CLI) app for exporting photos.
|
||||
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 Sierra (10.12.6) until macOS Catalina (10.15.7).
|
||||
Beta support for macOS Big Sur (10.16.01/11.01).
|
||||
|
||||
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.
|
||||
|
||||
Requires python >= ``3.7``.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
If you are new to python and just want to use the command line application, 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``\ )
|
||||
* Install ``homebrew`` according to instructions at `https://brew.sh/ <https://brew.sh/>`_
|
||||
* Type the following into Terminal: ``brew install pipx``
|
||||
* Then type this: ``pipx install osxphotos``
|
||||
* Now you should be able to run ``osxphotos`` by typing: ``osxphotos``
|
||||
|
||||
Installation using pip
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also install directly from `pypi <https://pypi.org/project/osxphotos/>`_\ :
|
||||
|
||||
.. code-block::
|
||||
|
||||
pip install osxphotos
|
||||
|
||||
|
||||
Installation from git repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
OSXPhotos uses setuptools, thus simply run:
|
||||
|
||||
.. code-block::
|
||||
|
||||
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
|
||||
------------------
|
||||
|
||||
This package will install a command line utility called ``osxphotos`` that allows you to query the Photos database and export photos.
|
||||
Alternatively, you can also run the command line utility like this: ``python3 -m osxphotos``
|
||||
|
||||
.. code-block::
|
||||
|
||||
> osxphotos
|
||||
Usage: osxphotos [OPTIONS] COMMAND [ARGS]...
|
||||
|
||||
Options:
|
||||
--db <Photos database path> Specify Photos database path. Path to Photos
|
||||
library/database can be specified using either
|
||||
--db or directly as PHOTOS_LIBRARY positional
|
||||
argument. If neither --db or PHOTOS_LIBRARY
|
||||
provided, will attempt to find the library to
|
||||
use in the following order: 1. last opened
|
||||
library, 2. system library, 3.
|
||||
~/Pictures/Photos Library.photoslibrary
|
||||
--json Print output in JSON format.
|
||||
-v, --version Show the version and exit.
|
||||
-h, --help Show this message and exit.
|
||||
|
||||
Commands:
|
||||
about Print information about osxphotos including license.
|
||||
albums Print out albums found in the Photos library.
|
||||
dump Print list of all photos & associated info from the Photos...
|
||||
export Export photos from the Photos database.
|
||||
help Print help; for help on commands: help <command>.
|
||||
info Print out descriptive info of the Photos library database.
|
||||
keywords Print out keywords found in the Photos library.
|
||||
labels Print out image classification labels found in the Photos...
|
||||
list Print list of Photos libraries found on the system.
|
||||
persons Print out persons (faces) found in the Photos library.
|
||||
places Print out places found in the Photos library.
|
||||
query Query the Photos database using 1 or more search options; if...
|
||||
|
||||
To get help on a specific command, use ``osxphotos help <command_name>``
|
||||
|
||||
Command line examples
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
export all photos to ~/Desktop/export group in folders by date created
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``osxphotos export --export-by-date ~/Pictures/Photos\ Library.photoslibrary ~/Desktop/export``
|
||||
|
||||
**Note**\ : Photos library/database path can also be specified using ``--db`` option:
|
||||
|
||||
``osxphotos export --export-by-date --db ~/Pictures/Photos\ Library.photoslibrary ~/Desktop/export``
|
||||
|
||||
find all photos with keyword "Kids" and output results to json file named results.json:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``osxphotos query --keyword Kids --json ~/Pictures/Photos\ Library.photoslibrary >results.json``
|
||||
|
||||
export photos to file structure based on 4-digit year and full name of month of photo's creation date:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``osxphotos export ~/Desktop/export --directory "{created.year}/{created.month}"``
|
||||
|
||||
(by default, it will attempt to use the system library)
|
||||
|
||||
export photos to file structure based on 4-digit year of photo's creation date and add keywords for media type and labels (labels are only awailable on Photos 5 and higher):
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``osxphotos export ~/Desktop/export --directory "{created.year}" --keyword-template "{label}" --keyword-template "{media_type}"``
|
||||
|
||||
export default library using 'country name/year' as output directory (but use "NoCountry/year" if country not specified), add persons, album names, and year as keywords, write exif metadata to files when exporting, update only changed files, print verbose ouput
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``osxphotos export ~/Desktop/export --directory "{place.name.country,NoCountry}/{created.year}" --person-keyword --album-keyword --keyword-template "{created.year}" --exiftool --update --verbose``
|
||||
|
||||
Example uses of the package
|
||||
---------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
""" Simple usage of the package """
|
||||
import osxphotos
|
||||
|
||||
def main():
|
||||
photosdb = osxphotos.PhotosDB()
|
||||
print(photosdb.keywords)
|
||||
print(photosdb.persons)
|
||||
print(photosdb.album_names)
|
||||
|
||||
print(photosdb.keywords_as_dict)
|
||||
print(photosdb.persons_as_dict)
|
||||
print(photosdb.albums_as_dict)
|
||||
|
||||
# find all photos with Keyword = Foo and containing John Smith
|
||||
photos = photosdb.photos(keywords=["Foo"],persons=["John Smith"])
|
||||
|
||||
# find all photos that include Alice Smith but do not contain the keyword Bar
|
||||
photos = [p for p in photosdb.photos(persons=["Alice Smith"])
|
||||
if p not in photosdb.photos(keywords=["Bar"]) ]
|
||||
for p in photos:
|
||||
print(
|
||||
p.uuid,
|
||||
p.filename,
|
||||
p.original_filename,
|
||||
p.date,
|
||||
p.description,
|
||||
p.title,
|
||||
p.keywords,
|
||||
p.albums,
|
||||
p.persons,
|
||||
p.path,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
""" Export all photos to specified directory using album names as folders
|
||||
If file has been edited, also export the edited version,
|
||||
otherwise, export the original version
|
||||
This will result in duplicate photos if photo is in more than album """
|
||||
|
||||
import os.path
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
import click
|
||||
from pathvalidate import is_valid_filepath, sanitize_filepath
|
||||
|
||||
import osxphotos
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("export_path", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--default-album",
|
||||
help="Default folder for photos with no album. Defaults to 'unfiled'",
|
||||
default="unfiled",
|
||||
)
|
||||
@click.option(
|
||||
"--library-path",
|
||||
help="Path to Photos library, default to last used library",
|
||||
default=None,
|
||||
)
|
||||
def export(export_path, default_album, library_path):
|
||||
export_path = os.path.expanduser(export_path)
|
||||
library_path = os.path.expanduser(library_path) if library_path else None
|
||||
|
||||
if library_path is not None:
|
||||
photosdb = osxphotos.PhotosDB(library_path)
|
||||
else:
|
||||
photosdb = osxphotos.PhotosDB()
|
||||
|
||||
photos = photosdb.photos()
|
||||
|
||||
for p in photos:
|
||||
if not p.ismissing:
|
||||
albums = p.albums
|
||||
if not albums:
|
||||
albums = [default_album]
|
||||
for album in albums:
|
||||
click.echo(f"exporting {p.filename} in album {album}")
|
||||
|
||||
# make sure no invalid characters in destination path (could be in album name)
|
||||
album_name = sanitize_filepath(album, platform="auto")
|
||||
|
||||
# create destination folder, if necessary, based on album name
|
||||
dest_dir = os.path.join(export_path, album_name)
|
||||
|
||||
# verify path is a valid path
|
||||
if not is_valid_filepath(dest_dir, platform="auto"):
|
||||
sys.exit(f"Invalid filepath {dest_dir}")
|
||||
|
||||
# create destination dir if needed
|
||||
if not os.path.isdir(dest_dir):
|
||||
os.makedirs(dest_dir)
|
||||
|
||||
# export the photo
|
||||
if p.hasadjustments:
|
||||
# export edited version
|
||||
exported = p.export(dest_dir, edited=True)
|
||||
edited_name = pathlib.Path(p.path_edited).name
|
||||
click.echo(f"Exported {edited_name} to {exported}")
|
||||
# export unedited version
|
||||
exported = p.export(dest_dir)
|
||||
click.echo(f"Exported {p.filename} to {exported}")
|
||||
else:
|
||||
click.echo(f"Skipping missing photo: {p.filename}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
export() # pylint: disable=no-value-for-parameter
|
||||
|
||||
Package Interface
|
||||
-----------------
|
||||
|
||||
Reference full documentation on `GitHub <https://github.com/RhetTbull/osxphotos/blob/master/README.md>`_
|
||||
BIN
docs/screencast/demo.gif
Normal file
BIN
docs/screencast/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
BIN
docs/screencast/osx-screenshot.png
Normal file
BIN
docs/screencast/osx-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
296
docs/screencast/terminalizer-demo.yml
Normal file
296
docs/screencast/terminalizer-demo.yml
Normal file
@@ -0,0 +1,296 @@
|
||||
# how to use this file? see https://github.com/faressoft/terminalizer
|
||||
|
||||
# running commands:
|
||||
# mkdir trip
|
||||
# osxphotos export --export-by-date --from-date 2021-01-01 trip
|
||||
# du -h trip
|
||||
# find trip | head -20
|
||||
|
||||
|
||||
|
||||
# The configurations that used for the recording, feel free to edit them
|
||||
config:
|
||||
|
||||
# Specify a command to be executed
|
||||
# like `/bin/bash -l`, `ls`, or any other commands
|
||||
# the default is bash for Linux
|
||||
# or powershell.exe for Windows
|
||||
command: zsh
|
||||
|
||||
# Specify the current working directory path
|
||||
# the default is the current working directory path
|
||||
cwd: /Users/aravindo/Downloads
|
||||
|
||||
# Export additional ENV variables
|
||||
env:
|
||||
recording: true
|
||||
|
||||
# Explicitly set the number of columns
|
||||
# or use `auto` to take the current
|
||||
# number of columns of your shell
|
||||
cols: 91
|
||||
|
||||
# Explicitly set the number of rows
|
||||
# or use `auto` to take the current
|
||||
# number of rows of your shell
|
||||
rows: 20
|
||||
|
||||
# Amount of times to repeat GIF
|
||||
# If value is -1, play once
|
||||
# If value is 0, loop indefinitely
|
||||
# If value is a positive number, loop n times
|
||||
repeat: 0
|
||||
|
||||
# Quality
|
||||
# 1 - 100
|
||||
quality: 100
|
||||
|
||||
# Delay between frames in ms
|
||||
# If the value is `auto` use the actual recording delays
|
||||
frameDelay: auto
|
||||
|
||||
# Maximum delay between frames in ms
|
||||
# Ignored if the `frameDelay` isn't set to `auto`
|
||||
# Set to `auto` to prevent limiting the max idle time
|
||||
maxIdleTime: 2000
|
||||
|
||||
# The surrounding frame box
|
||||
# The `type` can be null, window, floating, or solid`
|
||||
# To hide the title use the value null
|
||||
# Don't forget to add a backgroundColor style with a null as type
|
||||
frameBox:
|
||||
type: floating
|
||||
title: ""
|
||||
style:
|
||||
border: 0px black solid
|
||||
# boxShadow: none
|
||||
# margin: 0px
|
||||
|
||||
# Add a watermark image to the rendered gif
|
||||
# You need to specify an absolute path for
|
||||
# the image on your machine or a URL, and you can also
|
||||
# add your own CSS styles
|
||||
watermark:
|
||||
imagePath: null
|
||||
style:
|
||||
position: absolute
|
||||
right: 15px
|
||||
bottom: 15px
|
||||
width: 100px
|
||||
opacity: 0.9
|
||||
|
||||
# Cursor style can be one of
|
||||
# `block`, `underline`, or `bar`
|
||||
cursorStyle: block
|
||||
|
||||
# Font family
|
||||
# You can use any font that is installed on your machine
|
||||
# in CSS-like syntax
|
||||
fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace"
|
||||
|
||||
# The size of the font
|
||||
fontSize: 12
|
||||
|
||||
# The height of lines
|
||||
lineHeight: 1
|
||||
|
||||
# The spacing between letters
|
||||
letterSpacing: 0
|
||||
|
||||
# Theme
|
||||
theme:
|
||||
background: "transparent"
|
||||
foreground: "#afafaf"
|
||||
cursor: "#c7c7c7"
|
||||
black: "#232628"
|
||||
red: "#fc4384"
|
||||
green: "#b3e33b"
|
||||
yellow: "#ffa727"
|
||||
blue: "#75dff2"
|
||||
magenta: "#ae89fe"
|
||||
cyan: "#708387"
|
||||
white: "#d5d5d0"
|
||||
brightBlack: "#626566"
|
||||
brightRed: "#ff7fac"
|
||||
brightGreen: "#c8ed71"
|
||||
brightYellow: "#ebdf86"
|
||||
brightBlue: "#75dff2"
|
||||
brightMagenta: "#ae89fe"
|
||||
brightCyan: "#b1c6ca"
|
||||
brightWhite: "#f9f9f4"
|
||||
|
||||
# Records, feel free to edit them
|
||||
records:
|
||||
- delay: 100
|
||||
content: "\e[1m\e[7m%\e[27m\e[1m\e[0m \r \r\e]7;file://wingeier-macOS/Users/aravindo/Downloads\a\r\e[0m\e[27m\e[24m\e[J❯ \e[K\e[?2004h"
|
||||
- delay: 100
|
||||
content: m
|
||||
- delay: 100
|
||||
content: "\bmk"
|
||||
- delay: 100
|
||||
content: d
|
||||
- delay: 100
|
||||
content: i
|
||||
- delay: 100
|
||||
content: r
|
||||
- delay: 100
|
||||
content: ' '
|
||||
- delay: 100
|
||||
content: t
|
||||
- delay: 100
|
||||
content: r
|
||||
- delay: 100
|
||||
content: i
|
||||
- delay: 100
|
||||
content: p
|
||||
- delay: 100
|
||||
content: "\e[?2004l\r\r\n"
|
||||
- delay: 9
|
||||
content: "\e[1m\e[7m%\e[27m\e[1m\e[0m \r \r\e]7;file://wingeier-macOS/Users/aravindo/Downloads\a\r\e[0m\e[27m\e[24m\e[J❯ \e[K\e[?2004h"
|
||||
- delay: 300
|
||||
content: o
|
||||
- delay: 100
|
||||
content: "\bos"
|
||||
- delay: 100
|
||||
content: x
|
||||
- delay: 100
|
||||
content: p
|
||||
- delay: 100
|
||||
content: h
|
||||
- delay: 100
|
||||
content: o
|
||||
- delay: 100
|
||||
content: t
|
||||
- delay: 100
|
||||
content: o
|
||||
- delay: 100
|
||||
content: s
|
||||
- delay: 100
|
||||
content: ' '
|
||||
- delay: 100
|
||||
content: e
|
||||
- delay: 100
|
||||
content: x
|
||||
- delay: 100
|
||||
content: p
|
||||
- delay: 100
|
||||
content: o
|
||||
- delay: 100
|
||||
content: r
|
||||
- delay: 100
|
||||
content: t
|
||||
- delay: 100
|
||||
content: ' '
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: e
|
||||
- delay: 100
|
||||
content: x
|
||||
- delay: 100
|
||||
content: p
|
||||
- delay: 100
|
||||
content: o
|
||||
- delay: 100
|
||||
content: r
|
||||
- delay: 100
|
||||
content: t
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: b
|
||||
- delay: 100
|
||||
content: 'y'
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: d
|
||||
- delay: 100
|
||||
content: a
|
||||
- delay: 100
|
||||
content: t
|
||||
- delay: 100
|
||||
content: e
|
||||
- delay: 100
|
||||
content: ' '
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: f
|
||||
- delay: 100
|
||||
content: r
|
||||
- delay: 100
|
||||
content: o
|
||||
- delay: 100
|
||||
content: m
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: d
|
||||
- delay: 100
|
||||
content: a
|
||||
- delay: 100
|
||||
content: t
|
||||
- delay: 100
|
||||
content: e
|
||||
- delay: 100
|
||||
content: ' '
|
||||
- delay: 100
|
||||
content: '2'
|
||||
- delay: 100
|
||||
content: '0'
|
||||
- delay: 100
|
||||
content: '2'
|
||||
- delay: 100
|
||||
content: '1'
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: '0'
|
||||
- delay: 100
|
||||
content: '1'
|
||||
- delay: 100
|
||||
content: '-'
|
||||
- delay: 100
|
||||
content: '0'
|
||||
- delay: 100
|
||||
content: '1'
|
||||
- delay: 100
|
||||
content: ' '
|
||||
- delay: 100
|
||||
content: t
|
||||
- delay: 100
|
||||
content: r
|
||||
- delay: 100
|
||||
content: i
|
||||
- delay: 100
|
||||
content: p
|
||||
- delay: 300
|
||||
content: "\e[?2004l\r\r\n"
|
||||
- delay: 500
|
||||
content: "Using last opened Photos library: /Users/user/Pictures/Photos Library.photoslibrary\r\n"
|
||||
- delay: 8204
|
||||
content: "Exporting 79 photos to /Users/user/trip...\r\n"
|
||||
- delay: 321
|
||||
content: "Processed: 79 photos, exported: 80, missing: 0, error: 0\r\nElapsed time: 0.321 seconds\r\n"
|
||||
- delay: 317
|
||||
content: "\e[1m\e[7m%\e[27m\e[1m\e[0m \r \r\e]7;file://wingeier-macOS/Users/aravindo/Downloads\a\r\e[0m\e[27m\e[24m\e[J❯ \e[K\e[?2004h"
|
||||
|
||||
- delay: 4252
|
||||
content: "\e[7mdu -h trip\e[27m"
|
||||
- delay: 487
|
||||
content: "\e[10D\e[27md\e[27mu\e[27m \e[27m-\e[27mh\e[27m \e[27mt\e[27mr\e[27mi\e[27mp\e[?2004l\r\r\n"
|
||||
- delay: 7
|
||||
content: "229M\ttrip/2021/01/03\r\n712K\ttrip/2021/01/02\r\n7.5M\ttrip/2021/01/01\r\n237M\ttrip/2021/01\r\n237M\ttrip/2021\r\n238M\ttrip\r\n\e[1m\e[7m%\e[27m\e[1m\e[0m \r \r\e]7;file://wingeier-macOS/Users/aravindo/Downloads\a\r\e[0m\e[27m\e[24m\e[J❯ \e[K\e[?2004h"
|
||||
- delay: 4280
|
||||
content: "\e[7mfind trip | head -20\e[27m"
|
||||
- delay: 923
|
||||
content: "\e[20D\e[27mf\e[27mi\e[27mn\e[27md\e[27m \e[27mt\e[27mr\e[27mi\e[27mp\e[27m \e[27m|\e[27m \e[27mh\e[27me\e[27ma\e[27md\e[27m \e[27m-\e[27m2\e[27m0\e[?2004l\r\r\n"
|
||||
- delay: 5
|
||||
content: "trip\r\ntrip/2021\r\ntrip/2021/01\r\ntrip/2021/01/03\r\ntrip/2021/01/03/IMG_1234 (1).HEIC\r\ntrip/2021/01/03/IMG_1267.HEIC\r\ntrip/2021/01/03/IMG_1226.HEIC\r\ntrip/2021/01/03/IMG_1271.HEIC\r\ntrip/2021/01/03/IMG_1232 (1).JPG\r\ntrip/2021/01/03/IMG_1270.HEIC\r\ntrip/2021/01/03/IMG_1231.HEIC\r\ntrip/2021/01/03/IMG_6926.JPG\r\ntrip/2021/01/03/IMG_6932.JPG\r\ntrip/2021/01/03/IMG_1266.HEIC\r\ntrip/2021/01/03/IMG_6933.JPG\r\ntrip/2021/01/03/IMG_6927.JPG\r\ntrip/2021/01/03/IMG_1233 (1).JPG\r\ntrip/2021/01/03/IMG_1228 (1).HEIC\r\ntrip/2021/01/03/IMG_6931.JPG\r\ntrip/2021/01/03/IMG_6930.JPG\r\n\e[1m\e[7m%\e[27m\e[1m\e[0m \r \r\e]7;file://wingeier-macOS/Users/aravindo/Downloads\a\r\e[0m\e[27m\e[24m\e[J❯ \e[K\e[?2004h"
|
||||
- delay: 3615
|
||||
content: "\e[?2004l\r\r\n"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@ Constants used by osxphotos
|
||||
import os.path
|
||||
from datetime import datetime
|
||||
|
||||
OSXPHOTOS_URL = "https://github.com/RhetTbull/osxphotos"
|
||||
|
||||
# Time delta: add this to Photos times to get unix time
|
||||
# Apple Epoch is Jan 1, 2001
|
||||
TIME_DELTA = (datetime(2001, 1, 1, 0, 0) - datetime(1970, 1, 1, 0, 0)).total_seconds()
|
||||
@@ -58,8 +60,16 @@ _DB_TABLE_NAMES = {
|
||||
},
|
||||
}
|
||||
|
||||
# which major version operating systems have been tested
|
||||
_TESTED_OS_VERSIONS = ["12", "13", "14", "15", "16"]
|
||||
# which version operating systems have been tested
|
||||
_TESTED_OS_VERSIONS = [
|
||||
("10", "12"),
|
||||
("10", "13"),
|
||||
("10", "14"),
|
||||
("10", "15"),
|
||||
("10", "16"),
|
||||
("11", "0"),
|
||||
("11", "1"),
|
||||
]
|
||||
|
||||
# Photos 5 has persons who are empty string if unidentified face
|
||||
_UNKNOWN_PERSON = "_UNKNOWN_"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.39.9"
|
||||
|
||||
|
||||
__version__ = "0.39.19"
|
||||
|
||||
@@ -60,6 +60,11 @@ class FileUtilABC(ABC):
|
||||
def convert_to_jpeg(cls, src_file, dest_file, compression_quality=1.0):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def rename(cls, src, dest):
|
||||
pass
|
||||
|
||||
|
||||
class FileUtilMacOS(FileUtilABC):
|
||||
""" Various file utilities """
|
||||
@@ -201,6 +206,21 @@ class FileUtilMacOS(FileUtilABC):
|
||||
src_file, dest_file, compression_quality=compression_quality
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def rename(cls, src, dest):
|
||||
""" Copy src to dest
|
||||
|
||||
Args:
|
||||
src: path to source file
|
||||
dest: path to destination file
|
||||
|
||||
Returns:
|
||||
Name of renamed file (dest)
|
||||
|
||||
"""
|
||||
os.rename(str(src), str(dest))
|
||||
return dest
|
||||
|
||||
@staticmethod
|
||||
def _sig(st):
|
||||
""" return tuple of (mode, size, mtime) of file based on os.stat
|
||||
@@ -266,3 +286,7 @@ class FileUtilNoOp(FileUtil):
|
||||
@classmethod
|
||||
def convert_to_jpeg(cls, src_file, dest_file, compression_quality=1.0):
|
||||
cls.verbose(f"convert_to_jpeg: {src_file}, {dest_file}, {compression_quality}")
|
||||
|
||||
@classmethod
|
||||
def rename(cls, src, dest):
|
||||
cls.verbose(f"rename: {src}, {dest}")
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
""" ImageConverter class
|
||||
Convert an image to JPEG using CoreImage --
|
||||
Convert an image to JPEG using CoreImage --
|
||||
for example, RAW to JPEG. Only works if Mac equipped with GPU. """
|
||||
|
||||
# reference: https://stackoverflow.com/questions/59330149/coreimage-ciimage-write-jpg-is-shifting-colors-macos/59334308#59334308
|
||||
|
||||
import pathlib
|
||||
|
||||
import objc
|
||||
import Metal
|
||||
import Quartz
|
||||
from Cocoa import NSURL
|
||||
@@ -20,6 +21,7 @@ class ImageConversionError(Exception):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ImageConverter:
|
||||
""" Convert images to jpeg. This class is a singleton
|
||||
which will re-use the Core Image CIContext to avoid
|
||||
@@ -67,49 +69,58 @@ class ImageConverter:
|
||||
ImageConversionError if error during conversion
|
||||
"""
|
||||
|
||||
# accept input_path or output_path as pathlib.Path
|
||||
if not isinstance(input_path, str):
|
||||
input_path = str(input_path)
|
||||
# Set up a dedicated objc autorelease pool for this function call.
|
||||
# This is to ensure that all the NSObjects are cleaned up after each
|
||||
# call to prevent memory leaks.
|
||||
# https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
|
||||
# https://pyobjc.readthedocs.io/en/latest/api/module-objc.html#memory-management
|
||||
with objc.autorelease_pool():
|
||||
# accept input_path or output_path as pathlib.Path
|
||||
if not isinstance(input_path, str):
|
||||
input_path = str(input_path)
|
||||
|
||||
if not isinstance(output_path, str):
|
||||
output_path = str(output_path)
|
||||
if not isinstance(output_path, str):
|
||||
output_path = str(output_path)
|
||||
|
||||
if not pathlib.Path(input_path).is_file():
|
||||
raise FileNotFoundError(f"could not find {input_path}")
|
||||
if not pathlib.Path(input_path).is_file():
|
||||
raise FileNotFoundError(f"could not find {input_path}")
|
||||
|
||||
if not (0.0 <= compression_quality <= 1.0):
|
||||
raise ValueError(
|
||||
"illegal value for compression_quality: {compression_quality}"
|
||||
if not (0.0 <= compression_quality <= 1.0):
|
||||
raise ValueError(
|
||||
"illegal value for compression_quality: {compression_quality}"
|
||||
)
|
||||
|
||||
input_url = NSURL.fileURLWithPath_(input_path)
|
||||
output_url = NSURL.fileURLWithPath_(output_path)
|
||||
|
||||
with pipes() as (out, err):
|
||||
# capture stdout and stderr from system calls
|
||||
# otherwise, Quartz.CIImage.imageWithContentsOfURL_
|
||||
# prints to stderr something like:
|
||||
# 2020-09-20 20:55:25.538 python[73042:5650492] Creating client/daemon connection: B8FE995E-3F27-47F4-9FA8-559C615FD774
|
||||
# 2020-09-20 20:55:25.652 python[73042:5650492] Got the query meta data reply for: com.apple.MobileAsset.RawCamera.Camera, response: 0
|
||||
input_image = Quartz.CIImage.imageWithContentsOfURL_(input_url)
|
||||
|
||||
if input_image is None:
|
||||
raise ImageConversionError(f"Could not create CIImage for {input_path}")
|
||||
|
||||
output_colorspace = input_image.colorSpace() or Quartz.CGColorSpaceCreateWithName(
|
||||
Quartz.CoreGraphics.kCGColorSpaceSRGB
|
||||
)
|
||||
|
||||
input_url = NSURL.fileURLWithPath_(input_path)
|
||||
output_url = NSURL.fileURLWithPath_(output_path)
|
||||
|
||||
with pipes() as (out, err):
|
||||
# capture stdout and stderr from system calls
|
||||
# otherwise, Quartz.CIImage.imageWithContentsOfURL_
|
||||
# prints to stderr something like:
|
||||
# 2020-09-20 20:55:25.538 python[73042:5650492] Creating client/daemon connection: B8FE995E-3F27-47F4-9FA8-559C615FD774
|
||||
# 2020-09-20 20:55:25.652 python[73042:5650492] Got the query meta data reply for: com.apple.MobileAsset.RawCamera.Camera, response: 0
|
||||
input_image = Quartz.CIImage.imageWithContentsOfURL_(input_url)
|
||||
|
||||
if input_image is None:
|
||||
raise ImageConversionError(f"Could not create CIImage for {input_path}")
|
||||
|
||||
output_colorspace = input_image.colorSpace() or Quartz.CGColorSpaceCreateWithName(
|
||||
Quartz.CoreGraphics.kCGColorSpaceSRGB
|
||||
)
|
||||
|
||||
output_options = NSDictionary.dictionaryWithDictionary_(
|
||||
{"kCGImageDestinationLossyCompressionQuality": compression_quality}
|
||||
)
|
||||
_, error = self.context.writeJPEGRepresentationOfImage_toURL_colorSpace_options_error_(
|
||||
input_image, output_url, output_colorspace, output_options, None
|
||||
)
|
||||
if not error:
|
||||
return True
|
||||
else:
|
||||
raise ImageConversionError(
|
||||
"Error converting file {input_path} to jpeg at {output_path}: {error}"
|
||||
output_options = NSDictionary.dictionaryWithDictionary_(
|
||||
{"kCGImageDestinationLossyCompressionQuality": compression_quality}
|
||||
)
|
||||
(
|
||||
_,
|
||||
error,
|
||||
) = self.context.writeJPEGRepresentationOfImage_toURL_colorSpace_options_error_(
|
||||
input_image, output_url, output_colorspace, output_options, None
|
||||
)
|
||||
if not error:
|
||||
return True
|
||||
else:
|
||||
raise ImageConversionError(
|
||||
"Error converting file {input_path} to jpeg at {output_path}: {error}"
|
||||
)
|
||||
|
||||
|
||||
@@ -49,7 +49,16 @@ from ..photokit import (
|
||||
PhotoKitFetchFailed,
|
||||
PhotoLibrary,
|
||||
)
|
||||
from ..utils import dd_to_dms_str, findfiles, noop
|
||||
from ..utils import dd_to_dms_str, findfiles, noop, get_preferred_uti_extension
|
||||
|
||||
# retry if use_photos_export fails the first time (which sometimes it does)
|
||||
MAX_PHOTOSCRIPT_RETRIES = 3
|
||||
|
||||
|
||||
class ExportError(Exception):
|
||||
""" error during export """
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ExportResults:
|
||||
@@ -114,10 +123,10 @@ class ExportResults:
|
||||
+ self.sidecar_xmp_written
|
||||
+ self.sidecar_xmp_skipped
|
||||
+ self.missing
|
||||
+ self.error
|
||||
)
|
||||
files += [x[0] for x in self.exiftool_warning]
|
||||
files += [x[0] for x in self.exiftool_error]
|
||||
files += [x[0] for x in self.error]
|
||||
|
||||
files = list(set(files))
|
||||
return files
|
||||
@@ -188,56 +197,33 @@ def _export_photo_uuid_applescript(
|
||||
):
|
||||
"""Export photo to dest path using applescript to control Photos
|
||||
If photo is a live photo, exports both the photo and associated .mov file
|
||||
uuid: UUID of photo to export
|
||||
dest: destination path to export to
|
||||
filestem: (string) if provided, exported filename will be named stem.ext
|
||||
where ext is extension of the file exported by photos (e.g. .jpeg, .mov, etc)
|
||||
If not provided, file will be named with whatever name Photos uses
|
||||
If filestem.ext exists, it wil be overwritten
|
||||
original: (boolean) if True, export original image; default = True
|
||||
edited: (boolean) if True, export edited photo; default = False
|
||||
If photo not edited and edited=True, will still export the original image
|
||||
caller must verify image has been edited
|
||||
*Note*: must be called with either edited or original but not both,
|
||||
will raise error if called with both edited and original = True
|
||||
live_photo: (boolean) if True, export associated .mov live photo; default = False
|
||||
timeout: timeout value in seconds; export will fail if applescript run time exceeds timeout
|
||||
burst: (boolean) set to True if file is a burst image to avoid Photos export error
|
||||
dry_run: (boolean) set to True to run in "dry run" mode which will download file but not actually copy to destination
|
||||
|
||||
Args:
|
||||
uuid: UUID of photo to export
|
||||
dest: destination path to export to
|
||||
filestem: (string) if provided, exported filename will be named stem.ext
|
||||
where ext is extension of the file exported by photos (e.g. .jpeg, .mov, etc)
|
||||
If not provided, file will be named with whatever name Photos uses
|
||||
If filestem.ext exists, it wil be overwritten
|
||||
original: (boolean) if True, export original image; default = True
|
||||
edited: (boolean) if True, export edited photo; default = False
|
||||
If photo not edited and edited=True, will still export the original image
|
||||
caller must verify image has been edited
|
||||
*Note*: must be called with either edited or original but not both,
|
||||
will raise error if called with both edited and original = True
|
||||
live_photo: (boolean) if True, export associated .mov live photo; default = False
|
||||
timeout: timeout value in seconds; export will fail if applescript run time exceeds timeout
|
||||
burst: (boolean) set to True if file is a burst image to avoid Photos export error
|
||||
dry_run: (boolean) set to True to run in "dry run" mode which will download file but not actually copy to destination
|
||||
|
||||
Returns: list of paths to exported file(s) or None if export failed
|
||||
|
||||
Raises: ExportError if error during export
|
||||
|
||||
Note: For Live Photos, if edited=True, will export a jpeg but not the movie, even if photo
|
||||
has not been edited. This is due to how Photos Applescript interface works.
|
||||
"""
|
||||
|
||||
# setup the applescript to do the export
|
||||
# export_scpt = AppleScript(
|
||||
# """
|
||||
# on export_by_uuid(theUUID, thePath, original, edited, theTimeOut)
|
||||
# tell application "Photos"
|
||||
# set thePath to thePath
|
||||
# set theItem to media item id theUUID
|
||||
# set theFilename to filename of theItem
|
||||
# set itemList to {theItem}
|
||||
|
||||
# if original then
|
||||
# with timeout of theTimeOut seconds
|
||||
# export itemList to POSIX file thePath with using originals
|
||||
# end timeout
|
||||
# end if
|
||||
|
||||
# if edited then
|
||||
# with timeout of theTimeOut seconds
|
||||
# export itemList to POSIX file thePath
|
||||
# end timeout
|
||||
# end if
|
||||
|
||||
# return theFilename
|
||||
# end tell
|
||||
|
||||
# end export_by_uuid
|
||||
# """
|
||||
# )
|
||||
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError(f"dest {dest} must be a directory")
|
||||
@@ -250,15 +236,18 @@ def _export_photo_uuid_applescript(
|
||||
exported_files = []
|
||||
filename = None
|
||||
try:
|
||||
photo = photoscript.Photo(uuid)
|
||||
filename = photo.filename
|
||||
exported_files = photo.export(tmpdir.name, original=original, timeout=timeout)
|
||||
# filename = export_scpt.call(
|
||||
# "export_by_uuid", uuid, tmpdir.name, original, edited, timeout
|
||||
# )
|
||||
# I've seen intermittent failures with the PhotoScript export so retry if
|
||||
# export doesn't return anything
|
||||
retries = 0
|
||||
while not exported_files and retries < MAX_PHOTOSCRIPT_RETRIES:
|
||||
photo = photoscript.Photo(uuid)
|
||||
filename = photo.filename
|
||||
exported_files = photo.export(
|
||||
tmpdir.name, original=original, timeout=timeout
|
||||
)
|
||||
retries += 1
|
||||
except Exception as e:
|
||||
logging.warning(f"Error exporting uuid {uuid}: {e}")
|
||||
return None
|
||||
raise ExportError(e)
|
||||
|
||||
if exported_files and filename:
|
||||
# need to find actual filename as sometimes Photos renames JPG to jpeg on export
|
||||
@@ -289,7 +278,7 @@ def _export_photo_uuid_applescript(
|
||||
exported_paths.append(str(dest_new))
|
||||
return exported_paths
|
||||
else:
|
||||
return None
|
||||
return []
|
||||
|
||||
|
||||
# _check_export_suffix is not a class method, don't import this into PhotoInfo
|
||||
@@ -332,6 +321,34 @@ def _check_export_suffix(src, dest, edited):
|
||||
)
|
||||
|
||||
|
||||
# not a class method, don't import into PhotoInfo
|
||||
def rename_jpeg_files(files, jpeg_ext, fileutil):
|
||||
""" rename any jpeg files in files so that extension matches jpeg_ext
|
||||
|
||||
Args:
|
||||
files: list of file paths
|
||||
jpeg_ext: extension to use for jpeg files found in files, e.g. "jpg"
|
||||
fileutil: a FileUtil object
|
||||
|
||||
Returns:
|
||||
list of files with updated names
|
||||
|
||||
Note: If non-jpeg files found, they will be ignore and returned in the return list
|
||||
"""
|
||||
jpeg_ext = "." + jpeg_ext
|
||||
jpegs = [".jpeg", ".jpg"]
|
||||
new_files = []
|
||||
for file in files:
|
||||
path = pathlib.Path(file)
|
||||
if path.suffix.lower() in jpegs and path.suffix != jpeg_ext:
|
||||
new_file = path.parent / (path.stem + jpeg_ext)
|
||||
fileutil.rename(file, new_file)
|
||||
new_files.append(new_file)
|
||||
else:
|
||||
new_files.append(file)
|
||||
return new_files
|
||||
|
||||
|
||||
def export(
|
||||
self,
|
||||
dest,
|
||||
@@ -458,6 +475,7 @@ def export2(
|
||||
exiftool_flags=None,
|
||||
merge_exif_keywords=False,
|
||||
merge_exif_persons=False,
|
||||
jpeg_ext=None,
|
||||
):
|
||||
"""export photo, like export but with update and dry_run options
|
||||
dest: must be valid destination path or exception raised
|
||||
@@ -509,6 +527,7 @@ def export2(
|
||||
exiftool_flags: optional list of flags to pass to exiftool when using exiftool option, e.g ["-m", "-F"]
|
||||
merge_exif_keywords: boolean; if True, merged keywords found in file's exif data (requires exiftool)
|
||||
merge_exif_persons: boolean; if True, merged persons found in file's exif data (requires exiftool)
|
||||
jpeg_ext: if set, will use this value for extension on jpegs converted to jpeg with convert_to_jpeg; if not set, uses jpeg; do not include the leading "."
|
||||
|
||||
Returns: ExportResults class
|
||||
ExportResults has attributes:
|
||||
@@ -527,6 +546,7 @@ def export2(
|
||||
"sidecar_xmp_skipped",
|
||||
"missing",
|
||||
"error",
|
||||
"error_str",
|
||||
"exiftool_warning",
|
||||
"exiftool_error",
|
||||
|
||||
@@ -553,24 +573,6 @@ def export2(
|
||||
# e.g. name will be filename_edited.jpg
|
||||
edited_identifier = "_edited"
|
||||
|
||||
# list of all files exported during this call to export
|
||||
exported_files = []
|
||||
|
||||
# list of new files during update
|
||||
update_new_files = []
|
||||
|
||||
# list of files that were updated
|
||||
update_updated_files = []
|
||||
|
||||
# list of all files skipped because they do not need to be updated (for use with update=True)
|
||||
update_skipped_files = []
|
||||
|
||||
# list of all files with utime touched (touch_file = True)
|
||||
touched_files = []
|
||||
|
||||
# list of all files convereted to jpeg
|
||||
converted_to_jpeg_files = []
|
||||
|
||||
# check edited and raise exception trying to export edited version of
|
||||
# photo that hasn't been edited
|
||||
if edited and not self.hasadjustments:
|
||||
@@ -614,7 +616,8 @@ def export2(
|
||||
if convert_to_jpeg and self.isphoto and uti != "public.jpeg":
|
||||
# not a jpeg but will convert to jpeg upon export so fix file extension
|
||||
fname_new = pathlib.Path(fname)
|
||||
fname = str(fname_new.parent / f"{fname_new.stem}.jpeg")
|
||||
ext = "." + jpeg_ext if jpeg_ext else ".jpeg"
|
||||
fname = str(fname_new.parent / f"{fname_new.stem}{ext}")
|
||||
else:
|
||||
# nothing to convert
|
||||
convert_to_jpeg = False
|
||||
@@ -645,6 +648,7 @@ def export2(
|
||||
f"destination exists ({dest}); overwrite={overwrite}, increment={increment}"
|
||||
)
|
||||
|
||||
all_results = ExportResults()
|
||||
if not use_photos_export:
|
||||
# find the source file on disk and export
|
||||
# get path to source file and verify it's not None and is valid file
|
||||
@@ -738,12 +742,7 @@ def export2(
|
||||
jpeg_quality=jpeg_quality,
|
||||
ignore_signature=ignore_signature,
|
||||
)
|
||||
exported_files = results.exported
|
||||
update_new_files = results.new
|
||||
update_updated_files = results.updated
|
||||
update_skipped_files = results.skipped
|
||||
touched_files = results.touched
|
||||
converted_to_jpeg_files = results.converted_to_jpeg
|
||||
all_results += results
|
||||
|
||||
# copy live photo associated .mov if requested
|
||||
if live_photo and self.live_photo:
|
||||
@@ -764,12 +763,7 @@ def export2(
|
||||
fileutil=fileutil,
|
||||
ignore_signature=ignore_signature,
|
||||
)
|
||||
exported_files.extend(results.exported)
|
||||
update_new_files.extend(results.new)
|
||||
update_updated_files.extend(results.updated)
|
||||
update_skipped_files.extend(results.skipped)
|
||||
touched_files.extend(results.touched)
|
||||
converted_to_jpeg_files.extend(results.converted_to_jpeg)
|
||||
all_results += results
|
||||
|
||||
# copy associated RAW image if requested
|
||||
if raw_photo and self.has_raw:
|
||||
@@ -791,15 +785,11 @@ def export2(
|
||||
jpeg_quality=jpeg_quality,
|
||||
ignore_signature=ignore_signature,
|
||||
)
|
||||
exported_files.extend(results.exported)
|
||||
update_new_files.extend(results.new)
|
||||
update_updated_files.extend(results.updated)
|
||||
update_skipped_files.extend(results.skipped)
|
||||
touched_files.extend(results.touched)
|
||||
converted_to_jpeg_files.extend(results.converted_to_jpeg)
|
||||
all_results += results
|
||||
else:
|
||||
# TODO: move this big if/else block to separate functions
|
||||
# e.g. _export_with_photos_export or such
|
||||
# use_photo_export
|
||||
exported = []
|
||||
# export live_photo .mov file?
|
||||
live_photo = True if live_photo and self.live_photo else False
|
||||
if edited or self.shared:
|
||||
@@ -813,7 +803,10 @@ def export2(
|
||||
else:
|
||||
# didn't get passed a filename, add _edited
|
||||
filestem = f"{dest.stem}{edited_identifier}"
|
||||
dest = dest.parent / f"{filestem}.jpeg"
|
||||
uti = self.uti_edited if edited and self.uti_edited else self.uti
|
||||
ext = get_preferred_uti_extension(uti)
|
||||
dest = dest.parent / f"{filestem}{ext}"
|
||||
|
||||
if use_photokit:
|
||||
photolib = PhotoLibrary()
|
||||
photo = None
|
||||
@@ -829,25 +822,40 @@ def export2(
|
||||
photo = [p for p in bursts if p.uuid.startswith(self.uuid)]
|
||||
photo = photo[0] if photo else None
|
||||
if not photo:
|
||||
logging.warning(f"PhotoKitFetchFailed exception exporting photo {self.uuid}: {e}")
|
||||
all_results.error.append(
|
||||
(
|
||||
str(dest),
|
||||
f"PhotoKitFetchFailed exception exporting photo {self.uuid}: {e}",
|
||||
)
|
||||
)
|
||||
if photo:
|
||||
exported = photo.export(
|
||||
dest.parent, dest.name, version=PHOTOS_VERSION_CURRENT
|
||||
)
|
||||
else:
|
||||
exported = []
|
||||
if not dry_run:
|
||||
try:
|
||||
exported = photo.export(
|
||||
dest.parent, dest.name, version=PHOTOS_VERSION_CURRENT
|
||||
)
|
||||
all_results.exported.extend(exported)
|
||||
except Exception as e:
|
||||
all_results.error.append((str(dest), e))
|
||||
else:
|
||||
# dry_run, don't actually export
|
||||
all_results.exported.append(str(dest))
|
||||
else:
|
||||
exported = _export_photo_uuid_applescript(
|
||||
self.uuid,
|
||||
dest.parent,
|
||||
filestem=filestem,
|
||||
original=False,
|
||||
edited=True,
|
||||
live_photo=live_photo,
|
||||
timeout=timeout,
|
||||
burst=self.burst,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
try:
|
||||
exported = _export_photo_uuid_applescript(
|
||||
self.uuid,
|
||||
dest.parent,
|
||||
filestem=filestem,
|
||||
original=False,
|
||||
edited=True,
|
||||
live_photo=live_photo,
|
||||
timeout=timeout,
|
||||
burst=self.burst,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
all_results.exported.extend(exported)
|
||||
except ExportError as e:
|
||||
all_results.error.append((str(dest), e))
|
||||
else:
|
||||
# export original version and not edited
|
||||
filestem = dest.stem
|
||||
@@ -866,37 +874,48 @@ def export2(
|
||||
photo = [p for p in bursts if p.uuid.startswith(self.uuid)]
|
||||
photo = photo[0] if photo else None
|
||||
if photo:
|
||||
exported = photo.export(
|
||||
dest.parent, dest.name, version=PHOTOS_VERSION_ORIGINAL
|
||||
)
|
||||
else:
|
||||
exported = []
|
||||
if not dry_run:
|
||||
try:
|
||||
exported = photo.export(
|
||||
dest.parent, dest.name, version=PHOTOS_VERSION_ORIGINAL
|
||||
)
|
||||
all_results.exported.extend(exported)
|
||||
except Exception as e:
|
||||
all_results.error.append((str(dest), e))
|
||||
else:
|
||||
# dry_run, don't actually export
|
||||
all_results.exported.append(str(dest))
|
||||
else:
|
||||
exported = _export_photo_uuid_applescript(
|
||||
self.uuid,
|
||||
dest.parent,
|
||||
filestem=filestem,
|
||||
original=True,
|
||||
edited=False,
|
||||
live_photo=live_photo,
|
||||
timeout=timeout,
|
||||
burst=self.burst,
|
||||
dry_run=dry_run,
|
||||
try:
|
||||
exported = _export_photo_uuid_applescript(
|
||||
self.uuid,
|
||||
dest.parent,
|
||||
filestem=filestem,
|
||||
original=True,
|
||||
edited=False,
|
||||
live_photo=live_photo,
|
||||
timeout=timeout,
|
||||
burst=self.burst,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
all_results.exported.extend(exported)
|
||||
except ExportError as e:
|
||||
all_results.error.append((str(dest), e))
|
||||
if all_results.exported:
|
||||
if jpeg_ext:
|
||||
# use_photos_export (both PhotoKit and AppleScript) don't use the
|
||||
# file extension provided (instead they use extension for UTI)
|
||||
# so if jpeg_ext is set, rename any non-conforming jpegs
|
||||
all_results.exported = rename_jpeg_files(
|
||||
all_results.exported, jpeg_ext, fileutil
|
||||
)
|
||||
if exported:
|
||||
if touch_file:
|
||||
for exported_file in exported:
|
||||
touched_files.append(exported_file)
|
||||
for exported_file in all_results.exported:
|
||||
all_results.touched.append(exported_file)
|
||||
ts = int(self.date.timestamp())
|
||||
fileutil.utime(exported_file, (ts, ts))
|
||||
exported_files.extend(exported)
|
||||
if update:
|
||||
update_new_files.extend(exported)
|
||||
|
||||
else:
|
||||
logging.warning(
|
||||
f"Error exporting photo {self.uuid} to {dest} with use_photos_export"
|
||||
)
|
||||
all_results.new.extend(all_results.exported)
|
||||
|
||||
# export metadata
|
||||
sidecars = []
|
||||
@@ -1008,14 +1027,10 @@ def export2(
|
||||
|
||||
# if exiftool, write the metadata
|
||||
if update:
|
||||
exif_files = update_new_files + update_updated_files + update_skipped_files
|
||||
exif_files = all_results.new + all_results.updated + all_results.skipped
|
||||
else:
|
||||
exif_files = exported_files
|
||||
exif_files = all_results.exported
|
||||
|
||||
exif_files_updated = []
|
||||
exiftool_warning = []
|
||||
exiftool_error = []
|
||||
errors = []
|
||||
# TODO: remove duplicative code from below
|
||||
if exiftool and update and exif_files:
|
||||
for exported_file in exif_files:
|
||||
@@ -1054,10 +1069,10 @@ def export2(
|
||||
merge_exif_persons=merge_exif_persons,
|
||||
)
|
||||
if warning_:
|
||||
exiftool_warning.append((exported_file, warning_))
|
||||
all_results.exiftool_warning.append((exported_file, warning_))
|
||||
if error_:
|
||||
exiftool_error.append((exported_file, error_))
|
||||
errors.append(exported_file)
|
||||
all_results.exiftool_error.append((exported_file, error_))
|
||||
all_results.error.append((exported_file, error_))
|
||||
|
||||
export_db.set_exifdata_for_file(
|
||||
exported_file,
|
||||
@@ -1074,7 +1089,7 @@ def export2(
|
||||
export_db.set_stat_exif_for_file(
|
||||
exported_file, fileutil.file_sig(exported_file)
|
||||
)
|
||||
exif_files_updated.append(exported_file)
|
||||
all_results.exif_updated.append(exported_file)
|
||||
else:
|
||||
verbose(f"Skipped up to date exiftool metadata for {exported_file}")
|
||||
elif exiftool and exif_files:
|
||||
@@ -1093,10 +1108,10 @@ def export2(
|
||||
merge_exif_persons=merge_exif_persons,
|
||||
)
|
||||
if warning_:
|
||||
exiftool_warning.append((exported_file, warning_))
|
||||
all_results.exiftool_warning.append((exported_file, warning_))
|
||||
if error_:
|
||||
exiftool_error.append((exported_file, error_))
|
||||
errors.append(exported_file)
|
||||
all_results.exiftool_error.append((exported_file, error_))
|
||||
all_results.error.append((exported_file, error_))
|
||||
|
||||
export_db.set_exifdata_for_file(
|
||||
exported_file,
|
||||
@@ -1113,36 +1128,25 @@ def export2(
|
||||
export_db.set_stat_exif_for_file(
|
||||
exported_file, fileutil.file_sig(exported_file)
|
||||
)
|
||||
exif_files_updated.append(exported_file)
|
||||
all_results.exif_updated.append(exported_file)
|
||||
|
||||
if touch_file:
|
||||
for exif_file in exif_files_updated:
|
||||
for exif_file in all_results.exif_updated:
|
||||
verbose(f"Updating file modification time for {exif_file}")
|
||||
touched_files.append(exif_file)
|
||||
all_results.touched.append(exif_file)
|
||||
ts = int(self.date.timestamp())
|
||||
fileutil.utime(exif_file, (ts, ts))
|
||||
|
||||
touched_files = list(set(touched_files))
|
||||
all_results.touched = list(set(all_results.touched))
|
||||
|
||||
results = ExportResults(
|
||||
exported=exported_files,
|
||||
new=update_new_files,
|
||||
updated=update_updated_files,
|
||||
skipped=update_skipped_files,
|
||||
exif_updated=exif_files_updated,
|
||||
touched=touched_files,
|
||||
converted_to_jpeg=converted_to_jpeg_files,
|
||||
sidecar_json_written=sidecar_json_files_written,
|
||||
sidecar_json_skipped=sidecar_json_files_skipped,
|
||||
sidecar_exiftool_written=sidecar_exiftool_files_written,
|
||||
sidecar_exiftool_skipped=sidecar_exiftool_files_skipped,
|
||||
sidecar_xmp_written=sidecar_xmp_files_written,
|
||||
sidecar_xmp_skipped=sidecar_xmp_files_skipped,
|
||||
error=errors,
|
||||
exiftool_error=exiftool_error,
|
||||
exiftool_warning=exiftool_warning,
|
||||
)
|
||||
return results
|
||||
all_results.sidecar_json_written = sidecar_json_files_written
|
||||
all_results.sidecar_json_skipped = sidecar_json_files_skipped
|
||||
all_results.sidecar_exiftool_written = sidecar_exiftool_files_written
|
||||
all_results.sidecar_exiftool_skipped = sidecar_exiftool_files_skipped
|
||||
all_results.sidecar_xmp_written = sidecar_xmp_files_written
|
||||
all_results.sidecar_xmp_skipped = sidecar_xmp_files_skipped
|
||||
|
||||
return all_results
|
||||
|
||||
|
||||
def _export_photo(
|
||||
@@ -1502,7 +1506,7 @@ def _exiftool_dict(
|
||||
|
||||
if keyword_list:
|
||||
# remove duplicates
|
||||
keyword_list = sorted(list(set(keyword_list)))
|
||||
keyword_list = sorted(list(set([str(keyword) for keyword in keyword_list])))
|
||||
exif["IPTC:Keywords"] = keyword_list.copy()
|
||||
exif["XMP:Subject"] = keyword_list.copy()
|
||||
exif["XMP:TagsList"] = keyword_list.copy()
|
||||
@@ -1598,6 +1602,7 @@ def _get_exif_keywords(self):
|
||||
kw = exifdict[field]
|
||||
if kw and type(kw) != list:
|
||||
kw = [kw]
|
||||
kw = [str(k) for k in kw]
|
||||
keywords.extend(kw)
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -1614,6 +1619,7 @@ def _get_exif_persons(self):
|
||||
p = exifdict["XMP:PersonInImage"]
|
||||
if p and type(p) != list:
|
||||
p = [p]
|
||||
p = [str(p_) for p_ in p]
|
||||
persons.extend(p)
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -1818,3 +1824,4 @@ def _write_sidecar(self, filename, sidecar_str):
|
||||
f = open(filename, "w")
|
||||
f.write(sidecar_str)
|
||||
f.close()
|
||||
|
||||
|
||||
@@ -113,15 +113,15 @@ class PhotoInfo:
|
||||
# lastmodifieddate anytime photo database record is updated (e.g. adding tags)
|
||||
# only report lastmodified date for Photos <=4 if photo is edited;
|
||||
# even in this case, the date could be incorrect
|
||||
if self.hasadjustments or self._db._db_version > _PHOTOS_4_VERSION:
|
||||
imagedate = self._info["lastmodifieddate"]
|
||||
if imagedate:
|
||||
seconds = self._info["imageTimeZoneOffsetSeconds"] or 0
|
||||
delta = timedelta(seconds=seconds)
|
||||
tz = timezone(delta)
|
||||
return imagedate.astimezone(tz=tz)
|
||||
else:
|
||||
return None
|
||||
if not self.hasadjustments and self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
return None
|
||||
|
||||
imagedate = self._info["lastmodifieddate"]
|
||||
if imagedate:
|
||||
seconds = self._info["imageTimeZoneOffsetSeconds"] or 0
|
||||
delta = timedelta(seconds=seconds)
|
||||
tz = timezone(delta)
|
||||
return imagedate.astimezone(tz=tz)
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -501,37 +501,52 @@ class PhotoInfo:
|
||||
downloaded from cloud to local storate their status in the database might still show
|
||||
isMissing = 1
|
||||
"""
|
||||
return True if self._info["isMissing"] == 1 else False
|
||||
return self._info["isMissing"] == 1
|
||||
|
||||
@property
|
||||
def hasadjustments(self):
|
||||
""" True if picture has adjustments / edits """
|
||||
return True if self._info["hasAdjustments"] == 1 else False
|
||||
return self._info["hasAdjustments"] == 1
|
||||
|
||||
@property
|
||||
def external_edit(self):
|
||||
""" Returns True if picture was edited outside of Photos using external editor """
|
||||
return (
|
||||
True
|
||||
if self._info["adjustmentFormatID"] == "com.apple.Photos.externalEdit"
|
||||
else False
|
||||
)
|
||||
return self._info["adjustmentFormatID"] == "com.apple.Photos.externalEdit"
|
||||
|
||||
@property
|
||||
def favorite(self):
|
||||
""" True if picture is marked as favorite """
|
||||
return True if self._info["favorite"] == 1 else False
|
||||
return self._info["favorite"] == 1
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
""" True if picture is hidden """
|
||||
return True if self._info["hidden"] == 1 else False
|
||||
return self._info["hidden"] == 1
|
||||
|
||||
@property
|
||||
def visible(self):
|
||||
""" True if picture is visble """
|
||||
return self._info["visible"]
|
||||
|
||||
@property
|
||||
def intrash(self):
|
||||
""" True if picture is in trash ('Recently Deleted' folder)"""
|
||||
return self._info["intrash"]
|
||||
|
||||
@property
|
||||
def date_trashed(self):
|
||||
""" Date asset was placed in the trash or None """
|
||||
# TODO: add add_timezone(dt, offset_seconds) to datetime_utils
|
||||
# also update date_modified
|
||||
trasheddate = self._info["trasheddate"]
|
||||
if trasheddate:
|
||||
seconds = self._info["imageTimeZoneOffsetSeconds"] or 0
|
||||
delta = timedelta(seconds=seconds)
|
||||
tz = timezone(delta)
|
||||
return trasheddate.astimezone(tz=tz)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
""" returns (latitude, longitude) as float in degrees or None """
|
||||
@@ -551,14 +566,15 @@ class PhotoInfo:
|
||||
"""Returns Uniform Type Identifier (UTI) for the image
|
||||
for example: public.jpeg or com.apple.quicktime-movie
|
||||
"""
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
if self.hasadjustments:
|
||||
return self._info["UTI_edited"]
|
||||
elif self.has_raw and self.raw_original:
|
||||
# return UTI of the non-raw image to match Photos 5+ behavior
|
||||
return self._info["raw_pair_info"]["UTI"]
|
||||
else:
|
||||
return self._info["UTI"]
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION and self.hasadjustments:
|
||||
return self._info["UTI_edited"]
|
||||
elif (
|
||||
self._db._db_version <= _PHOTOS_4_VERSION
|
||||
and self.has_raw
|
||||
and self.raw_original
|
||||
):
|
||||
# return UTI of the non-raw image to match Photos 5+ behavior
|
||||
return self._info["raw_pair_info"]["UTI"]
|
||||
else:
|
||||
return self._info["UTI"]
|
||||
|
||||
@@ -597,12 +613,12 @@ class PhotoInfo:
|
||||
@property
|
||||
def ismovie(self):
|
||||
"""Returns True if file is a movie, otherwise False"""
|
||||
return True if self._info["type"] == _MOVIE_TYPE else False
|
||||
return self._info["type"] == _MOVIE_TYPE
|
||||
|
||||
@property
|
||||
def isphoto(self):
|
||||
"""Returns True if file is an image, otherwise False"""
|
||||
return True if self._info["type"] == _PHOTO_TYPE else False
|
||||
return self._info["type"] == _PHOTO_TYPE
|
||||
|
||||
@property
|
||||
def incloud(self):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
# add original=False to export instead of version= (and maybe others like path())
|
||||
# make burst/live methods get uuid from self instead of passing as arg
|
||||
|
||||
import copy
|
||||
import pathlib
|
||||
import threading
|
||||
import time
|
||||
@@ -169,12 +170,14 @@ class ImageData:
|
||||
requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.metadata = None
|
||||
self.uti = None
|
||||
self.image_data = None
|
||||
self.info = None
|
||||
self.orientation = None
|
||||
def __init__(
|
||||
self, metadata=None, uti=None, image_data=None, info=None, orientation=None
|
||||
):
|
||||
self.metadata = metadata
|
||||
self.uti = uti
|
||||
self.image_data = image_data
|
||||
self.info = info
|
||||
self.orientation = orientation
|
||||
|
||||
|
||||
class AVAssetData:
|
||||
@@ -475,44 +478,48 @@ class PhotoAsset:
|
||||
# if self.live:
|
||||
# raise NotImplementedError("Live photos not implemented yet")
|
||||
|
||||
filename = (
|
||||
pathlib.Path(filename) if filename else pathlib.Path(self.original_filename)
|
||||
)
|
||||
with objc.autorelease_pool():
|
||||
filename = (
|
||||
pathlib.Path(filename)
|
||||
if filename
|
||||
else pathlib.Path(self.original_filename)
|
||||
)
|
||||
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError("dest must be a valid directory: {dest}")
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError("dest must be a valid directory: {dest}")
|
||||
|
||||
output_file = None
|
||||
if self.isphoto:
|
||||
imagedata = self._request_image_data(version=version)
|
||||
ext = get_preferred_uti_extension(imagedata.uti)
|
||||
output_file = None
|
||||
if self.isphoto:
|
||||
imagedata = self._request_image_data(version=version)
|
||||
ext = get_preferred_uti_extension(imagedata.uti)
|
||||
|
||||
output_file = dest / f"{filename.stem}.{ext}"
|
||||
output_file = dest / f"{filename.stem}.{ext}"
|
||||
|
||||
if not overwrite:
|
||||
output_file = pathlib.Path(increment_filename(output_file))
|
||||
if not overwrite:
|
||||
output_file = pathlib.Path(increment_filename(output_file))
|
||||
|
||||
with open(output_file, "wb") as fd:
|
||||
fd.write(imagedata.image_data)
|
||||
elif self.ismovie:
|
||||
videodata = self._request_video_data(version=version)
|
||||
if videodata.asset is None:
|
||||
raise PhotoKitExportError("Could not get video for asset")
|
||||
with open(output_file, "wb") as fd:
|
||||
fd.write(imagedata.image_data)
|
||||
del imagedata
|
||||
elif self.ismovie:
|
||||
videodata = self._request_video_data(version=version)
|
||||
if videodata.asset is None:
|
||||
raise PhotoKitExportError("Could not get video for asset")
|
||||
|
||||
url = videodata.asset.URL()
|
||||
path = pathlib.Path(NSURL_to_path(url))
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError("Could not get path to video file")
|
||||
ext = path.suffix
|
||||
output_file = dest / f"{filename.stem}{ext}"
|
||||
url = videodata.asset.URL()
|
||||
path = pathlib.Path(NSURL_to_path(url))
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError("Could not get path to video file")
|
||||
ext = path.suffix
|
||||
output_file = dest / f"{filename.stem}{ext}"
|
||||
|
||||
if not overwrite:
|
||||
output_file = pathlib.Path(increment_filename(output_file))
|
||||
if not overwrite:
|
||||
output_file = pathlib.Path(increment_filename(output_file))
|
||||
|
||||
FileUtil.copy(path, output_file)
|
||||
FileUtil.copy(path, output_file)
|
||||
|
||||
return [str(output_file)]
|
||||
return [str(output_file)]
|
||||
|
||||
def _request_image_data(self, version=PHOTOS_VERSION_ORIGINAL):
|
||||
""" Request image data and metadata for self._phasset
|
||||
@@ -529,50 +536,56 @@ class PhotoAsset:
|
||||
|
||||
# reference: https://developer.apple.com/documentation/photokit/phimagemanager/3237282-requestimagedataandorientationfo?language=objc
|
||||
|
||||
if version not in [
|
||||
PHOTOS_VERSION_CURRENT,
|
||||
PHOTOS_VERSION_ORIGINAL,
|
||||
PHOTOS_VERSION_UNADJUSTED,
|
||||
]:
|
||||
raise ValueError("Invalid value for version")
|
||||
with objc.autorelease_pool():
|
||||
if version not in [
|
||||
PHOTOS_VERSION_CURRENT,
|
||||
PHOTOS_VERSION_ORIGINAL,
|
||||
PHOTOS_VERSION_UNADJUSTED,
|
||||
]:
|
||||
raise ValueError("Invalid value for version")
|
||||
|
||||
# pylint: disable=no-member
|
||||
options_request = Photos.PHImageRequestOptions.alloc().init()
|
||||
options_request.setNetworkAccessAllowed_(True)
|
||||
options_request.setSynchronous_(True)
|
||||
options_request.setVersion_(version)
|
||||
options_request.setDeliveryMode_(
|
||||
Photos.PHImageRequestOptionsDeliveryModeHighQualityFormat
|
||||
)
|
||||
requestdata = ImageData()
|
||||
event = threading.Event()
|
||||
|
||||
def handler(imageData, dataUTI, orientation, info):
|
||||
""" result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||
all returned by the request is set as properties of nonlocal data (Fetchdata object) """
|
||||
|
||||
nonlocal requestdata
|
||||
|
||||
options = {}
|
||||
# pylint: disable=no-member
|
||||
options[Quartz.kCGImageSourceShouldCache] = Foundation.kCFBooleanFalse
|
||||
imgSrc = Quartz.CGImageSourceCreateWithData(imageData, options)
|
||||
requestdata.metadata = Quartz.CGImageSourceCopyPropertiesAtIndex(
|
||||
imgSrc, 0, options
|
||||
options_request = Photos.PHImageRequestOptions.alloc().init()
|
||||
options_request.setNetworkAccessAllowed_(True)
|
||||
options_request.setSynchronous_(True)
|
||||
options_request.setVersion_(version)
|
||||
options_request.setDeliveryMode_(
|
||||
Photos.PHImageRequestOptionsDeliveryModeHighQualityFormat
|
||||
)
|
||||
requestdata.uti = dataUTI
|
||||
requestdata.orientation = orientation
|
||||
requestdata.info = info
|
||||
requestdata.image_data = imageData
|
||||
requestdata = ImageData()
|
||||
event = threading.Event()
|
||||
|
||||
event.set()
|
||||
def handler(imageData, dataUTI, orientation, info):
|
||||
""" result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||
all returned by the request is set as properties of nonlocal data (Fetchdata object) """
|
||||
|
||||
self._manager.requestImageDataAndOrientationForAsset_options_resultHandler_(
|
||||
self.phasset, options_request, handler
|
||||
)
|
||||
event.wait()
|
||||
self._imagedata = requestdata
|
||||
return requestdata
|
||||
nonlocal requestdata
|
||||
|
||||
options = {}
|
||||
# pylint: disable=no-member
|
||||
options[Quartz.kCGImageSourceShouldCache] = Foundation.kCFBooleanFalse
|
||||
imgSrc = Quartz.CGImageSourceCreateWithData(imageData, options)
|
||||
requestdata.metadata = Quartz.CGImageSourceCopyPropertiesAtIndex(
|
||||
imgSrc, 0, options
|
||||
)
|
||||
requestdata.uti = dataUTI
|
||||
requestdata.orientation = orientation
|
||||
requestdata.info = info
|
||||
requestdata.image_data = imageData
|
||||
|
||||
event.set()
|
||||
|
||||
self._manager.requestImageDataAndOrientationForAsset_options_resultHandler_(
|
||||
self.phasset, options_request, handler
|
||||
)
|
||||
event.wait()
|
||||
# options_request.dealloc()
|
||||
|
||||
# not sure why this is needed -- some weird ref count thing maybe
|
||||
# if I don't do this, memory leaks
|
||||
data = copy.copy(requestdata)
|
||||
del requestdata
|
||||
return data
|
||||
|
||||
def _make_result_handle_(self, data):
|
||||
""" Make handler function and threading event to use with
|
||||
@@ -634,37 +647,41 @@ class SlowMoVideoExporter(NSObject):
|
||||
Returns:
|
||||
path to exported file
|
||||
"""
|
||||
exporter = AVFoundation.AVAssetExportSession.alloc().initWithAsset_presetName_(
|
||||
self.avasset, AVFoundation.AVAssetExportPresetHighestQuality
|
||||
)
|
||||
exporter.setOutputURL_(self.url)
|
||||
exporter.setOutputFileType_(AVFoundation.AVFileTypeQuickTimeMovie)
|
||||
exporter.setShouldOptimizeForNetworkUse_(True)
|
||||
|
||||
self.done = False
|
||||
with objc.autorelease_pool():
|
||||
exporter = AVFoundation.AVAssetExportSession.alloc().initWithAsset_presetName_(
|
||||
self.avasset, AVFoundation.AVAssetExportPresetHighestQuality
|
||||
)
|
||||
exporter.setOutputURL_(self.url)
|
||||
exporter.setOutputFileType_(AVFoundation.AVFileTypeQuickTimeMovie)
|
||||
exporter.setShouldOptimizeForNetworkUse_(True)
|
||||
|
||||
def handler():
|
||||
""" result handler for exportAsynchronouslyWithCompletionHandler """
|
||||
self.done = True
|
||||
self.done = False
|
||||
|
||||
exporter.exportAsynchronouslyWithCompletionHandler_(handler)
|
||||
# wait for export to complete
|
||||
# would be more elegant to use a dispatch queue, notification, or thread event to wait
|
||||
# but I can't figure out how to make that work and this does work
|
||||
while True:
|
||||
status = exporter.status()
|
||||
if status == AVFoundation.AVAssetExportSessionStatusCompleted:
|
||||
break
|
||||
elif status not in (
|
||||
AVFoundation.AVAssetExportSessionStatusWaiting,
|
||||
AVFoundation.AVAssetExportSessionStatusExporting,
|
||||
):
|
||||
raise PhotoKitExportError(
|
||||
f"Error encountered during exportAsynchronouslyWithCompletionHandler: status = {status}"
|
||||
)
|
||||
time.sleep(MIN_SLEEP)
|
||||
def handler():
|
||||
""" result handler for exportAsynchronouslyWithCompletionHandler """
|
||||
self.done = True
|
||||
|
||||
return NSURL_to_path(exporter.outputURL())
|
||||
exporter.exportAsynchronouslyWithCompletionHandler_(handler)
|
||||
# wait for export to complete
|
||||
# would be more elegant to use a dispatch queue, notification, or thread event to wait
|
||||
# but I can't figure out how to make that work and this does work
|
||||
while True:
|
||||
status = exporter.status()
|
||||
if status == AVFoundation.AVAssetExportSessionStatusCompleted:
|
||||
break
|
||||
elif status not in (
|
||||
AVFoundation.AVAssetExportSessionStatusWaiting,
|
||||
AVFoundation.AVAssetExportSessionStatusExporting,
|
||||
):
|
||||
raise PhotoKitExportError(
|
||||
f"Error encountered during exportAsynchronouslyWithCompletionHandler: status = {status}"
|
||||
)
|
||||
time.sleep(MIN_SLEEP)
|
||||
|
||||
exported_path = NSURL_to_path(exporter.outputURL())
|
||||
# exporter.dealloc()
|
||||
return exported_path
|
||||
|
||||
def __del__(self):
|
||||
self.avasset = None
|
||||
@@ -701,39 +718,43 @@ class VideoAsset(PhotoAsset):
|
||||
ValueError if dest is not a valid directory
|
||||
"""
|
||||
|
||||
if self.slow_mo and version == PHOTOS_VERSION_CURRENT:
|
||||
return [
|
||||
self._export_slow_mo(
|
||||
dest, filename=filename, version=version, overwrite=overwrite
|
||||
)
|
||||
]
|
||||
with objc.autorelease_pool():
|
||||
if self.slow_mo and version == PHOTOS_VERSION_CURRENT:
|
||||
return [
|
||||
self._export_slow_mo(
|
||||
dest, filename=filename, version=version, overwrite=overwrite
|
||||
)
|
||||
]
|
||||
|
||||
filename = (
|
||||
pathlib.Path(filename) if filename else pathlib.Path(self.original_filename)
|
||||
)
|
||||
filename = (
|
||||
pathlib.Path(filename)
|
||||
if filename
|
||||
else pathlib.Path(self.original_filename)
|
||||
)
|
||||
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError("dest must be a valid directory: {dest}")
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError("dest must be a valid directory: {dest}")
|
||||
|
||||
output_file = None
|
||||
videodata = self._request_video_data(version=version)
|
||||
if videodata.asset is None:
|
||||
raise PhotoKitExportError("Could not get video for asset")
|
||||
output_file = None
|
||||
videodata = self._request_video_data(version=version)
|
||||
if videodata.asset is None:
|
||||
raise PhotoKitExportError("Could not get video for asset")
|
||||
|
||||
url = videodata.asset.URL()
|
||||
path = pathlib.Path(NSURL_to_path(url))
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError("Could not get path to video file")
|
||||
ext = path.suffix
|
||||
output_file = dest / f"{filename.stem}{ext}"
|
||||
url = videodata.asset.URL()
|
||||
path = pathlib.Path(NSURL_to_path(url))
|
||||
del videodata
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError("Could not get path to video file")
|
||||
ext = path.suffix
|
||||
output_file = dest / f"{filename.stem}{ext}"
|
||||
|
||||
if not overwrite:
|
||||
output_file = pathlib.Path(increment_filename(output_file))
|
||||
if not overwrite:
|
||||
output_file = pathlib.Path(increment_filename(output_file))
|
||||
|
||||
FileUtil.copy(path, output_file)
|
||||
FileUtil.copy(path, output_file)
|
||||
|
||||
return [str(output_file)]
|
||||
return [str(output_file)]
|
||||
|
||||
def _export_slow_mo(
|
||||
self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
|
||||
@@ -752,33 +773,38 @@ class VideoAsset(PhotoAsset):
|
||||
Raises:
|
||||
ValueError if dest is not a valid directory
|
||||
"""
|
||||
if not self.slow_mo:
|
||||
raise PhotoKitMediaTypeError("Not a slow-mo video")
|
||||
with objc.autorelease_pool():
|
||||
if not self.slow_mo:
|
||||
raise PhotoKitMediaTypeError("Not a slow-mo video")
|
||||
|
||||
videodata = self._request_video_data(version=version)
|
||||
if (
|
||||
not isinstance(videodata.asset, AVFoundation.AVComposition)
|
||||
or len(videodata.asset.tracks()) != 2
|
||||
):
|
||||
raise PhotoKitMediaTypeError("Does not appear to be slow-mo video")
|
||||
videodata = self._request_video_data(version=version)
|
||||
if (
|
||||
not isinstance(videodata.asset, AVFoundation.AVComposition)
|
||||
or len(videodata.asset.tracks()) != 2
|
||||
):
|
||||
raise PhotoKitMediaTypeError("Does not appear to be slow-mo video")
|
||||
|
||||
filename = (
|
||||
pathlib.Path(filename) if filename else pathlib.Path(self.original_filename)
|
||||
)
|
||||
filename = (
|
||||
pathlib.Path(filename)
|
||||
if filename
|
||||
else pathlib.Path(self.original_filename)
|
||||
)
|
||||
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError("dest must be a valid directory: {dest}")
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError("dest must be a valid directory: {dest}")
|
||||
|
||||
output_file = dest / f"{filename.stem}.mov"
|
||||
output_file = dest / f"{filename.stem}.mov"
|
||||
|
||||
if not overwrite:
|
||||
output_file = pathlib.Path(increment_filename(output_file))
|
||||
if not overwrite:
|
||||
output_file = pathlib.Path(increment_filename(output_file))
|
||||
|
||||
exporter = SlowMoVideoExporter.alloc().initWithAVAsset_path_(
|
||||
videodata.asset, output_file
|
||||
)
|
||||
return exporter.exportSlowMoVideo()
|
||||
exporter = SlowMoVideoExporter.alloc().initWithAVAsset_path_(
|
||||
videodata.asset, output_file
|
||||
)
|
||||
video = exporter.exportSlowMoVideo()
|
||||
# exporter.dealloc()
|
||||
return video
|
||||
|
||||
# todo: rewrite this with NotificationCenter and App event loop?
|
||||
def _request_video_data(self, version=PHOTOS_VERSION_ORIGINAL):
|
||||
@@ -793,38 +819,43 @@ class VideoAsset(PhotoAsset):
|
||||
Raises:
|
||||
ValueError if passed invalid value for version
|
||||
"""
|
||||
with objc.autorelease_pool():
|
||||
if version not in [
|
||||
PHOTOS_VERSION_CURRENT,
|
||||
PHOTOS_VERSION_ORIGINAL,
|
||||
PHOTOS_VERSION_UNADJUSTED,
|
||||
]:
|
||||
raise ValueError("Invalid value for version")
|
||||
|
||||
if version not in [
|
||||
PHOTOS_VERSION_CURRENT,
|
||||
PHOTOS_VERSION_ORIGINAL,
|
||||
PHOTOS_VERSION_UNADJUSTED,
|
||||
]:
|
||||
raise ValueError("Invalid value for version")
|
||||
options_request = Photos.PHVideoRequestOptions.alloc().init()
|
||||
options_request.setNetworkAccessAllowed_(True)
|
||||
options_request.setVersion_(version)
|
||||
options_request.setDeliveryMode_(
|
||||
Photos.PHVideoRequestOptionsDeliveryModeHighQualityFormat
|
||||
)
|
||||
requestdata = AVAssetData()
|
||||
event = threading.Event()
|
||||
|
||||
options_request = Photos.PHVideoRequestOptions.alloc().init()
|
||||
options_request.setNetworkAccessAllowed_(True)
|
||||
options_request.setVersion_(version)
|
||||
options_request.setDeliveryMode_(
|
||||
Photos.PHVideoRequestOptionsDeliveryModeHighQualityFormat
|
||||
)
|
||||
requestdata = AVAssetData()
|
||||
event = threading.Event()
|
||||
def handler(asset, audiomix, info):
|
||||
""" result handler for requestAVAssetForVideo:asset options:options resultHandler """
|
||||
nonlocal requestdata
|
||||
|
||||
def handler(asset, audiomix, info):
|
||||
""" result handler for requestAVAssetForVideo:asset options:options resultHandler """
|
||||
nonlocal requestdata
|
||||
requestdata.asset = asset
|
||||
requestdata.audiomix = audiomix
|
||||
requestdata.info = info
|
||||
|
||||
requestdata.asset = asset
|
||||
requestdata.audiomix = audiomix
|
||||
requestdata.info = info
|
||||
event.set()
|
||||
|
||||
event.set()
|
||||
self._manager.requestAVAssetForVideo_options_resultHandler_(
|
||||
self.phasset, options_request, handler
|
||||
)
|
||||
event.wait()
|
||||
|
||||
self._manager.requestAVAssetForVideo_options_resultHandler_(
|
||||
self.phasset, options_request, handler
|
||||
)
|
||||
event.wait()
|
||||
return requestdata
|
||||
# not sure why this is needed -- some weird ref count thing maybe
|
||||
# if I don't do this, memory leaks
|
||||
data = copy.copy(requestdata)
|
||||
del requestdata
|
||||
return data
|
||||
|
||||
|
||||
class LivePhotoRequest(NSObject):
|
||||
@@ -843,47 +874,54 @@ class LivePhotoRequest(NSObject):
|
||||
|
||||
def requestLivePhotoResources(self, version=PHOTOS_VERSION_CURRENT):
|
||||
""" return the photos and video components of a live video as [PHAssetResource] """
|
||||
options = Photos.PHLivePhotoRequestOptions.alloc().init()
|
||||
options.setNetworkAccessAllowed_(True)
|
||||
options.setVersion_(version)
|
||||
options.setDeliveryMode_(
|
||||
Photos.PHVideoRequestOptionsDeliveryModeHighQualityFormat
|
||||
)
|
||||
delegate = PhotoKitNotificationDelegate.alloc().init()
|
||||
|
||||
self.nc.addObserver_selector_name_object_(
|
||||
delegate, "liveNotification:", None, None
|
||||
)
|
||||
|
||||
self.live_photo = None
|
||||
|
||||
def handler(result, info):
|
||||
""" result handler for requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler: """
|
||||
if not info["PHImageResultIsDegradedKey"]:
|
||||
self.live_photo = result
|
||||
self.info = info
|
||||
self.nc.postNotificationName_object_(
|
||||
PHOTOKIT_NOTIFICATION_FINISHED_REQUEST, self
|
||||
)
|
||||
|
||||
try:
|
||||
self.manager.requestLivePhotoForAsset_targetSize_contentMode_options_resultHandler_(
|
||||
self.asset,
|
||||
Photos.PHImageManagerMaximumSize,
|
||||
Photos.PHImageContentModeDefault,
|
||||
options,
|
||||
handler,
|
||||
with objc.autorelease_pool():
|
||||
options = Photos.PHLivePhotoRequestOptions.alloc().init()
|
||||
options.setNetworkAccessAllowed_(True)
|
||||
options.setVersion_(version)
|
||||
options.setDeliveryMode_(
|
||||
Photos.PHVideoRequestOptionsDeliveryModeHighQualityFormat
|
||||
)
|
||||
AppHelper.runConsoleEventLoop(installInterrupt=True)
|
||||
except KeyboardInterrupt:
|
||||
AppHelper.stopEventLoop()
|
||||
finally:
|
||||
pass
|
||||
delegate = PhotoKitNotificationDelegate.alloc().init()
|
||||
|
||||
asset_resources = Photos.PHAssetResource.assetResourcesForLivePhoto_(
|
||||
self.live_photo
|
||||
)
|
||||
return asset_resources
|
||||
self.nc.addObserver_selector_name_object_(
|
||||
delegate, "liveNotification:", None, None
|
||||
)
|
||||
|
||||
self.live_photo = None
|
||||
|
||||
def handler(result, info):
|
||||
""" result handler for requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler: """
|
||||
if not info["PHImageResultIsDegradedKey"]:
|
||||
self.live_photo = result
|
||||
self.info = info
|
||||
self.nc.postNotificationName_object_(
|
||||
PHOTOKIT_NOTIFICATION_FINISHED_REQUEST, self
|
||||
)
|
||||
|
||||
try:
|
||||
self.manager.requestLivePhotoForAsset_targetSize_contentMode_options_resultHandler_(
|
||||
self.asset,
|
||||
Photos.PHImageManagerMaximumSize,
|
||||
Photos.PHImageContentModeDefault,
|
||||
options,
|
||||
handler,
|
||||
)
|
||||
AppHelper.runConsoleEventLoop(installInterrupt=True)
|
||||
except KeyboardInterrupt:
|
||||
AppHelper.stopEventLoop()
|
||||
finally:
|
||||
pass
|
||||
|
||||
asset_resources = Photos.PHAssetResource.assetResourcesForLivePhoto_(
|
||||
self.live_photo
|
||||
)
|
||||
|
||||
# not sure why this is needed -- some weird ref count thing maybe
|
||||
# if I don't do this, memory leaks
|
||||
data = copy.copy(asset_resources)
|
||||
del asset_resources
|
||||
return data
|
||||
|
||||
def __del__(self):
|
||||
self.manager = None
|
||||
@@ -923,88 +961,99 @@ class LivePhotoAsset(PhotoAsset):
|
||||
ValueError if dest is not a valid directory
|
||||
PhotoKitExportError if error during export
|
||||
"""
|
||||
filename = (
|
||||
pathlib.Path(filename) if filename else pathlib.Path(self.original_filename)
|
||||
)
|
||||
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError("dest must be a valid directory: {dest}")
|
||||
|
||||
request = LivePhotoRequest.alloc().initWithManager_Asset_(
|
||||
self._manager, self.phasset
|
||||
)
|
||||
resources = request.requestLivePhotoResources(version=version)
|
||||
|
||||
video_resource = None
|
||||
photo_resource = None
|
||||
for resource in resources:
|
||||
if resource.type() == Photos.PHAssetResourceTypePairedVideo:
|
||||
video_resource = resource
|
||||
elif resource.type() == Photos.PHAssetMediaTypeImage:
|
||||
photo_resource = resource
|
||||
|
||||
if not video_resource or not photo_resource:
|
||||
raise PhotoKitExportError(
|
||||
"Did not find photo/video resources for live photo"
|
||||
with objc.autorelease_pool():
|
||||
filename = (
|
||||
pathlib.Path(filename)
|
||||
if filename
|
||||
else pathlib.Path(self.original_filename)
|
||||
)
|
||||
|
||||
photo_ext = get_preferred_uti_extension(photo_resource.uniformTypeIdentifier())
|
||||
photo_output_file = dest / f"{filename.stem}.{photo_ext}"
|
||||
video_ext = get_preferred_uti_extension(video_resource.uniformTypeIdentifier())
|
||||
video_output_file = dest / f"{filename.stem}.{video_ext}"
|
||||
dest = pathlib.Path(dest)
|
||||
if not dest.is_dir():
|
||||
raise ValueError("dest must be a valid directory: {dest}")
|
||||
|
||||
if not overwrite:
|
||||
photo_output_file = pathlib.Path(increment_filename(photo_output_file))
|
||||
video_output_file = pathlib.Path(increment_filename(video_output_file))
|
||||
request = LivePhotoRequest.alloc().initWithManager_Asset_(
|
||||
self._manager, self.phasset
|
||||
)
|
||||
resources = request.requestLivePhotoResources(version=version)
|
||||
|
||||
# def handler(error):
|
||||
# if error:
|
||||
# raise PhotoKitExportError(f"writeDataForAssetResource error: {error}")
|
||||
video_resource = None
|
||||
photo_resource = None
|
||||
for resource in resources:
|
||||
if resource.type() == Photos.PHAssetResourceTypePairedVideo:
|
||||
video_resource = resource
|
||||
elif resource.type() == Photos.PHAssetMediaTypeImage:
|
||||
photo_resource = resource
|
||||
|
||||
# resource_manager = Photos.PHAssetResourceManager.defaultManager()
|
||||
# options = Photos.PHAssetResourceRequestOptions.alloc().init()
|
||||
# options.setNetworkAccessAllowed_(True)
|
||||
# exported = []
|
||||
# Note: Tried writeDataForAssetResource_toFile_options_completionHandler_ which works
|
||||
# but sets quarantine flag and for reasons I can't determine (maybe quarantine flag)
|
||||
# causes pathlib.Path().is_file() to fail in tests
|
||||
if not video_resource or not photo_resource:
|
||||
raise PhotoKitExportError(
|
||||
"Did not find photo/video resources for live photo"
|
||||
)
|
||||
|
||||
# if photo:
|
||||
# photo_output_url = path_to_NSURL(photo_output_file)
|
||||
# resource_manager.writeDataForAssetResource_toFile_options_completionHandler_(
|
||||
# photo_resource, photo_output_url, options, handler
|
||||
# )
|
||||
# exported.append(str(photo_output_file))
|
||||
photo_ext = get_preferred_uti_extension(
|
||||
photo_resource.uniformTypeIdentifier()
|
||||
)
|
||||
photo_output_file = dest / f"{filename.stem}.{photo_ext}"
|
||||
video_ext = get_preferred_uti_extension(
|
||||
video_resource.uniformTypeIdentifier()
|
||||
)
|
||||
video_output_file = dest / f"{filename.stem}.{video_ext}"
|
||||
|
||||
# if video:
|
||||
# video_output_url = path_to_NSURL(video_output_file)
|
||||
# resource_manager.writeDataForAssetResource_toFile_options_completionHandler_(
|
||||
# video_resource, video_output_url, options, handler
|
||||
# )
|
||||
# exported.append(str(video_output_file))
|
||||
if not overwrite:
|
||||
photo_output_file = pathlib.Path(increment_filename(photo_output_file))
|
||||
video_output_file = pathlib.Path(increment_filename(video_output_file))
|
||||
|
||||
# def completion_handler(error):
|
||||
# if error:
|
||||
# raise PhotoKitExportError(f"writeDataForAssetResource error: {error}")
|
||||
# def handler(error):
|
||||
# if error:
|
||||
# raise PhotoKitExportError(f"writeDataForAssetResource error: {error}")
|
||||
|
||||
# would be nice to be able to usewriteDataForAssetResource_toFile_options_completionHandler_
|
||||
# but it sets quarantine flags that cause issues so instead, request the data and write the files directly
|
||||
# resource_manager = Photos.PHAssetResourceManager.defaultManager()
|
||||
# options = Photos.PHAssetResourceRequestOptions.alloc().init()
|
||||
# options.setNetworkAccessAllowed_(True)
|
||||
# exported = []
|
||||
# Note: Tried writeDataForAssetResource_toFile_options_completionHandler_ which works
|
||||
# but sets quarantine flag and for reasons I can't determine (maybe quarantine flag)
|
||||
# causes pathlib.Path().is_file() to fail in tests
|
||||
|
||||
exported = []
|
||||
if photo:
|
||||
data = self._request_resource_data(photo_resource)
|
||||
# image_data = self.request_image_data(version=version)
|
||||
with open(photo_output_file, "wb") as fd:
|
||||
fd.write(data)
|
||||
exported.append(str(photo_output_file))
|
||||
if video:
|
||||
data = self._request_resource_data(video_resource)
|
||||
with open(video_output_file, "wb") as fd:
|
||||
fd.write(data)
|
||||
exported.append(str(video_output_file))
|
||||
# if photo:
|
||||
# photo_output_url = path_to_NSURL(photo_output_file)
|
||||
# resource_manager.writeDataForAssetResource_toFile_options_completionHandler_(
|
||||
# photo_resource, photo_output_url, options, handler
|
||||
# )
|
||||
# exported.append(str(photo_output_file))
|
||||
|
||||
return exported
|
||||
# if video:
|
||||
# video_output_url = path_to_NSURL(video_output_file)
|
||||
# resource_manager.writeDataForAssetResource_toFile_options_completionHandler_(
|
||||
# video_resource, video_output_url, options, handler
|
||||
# )
|
||||
# exported.append(str(video_output_file))
|
||||
|
||||
# def completion_handler(error):
|
||||
# if error:
|
||||
# raise PhotoKitExportError(f"writeDataForAssetResource error: {error}")
|
||||
|
||||
# would be nice to be able to usewriteDataForAssetResource_toFile_options_completionHandler_
|
||||
# but it sets quarantine flags that cause issues so instead, request the data and write the files directly
|
||||
|
||||
exported = []
|
||||
if photo:
|
||||
data = self._request_resource_data(photo_resource)
|
||||
# image_data = self.request_image_data(version=version)
|
||||
with open(photo_output_file, "wb") as fd:
|
||||
fd.write(data)
|
||||
exported.append(str(photo_output_file))
|
||||
del data
|
||||
if video:
|
||||
data = self._request_resource_data(video_resource)
|
||||
with open(video_output_file, "wb") as fd:
|
||||
fd.write(data)
|
||||
exported.append(str(video_output_file))
|
||||
del data
|
||||
|
||||
request.dealloc()
|
||||
return exported
|
||||
|
||||
def _request_resource_data(self, resource):
|
||||
""" Request asset resource data (either photo or video component)
|
||||
@@ -1015,33 +1064,40 @@ class LivePhotoAsset(PhotoAsset):
|
||||
Raises:
|
||||
"""
|
||||
|
||||
resource_manager = Photos.PHAssetResourceManager.defaultManager()
|
||||
options = Photos.PHAssetResourceRequestOptions.alloc().init()
|
||||
options.setNetworkAccessAllowed_(True)
|
||||
with objc.autorelease_pool():
|
||||
resource_manager = Photos.PHAssetResourceManager.defaultManager()
|
||||
options = Photos.PHAssetResourceRequestOptions.alloc().init()
|
||||
options.setNetworkAccessAllowed_(True)
|
||||
|
||||
requestdata = PHAssetResourceData()
|
||||
event = threading.Event()
|
||||
requestdata = PHAssetResourceData()
|
||||
event = threading.Event()
|
||||
|
||||
def handler(data):
|
||||
""" result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||
all returned by the request is set as properties of nonlocal data (Fetchdata object) """
|
||||
def handler(data):
|
||||
""" result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
|
||||
all returned by the request is set as properties of nonlocal data (Fetchdata object) """
|
||||
|
||||
nonlocal requestdata
|
||||
nonlocal requestdata
|
||||
|
||||
requestdata.data += data
|
||||
requestdata.data += data
|
||||
|
||||
def completion_handler(error):
|
||||
if error:
|
||||
raise PhotoKitExportError("Error requesting data for asset resource")
|
||||
event.set()
|
||||
def completion_handler(error):
|
||||
if error:
|
||||
raise PhotoKitExportError(
|
||||
"Error requesting data for asset resource"
|
||||
)
|
||||
event.set()
|
||||
|
||||
resource_manager.requestDataForAssetResource_options_dataReceivedHandler_completionHandler_(
|
||||
resource, options, handler, completion_handler
|
||||
)
|
||||
resource_manager.requestDataForAssetResource_options_dataReceivedHandler_completionHandler_(
|
||||
resource, options, handler, completion_handler
|
||||
)
|
||||
|
||||
event.wait()
|
||||
options.dealloc()
|
||||
return requestdata.data
|
||||
event.wait()
|
||||
|
||||
# not sure why this is needed -- some weird ref count thing maybe
|
||||
# if I don't do this, memory leaks
|
||||
data = copy.copy(requestdata.data)
|
||||
del requestdata
|
||||
return data
|
||||
|
||||
# def request_image_data(self, version=PHOTOS_VERSION_CURRENT):
|
||||
# # Returns an NSImage which isn't overly useful
|
||||
@@ -1127,19 +1183,20 @@ class PhotoLibrary:
|
||||
"""
|
||||
|
||||
# pylint: disable=no-member
|
||||
fetch_options = Photos.PHFetchOptions.alloc().init()
|
||||
fetch_result = Photos.PHAsset.fetchAssetsWithLocalIdentifiers_options_(
|
||||
uuid_list, fetch_options
|
||||
)
|
||||
if fetch_result and fetch_result.count() >= 1:
|
||||
return [
|
||||
self._asset_factory(fetch_result.objectAtIndex_(idx))
|
||||
for idx in range(fetch_result.count())
|
||||
]
|
||||
else:
|
||||
raise PhotoKitFetchFailed(
|
||||
f"Fetch did not return result for uuid_list {uuid_list}"
|
||||
with objc.autorelease_pool():
|
||||
fetch_options = Photos.PHFetchOptions.alloc().init()
|
||||
fetch_result = Photos.PHAsset.fetchAssetsWithLocalIdentifiers_options_(
|
||||
uuid_list, fetch_options
|
||||
)
|
||||
if fetch_result and fetch_result.count() >= 1:
|
||||
return [
|
||||
self._asset_factory(fetch_result.objectAtIndex_(idx))
|
||||
for idx in range(fetch_result.count())
|
||||
]
|
||||
else:
|
||||
raise PhotoKitFetchFailed(
|
||||
f"Fetch did not return result for uuid_list {uuid_list}"
|
||||
)
|
||||
|
||||
def fetch_uuid(self, uuid):
|
||||
""" fetch PHAsset with uuid = uuid
|
||||
|
||||
@@ -85,12 +85,12 @@ class PhotosDB:
|
||||
|
||||
# Check OS version
|
||||
system = platform.system()
|
||||
(_, major, _) = _get_os_version()
|
||||
if system != "Darwin" or (major not in _TESTED_OS_VERSIONS):
|
||||
(ver, major, _) = _get_os_version()
|
||||
if system != "Darwin" or ((ver, major) not in _TESTED_OS_VERSIONS):
|
||||
logging.warning(
|
||||
f"WARNING: This module has only been tested with MacOS 10."
|
||||
f"[{', '.join(_TESTED_OS_VERSIONS)}]: "
|
||||
f"you have {system}, OS version: {major}"
|
||||
f"WARNING: This module has only been tested with macOS versions "
|
||||
f"[{', '.join(f'{v}.{m}' for (v, m) in _TESTED_OS_VERSIONS)}]: "
|
||||
f"you have {system}, OS version: {ver}.{major}"
|
||||
)
|
||||
|
||||
if verbose is None:
|
||||
@@ -887,7 +887,9 @@ class PhotosDB:
|
||||
RKMaster.width,
|
||||
RKMaster.orientation,
|
||||
RKMaster.fileSize,
|
||||
RKVersion.subType
|
||||
RKVersion.subType,
|
||||
RKVersion.inTrashDate,
|
||||
RKVersion.showInLibrary
|
||||
FROM RKVersion, RKMaster
|
||||
WHERE RKVersion.masterUuid = RKMaster.uuid"""
|
||||
)
|
||||
@@ -915,7 +917,9 @@ class PhotosDB:
|
||||
RKMaster.width,
|
||||
RKMaster.orientation,
|
||||
RKMaster.originalFileSize,
|
||||
RKVersion.subType
|
||||
RKVersion.subType,
|
||||
RKVersion.inTrashDate,
|
||||
RKVersion.showInLibrary
|
||||
FROM RKVersion, RKMaster
|
||||
WHERE RKVersion.masterUuid = RKMaster.uuid"""
|
||||
)
|
||||
@@ -962,6 +966,8 @@ class PhotosDB:
|
||||
# 38 RKMaster.orientation,
|
||||
# 39 RKMaster.originalFileSize
|
||||
# 40 RKVersion.subType
|
||||
# 41 RKVersion.inTrashDate
|
||||
# 42 RKVersion.showInLibrary -- is item visible in library (e.g. non-selected burst images are not visible)
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@@ -1136,7 +1142,14 @@ class PhotosDB:
|
||||
)
|
||||
|
||||
# recently deleted items
|
||||
self._dbphotos[uuid]["intrash"] = True if row[32] == 1 else False
|
||||
self._dbphotos[uuid]["intrash"] = row[32] == 1
|
||||
self._dbphotos[uuid]["trasheddate_timestamp"] = row[41]
|
||||
try:
|
||||
self._dbphotos[uuid]["trasheddate"] = datetime.fromtimestamp(
|
||||
row[41] + TIME_DELTA
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
self._dbphotos[uuid]["trasheddate"] = None
|
||||
|
||||
# height/width/orientation
|
||||
self._dbphotos[uuid]["height"] = row[33]
|
||||
@@ -1147,6 +1160,10 @@ class PhotosDB:
|
||||
self._dbphotos[uuid]["original_orientation"] = row[38]
|
||||
self._dbphotos[uuid]["original_filesize"] = row[39]
|
||||
|
||||
# visibility state
|
||||
self._dbphotos[uuid]["visibility_state"] = row[42]
|
||||
self._dbphotos[uuid]["visible"] = row[42] == 1
|
||||
|
||||
# import session not yet handled for Photos 4
|
||||
self._dbphotos[uuid]["import_session"] = None
|
||||
self._dbphotos[uuid]["import_uuid"] = None
|
||||
@@ -1840,7 +1857,9 @@ class PhotosDB:
|
||||
ZADDITIONALASSETATTRIBUTES.ZORIGINALORIENTATION,
|
||||
ZADDITIONALASSETATTRIBUTES.ZORIGINALFILESIZE,
|
||||
{depth_state},
|
||||
{asset_table}.ZADJUSTMENTTIMESTAMP
|
||||
{asset_table}.ZADJUSTMENTTIMESTAMP,
|
||||
{asset_table}.ZVISIBILITYSTATE,
|
||||
{asset_table}.ZTRASHEDDATE
|
||||
FROM {asset_table}
|
||||
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
|
||||
ORDER BY {asset_table}.ZUUID """
|
||||
@@ -1885,6 +1904,8 @@ class PhotosDB:
|
||||
# 35 ZADDITIONALASSETATTRIBUTES.ZORIGINALFILESIZE
|
||||
# 36 ZGENERICASSET.ZDEPTHSTATES / ZASSET.ZDEPTHTYPE
|
||||
# 37 ZGENERICASSET.ZADJUSTMENTTIMESTAMP -- when was photo edited?
|
||||
# 38 ZGENERICASSET.ZVISIBILITYSTATE -- 0 if visible, 2 if not (e.g. a burst image)
|
||||
# 39 ZGENERICASSET.ZTRASHEDDATE -- date item placed in the trash or null if not in trash
|
||||
|
||||
for row in c:
|
||||
uuid = row[0]
|
||||
@@ -1901,9 +1922,7 @@ class PhotosDB:
|
||||
info["lastmodifieddate_timestamp"] = row[37]
|
||||
try:
|
||||
info["lastmodifieddate"] = datetime.fromtimestamp(row[37] + TIME_DELTA)
|
||||
except ValueError:
|
||||
info["lastmodifieddate"] = None
|
||||
except TypeError:
|
||||
except (ValueError, TypeError):
|
||||
info["lastmodifieddate"] = None
|
||||
|
||||
info["imageTimeZoneOffsetSeconds"] = row[6]
|
||||
@@ -2046,6 +2065,11 @@ class PhotosDB:
|
||||
|
||||
# recently deleted items
|
||||
info["intrash"] = True if row[28] == 1 else False
|
||||
info["trasheddate_timestamp"] = row[39]
|
||||
try:
|
||||
info["trasheddate"] = datetime.fromtimestamp(row[39] + TIME_DELTA)
|
||||
except (ValueError, TypeError):
|
||||
info["trasheddate"] = None
|
||||
|
||||
# height/width/orientation
|
||||
info["height"] = row[29]
|
||||
@@ -2056,6 +2080,11 @@ class PhotosDB:
|
||||
info["original_orientation"] = row[34]
|
||||
info["original_filesize"] = row[35]
|
||||
|
||||
# visibility state, visible (True) if 0, otherwise not visible (False)
|
||||
# only values I've seen are 0 for visible, 2 for not-visible
|
||||
info["visibility_state"] = row[38]
|
||||
info["visible"] = row[38] == 0
|
||||
|
||||
# initialize import session info which will be filled in later
|
||||
# not every photo has an import session so initialize all records now
|
||||
info["import_session"] = None
|
||||
|
||||
@@ -82,11 +82,11 @@ TEMPLATE_SUBSTITUTIONS = {
|
||||
"{modified.hour}": "2-digit hour of the photo modification time; uses creation date if photo is not modified",
|
||||
"{modified.min}": "2-digit minute of the photo modification time; uses creation date if photo is not modified",
|
||||
"{modified.sec}": "2-digit second of the photo modification time; uses creation date if photo is not modified",
|
||||
# "{modified.strftime}": "Apply strftime template to file modification date/time. Should be used in form "
|
||||
# + "{modified.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. "
|
||||
# + "{modified.strftime,%Y-%U} would result in year-week number of year: '2020-23'. "
|
||||
# + "If used with no template will return null value. "
|
||||
# + "See https://strftime.org/ for help on strftime templates.",
|
||||
"{modified.strftime}": "Apply strftime template to file modification date/time. Should be used in form "
|
||||
+ "{modified.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. "
|
||||
+ "{modified.strftime,%Y-%U} would result in year-week number of year: '2020-23'. "
|
||||
+ "If used with no template will return null value. Uses creation date if photo is not modified. "
|
||||
+ "See https://strftime.org/ for help on strftime templates.",
|
||||
"{today.date}": "Current date in iso format, e.g. '2020-03-22'",
|
||||
"{today.year}": "4-digit year of current date",
|
||||
"{today.yy}": "2-digit year of current date",
|
||||
@@ -118,9 +118,9 @@ 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'",
|
||||
"{exif.camera_make}": "Camera make from original photo's EXIF information as imported by Photos, e.g. 'Apple'",
|
||||
"{exif.camera_model}": "Camera model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s'",
|
||||
"{exif.lens_model}": "Lens model from original photo's EXIF information 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'",
|
||||
}
|
||||
|
||||
@@ -797,6 +797,15 @@ class PhotoTemplate:
|
||||
if self.photo.date_modified
|
||||
else DateTimeFormatter(self.photo.date).sec
|
||||
)
|
||||
elif field == "modified.strftime":
|
||||
if default:
|
||||
try:
|
||||
date = self.photo.date_modified or self.photo.date
|
||||
value = date.strftime(default)
|
||||
except:
|
||||
raise ValueError(f"Invalid strftime template: '{default}'")
|
||||
else:
|
||||
value = None
|
||||
elif field == "today.date":
|
||||
value = DateTimeFormatter(self.today).date
|
||||
elif field == "today.year":
|
||||
|
||||
@@ -10,28 +10,44 @@
|
||||
|
||||
<%def name="dc_description(desc)">
|
||||
% if desc is None:
|
||||
<dc:description></dc:description>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'/>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
% else:
|
||||
<dc:description>${desc | x}</dc:description>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>${desc | x}</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="dc_title(title)">
|
||||
% if title is None:
|
||||
<dc:title></dc:title>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'/>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
% else:
|
||||
<dc:title>${title | x}</dc:title>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>${title | x}</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="dc_subject(subject)">
|
||||
% if subject:
|
||||
<dc:subject>
|
||||
<rdf:Seq>
|
||||
% for subj in subject:
|
||||
<rdf:li>${subj | x}</rdf:li>
|
||||
% endfor
|
||||
</rdf:Seq>
|
||||
<rdf:Bag>
|
||||
% for subj in subject:
|
||||
<rdf:li>${subj | x}</rdf:li>
|
||||
% endfor
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
% endif
|
||||
</%def>
|
||||
@@ -86,7 +102,6 @@
|
||||
</%def>
|
||||
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<!-- mirrors Photos 5 "Export IPTC as XMP" option -->
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
|
||||
@@ -63,8 +63,8 @@ def noop(*args, **kwargs):
|
||||
|
||||
|
||||
def _get_os_version():
|
||||
# returns tuple containing OS version
|
||||
# e.g. 10.13.6 = (10, 13, 6)
|
||||
# returns tuple of str containing OS version
|
||||
# e.g. 10.13.6 = ("10", "13", "6")
|
||||
version = platform.mac_ver()[0].split(".")
|
||||
if len(version) == 2:
|
||||
(ver, major) = version
|
||||
@@ -260,10 +260,10 @@ def get_preferred_uti_extension(uti):
|
||||
returns: preferred extension as str """
|
||||
|
||||
# reference: https://developer.apple.com/documentation/coreservices/1442744-uttypecopypreferredtagwithclass?language=objc
|
||||
|
||||
return CoreServices.UTTypeCopyPreferredTagWithClass(
|
||||
uti, CoreServices.kUTTagClassFilenameExtension
|
||||
)
|
||||
with objc.autorelease_pool():
|
||||
return CoreServices.UTTypeCopyPreferredTagWithClass(
|
||||
uti, CoreServices.kUTTagClassFilenameExtension
|
||||
)
|
||||
|
||||
|
||||
def findfiles(pattern, path_):
|
||||
|
||||
6
setup.py
6
setup.py
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# setup.py script for osxphotos
|
||||
#
|
||||
# Copyright (c) 2019, 2020 Rhet Turnbull, rturnbull+git@gmail.com
|
||||
# Copyright (c) 2019, 2020, 2021 Rhet Turnbull, rturnbull+git@gmail.com
|
||||
# All rights reserved.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
@@ -45,7 +45,7 @@ with open(
|
||||
exec(f.read(), about)
|
||||
|
||||
# read README.md into long_description
|
||||
with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f:
|
||||
with open(os.path.join(this_directory, "README.rst"), encoding="utf-8") as f:
|
||||
about["long_description"] = f.read()
|
||||
|
||||
setup(
|
||||
@@ -53,7 +53,7 @@ setup(
|
||||
version=about["__version__"],
|
||||
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",
|
||||
long_description_content_type="text/x-rst",
|
||||
author="Rhet Turnbull",
|
||||
author_email="rturnbull+git@gmail.com",
|
||||
url="https://github.com/RhetTbull/",
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
# reads data from the author's system photo library to build the test data
|
||||
# used to test SearchInfo
|
||||
# run as:
|
||||
# python3 tests/generate_search_info_test_data.py >tests/search_info_test_data_10_15_7.json
|
||||
|
||||
import json
|
||||
|
||||
113
tests/generate_sidecars_for_test.py
Normal file
113
tests/generate_sidecars_for_test.py
Normal file
@@ -0,0 +1,113 @@
|
||||
""" Generate sidecars used for tests """
|
||||
|
||||
# Generates sidecars used for test cases
|
||||
# tests generate sidecars and compare to the ones generated here to assert nothing changed
|
||||
# Run this any time you change code that would result in a change to sidecar files
|
||||
|
||||
import pathlib
|
||||
|
||||
import osxphotos
|
||||
|
||||
PHOTOS_DB_15_7 = "./tests/Test-10.15.7.photoslibrary/database/photos.db"
|
||||
PHOTOS_DB_14_6 = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
|
||||
|
||||
UUID_DICT_15_7 = {
|
||||
"not_favorite": "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C",
|
||||
"has_adjustments": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
"location": "DC99FBDD-7A52-4100-A5BB-344131646C30",
|
||||
"no_location": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
||||
"export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg"
|
||||
"xmp": "F12384F6-CD17-4151-ACBA-AE0E3688539E", # Pumkins1.jpg
|
||||
}
|
||||
|
||||
UUID_DICT_14_6 = {
|
||||
"has_adjustments": "6bxcNnzRQKGnK4uPrCJ9UQ",
|
||||
"no_adjustments": "15uNd7%8RguTEgNPKHfTWw",
|
||||
"location": "3Jn73XpSQQCluzRBMWRsMA",
|
||||
"xmp": "8SOE9s0XQVGsuq4ONohTng",
|
||||
}
|
||||
|
||||
SIDECAR_DIR = "tests/sidecars"
|
||||
|
||||
|
||||
def generate_sidecars(dbname, uuid_dict):
|
||||
""" generate XMP and JSON sidecars for testing """
|
||||
photosdb = osxphotos.PhotosDB(dbname)
|
||||
|
||||
for _, uuid in uuid_dict.items():
|
||||
photo = photosdb.get_photo(uuid)
|
||||
|
||||
# plain xmp
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}.xmp")
|
||||
xmp = photo._xmp_sidecar()
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(xmp)
|
||||
|
||||
# with extension
|
||||
ext = osxphotos.utils.get_preferred_uti_extension(photo.uti)
|
||||
ext = "jpg" if ext == "jpeg" else ext
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_ext.xmp")
|
||||
xmp = photo._xmp_sidecar(extension=ext)
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(xmp)
|
||||
|
||||
# persons_as_keywords
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_persons_as_keywords.xmp")
|
||||
xmp = photo._xmp_sidecar(use_persons_as_keywords=True, extension=ext)
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(xmp)
|
||||
|
||||
# albums_as_keywords
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_albums_as_keywords.xmp")
|
||||
xmp = photo._xmp_sidecar(use_albums_as_keywords=True, extension=ext)
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(xmp)
|
||||
|
||||
# keyword_template
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_keyword_template.xmp")
|
||||
xmp = photo._xmp_sidecar(
|
||||
keyword_template=["{created.year}", "{folder_album}"], extension=ext
|
||||
)
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(xmp)
|
||||
|
||||
# generate JSON files
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}.json")
|
||||
json_ = photo._exiftool_json_sidecar()
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(json_)
|
||||
|
||||
# no tag groups
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_no_tag_groups.json")
|
||||
json_ = photo._exiftool_json_sidecar(tag_groups=False)
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(json_)
|
||||
|
||||
# ignore_date_modified
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_ignore_date_modified.json")
|
||||
json_ = photo._exiftool_json_sidecar(ignore_date_modified=True)
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(json_)
|
||||
|
||||
# keyword_template
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_keyword_template.json")
|
||||
json_ = photo._exiftool_json_sidecar(keyword_template=["{folder_album}"])
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(json_)
|
||||
|
||||
# persons_as_keywords
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_persons_as_keywords.json")
|
||||
json_ = photo._exiftool_json_sidecar(use_persons_as_keywords=True)
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(json_)
|
||||
|
||||
# albums_as_keywords
|
||||
sidecar = str(pathlib.Path(SIDECAR_DIR) / f"{uuid}_albums_as_keywords.json")
|
||||
json_ = photo._exiftool_json_sidecar(use_albums_as_keywords=True)
|
||||
with open(sidecar, "w") as file:
|
||||
file.write(json_)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_sidecars(PHOTOS_DB_15_7, UUID_DICT_15_7)
|
||||
generate_sidecars(PHOTOS_DB_14_6, UUID_DICT_14_6)
|
||||
File diff suppressed because one or more lines are too long
1
tests/sidecars/15uNd7%8RguTEgNPKHfTWw.json
Normal file
1
tests/sidecars/15uNd7%8RguTEgNPKHfTWw.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "XMP:PersonInImage": ["Katie"], "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
50
tests/sidecars/15uNd7%8RguTEgNPKHfTWw.xmp
Normal file
50
tests/sidecars/15uNd7%8RguTEgNPKHfTWw.xmp
Normal file
@@ -0,0 +1,50 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["AlbumInFolder", "Kids", "Pumpkin Farm", "Test Album (1)"], "XMP:Subject": ["AlbumInFolder", "Kids", "Pumpkin Farm", "Test Album (1)"], "XMP:TagsList": ["AlbumInFolder", "Kids", "Pumpkin Farm", "Test Album (1)"], "XMP:PersonInImage": ["Katie"], "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
56
tests/sidecars/15uNd7%8RguTEgNPKHfTWw_albums_as_keywords.xmp
Normal file
56
tests/sidecars/15uNd7%8RguTEgNPKHfTWw_albums_as_keywords.xmp
Normal file
@@ -0,0 +1,56 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>AlbumInFolder</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album (1)</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>AlbumInFolder</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album (1)</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
50
tests/sidecars/15uNd7%8RguTEgNPKHfTWw_ext.xmp
Normal file
50
tests/sidecars/15uNd7%8RguTEgNPKHfTWw_ext.xmp
Normal file
@@ -0,0 +1,50 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "XMP:PersonInImage": ["Katie"], "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["Folder1/SubFolder2/AlbumInFolder", "Kids", "Pumpkin Farm", "Test Album (1)"], "XMP:Subject": ["Folder1/SubFolder2/AlbumInFolder", "Kids", "Pumpkin Farm", "Test Album (1)"], "XMP:TagsList": ["Folder1/SubFolder2/AlbumInFolder", "Kids", "Pumpkin Farm", "Test Album (1)"], "XMP:PersonInImage": ["Katie"], "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
58
tests/sidecars/15uNd7%8RguTEgNPKHfTWw_keyword_template.xmp
Normal file
58
tests/sidecars/15uNd7%8RguTEgNPKHfTWw_keyword_template.xmp
Normal file
@@ -0,0 +1,58 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>Folder1/SubFolder2/AlbumInFolder</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album (1)</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>Folder1/SubFolder2/AlbumInFolder</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album (1)</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/15uNd7%8RguTEgNPKHfTWw_no_tag_groups.json
Normal file
1
tests/sidecars/15uNd7%8RguTEgNPKHfTWw_no_tag_groups.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"ImageDescription": "Girl holding pumpkin", "Description": "Girl holding pumpkin", "Title": "I found one!", "Keywords": ["Kids"], "Subject": ["Kids"], "TagsList": ["Kids"], "PersonInImage": ["Katie"], "DateTimeOriginal": "2018:09:28 16:07:07", "CreateDate": "2018:09:28 16:07:07", "OffsetTimeOriginal": "-04:00", "DateCreated": "2018:09:28", "TimeCreated": "16:07:07-04:00", "ModifyDate": "2018:09:28 16:07:07"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["Katie", "Kids"], "XMP:Subject": ["Katie", "Kids"], "XMP:TagsList": ["Katie", "Kids"], "XMP:PersonInImage": ["Katie"], "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA.json
Normal file
1
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "St. James's Park", "IPTC:Keywords": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:Subject": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:TagsList": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "EXIF:GPSLatitude": 51.50357167, "EXIF:GPSLongitude": -0.1318055, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:10:13 09:18:12", "EXIF:CreateDate": "2018:10:13 09:18:12", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:10:13", "IPTC:TimeCreated": "09:18:12-04:00", "EXIF:ModifyDate": "2019:12:01 11:43:45"}]
|
||||
57
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA.xmp
Normal file
57
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA.xmp
Normal file
@@ -0,0 +1,57 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>St. James's Park</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-10-13T09:18:12.501000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-10-13T09:18:12</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-10-13T09:18:12</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>0,7.908329999999999W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>51,30.21430019999997N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "St. James's Park", "IPTC:Keywords": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:Subject": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:TagsList": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "EXIF:GPSLatitude": 51.50357167, "EXIF:GPSLongitude": -0.1318055, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:10:13 09:18:12", "EXIF:CreateDate": "2018:10:13 09:18:12", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:10:13", "IPTC:TimeCreated": "09:18:12-04:00", "EXIF:ModifyDate": "2019:12:01 11:43:45"}]
|
||||
57
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA_albums_as_keywords.xmp
Normal file
57
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA_albums_as_keywords.xmp
Normal file
@@ -0,0 +1,57 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>St. James's Park</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-10-13T09:18:12.501000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-10-13T09:18:12</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-10-13T09:18:12</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>0,7.908329999999999W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>51,30.21430019999997N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
57
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA_ext.xmp
Normal file
57
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA_ext.xmp
Normal file
@@ -0,0 +1,57 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>St. James's Park</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-10-13T09:18:12.501000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-10-13T09:18:12</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-10-13T09:18:12</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>0,7.908329999999999W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>51,30.21430019999997N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "St. James's Park", "IPTC:Keywords": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:Subject": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:TagsList": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "EXIF:GPSLatitude": 51.50357167, "EXIF:GPSLongitude": -0.1318055, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:10:13 09:18:12", "EXIF:CreateDate": "2018:10:13 09:18:12", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:10:13", "IPTC:TimeCreated": "09:18:12-04:00", "EXIF:ModifyDate": "2018:10:13 09:18:12"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "St. James's Park", "IPTC:Keywords": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:Subject": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:TagsList": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "EXIF:GPSLatitude": 51.50357167, "EXIF:GPSLongitude": -0.1318055, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:10:13 09:18:12", "EXIF:CreateDate": "2018:10:13 09:18:12", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:10:13", "IPTC:TimeCreated": "09:18:12-04:00", "EXIF:ModifyDate": "2019:12:01 11:43:45"}]
|
||||
59
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA_keyword_template.xmp
Normal file
59
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA_keyword_template.xmp
Normal file
@@ -0,0 +1,59 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>St. James's Park</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-10-13T09:18:12.501000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-10-13T09:18:12</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-10-13T09:18:12</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>0,7.908329999999999W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>51,30.21430019999997N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA_no_tag_groups.json
Normal file
1
tests/sidecars/3Jn73XpSQQCluzRBMWRsMA_no_tag_groups.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"Title": "St. James's Park", "Keywords": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "Subject": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "TagsList": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "GPSLatitude": 51.50357167, "GPSLongitude": -0.1318055, "GPSLatitudeRef": "N", "GPSLongitudeRef": "W", "DateTimeOriginal": "2018:10:13 09:18:12", "CreateDate": "2018:10:13 09:18:12", "OffsetTimeOriginal": "-04:00", "DateCreated": "2018:10:13", "TimeCreated": "09:18:12-04:00", "ModifyDate": "2019:12:01 11:43:45"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "St. James's Park", "IPTC:Keywords": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:Subject": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "XMP:TagsList": ["England", "London", "London 2018", "St. James's Park", "UK", "United Kingdom"], "EXIF:GPSLatitude": 51.50357167, "EXIF:GPSLongitude": -0.1318055, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:10:13 09:18:12", "EXIF:CreateDate": "2018:10:13 09:18:12", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:10:13", "IPTC:TimeCreated": "09:18:12-04:00", "EXIF:ModifyDate": "2019:12:01 11:43:45"}]
|
||||
@@ -0,0 +1,57 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>St. James's Park</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-10-13T09:18:12.501000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>England</rdf:li>
|
||||
<rdf:li>London</rdf:li>
|
||||
<rdf:li>London 2018</rdf:li>
|
||||
<rdf:li>St. James's Park</rdf:li>
|
||||
<rdf:li>UK</rdf:li>
|
||||
<rdf:li>United Kingdom</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-10-13T09:18:12</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-10-13T09:18:12</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>0,7.908329999999999W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>51,30.21430019999997N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/6191423D-8DB8-4D4C-92BE-9BBBA308AAC4.json
Normal file
1
tests/sidecars/6191423D-8DB8-4D4C-92BE-9BBBA308AAC4.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Wedding tulips", "XMP:Description": "Wedding tulips", "XMP:Title": "Tulips tied together at a flower shop", "IPTC:Keywords": ["flowers", "wedding"], "XMP:Subject": ["flowers", "wedding"], "XMP:TagsList": ["flowers", "wedding"], "EXIF:DateTimeOriginal": "2019:07:04 16:24:01", "EXIF:CreateDate": "2019:07:04 16:24:01", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:07:04", "IPTC:TimeCreated": "16:24:01-04:00", "EXIF:ModifyDate": "2020:05:28 23:39:38"}]
|
||||
47
tests/sidecars/6191423D-8DB8-4D4C-92BE-9BBBA308AAC4.xmp
Normal file
47
tests/sidecars/6191423D-8DB8-4D4C-92BE-9BBBA308AAC4.xmp
Normal file
@@ -0,0 +1,47 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Wedding tulips</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Tulips tied together at a flower shop</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-07-04T16:24:01-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-07-04T16:24:01</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-07-04T16:24:01</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Wedding tulips", "XMP:Description": "Wedding tulips", "XMP:Title": "Tulips tied together at a flower shop", "IPTC:Keywords": ["flowers", "wedding"], "XMP:Subject": ["flowers", "wedding"], "XMP:TagsList": ["flowers", "wedding"], "EXIF:DateTimeOriginal": "2019:07:04 16:24:01", "EXIF:CreateDate": "2019:07:04 16:24:01", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:07:04", "IPTC:TimeCreated": "16:24:01-04:00", "EXIF:ModifyDate": "2020:05:28 23:39:38"}]
|
||||
@@ -0,0 +1,47 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Wedding tulips</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Tulips tied together at a flower shop</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-07-04T16:24:01-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-07-04T16:24:01</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-07-04T16:24:01</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
47
tests/sidecars/6191423D-8DB8-4D4C-92BE-9BBBA308AAC4_ext.xmp
Normal file
47
tests/sidecars/6191423D-8DB8-4D4C-92BE-9BBBA308AAC4_ext.xmp
Normal file
@@ -0,0 +1,47 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Wedding tulips</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Tulips tied together at a flower shop</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-07-04T16:24:01-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-07-04T16:24:01</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-07-04T16:24:01</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Wedding tulips", "XMP:Description": "Wedding tulips", "XMP:Title": "Tulips tied together at a flower shop", "IPTC:Keywords": ["flowers", "wedding"], "XMP:Subject": ["flowers", "wedding"], "XMP:TagsList": ["flowers", "wedding"], "EXIF:DateTimeOriginal": "2019:07:04 16:24:01", "EXIF:CreateDate": "2019:07:04 16:24:01", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:07:04", "IPTC:TimeCreated": "16:24:01-04:00", "EXIF:ModifyDate": "2019:07:04 16:24:01"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Wedding tulips", "XMP:Description": "Wedding tulips", "XMP:Title": "Tulips tied together at a flower shop", "IPTC:Keywords": ["flowers", "wedding"], "XMP:Subject": ["flowers", "wedding"], "XMP:TagsList": ["flowers", "wedding"], "EXIF:DateTimeOriginal": "2019:07:04 16:24:01", "EXIF:CreateDate": "2019:07:04 16:24:01", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:07:04", "IPTC:TimeCreated": "16:24:01-04:00", "EXIF:ModifyDate": "2020:05:28 23:39:38"}]
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Wedding tulips</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Tulips tied together at a flower shop</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>2019</rdf:li>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-07-04T16:24:01-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>2019</rdf:li>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-07-04T16:24:01</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-07-04T16:24:01</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"ImageDescription": "Wedding tulips", "Description": "Wedding tulips", "Title": "Tulips tied together at a flower shop", "Keywords": ["flowers", "wedding"], "Subject": ["flowers", "wedding"], "TagsList": ["flowers", "wedding"], "DateTimeOriginal": "2019:07:04 16:24:01", "CreateDate": "2019:07:04 16:24:01", "OffsetTimeOriginal": "-04:00", "DateCreated": "2019:07:04", "TimeCreated": "16:24:01-04:00", "ModifyDate": "2020:05:28 23:39:38"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Wedding tulips", "XMP:Description": "Wedding tulips", "XMP:Title": "Tulips tied together at a flower shop", "IPTC:Keywords": ["flowers", "wedding"], "XMP:Subject": ["flowers", "wedding"], "XMP:TagsList": ["flowers", "wedding"], "EXIF:DateTimeOriginal": "2019:07:04 16:24:01", "EXIF:CreateDate": "2019:07:04 16:24:01", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:07:04", "IPTC:TimeCreated": "16:24:01-04:00", "EXIF:ModifyDate": "2020:05:28 23:39:38"}]
|
||||
@@ -0,0 +1,47 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Wedding tulips</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Tulips tied together at a flower shop</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-07-04T16:24:01-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>flowers</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-07-04T16:24:01</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-07-04T16:24:01</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ.json
Normal file
1
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Bride Wedding day", "XMP:Description": "Bride Wedding day", "IPTC:Keywords": ["wedding"], "XMP:Subject": ["wedding"], "XMP:TagsList": ["wedding"], "XMP:PersonInImage": ["Maria"], "EXIF:DateTimeOriginal": "2019:04:15 14:40:24", "EXIF:CreateDate": "2019:04:15 14:40:24", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:04:15", "IPTC:TimeCreated": "14:40:24-04:00", "EXIF:ModifyDate": "2019:11:27 01:30:16"}]
|
||||
50
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ.xmp
Normal file
50
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ.xmp
Normal file
@@ -0,0 +1,50 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Bride Wedding day</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'/>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-04-15T14:40:24.086000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Maria</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-04-15T14:40:24</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-04-15T14:40:24</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Bride Wedding day", "XMP:Description": "Bride Wedding day", "IPTC:Keywords": ["wedding"], "XMP:Subject": ["wedding"], "XMP:TagsList": ["wedding"], "XMP:PersonInImage": ["Maria"], "EXIF:DateTimeOriginal": "2019:04:15 14:40:24", "EXIF:CreateDate": "2019:04:15 14:40:24", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:04:15", "IPTC:TimeCreated": "14:40:24-04:00", "EXIF:ModifyDate": "2019:11:27 01:30:16"}]
|
||||
50
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ_albums_as_keywords.xmp
Normal file
50
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ_albums_as_keywords.xmp
Normal file
@@ -0,0 +1,50 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Bride Wedding day</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'/>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-04-15T14:40:24.086000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Maria</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-04-15T14:40:24</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-04-15T14:40:24</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
50
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ_ext.xmp
Normal file
50
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ_ext.xmp
Normal file
@@ -0,0 +1,50 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Bride Wedding day</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'/>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-04-15T14:40:24.086000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Maria</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-04-15T14:40:24</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-04-15T14:40:24</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Bride Wedding day", "XMP:Description": "Bride Wedding day", "IPTC:Keywords": ["wedding"], "XMP:Subject": ["wedding"], "XMP:TagsList": ["wedding"], "XMP:PersonInImage": ["Maria"], "EXIF:DateTimeOriginal": "2019:04:15 14:40:24", "EXIF:CreateDate": "2019:04:15 14:40:24", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:04:15", "IPTC:TimeCreated": "14:40:24-04:00", "EXIF:ModifyDate": "2019:04:15 14:40:24"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Bride Wedding day", "XMP:Description": "Bride Wedding day", "IPTC:Keywords": ["wedding"], "XMP:Subject": ["wedding"], "XMP:TagsList": ["wedding"], "XMP:PersonInImage": ["Maria"], "EXIF:DateTimeOriginal": "2019:04:15 14:40:24", "EXIF:CreateDate": "2019:04:15 14:40:24", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:04:15", "IPTC:TimeCreated": "14:40:24-04:00", "EXIF:ModifyDate": "2019:11:27 01:30:16"}]
|
||||
52
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ_keyword_template.xmp
Normal file
52
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ_keyword_template.xmp
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Bride Wedding day</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'/>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>2019</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-04-15T14:40:24.086000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Maria</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>2019</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-04-15T14:40:24</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-04-15T14:40:24</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ_no_tag_groups.json
Normal file
1
tests/sidecars/6bxcNnzRQKGnK4uPrCJ9UQ_no_tag_groups.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"ImageDescription": "Bride Wedding day", "Description": "Bride Wedding day", "Keywords": ["wedding"], "Subject": ["wedding"], "TagsList": ["wedding"], "PersonInImage": ["Maria"], "DateTimeOriginal": "2019:04:15 14:40:24", "CreateDate": "2019:04:15 14:40:24", "OffsetTimeOriginal": "-04:00", "DateCreated": "2019:04:15", "TimeCreated": "14:40:24-04:00", "ModifyDate": "2019:11:27 01:30:16"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Bride Wedding day", "XMP:Description": "Bride Wedding day", "IPTC:Keywords": ["Maria", "wedding"], "XMP:Subject": ["Maria", "wedding"], "XMP:TagsList": ["Maria", "wedding"], "XMP:PersonInImage": ["Maria"], "EXIF:DateTimeOriginal": "2019:04:15 14:40:24", "EXIF:CreateDate": "2019:04:15 14:40:24", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2019:04:15", "IPTC:TimeCreated": "14:40:24-04:00", "EXIF:ModifyDate": "2019:11:27 01:30:16"}]
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Bride Wedding day</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'/>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Maria</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2019-04-15T14:40:24.086000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Maria</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Maria</rdf:li>
|
||||
<rdf:li>wedding</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2019-04-15T14:40:24</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2019-04-15T14:40:24</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/8SOE9s0XQVGsuq4ONohTng.json
Normal file
1
tests/sidecars/8SOE9s0XQVGsuq4ONohTng.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girls with pumpkins", "XMP:Description": "Girls with pumpkins", "XMP:Title": "Can we carry this?", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "XMP:PersonInImage": ["Katie", "Suzy"], "EXIF:DateTimeOriginal": "2018:09:28 15:35:49", "EXIF:CreateDate": "2018:09:28 15:35:49", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:35:49-04:00", "EXIF:ModifyDate": "2018:09:28 15:35:49"}]
|
||||
51
tests/sidecars/8SOE9s0XQVGsuq4ONohTng.xmp
Normal file
51
tests/sidecars/8SOE9s0XQVGsuq4ONohTng.xmp
Normal file
@@ -0,0 +1,51 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girls with pumpkins</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Can we carry this?</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:35:49.063000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Suzy</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:35:49</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:35:49</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girls with pumpkins", "XMP:Description": "Girls with pumpkins", "XMP:Title": "Can we carry this?", "IPTC:Keywords": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:Subject": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:TagsList": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:PersonInImage": ["Katie", "Suzy"], "EXIF:DateTimeOriginal": "2018:09:28 15:35:49", "EXIF:CreateDate": "2018:09:28 15:35:49", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:35:49-04:00", "EXIF:ModifyDate": "2018:09:28 15:35:49"}]
|
||||
55
tests/sidecars/8SOE9s0XQVGsuq4ONohTng_albums_as_keywords.xmp
Normal file
55
tests/sidecars/8SOE9s0XQVGsuq4ONohTng_albums_as_keywords.xmp
Normal file
@@ -0,0 +1,55 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girls with pumpkins</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Can we carry this?</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:35:49.063000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Suzy</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:35:49</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:35:49</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
51
tests/sidecars/8SOE9s0XQVGsuq4ONohTng_ext.xmp
Normal file
51
tests/sidecars/8SOE9s0XQVGsuq4ONohTng_ext.xmp
Normal file
@@ -0,0 +1,51 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girls with pumpkins</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Can we carry this?</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:35:49.063000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Suzy</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:35:49</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:35:49</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girls with pumpkins", "XMP:Description": "Girls with pumpkins", "XMP:Title": "Can we carry this?", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "XMP:PersonInImage": ["Katie", "Suzy"], "EXIF:DateTimeOriginal": "2018:09:28 15:35:49", "EXIF:CreateDate": "2018:09:28 15:35:49", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:35:49-04:00", "EXIF:ModifyDate": "2018:09:28 15:35:49"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girls with pumpkins", "XMP:Description": "Girls with pumpkins", "XMP:Title": "Can we carry this?", "IPTC:Keywords": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:Subject": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:TagsList": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:PersonInImage": ["Katie", "Suzy"], "EXIF:DateTimeOriginal": "2018:09:28 15:35:49", "EXIF:CreateDate": "2018:09:28 15:35:49", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:35:49-04:00", "EXIF:ModifyDate": "2018:09:28 15:35:49"}]
|
||||
57
tests/sidecars/8SOE9s0XQVGsuq4ONohTng_keyword_template.xmp
Normal file
57
tests/sidecars/8SOE9s0XQVGsuq4ONohTng_keyword_template.xmp
Normal file
@@ -0,0 +1,57 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girls with pumpkins</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Can we carry this?</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:35:49.063000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Suzy</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:35:49</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:35:49</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/8SOE9s0XQVGsuq4ONohTng_no_tag_groups.json
Normal file
1
tests/sidecars/8SOE9s0XQVGsuq4ONohTng_no_tag_groups.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"ImageDescription": "Girls with pumpkins", "Description": "Girls with pumpkins", "Title": "Can we carry this?", "Keywords": ["Kids"], "Subject": ["Kids"], "TagsList": ["Kids"], "PersonInImage": ["Katie", "Suzy"], "DateTimeOriginal": "2018:09:28 15:35:49", "CreateDate": "2018:09:28 15:35:49", "OffsetTimeOriginal": "-04:00", "DateCreated": "2018:09:28", "TimeCreated": "15:35:49-04:00", "ModifyDate": "2018:09:28 15:35:49"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girls with pumpkins", "XMP:Description": "Girls with pumpkins", "XMP:Title": "Can we carry this?", "IPTC:Keywords": ["Katie", "Kids", "Suzy"], "XMP:Subject": ["Katie", "Kids", "Suzy"], "XMP:TagsList": ["Katie", "Kids", "Suzy"], "XMP:PersonInImage": ["Katie", "Suzy"], "EXIF:DateTimeOriginal": "2018:09:28 15:35:49", "EXIF:CreateDate": "2018:09:28 15:35:49", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:35:49-04:00", "EXIF:ModifyDate": "2018:09:28 15:35:49"}]
|
||||
@@ -0,0 +1,55 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girls with pumpkins</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Can we carry this?</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Suzy</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:35:49.063000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Suzy</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Suzy</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:35:49</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:35:49</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C.json
Normal file
1
tests/sidecars/A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "Pumpkin heads", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "EXIF:DateTimeOriginal": "2018:09:28 15:39:59", "EXIF:CreateDate": "2018:09:28 15:39:59", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:39:59-04:00", "EXIF:ModifyDate": "2018:09:28 15:39:59"}]
|
||||
45
tests/sidecars/A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C.xmp
Normal file
45
tests/sidecars/A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C.xmp
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Pumpkin heads</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:39:59.008000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:39:59</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:39:59</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "Pumpkin heads", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "EXIF:DateTimeOriginal": "2018:09:28 15:39:59", "EXIF:CreateDate": "2018:09:28 15:39:59", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:39:59-04:00", "EXIF:ModifyDate": "2018:09:28 15:39:59"}]
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Pumpkin heads</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:39:59.008000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:39:59</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:39:59</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
45
tests/sidecars/A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C_ext.xmp
Normal file
45
tests/sidecars/A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C_ext.xmp
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Pumpkin heads</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:39:59.008000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:39:59</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:39:59</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "Pumpkin heads", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "EXIF:DateTimeOriginal": "2018:09:28 15:39:59", "EXIF:CreateDate": "2018:09:28 15:39:59", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:39:59-04:00", "EXIF:ModifyDate": "2018:09:28 15:39:59"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "Pumpkin heads", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "EXIF:DateTimeOriginal": "2018:09:28 15:39:59", "EXIF:CreateDate": "2018:09:28 15:39:59", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:39:59-04:00", "EXIF:ModifyDate": "2018:09:28 15:39:59"}]
|
||||
@@ -0,0 +1,47 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Pumpkin heads</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:39:59.008000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:39:59</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:39:59</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"Title": "Pumpkin heads", "Keywords": ["Kids"], "Subject": ["Kids"], "TagsList": ["Kids"], "DateTimeOriginal": "2018:09:28 15:39:59", "CreateDate": "2018:09:28 15:39:59", "OffsetTimeOriginal": "-04:00", "DateCreated": "2018:09:28", "TimeCreated": "15:39:59-04:00", "ModifyDate": "2018:09:28 15:39:59"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"XMP:Title": "Pumpkin heads", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "EXIF:DateTimeOriginal": "2018:09:28 15:39:59", "EXIF:CreateDate": "2018:09:28 15:39:59", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "15:39:59-04:00", "EXIF:ModifyDate": "2018:09:28 15:39:59"}]
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'></rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Pumpkin heads</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T15:39:59.008000-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T15:39:59</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T15:39:59</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
1
tests/sidecars/D79B8D77-BFFC-460B-9312-034F2877D35B.json
Normal file
1
tests/sidecars/D79B8D77-BFFC-460B-9312-034F2877D35B.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "XMP:PersonInImage": ["Katie"], "EXIF:GPSLatitude": 41.256566, "EXIF:GPSLongitude": -95.940257, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
52
tests/sidecars/D79B8D77-BFFC-460B-9312-034F2877D35B.xmp
Normal file
52
tests/sidecars/D79B8D77-BFFC-460B-9312-034F2877D35B.xmp
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>95,56.415420000000154W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>41,15.393959999999964N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:Subject": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:TagsList": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:PersonInImage": ["Katie"], "EXIF:GPSLatitude": 41.256566, "EXIF:GPSLongitude": -95.940257, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
@@ -0,0 +1,56 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>95,56.415420000000154W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>41,15.393959999999964N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
52
tests/sidecars/D79B8D77-BFFC-460B-9312-034F2877D35B_ext.xmp
Normal file
52
tests/sidecars/D79B8D77-BFFC-460B-9312-034F2877D35B_ext.xmp
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>95,56.415420000000154W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>41,15.393959999999964N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["Kids"], "XMP:Subject": ["Kids"], "XMP:TagsList": ["Kids"], "XMP:PersonInImage": ["Katie"], "EXIF:GPSLatitude": 41.256566, "EXIF:GPSLongitude": -95.940257, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"EXIF:ImageDescription": "Girl holding pumpkin", "XMP:Description": "Girl holding pumpkin", "XMP:Title": "I found one!", "IPTC:Keywords": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:Subject": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:TagsList": ["Kids", "Pumpkin Farm", "Test Album"], "XMP:PersonInImage": ["Katie"], "EXIF:GPSLatitude": 41.256566, "EXIF:GPSLongitude": -95.940257, "EXIF:GPSLatitudeRef": "N", "EXIF:GPSLongitudeRef": "W", "EXIF:DateTimeOriginal": "2018:09:28 16:07:07", "EXIF:CreateDate": "2018:09:28 16:07:07", "EXIF:OffsetTimeOriginal": "-04:00", "IPTC:DateCreated": "2018:09:28", "IPTC:TimeCreated": "16:07:07-04:00", "EXIF:ModifyDate": "2018:09:28 16:07:07"}]
|
||||
@@ -0,0 +1,58 @@
|
||||
<!-- Created with osxphotos https://github.com/RhetTbull/osxphotos -->
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
|
||||
<photoshop:SidecarForExtension>jpg</photoshop:SidecarForExtension>
|
||||
<dc:description>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>Girl holding pumpkin</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:description>
|
||||
<dc:title>
|
||||
<rdf:Alt>
|
||||
<rdf:li xml:lang='x-default'>I found one!</rdf:li>
|
||||
</rdf:Alt>
|
||||
</dc:title>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<photoshop:DateCreated>2018-09-28T16:07:07-04:00</photoshop:DateCreated>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:Iptc4xmpExt='http://iptc.org/std/Iptc4xmpExt/2008-02-29/'>
|
||||
<Iptc4xmpExt:PersonInImage>
|
||||
<rdf:Bag>
|
||||
<rdf:li>Katie</rdf:li>
|
||||
</rdf:Bag>
|
||||
</Iptc4xmpExt:PersonInImage>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:digiKam='http://www.digikam.org/ns/1.0/'>
|
||||
<digiKam:TagsList>
|
||||
<rdf:Seq>
|
||||
<rdf:li>2018</rdf:li>
|
||||
<rdf:li>Kids</rdf:li>
|
||||
<rdf:li>Pumpkin Farm</rdf:li>
|
||||
<rdf:li>Test Album</rdf:li>
|
||||
</rdf:Seq>
|
||||
</digiKam:TagsList>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
|
||||
<xmp:CreateDate>2018-09-28T16:07:07</xmp:CreateDate>
|
||||
<xmp:ModifyDate>2018-09-28T16:07:07</xmp:ModifyDate>
|
||||
</rdf:Description>
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||
<exif:GPSLongitude>95,56.415420000000154W</exif:GPSLongitude>
|
||||
<exif:GPSLatitude>41,15.393959999999964N</exif:GPSLatitude>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user