1 votos

¿Cómo eliminar un gran número de palabras clave no utilizadas de la aplicación Fotos de Apple?

Mi fototeca ha evolucionado a lo largo de muchos años, comenzando como una biblioteca de iPhoto, luego fusionada con una biblioteca de Aperture y después convertida en una biblioteca de Fotos. En el transcurso de los últimos 20 años, más o menos, he acumulado miles de palabras clave que no tienen ninguna foto asociada.

La propia aplicación Fotos no admite la función "Eliminar palabras clave no utilizadas", y he intentado utilizar AppleScript para hacerlo, pero, a pesar de ser un programador bastante experimentado, no he podido averiguar cómo hacerlo (AppleScript me vuelve loco, sinceramente).

Espero que alguien haya escrito ya un script, o si no es un script entonces alguna otra utilidad que haga esto por mí.

2voto

qarma Puntos 71

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

  1. 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).

  2. 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.

  3. 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.

AppleAyuda.com

AppleAyuda es una comunidad de usuarios de los productos de Apple en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X