Journal toutf8: autodétecter et convertir de n'importe quel encodage de caractères vers UTF8

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
42
23
nov.
2015

Les fichiers textes encodés avec des codecs exotiques, c'est toujours énervant. Il existe des tonnes et des tonnes normes d'encodage de caractères différentes, parfois partiellement compatibles entre elles, qui font qu'ouvrir un fichier texte est parfois une galère.

Personnellement, j'ai souvent le problème avec des sous-titres de films en français où en russes, qui ne sont jamais dans le même encodage de caractère, et dont il faut à chaque fois deviner l'encodage, avant de le convertir.

Pour résoudre ce problème, il existe déjà certains outils…

Les outils

uchardet

uchardet, développé par mozilla pour son navigateur, permet de détecter l'encodage du fichier. Il est codé en C++ et se fonde sur une méthode statistique pour détecter l'encodage de caractères: il connaît la fréquence des lettres dans différentes langues, et l'encodage de ces lettres dans différents codecs, et s'en sert pour deviner l'encodage de caractères.
Il est disponible dans les dépôts debian et ubuntu sous le nom uchardet:
sudo apt install uchardet

iconv

iconv est un outil permettant de transcoder un fichier d'un encodage de caractère à un autre (par défaut, il transcode vers l'UTF8). Il sort le fichier décodé sur la sortie standart, et s'utilise ainsi:

iconv -f INPUT-ENCODING -t OUTPUT-ENCODING input-file.txt > output-file.txt

recode

recode fait la même chose qu'iconv, mais écrase le fichier d'entrée. C'est dangereux, mais parfois pratique. À n'utiliser que si l'on est sûr de l'encodage d'entrée.

recode INPUT-ENCODING file-to-transcode.txt

Comment combiner ces outils

Le plus simple pour utiliser ces outils ensembles, en bash:

recode $(uchardet fichier.txt) fichier.txt

Et si ça ne marche pas

Et voilà, on en arrive enfin à la raison pour laquelle j'ai écrit ce journal. uchardet utilise une méthode statistiques fondée sur la fréquence des lettres. C'est bien, parce que c'est rapide, et que ce n'est pas gros. Mais l'inconvénient, c'est qu'il lui arrive de se tromper, entre deux codecs proches et deux langues proches, quand le fichier comprend peu de caractères spéciaux.

Donc j'ai écrit un petit script python, qui se fonde sur l'utilsation d'un dictionnaire pour détecter la langue du fichier. L'inconvénient, c'est que c'est beaucoup plus lent: 1.5 secondes pour un fichier de sous-titres d'un film d'1h30 (~100Kio). Le script est tout petit, donc je me permets de le poster ici. Il est aussi disponible sur github: toutf8.

#!/usr/bin/env python3
#coding: utf8

import encodings
import re
import sys

if   len(sys.argv) is 1: filein,fileout = ("/dev/stdin", "/dev/stdout")
elif len(sys.argv) is 2: filein,fileout = (sys.argv[1],  sys.argv[1])
else                   : filein,fileout = (sys.argv[1],  sys.argv[2])

with open(filein, "rb") as f:
    buf = f.read()

wordlists = tuple(map(lambda f:set(map(str.strip, open(f,"r"))),
                    ("/usr/share/dict/russian",
                     "/usr/share/dict/french",
                     "/usr/share/dict/american-english")))

scores = dict()

tok = re.compile("\w+")
codings = set(encodings.aliases.aliases.values())
for i,encoding in enumerate(codings):
    try: txt = encodings.codecs.decode(buf, encoding)
    except: continue
    if type(txt) is not str: continue
    found_words = (sum(m.group(0) in wl for m in tok.finditer(txt)) for wl in wordlists)
    scores[encoding] = max(found_words) 

codec = max(scores.keys(), key=lambda k:scores[k])

with open(fileout, "w") as f:
    f.write(encodings.codecs.decode(buf, codec))
  • # précision

    Posté par  (site web personnel) . Évalué à 10.

    Je me rends compte que j'aurais peut-être dû préciser pourquoi une connaissance de la fréquence des lettres ou d'un dictionnaire est nécessaire.

    La plupart des encodages de caractères (à l'exception notable d'UTF8) sauront décoder n'importe quelle suite d'octets que vous lui donnez. Donc on ne peut pas se contenter de vérifier si le codec ne retourne pas d'erreur en décodant le texte, il faut aussi vérifier que ce qu'il retourne est cohérent avec une langue.

    Par exemple, la chaîne d'octets f3 ec ed fb e9 peut-être décodée par les codecs WINDOWS-1251 (donnant умный), ISO-8859-1 (donnant óìíûé), KOI8-R (donnant СЛМШИ), et beaucoup d'autres. Seul умный a un sens dans une langue (cela signifie intelligent en russe). Mon programme va le détecter en détectant que c'est le seul qui existe dans un dictionnaire. uchardet va le détecter en se rendant compte que les lettres у, м, н, ы, й sont plus fréquentes en russe que n'importe lesquelles des lettres des autres résultats dans n'importe quel langage.

    • [^] # Re: précision

      Posté par  (site web personnel, Mastodon) . Évalué à 7.

      Par exemple, la chaîne d'octets f3 ec ed fb e9

      Tu ne donnes que cette chaîne de caractère à manger à uchardet? Ça, c'est sûr, tu lui donnes pas beaucoup de chances. Avec n'importe quel système de détection (même en utilisant un dico), tu peux trouver très facilement des cas d'erreur si ton texte en entrée est trop petit. Il faut au minimum avoir une petite phrase pour que ça commence à avoir du sens de comparer des systèmes de détection.

      ISO-8859-1

      Il n'y a pas encore de prise en charge de ce charset malheureusement.

      Seul умный a un sens dans une langue (cela signifie intelligent en russe).

      Ok en effet si ton cas d'usage est de détecter la langue/charset de mots courts, tous seul, alors il me paraît clair qu'un tel système de détection par dictionnaire uniquement sera fiable. Et encore, cela cassera à la moindre faute d'orthographe, en conjuguant ou accordant en genre/nombre, ou simplement si ton mot n'est pas dans le dico, comme un argot récent ou un mot très moderne. Choses qui peuvent être améliorées en ajoutant une prise en charge grammaticale. Bon au final tu implémentes du traitement de la langue! :-)

      uchardet va le détecter en se rendant compte que les lettres у, м, н, ы, й sont plus fréquentes en russe que n'importe lesquelles des lettres des autres résultats dans n'importe quel langage.

      Pas seulement, il utilise aussi les stats de fréquence de suites de 2 caractères.

      Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

  • # exemple

    Posté par  (site web personnel) . Évalué à 1.

    Et pour donner un exemple d'un cas où uchardet peut être induit en erreur à cause de sa méthode statistique:

    La suite d'octets e0 f0 f4 fb, est détectée par uchardet comme étant du WINDOWS-1255. Or ce n'est même pas une chaîne valide dans ce codec. Simplement, les trois premiers octets correspondent à des lettres fréquentes en hébreux. Je laisse au lecteur le plaisir de découvrir la chaîne de caractères qui se cache vraiment derrière ces octets…

    • [^] # Re: exemple

      Posté par  . Évalué à 2.

      Ne vaudrait-il pas mieux détecter les caractères qui n'existent pas dans une langue plutôt que d'essayer de détecter ceux qui y sont le plus fréquents ? En travaillant par exclusion, on éviterait l'erreur donnée en exemple.

      Cette signature est publiée sous licence WTFPL

      • [^] # Re: exemple

        Posté par  (site web personnel) . Évalué à 4.

        Pour reprendre l'exemple précédent: f3 ec ed fb e9 va donner СЛМШИ en KOI8-R. Ce ne sont que des lettres qui existent en russe. Pourtant, le résultat ne veut rien dire. On est bien obligé de travailler avec des données précises sur chacune des langues que l'on veut détecter.

        Par contre, on peut, et je pense qu'il faut, détecter les points de codes qui ne sont pas valides dans un encodage donné. Et ça, uchardet ne le fait visiblement pas.

        On pourrait imaginer, en plus, d'attribuer des points négatifs quand un caractère qui n'appartient pas à la langue est détecté.

        • [^] # Re: exemple

          Posté par  (site web personnel, Mastodon) . Évalué à 5. Dernière modification le 24 novembre 2015 à 15:54.

          Par contre, on peut, et je pense qu'il faut, détecter les points de codes qui ne sont pas valides dans un encodage donné. Et ça, uchardet ne le fait visiblement pas.

          Uchardet le fait.
          Son algo est basé sur un papier de recherche qui combine 3 méthodes:

          1/ le codage, c'est à dire la détection de séquences d'octets illégaux. Ce dont vous parlez. Il s'agit en fait de la méthode naïve la plus répandue quand quelqu'un va vouloir réimplémenter une détection de charset (par exemple gtksourceview utilisait grosso modo cet algorithme). Elle marche plutôt bien notamment pour les codages multi-bytes qui ont pas mal de séquences illégales. Par contre elle est très peu utile (voire souvent carrêment inutile) pour différencier les codages single-byte qui ont tous quasi les mêmes tableaux de caractères légaux (en particulier les ISO-8859-X).
          Donc si uchardet le fait, bien entendu. Mais c'est loin d'être suffisant pour traiter la plupart des cas (il y a énormément de codages single-byte. En fait tous les langages européens sont codables sur une variante ISO-8859, et cet algo ne permet aucune détection pour un texte qui utilise une de ces variantes).

          2/ La distribution de caractère.

          3/ La distribution de suites de 2 caractères.

          Voir le papier ici: http://www-archive.mozilla.org/projects/intl/UniversalCharsetDetection.html

          Notez pour l'anecdote, et c'est assez marrant, que Firefox rend mal la page même qui explique l'algo de leur ancienne méthode de détection (on voit des caractères cassés, typique d'un encodage raté). En fait, Mozilla n'utilise plus cet algo et a remplacé par une détection par langue.
          Or un simple test avec uchardet montre que ce dernier détecte correctement le codage comme "WINDOWS-1252" (bon c'était probablement de l'ISO-8859-1, mais Windows-1252 est un superset donc acceptable). Comme quoi, je pense que c'était une erreur de la part de Mozilla d'abandonner cet algo.

          Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

          • [^] # Re: exemple

            Posté par  (site web personnel) . Évalué à 3.

            Uchardet le fait.

            C'est faux, et je l'ai montré dans un commentaire précédent.
            Mais puisque tu dis qu'il est censé le faire, je viens d'ouvrir un rapport de bug.

            • [^] # Re: exemple

              Posté par  (site web personnel, Mastodon) . Évalué à 5.

              C'est faux, et je l'ai montré dans un commentaire précédent.

              En effet. Je me basais sur le papier de recherche et j'ai eu tort. Tu sembles avoir raison sur le fait que l'implémentation n'a pas l'air d'être stricte. J'ai regardé un peu le code et je regarderai plus tard en détail les algos implémentés pour voir s'il y a une raison et si cela vaudra le coup d'essayer de rendre le test strict.

              Ce n'est pas dit, et il faut bien se dire que les gens qui ont écrit ça à l'origine ont clairement testé eux-mêmes plusieurs implémentations (même dans le papier de recherche, ils citent déjà plusieurs types de tests et comment ils ont simplifié). Il faut bien voir que la rapidité et l'exhaustivité des langages est importante. Idéalement uchardet devrait être capable de détecter n'importe quel langage/charset et toujours rester rapide (voire très rapide). Si on se rend compte que certaines approximations peuvent accélérer la détection par 10 avec une qualité de détection très proche, clairement l'approximation vaut le coup.

              Par exemple dans le papier, ils expliquent que l'approche naïve est de faire des stats par caractère, mais cela prend énormément de ressources CPU et mémoire. Au final, ils séparent juste la distribution en 2 groupes (caractères fréquemment et peu utilisés), ce qui rend le calcul beaucoup plus rapide et moins gourmand en ressource tout en gardant un résultat satisfaisant sur des textes d'une longueur minimale.
              C'est certes moins précis, mais entre un résultat instantané et un calcul qui va charger des dizaines de dictionnaires en mémoire, créer un pic CPU, et prendre plusieurs secondes (pour ouvrir juste un bête fichier texte!), le choix est vite fait. Personne ne veut attendre plusieurs secondes à chaque ouverture d'un fichier dans gedit ou pour lancer son lecteur multimédia avec des sous-titres.

              Je pense que le vrai problème de tes exemples est que ce sont des mots seuls et courts. uchardet ne sera probablement jamais efficace sur ce type de cas d'usage; et si c'est le tien principalement, alors uchardet n'est en effet pas fait pour toi.
              Par contre uchardet sera toujours plus efficace à partir d'une ou deux phrases (plus efficace dans le sens où ce sera instantané tout en prenant en charge énormément plus de langages et charset; dans tous les cas, clairement si tu compares chaque mot avec un dictionnaire, ton algorithme sera toujours plus efficace, mais à quel prix!).

              En tous les cas, merci pour le rapport de bug. Je regarderai si ça peut être amélioré avec un coût raisonnable quand je pourrai. :-)

              Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

              • [^] # Re: exemple

                Posté par  (site web personnel) . Évalué à 1. Dernière modification le 24 novembre 2015 à 19:04.

                Il me semble évident que détecter un charset invalide accélère le programme, et ne le ralentit pas. En effet, quand une incohérence entre un charset et le texte courant est trouvée, on peut arrêter la collecte de statistiques pour ce charset, et gagner du temps pour la poursuite de la lecture. C'est ce qui semble être fait dans uchardet pour WINDOWS-1252, mais visiblement pas pour WINDOWS-1255.

                Je ne sais pas si c'est le seul problème, mais cette ligne me semble fautive. Je pense que les cinq dernières valeurs devraient être 255,255,128, 96,255, du moins si l'on en croit le commentaire au dessus de la liste de valeur:

                /****************************************************************
                255: Control characters that usually does not exist in any text
                254: Carriage/Return
                253: symbol (punctuation) that does not belong to word
                252: 0 - 9
                *****************************************************************/
                • [^] # Re: exemple

                  Posté par  (site web personnel, Mastodon) . Évalué à 4. Dernière modification le 24 novembre 2015 à 23:49.

                  Il me semble évident que […]

                  Rien n'est évident en algorithmique. C'est la raison pour laquelle les algos naïfs (terme employé pour nommer justement les algorithmes "évidents") sont rarement les bons (ils sont suffisants pour commencer par contre et avoir quelque chose qui "marche" quand avant on avait juste rien, ça oui).
                  C'est la raison pour laquelle même si j'ai aussi moi-même diverses "croyances", je ne les considère pas comme évidentes et sûrement pas comme justes tant que je ne les ai pas implémentées, testées et comparées (quand il existe une autre implémentation). Encore moins quand je reprends un code sur lequel d'autres ont travaillés et se sont posés peut-être les mêmes questions.

                  En tous les cas, merci pour le patch. J'ai répondu. Je pense que le patch est sur la bonne voie mais il y a beaucoup de choses à changer plus en profondeur pour qu'il soit valide. Là tu casses l'utilisation des caractères de contrôle dans l'ensemble des codages single-byte. Or même s'ils sont rares et peu utilisés, les caractères de contrôle ne sont pas techniquement une erreur de codage. Ton patch risque de casser la détection de textes parfaitement valides dans n'importe lequel des codages single-byte pris en charge.
                  Ensuite peut-être que les caractères de contrôle peuvent être des séquences "négatives" (c'est à dire qu'ils feraient baisser la "confiance" en ce charset), bien que je ne sois pas sûr que ce soit utile (voire souhaitable) pour tous les caractères de contrôle en commun à tous les charsets (puisque baisser la confiance en tous les charsets n'apporte probablement rien, voire pourrait poser problème); mais ça peut l'être pour ceux qui sont uniques à quelques charsets. Ensuite encore une fois, ce serait à essayer et tester.

                  En tous les cas, si tu souhaites approfondir la question et proposer un patch plus complet, il est le bienvenu. :-)

                  Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

          • [^] # Re: exemple

            Posté par  . Évalué à 2.

            Notez pour l'anecdote, et c'est assez marrant, que Firefox rend mal la page même qui explique l'algo de leur ancienne méthode de détection (on voit des caractères cassés, typique d'un encodage raté). En fait, Mozilla n'utilise plus cet algo et a remplacé par une détection par langue.
            Or un simple test avec uchardet montre que ce dernier détecte correctement le codage comme "WINDOWS-1252" (bon c'était probablement de l'ISO-8859-1, mais Windows-1252 est un superset donc acceptable). Comme quoi, je pense que c'était une erreur de la part de Mozilla d'abandonner cet algo.

            Firefox utilise UTF-8 pour décoder la page car c'est que le serveur indique d'utiliser dans les en-têtes HTTP.
            Si on supprime l'en-tête fautif et qu'on supprime la balise meta embarquée dans la page, Firefox décode correctement la page (mais je ne saurais pas dire si c'est grâce à l'algorithme de détection de la langue ou grâce à un autre mécanisme).

            L'erreur ici se situe donc dans la configuration du serveur, non dans l'algorithme de détection de la langue utilisé par Firefox.

            • [^] # Re: exemple

              Posté par  (site web personnel, Mastodon) . Évalué à 3. Dernière modification le 25 novembre 2015 à 23:26.

              Salut,

              Je loupe peut-être quelque chose, mais je vois pas de "Content-type" dans les headers en réponse. Cette page me retourne ça.

              Cache-Control: max-age=600
              Connection: Keep-Alive
              Date: Wed, 25 Nov 2015 22:16:31 GMT
              Expires: Wed, 25 Nov 2015 22:26:31 GMT
              Keep-Alive: timeout=20, max=984
              Server: Apache
              X-Cache-Info: not cacheable; response code not cacheable

              Quant à la balise méta dans la page web (j'avais même pas vérifié, tu as raison, j'aurais dû le faire), elle dit:

              <meta http-equiv="content-type"
              content="text/html; charset=windows-1252">

              Donc il semble que Firefox n'ait pas suivi les indications de la page au contraire.
              Donc ton commentaire m'étonne car il donne l'impression que tu as vérifié tout cela et trouve, à l'inverse de moi, mention de UTF-8 dans les headers HTTP et dans le html. Est-ce le cas?

              Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

              • [^] # Re: exemple

                Posté par  . Évalué à 1. Dernière modification le 26 novembre 2015 à 20:28.

                Salut,

                J'ai ça en en-têtes :

                HTTP/1.1 200 OK
                Server: Apache
                X-Backend-Server: pp-web04
                Vary: Accept-Encoding
                Cache-Control: max-age=600
                Content-Type: text/html; charset=UTF-8
                Content-Encoding: gzip
                Date: Thu, 26 Nov 2015 19:17:37 GMT
                Keep-Alive: timeout=20, max=923
                Expires: Thu, 26 Nov 2015 19:27:37 GMT
                Transfer-Encoding: chunked
                Connection: Keep-Alive
                Last-Modified: Tue, 29 Jul 2014 18:51:00 GMT
                X-Cache-Info: caching

                J'ai vérifié entre ma connexion du boulot (FF 42 / Windows), mon ADSL perso (FF 42 / Linux) et http://headers.cloxy.net/ : c'est la même chose. Tu ne serais pas derrière un proxy ?

                Concernant la balise meta, j'ai bien du windows-1252. Je l'avais retirée dans mes tests pour forcer Firefox à faire une détection d'encodage.

                • [^] # Re: exemple

                  Posté par  (site web personnel, Mastodon) . Évalué à 2. Dernière modification le 27 novembre 2015 à 02:23.

                  Oui, ben je sais pas. J'utilise le debugger inclus dans Firefox (touche F12) pour voir les headers, dans l'onglet network. Il me donnait que les headers copiés plus haut, mais je me rends compte que si je fais "edit and resend", la nouvelle requête a plus de headers, avec notamment le Content-type. C'est étrange. Je sais pas trop comment ce debugger marche (et si même c'est là le problème).
                  T'utilises quoi pour voir les headers?

                  Enfin bon, en effet, en renvoyant une seconde fois la requête donc, j'ai pu voir que le serveur annonce de l'UTF-8.

                  Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

                  • [^] # Re: exemple

                    Posté par  . Évalué à 2.

                    Oui, ben je sais pas. J'utilise le debugger inclus dans Firefox (touche F12) pour voir les headers, dans l'onglet network. Il me donnait que les headers copiés plus haut, mais je me rends compte que si je fais "edit and resend", la nouvelle requête a plus de headers, avec notamment le Content-type. C'est étrange. Je sais pas trop comment ce debugger marche (et si même c'est là le problème).

                    Ok j'ai compris, ce n'est pas le debugger le problème. Tu avais pris ta trace alors que la page était en cache. Du coup Firefox a juste demandé au serveur si la page avait changé. Le serveur a répondu 304 (not modified) : la page n'a pas changé, tu peux afficher celle en cache. Dans ce cas, il n'a pas besoin de redonner l'encoding.

                    Si tu forces une réactualisation de la page (ctrl+F5), tu verras bien passer les bons en-têtes.

                    T'utilises quoi pour voir les headers?

                    Sous linux, le debugger inclus aussi. Sous Windows, j'utilise http://www.telerik.com/fiddler. Sapuçaypalibre mais c'est une boîte à outils très pratique pour le trafic HTTP / HTTPS. C'est notamment avec ça que j'ai modifié la réponse du serveur pour retirer l'en-tête et la balise meta pour faire mes tests.

          • [^] # Re: exemple

            Posté par  . Évalué à 1.

            Pour la visu de la distribution de suites de 2 caractères voir ce billet et la video qui l'accompagne :
            https://sciencetonnante.wordpress.com/2015/11/06/la-machine-a-inventer-des-mots-version-ikea/

  • # Au sujet d'uchardet

    Posté par  (site web personnel, Mastodon) . Évalué à 10.

    Salut,

    Je suis le mainteneur de uchardet depuis peu. C'est effectivement la meilleure alternative à l'heure actuelle pour la détection de charset en Libre. Les "classiques" (enca, libguess…) utilisés jusque là sont ridicules d'inefficacité, sans compter ceux qui essaient de réimplémenter une telle détection avec des algos naïfs (ex: gtksourceview, voire VLC, mais eux ne font pas vraiment de détection basé sur le fichier en fait).

    Néanmoins la lib est encore loin d'être parfaite. Mais je vais essayer d'améliorer cela avec le temps.

    Et voilà, on en arrive enfin à la raison pour laquelle j'ai écrit ce journal. uchardet utilise une méthode statistiques fondée sur la fréquence des lettres. C'est bien, parce que c'est rapide, et que ce n'est pas gros. Mais l'inconvénient, c'est qu'il lui arrive de se tromper, entre deux codecs proches et deux langues proches, quand le fichier comprend peu de caractères spéciaux.

    Même si c'est possible que l'algo se trompe entre des codages proches sur certains textes, je pense que le vrai problème est que les données devraient être mises à jour pour certains codages, et surtout uchardet ne prend pas encore en charge tous les codages (même certains qui sont très courant). Un exemple simple, c'est que uchardet est nul pour le français par exemple si vous avez des vieux textes pas écrit en UTF-* (en français, historiquement on utilisait surtout ISO-8859-15, voire ISO-8859-1, y a encore pas si longtemps de cela. Or uchardet n'a de prise en charge d'aucun de ces deux charsets assez communs).

    Mais ça devrait s'améliorer. Dans tous les cas, ça va dans le bon sens. mpv utilise dorénavant uchardet comme mode de détection par défaut (si c'est compilé avec). Ce qui fait que je peux enfin regarder mes films avec des sous-titres japonais ou coréen (ce qui était mon cas d'usage principal qui m'a poussé à regarder et tester ce qui existait, le pousser dans divers logiciels, pour finalement devenir le mainteneur d'uchardet) sans me prendre la tête à savoir si le créateur du fichier l'a fait en UTF-8, en EUC-KR ou quoi que ce soit d'autre…
    gtksourceview sont sur le point de merger mes patchs, ce qui veut dire par exemple que gedit pourra enfin ouvrir mes fichiers non-UTF-8 sans que je me prenne la tête à sélectionner le bon charset dans une liste déroulante.
    Etc.

    Donc j'ai écrit un petit script python, qui se fonde sur l'utilsation d'un dictionnaire pour détecter la langue du fichier.

    J'y avais pensé aussi. Par contre, ton approche est trop bourrine pour être facilement extensible. Utiliser un dico complet (en plus tu prends en charge que 3 langues, et ça ralentit déjà), c'est normal que ce soit lent. Par contre toutes les langues ont des mots très courants, et ceux-là, oui clairement c'est une approche très intéressante. En fait c'est une approche très proche de l'algo d'uchardet qui se base sur des stats de suites de deux ou trois lettres, ce que tu appliques à des mots complets. À partir de là se pose aussi la question de ce qu'est un mot.
    Je vois que tu tokenizes tes mots avec l'expression régulières "\w". Ça marchera pour tes trois langues choisies (anglais, français, russe), mais par par exemple ça ne marchera pas avec du Japonais ou du Chinois qui ne séparent pas les mots avec des espaces.
    Pour cela, il y a des segmenteurs (comme tinysegmenter que je maintiens aussi mais n'ai pas touché depuis longtemps).

    En tous les cas, oui, utiliser une liste limitée des mots (pas tous les mots) très utilisés statistiquement dans un langage peut être très intéressant pour compléter d'autres méthodes de détection (uchardet lui-même combine plusieurs méthodes de détection d'ailleurs) et donner une note globale de "confiance" dans un résultat de détection.

    Film d'animation libre en CC by-sa/Art Libre, fait avec GIMP et autre logiciels libres: ZeMarmot [ http://film.zemarmot.net ]

    • [^] # Re: Au sujet d'uchardet

      Posté par  (site web personnel) . Évalué à 5.

      Merci de toutes ces précisions et conseils. Je suis très content d'avoir le mainteneur d'uchardet parmi mes lecteurs!

      C'est vrai qu'uchardet ne supporte pas ISO-8859-1, mais tu devrais peut-être préciser aux lecteurs qu'il supporte WINDOWS-1252, qui est très très proche, et qui permet donc souvent de décoder ses fichiers texte en français sans problèmes.

      Je me réjouis grandement de savoir que bientôt gedit ouvrira naturellement les fichiers textes qui ne sont pas en UTF-8. Merci pour ton travail, il me sera utile!

      Effectivement, mon approche est plus complexe algorithmiquement, mais c'est le prix à payer pour des résultats plus fiables. Je pense que mettre uniquement les mots les plus courants ne permettrait pas de surpasser la fiabilité d'uchardet, qui est déjà plutôt bonne. Je voulais surtout avoir un truc dont je suis sûr qu'il marche à tous les coups, et ça ne me dérange pas de perdre systématiquement 2 secondes avant de démarrer un film avec des sous-titres. Mais j'ai conscience que cette approche n'est pas utilisable au sein d'une librairie qui aurait autant d'utilisations différentes qu'uchardet.

      • [^] # Re: Au sujet d'uchardet

        Posté par  (site web personnel) . Évalué à 3.

        Je viens de vérifier sur l'article que j'ai mis en lien, que j'avais lu trop vite. WINDOWS-1252 n'est pas seulement très proche, il est totalement compatible avec ISO-8859-1, la seule différence se trouvant dans des caractères qui sont "non-imprimables" en ISO-8859-1. Donc n'importe quel texte ISO-8859-1 sera détecté par uchardet comme du WINDOWS-1252, et sera effectivement décodé correctement en tant que WINDOWS-1252.

  • # Limitations?

    Posté par  . Évalué à 3.

    Je m'interroge sur plusieurs limitations, du coup…

    Pour la lenteur:
    Est-ce l'accès au disque (lire des dico, c'est long, vu le nombre de mots qu'une langue peut avoir…), le tri (en supposant que map en pyhton implique le tri, je ne comprend pas vraiment le python, je l'admets… un jour peut-être que je m'y mettrais… qui sait?), la recherche dans l'arbre?

    Si c'est le cas, ne serait-il pas possible d'avoir de meilleures performances en utilisant une véritable base de données?
    Si ce n'est pas le cas, utiliser un binaire, plutôt qu'un script, serait-il plus performant?

    De plus, ton script utilise une liste codée en dur de dictionnaires… j'imagine que c'est par flemme, si tu n'as pas utilisé l'ensemble des fichiers dans /usr/share/dict/ ? Ou est-il à craindre que l'empreinte mémoire finisse par être importante sur certains systèmes?

    • [^] # Re: Limitations?

      Posté par  (site web personnel) . Évalué à 2.

      À propos de l'algorithme: il est tout bête. On construit des HashMap (des set pythons, ici) contenant tous les mots de chaque langue que l'on veut détecter. En suite, pour chaque codec, on cherche la langue dans laquelle il donne le plus de mots d'un HashMap. On retient ce nombre de mots. À la fin, on cherche le codec qui a donné le plus de mots existants.

      Bien sûr, cela implique d'avoir des dictionnaires qui comprennent toutes les formes, déclinées et conjuguées, de tous les mots. Pour certaines langues, ce n'est pas possible…

      Effectivement, utiliser un autre langage de programmation devrait permettre d'obtenir de meilleures performances.

      On pourrait aussi envisager de ne pas tester les couples langue-encodage qui n'ont pas de sens. Ça devrait permettre de gagner encore du temps.

      Chez moi, /usr/share/dict contient aussi d'autres fichiers, qui ne sont pas des dictionnaires. Mais effectivement, il y a mieux que de lister en dur les noms des fichiers :/

      • [^] # Re: Limitations?

        Posté par  . Évalué à 3.

        On pourrait aussi envisager de construire un arbre de recherche, non? Histoire de gagner du temps en recherche ET en occupation mémoire par rapport aux dicos, avec un surcoût lors de la production de l'arbre (mais qui peut être faite une fois pour toutes, les langues ne bougent pas si souvent).

        • [^] # Re: Limitations?

          Posté par  (site web personnel) . Évalué à 1.

          C'est vrai que ça améliorerait l'empreinte mémoire. Pour l'instant, chez moi, avec 3 dictionnaires, le programme utilise au maximum 100Mo de mémoire.

          Pour ce qui est du temps de recherche, je ne pense pas que ce serait mieux… La recherche dans un set python est en O(1), et est en pratique rapide.

          • [^] # Re: Limitations?

            Posté par  . Évalué à 2.

            Peut-être que si ça prend beaucoup moins de place en mémoire, ça rentrera mieux dans un cache du processeur.

            Parce que ton O(1) cache une grosse constante d'accès à la RAM.

            Un cache L3 de core i7 peut faire dans les 15 Mo donc ça peut valoir le coup et je pense qu'un Trie ou mieux, un Compact prefix tree qui est censé prendre moins de place pourrait être adapté (mais peut-être que la construction de la structure prendrait trop de temps par rapport à la recherche des mots).

            • [^] # Re: Limitations?

              Posté par  . Évalué à 3.

              mais peut-être que la construction de la structure prendrait trop de temps par rapport à la recherche des mots

              En ce cas on peut envisager de distribuer la structure (i.e. pré-compilée) avec le logiciel, non?

  • # attention..

    Posté par  . Évalué à 8.

    Je sais que cela ne porte absolument pas à conséquence ici (en utilisant CPython en tout cas) mais :

    if len(sys.argv) is 1: filein,fileout = ("/dev/stdin", "/dev/stdout")
    elif len(sys.argv) is 2: filein,fileout = (sys.argv[1], sys.argv[1])

    seraient plus "correctes" en utilisant == en lieu et place de is.

    C'est en effet un détail d'implémentation de CPython que les (petits) int peuvent être comparés par identité et pas seulement par valeur.
    Hors c'est bien la comparaison de valeur que tu souhaites (exprimer) ici.

  • # iconv

    Posté par  . Évalué à 9.

    Petite précision concernant iconv, à l'attention de ceux qui voudraient convertir en masse des fichiers vers UTF-8 avec cet outil ; dans le journal, il est dit :

    iconv est un outil permettant de transcoder un fichier d'un encodage de caractère à un autre (par défaut, il transcode vers l'UTF8).

    Or il est précisé dans la page man d'iconv :

    it converts from the encoding given for the -f option to the encoding given for the -t option. Either of these encodings defaults to the encoding of the current locale._

    Autrement dit, si aucun codage n'est précisé pour l'option -f ou pour l'option -t, c'est la variable LANG(UAGE ?) qui est utilisée par défaut ; bien sûr, c'est très souvent fr_….UTF-8 ou en_….UTF-8 qui est utilisé, mais ce n'est pas systématiquement le cas : LANG=fr_FR.iso-8859-1 est une possibilité.

  • # En shell

    Posté par  (site web personnel) . Évalué à 4.

    Le plus simple pour utiliser ces outils ensembles, en bash:

    Non, en shell. Inutile de se limiter à Bash, ce que tu utilises n'est que du très standard, qui fonctionnera avec tout shell conforme à Posix.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.