diff --git a/osxphotos/cli/common.py b/osxphotos/cli/common.py index 9cfe8024..09bb1522 100644 --- a/osxphotos/cli/common.py +++ b/osxphotos/cli/common.py @@ -289,6 +289,7 @@ def QUERY_OPTIONS(f): help="Case insensitive search for title, description, place, keyword, person, or album.", ), o("--edited", is_flag=True, help="Search for photos that have been edited."), + o("--not-edited", is_flag=True, help="Search for photos that have not been edited."), o( "--external-edit", is_flag=True, diff --git a/osxphotos/cli/export.py b/osxphotos/cli/export.py index d7608cb9..6c4011de 100644 --- a/osxphotos/cli/export.py +++ b/osxphotos/cli/export.py @@ -805,6 +805,7 @@ def export( no_title, not_burst, not_cloudasset, + not_edited, not_favorite, not_hdr, not_hidden, @@ -1024,6 +1025,7 @@ def export( no_title = cfg.no_title not_burst = cfg.not_burst not_cloudasset = cfg.not_cloudasset + not_edited = cfg.not_edited not_favorite = cfg.not_favorite not_hdr = cfg.not_hdr not_hidden = cfg.not_hidden @@ -1117,6 +1119,7 @@ def export( ("cloudasset", "not_cloudasset"), ("deleted", "deleted_only"), ("description", "no_description"), + ("edited", "not_edited"), ("export_as_hardlink", "convert_to_jpeg"), ("export_as_hardlink", "download_missing"), ("export_as_hardlink", "exiftool"), @@ -1398,6 +1401,7 @@ def export( no_title=no_title, not_burst=not_burst, not_cloudasset=not_cloudasset, + not_edited = not_edited, not_favorite=not_favorite, not_hdr=not_hdr, not_hidden=not_hidden, diff --git a/osxphotos/cli/query.py b/osxphotos/cli/query.py index 42b4328e..71834f59 100644 --- a/osxphotos/cli/query.py +++ b/osxphotos/cli/query.py @@ -121,6 +121,7 @@ def query( no_title, not_burst, not_cloudasset, + not_edited, not_favorite, not_hdr, not_hidden, @@ -183,7 +184,6 @@ def query( added_in_last, album, duplicate, - edited, exif, external_edit, folder, @@ -215,6 +215,7 @@ def query( (burst, not_burst), (cloudasset, not_cloudasset), (deleted, deleted_only), + (edited, not_edited), (favorite, not_favorite), (has_comment, no_comment), (has_likes, no_likes), @@ -318,6 +319,7 @@ def query( no_title=no_title, not_burst=not_burst, not_cloudasset=not_cloudasset, + not_edited=not_edited, not_favorite=not_favorite, not_hdr=not_hdr, not_hidden=not_hidden, diff --git a/osxphotos/photosdb/photosdb.py b/osxphotos/photosdb/photosdb.py index afbcbb0c..5906a9c4 100644 --- a/osxphotos/photosdb/photosdb.py +++ b/osxphotos/photosdb/photosdb.py @@ -3264,6 +3264,8 @@ class PhotosDB: if options.edited: photos = [p for p in photos if p.hasadjustments] + elif options.not_edited: + photos = [p for p in photos if not p.hasadjustments] if options.external_edit: photos = [p for p in photos if p.external_edit] diff --git a/osxphotos/queryoptions.py b/osxphotos/queryoptions.py index 48ec940c..c645a6c5 100644 --- a/osxphotos/queryoptions.py +++ b/osxphotos/queryoptions.py @@ -61,6 +61,7 @@ class QueryOptions: no_title: search for photos with no title not_burst: search for non-burst photos not_cloudasset: search for photos that are not managed by iCloud + not_edited: search for photos that have not been edited not_favorite: search for non-favorite photos not_hdr: search for non-HDR photos not_hidden: search for non-hidden photos @@ -143,6 +144,7 @@ class QueryOptions: no_title: Optional[bool] = None not_burst: Optional[bool] = None not_cloudasset: Optional[bool] = None + not_edited: Optional[bool] = None not_favorite: Optional[bool] = None not_hdr: Optional[bool] = None not_hidden: Optional[bool] = None diff --git a/tests/test_cli.py b/tests/test_cli.py index 98c4a790..776b1927 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -875,6 +875,39 @@ UUID_IS_REFERENCE = [ "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C", ] +UUID_EDITED = [ + "D1D4040D-D141-44E8-93EA-E403D9F63E07", + "7783E8E6-9CAC-40F3-BE22-81FB7051C266", + "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51", + "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4", + "1793FAAB-DE75-4E25-886C-2BD66C780D6A", + "DC99FBDD-7A52-4100-A5BB-344131646C30", +] + +UUID_NOT_EDITED = [ + "F12384F6-CD17-4151-ACBA-AE0E3688539E", + "7F74DD34-5920-4DA3-B284-479887A34F66", + "4D521201-92AC-43E5-8F7C-59BC41C37A96", + "54E76FCB-D353-4557-9997-0A457BCB4D48", + "8E1D7BC9-9321-44F9-8CFB-4083F6B9232A", + "F207D5DE-EFAD-4217-8424-0764AAC971D0", + "D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068", + "52083079-73D5-4921-AC1B-FE76F279133F", + "A92D9C26-3A50-4197-9388-CB5F7DB9FA91", + "3DD2C897-F19E-4CA6-8C22-B027D5A71907", + "7FD37B5F-6FAA-4DB1-8A29-BF9C37E38091", + "B13F4485-94E0-41CD-AF71-913095D62E31", + "A8266C97-9BAF-4AF4-99F3-0013832869B8", + "1EB2B765-0765-43BA-A90C-0D0580E6172C", + "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91", + "D79B8D77-BFFC-460B-9312-034F2877D35B", + "8846E3E6-8AC8-4857-8448-E3D025784410", + "D1359D09-1373-4F3B-B0E3-1A4DE573E4A3", + "A1DD1F98-2ECD-431F-9AC9-5AFEFE2D3A5C", + "35329C57-B963-48D6-BB75-6AFF9370CBBC", + "2DFD33F1-A5D8-486F-A3A9-98C07995535A", +] + UUID_IN_ALBUM = [ "1EB2B765-0765-43BA-A90C-0D0580E6172C", "2DFD33F1-A5D8-486F-A3A9-98C07995535A", @@ -1250,6 +1283,38 @@ def test_query_is_reference(): assert sorted(uuid_got) == sorted(UUID_IS_REFERENCE) +def test_query_edited(): + """Test query with --edited""" + + runner = CliRunner() + cwd = os.getcwd() + result = runner.invoke( + query, ["--json", "--db", os.path.join(cwd, CLI_PHOTOS_DB), "--edited"] + ) + assert result.exit_code == 0 + + # build list of uuids we got from the output JSON + json_got = json.loads(result.output) + uuid_got = [photo["uuid"] for photo in json_got] + assert sorted(uuid_got) == sorted(UUID_EDITED) + + +def test_query_not_edited(): + """Test query with --not-edited""" + + runner = CliRunner() + cwd = os.getcwd() + result = runner.invoke( + query, ["--json", "--db", os.path.join(cwd, CLI_PHOTOS_DB), "--not-edited"] + ) + assert result.exit_code == 0 + + # build list of uuids we got from the output JSON + json_got = json.loads(result.output) + uuid_got = [photo["uuid"] for photo in json_got] + assert sorted(uuid_got) == sorted(UUID_NOT_EDITED) + + def test_query_in_album(): """Test query with --in-album""" @@ -1835,6 +1900,45 @@ def test_export_skip_edited(): assert "St James Park_edited.jpeg" not in files +def test_export_edited(): + + 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), ".", "--edited", "-V"] + ) + assert result.exit_code == 0 + files = glob.glob("*") + # make sure edited versions did get exported + assert "wedding_edited.jpeg" in files + assert "Tulips_edited.jpeg" in files + assert "St James Park_edited.jpeg" in files + + +def test_export_not_edited(): + + 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), ".", "--not-edited", "-V"] + ) + assert result.exit_code == 0 + files = glob.glob("*") + # make sure not edited files were exported + assert "Pumkins1.jpg" in files + assert "Pumkins2.jpg" in files + assert "Jellyfish1.mp4" in files + + # make sure edited versions did not get exported + assert "wedding_edited.jpeg" not in files + assert "Tulips_edited.jpeg" not in files + assert "St James Park_edited.jpeg" not in files + + def test_export_skip_original_if_edited(): """test export with --skip-original-if-edited"""