1377 lines
49 KiB
Python
1377 lines
49 KiB
Python
""" Test template.py """
|
|
import os
|
|
import re
|
|
|
|
import pytest
|
|
|
|
import osxphotos
|
|
from osxphotos.exiftool import get_exiftool_path
|
|
from osxphotos.export_db import ExportDBInMemory
|
|
from osxphotos.photoinfo import PhotoInfoNone
|
|
from osxphotos.phototemplate import (
|
|
PUNCTUATION,
|
|
TEMPLATE_SUBSTITUTIONS,
|
|
TEMPLATE_SUBSTITUTIONS_MULTI_VALUED,
|
|
PhotoTemplate,
|
|
RenderOptions,
|
|
)
|
|
from osxphotos.utils import is_macos
|
|
|
|
from .locale_util import setlocale
|
|
from .photoinfo_mock import PhotoInfoMock
|
|
|
|
try:
|
|
exiftool = get_exiftool_path()
|
|
except:
|
|
exiftool = None
|
|
|
|
PHOTOS_DB_PLACES = (
|
|
"./tests/Test-Places-Catalina-10_15_7.photoslibrary/database/photos.db"
|
|
)
|
|
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"
|
|
PHOTOS_DB_COMMENTS = "tests/Test-Cloud-10.15.6.photoslibrary"
|
|
PHOTOS_DB_CLOUD = "./tests/Test-Cloud-10.15.6.photoslibrary/database/photos.db"
|
|
PHOTOS_DB_PROJECTS = "./tests/Test-iPhoto-Projects-10.15.7.photoslibrary"
|
|
|
|
UUID_DICT = {
|
|
"place_dc": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
|
|
"1_1_2": "1EB2B765-0765-43BA-A90C-0D0580E6172C",
|
|
"2_1_1": "D79B8D77-BFFC-460B-9312-034F2877D35B",
|
|
"0_2_0": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
|
"folder_album_1": "3DD2C897-F19E-4CA6-8C22-B027D5A71907",
|
|
"folder_album_no_folder": "D79B8D77-BFFC-460B-9312-034F2877D35B",
|
|
"mojave_album_1": "15uNd7%8RguTEgNPKHfTWw",
|
|
"date_modified": "A9B73E13-A6F2-4915-8D67-7213B39BAE9F",
|
|
"date_not_modified": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
|
|
"favorite": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
|
}
|
|
|
|
UUID_MEDIA_TYPE = {
|
|
"photo": "C2BBC7A4-5333-46EE-BAF0-093E72111B39",
|
|
"video": "45099D34-A414-464F-94A2-60D6823679C8",
|
|
"selfie": "080525C4-1F05-48E5-A3F4-0C53127BB39C",
|
|
"time_lapse": "4614086E-C797-4876-B3B9-3057E8D757C9",
|
|
"panorama": "1C1C8F1F-826B-4A24-B1CB-56628946A834",
|
|
"slow_mo": None,
|
|
"screenshot": None,
|
|
"portrait": "7CDA5F84-AA16-4D28-9AA6-A49E1DF8A332",
|
|
"live_photo": "51F2BEF7-431A-4D31-8AC1-3284A57826AE",
|
|
"burst": None,
|
|
}
|
|
|
|
# multi keywords
|
|
UUID_MULTI_KEYWORDS = "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4"
|
|
TEMPLATE_VALUES_MULTI_KEYWORDS = {
|
|
"{keyword}": ["flowers", "wedding"],
|
|
"{keyword|parens}": ["(flowers)", "(wedding)"],
|
|
"{keyword|braces}": ["{flowers}", "{wedding}"],
|
|
"{keyword|brackets}": ["[flowers]", "[wedding]"],
|
|
"{keyword|parens|brackets|capitalize}": ["[(flowers)]", "[(wedding)]"],
|
|
"{keyword|capitalize|parens|brackets}": ["[(Flowers)]", "[(Wedding)]"],
|
|
"{keyword|upper}": ["FLOWERS", "WEDDING"],
|
|
"{keyword|lower}": ["flowers", "wedding"],
|
|
"{keyword|titlecase}": ["Flowers", "Wedding"],
|
|
"{keyword|capitalize}": ["Flowers", "Wedding"],
|
|
"{keyword|shell_quote}": ["flowers", "wedding"],
|
|
"{+keyword}": ["flowerswedding"],
|
|
"{+keyword|titlecase}": ["Flowerswedding"],
|
|
"{+keyword|capitalize}": ["Flowerswedding"],
|
|
"{;+keyword}": ["flowers;wedding"],
|
|
"{; +keyword}": ["flowers; wedding"],
|
|
"{; +keyword|titlecase}": ["Flowers; Wedding"],
|
|
"{; +keyword|titlecase|parens}": ["(Flowers; Wedding)"],
|
|
"{keyword|appends(:keyword)}": ["flowers:keyword", "wedding:keyword"],
|
|
"{keyword|prepends(keyword:)}": ["keyword:flowers", "keyword:wedding"],
|
|
}
|
|
|
|
UUID_TITLE = "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4"
|
|
TEMPLATE_VALUES_TITLE = {
|
|
"{title}": ["Tulips tied together at a flower shop"],
|
|
"{title|appends(:title)}": ["Tulips tied together at a flower shop:title"],
|
|
"{title|prepends(title:)}": ["title:Tulips tied together at a flower shop"],
|
|
"{title|titlecase}": ["Tulips Tied Together At A Flower Shop"],
|
|
"{title|upper}": ["TULIPS TIED TOGETHER AT A FLOWER SHOP"],
|
|
"{title|titlecase|lower|upper}": ["TULIPS TIED TOGETHER AT A FLOWER SHOP"],
|
|
"{title|titlecase|lower|upper|shell_quote}": [
|
|
"'TULIPS TIED TOGETHER AT A FLOWER SHOP'"
|
|
],
|
|
"{title|upper|titlecase}": ["Tulips Tied Together At A Flower Shop"],
|
|
"{title|capitalize}": ["Tulips tied together at a flower shop"],
|
|
"{title[ ,_]}": ["Tulips_tied_together_at_a_flower_shop"],
|
|
"{title[ ,_|e,]}": ["Tulips_tid_togthr_at_a_flowr_shop"],
|
|
"{title[ ,|e,]}": ["Tulipstidtogthrataflowrshop"],
|
|
"{title[e,]}": ["Tulips tid togthr at a flowr shop"],
|
|
"{+title}": ["Tulips tied together at a flower shop"],
|
|
"{,+title}": ["Tulips tied together at a flower shop"],
|
|
"{, +title}": ["Tulips tied together at a flower shop"],
|
|
"{title|shell_quote}": ["'Tulips tied together at a flower shop'"],
|
|
}
|
|
|
|
# Boolean type values that render to True
|
|
UUID_BOOL_VALUES = {
|
|
"hdr": "D11D25FF-5F31-47D2-ABA9-58418878DC15",
|
|
"edited": "51F2BEF7-431A-4D31-8AC1-3284A57826AE",
|
|
"edited_version": "51F2BEF7-431A-4D31-8AC1-3284A57826AE",
|
|
}
|
|
|
|
# Boolean type values that render to False
|
|
UUID_BOOL_VALUES_NOT = {
|
|
"hdr": "51F2BEF7-431A-4D31-8AC1-3284A57826AE",
|
|
"edited_version": "51F2BEF7-431A-4D31-8AC1-3284A57826AE",
|
|
"edited": "CCBE0EB9-AE9F-4479-BFFD-107042C75227",
|
|
}
|
|
|
|
# for exiftool template
|
|
UUID_EXIFTOOL = {
|
|
"A92D9C26-3A50-4197-9388-CB5F7DB9FA91": {
|
|
"{exiftool:EXIF:Make}": ["Canon"],
|
|
"{exiftool:EXIF:Make[Canon,CANON]}": ["CANON"],
|
|
"{exiftool:EXIF:Model}": ["Canon PowerShot G10"],
|
|
"{exiftool:EXIF:Model[ G10,]}": ["Canon PowerShot"],
|
|
"{exiftool:EXIF:Make}/{exiftool:EXIF:Model}": ["Canon/Canon PowerShot G10"],
|
|
"{exiftool:IPTC:Keywords,foo}": ["foo"],
|
|
},
|
|
"DC99FBDD-7A52-4100-A5BB-344131646C30": {
|
|
"{exiftool:IPTC:Keywords}": [
|
|
"England",
|
|
"London",
|
|
"London 2018",
|
|
"St. James's Park",
|
|
"UK",
|
|
"United Kingdom",
|
|
],
|
|
"{exiftool:IPTC:Keywords[ ,_|.,]}": [
|
|
"England",
|
|
"London",
|
|
"London_2018",
|
|
"St_James's_Park",
|
|
"UK",
|
|
"United_Kingdom",
|
|
],
|
|
"{exiftool:IPTC:Keywords[ ,_|.,|L,]}": [
|
|
"England",
|
|
"ondon",
|
|
"ondon_2018",
|
|
"St_James's_Park",
|
|
"UK",
|
|
"United_Kingdom",
|
|
],
|
|
"{,+exiftool:IPTC:Keywords}": [
|
|
"England,London,London 2018,St. James's Park,UK,United Kingdom"
|
|
],
|
|
},
|
|
"1EB2B765-0765-43BA-A90C-0D0580E6172C": {
|
|
"{exiftool:EXIF:SubSecTimeOriginal}": ["22"]
|
|
},
|
|
}
|
|
|
|
TEMPLATE_VALUES = {
|
|
"{name}": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
|
|
"{original_name}": "IMG_1064",
|
|
"{original_name[_,-]}": "IMG-1064",
|
|
"{title}": "Glen Ord",
|
|
"{title[ ,]}": "GlenOrd",
|
|
"{descr}": "Jack Rose Dining Saloon",
|
|
"{created}": "2020-02-04",
|
|
"{created.date}": "2020-02-04",
|
|
"{created.year}": "2020",
|
|
"{created.yy}": "20",
|
|
"{created.mm}": "02",
|
|
"{created.month}": "February",
|
|
"{created.mon}": "Feb",
|
|
"{created.dd}": "04",
|
|
"{created.dow}": "Tuesday",
|
|
"{created.doy}": "035",
|
|
"{created.hour}": "19",
|
|
"{created.min}": "07",
|
|
"{created.sec}": "38",
|
|
"{place.name}": "Washington, District of Columbia, United States",
|
|
"{place.country_code}": "US",
|
|
"{place.name.country}": "United States",
|
|
"{place.name.state_province}": "District of Columbia",
|
|
"{place.name.city}": "Washington",
|
|
"{place.name.area_of_interest}": "_",
|
|
"{place.address}": "2038 18th St NW, Washington, DC 20009, United States",
|
|
"{place.address.street}": "2038 18th St NW",
|
|
"{place.address.city}": "Washington",
|
|
"{place.address.state_province}": "DC",
|
|
"{place.address.postal_code}": "20009",
|
|
"{place.address.country}": "United States",
|
|
"{place.address.country_code}": "US",
|
|
"{uuid}": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
|
|
"{shortuuid}": "5KFgrKKnwmnN99jkHmJP8M",
|
|
"{shortuuid|sslice(:7)}": "5KFgrKK",
|
|
"{exif.camera_make}": "Apple",
|
|
"{exif.camera_model}": "iPhone 6s",
|
|
"{exif.lens_model}": "iPhone 6s back camera 4.15mm f/2.2",
|
|
"{album?{folder_album},{created.year}/{created.mm}}": "2020/02",
|
|
"{title?Title is '{title} - {descr}',No Title}": "Title is 'Glen Ord - Jack Rose Dining Saloon'",
|
|
"{favorite}": "_",
|
|
"{favorite?FAV,NOTFAV}": "NOTFAV",
|
|
"{var:myvar,{semicolon}}{created.dow}{%myvar}": "Tuesday;",
|
|
"{var:pipe,{pipe}}{place.address[,,%pipe]}": "2038 18th St NW| Washington| DC 20009| United States",
|
|
"{format:float:.2f,{photo.exif_info.aperture}}": "2.20",
|
|
"{format:int:02d,{photo.exif_info.aperture}}": "02",
|
|
"{format:int:03d,{photo.exif_info.aperture}}": "002",
|
|
"{format:float:10.4f,{photo.exif_info.aperture}}": " 2.2000",
|
|
"{format:str:-^10,{photo.exif_info.aperture}}": "---2.2----",
|
|
"{descr|lower}": "jack rose dining saloon",
|
|
"{descr|upper}": "JACK ROSE DINING SALOON",
|
|
"{var:spaces, {descr}}{%spaces|strip,}": "Jack Rose Dining Saloon",
|
|
"{descr|titlecase}": "Jack Rose Dining Saloon",
|
|
"{descr|capitalize}": "Jack rose dining saloon",
|
|
"{descr|braces}": "{Jack Rose Dining Saloon}",
|
|
"{descr|parens}": "(Jack Rose Dining Saloon)",
|
|
"{descr|brackets}": "[Jack Rose Dining Saloon]",
|
|
"{descr|split( )|join(|)}": "Jack|Rose|Dining|Saloon",
|
|
"{descr|autosplit|join(|)}": "Jack|Rose|Dining|Saloon",
|
|
"{descr|autosplit|join()}": "JackRoseDiningSaloon",
|
|
"{descr|autosplit|chop(1)|join(|)}": "Jac|Ros|Dinin|Saloo",
|
|
"{descr|autosplit|chomp(1)|join(|)}": "ack|ose|ining|aloon",
|
|
"{descr|chop(2)}": "Jack Rose Dining Salo",
|
|
"{descr|chomp(2)}": "ck Rose Dining Saloon",
|
|
"{descr|autosplit|sort|join(|)}": "Dining|Jack|Rose|Saloon",
|
|
"{descr|autosplit|rsort|join(|)}": "Saloon|Rose|Jack|Dining",
|
|
"{descr|autosplit|reverse|join(|)}": "Saloon|Dining|Rose|Jack",
|
|
"{var:myvar,a e a b d c c d e}{%myvar|autosplit|uniq|sort|join(,)}": "a,b,c,d,e",
|
|
"{descr|chop(6)|autosplit|append(Restaurant)|join( )}": "Jack Rose Dining Restaurant",
|
|
"{descr|chomp(4)|autosplit|prepend(Mack)|join( )}": "Mack Rose Dining Saloon",
|
|
"{descr|autosplit|remove(Rose)|join( )}": "Jack Dining Saloon",
|
|
"{descr|sslice(0:3)}": "Jac",
|
|
"{descr|sslice(5:11)}": "Rose D",
|
|
"{descr|sslice(:-6)}": "Jack Rose Dining ",
|
|
"{descr|sslice(::2)}": "Jc oeDnn aon",
|
|
"{descr|autosplit|slice(1:3)|join()}": "RoseDining",
|
|
"{descr|autosplit|slice(2:)|join()}": "DiningSaloon",
|
|
"{descr|autosplit|slice(:2)|join()}": "JackRose",
|
|
"{descr|autosplit|slice(:-1)|join()}": "JackRoseDining",
|
|
"{descr|autosplit|slice(::2)|join()}": "JackDining",
|
|
"{descr|filter(startswith Jack)}": "Jack Rose Dining Saloon",
|
|
"{descr|filter(startswith Rose)}": "_",
|
|
"{descr|filter(endswith Saloon)}": "Jack Rose Dining Saloon",
|
|
"{descr|filter(endswith Rose)}": "_",
|
|
"{descr|filter(contains Rose)}": "Jack Rose Dining Saloon",
|
|
"{descr|filter(not contains Rose)}": "_",
|
|
"{descr|filter(matches Jack Rose Dining Saloon)}": "Jack Rose Dining Saloon",
|
|
"{created.mm|filter(== 02)}": "02",
|
|
"{created.mm|filter(<= 2)}": "02",
|
|
"{created.mm|filter(>= 2)}": "02",
|
|
"{created.mm|filter(> 3)}": "_",
|
|
"{created.mm|filter(< 1)}": "_",
|
|
"{created.mm|filter(!= 02)}": "_",
|
|
"{created.mm|int|filter(== 2)}": "2",
|
|
"{created.mm|float|filter(== 2.0)}": "2.0",
|
|
}
|
|
|
|
|
|
TEMPLATE_VALUES_DEU = {
|
|
"{name}": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
|
|
"{original_name}": "IMG_1064",
|
|
"{title}": "Glen Ord",
|
|
"{descr}": "Jack Rose Dining Saloon",
|
|
"{created.date}": "2020-02-04",
|
|
"{created.year}": "2020",
|
|
"{created.yy}": "20",
|
|
"{created.mm}": "02",
|
|
"{created.month}": "Februar",
|
|
"{created.mon}": "Feb",
|
|
"{created.dd}": "04",
|
|
"{created.doy}": "035",
|
|
"{created.dow}": "Dienstag",
|
|
"{place.name}": "Washington, District of Columbia, United States",
|
|
"{place.country_code}": "US",
|
|
"{place.name.country}": "United States",
|
|
"{place.name.state_province}": "District of Columbia",
|
|
"{place.name.city}": "Washington",
|
|
"{place.name.area_of_interest}": "_",
|
|
"{place.address}": "2038 18th St NW, Washington, DC 20009, United States",
|
|
"{place.address.street}": "2038 18th St NW",
|
|
"{place.address.city}": "Washington",
|
|
"{place.address.state_province}": "DC",
|
|
"{place.address.postal_code}": "20009",
|
|
"{place.address.country}": "United States",
|
|
"{place.address.country_code}": "US",
|
|
}
|
|
|
|
TEMPLATE_VALUES_DATE_MODIFIED = {
|
|
"{name}": "A9B73E13-A6F2-4915-8D67-7213B39BAE9F",
|
|
"{original_name}": "IMG_3984",
|
|
"{modified}": "2020-10-31",
|
|
"{modified.date}": "2020-10-31",
|
|
"{modified.year}": "2020",
|
|
"{modified.yy}": "20",
|
|
"{modified.mm}": "10",
|
|
"{modified.month}": "October",
|
|
"{modified.mon}": "Oct",
|
|
"{modified.dd}": "31",
|
|
"{modified.doy}": "305",
|
|
"{modified.dow}": "Saturday",
|
|
"{modified.strftime,%Y-%m-%d-%H%M%S}": "2020-10-31-080321",
|
|
"{modified.strftime}": "_",
|
|
}
|
|
|
|
TEMPLATE_VALUES_DATE_NOT_MODIFIED = {
|
|
# uses creation date instead of modified date
|
|
"{name}": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
|
|
"{original_name}": "IMG_1064",
|
|
"{modified.date}": "2020-02-04",
|
|
"{modified.year}": "2020",
|
|
"{modified.yy}": "20",
|
|
"{modified.mm}": "02",
|
|
"{modified.month}": "February",
|
|
"{modified.mon}": "Feb",
|
|
"{modified.dd}": "04",
|
|
"{modified.dow}": "Tuesday",
|
|
"{modified.doy}": "035",
|
|
"{modified.hour}": "19",
|
|
"{modified.min}": "07",
|
|
"{modified.sec}": "38",
|
|
"{modified.strftime,%Y-%m-%d-%H%M%S}": "2020-02-04-190738",
|
|
}
|
|
|
|
UUID_DETECTED_TEXT = "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91"
|
|
TEMPLATE_VALUES_DETECTED_TEXT = {
|
|
"{detected_text}": " ",
|
|
"{;+detected_text:0.5}": " ",
|
|
}
|
|
|
|
COMMENT_UUID_DICT = {
|
|
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
|
|
"Rhet Turnbull: Nice photo!",
|
|
"None: Wish I was back here!",
|
|
],
|
|
"CCBE0EB9-AE9F-4479-BFFD-107042C75227": ["_"],
|
|
"4E4944A0-3E5C-4028-9600-A8709F2FA1DB": ["None: Nice trophy"],
|
|
}
|
|
|
|
UUID_PHOTO = {
|
|
"DC99FBDD-7A52-4100-A5BB-344131646C30": {
|
|
"{photo.title}": ["St. James's Park"],
|
|
"{photo.favorite?FAVORITE,NOTFAVORITE}": ["NOTFAVORITE"],
|
|
"{photo.hdr}": ["_"],
|
|
"{photo.keywords}": [
|
|
"England",
|
|
"London",
|
|
"London 2018",
|
|
"St. James's Park",
|
|
"UK",
|
|
"United Kingdom",
|
|
],
|
|
"{photo.keywords|lower}": [
|
|
"england",
|
|
"london",
|
|
"london 2018",
|
|
"st. james's park",
|
|
"uk",
|
|
"united kingdom",
|
|
],
|
|
},
|
|
"3DD2C897-F19E-4CA6-8C22-B027D5A71907": {"{photo.place.country_code}": ["AU"]},
|
|
"F12384F6-CD17-4151-ACBA-AE0E3688539E": {"{photo.place.name}": ["_"]},
|
|
"E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51": {"{photo.favorite}": ["favorite"]},
|
|
}
|
|
|
|
UUID_CONDITIONAL = {
|
|
"3DD2C897-F19E-4CA6-8C22-B027D5A71907": {
|
|
"{title matches Elder Park?YES,NO}": ["YES"],
|
|
"{title matches not Elder Park?YES,NO}": ["NO"],
|
|
"{title contains Park?YES,NO}": ["YES"],
|
|
"{title not contains Park?YES,NO}": ["NO"],
|
|
"{title matches Park?YES,NO}": ["NO"],
|
|
"{title matches Elder Park?YES,NO}": ["YES"],
|
|
"{title == Elder Park?YES,NO}": ["YES"],
|
|
"{title != Elder Park?YES,NO}": ["NO"],
|
|
"{title[ ,] == ElderPark?YES,NO}": ["YES"],
|
|
"{title not != Elder Park?YES,NO}": ["YES"],
|
|
"{title not == Elder Park?YES,NO}": ["NO"],
|
|
"{title endswith Park?YES,NO}": ["YES"],
|
|
"{title endswith Elder?YES,NO}": ["NO"],
|
|
"{title startswith Elder?YES,NO}": ["YES"],
|
|
"{title startswith Elder|endswith Park?YES,NO}": ["YES"],
|
|
"{title endswith Elder?YES,NO}": ["NO"],
|
|
"{photo.place.name contains Adelaide?YES,NO}": ["YES"],
|
|
"{photo.place.name|lower contains adelaide?YES,NO}": ["YES"],
|
|
"{photo.place.name|lower contains adelaide|australia?YES,NO}": ["YES"],
|
|
"{photo.place.name|lower not contains adelaide?YES,NO}": ["NO"],
|
|
"{photo.score.overall < 0.7?YES,NO}": ["YES"],
|
|
"{photo.score.overall <= 0.7?YES,NO}": ["YES"],
|
|
"{photo.score.overall > 0.7?YES,NO}": ["NO"],
|
|
"{photo.score.overall >= 0.7?YES,NO}": ["NO"],
|
|
"{photo.score.overall not < 0.7?YES,NO}": ["NO"],
|
|
"{folder_album(-) contains Folder1-SubFolder2-AlbumInFolder?YES,NO}": ["YES"],
|
|
"{folder_album( - ) contains Folder1 - SubFolder2 - AlbumInFolder?YES,NO}": [
|
|
"YES"
|
|
],
|
|
"{folder_album(-)[In,] contains Folder1-SubFolder2-AlbumFolder?YES,NO}": [
|
|
"YES"
|
|
],
|
|
},
|
|
"DC99FBDD-7A52-4100-A5BB-344131646C30": {
|
|
"{keyword == {keyword}?YES,NO}": ["YES"],
|
|
"{keyword contains England?YES,NO}": ["YES"],
|
|
"{keyword contains Eng?YES,NO}": ["YES"],
|
|
"{keyword contains Foo?YES,NO}": ["NO"],
|
|
"{keyword matches England?YES,NO}": ["YES"],
|
|
"{keyword matches Eng?YES,NO}": ["NO"],
|
|
"{keyword contains Foo|Bar|England?YES,NO}": ["YES"],
|
|
"{keyword contains Foo|Bar?YES,NO}": ["NO"],
|
|
"{keyword matches Foo|Bar|England?YES,NO}": ["YES"],
|
|
"{keyword matches Foo|Bar?YES,NO}": ["NO"],
|
|
},
|
|
}
|
|
|
|
UUID_ALBUM_SEQ = {
|
|
"7783E8E6-9CAC-40F3-BE22-81FB7051C266": {
|
|
"album": "/Sorted Manual",
|
|
"templates": {
|
|
"{album_seq}": "0",
|
|
"{album_seq:02d}": "00",
|
|
"{album_seq(1)}": "1",
|
|
"{album_seq(2)}": "2",
|
|
"{album_seq:03d(1)}": "001",
|
|
"{folder_album_seq}": "0",
|
|
"{folder_album_seq:02d}": "00",
|
|
"{folder_album_seq(1)}": "1",
|
|
"{folder_album_seq(2)}": "2",
|
|
"{folder_album_seq:03d(1)}": "001",
|
|
},
|
|
},
|
|
"F12384F6-CD17-4151-ACBA-AE0E3688539E": {
|
|
"album": "/Sorted Manual",
|
|
"templates": {
|
|
"{album_seq}": "2",
|
|
"{album_seq:02d}": "02",
|
|
"{album_seq(1)}": "3",
|
|
"{album_seq:03d(1)}": "003",
|
|
"{folder_album_seq}": "2",
|
|
"{folder_album_seq:02d}": "02",
|
|
"{folder_album_seq(1)}": "3",
|
|
"{folder_album_seq:03d(1)}": "003",
|
|
},
|
|
},
|
|
"E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51": {
|
|
"album": "/Folder1/SubFolder2/AlbumInFolder",
|
|
"templates": {
|
|
"{album_seq}": "1",
|
|
"{album_seq:02d}": "01",
|
|
"{album_seq(1)}": "2",
|
|
"{album_seq:03d(1)}": "002",
|
|
"{folder_album_seq}": "1",
|
|
"{folder_album_seq:02d}": "01",
|
|
"{folder_album_seq(1)}": "2",
|
|
"{folder_album_seq(0)}": "1",
|
|
"{folder_album_seq:03d(1)}": "002",
|
|
"{folder_album|filter(startswith Folder1)}": "Folder1/SubFolder2/AlbumInFolder",
|
|
},
|
|
},
|
|
}
|
|
|
|
UUID_MOMENT = {
|
|
"7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091": {
|
|
"templates": {
|
|
"{moment}": ["Hawaiian Islands"],
|
|
}
|
|
}
|
|
}
|
|
|
|
UUID_EMPTY_TITLE = "7783E8E6-9CAC-40F3-BE22-81FB7051C266" # IMG_3092.heic
|
|
UUID_EMPTY_TITLE_HAS_DESCRIPTION = "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51" # wedding.jpg
|
|
|
|
TEMPLATE_VALUES_EMPTY_TITLE = {
|
|
"{title,No Title} and {descr,No Descr}": "No Title and No Descr",
|
|
"{title?true,false}": "false",
|
|
}
|
|
|
|
TEMPLATE_VALUES_EMPTY_TITLE_HAS_DESCRIPTION = {
|
|
"{title,} {descr} ": " Bride Wedding day ",
|
|
"{strip,{title,} {descr} }": "Bride Wedding day",
|
|
}
|
|
|
|
UUID_PROJECT = "96615063-993E-458B-A9E5-7A68C75A04B6"
|
|
TEMPLATE_VALUES_PROJECT = {
|
|
"{project}": ["Photos Card"],
|
|
"{album}": ["_"],
|
|
"{album_project}": ["Photos Card"],
|
|
"{folder_album}": ["_"],
|
|
"{folder_album_project}": ["Photos Card"],
|
|
}
|
|
|
|
UUID_NO_PROJECT = "C4EA300F-50AD-4FCB-9173-D29B57B52BCF"
|
|
TEMPLATE_VALUES_NO_PROJECT = {"{project}": ["_"]}
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def photosdb_places():
|
|
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_PLACES)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def photosdb():
|
|
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_15_7)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def photosdb_14_6():
|
|
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_14_6)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def photosdb_comments():
|
|
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_COMMENTS)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def photosdb_cloud():
|
|
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_CLOUD)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def photosdb_project():
|
|
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_PROJECTS)
|
|
|
|
|
|
def test_lookup(photosdb_places):
|
|
"""Test that a lookup is returned for every possible value"""
|
|
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
template = PhotoTemplate(photo)
|
|
|
|
for subst in TEMPLATE_SUBSTITUTIONS:
|
|
lookup_str = re.match(r"\{([^\\,}]+)\}", subst).group(1)
|
|
lookup = template.get_template_value(lookup_str, None, None, None)
|
|
assert lookup or lookup is None
|
|
|
|
|
|
def test_lookup_multi(photosdb_places):
|
|
"""Test that a lookup is returned for every possible value"""
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
template = PhotoTemplate(photo)
|
|
|
|
for subst in TEMPLATE_SUBSTITUTIONS_MULTI_VALUED:
|
|
lookup_str = re.match(r"\{([^\\,}]+)\}", subst).group(1)
|
|
if subst in ["{exiftool}", "{photo}", "{function}", "{format}"]:
|
|
continue
|
|
if subst == "{detected_text}" and not is_macos:
|
|
continue
|
|
lookup = template.get_template_value_multi(
|
|
lookup_str,
|
|
path_sep=os.path.sep,
|
|
default=[],
|
|
subfield=None,
|
|
)
|
|
assert isinstance(lookup, list)
|
|
|
|
|
|
def test_subst(photosdb_places):
|
|
"""Test that substitutions are correct"""
|
|
import locale
|
|
|
|
setlocale(locale.LC_ALL, "en_US")
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
for template in TEMPLATE_VALUES:
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered[0] == TEMPLATE_VALUES[template]
|
|
|
|
|
|
def test_subst_date_modified(photosdb_places):
|
|
"""Test that substitutions are correct for date modified"""
|
|
import locale
|
|
|
|
setlocale(locale.LC_ALL, "en_US")
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["date_modified"]])[0]
|
|
|
|
for template in TEMPLATE_VALUES_DATE_MODIFIED:
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered[0] == TEMPLATE_VALUES_DATE_MODIFIED[template]
|
|
|
|
|
|
def test_subst_date_not_modified(photosdb_places):
|
|
"""Test that substitutions are correct for date modified when photo isn't modified"""
|
|
import locale
|
|
|
|
setlocale(locale.LC_ALL, "en_US")
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["date_not_modified"]])[0]
|
|
|
|
for template in TEMPLATE_VALUES_DATE_NOT_MODIFIED:
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered[0] == TEMPLATE_VALUES_DATE_NOT_MODIFIED[template]
|
|
|
|
|
|
def test_subst_locale_1(photosdb_places):
|
|
"""Test that substitutions are correct in user locale"""
|
|
import locale
|
|
|
|
# osxphotos.template sets local on load so set the environment first
|
|
# set locale to DE
|
|
setlocale(locale.LC_ALL, "de_DE.UTF-8")
|
|
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
for template in TEMPLATE_VALUES_DEU:
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered[0] == TEMPLATE_VALUES_DEU[template]
|
|
|
|
|
|
def test_subst_locale_2(photosdb_places):
|
|
"""Test that substitutions are correct in user locale"""
|
|
import locale
|
|
import os
|
|
|
|
# Check if locale is available
|
|
setlocale(locale.LC_ALL, "de_DE.UTF-8")
|
|
|
|
# osxphotos.template sets local on load so set the environment first
|
|
os.environ["LANG"] = "de_DE.UTF-8"
|
|
os.environ["LC_COLLATE"] = "de_DE.UTF-8"
|
|
os.environ["LC_CTYPE"] = "de_DE.UTF-8"
|
|
os.environ["LC_MESSAGES"] = "de_DE.UTF-8"
|
|
os.environ["LC_MONETARY"] = "de_DE.UTF-8"
|
|
os.environ["LC_NUMERIC"] = "de_DE.UTF-8"
|
|
os.environ["LC_TIME"] = "de_DE.UTF-8"
|
|
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
for template in TEMPLATE_VALUES_DEU:
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered[0] == TEMPLATE_VALUES_DEU[template]
|
|
|
|
|
|
def test_subst_default_val(photosdb_places):
|
|
"""Test substitution with default value specified"""
|
|
import locale
|
|
|
|
setlocale(locale.LC_ALL, "en_US")
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
template = "{place.name.area_of_interest,UNKNOWN}"
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered[0] == "UNKNOWN"
|
|
|
|
|
|
def test_subst_default_val_2(photosdb_places):
|
|
"""Test substitution with ',' but no default value"""
|
|
import locale
|
|
|
|
setlocale(locale.LC_ALL, "en_US")
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
template = "{place.name.area_of_interest,}"
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered[0] == ""
|
|
|
|
|
|
def test_subst_unknown_val(photosdb_places):
|
|
"""Test substitution with unknown value specified"""
|
|
import locale
|
|
|
|
setlocale(locale.LC_ALL, "en_US")
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
template = "{created.year}/{foo}"
|
|
rendered, unknown = photo.render_template(template)
|
|
# assert rendered[0] == "2020/{foo}"
|
|
assert unknown == ["foo"]
|
|
|
|
|
|
# def test_subst_double_brace(photosdb_places):
|
|
# """ Test substitution with double brace {{ which should be ignored """
|
|
|
|
# photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
# template = "{created.year}/{{foo}}"
|
|
# rendered, unknown = photo.render_template(template)
|
|
# assert rendered[0] == "2020/{foo}"
|
|
# assert not unknown
|
|
|
|
|
|
def test_subst_unknown_val_with_default(photosdb_places):
|
|
"""Test substitution with unknown value specified"""
|
|
import locale
|
|
|
|
setlocale(locale.LC_ALL, "en_US")
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
template = "{created.year}/{foo,bar}"
|
|
rendered, unknown = photo.render_template(template)
|
|
# assert rendered[0] == "2020/{foo,bar}"
|
|
assert unknown == ["foo"]
|
|
|
|
|
|
def test_subst_multi_1_1_2(photosdb):
|
|
"""Test that substitutions are correct"""
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["1_1_2"]])[0]
|
|
|
|
template = "{created.year}/{album}/{keyword}/{person}"
|
|
expected = ["2018/Pumpkin Farm/Kids/Katie", "2018/Pumpkin Farm/Kids/Suzy"]
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_subst_multi_2_1_1(photosdb):
|
|
"""Test that substitutions are correct"""
|
|
# 2 albums, 1 keyword, 1 person
|
|
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["2_1_1"]])[0]
|
|
|
|
template = "{created.year}/{album}/{keyword}/{person}"
|
|
expected = [
|
|
"2018/Pumpkin Farm/Kids/Katie",
|
|
"2018/Test Album/Kids/Katie",
|
|
"2018/Multi Keyword/Kids/Katie",
|
|
]
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_subst_multi_2_1_1_single(photosdb):
|
|
"""Test that substitutions are correct"""
|
|
# 2 albums, 1 keyword, 1 person but only do keywords
|
|
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["2_1_1"]])[0]
|
|
|
|
template = "{keyword}"
|
|
expected = ["Kids"]
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_subst_multi_0_2_0(photosdb):
|
|
"""Test that substitutions are correct"""
|
|
# 0 albums, 2 keywords, 0 persons
|
|
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["0_2_0"]])[0]
|
|
|
|
template = "{created.year}/{album}/{keyword}/{person}"
|
|
expected = ["2019/_/wedding/_", "2019/_/flowers/_"]
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_subst_multi_0_2_0_single(photosdb):
|
|
"""Test that substitutions are correct"""
|
|
# 0 albums, 2 keywords, 0 persons, but only do albums
|
|
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["0_2_0"]])[0]
|
|
|
|
template = "{created.year}/{album}"
|
|
expected = ["2019/_"]
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_subst_multi_0_2_0_default_val(photosdb):
|
|
"""Test that substitutions are correct"""
|
|
# 0 albums, 2 keywords, 0 persons, default vals provided
|
|
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["0_2_0"]])[0]
|
|
|
|
template = "{created.year}/{album,NOALBUM}/{keyword,NOKEYWORD}/{person,NOPERSON}"
|
|
expected = ["2019/NOALBUM/wedding/NOPERSON", "2019/NOALBUM/flowers/NOPERSON"]
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_subst_multi_0_2_0_default_val_unknown_val(photosdb):
|
|
"""Test that substitutions are correct"""
|
|
# 0 albums, 2 keywords, 0 persons, default vals provided, unknown val in template
|
|
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["0_2_0"]])[0]
|
|
|
|
template = "{created.year}/{album,NOALBUM}/{keyword,NOKEYWORD}/{person}/{foo}/{baz}"
|
|
expected = [
|
|
"2019/NOALBUM/wedding/_/{foo}/{baz}",
|
|
"2019/NOALBUM/flowers/_/{foo}/{baz}",
|
|
]
|
|
rendered, unknown = photo.render_template(template)
|
|
# assert sorted(rendered) == sorted(expected)
|
|
assert unknown == ["foo", "baz"]
|
|
|
|
|
|
def test_subst_multi_0_2_0_default_val_unknown_val_2(photosdb):
|
|
"""Test that substitutions are correct"""
|
|
# 0 albums, 2 keywords, 0 persons, default vals provided, unknown val in template
|
|
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["0_2_0"]])[0]
|
|
|
|
template = "{created.year}/{album,NOALBUM}/{keyword,NOKEYWORD}/{person}/{foo,bar}/{baz,bar}"
|
|
expected = [
|
|
"2019/NOALBUM/wedding/_/{foo,bar}/{baz,bar}",
|
|
"2019/NOALBUM/flowers/_/{foo,bar}/{baz,bar}",
|
|
]
|
|
rendered, unknown = photo.render_template(template)
|
|
# assert sorted(rendered) == sorted(expected)
|
|
assert unknown == ["foo", "baz"]
|
|
|
|
|
|
def test_subst_multi_folder_albums_1(photosdb):
|
|
"""Test substitutions for folder_album are correct"""
|
|
|
|
# photo in an album in a folder
|
|
photo = photosdb.photos(uuid=[UUID_DICT["folder_album_1"]])[0]
|
|
template = "{folder_album}"
|
|
expected = [
|
|
"2018-10 - Sponsion, Museum, Frühstück, Römermuseum",
|
|
"2019-10/11 Paris Clermont",
|
|
"Folder1/SubFolder2/AlbumInFolder",
|
|
"Sorted Manual",
|
|
"Sorted Newest First",
|
|
"Sorted Oldest First",
|
|
"Sorted Title",
|
|
]
|
|
rendered, unknown = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
assert unknown == []
|
|
|
|
|
|
def test_subst_multi_folder_albums_1_path_sep(photosdb):
|
|
"""Test substitutions for folder_album are correct with custom PATH_SEP"""
|
|
|
|
# photo in an album in a folder
|
|
photo = photosdb.photos(uuid=[UUID_DICT["folder_album_1"]])[0]
|
|
template = "{folder_album(:)}"
|
|
expected = [
|
|
"2018-10 - Sponsion, Museum, Frühstück, Römermuseum",
|
|
"2019-10/11 Paris Clermont",
|
|
"Folder1:SubFolder2:AlbumInFolder",
|
|
"Sorted Manual",
|
|
"Sorted Newest First",
|
|
"Sorted Oldest First",
|
|
"Sorted Title",
|
|
]
|
|
rendered, unknown = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
assert unknown == []
|
|
|
|
|
|
def test_subst_multi_folder_albums_1_path_sep_lower(photosdb):
|
|
"""Test substitutions for folder_album are correct with custom PATH_SEP"""
|
|
|
|
# photo in an album in a folder
|
|
photo = photosdb.photos(uuid=[UUID_DICT["folder_album_1"]])[0]
|
|
template = "{folder_album(:)|lower}"
|
|
expected = [
|
|
"2018-10 - sponsion, museum, frühstück, römermuseum",
|
|
"2019-10/11 paris clermont",
|
|
"folder1:subfolder2:albuminfolder",
|
|
"sorted manual",
|
|
"sorted newest first",
|
|
"sorted oldest first",
|
|
"sorted title",
|
|
]
|
|
rendered, unknown = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
assert unknown == []
|
|
|
|
|
|
def test_subst_multi_folder_albums_2(photosdb):
|
|
"""Test substitutions for folder_album are correct"""
|
|
|
|
# photo in an album in a folder
|
|
photo = photosdb.photos(uuid=[UUID_DICT["folder_album_no_folder"]])[0]
|
|
template = "{folder_album}"
|
|
expected = ["Multi Keyword", "Pumpkin Farm", "Test Album"]
|
|
rendered, unknown = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
assert unknown == []
|
|
|
|
|
|
def test_subst_multi_folder_albums_2_path_sep(photosdb):
|
|
"""Test substitutions for folder_album are correct with custom PATH_SEP"""
|
|
|
|
# photo in an album in a folder
|
|
photo = photosdb.photos(uuid=[UUID_DICT["folder_album_no_folder"]])[0]
|
|
template = "{folder_album(:)}"
|
|
expected = ["Multi Keyword", "Pumpkin Farm", "Test Album"]
|
|
rendered, unknown = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
assert unknown == []
|
|
|
|
|
|
def test_subst_multi_folder_albums_3(photosdb_14_6):
|
|
"""Test substitutions for folder_album on < Photos 5"""
|
|
|
|
# photo in an album in a folder
|
|
photo = photosdb_14_6.photos(uuid=[UUID_DICT["mojave_album_1"]])[0]
|
|
template = "{folder_album}"
|
|
expected = ["Folder1/SubFolder2/AlbumInFolder", "Pumpkin Farm", "Test Album (1)"]
|
|
rendered, unknown = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
assert unknown == []
|
|
|
|
|
|
def test_subst_multi_folder_albums_3_path_sep(photosdb_14_6):
|
|
"""Test substitutions for folder_album on < Photos 5 with custom PATH_SEP"""
|
|
|
|
# photo in an album in a folder
|
|
photo = photosdb_14_6.photos(uuid=[UUID_DICT["mojave_album_1"]])[0]
|
|
template = "{folder_album(>)}"
|
|
expected = ["Folder1>SubFolder2>AlbumInFolder", "Pumpkin Farm", "Test Album (1)"]
|
|
rendered, unknown = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
assert unknown == []
|
|
|
|
|
|
def test_subst_multi_folder_albums_4_path_sep_lower(photosdb_14_6):
|
|
"""Test substitutions for folder_album on < Photos 5 with custom PATH_SEP"""
|
|
|
|
# photo in an album in a folder
|
|
photo = photosdb_14_6.photos(uuid=[UUID_DICT["mojave_album_1"]])[0]
|
|
template = "{folder_album(>)|lower}"
|
|
expected = ["folder1>subfolder2>albuminfolder", "pumpkin farm", "test album (1)"]
|
|
rendered, unknown = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(expected)
|
|
assert unknown == []
|
|
|
|
|
|
def test_subst_strftime(photosdb_places):
|
|
"""Test that strftime substitutions are correct"""
|
|
import locale
|
|
|
|
setlocale(locale.LC_ALL, "en_US")
|
|
photo = photosdb_places.photos(uuid=[UUID_DICT["place_dc"]])[0]
|
|
|
|
rendered, unmatched = photo.render_template("{created.strftime,%Y-%m-%d-%H%M%S}")
|
|
assert rendered[0] == "2020-02-04-190738"
|
|
|
|
rendered, unmatched = photo.render_template("{created.strftime}")
|
|
assert rendered[0] == "_"
|
|
|
|
|
|
def test_subst_expand_inplace_1(photosdb):
|
|
"""Test that substitutions are correct when expand_inplace=True"""
|
|
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["1_1_2"]])[0]
|
|
|
|
template = "{person}"
|
|
expected = ["Katie,Suzy"]
|
|
options = RenderOptions(expand_inplace=True)
|
|
rendered, unknown = photo.render_template(template, options)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_subst_expand_inplace_2(photosdb):
|
|
"""Test that substitutions are correct when expand_inplace=True"""
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["1_1_2"]])[0]
|
|
|
|
template = "{person}-{keyword}"
|
|
expected = ["Katie,Suzy-Kids"]
|
|
options = RenderOptions(expand_inplace=True)
|
|
rendered, unknown = photo.render_template(template, options)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_subst_expand_inplace_3(photosdb):
|
|
"""Test that substitutions are correct when expand_inplace=True and inplace_sep specified"""
|
|
# one album, one keyword, two persons
|
|
photo = photosdb.photos(uuid=[UUID_DICT["1_1_2"]])[0]
|
|
|
|
template = "{person}-{keyword}"
|
|
expected = ["Katie; Suzy-Kids"]
|
|
|
|
options = RenderOptions(expand_inplace=True, inplace_sep="; ")
|
|
rendered, unknown = photo.render_template(template, options)
|
|
assert sorted(rendered) == sorted(expected)
|
|
|
|
|
|
def test_comment(photosdb_comments):
|
|
import osxphotos
|
|
|
|
for uuid in COMMENT_UUID_DICT:
|
|
photo = photosdb_comments.get_photo(uuid)
|
|
comments = photo.render_template("{comment}")
|
|
assert comments[0] == COMMENT_UUID_DICT[uuid]
|
|
|
|
|
|
def test_media_type(photosdb_cloud):
|
|
"""test {media_type} template"""
|
|
|
|
for field, uuid in UUID_MEDIA_TYPE.items():
|
|
if uuid is not None:
|
|
photo = photosdb_cloud.get_photo(uuid)
|
|
rendered, _ = photo.render_template("{media_type}")
|
|
assert rendered[0] == osxphotos.phototemplate.MEDIA_TYPE_DEFAULTS[field]
|
|
|
|
|
|
def test_media_type_default(photosdb_cloud):
|
|
"""test {media_type,photo=foo} template style"""
|
|
|
|
for field, uuid in UUID_MEDIA_TYPE.items():
|
|
if uuid is not None:
|
|
photo = photosdb_cloud.get_photo(uuid)
|
|
rendered, _ = photo.render_template("{media_type," + f"{field}" + "=foo}")
|
|
assert rendered[0] == "foo"
|
|
|
|
|
|
def test_bool_values(photosdb_cloud):
|
|
"""test {bool?TRUE,FALSE} template values"""
|
|
|
|
for field, uuid in UUID_BOOL_VALUES.items():
|
|
if uuid is not None:
|
|
photo = photosdb_cloud.get_photo(uuid)
|
|
edited = field == "edited_version"
|
|
options = RenderOptions(edited_version=edited)
|
|
rendered, _ = photo.render_template(
|
|
"{" + f"{field}" + "?True,False}", options
|
|
)
|
|
assert rendered[0] == "True"
|
|
|
|
|
|
def test_bool_values_not(photosdb_cloud):
|
|
"""test {bool?TRUE,FALSE} template values for FALSE values"""
|
|
|
|
for field, uuid in UUID_BOOL_VALUES_NOT.items():
|
|
if uuid is not None:
|
|
photo = photosdb_cloud.get_photo(uuid)
|
|
rendered, _ = photo.render_template("{" + f"{field}" + "?True,False}")
|
|
assert rendered[0] == "False"
|
|
|
|
|
|
def test_partial_match(photosdb_cloud):
|
|
"""test that template successfully rejects a field that is superset of valid field"""
|
|
|
|
for uuid in COMMENT_UUID_DICT:
|
|
photo = photosdb_cloud.get_photo(uuid)
|
|
rendered, notmatched = photo.render_template("{keywords}")
|
|
assert [rendered, notmatched] == [[], ["keywords"]]
|
|
rendered, notmatched = photo.render_template("{keywords,}")
|
|
assert [rendered, notmatched] == [[], ["keywords"]]
|
|
rendered, notmatched = photo.render_template("{keywords,foo}")
|
|
assert [rendered, notmatched] == [[], ["keywords"]]
|
|
rendered, notmatched = photo.render_template("{,+keywords,foo}")
|
|
assert [rendered, notmatched] == [[], ["keywords"]]
|
|
|
|
|
|
def test_expand_in_place_with_delim(photosdb):
|
|
"""Test that substitutions are correct when {DELIM+FIELD} format used"""
|
|
import osxphotos
|
|
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
|
|
for template in TEMPLATE_VALUES_MULTI_KEYWORDS:
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(TEMPLATE_VALUES_MULTI_KEYWORDS[template])
|
|
|
|
|
|
def test_expand_in_place_with_delim_single_value(photosdb):
|
|
"""Test that single-value substitutions are correct when {DELIM+FIELD} format used"""
|
|
|
|
photo = photosdb.get_photo(UUID_TITLE)
|
|
|
|
for template in TEMPLATE_VALUES_TITLE:
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(TEMPLATE_VALUES_TITLE[template])
|
|
|
|
|
|
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
|
|
def test_exiftool_template(photosdb):
|
|
for uuid in UUID_EXIFTOOL:
|
|
photo = photosdb.get_photo(uuid)
|
|
for template in UUID_EXIFTOOL[uuid]:
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(UUID_EXIFTOOL[uuid][template])
|
|
|
|
|
|
def test_hdr(photosdb):
|
|
"""Test hdr"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
photomock = PhotoInfoMock(photo, hdr="hdr")
|
|
rendered, _ = photomock.render_template("{hdr}")
|
|
assert rendered == ["hdr"]
|
|
|
|
|
|
def test_edited(photosdb):
|
|
"""Test edited"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
photomock = PhotoInfoMock(photo, hasadjustments=True)
|
|
rendered, _ = photomock.render_template("{edited}")
|
|
assert rendered == ["edited"]
|
|
|
|
|
|
def test_favorite(photosdb):
|
|
"""Test favorite"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
photomock = PhotoInfoMock(photo, favorite=True)
|
|
rendered, _ = photomock.render_template("{favorite}")
|
|
assert rendered == ["favorite"]
|
|
|
|
|
|
def test_nested_template_bool(photosdb):
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
template = "{hdr?{edited?HDR_EDITED,HDR_NOT_EDITED},{edited?NOT_HDR_EDITED,NOT_HDR_NOT_EDITED}}"
|
|
|
|
photomock = PhotoInfoMock(photo, hdr=True, hasadjustments=True)
|
|
rendered, _ = photomock.render_template(template)
|
|
assert rendered == ["HDR_EDITED"]
|
|
|
|
photomock = PhotoInfoMock(photo, hdr=True, hasadjustments=False)
|
|
rendered, _ = photomock.render_template(template)
|
|
assert rendered == ["HDR_NOT_EDITED"]
|
|
|
|
photomock = PhotoInfoMock(photo, hdr=False, hasadjustments=False)
|
|
rendered, _ = photomock.render_template(template)
|
|
assert rendered == ["NOT_HDR_NOT_EDITED"]
|
|
|
|
photomock = PhotoInfoMock(photo, hdr=False, hasadjustments=True)
|
|
rendered, _ = photomock.render_template(template)
|
|
assert rendered == ["NOT_HDR_EDITED"]
|
|
|
|
|
|
def test_nested_template(photosdb):
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
photomock = PhotoInfoMock(photo, keywords=[], title="My Title")
|
|
|
|
rendered, _ = photomock.render_template("{keyword,{title}}")
|
|
assert rendered == ["My Title"]
|
|
|
|
|
|
def test_punctuation(photosdb):
|
|
from osxphotos.phototemplate import PUNCTUATION
|
|
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
for punc in PUNCTUATION:
|
|
rendered, _ = photo.render_template("{" + punc + "}")
|
|
assert rendered[0] == PUNCTUATION[punc]
|
|
|
|
|
|
def test_photo_template(photosdb):
|
|
for uuid in UUID_PHOTO:
|
|
photo = photosdb.get_photo(uuid)
|
|
for template in UUID_PHOTO[uuid]:
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(UUID_PHOTO[uuid][template])
|
|
|
|
|
|
def test_conditional(photosdb):
|
|
for uuid in UUID_CONDITIONAL:
|
|
photo = photosdb.get_photo(uuid)
|
|
for template in UUID_CONDITIONAL[uuid]:
|
|
rendered, _ = photo.render_template(template)
|
|
assert sorted(rendered) == sorted(UUID_CONDITIONAL[uuid][template])
|
|
|
|
|
|
def test_function_hyphen_dir(photosdb):
|
|
"""Test {function} with a hyphenated directory (#477)"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
rendered, _ = photo.render_template(
|
|
"{function:tests/hyphen-dir/template_function.py::foo}"
|
|
)
|
|
assert rendered == [f"{photo.original_filename}-FOO"]
|
|
|
|
|
|
def test_function(photosdb):
|
|
"""Test {function}"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
rendered, _ = photo.render_template("{function:tests/template_function.py::foo}")
|
|
assert rendered == [f"{photo.original_filename}-FOO"]
|
|
|
|
|
|
def test_function_bad(photosdb):
|
|
"""Test invalid {function}"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
with pytest.raises(ValueError):
|
|
rendered, _ = photo.render_template(
|
|
"{function:tests/template_function.py::foobar}"
|
|
)
|
|
|
|
|
|
def test_function_filter(photosdb):
|
|
"""Test {field|function} filter"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
|
|
rendered, _ = photo.render_template(
|
|
"{photo.original_filename|function:tests/template_filter.py::myfilter}"
|
|
)
|
|
assert rendered == [f"foo-{photo.original_filename}"]
|
|
|
|
rendered, _ = photo.render_template(
|
|
"{photo.original_filename|lower|function:tests/template_filter.py::myfilter}"
|
|
)
|
|
assert rendered == [f"foo-{photo.original_filename.lower()}"]
|
|
|
|
rendered, _ = photo.render_template(
|
|
"{photo.original_filename|function:tests/template_filter.py::myfilter|lower}"
|
|
)
|
|
assert rendered == [f"foo-{photo.original_filename.lower()}"]
|
|
|
|
|
|
def test_function_filter_bad(photosdb):
|
|
"""Test invalid {field|function} filter"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
# bad field raises SyntaxError
|
|
# bad function raises ValueError
|
|
with pytest.raises((SyntaxError, ValueError)):
|
|
rendered, _ = photo.render_template(
|
|
"{photo.original_filename|function:tests/template_filter.py::foobar}"
|
|
)
|
|
|
|
|
|
def test_export_dir():
|
|
"""Test {export_dir} template"""
|
|
from osxphotos.photoinfo import PhotoInfoNone
|
|
from osxphotos.phototemplate import PhotoTemplate
|
|
|
|
options = RenderOptions(export_dir="/foo/bar")
|
|
template = PhotoTemplate(PhotoInfoNone())
|
|
rendered, _ = template.render("{export_dir}", options)
|
|
assert rendered[0] == "/foo/bar"
|
|
|
|
rendered, _ = template.render("{export_dir.name}", options)
|
|
assert rendered[0] == "bar"
|
|
|
|
rendered, _ = template.render("{export_dir.parent}", options)
|
|
assert rendered[0] == "/foo"
|
|
|
|
rendered, _ = template.render("{export_dir.stem}", options)
|
|
assert rendered[0] == "bar"
|
|
|
|
rendered, _ = template.render("{export_dir.suffix}", options)
|
|
assert rendered[0] == ""
|
|
|
|
with pytest.raises(ValueError):
|
|
rendered, _ = template.render("{export_dir.foo}", options)
|
|
|
|
|
|
def test_filepath():
|
|
"""Test {filepath} template"""
|
|
from osxphotos.photoinfo import PhotoInfoNone
|
|
from osxphotos.phototemplate import PhotoTemplate
|
|
|
|
options = RenderOptions(filepath="/foo/bar.jpeg")
|
|
template = PhotoTemplate(PhotoInfoNone())
|
|
rendered, _ = template.render("{filepath}", options)
|
|
assert rendered[0] == "/foo/bar.jpeg"
|
|
|
|
rendered, _ = template.render("{filepath.name}", options)
|
|
assert rendered[0] == "bar.jpeg"
|
|
|
|
rendered, _ = template.render("{filepath.parent}", options)
|
|
assert rendered[0] == "/foo"
|
|
|
|
rendered, _ = template.render("{filepath.parent.name}", options)
|
|
assert rendered[0] == "foo"
|
|
|
|
rendered, _ = template.render("{filepath.stem}", options)
|
|
assert rendered[0] == "bar"
|
|
|
|
rendered, _ = template.render("{filepath.suffix}", options)
|
|
assert rendered[0] == ".jpeg"
|
|
|
|
with pytest.raises(ValueError):
|
|
rendered, _ = template.render("{filepath.foo}", options)
|
|
|
|
|
|
def test_id(photosdb):
|
|
"""Test {id} template"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
rendered, _ = photo.render_template("{id}")
|
|
assert rendered[0] == "7"
|
|
|
|
rendered, _ = photo.render_template("{id:03d}")
|
|
assert rendered[0] == "007"
|
|
|
|
|
|
def test_album_seq(photosdb):
|
|
"""Test {album_seq} and {folder_album_seq} templates"""
|
|
from osxphotos.phototemplate import RenderOptions
|
|
|
|
for uuid in UUID_ALBUM_SEQ:
|
|
photo = photosdb.get_photo(uuid)
|
|
album = UUID_ALBUM_SEQ[uuid]["album"]
|
|
options = RenderOptions(dest_path=album)
|
|
for template, value in UUID_ALBUM_SEQ[uuid]["templates"].items():
|
|
rendered, _ = photo.render_template(template, options=options)
|
|
assert rendered[0] == value
|
|
|
|
|
|
@pytest.mark.skipif(not is_macos, reason="Only works on macOS")
|
|
def test_detected_text(photosdb):
|
|
"""Test {detected_text} template"""
|
|
photo = photosdb.get_photo(UUID_DETECTED_TEXT)
|
|
for template, value in TEMPLATE_VALUES_DETECTED_TEXT.items():
|
|
rendered, _ = photo.render_template(template)
|
|
assert value in "".join(rendered)
|
|
|
|
|
|
def test_empty_title(photosdb):
|
|
"""Test for issue #506"""
|
|
photo = photosdb.get_photo(UUID_EMPTY_TITLE)
|
|
for template, value in TEMPLATE_VALUES_EMPTY_TITLE.items():
|
|
rendered, _ = photo.render_template(template)
|
|
assert value in "".join(rendered)
|
|
|
|
|
|
def test_strip(photosdb):
|
|
"""Test {strip} template"""
|
|
photo = photosdb.get_photo(UUID_EMPTY_TITLE_HAS_DESCRIPTION)
|
|
for template, value in TEMPLATE_VALUES_EMPTY_TITLE_HAS_DESCRIPTION.items():
|
|
rendered, _ = photo.render_template(template)
|
|
assert value in "".join(rendered)
|
|
|
|
|
|
def test_project(photosdb_project):
|
|
"""Test {project} template"""
|
|
photo = photosdb_project.get_photo(UUID_PROJECT)
|
|
for template, value in TEMPLATE_VALUES_PROJECT.items():
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered == value
|
|
|
|
|
|
def test_no_project(photosdb_project):
|
|
"""Test {project} template with no project"""
|
|
photo = photosdb_project.get_photo(UUID_NO_PROJECT)
|
|
for template, value in TEMPLATE_VALUES_NO_PROJECT.items():
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered == value
|
|
|
|
|
|
def test_moment(photosdb):
|
|
"""Test {moment} template"""
|
|
for uuid in UUID_MOMENT:
|
|
photo = photosdb.get_photo(uuid)
|
|
for template, value in UUID_MOMENT[uuid]["templates"].items():
|
|
rendered, _ = photo.render_template(template)
|
|
assert rendered == value
|
|
|
|
|
|
def test_bad_slice(photosdb):
|
|
"""Test invalid {|slice} filter"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
# bad field raises SyntaxError
|
|
# bad function raises ValueError
|
|
with pytest.raises((SyntaxError, ValueError)):
|
|
rendered, _ = photo.render_template("{photo.original_filename|slice(1:2:3:4)}")
|
|
|
|
|
|
def test_bad_sslice(photosdb):
|
|
"""Test invalid {|sslice} filter"""
|
|
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
|
|
# bad field raises SyntaxError
|
|
# bad function raises ValueError
|
|
with pytest.raises((SyntaxError, ValueError)):
|
|
rendered, _ = photo.render_template("{photo.original_filename|sslice(1:2:3:4)}")
|
|
|
|
|
|
def test_punctuation():
|
|
"""Test punctuation template fields"""
|
|
template_string = ""
|
|
expected_string = ""
|
|
for field, value in PUNCTUATION.items():
|
|
template_string += "{" + field + "}"
|
|
expected_string += f"{value}"
|
|
template = PhotoTemplate(PhotoInfoNone())
|
|
options = RenderOptions()
|
|
rendered, _ = template.render(template_string, options)
|
|
assert rendered == [expected_string]
|