AppleScript: Kann überprüft werden, ob Speech gerade ausgeführt wird?

Ich möchte die in macOS integrierte Text-to-Speech-Tastaturkürzelfunktion mit AppleScript genau nachbilden. Wenn ich „genau“ sage, meine ich „genau“.

Die integrierte Option finden Sie in den Systemeinstellungen → Diktat & Sprache → Text zu Sprache:

Bildschirmfoto

Hier ist die Beschreibung dieser Funktion:

Stellen Sie eine Tastenkombination ein, um ausgewählten Text zu sprechen.

Verwenden Sie diese Tastenkombination, um Ihren Computer ausgewählten Text sprechen zu hören. Wenn der Computer spricht, drücken Sie die Tasten zum Stoppen.

Der Grund, warum ich diese Funktion neu erstellen möchte (anstatt sie einfach zu verwenden), ist, dass sie fehlerhaft ist; Manchmal funktioniert es, aber manchmal drücke ich die Tastenkombination und es passiert nichts. Wenn ich es manuell in AppleScript codiere, hoffe ich, dass der Prozess zuverlässiger ist.


Ich verstehe, wie man Speech in AppleScript startet und stoppt, wie hier erklärt .

Ich möchte jedoch dieselbe Tastenkombination und damit dieselbe .scpt-Datei verwenden, um die Sprachausgabe sowohl zu starten als auch zu stoppen, wodurch die Funktionalität der integrierten Sprachtastenkombination widergespiegelt wird.

Ich verwende FastScripts, um die .scpt-Datei über eine Tastenkombination auszuführen.

Wenn die gleiche .scpt-Datei sowohl für das Starten als auch für das Stoppen der Sprache zuständig ist, benötigt das Skript eine if-Anweisung am Anfang des AppleScripts oder etwas Ähnliches, um sofort zu prüfen, ob die Sprache gerade gesprochen wird oder nicht, bevor das Skript dies kann fortfahren. Ich weiß nicht, wie ich diese Überprüfung implementieren soll, oder ob es überhaupt möglich ist.

Aber hier ist, was ich habe:

if <This is where I need your help, Ask Different> then
    say "" with stopping current speech
    error number -128 -- quits the AppleScript
end if



-- Back up original clipboard contents:
set savedClipboard to my fetchStorableClipboard()

-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
delay 1 -- Without this, the clipboard may have stale data.

set theSelectedText to the clipboard
    
-- Restore original clipboard:
my putOnClipboard:savedClipboard

-- Speak the selected text:
say theSelectedText waiting until completion no





use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"


on fetchStorableClipboard()
    set aMutableArray to current application's NSMutableArray's array() -- used to store contents
    -- get the pasteboard and then its pasteboard items
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    -- loop through pasteboard items
    repeat with anItem in thePasteboard's pasteboardItems()
        -- make a new pasteboard item to store existing item's stuff
        set newPBItem to current application's NSPasteboardItem's alloc()'s init()
        -- get the types of data stored on the pasteboard item
        set theTypes to anItem's types()
        -- for each type, get the corresponding data and store it all in the new pasteboard item
        repeat with aType in theTypes
            set theData to (anItem's dataForType:aType)'s mutableCopy()
            if theData is not missing value then
                (newPBItem's setData:theData forType:aType)
            end if
        end repeat
        -- add new pasteboard item to array
        (aMutableArray's addObject:newPBItem)
    end repeat
    return aMutableArray
end fetchStorableClipboard


on putOnClipboard:theArray
    -- get pasteboard
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    -- clear it, then write new contents
    thePasteboard's clearContents()
    thePasteboard's writeObjects:theArray
end putOnClipboard:

(Ursprünglich wollte ich, dass das AppleScript spricht the clipboard, aber dann wurde mir klar, dass dies den ursprünglichen Inhalt der Zwischenablage überschrieb. Also möchte ich eigentlich, dass das AppleScript den Inhalt der theSelectedTextVariablen spricht, wie im obigen Code gezeigt.)

Antworten (1)

Es ist mit dem sayBefehl in einer Shell möglich, nicht mit dem AppleScript- sayBefehl.

Info zum AppleScript say Befehl:

  • Sie können die Ansprache des Befehls say aus demselben Skript stoppen, bis das Skript ausgeführt wird, nicht danach, wenn das Skript beendet wird.
  • Beispiel:
say "I want to recreate macOS's built-in Text To Speech" waiting until completion no
delay 0.5
say "" with stopping current speech -- this stop the first say command of this script
delay 1
say "Hello"

Dieses Skript verwendet den sayBefehl in einer Shell, um den Inhalt des pbpasteBefehls (die Zwischenablage) zu sprechen, und fügt die PID des sayBefehls in eine dauerhafte Eigenschaft ein:

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property this_say_Pid : missing value -- the persistent property

if this_say_Pid is not missing value then -- check the pid of all 'say' commands, if exists then quit the unix process
    set allSayPid to {}
    try
        set allSayPid to words of (do shell script "pgrep -x 'say'")
    end try
    if this_say_Pid is in allSayPid then -- the PID = an item in the list
        do shell script "/bin/kill " & this_say_Pid -- quit this process to stop the speech
        error number -128 -- quits the AppleScript
    end if
end if

-- Back up original clipboard contents:
set savedClipboard to my fetchStorableClipboard()

-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
delay 1 -- Without this, the clipboard may have stale data.

-- Speak the clipboard:
--  pbpaste = the contents of the clipboard , this run the commands without waiting, and get the PID of the 'say' command 
set this_say_Pid to do shell script "LANG=en_US.UTF-8 pbpaste -Prefer txt | say > /dev/null 2>&1 & echo $!"

-- Restore original clipboard:
my putOnClipboard:savedClipboard

on fetchStorableClipboard()
    set aMutableArray to current application's NSMutableArray's array() -- used to store contents
    -- get the pasteboard and then its pasteboard items
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    -- loop through pasteboard items
    repeat with anItem in thePasteboard's pasteboardItems()
        -- make a new pasteboard item to store existing item's stuff
        set newPBItem to current application's NSPasteboardItem's alloc()'s init()
        -- get the types of data stored on the pasteboard item
        set theTypes to anItem's types()
        -- for each type, get the corresponding data and store it all in the new pasteboard item
        repeat with aType in theTypes
            set theData to (anItem's dataForType:aType)'s mutableCopy()
            if theData is not missing value then
                (newPBItem's setData:theData forType:aType)
            end if
        end repeat
        -- add new pasteboard item to array
        (aMutableArray's addObject:newPBItem)
    end repeat
    return aMutableArray
end fetchStorableClipboard


on putOnClipboard:theArray
    -- get pasteboard
    set thePasteboard to current application's NSPasteboard's generalPasteboard()
    -- clear it, then write new contents
    thePasteboard's clearContents()
    thePasteboard's writeObjects:theArray
end putOnClipboard:

Es ist möglich, dass das erste Skript nicht funktioniert, wenn der Wert der Variable this_say_Pid nicht über Läufe hinweg bestehen bleibt, hängt davon ab, wie das Skript gestartet wird. In diesem Fall müssen Sie die PID in eine Datei schreiben, verwenden Sie also dieses Skript:

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"

set tFile to POSIX path of (path to temporary items as text) & "_the_Pid_of_say_command_of_this_script.txt" -- the temp file
set this_say_Pid to missing value
try
    set this_say_Pid to paragraph 1 of (read tFile) -- get the pid of the last speech
end try

if this_say_Pid is not in {"", missing value} then -- check the pid of all 'say' commands, if exists then quit the unix process
    set allSayPid to {}
    try
        set allSayPid to words of (do shell script "pgrep -x 'say'")
    end try
    if this_say_Pid is in allSayPid then -- the PID = an item in the list
        do shell script "/bin/kill " & this_say_Pid -- quit this process to stop the speech
        error number -128 -- quits the AppleScript
    end if
end if

-- Back up original clipboard contents:
set savedClipboard to my fetchStorableClipboard()

-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
delay 1 -- Without this, the clipboard may have stale data.

-- Speak the clipboard:

--  pbpaste = the contents of the clipboard , this run the commands without waiting, and it write the PID of the 'say' command to the temp file
do shell script "LANG=en_US.UTF-8 pbpaste -Prefer txt | say > /dev/null 2>&1 & echo $! > " & quoted form of tFile

-- Restore original clipboard:
my putOnClipboard:savedClipboard

-- *** Important *** : This script is not complete,  you must add the 'putOnClipboard:' handler and the 'fetchStorableClipboard()' handler to this script.
Ich wollte nur hinzufügen, dass es tatsächlich eine Möglichkeit gibt, festzustellen, ob der (Apple) Script Editor oder eine AppleScript-App usw. den AppleScript- say Befehl ausführt . Beim Lesen von mit psund lsofdavor, während und danach konnte ich mithilfe von ein Muster diffvon Dateien isolieren, die ins Spiel kommen, die mit dem Aufrufprozess verknüpft sind und zum Testen codiert werden könnten. Ich würde mich jedoch wahrscheinlich für Ihre Methode entscheiden, da es einfach ist, die von Ihnen vorgeschlagene Methode zu verwenden oder zu verwenden, pgrep sayum ihre PID.
@ jackjr300 Ich habe meiner Frage einige wichtige Details hinzugefügt und festgestellt, dass ich die Zwischenablage eigentlich nicht direkt als Sprachtextquelle verwenden möchte, da dann der ursprüngliche Inhalt der Zwischenablage verloren gehen kann. Bitte sehen Sie sich meine Bearbeitung an. Aber wie auch immer ... Ihre erste Lösung hat den aktuell sprechenden Text nicht angehalten und stattdessen einfach den Sprachbefehl erneut ausgelöst, sodass sie sich überlappten. Die beiden Reden finden gleichzeitig statt. Die zweite Lösung verhält sich genauso, wo zB 5 Reden alle gleichzeitig sprechen, wenn ich die .scpt-Datei 5 mal hintereinander laufen lasse. Funktioniert es richtig für Sie?
@rubik's sphere, Das von diesen Seiten kopierte und eingefügte Skript funktioniert nicht, da das Ergebnis des Befehls do shell script "/bin/ps ....ein Leerzeichen vor der PID enthält, während das Ergebnis meines gespeicherten Skripts nur die PID ist. Auf jeden Fall wird das Skript nach Ihren Bedürfnissen geändert.
@ user3439894 Ja, ich werde den pgrepBefehl jetzt verwenden, danke.
@jackjr300 (1/2) Ich schätze es. Wir kommen näher, aber es gibt immer noch einen Fehler in Ihrem Code. Dies gilt sowohl für die erste als auch für die zweite Lösung. Wenn ich Ihren Code zum ersten Mal ausführe (entweder die erste oder die zweite Lösung), wird der ausgewählte Text gesprochen. Großartig. Dann führe ich den Code sofort ein zweites Mal aus und das Sprechen wird gestoppt. Großartig. Wenn ich das Skript jedoch ein drittes Mal ausführe, wird anstelle des ausgewählten Textes der Text der Zwischenablage gesprochen (wie in, der ursprüngliche Inhalt der Zwischenablage, bevor der ausgewählte Text in die Zwischenablage kopiert wird). Aber, wenn ich vorher genau 6 Minuten warte
(2/2) den Code erneut ausführen, das Skript funktioniert für einen Durchlauf korrekt (dh das Skript spricht den ausgewählten Text anstelle des Zwischenablagetexts), aber dann wiederholt sich der Zyklus, wobei ich weitere 6 Minuten warten muss, wenn ich möchten, dass das Skript den ausgewählten Text anstelle des ursprünglichen Texts aus der Zwischenablage spricht. Ich weiß auch nicht, ob dies ein separates Problem ist oder ob dies mit dem oben genannten Fehler zusammenhängt, aber gelegentlich muss ich das Skript zweimal ausführen, um eine aktuelle Rede zum Stoppen zu bringen. Das Ausführen des Skripts während einer Rede bewirkt nichts, also führe ich es erneut aus, und das stoppt die Rede.
Ich habe mit dem Dienstprogramm „FastScripts“ getestet (ich verwende eine Tastenkombination, um das Skript auszuführen), und ich habe dieses Problem auf meinem Computer nicht (selbst bei einer großen Auswahl). Stellen Sie sicher, dass die Verknüpfung keystroke "c" using {command down}von der vordersten Anwendung aus funktioniert, wenn Sie einen Piepton hören (der Tastendruck hat nicht funktioniert). Versuchen Sie, die Zeit in Sekunden des delayBefehls zu erhöhen. delayVersuchen Sie , vor dieser Zeile einen Befehl einzufügen my putOnClipboard:savedClipboard. delayVersuchen Sie , vor dieser Zeile einen Befehl einzufügen tell application "System Events" to keystroke "c" using {command down}.
@jackjr300 Um den Debugging-Prozess für mich zu vereinfachen, beziehen Sie sich auf Ihren ersten Codeblock oder Ihren zweiten Codeblock, wenn Sie sagen, dass er für Sie mit einer dem Skript in FastScripts zugewiesenen Tastenkombination korrekt funktioniert?
@rubiks Sphäre, ich verwende meinen ersten Codeblock auf macOS Sierra V10.12.3
@jackjr300 (1/2) Ich habe das Problem gefunden. Ich entschuldige mich für das Anheften des Fehlers an Ihrem Code. Wie Sie vielleicht in meinem ursprünglichen Beitrag bemerkt haben, habe ich die Taste F8 verwendet, um das Skript auszulösen. Da F8 eine Funktionstaste in der obersten Reihe ist (als feste Play/Pause-Steuerung für iTunes eingestellt), hatte ich eine Software von Drittanbietern mit dem Titel FunctionFlip heruntergeladen, um meine F8-Taste freizugeben. Nun, FunctionFlip war für alle von mir beschriebenen Reaktionsprobleme verantwortlich. Ich habe aufgehört, FunctionFlip zu verwenden und verwende jetzt Karabiner, um Schlüssel freizugeben . Ich verwende deinen ersten Block
(2/2) des Codes und es funktioniert perfekt. Die Tastenkombination und das Skript sind 100% zuverlässig. Ich habe sogar Ihre delay 1Zeile in geändert delay 0.1, und es funktioniert immer noch perfekt. Ich habe dann die Reaktionszeit Ihres Skripts mit der Reaktionszeit der integrierten Text-zu-Sprache-Tastenkombination verglichen, und Ihr Skript ist tatsächlich etwas schneller, sowohl was das Starten als auch das Stoppen der Sprache betrifft. Vielen Dank für Ihre Hilfe! Ich werde Ihrem Code nur eine Überprüfung hinzufügen, damit der Code den Text aus der Zwischenablage nicht spricht, sondern das Skript beendet, wenn der ausgewählte Text leer ist, wenn das Skript ausgeführt wird.