diff --git a/README.md b/README.md index 77208451..a0b98905 100644 --- a/README.md +++ b/README.md @@ -738,6 +738,10 @@ Options: or 12:00:00. --to-time TIME Search by item end time of day, e.g. 12:00 or 12:00:00. + --year INTEGER Search for items from a specific year, e.g. + --year 2022 to find all photos from the year + 2022. May be repeated to search multiple + years. --has-comment Search for photos that have comments. --no-comment Search for photos with no comments. --has-likes Search for photos that have likes. diff --git a/docs/_modules/osxphotos/photosdb/photosdb.html b/docs/_modules/osxphotos/photosdb/photosdb.html index 1809d7d9..fc73d05d 100644 --- a/docs/_modules/osxphotos/photosdb/photosdb.html +++ b/docs/_modules/osxphotos/photosdb/photosdb.html @@ -5,7 +5,7 @@ - osxphotos.photosdb.photosdb — osxphotos 0.47.5 documentation + osxphotos.photosdb.photosdb — osxphotos 0.47.8 documentation @@ -3322,6 +3322,9 @@ if options.to_time: photos = [p for p in photos if p.date.time() <= options.to_time] + if options.year: + photos = [p for p in photos if p.date.year in options.year] + if name: # search filename fields for text # if more than one, find photos with all title values in filename diff --git a/osxphotos/cli/common.py b/osxphotos/cli/common.py index 9c6114cc..12844bcf 100644 --- a/osxphotos/cli/common.py +++ b/osxphotos/cli/common.py @@ -368,6 +368,13 @@ def QUERY_OPTIONS(f): help="Search by item end time of day, e.g. 12:00 or 12:00:00.", type=TimeISO8601(), ), + o( + "--year", + help="Search for items from a specific year, e.g. --year 2022 to find all photos from the year 2022. " + "May be repeated to search multiple years.", + multiple=True, + type=int, + ), o("--has-comment", is_flag=True, help="Search for photos that have comments."), o("--no-comment", is_flag=True, help="Search for photos with no comments."), o("--has-likes", is_flag=True, help="Search for photos that have likes."), diff --git a/osxphotos/cli/export.py b/osxphotos/cli/export.py index b01e4fe2..ac059ff9 100644 --- a/osxphotos/cli/export.py +++ b/osxphotos/cli/export.py @@ -697,6 +697,7 @@ def export( to_date, from_time, to_time, + year, verbose, timestamp, no_progress, @@ -1018,6 +1019,7 @@ def export( uuid_from_file = cfg.uuid_from_file verbose = cfg.verbose xattr_template = cfg.xattr_template + year = cfg.year # config file might have changed verbose color_theme = get_theme(theme) @@ -1300,6 +1302,7 @@ def export( to_date=to_date, from_time=from_time, to_time=to_time, + year=year, portrait=portrait, not_portrait=not_portrait, screenshot=screenshot, diff --git a/osxphotos/cli/query.py b/osxphotos/cli/query.py index efbe7bc6..b9642b2d 100644 --- a/osxphotos/cli/query.py +++ b/osxphotos/cli/query.py @@ -111,6 +111,7 @@ def query( to_date, from_time, to_time, + year, portrait, not_portrait, screenshot, @@ -174,6 +175,7 @@ def query( to_date, from_time, to_time, + year, label, is_reference, query_eval, @@ -280,6 +282,7 @@ def query( to_date=to_date, from_time=from_time, to_time=to_time, + year=year, portrait=portrait, not_portrait=not_portrait, screenshot=screenshot, diff --git a/osxphotos/cli/repl.py b/osxphotos/cli/repl.py index 44d87bb0..d4c6c3d8 100644 --- a/osxphotos/cli/repl.py +++ b/osxphotos/cli/repl.py @@ -256,6 +256,7 @@ def _query_options_from_kwargs(**kwargs) -> QueryOptions: "to_date", "from_time", "to_time", + "year", "label", "is_reference", "query_eval", diff --git a/osxphotos/docs/_modules/osxphotos/photosdb/photosdb.html b/osxphotos/docs/_modules/osxphotos/photosdb/photosdb.html index 1809d7d9..fc73d05d 100644 --- a/osxphotos/docs/_modules/osxphotos/photosdb/photosdb.html +++ b/osxphotos/docs/_modules/osxphotos/photosdb/photosdb.html @@ -5,7 +5,7 @@ - osxphotos.photosdb.photosdb — osxphotos 0.47.5 documentation + osxphotos.photosdb.photosdb — osxphotos 0.47.8 documentation @@ -3322,6 +3322,9 @@ if options.to_time: photos = [p for p in photos if p.date.time() <= options.to_time] + if options.year: + photos = [p for p in photos if p.date.year in options.year] + if name: # search filename fields for text # if more than one, find photos with all title values in filename diff --git a/osxphotos/photosdb/photosdb.py b/osxphotos/photosdb/photosdb.py index c8a1f9eb..311c6587 100644 --- a/osxphotos/photosdb/photosdb.py +++ b/osxphotos/photosdb/photosdb.py @@ -3289,6 +3289,9 @@ class PhotosDB: if options.to_time: photos = [p for p in photos if p.date.time() <= options.to_time] + if options.year: + photos = [p for p in photos if p.date.year in options.year] + if name: # search filename fields for text # if more than one, find photos with all title values in filename diff --git a/osxphotos/queryoptions.py b/osxphotos/queryoptions.py index 813605f9..f81c9f65 100644 --- a/osxphotos/queryoptions.py +++ b/osxphotos/queryoptions.py @@ -87,6 +87,7 @@ class QueryOptions: function: Optional[List[Tuple[callable, str]]] = None selected: Optional[bool] = None exif: Optional[Iterable[Tuple[str, str]]] = None + year: Optional[int] = None def asdict(self): return asdict(self) diff --git a/tests/test_cli.py b/tests/test_cli.py index 5b18d2e5..6db77358 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -740,6 +740,8 @@ CLI_FINDER_TAGS = { }, } +CLI_EXPORT_YEAR_2017 = ["IMG_4547.jpg"] + LABELS_JSON = { "labels": { "Water": 2, @@ -1494,6 +1496,28 @@ def test_export_skip_uuid(): assert skipped_file not in files +def test_export_year(): + """test export with --year""" + runner = CliRunner() + cwd = os.getcwd() + # pylint: disable=not-context-manager + with runner.isolated_filesystem(): + result = runner.invoke( + export, + [ + os.path.join(cwd, CLI_PHOTOS_DB), + ".", + "-V", + "--year", + "2017", + "--skip-edited", + ], + ) + assert result.exit_code == 0 + files = glob.glob("*") + assert sorted(files) == sorted(CLI_EXPORT_YEAR_2017) + + def test_export_preview(): """test export with --preview""" @@ -2572,6 +2596,80 @@ def test_query_time(): assert len(json_got) == 3 +def test_query_year_1(): + """Test --year""" + + os.environ["TZ"] = "US/Pacific" + time.tzset() + + runner = CliRunner() + cwd = os.getcwd() + result = runner.invoke( + query, + [ + "--json", + "--db", + os.path.join(cwd, CLI_PHOTOS_DB), + "--year", + 2017, + ], + ) + assert result.exit_code == 0 + + json_got = json.loads(result.output) + assert len(json_got) == 1 + + +def test_query_year_2(): + """Test --year with multiple years""" + + os.environ["TZ"] = "US/Pacific" + time.tzset() + + runner = CliRunner() + cwd = os.getcwd() + result = runner.invoke( + query, + [ + "--json", + "--db", + os.path.join(cwd, CLI_PHOTOS_DB), + "--year", + 2017, + "--year", + 2018, + ], + ) + assert result.exit_code == 0 + + json_got = json.loads(result.output) + assert len(json_got) == 6 + + +def test_query_year_3(): + """Test --year with invalid year""" + + os.environ["TZ"] = "US/Pacific" + time.tzset() + + runner = CliRunner() + cwd = os.getcwd() + result = runner.invoke( + query, + [ + "--json", + "--db", + os.path.join(cwd, CLI_PHOTOS_DB), + "--year", + 3000, + ], + ) + assert result.exit_code == 0 + + json_got = json.loads(result.output) + assert len(json_got) == 0 + + def test_query_keyword_1(): """Test query --keyword"""