A continuación hay un script que escribí y probé durante bastantes días. Mientras que el programa de Apple Fotos.app es scriptable, ya ha observado que carece de los métodos necesarios para eliminar las palabras clave no utilizadas. Si está familiarizado con AppleScript y la noción de scripting de la interfaz de usuario, esta parece ser la única opción disponible.
Nota: Para que los scripts de la interfaz de usuario funcionen, es necesario proporcionar los privilegios de accesibilidad a Script Editor .
Mi opinión personal hacia los scripts de interfaz de usuario es generalmente negativa, pero he tenido un cuidado extra para tratar de mitigar la típica naturaleza temperamental y la fragilidad de los scripts, y he hecho algunas pruebas en mi sistema para observar un funcionamiento razonablemente suave.
Sin embargo, La única característica específica que no pude (no estaba dispuesto a) comprobar durante las pruebas es cómo el script se comporta cuando hay miles de palabras clave, y/o un Fotos biblioteca. Yo mismo tengo una Fotos biblioteca compuesta por menos de 100 fotos, y ninguna de ellas había sido etiquetada con palabras clave, así que creé una muestra de 20, de las cuales asigné al azar la mitad.
En teoría, el único efecto que debería tener el volumen de la biblioteca o el número de palabras clave en el funcionamiento del script es el tiempo que tarda en ejecutarse. Pero, con AppleScript, puede haber problemas de tiempo de espera que aborten prematuramente el script; y con el scripting de la UI, la probabilidad de que arroje un error tiende a aumentar con el tiempo de ejecución.
Las notas específicas relativas a estas incertidumbres sobre el rendimiento se amplían debajo del script. Si se encuentra con algún problema, por favor infórmeme y consideraré cómo implementar una solución. No debería haber efectos adversos en caso de que el script no funcione de forma ideal (es decir, que no lo hará perder las fotos). Un rendimiento subóptimo sólo debería dar lugar a una purga incompleta de las palabras clave.
#!/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
Incertidumbres sobre las amenazas al rendimiento
-
En cuanto al volumen de fotos en su biblioteca La línea que más me preocupa es ésta:
property _M : a reference to every media item
cuyo efecto entrará en juego en los puntos del script donde se dereferencie la propiedad, es decir
set activeKeywords to the list of currentKeywords()
La función de esta línea es recuperar una lista de todas las palabras clave asignadas actualmente a al menos una foto. Para ello, hay que enumerar (recuperar) cada una de las fotos de la biblioteca, y su keywords
propiedad evaluada. Esto ocurre prácticamente de forma inmediata al inicio del script; y de nuevo después de purgar las palabras clave para determinar si la purga fue completa. Es un proceso que consume tiempo, y por lo tanto una potencial amenaza de tiempo de espera para el script.
Se debería poder ampliar el valor del tiempo de espera por defecto así con un tiempo de espera de 600 segundos establecer activeKeywords a la lista de currentKeywords() fin del tiempo de espera
o puede ser necesario alterar ligeramente la sintaxis en la recuperación de las fotos para que el scriptse dirija directamente al Fotos app en el punto de enumeración, en lugar de por medio de referencias a la propiedad; y entonces para encerrar el Fotos dentro de un timeout
bloque. Pero, por ahora, lo he dejado para ver si el script se ejecuta en tu sistema usando el tiempo de espera por defecto, lo que podría no ser una limitación si la enumeración tiene lugar de forma sincrónica (y no sé si lo hace o no).
-
En cuanto a los posibles bloqueos de la interfaz de usuario: el Fotos El diccionario de AppleScript no proporciona una forma de recuperar todas las palabras clave que existen en la aplicación. La forma en que el script funciona para evitar esto es abrir el Gestor de palabras clave y leerá el nombre de cada etiqueta de palabra clave que detecte en la sección "Keywords"
. Lo que no sé es si cada elemento de la interfaz de usuario que contiene una etiqueta de palabra clave se carga cuando el Gestor de palabras clave o si se cargan por partes cuando el usuario se desplaza por la lista. Esta última situación sería molesta, ya que daría lugar a una lista incompleta de palabras clave y a una purga incompleta.
Una solución obvia sería ejecutar el script varias veces para realizar múltiples purgas hasta que no queden elementos purgables.
-
Al considerar el peor escenario posible El análisis del script parece tener uno de los tres resultados posibles (independientemente de cómo termine el script, ya sea completando su ejecución o lanzando un error):
- O bien el script no hace nada (resultado nulo);
- O se produce una purga incompleta (éxito parcial);
- O se produce una purga completa (éxito).
Allí no parece ser una forma de que el script falle de forma que afecte negativamente al Fotos biblioteca, por lo que el peor escenario parece ser el resultado nulo . Sin embargo, si asumimos que puedo estar equivocado, es posible que quieras poner un margen de error en el potencial escenario peor.
Este margen depende de ti y de tu juicio, que es difícil de hacer cuando quizás no sabes en qué tipo de cosas me suelo equivocar. Si te sirve de ayuda, yo diría que la etiqueta imposible para que el script borre alguna de sus fotos, porque no realizar cualquier operación del sistema de archivos. Si imposible no es suficiente, entonces la precaución clara que hay que tomar es hacer una copia de seguridad de todo el Fotos biblioteca de antemano. Dependiendo del tamaño de su biblioteca, esto puede ir desde lo más sencillo hasta lo más laborioso en aras de un evento de probabilidad casi nula.
Lo que el script hace hacer (obviamente) es leer y editar listas de palabras clave. Así que, aunque no debería ser posible, no sería descabellado considerar que todas tus palabras clave para todas tus fotos podrían desaparecer. Si quieres cubrirte para este improbable caso, te proporciono este "scriptlet" que puedes ejecutar de antemano para respaldar tus palabras clave:
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
Al respaldar sus palabras clave, el scripttendrá que enumerar todo el Fotos biblioteca. Por lo tanto, independientemente de que Necesito la copia de seguridad, la ejecución de este script en primer lugar le dará una indicación de lo lento/rápido que puede leer su biblioteca.