Poniżej znajduje się skrypt, który napisałem i przetestowałem przez kilka dni. Chociaż Photos.app firmy Apple jest skryptowalny, już zauważyłeś, że brakuje w nim niezbędnych metod usuwania nieużywanych słów kluczowych. Jeśli znasz AppleScript i pojęcie skryptów interfejsu użytkownika, wydaje się, że jest to jedyna dostępna opcja.
Uwaga: Aby skrypty interfejsu użytkownika działały, musisz zapewnić niezbędne uprawnienia dostępu do edytora skryptów .
Mój osobisty pogląd na skrypty interfejsu użytkownika jest ogólnie negatywny, ale dołożyłem wszelkich starań, aby złagodzić typową temperamentalną naturę i kruchość skryptów interfejsu użytkownika, i wykonałem kilka testów w moim systemie, aby zaobserwować dość płynne działanie.
Jednak jedną szczególną funkcją, której nie mogłem (nie chciałem) sprawdzić podczas testowania, jest to, jak działa skrypt, gdy istnieją tysiące słów kluczowych i / lub bardzo duża biblioteka Zdjęć . Sam mam bibliotekę zdjęć składającą się z mniej niż 100 zdjęć i żadne z nich nie było oznaczone słowami kluczowymi, dlatego stworzyłem próbkę 20, z których losowo przypisałem około połowy z nich.
Teoretycznie jedynym czynnikiem wpływającym na wielkość biblioteki lub liczbę słów kluczowych na działanie skryptu jest czas wykonywania. Jednak w AppleScript mogą wystąpić problemy z przekroczeniem limitu czasu, które przedwcześnie przerywają skrypt; a w przypadku skryptów interfejsu użytkownika prawdopodobieństwo zgłoszenia błędu zwykle wzrasta wraz z czasem działania.
Szczegółowe uwagi dotyczące tych niepewności dotyczących wydajności znajdują się poniżej skryptu. Jeśli napotkasz jakiekolwiek problemy, zgłoś to, a ja rozważę, jak wdrożyć poprawkę. Nie powinno być żadnych negatywnych skutków, gdyby skrypt nie działał idealnie (tzn. Nie stracisz żadnych zdjęć). Nieoptymalna wydajność powinna jedynie doprowadzić do niepełnego oczyszczenia słów kluczowych.
#!/usr/bin/osascript
--------------------------------------------------------------------------------
# pnam: PHOTOS#DELETE UNUSED KEYWORDS
# nmxt: .applescript
# pDSC: A UI scripting-dependent script to remove keywords from Photos.app that
# have not been assigned to any photos
# plst: -
# rslt: «list» : On successful completion, the script reacquires an updated
# list of disused keywords and returns the result (hopefully
# an empty list)
# «err » : Script failure throws an error. Running the script again
# with Photos.app already open may yield a different result.
--------------------------------------------------------------------------------
# sown: CK
# ascd: 2019-01-07
# asmo: 2019-01-07
# vers: 1.0
--------------------------------------------------------------------------------
use sys : application "System Events"
use Photos : application "Photos"
property process : a reference to application process "Photos"
property _M : a reference to every media item
--------------------------------------------------------------------------------
# IMPLEMENTATION:
activate Photos
open the keywordManager
set everyKeyword to the list of allKeywords()
set activeKeywords to the list of currentKeywords()
set disusedKeywords to difference(everyKeyword, activeKeywords)
tell the keywordManager
tell its keywordEditor
open it
select disusedKeywords
delete
close
end tell
end tell
set everyKeyword to the list of allKeywords()
set activeKeywords to the list of currentKeywords()
close the keywordManager
set disusedKeywords to difference(everyKeyword, activeKeywords)
--------------------------------------------------------------------------------
# HANDLERS & SCRIPT OBJECTS:
script keywordManager
property window : a reference to window "Keywords" of my process
property scroll area : a reference to scroll area 2 of my window
property button : a reference to button "Edit Keywords" of my window
property menu item : a reference to ¬
(menu item "Keyword Manager" of ¬
menu 1 of ¬
menu bar item "Window" of ¬
menu bar 1 of my process)
script keywordEditor
property title : "Manage My Keywords"
property window : a reference to window title of my process
property scroll area : a reference to scroll area 1 ¬
of my window
property table : a reference to table 1 of my scroll area
property group : a reference to group 1 of my window
property button : a reference to (first button of my group ¬
whose accessibility description = "remove")
property menu item : a reference to ¬
(menu item "Select All" of ¬
menu 1 of ¬
menu bar item "Edit" of ¬
menu bar 1 of my process)
to open
if my window exists then return
tell the keywordManager to if not ¬
(its window exists) then ¬
open it
click the keywordManager's button
with timeout of 10 seconds
repeat until the my window exists
delay 0.5
end repeat
end timeout
perform action "AXRaise" of my window
end open
to close
if not (my window exists) then return
click button "OK" of my window
end close
to select |keywords| as list
local |keywords|
set focused of my table to true
click my menu item
script deselect
property list : |keywords|
on fn(x)
if the value of x's text field 1 ¬
is not in my list then
set x's selected to false
return true
end if
false
end fn
end script
filterItems from rows of my table ¬
given handler:deselect
end select
to delete
if not (my button exists) then return 0
click my button
end delete
end script
on menuItem()
tell my menu item to if exists then return it
false
end menuItem
to open
if my window exists then return false
tell the keywordEditor to if ¬
(its window exists) then ¬
return close it
click my menuItem()
# tell sys to keystroke "k" using command down
with timeout of 10 seconds
repeat until my window exists
delay 0.5
set my process's frontmost to true
end repeat
end timeout
perform action "AXRaise" of my window
end open
to close
if not (my window exists) then return
click (value of attribute "AXCloseButton" of my window)
end close
end script
on allKeywords()
script |keywords|
property list : accessibility description of ¬
every checkbox of the keywordManager's scroll area ¬
whose role description = "keyword checkbox"
end script
end allKeywords
on currentKeywords()
script
property keep : keywords of _M
property list : strings in unique_(flatten_(keep))
end script
end currentKeywords
on __(function)
if the function's class = script ¬
then return the function
script
property fn : function
end script
end __
to filterItems from L as list into R as list : missing value ¬
given handler:function
local L, R
if R = missing value then set R to {}
script
property list : L
property result : R
end script
tell the result to repeat with x in its list
if __(function)'s fn(x, its list, its result) ¬
then set end of its result to x's contents
end repeat
R
end filterItems
to foldItems from L at |ξ| : 0 given handler:function
local L, |ξ|, function
script
property list : L
end script
tell the result to repeat with i from 1 to length of its list
set x to item i in its list
tell __(function)'s fn(x, |ξ|, i, L) to ¬
if it = missing value then
exit repeat
else
set |ξ| to it
end if
end repeat
|ξ|
end foldItems
on difference(A as list, B as list)
local A, B
script
on notMember(M)
script
on fn(x)
x is not in M
end fn
end script
end notMember
end script
filterItems from A given handler:result's notMember(B)
end difference
on union(A as list, B as list)
local A, B
script
on insert(x, L)
set end of L to x
L
end insert
end script
foldItems from A at B given handler:result's insert
end union
to flatten:L
foldItems from L at {} given handler:union
end flatten:
on unique:L
local L
script
on notMember(x, i, L)
x is not in L
end notMember
end script
filterItems from L given handler:result's notMember
end unique:
---------------------------------------------------------------------------❮END❯
Niepewności dotyczące zagrożeń dla wydajności
Jeśli chodzi o ilość zdjęć w bibliotece , najbardziej pamiętam o następującej linii:
property _M : a reference to every media item
których efekt wejdzie w grę w punktach skryptu, w których właściwość jest wyłuskiwana, tj
set activeKeywords to the list of currentKeywords()
Funkcja tego wiersza polega na pobraniu listy wszystkich słów kluczowych aktualnie przypisanych do co najmniej jednego zdjęcia. W tym celu należy wyliczyć (pobrać) każde zdjęcie w bibliotece i keywords
ocenić jego właściwość. Dzieje się to praktycznie natychmiast na początku skryptu; i ponownie po usunięciu słów kluczowych w celu ustalenia, czy czyszczenie zostało zakończone. Jest to proces czasochłonny, a zatem potencjalne zagrożenie przekroczeniem limitu czasu przez skrypt.
Powinno być możliwe przedłużenie domyślnej wartości limitu czasu w następujący sposób: z limitem 600 sekund ustaw wartość activeKe words na listę bieżących limitów czasu zakończenia
lub może być konieczna nieznaczna zmiana składni przy pobieraniu zdjęć, tak aby skrypt bezpośrednio celował w aplikację Zdjęcia w punkcie wyliczenia, zamiast poprzez odwołania do właściwości; a następnie ująć polecenie Zdjęcia w timeout
bloku. Ale na razie zostawiłem to, aby sprawdzić, czy skrypt będzie działał w twoim systemie z domyślnym limitem czasu, co może nie być ograniczeniem, jeśli wyliczenie odbywa się synchronicznie (i nie wiem, czy to robi) .
Jeśli chodzi o potencjalne blokady skryptów interfejsu użytkownika: słownik AppleScript ze zdjęciami nie zapewnia sposobu na odzyskanie wszystkich słów kluczowych istniejących w aplikacji. Sposób działania tego skryptu polega na otwarciu Menedżera słów kluczowych i odczytaniu nazwy każdej etykiety słowa kluczowego wykrytej w sekcji "Keywords"
. Nie wiem, czy każdy element interfejsu użytkownika zawierający etykietę słowa kluczowego jest ładowany podczas tworzenia okna Menedżera słów kluczowych ; lub czy zostaną one załadowane fragmentarycznie, gdy użytkownik przewinie listę. Ta ostatnia sytuacja byłaby uciążliwa, ponieważ spowodowałaby niepełną listę słów kluczowych, a następnie niepełne oczyszczenie.
Jednym oczywistym rozwiązaniem byłoby uruchomienie skryptu wiele razy, aby wykonać wiele czystek, dopóki nie pozostaną żadne usuwalne elementy.
Biorąc pod uwagę najgorszy scenariusz , analiza skryptu wydaje się mieć jeden z trzech możliwych wyników (niezależnie od tego, jak skrypt się zakończy, czy to przez zakończenie jego uruchomienia, czy przez zgłoszenie błędu):
- Albo skrypt nic nie robi (wynik zerowy);
- LUB następuje niepełne oczyszczenie (częściowy sukces);
- LUB nastąpi całkowite oczyszczenie (sukces).
Wydaje się, że nie ma sposobu na awarię skryptu w sposób, który negatywnie wpłynie na bibliotekę Zdjęć , więc najgorszym scenariuszem wydaje się być zero . Jeśli jednak założymy, że mogę się mylić, możesz zaryzykować błąd w potencjalnym gorszym scenariuszu.
Ten margines zależy od ciebie i twojego osądu, co jest trudne do ustalenia, gdy być może nie wiesz, o jakich typach rzeczy zwykle się mylę. Jeśli to pomoże, powiedziałbym, że etykieta nie może usunąć żadnego z twoich zdjęć, ponieważ nie wykonuje żadnych operacji na systemie plików. Jeśli niemożliwe nie jest wystarczające, wyraźnym środkiem ostrożności jest wcześniejsze wykonanie kopii zapasowej całej biblioteki zdjęć . W zależności od wielkości twojej biblioteki, może to wahać się od prostego do nakładania bólu ze względu na niemal zerową szansę.
Oczywiście skrypt wykonuje (oczywiście) czytanie i edycję list słów kluczowych. Więc chociaż nie powinno to być możliwe, nie byłoby głupotą uważać, że wszystkie słowa kluczowe dla wszystkich zdjęć mogą po prostu zniknąć. Jeśli chcesz zabezpieczyć się przed tym mało prawdopodobnym wydarzeniem, zapewniam ten „skryptlet”, który możesz uruchomić wcześniej, aby utworzyć kopię zapasową słów kluczowych:
property path : "~/Desktop/Photos.Keywords.Backup.plist"
backupKeywordsToFile at path
--! CAUTION: Uncommenting the line below
--! WILL OVERWRITE ALL KEYWORDS FOR ALL PHOTOS
-- restoreKeywordsFromFile at path
--------------------------------------------------------------------------------
# HANDLERS & SCRIPT OBJECTS:
use framework "Foundation"
property this : a reference to current application
property _0 : a reference to missing value
property _1 : a reference to reference
property NSDictionary : a reference to NSDictionary of this
property NSString : a reference to NSString of this
property NSURL : a reference to NSURL of this
to backupKeywordsToFile at fp as text
local fp
set fURL to NSURL's fileURLWithPath:((NSString's ¬
stringWithString:fp)'s ¬
stringByStandardizingPath())
script
use application "Photos"
property _M : a reference to media items
property properties : [keywords, id] of _M
property keys : item 1 of my properties
property refs : item 2 of my properties
end script
tell the result
repeat with i from 1 to length of its keys
if (item i of its keys) = missing value ¬
then set item i of its keys to {}
end repeat
tell (NSDictionary's dictionaryWithObjects:(its keys) ¬
forKeys:(its refs)) to set [success, E] ¬
to its writeToURL:fURL |error|:_1
end tell
if E ≠ missing value then return E's localizedDescription() as text
success
end backupKeywordsToFile
to restoreKeywordsFromFile at fp as text
local fp
set fURL to NSURL's fileURLWithPath:((NSString's ¬
stringWithString:fp)'s ¬
stringByStandardizingPath())
script
property result : NSDictionary's ¬
dictionaryWithContentsOfURL:fURL ¬
|error|:_1
property mediakeys : item 1 of my result
property E : item 2 of my result
property keys : null
property refs : null
end script
tell the result
if its E ≠ missing value then return its E's ¬
localizedDescription() as text
set its keys to its mediakeys's allObjects() as list
set its refs to its mediakeys's allKeys() as list
repeat with i from 1 to length of its refs
set x to item i of its refs
set keys to item i of its keys
tell application "Photos" to set ¬
keywords of media item id x ¬
to keys
end repeat
end tell
end restoreKeywordsFromFile
Podczas tworzenia kopii zapasowej słów kluczowych skrypt będzie musiał wyliczyć całą bibliotekę Zdjęć . Dlatego bez względu na to, czy potrzebujesz kopii zapasowej, najpierw uruchom ten skrypt, aby wskazać, jak wolno / szybko można odczytać bibliotekę.