# Lecture de documents à haute voix



## armel35 (11 Avril 2015)

Bonjour,

Gautier Delorme a publié un script via automator qui permet de lire à haute voix, en français, des documents pdf, doc, rtf ou txt, et ce juste en cliquant sur le nom du fichier dans le finder.

http://www.gautierdelorme.com/104-l...haute-sur-mac-os-x-10-9-mavericks#comment-876

J'ai installé xcode, la ligne de commandes de xcode, un package pdftotext ( http://www.bluem.net/files/pdftotext.dmg ), mis les bons chemins dans le .bath_profile, mais je n'arrive pas à faire fonctionner le service (clic droit sur un fichier txt par exemple), rien ne se passe.

Curieusement, quand j'utilise le terminal et tape 'Lire un fichier.txt/doc/rtf, tout fonctionne, et dans le cas d'un fichier pdf cela fonctionne quand il ne contient pas de caractères accentués, sinon j'ai un message d'erreur non UT-F8

Quelqu'un connait-il ce script ? aurait une idée sur les pré-requis que l'auteur aurait oublié de préciser ?
Je pense notamment à son chemin /opt/local/bin/pdftotext : je n'ai pas trouvé le programme qui aurait un tel chemin, ceux que j'ai trouvés installent pdftotext vers usr/local/bin

D'avance merci,


Armel


----------



## JacqR (11 Avril 2015)

Bonjour,

Info : Xcode n'est pas nécessaire pour installer *pdftotext*, il suffit d'ouvrir le paquet "Installer.pkg" avec le menu contextuel "Ouvrir avec" --> "*Programme d'installation (par défaut)*" pour l'installer, cela exige un mot de passe administrateur, l'installeur placera *pdftotext* dans le dossier /usr/local/bin/ automatiquement.

Il y a plusieurs problèmes avec ce script.
1- Ce script ne vérifie pas l'existence d'un fichier quand il crée un fichier temporaire, donc, il peut supprimer un fichier existant (par exemple : j'ai un fichier "*abc.pdf*" et un fichier "*abc.txt*" dans le même dossier, lorsque que j'exécute le script avec le fichier "*abc.pd*f", il supprime le fichier "*abc.txt*").

2- il est inutile d'écrire le résultat de *pdftotext* ou *textutil* dans un fichier temporaire, car on peut obtenir le résultat dans le sdout avec l'option - a la fin de la commande *pdftotext* ou l'option -stdout pour la commande *textutil*.

3-Le script ne fonctionne pas avec les espaces dans les noms de dossier ou de fichier, il faut protéger les variables par des "" (ex : "$1" au lieu de $1

4- Les fichiers "*.txt*" ne fonctionne pas plus (si il y a des accents) quand l'encodage de ces fichiers sont en *MacRoman* par exemple, donc il faut utiliser la commande textutil aussi sur les fichiers ".txt", l'encodage de la sortie de textutil est UTF8 par défaut.

5- Pour le problème des accents dans les *PDF*, il faut spécifier l'encodage de sortie de la commande *pdftotext* (c'est l'option  -enc UTF-8)

Voici le script modifié :


```
#!/bin/bash
if test $# -ne 1 || test ! -f "$1"; then echo "Vous avez oublié le nom du fichier à lire (ou il n'existe pas)."; exit 0; fi
if [ ${1##*.} = pdf ]
then
    pdftotext -enc UTF-8 "$1" - | say
else
    textutil -convert txt -stdout "$1" | say
fi
```


--
Pour Automator : cela ne fonctionne pas car les données en entrée doit-être "Comme arguments" au lieu de "stdin" dans le popup menu.
Utilise ce script directement dans l'action au lieu d'utiliser le fichier (ton script shell), comme cela le script pourra lire un ou plusieurs fichiers.


```
for f in "$@"
do
    if test -f "$f"
    then
        if [ ${f##*.} = pdf ]
        then
            /usr/local/bin/pdftotext -enc UTF-8 "$f" - | say
        else
            textutil -convert txt -stdout "$f" | say
        fi
    fi
done
```

Important : remplace /usr/local/bin/ par le chemin de ton exécutable *pdftotext*, pour connaitre le chemin, tape ceci dans le Terminal

```
which pdftotext
```
Cela fonctionne chez-moi sans le chemin complet de *pdftotext*, mais ce n'est pas le cas pour tout le monde.


----------



## armel35 (12 Avril 2015)

Merci beaucoup JacqR, tout a fonctionné du premier coup grâce à tes indications !


----------



## armel35 (12 Avril 2015)

JacqR a dit:


> Bonjour,
> 
> Info : Xcode n'est pas nécessaire pour installer *pdftotext*, il suffit d'ouvrir le paquet "Installer.pkg" avec le menu contextuel "Ouvrir avec" --> "*Programme d'installation (par défaut)*" pour l'installer, cela exige un mot de passe administrateur, l'installeur placera *pdftotext* dans le dossier /usr/local/bin/ automatiquement.



Dans ma joie, j'ai oublié de te demander où trouver "installer.pkg" ?
Fais-tu référence à l'installation de la ligne de commande préconisée par Gautier Delorme ?

"Si la commande pdftotext ne fonctionne pas, c’est sûrement parce que vous n’avez pas installé les outils de ligne de commande de XCode via cette commande (pensez aussi à vérifier votre PATH, le chemin de la commande est */opt/local/bin/*) :

xcode-select --install"


----------



## JacqR (12 Avril 2015)

Bonjour,



armel35 a dit:


> Dans ma joie, j'ai oublié de te demander où trouver "installer.pkg" ?
> Fais-tu référence à l'installation de la ligne de commande préconisée par Gautier Delorme ?


Oui, vous avez besoin de *Xcode* si vous voulez utiliser la commande xcode-select --install.

Mais cela n'est pas nécessaire, il suffit de monter le fichier "*pdftotext.dmg*", de faire un double-clic sur le fichier "*Installer.pkg*" dans le volume monté pour que le programme d'installation standard de l'OS l'installe, seul inconvénient sur les versions récentes de l'OS, il refusera car cela ne provient pas de l'App Store', donc il faut faire un clic-droit sur le fichier "*Installer.pkg*" et sélectionner le menu "Ouvrir avec" --> "Programme d'installation (par défaut)"


----------



## armel35 (12 Avril 2015)

Merci encore,

Je donne ici le lien vers la page où l'on peut télécharger pdftotext :
http://www.bluem.net/en/mac/packages/

et le lien direct:
http://www.bluem.net/files/pdftotext.dmg


----------



## armel35 (12 Avril 2015)

JacqR,

Sans vouloir abuser de ton temps : comment faire pour arrêter le script quand le fichier est très long ?

Penses-tu que ce soit possible de générer une petite fenêtre de dialogue offrant les possibilités Pause/reprise ou arrêt lecture ?

D'avance, merci pour ta répônse


Didier


----------



## JacqR (13 Avril 2015)

Bonjour,

Il n'y a pas de solution pour mettre en pause, car la commande *say* n'accepte pas de se mettre en pause, alors qu'on peut le faire sur des scripts avec pkill -STOP "id du processus" pour mettre en pause et pkill -CONT "id du processus"  pour la reprise.
Peut-être que c'est possible en programmant une application *Cocoa* (je ne sais pas).
Sinon seul *VoiceOver* en standard sur l'os peut faire cela, cela implique qu'il faut ouvrir le fichier dans une fenêtre d'une application.
Aussi, c'est possible avec la commande *say* d'enregistrer la parole dans un fichier audio, mais cela est très demandant pour les processeurs et c'est très long quand la taille du fichier texte est grande, après il faut l'ouvrir dans un lecteur audio dont on peut contrôler la pause etc..

--
Donc, il reste l'arrêt du script :
Si vous avez lancé la commande dans le *Terminal*, il suffit de presser les touches Contrôle et c ou de fermer la fenêtre pour arrêter la lecture.

Pour *Automator* :
Voici le script bash qui utilise l'application Python pour afficher un bouton qui arrêtera la lecture du fichier

```
scriptPid=$$
function pcall_python() {
PYTHON_ARG="$1" /usr/bin/python - <<END
# -*- coding: utf-8 -*-
import os, sys, Tkinter, subprocess
def killP():
    subprocess.check_call(['/usr/bin/pkill', '-P', os.environ['PYTHON_ARG']]); f.destroy()
f=Tkinter.Tk(); f.title('Lire (service Automator)'); btn=Tkinter.Button(f, text='Arrêter la lecture', width=25, command=killP); btn.pack()
f.call('wm', 'attributes', '.', '-topmost', '1'); f.mainloop()
END
}
b=true
for f in "$@"
do
    if test -f "$f"
    then
        if $b; then ## python n'est pas lancé
            pcall_python "$scriptPid" & ## on lance l'application Python pour afficher le dialogue
            bashpythonPid=$!
        fi
        if [ ${f##*.} = pdf ]
        then
            /usr/local/bin/pdftotext -enc UTF-8 "$f" - | say
        else
            textutil -convert txt -stdout "$f" | say
        fi
        subP=$(pgrep -P "$bashpythonPid") ## on récupère le PID de l'application Python, s'il n'est pas fermé
        b=true; if [ ! -z "$subP" ]; then ps -Axcro pid,command | grep -q " $subP python" && b=false; fi ## si le dialogue est toujours affiché, on ne lancera pas l'app Python au prochain fichier
    fi
done
if ! $b; then /bin/kill $subP > /dev/null 2>&1; fi ## on quitte l'application Python, car il est encore ouvert
```

Info sur le script :
Si vous pressez le bouton  "Arrêter la lecture", Python utilise la commande /usr/bin/pkill -P pour quitter tous les sous-processus en cours du script *bash*, f.destroy() : cela ferme la fenêtre et quitte l'application Python
Si vous fermez la fenêtre de l'application Python ou vous quittez l'application Python cela ne fera rien, le script continuera normalement.​


----------



## armel35 (14 Avril 2015)

Merci beaucoup !


Cette fois encore, tout a fonctionné au premier clic !

Didier


----------



## JacqR (30 Juin 2015)

Bonjour,



armel35 a dit:


> JacqR,
> Penses-tu que ce soit possible de générer une petite fenêtre de dialogue offrant les possibilités Pause/reprise ou arrêt lecture ?
> 
> Didier


Oui, cela est possible avec le framework (cocoa) NSSpeechSynthesizer.
J’ai trouvé en faisant une recherche pour récupérer les voix disponibles de l’utilisateur par script.

Voici le script *bash*, il suffit de remplacer l’ancien par celui-ci, il n’y a pas d’autre changement à faire.

```
function pcall_python() {
PYTHON_ARG="$1" /usr/bin/python - <<END
# -*- coding: utf-8 -*-
import os, Tkinter
from AppKit import NSSpeechSynthesizer, NSSpeechWordBoundary
from Foundation import NSObject

def pauseResume():
    if (parole.isSpeaking()):
        btn.config(text='Reprendre la lecture')
        parole.pauseSpeakingAtBoundary_(NSSpeechWordBoundary)
    else:
        if b:
            parole.startSpeakingString_(' ')
            return
        btn.config(text='Pause')
        parole.continueSpeaking()

def stopParole():
    parole.stopSpeaking
    f.destroy()

class SpeechSynthDelegate(NSObject):
    def speechSynthesizer_didFinishSpeaking_(self, synthesizer, success):
        f.destroy()
    def speechSynthesizer_willSpeakWord_ofString_(self, synthesizer, aRange, tText):
        global b
        z = (aRange.location + aRange.length) - len(tText)
        if z==0: b=True

b=False
parole = NSSpeechSynthesizer.alloc().init()
delegate_ = SpeechSynthDelegate.alloc().init();
f=Tkinter.Tk(); f.title('Lire (service Automator)'); btn=Tkinter.Button(f, text='Pause', width=25, command=pauseResume); btn.pack()
btn2=Tkinter.Button(f, text='Arrêter la lecture', width=25, command=stopParole); btn2.pack()
f.call('wm', 'attributes', '.', '-topmost', '1')
parole.setDelegate_(delegate_)
parole.startSpeakingString_(os.environ['PYTHON_ARG'])
f.mainloop()
END
}
for f in "$@"
do
    if test -f "$f"
    then
        if [ ${f##*.} = pdf ]
        then
            pcall_python "$(/usr/local/bin/pdftotext -enc UTF-8 "$f" -)"
        else
            pcall_python "$(textutil -convert txt -stdout "$f")"
        fi
    fi
done
```




Comme c’est le script python qui part la lecture du texte, fermer la fenêtre ou quitter l’application "*Python*" fait exactement la même chose que presser le bouton "*Arrêter la lecture*".


----------

