# -*- coding: utf8 -*-
# mod/aba.py

import cfg
import inc.infos as infos
import inc.mpc as mpc
import mod.commun as commun

from inc.inputkiz import input_seq

# variables du modules
# question : est-ce nécessaire de les garder en mémoire,
#            ou bien on les définit à chaque opération ?
all_lvr = []    # all livres = tous les livres de la bibliothèque
sel_lvr = []    # la sélection des livres
sel_n2i = {}    # retrouver l'index d'un champ "n" dans sel_lvr[]

ouverture_biblio=False

def aba_infos_go():
    """ infos visuelles simples aba """
    infos.m_g("vous écoutez, " lv_titre() + ", de " lv_auteur())


def aba_fav1():
    """ ajoute le livre actuel aux favoris et téléchargements
        trois fois de suite : retélécharge, après confirmation, le livre
    """
    fav_add()


def aba_fav2():
    """ supprime le livre actuel des favoris et téléchargements,
        trois fois de suite : ajoute aux bannis
    """
    del_num4c lv_num4c()
    msg "supprimer " zero_off(del_num4c) + " des favoris"
    infos.pdrNew(msg)
    suppression favoris_bannis(del_num4c"del_fav")
    if suppression == False:
        # le livre n'est pas dans les favoris,
        # on supprime le dossier
        dossier_du_livre bbl_folder() + "/" del_num4c
        if commun.dir_exists(dossier_du_livre):
            cmd "rm -rf " dossier_du_livre
            mpc.sub_proc(cmd)
        else:
            # le dossier n'existe pas,
            # on ajoute ce livre aux bannis
            favoris_bannis(del_num4c"add_bannis")


def aba_fav3():
    """ menu favoris """
    pass


def alogger(s):
    """ fichier log pour débugger """
    pass


def bbl_changer(cle_biblio):
    """ changement de bibliothèque grâce au menu vocal """
    bibo bbl_actu()
    if bibo == cle_biblio:
        msg "Vous êtes déjà dans la bibliothèque " bbl_nom()
        infos.pdrNew(msg)
    else:
        if nb_livres_total() > 0:
            memo_pos_livre_aba()
            save_num4c_actuel(lv_num4c())
        infos.ini_set("aba_bbl_actuelle"cle_biblio)
        reprendre('pas_lancement des audiolivres')

def bbl_audio_cite_classiques():
    bbl_changer("audio_cite_classiques")

def bbl_audio_cite_contemporains():
    bbl_changer("audio_cite_contemporains")

def bbl_cle_usb_anarchie():
    """ lire les livres de la clé usb """
    bbl_changer("cle_usb_anarchie")

def bbl_librivoxde():
    bbl_changer("librivoxde")

def bbl_librivoxen():
    bbl_changer("librivoxen")

def bbl_librivoxfr():
    bbl_changer("librivoxfr")

def bbl_litterature():
    bbl_changer("litteaudio")

def bbl_perso_zip():
    """ lire les livres en ligne perso """
    # procéder pour cette bibliothèque comme pour litterature audio, librivox ...
    # en créant le tableau all_lvr[] adéquat
    bbl_changer("perso_zip")

def bbl_loyalbooksfr():
    bbl_changer("loyalbooksfr")

def bib_prec():
    """ aller à la bibliothèque précédente
    """
    # "loyalbooksfr"
    # "divers_src"
    # "litteaudio"
    # "librivoxfr"
    # "librivoxen"
    # "librivoxde"
    # cfg.bbl_PERSO_ZIP
    # cfg.bbl_CLE_USB_ORDONNEE
    # cfg.bbl_CLE_USB_ANARCHIE
    # "audio_cite_contemporains"
    # "audio_cite_classiques"
    ba bbl_actu()
    nba ""
    if ba == "loyalbooksfr":
        nba "divers_src"
    if ba == "divers_src":
        nba "litteaudio"
    elif ba == "litteaudio":
        nba "librivoxfr"
    elif ba == "librivoxfr":
        nba "librivoxen"
    elif ba == "librivoxen":
        nba "librivoxde"
    elif ba == "librivoxde":
        nba cfg.bbl_PERSO_ZIP
    elif ba == cfg.bbl_PERSO_ZIP:
        nba cfg.bbl_CLE_USB_ORDONNEE
    elif ba == cfg.bbl_CLE_USB_ORDONNEE:
        nba cfg.bbl_CLE_USB_ANARCHIE
    elif ba == cfg.bbl_CLE_USB_ANARCHIE:
        nba "audio_cite_contemporains"
    elif ba == "audio_cite_contemporains":
        nba "audio_cite_classiques"
    elif ba == "audio_cite_classiques":
        nba "loyalbooksfr"

    if nba == "":
        nba "audio_cite_classiques"
    bbl_changer(nba)

def bib_suiv():
    """ aller à la bibliothèque suivante
    """
    # "audio_cite_classiques"
    # "audio_cite_contemporains"
    # cfg.bbl_CLE_USB_ANARCHIE
    # cfg.bbl_CLE_USB_ORDONNEE
    # cfg.bbl_PERSO_ZIP
    # "librivoxde"
    # "librivoxen"
    # "librivoxfr"
    # "litteaudio"
    # "divers_src"
    # "loyalbooksfr"
    ba bbl_actu()
    nba ""
    if ba == "audio_cite_classiques":
        nba "audio_cite_contemporains"
    elif ba == "audio_cite_contemporains":
        nba cfg.bbl_CLE_USB_ANARCHIE
    elif ba == cfg.bbl_CLE_USB_ANARCHIE:
        nba cfg.bbl_CLE_USB_ORDONNEE
    elif ba == cfg.bbl_CLE_USB_ORDONNEE:
        nba cfg.bbl_PERSO_ZIP
    elif ba == cfg.bbl_PERSO_ZIP:
        nba "librivoxde"
    elif ba == "librivoxde":
        nba "librivoxen"
    elif ba == "librivoxen":
        nba "librivoxfr"
    elif ba == "librivoxfr":
        nba "litteaudio"
    elif ba == "litteaudio":
        nba "divers_src"
    elif ba == "divers_src":
        nba "loyalbooksfr"
    elif ba == "loyalbooksfr":
        nba "audio_cite_classiques"

    if nba == "":
        nba "audio_cite_classiques"
    bbl_changer(nba)


# gestion des favoris et des bannis
def fav_add():
    """ ajouter le livre aux favoris de la bibliothèque en cours """
    favoris_bannis(lv_num4c(), "add_fav")


def fav_del():
    """ supprime le livre des favoris de la bibliothèque en cours """
    favoris_bannis(lv_num4c(), "del_fav")


def ban_add():
    """ ajouter le livre aux bannis de la bibliothèque en cours """
    favoris_bannis(lv_num4c(), "add_ban")


def ban_del():
    """ supprime le livre des bannis de la bibliothèque en cours """
    favoris_bannis(lv_num4c(), "del_ban")


def fav_menu():
    """ lance le menu vocal fav_ban """
    explic "utiliser les touches 1 à {} pour gérer vos favoris, " \
        " zéro pour sortir de ce menu, " \
        "Entrée pour valider la commande entendue"
    commun.menu_vocal("aba_fav_ban""favoris et indésirables"explic)


def favoris_bannis(num4coperation):
    """ gère les favoris et les bannis
        qui sont en fait deux listes de mémoire
    """
    SEP ","
    def add_ban_fav(num4cban_fav):
        """ ajoute num4c aux favoris ou bannis
            renvoie True si ajout, False sinon """
        v_ini "aba_" ban_fav "_" bbl_actu()
        str_fav_actu infos.ini_get(v_ini"")
        fav_actu str_fav_actu.strip().split(SEP)
        if str_fav_actu == "":
            infos.ini_set(v_ininum4c)  # pour éviter erreur aba_favoris_litteaudio=,0150
        elif not num4c in fav_actu:
            fav_actu.append(num4c)
            fav_actu.sort()     # https://docs.python.org/3/howto/sorting.html
            if len(fav_actu) == 0:
                infos.ini_set(v_ininum4c)
            else:
                infos.ini_set(v_iniSEP.join(fav_actu))
        else:
            msg "livre déjà présent dans les " ban_fav
            infos.pdrNew(msg)
            return False
        # 3 lignes communes au if et 1er elif
        msg "livre " zero_off(num4c) + " ajouté aux " ban_fav
        infos.pdrNew(msg)
        return True


    def del_ban_fav(num4cban_fav):
        """ supprime num4c des favoris ou bannis
            renvoie True si suppression, False sinon """
        v_ini "aba_" ban_fav "_" bbl_actu()
        str_ban_actu infos.ini_get(v_ini"")
        ban_actu str_ban_actu.strip().split(",")
        if str_ban_actu == "":
            pass    # on ne peut pas enlever qch de rien
                    # éviter ce type d'erreur, aba_favoris_litteaudio=,0150
        elif num4c in ban_actu:
            ban_actu.remove(num4c)
            if len(ban_actu) == 0:
                infos.ini_set(v_ini'')
            else:
                infos.ini_set(v_iniSEP.join(ban_actu))
            msg "livre " zero_off(num4c) + " supprimé des " ban_fav
            infos.pdrNew(msg)
            return True
        else:
            msg "livre déjà absent des " ban_fav
            infos.pdrNew(msg)
            return False


    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    if num4c is None:
        return False

    if operation == "add_fav":
        return add_ban_fav(num4c"favoris")
    elif operation == "del_fav":
        return del_ban_fav(num4c"favoris")
    elif operation == "add_ban":
        return add_ban_fav(num4c"bannis")
    elif operation == "del_ban":
        return del_ban_fav(num4c"bannis")


def aba_nfo1():
    """ message sonore : vous écoutez "Des hommes sans femmes", de "Haruki Murakami"
    """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    bbll bbl_langue()
    if mpc.is_playing():
        if bbll == cfg.LG_FRANCAIS:
            masque "vous écoutez le livre {}, rubrique {}, {}, de {}"
        elif bbll == cfg.LG_ANGLAIS:
            masque "you are listening to the book {}, domain {}, {}, the author is {}"
        elif bbll == cfg.LG_ALLEMAND:
            masque "Sie hören das Buch {}, im Besitz {}, {}, der Autor ist {}"
    else:
        if bbll == cfg.LG_FRANCAIS:
            masque "vous écoutez LE livre {}, rubrique {}, {}, de {}"
        elif bbll == cfg.LG_ANGLAIS:
            masque "the current bOOk is {}, in the area {}, {}, the author is {}"
        elif bbll == cfg.LG_ALLEMAND:
            masque "das aktuelle BUCh ist Nummer {}, im Besitz {}, {}, der Autor ist {}"

    annonce masque.format(zero_off(lv_num4c()), lv_genre(), lv_titre(), lv_auteur())
    infos.pdrNew(annonce)


def aba_nfo2():
    """ message sonore :
        vous êtes à 2 minutes 43
        TODO vous êtes à 2 minutes 43, sur la piste 3
    """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    duree_conv duree_conviviale(str(lv_duree()))
    bbll bbl_langue()
    if duree_conv == "":
        duree2 ""
    else:
        if bbll == cfg.LG_FRANCAIS:
            duree2 ", la durée totale est de " duree_conv
        elif bbll == cfg.LG_ANGLAIS:
            duree2 ", total duration is " duree_conv
        elif bbll == cfg.LG_ALLEMAND:
            duree2 ", im Ganzen dauert es " duree_conv
    mpc.status_progression()[0]
    p.split(":")

    # numéro de piste
    # playlist_position
    # playlist_longueur
    piste str(mpc.playlist_position())
    piste_total str(mpc.playlist_longueur())

    mpc.ferme_ton_clapet()
    if len(q) == 2:
        if bbll == cfg.LG_FRANCAIS:
            msg "vous êtes à " q[0] + " minutes " q[1] + duree2
        elif bbll == cfg.LG_ANGLAIS:
            msg "you are at " q[0] + " minutes " q[1] + duree2
        elif bbll == cfg.LG_ALLEMAND:
            msg "Sie sind um " q[0] + " Minuten " q[1] + duree2
        infos.pdrNew(msg)

    if bbll == cfg.LG_FRANCAIS:
        msg "chapitre " piste " sur " piste_total
    elif bbll == cfg.LG_ANGLAIS:
        msg "chapter " piste " from " piste_total
    elif bbll == cfg.LG_ALLEMAND:
        msg "Kapitel " piste " von " piste_total
    infos.pdrNew(msg)


def aba_nfo3():
    """ annonce le nombre de livres (pour le filtre actuel), le lecteur
    """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    nb_livres str(nb_livres_total())
    if lv_poids().strip() == "":
        # espace à donner si livre téléchargé,
        # ou bien redonner la longueur en temps
        espace_mo ""
    else:
        espace_mo "il occupe un espace de " commun.entier(lv_poids()) + " méga octets"
    presents_bannis "" # TODO
    msg "bibliothèque " bbl_nom() + ", " \
        "vous ACTION un des " nb_livres " livres" presents_bannis " , " \
        "ce livre est lu par " lv_lecteur() + ", " espace_mo
    if mpc.is_playing():
        msg msg.replace("ACTION""écoutez")
    else:
        msg msg.replace("ACTION""êtes sur")
    infos.pdrNew(msg)


def bbl_pos_livres():
    """ renvoie le fichier (nom complet) qui mémorise
        la position et progression des livres entamés d'une bibliothèque
    """
    return "/m/memo/aba/pos_lvr_{}.txt".format(bbl_actu())


def bbl_folder(cle_biblio None):
    """ renvoie le dossier où sont téléchargés les audiolivres
        de la bibliothèque online actuelle :
        /m/music/aba/audio_cite_classiques
        /m/music/aba/litteaudio
    """
    if cle_biblio is None:
        cle_biblio bbl_actu()
    mpd_dossier cfg.M_MUSIC "/aba/" cle_biblio
    return mpd_dossier


def bbl_sub_folder():
    """ renvoie le dossier relatif où sont téléchargés les audiolivres
        de la bibliothèque online actuelle :
        aba/audio_cite_classiques
        aba/litteaudio
    """
    sub_dossier "aba/" bbl_actu()
    return sub_dossier


def bbl_langue():
    """ renvoie la langue de la bibliothèque en cours
    """
    return cfg.BIB_DISPOS[bbl_actu()]["langue"]


def bbl_nom(cle ''):
    """ renvoie le nom de la bibliothèque en cours
    """
    if cle == '':
        return cfg.BIB_DISPOS[bbl_actu()]["nom"]
    else:
        return cfg.BIB_DISPOS[cle]["nom"]


def bbl_mpc_update():
    """ màj mpd audiobooks biblio actuelle """
    cmd_update "mpc -q update " "aba/" bbl_actu()
    retour_update mpc.sub_proc(cmd_update)


def bbl_actu():
    """ renvoie la clé de la bibliothèque online actuelle """
    la_biblio infos.ini_get("aba_bbl_actuelle"cfg.DEFAUT_BIBLIO)
    alogger("la biblio = " la_biblio)
    alogger("cfg.BIB_DISPOS.keys() = ")
    alogger(str(cfg.BIB_DISPOS.keys()))
    if la_biblio in cfg.BIB_DISPOS.keys():
        return la_biblio
    else:
        # la clé peut être inexistante, si développeur
        # a modifié ou supprimé la clé mémorisée
        cles list(cfg.BIB_DISPOS.keys())
        return cles[0]


def build_selektion(list_champs_n None):
    """ construit les tableaux globaux
        sel_lvr[] et sel_n2i{} depuis all_lvr[],

        sel_lvr[] est une liste d'objets livre
        C'est la sélection des livres en cours,
        c'est une partie du tableau complet all_lvr[]
        sel_n2i{} fait pointer un champ "n" vers l'index de sel_lvr[]

        list_champs_n : tableau de champs "n" d'un objet livre,
                        anciennement appelé num4c
    """
    global sel_lvr
    global sel_n2i

    # raz tableaux globaux ...
    sel_lvr = []
    sel_n2i = {}
    nb_mp3 0
    nb_zip 0
    nb_undef 0

    if list_champs_n is None:
        # parcours des all_lvr[]
        for un_livre in all_lvr:
            l_type_url un_livre["type_url"]
            if l_type_url == "mp3":
                nb_mp3 += 1
            elif l_type_url == "zip":
                nb_zip += 1
            else:
                nb_undef += 1
        sel_lvr list(all_lvr)  # duplication
        indice_ze 0
        for un_livre in sel_lvr:
            sel_n2iun_livre["n"] ] = indice_ze
            indice_ze += 1
        infos.log("{} livres, dont mp3/zip/undef {}/{}/{}".format(nb_livres_total(), nb_mp3nb_zipnb_undef))

    else:
        # on construit sel_lvr[] et sel_n2i[]
        # depuis all_lvr ET list_champs_n
        # ex : pour les livres présents seuls
        for un_livre in all_lvr:
            if un_livre["n"in list_champs_n:
                sel_lvr.append(un_livre)
        indice_ze 0
        for un_livre in sel_lvr:
            sel_n2iun_livre["n"] ] = indice_ze
            indice_ze += 1
        infos.m_g("{} livres".format(nb_livres_total()))


def charger_all_lvr(biblio None):
    """ crée le tableau all_lvr[]
        d'une bibliothèque depuis le fichier catalog.txt correspondant
        (ex : /opt/jk2019/tout/ini/aba/audiocite_classiques/catalog.txt)

        all_lvr[] est un tableau d'objets livres

        TODO : devra vérifier des champs essentiels :

        n=  OBLIGATOIRE
        y=  facultatif
        d=  facultatif
        p=  facultatif
        l=  facultatif
        a=  OBLIGATOIRE
        t=  OBLIGATOIRE
        u=  OBLIGATOIRE, liste d'urls de .mp3 ou .zip,
            ou d'une url commençant par http et
            ne se finissant pas par zip ou mp3
        f=  facultatif
        x=  facultatif

        renvoie True si succès, False sinon
    """
    if biblio is None:
        biblio bbl_actu()
    global all_lvr
    all_lvr = []
    num_ligne = -1

    cata_type cfg.BIB_DISPOS[biblio]["cata_type"]
    if cata_type == "dynamique":
        infos.m_g('catalogue dynamic')
        return False

    catalog cfg.BIB_DISPOS[biblio]["catalogue"] + "/catalog.txt"
    if commun.file_exists(catalog) == False:
        infos.g_m("Le fichier {} n'existe pas".format(catalog))
        return False

    t_auteurs = {}
    t_lecteurs = {}
    t_genres = {}
    t_url = {}
    t_duree = {}
    t_poids = {}
    t_titre = {}

    with open(catalog"r"as f:
        for ligne in f:
            if ligne.startswith("#"):
                continue
            elements ligne.split("|")
            num_ligne += 1
            url_media elements[7].split("="1)[1].strip()
            # on suppose ici que tous les éléments d'une liste d'urls
            # sont de la même nature que
            if url_media.endswith(".mp3"):
                type_url "mp3"
            elif url_media.endswith(".zip"):
                type_url "zip"
            elif url_media.startswith("CE_TOME"):
                type_url "CE_TOME_la.com"
            elif url_media.startswith("PARTIES"):
                type_url "PARTIES_la.com"
            elif url_media == "":
                type_url "url_vide"
                print("url_vide : " \
                    elements[0].split("="1)[1].strip() + ", " \
                    elements[8].split("="1)[1].strip())
            else# ex : 1347, https://www.audiocite.net/download.php?id=4491 (qui est un mp3 dans ce cas)
                type_url "undef"

            num4c elements[0].split("="1)[1].strip()
            duree elements[2].split("="1)[1].strip()

            lecteur elements[4].split("="1)[1].strip()
            auteur elements[5].split("="1)[1].strip()
            genre elements[1].split("="1)[1].strip()
            url_media1 url_media.replace(" ""%20")      # ex : 0186
            arr_url_media url_media1.split("+")   # nouveauté avec litteratureaudio.com
            titre elements[6].split("="1)[1].strip()
            poids elements[3].split("="1)[1].strip()
            poids poids.replace(","".")
            # tableau asso d'un livre
            # TODO : charger_element() permettra de charger chaque champ,
            #        quel que soit l'ordre d'apparition sur la ligne
            #        nydplatuf sera du passé
            #        on pourra même ajouter d'autres champs,
            #        et ne même pas faire figurer les champs optionnels :
            #        x = "ECOUTER_EXTRAIT"
            #        b = "publie_le"

            t_auteurs[auteur] = 0
            t_genres[genre] = 0
            t_lecteurs[lecteur] = 0
            t_url[url_media[0]] = 0
            t_duree[duree] = 0
            t_poids[poids] = 0
            t_titre[titre] = 0

            if len(elements) > 9:
                if elements[9].strip() == "":
                    extrait ""
                else:
                    xx elements[9].split("="1)
                    extrait xx[1].strip()
            else:
                extrait ""

            un_livre = {
                "n"num4c    # numéro à 4 chiffres
                "y"genre    # type
                "d"duree    # durée
                "p"poids    # poids (string : "5.78", "12", ...)
                "l"lecteur  # lecteur
                "a"auteur   ,  # auteur
                "t"titre,  # titre
                "u"arr_url_media# url mp3 ou zip
                "f"elements[8].split("="1)[1].strip(),  # url fiche
                "x"extrait,  # extrait
                "type_url"type_url,  # type url : mp3, zip
            }
            all_lvr.append(un_livre)

    # tables annexes, à numéroter, puis réindexer catalog.txt
    # décommenter les lignes ci-dessous
    # pour vérifier un catalogue :
    # - auteurs en doublons du genre "Victor Hugo" et "Victor hugo" ou "Victor  Hugo"
    # - media_url mal formé : ne commence pas par http, par exemple
    # ~ creer_nouvelles_tables(
        # ~ biblio,
        # ~ all_lvr,
        # ~ list(t_auteurs.keys()),
        # ~ list(t_genres.keys()),
        # ~ list(t_lecteurs.keys()),
        # ~ list(t_url.keys()),
        # ~ list(t_duree.keys()),
        # ~ list(t_poids.keys()),
        # ~ list(t_titre.keys()),
        # ~ )
    return True


def choisir_livre():
    """ accès direct à un livre depuis son num4c (de 1 à 4 chiffres demandés) """
    # à l'avenir, le user pourra utiliser les favoris également, plus pratiques
    memo_pos_livre_aba()
    msg "Entrez un numéro de livre " \
        " de 1 à " str(nb_livres_total()) + ", " \
        " puis appuyez sur Entrée"
    infos.pdrNew(msg)
    try:
        choix_user input("")
        global sel_n2i
        n_0000 zero_on(choix_user)
        if n_0000 in sel_n2i.keys():
            save_num4c_actuel(n_0000)
            lire_livre_actuel("annoncer")
        else:
            msg "Vous avez choisi un nombre hors limites. " \
                "Abandon de l'opération."
            infos.pdrNew(msg)
    except:
        msg "Veuillez choisir un nombre de 1 à " str(nb_livres_total())
        infos.pdrNew(msg)


def devDirSize(start_path):
    """ taille totale du dossier en méga octets """
    # https://stackoverflow.com/questions/1392413/calculating-a-directorys-size-using-python
    import os
    total_size 0
    for dirpathdirnamesfilenames in os.walk(start_path):
        for in filenames:
            fp os.path.join(dirpathf)
            # skip if it is symbolic link
            if not os.path.islink(fp):
                total_size += os.path.getsize(fp)
    return total_size 1000000


def dezipper_del(fichierdossierprefixeInfo ""):
    """ va dans le dossier dossier,
        dézippe avec 7z le fichier fichier,
        supprime le fichier fichier
        Renvoie True si tout ok, False si décompression échouée
    """
    # dézipper (cf. doc_04)
    pfx prefixeInfo

    cmd "cd " dossier "; 7z e -y " fichier
    infos.g_m(pfx "dézipper {} 1/4. {}".format(fichiercmd))
    unzip_please mpc.sub_proc(cmd)
    # 7z (cf. doc_07)
    if unzip_please.find("Everything is Ok"):
        infos.g_m(pfx "dézipper {} 2/4. Everything is Ok".format(fichier))
    else:
        infos.g_m(pfx "dézipper {} 2/4. problème dézippage 7z e".format(fichier))
        return False
    cmd "cd " dossier "; rm " fichier
    infos.g_m(pfx "dézipper {} 3/4. {}".format(fichiercmd))

    remove_zip mpc.sub_proc(cmd)
    infos.g_m(pfx "dézipper {} 4/4. {}".format(fichierremove_zip))

    return True


def dl_files_Now(poidsnum4clist_urlsdossierfichierextension):
    """ téléchargements simple ou multiple
        list_urls est une liste à 1 ou plusieurs éléments
        seul le téléchargement a lieu ici, zip ou mp3,
        le dézippage se fait dans dl_media
    """
    if len(list_urls) == 1:
        # un seul fichier à télécharger
        dl_fileNow_one(poidsnum4clist_urls[0], dossierfichierextension)
        if commun.file_exists("/m/quitter_download"):
            return 1
        else:
            return r
    else:
        # ATTENTION : le bloc ci-dessous est identique
        #             au bloc similaire de dl_files_Now
        #             modifier l'un nécessite de modifier l'autre
        suffixe 1000
        for une_url in list_urls:
            suffixe += 1
            if suffixe == 1001:
                fichier num4c
            else:
                fichier num4c "_" str(suffixe)[-3:]
            infos.log("dl files Now, fichier N° {} = {}".format(suffixefichier))
            infos.log("dl files Now, url = " une_url)
            retour_un_fichier dl_fileNow_one(poidsnum4cune_urldossierfichierextension)
            if commun.file_exists("/m/quitter_download"):
                return False
            if retour_un_fichier == False:
                infos.m_g("ERR download url " une_url)
                return False

    return True


def dl_fileNow_one(poidsnum4cune_urldossierfichierextension):
    """ télécharge un fichier """
    # Entrée :
    # une_url   : liste d'urls http à télécharger
    # dossier   : dossier destination
    # num4c     : désigne le livre et le radical du fichier à créer
    #
    # Sortie :
    # cette fonction renvoie True si fichier téléchargé, sinon False

    # télécharger et mesurer temps d'exécution (pour estimer vitesse)
    if poids != "":
        taille poids
        if taille == "0":
            taille "1"
        dire_poids " de " str(int(float(taille))) + " méga octets"
    else:
        dire_poids ""

    nfo_pfx "dl fileNow one [" num4c "] "
    from time import time
    download_start time()

    prononcer ""
    # créer dossier destination
    # et télécharger avec wget
    # entourer l'url de guillemets
    # NOK wget -O 1661.mp3 https://archive.org/download/Lenfant_Maupassant/L'enfant.mp3
    # OK  wget -O 1661.mp3 "https://archive.org/download/Lenfant_Maupassant/L'enfant.mp3"
    masque "mkdir {0}; wget --timeout=20 --tries=2 -O {0}/{1}.{2} \"{3}\" || echo nok"
    cmd masque.format(dossierfichierextensionune_url)
    infos.g_m(nfo_pfx cmd)
    retour_cmd mpc.sub_proc(cmd)
    # killé par commun.quitter()
    infos.g_m("wget killé ou terminé normalement")
    if commun.file_exists("/m/quitter_download"):
        return False
    # TODO : vérifier la présence du fichier après téléchargement
    if retour_cmd == "nok":
        # le téléchargement a échoué
        return False

    # infos vitesse
    if dire_poids != "":
        duree int(time() - download_start)
        taille float(str(taille))
        try:
            vitesse int(taille duree 1000)
            infos.g_m(nfo_pfx "1. durée    : {} secondes, vitesse : {} Ko/s".format(dureevitesse))
        except ZeroDivisionError:
            infos.g_m("division par zéro")
            infos.g_m("taille = " str(taille))
            infos.g_m("durée  = " str(duree))
            infos.g_m(nfo_pfx "1. durée    : pb de mesure")
    # vérification du résultat
    cmd "ls -lh " dossier "/" num4c "." extension
    infos.g_m(nfo_pfx "2. vérif    : " cmd)
    retour_cmd mpc.sub_proc(cmd)
    infos.g_m(nfo_pfx "3. résultat : " retour_cmd)
    cmd_ls "[ -f {}/{}.{} ] && echo dl_ok".format(dossierfichierextension)
    retour_ls mpc.sub_proc(cmd_ls)
    infos.g_m(nfo_pfx "4.          : " retour_ls)

    if retour_ls == "dl_ok":
        return True
    else:
        return False


def dl_media(bib_indexnum4cpoidsdureelist_urlstype_url):
    """ se charge de télécharger le mp3, le zip, ou le undef
        de le placer où il faut,
        et de mettre à jour mpd
    """
    # cf. doc_09, pour exemples num4c à tester

    # infos user
    # ~ msg = "dl media [], ze_idx = " + str(ze_idx) + ", num4c = " + num4c
    if len(list_urls) == 1:
        msg1 "un seul fichier"
    else:
        msg1 str(len(list_urls)) + " fichiers"
    infos.g_m(msg1)

    # vaut mieux nettoyer complètement ce dossier
    # avant de travailler plus bas
    temp_dir_dlMedia "/m/temp_dl_media"
    cmd "rm -rf " temp_dir_dlMedia
    purger mpc.sub_proc(cmd)

    # 1. télécharger, et dézipper si zip, vers dossier temporaire
    if type_url == "mp3":
        dl_files_Nowpoidsnum4clist_urlstemp_dir_dlMedianum4c"mp3")
        if commun.file_exists("/m/quitter_download"):
            return 1
        if not r:
            infos.m_g("ERR dl media, dl files Now mp3")
    elif type_url == "zip":
        dl_files_Nowpoidsnum4clist_urlstemp_dir_dlMedianum4c"zip")
        if commun.file_exists("/m/quitter_download"):
            return 1
        if not r:
            infos.m_g("ERR dl media, dl files Now zip")
        # dézipper un ou plusieurs fichiers
        # les noms sont générés de la même façon que dans dl_files_Now()
        # numéroter sur 2, ou mieux sur 3 chiffres
        # pour éviter un problème d'ordre (le fichier
        # 0123_20.mp3 viendra entre le 0123_2.mp3 et le 0123_3.mp3)

        # ATTENTION : le bloc ci-dessous est identique
        #             au bloc similaire de dl_files_Now
        #             modifier l'un nécessite de modifier l'autre
        suffixe 1000
        for une_url in list_urls:
            suffixe += 1
            if suffixe == 1001:
                fichier num4c
            else:
                fichier num4c "_" str(suffixe)[-3:]
            r2 dezipper_del(fichier ".zip"temp_dir_dlMedia)
            if r2 == False:
                break
    elif type_url == "undef":
        infos.m_g("undef, wget --spider pour connaître type")
        infos.m_g("undef, wget --spider pour connaître type")

        # 1. déterminer le type de fichier avec wget --spider
        cmd "wget --tries=5 --spider {} | grep Taille".format(list_urls[0])
        infos.g_m("wget SPIDER : wget --tries=5 --spider {} 2>&1 | grep Taille".format(list_urls[0]))
        infos.g_m("wget SPIDER : wget --tries=5 --spider {} 2>&1 | grep Taille".format(list_urls[0]))
        r_spider mpc.sub_proc(cmd)
        infos.g_m("wget SPIDER : {}".format(r_spider))
        infos.g_m("wget SPIDER : {}".format(r_spider))
        # 2. lancer dl_media en récursif en forçant mp3 ou zip
        if r_spider.find("application/zip") > 0:
            infos.g_m("undef, zip")
            infos.g_m("undef, zip")
            dl_media(bib_indexnum4cpoidsdureelist_urls"zip")
        elif r_spider.find("audio/mpeg") > 0:
            infos.g_m("undef, mp3")
            infos.g_m("undef, mp3")
            dl_media(bib_indexnum4cpoidsdureelist_urls"mp3")
        else:
            infos.g_m("undef, UNKNOWN")
            infos.g_m("undef, UNKNOWN")
            # signaler un problème de téléchargement :
            # dans fichier .ini, ou mémoire utilisateur
            # wget ne trouve pas le fichier dans le temps imparti
            return False
        return r


    if commun.file_exists("/m/quitter_download"):
        return 1

    # 2. déplacer les mp3 vers dossier num4c
    if type_url in "mp3""zip" ]:
        dir_dest "/m/music/aba/" bib_index "/" num4c

        if not commun.dir_exists(dir_dest):
            r_creer_dossier commun.make_dir(dir_dest)

        if commun.dir_exists(dir_dest):
            for audio in "mp3""m4a""wav" ]:
                cmd "mv {}/*{} {}".format(temp_dir_dlMediaaudiodir_dest)
                infos.log("dl media déplacer : " cmd)
                r_deplacement mpc.sub_proc(cmd)
        else:
            infos.m_g("ERR création impossible dossier \"" dir_dest "\"")

    # TOUDOU_optionnel émettre un bip sonore spécial quand un téléchargement est terminé
    # TOUDOU_optionnel émettre un bip sonore encore spécial quand tous les téléchargements sont terminés
    return r


def DOKU_enregistrer_dans_tableur_html():
    """ enregistre dans un fichier html
        le contenu de all_lvr[]
        et calculent des stats : total Mo, toital durée,
        nb livres, nb auteurs, nb_lecteurs, ...
    """

    def create_auteurs(auteursquoi):
        """ renvoie corps de table des auteurs à deux colonnes
            1. auteur
            2. nb de livres
        """
        # table basique, sans tri
        corps_auteurs ""
        cles list(auteurs.keys())
        cles.sort()
        0
        for in cles:
            # i = nom auteur
            # auteurs[i] = nombre de livres
            += 1
            # ligne = tr_td([ str(n) + "/" + str(len(cles)), i, auteurs[i] ])
            ligne tr_td([ niauteurs[i] ])
            corps_auteurs corps_auteurs "\n" ligne
        tbl_auteurs_noms '<a href="#menu" name="' quoi '">' \
        """<h2 style="text-align: center;">""" quoi """</h2></a>
    <table class="table_touches">
        <caption>""" quoi """ classés par ordre alpha</a></caption>
        """ \
        entete_table("auteurs") + \
        corps_auteurs \
        """
    </table>
        """

        # table triée sur nombre de livres décroissants
        # https://docs.python.org/3/howto/sorting.html
        # cf. Operator Module Functions
        from operator import itemgetterattrgetter
        = []
        for in auteurs.keys():
            auteur i
            nb_livres auteurs[i]
            t.append( (auteurint(nb_livres)) )
        sorted(tkey=itemgetter(0))                  # tri sur auteur (colonne 0)
        sorted(skey=itemgetter(1), reverse=True)    # tri sur nombre de livres (colonne 1)
        sorted(xkey=itemgetter(0))                      # tri sur auteur (colonne 0)
        # for i in x:
            # print("{}, {}".format(i[0], i[1]))

        corps_auteurs ""
        0
        for in x:
            += 1
            ligne tr_td([ str(n) + "/" str(len(x)), i[0], i[1] ])
            # ligne = tr_td([ str(n) + "/" + str(len(cles)), i, auteurs[i] ])
            corps_auteurs corps_auteurs "\n" ligne
        tbl_auteurs_nb_livres '<a href="#menu" name="' quoi '">' \
        """<h2 style="text-align: center;">""" quoi """</h2></a>
    <table class="table_touches">
        <caption>""" quoi """ classés par ordre alpha</a></caption>
        """ \
        entete_table(quoi) + \
        corps_auteurs \
        """
    </table>
        """

        xxx "<center><table>" \
            "<tr>" \
            "<td>" tbl_auteurs_noms "</td>" \
            "<td>" tbl_auteurs_nb_livres "</td>" \
            "</tr>" \
            "</table></center>"

        return xxx

    def create_resume():
        """ tableau résumé
        """
        table_resume ""
        taille_totale_mo 0
        duree_totale_minutes 0
        genres__ = {}     # champ "y"
        lecteurs = {}   # champ l
        auteurs_ = {}    # champ a
        0
        max_poids 1
        min_poids 1000
        max_duree 1       # minute
        min_duree 10000    # 10000 minutes
        nb_cases_poids_vides 0
        nb_cases_durees_vides 0
        nb_poids 0
        nb_duree 0
        for lvr in all_lvr:     # init tableaux
            genres__[lvr["y"]] = 0
            auteurs_[lvr["a"]] = 0
            lecteurs[lvr["l"]] = 0
        for lvr in all_lvr:
            genres__[lvr["y"]] += 1
            auteurs_[lvr["a"]] += 1
            lecteurs[lvr["l"]] += 1
            if lvr["p"].strip() == "":
                nb_cases_poids_vides += 1
            else:
                nb_poids += 1
                taille_totale_mo += float(lvr["p"])
            if lvr["d"].strip() == "":
                nb_cases_durees_vides += 1
            else:
                nb_duree += 1
                duree_totale_minutes += duree_minutes(lvr["d"])
            += 1

        # taille
        if nb_poids 0:
            # taille totale
            # str_taille_totale = \
                # '{:,}'.format(int(taille_totale_mo)).replace(",", " ") + \
                # " Mo"
            str_taille_totale infos.cvt_taille_mo2go_mo(taille_totale_mo)

            # taille moyenne
            taille_moyenne_mo taille_totale_mo nb_poids
            # str_taille_moyenne_mo = \
                # '{:,}'.format(int(taille_moyenne_mo)).replace(",", " ") + \
                # " Mo"
            str_taille_moyenne_mo infos.cvt_taille_mo2go_mo(taille_moyenne_mo)
        else:
            str_taille_totale ""
            str_taille_moyenne_mo "poids NC"

        # durée
        if nb_duree 0:
            # durée totale
            # str_duree_totale = \
                # '{:,}'.format(int(duree_totale_minutes)).replace(",", " ") + \
                # "<br>" + infos.cvt_duree_minutes2jhm(duree_totale_minutes)
            str_duree_totale infos.cvt_duree_minutes2jhm(duree_totale_minutes)
            # durée moyenne
            duree_moyenne_minutes duree_totale_minutes nb_duree
            str_duree_moyenne_minutes \
                '{:,}'.format(int(duree_moyenne_minutes)).replace(","" ") + \
                " mn"
        else:
            str_duree_totale ""
            str_duree_moyenne_minutes "durées NC"
        remarque_poids "Poids max : ..., min : ..."
        remarque_duree "Durée max : ..., min : ..."

        # table résumé
        table_resume """
    <a href="#menu" name="resume"><h2 style="text-align: center;">Résumé</h2></a>
    <table class="table_touches">
        <caption>Statistik</a></caption>
        <tr>
            <th>Champ</th>
            <th>Valeur</th>
            <th>Remarque</th>
        </tr>
        """ \
        tr_td(["Nombre de livres"'{:,}'.format(n).replace(","" "), ""]) + \
        tr_td(["Taille totale"   str_taille_totalestr(nb_cases_poids_vides) + " case(s) vide(s)"]) + \
        tr_td(["Taille moyenne"  str_taille_moyenne_moremarque_poids]) + \
        tr_td(["Durée totale"    str_duree_totalestr(nb_cases_durees_vides) + " case(s) vide(s)"]) + \
        tr_td(["Durée moyenne"   str_duree_moyenne_minutesremarque_duree]) + \
        """
    </table>
        """
        return table_resumeauteurs_genres__lecteurs

    def create_tous_les_livres():
        """
        """
        global all_lvr
        corps_table ""
        0
        for lvr in all_lvr:
            ligne_tr \
                "<tr>" \
                    td(n) + \
                    td(lvr["n"]) + \
                    td(lvr["y"]) + \
                    td(lvr["d"]) + \
                    td(lvr["p"]) + \
                    td(lvr["l"]) + \
                    td(lvr["a"]) + \
                    td(lvr["t"]) + \
                    td(lvr["x"][:40] + "...") + \
                "</tr>" "\n"
            corps_table += ligne_tr
            += 1

        table_all_lvr """
                <a href="#menu" name="tous_les_livres"><h2 style="text-align: center;">Tous les livres (all_lvr)</h2></a>
                <table class="table_touches">
                    <caption>Documentation http://gangand./pp/projets/xavbox/adm/docs</a></caption>
        """ entete_table("all_lvr") + corps_table "</table>"
        return table_all_lvr

    def duree_minutes(d):
        d.split(":")
        return int(t[0]) * 60 int(t[1])

    def entete_table(quoi):
        """
        """
        if quoi == "all_lvr":
            return """
                    <tr>
                        <th>#</th>
                        <th>num4c</th>
                        <th>Genre</th>
                        <th>Durée</th>
                        <th>Poids</th>
                        <th>Lecteur</th>
                        <th>Auteur</th>
                        <th>Titre</th>
                        <th>eXtrait</th>
                    </tr>
            """
        elif quoi == "auteurs":
            return """
                    <tr>
                        <th>#</th>
                        <th>Auteur</th>
                        <th>Nb de livres</th>
                    </tr>
            """
        elif quoi == "lecteurs":
            return """
                    <tr>
                        <th>#</th>
                        <th>Lecteur</th>
                        <th>Nb de livres</th>
                    </tr>
            """
        elif quoi == "genres":
            return """
                    <tr>
                        <th>#</th>
                        <th>Genre</th>
                        <th>Nb de livres</th>
                    </tr>
            """

    def footer_html():
        """
        """
        return """</body>
</html>
        """

    def header_html():
        return """<!DOCTYPE html>
<html>
<head>
    <title>Module audiolivres</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="http://gangand.net/ff/formation/notes/style_notes.css">
    <link rel="stylesheet" href="http://gangand.net/pp/projets/xavbox/xavbox.css">
</head>
<style>
html {
  scroll-behavior: auto;
}
</style>
<body>

"""

    def header_page():
        return "<center><h1>" bbl_actu() + "</h1><br></center>"

    def menu():
        """ menu en te de page
        """
        return """<a name="menu">
<table class="table_touches">
    <caption>Menu</caption>
    <tr>
        <th><a href="#resume">Résumé</a></th>
        <th><a href="#auteurs">Auteurs</a></th>
        <th><a href="#genres">Genres</a></th>
        <th><a href="#lecteurs">Lecteurs</a></th>
        <th><a href="#tous_les_livres">Tous les livres</a></th>
    </tr>
    </table>
</a>
"""

    def td(s):
        return "          <td>" str(s) + "</td>"

    def tr_td(arr_td):
        """ renvoie une ligne tr de td
        """
        ligne ""
        for in arr_td:
            ligne += td(i) + "\n"
        return "        <tr>" "\n" ligne "</tr>" "\n"

    if commun.reseau("hostname") == cfg.MACHINE_DEV_1:
        infos.pdrNew("Fichier tableur créé pour la documentation.")
        infos.m_g("*** Lancer dd doku sur bureau => Doku http://gangand.net/pp/projets/xavbox/adm/docs/stats...")
    else:
        return False

    table_tous_les_livres create_tous_les_livres()
    table_resumeauteurs_genres__lecteurs create_resume()
    table_auteurs  create_auteurs (auteurs_"auteurs")
    table_genres   create_auteurs  (genres__"genres")
    table_lecteurs create_auteurs  (lecteurs"lecteurs")

    corps_html header_page() + \
        menu() + \
        table_resume \
        table_auteurs \
        table_genres \
        table_lecteurs \
        table_tous_les_livres
    page_entiere header_html() + corps_html footer_html()

    file_write "/tmp/proj_xbx_adm_dok_stats_" bbl_actu() + ".html"
    with open(file_write"w"as f:
        f.write(page_entiere)
    infos.m_g("===== Fichier tableur créé " file_write)

def duree_conviviale(duree):
    """ renvoie la durée en heures et minutes """
    # AVANT
    # 1h09 --> 1 heure 9 minutes
    #   03 --> 3 minutes
    # 2h   --> 2 heures

    # APRES
    # 01:54:23 --> une heure 54 minutes 23
    def duree_tester(quantiteunite):
        """ chaine à prononcer selon quantité et unité
        """
        bbll bbl_langue()
        if int(quantite) == 0:
            dire ""
        elif int(quantite) == 1:
            if bbll == cfg.LG_FRANCAIS:
                article_singulier "une"
            elif bbll == cfg.LG_ANGLAIS:
                article_singulier "one"
            elif bbll == cfg.LG_ALLEMAND:
                article_singulier "eine"
            dire "{} {} ".format(article_singulierunite)
        else:
            # int(quantite) permet de transformer 02 en 2
            # pluriel
            if bbll == cfg.LG_FRANCAIS:
                marque_pluriel "s"
            elif bbll == cfg.LG_ANGLAIS:
                marque_pluriel "s"
            elif bbll == cfg.LG_ALLEMAND:
                # eine Stunde --> zwei Stunden
                # eine Minute --> zwei Minuten
                marque_pluriel "n"
            dire "{} {}{} ".format(int(quantite), unitemarque_pluriel)
        return dire

    if duree == "":
        return ""

    hmduree.split(":")
    dire_s ""
    bbll bbl_langue()
    if bbll == cfg.LG_FRANCAIS:
        dire_h duree_tester(h"heure")
        dire_m duree_tester(m"minute")
    elif bbll == cfg.LG_ANGLAIS:
        dire_h duree_tester(h"hour")
        dire_m duree_tester(m"minute")
    elif bbll == cfg.LG_ALLEMAND:
        dire_h duree_tester(h"Stunde")
        dire_m duree_tester(m"Minute")

    # une heure trois minutes
    return dire_h dire_m dire_s


def enqueue_download(bbl_keylvrsilent ""):
    """ ajoute le téléchargement à la file d'attente,
        en créant le fichier témoin,
        que le thread Enqueue traitera

        ajouter le livre actuel, de la bibliothèque actuelle
        au thread thread_download
        ex: /m/aba_dl_bg_bib_audio_cite__0152.txt
            pour télécharger le num4c 0152 de la biblio audio_cite
            audio_cite est la clé de BIB_DISPOS dans cfg.py
    """
    # dl_bg = download background (téléchargement en arrière-plan)
    espace_oks_espace commun.espace("suffisant")
    if espace_ok == True:
        masque "/m/aba_dl_bg_{}__{}.txt"
        file_download masque.format(bbl_keylvr["n"])
        with open(file_download"w"as fd:
            # contenu du fichier témoin sans importance pour l'instant
            # bib_librivoxfr|0152|35|1h18|http://...zip+http://...zip
            contenu "{}|{}|{}|{}|{}|{}".format(bbl_keylvr["n"], lvr["type_url"], lvr["p"], lvr["d"], "+".join(lvr["u"]))
            fd.write(contenu)

    if silent == "":
        if espace_ok == True:
            msg "enqueue " file_download " créé pour " bbl_key
            infos.m_g(msg)
            msg "espAce libre, " \
                str(commun.espace("libre")) + ", " \
                "téléchargement programmé"
            infos.pdrNew(msg)  # "il reste 3200 méga octets sur la clé usb"
        else:
            msg "espace  libre trop  faible, " str(commun.espace("libre")) + \
                "téléchargement annulé"
            infos.pdrNew(msg)
    else:
        if espace_ok == True:
            msg "enqueue " file_download ", " \
                lvr["p"] + ", " \
                lvr["t"] + ", " \
                " créé pour " bbl_key
        else:
            msg "enqueue annulé, espace trop faible"
        infos.m_g(msg)
    return msg


from threading import Thread
class Download(Thread):
    """ Thread chargé de télécharger en arrière-plan """
    # thread à lancer dès le démarrage du module aba

    def __init__(self):
        Thread.__init__(self)
        # ~ print("Lancement thread Download ...")

    def run(self):
        """Code à exécuter pendant l'exécution du thread."""
        # pause pour laisser temps chargement de sel_lvr[]
        commun.faire_pause(10)

        while True:
            commun.faire_pause(4)
            cmd "ls -1 /m/aba_dl_bg_*.txt 2> /dev/null"
            str_dir_command mpc.sub_proc(cmd)

            # on quitte le thread et l'appli
            if commun.file_exists("/m/quitter_download"):
                infos.g_m("fin thread Download")
                cmd "rm /m/quitter_download"
                mpc.sub_proc(cmd)
                cmd "rm /m/aba_dl_bg*txt"
                mpc.sub_proc(cmd)
                break

            # si fichiers témoin présents
            # un fichier témoin est du type :
            # /m/aba_dl_bg_audio_cite_classiques__2254.txt
            # et contient une ligne comme ci-dessous :
            # audio_cite_classiques|2254|mp3|15.0|0:15:00|http://www.archive.org/download/LhommeVoil/Lhomme_voil_Marcel_-_schwob.mp3
            if len(str_dir_command) > 10:
                # TOUDOU_optionnel afficher les téléchargements en attente pour le DEV
                list_downloads str_dir_command.split("\n")
                n_dl_bg 0
                # afficher tous les .txt présents
                for dl_bg in list_downloads:
                    n_dl_bg += 1
                    masque "DEBUG dl en attente, {} sur {} {}"
                    msg masque.format(n_dl_bglen(list_downloads), dl_bg)
                    infos.log(msg)
                # nettoyer liste_dl pour ne conserver que
                # les lignes qui commencent par "aba_dl_bg_"
                num4c_dl ""
                for temoin in list_downloads:
                    if temoin.startswith("/m/aba_dl_bg_"):
                        # lire le contenu du fichier
                        with open(temoin"r"as file_data:
                            data_ligne file_data.readline().strip()
                        data_livre data_ligne.split("|")
                        # bib_librivoxfr|0152|mp3|35|1h18|http://...zip+http://...zip
                        bib_index data_livre[0]      # bib_audiocite
                        num4c_dl data_livre[1]       # 0152
                        type_url data_livre[2]
                        poids data_livre[3]
                        duree data_livre[4]
                        liste_d_urls data_livre[5].split("+")
                        # on ne télécharge que le premier de la liste
                        # avec dl_media, on est en synchrone

                        b_espaces_espace commun.espace("suffisant")
                        if b_espace == True:
                            mask "téléch. {} n:{}, {} Mo, {}, type {}"
                            msg mask.format(bib_indexnum4c_dlpoidsdureetype_url)
                            infos.m_g(msg)
                            dl_media(bib_indexnum4c_dlpoidsdureeliste_d_urlstype_url)
                            # contrôler encore ici si téléchargement
                            # a bien eu lieu
                            # (pb d'espace, connexion interrompu, ...)
                            # la tâche doit se finir pour que les lignes
                            # ci-dessous soient exécutées
                            msg "espace  libre, " \
                                str(commun.espace("libre")) + ", " \
                                "après téléchargement arrière-plan fini " \
                                str(temoin)
                            infos.m_g(msg)
                            # on émet un son quand un téléchargement est terminé
                            commun.sound_tache_terminee()
                            # on met à jour mpd pour la biblio actuelle
                            bbl_mpc_update()
                        else:
                            msg "le livre ne peut pas être " \
                                "téléchargé faute de place"
                            # car s_espace =
                            # "espace restant inférieur à 2000 méga octets"
                            infos.pdrNew(msg)
                            # infos.m_g("Download, " + message_espace_insuffisant(s_espace)[0])
                        # on supprime le fichier témoin
                        cmd "rm {}".format(temoin)
                        mpc.sub_proc(cmd)


                        # on ne télécharge que le premier de la liste
                        break


DEV_ne_pas_lancer_thread False
if DEV_ne_pas_lancer_thread == False:
    # démarrer le thread avec le module aba ?
    thread_queue Download()
    # Lancement des threads
    thread_queue.start()


def entree_lire():
    """ lit le livre actu si présent,
        le télécharge sinon
        séquence : touche Entrée une fois
    """
    mpc.ferme_ton_clapet()
    if infos.ini_get("mode_consultation""consulter") == "consulter":
        if mpc.is_playing():
            msg "Vous êtes déjà en train de lire le livre, " \
                "utilisez les touches 1, 2, 3 pour naviguer"
            infos.m_g(msg)
        else:
            lire_livre_actuel("lire")
    else:
        infos.m_g("En mode lire / consultation, touche Entrée pressée => RIEN")


def espace_suffisant_aba():
    """ vérifie dès démarrage du module aba
        si on pourra télécharger des livres """
    b_espaces_espace commun.espace("suffisant")
    if b_espace == False:
        infos.pdrNew(str(s_espace));
        msg "L'espace est insuffisant actuellement " \
            "pour télécharger de nouveaux livres, " \
            "mais vous pouvez toujours écouter les présents."
        infos.pdrNew(msg)
        # mega_octets_biblio = commun.du(bbl_folder())
        # bbll = bbl_langue()
        # if bbll == cfg.LG_FRANCAIS:
            # voc_dl1 = "les livres téléchargés de cette bibliothèque occupent {} méga octets ".format(mega_octets_biblio)
            # voc_dl2 = "téléchargement impossible par manque de place, " + \
                # "veuillez supprimer des livres, " + \
                # "ou éteindre la machine et insérer " + \
                # "une clé avec de l'espace libre "
        # elif bbll == cfg.LG_ANGLAIS:
            # voc_dl1 = "download of absent books will not happen because space is missing "
            # voc_dl2 = "download of absent books will not happen because space is missing "
        # elif bbll == cfg.LG_ALLEMAND:
            # voc_dl1 = "Herunterladung unmöglich, Raum zu kurz "
            # voc_dl2 = "Herunterladung unmöglich, Raum zu kurz "
        # infos.pdrNew(voc_dl1, voc_dl2)


def supprimer_livres():
    """ supprimes les livres présents de la bibliothèque actuelle
    """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    mpc.stop()
    mpc.clear()
    mpc.ferme_ton_clapet()

    space_left_avant commun.espace("libre")
    bn bbl_nom()
    nb_prez nb_livres_prezents()
    space commun.du(bbl_folder())

    mask "suppression des {} livres présents dans la bibliothèque {}"
    infos.pdrNew(mask.format(nb_prezbn))

    dossier bbl_folder()
    cmd "rm -rf " dossier
    mpc.sub_proc(cmd)

    space_left_apres commun.espace("libre")
    mask "espace avant, {}, espace après suppression, {}"
    infos.pdrNew(mask.format(space_left_avantspace_left_apres))

    mask "gain obtenu : {} méga octets"
    infos.pdrNew(mask.format(space))

    cmd "mkdir " dossier
    mpc.sub_proc(cmd)

    slct_tout()


def supprimer_livre():
    """ supprime le livre présent actuel
    """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    space_left_avant commun.espace("libre")
    mpc.stop()
    mpc.clear()
    mpc.ferme_ton_clapet()

    # - supprimer de la liste sel_lvr[]
    del_num4c lv_num4c()
    del_titre lv_titre()
    infos.g_m("Nb livres AVANT : {}".format(nb_livres_total()))
    ze_idx ze__indice_actuel()

    # supprimer dossier physique
    dossier_du_livre bbl_folder() + "/" del_num4c
    cmd "rm -rf " dossier_du_livre
    infos.g_m(cmd)
    mpc.sub_proc(cmd)

    # reconstruire sel_lvr et sel_n2i
    global sel_n2i
    global sel_lvr
    ze2 = []
    sel_n2i = {}
    idx 0
    nb_append 0
    for in sel_lvr:
        if idx != ze_idx:
            ze2.append(i)
            sel_n2i[i["n"]] = nb_append
            nb_append += 1
        idx += 1
    # print("==================== ze_idx = " + str(ze_idx))
    # print("==================== sel_n2i keys = " + str(sel_n2i.keys()))
    # print("==================== sel_n2i values = " + str(sel_n2i.values()))
    sel_lvr = []
    if nb_append == 0:
        # on a supprimé tous les livres
        infos.pdrNew("vous avez supprimé le dernier livre de la sélection")
        commun.faire_pause(3)
        space_left_apres commun.espace("libre")
        masque "espace avant, {}, espace après suppression, {}"
        infos.pdrNew(masque.format(space_left_avantspace_left_apres))
        slct_tout()
    else:
        idx 0
        for in ze2:
            sel_lvr.append(i)
        infos.g_m("Nb livres APRES : {}".format(nb_livres_total()))
        masque "suppression du livre {}, de la bibliothèque {}, " \
            "il vous reste {} livres en sélection"
        infos.pdrNew(masque.format(del_titrebbl_nom(), nb_livres_total()))

        space_left_apres commun.espace("libre")
        masque "espace avant, {}, espace après suppression, {}"
        infos.pdrNew(masque.format(space_left_avantspace_left_apres))

        msg "Appuyer sur 1, 2 ou 3 pour naviguer dans les livres"
        infos.pdrNew(msg)


def choisir_autre_biblio_svp():
    msg "Aucun livre disponible, veuillez choisir " \
        "une autre bibliothèque avec la touche 11"
    infos.pdrNew(msg)


def livre_hasard(quiet ""):
    """ livre au petit bonheur la chance dans sel_lvr[] """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    memo_pos_livre_aba()
    mpc.ferme_ton_clapet()
    import random
    num_rand random.randint(0nb_livres_total() - 1)
    num4c_ze_actu lv_num4c(num_rand)
    save_num4c_actuel(num4c_ze_actu)
    msg34 "un livre au hasard parmi " str(nb_livres_total())
    arr_msg = [ "",         # ne rien dire au niveau 0
                "hasard",
                msg34,
                msg34
            ]
    infos.pdrNew(arr_msg)
    if infos.niveau_aide_voc() in (23):
        commun.faire_pause(2False)
    infos.g_m("lire_livre_actuel")
    lire_livre_actuel("annoncer")


def offline_download():
    """ appareil offline, télécharger tous les livres de la bibliothèques en cours
        en vue de créer un appareil autonome qui aura à disposition
        tous les livres du catalogue sur place
        On télécharge hors clé usb sur ip12, car il y a 22 Go d'espace
        libre sur / le 23 juin 2020.

        touche OFFLINE
    """
    # infos.m_g("

    # télécharger
    commun.tts_niveau_aide(0)
    # infos.m_g("Début du téléchargement de quelques audiolivre hors ligne")
    bbl_audio_cite_classiques()
    offline_sr1("audio_cite_classiques"15)

    bbl_litterature()
    offline_sr1("litteaudio"5)
    commun.tts_niveau_aide(3)
    # infos.m_g("Fin du téléchargement de quelques audiolivre hors ligne")


def offline_sr1(fichier_faitsnb_downloads_max):
    """ télécharger nb_downloads_max livres
        de la biblio en cours
    """

    def dir_classer_define(lvr_n):
        """ renvoie sous-dossier de rangement
            pour le lvr["n"] demandé
        """
        # ex : lvr["n"] = 0158 => dir_classer = 0101-0200
        #    : lvr["n"] = 1502 => dir_classer = 1501-1600

        #    : lvr["n"] = 0100 => dir_classer = 0101-0200   ERREUR
        #    : lvr["n"] = 0100 => dir_classer = 0001-0100   OK

        # A B            C    D
        # 0001 à 0100 => 0001-0100

        lvr_n[:2]
        lvr_n[-2:]

        if == "00":                       # lvr_n = 0100
            C1 str(int(A) + 100 1)[-2:] # 00
            D1 A                          # 01
        else:                               # lvr_n = 0099
            C1 A                          # 00
            D1 "0" str(int(A) + 1)[-2:] # 001
            D1 D1[-2:]                    # 01

        sous_dir C1 "01" "-" D1 "00"
        return sous_dir

    def batch():
        """ crée le fichier offline_<BIBLIO>.sh
        """
        output "/m/offline_" bbl_actu() + ".sh"
        global all_lvr
        0
        open(output"w")
        ligne "# bibliothèque " bbl_actu()
        print(lignefile=f)
        print(""file=f)

        ligne "[ -d /home/pi/xavbox_offline ] || echo Dossier /home/pi/xavbox_offline absent"
        print(lignefile=f)
        ligne "[ -d /home/pi/xavbox_offline ] || exit"
        print(lignefile=f)
        ligne "cd /home/pi/xavbox_offline"
        print(lignefile=f)

        ligne "if [ -f livres_faits.txt ]; then "
        print(lignefile=f)
        ligne "  echo livres_faits.txt présent"
        print(lignefile=f)
        ligne "else"
        print(lignefile=f)
        ligne "  echo livres_faits.txt absent"
        print(lignefile=f)
        ligne "  exit"
        print(lignefile=f)
        ligne "fi"
        print(lignefile=f)

        for lvr in all_lvr:
            ligne "# livre " lvr["n"]
            print(lignefile=f)

            dir_classer dir_classer_define(lvr["n"])
            partial_path bbl_actu() + "/" dir_classer "/" lvr["n"]
            full_path "/home/pi/xavbox_offline/" partial_path

            ligne "cd /home/pi/xavbox_offline"
            print(lignefile=f)
            print(""file=f)

            ligne "[ -f " bbl_actu() + ".stop ] && echo Dossier biblio_stop"
            print(lignefile=f)
            ligne "[ -f " bbl_actu() + ".stop ] && exit"
            print(lignefile=f)
            print(""file=f)

            ligne "if grep " partial_path " livres_faits.txt ; then "
            print(lignefile=f)
            ligne " echo PRESENT " full_path
            print(lignefile=f)
            print("else"file=f)

            ligne "  echo dl " full_path
            print(lignefile=f)

            ligne "  mkdir -p " full_path
            print(lignefile=f)

            ligne "  cd " full_path
            print(lignefile=f)

            pfx 1000
            for in lvr["u"]:  # plusieurs fichiers pour ce livre
                pfx += 1
                dest lvr["n"] + "_" str(pfx)[1:] + "." lvr["type_url"]

                ligne "  wget -O " dest " \"" "\""
                print(lignefile=f)

                if lvr["type_url"] == "zip":
                    ligne "  7z e -y " dest
                    print(lignefile=f)

                    ligne "  rm " dest
                    print(lignefile=f)


            print("fi"file=f)
            print("[ $(df . | tail -1 | awk '{print $4}') -lt 5000000 ] && echo 5 Go restants, FIN"file=f)
            print("[ $(df . | tail -1 | awk '{print $4}') -lt 5000000 ] && exit"file=f)
            print(""file=f)

        infos.m_g("Fichier {} crée".format(output))


    def ancienne_fonction():
        """ télécharge un par un les livres non présents
            dans le fichier faits.txt
            PAS AU POINT
        """
        global all_lvr
        0
        taille_totale 0
        nb_passer 0
        with open("/home/pi/xavbox_offline/livres_faits.txt""r"as f:
            ff f.readlines()
        faits = []
        for in ff:
            faits.append(i.strip())

        for lvr in all_lvr:
            if bbl_actu() + "/" lvr["n"in faits:
                continue

            dir_classer dir_classer_define(lvr["n"])
            full_path "/home/pi/xavbox_offline/" bbl_actu() + "/" dir_classer lvr["n"]
            if commun.dir_exists(full_path):
                # infos.m_g("livre fait " + full_path + ", on passe")
                pass
            else:
                commun.make_dir(full_path)
                += 1
                # wget_spider_taille indique la taille d'un fichier
                # sans besoin de le télécharger
                pfx 0
                for in lvr["u"]:  # plusieurs fichiers pour ce livre
                    pfx += 1
                    if lvr["p"] == "":
                        poids wget_spider_taille(u)
                        # poids = '{:,} octets'.format(poids)
                    else:
                        poids lvr["p"]
                    dest lvr["n"] + "_" str(pfx) + "." lvr["type_url"]
                    cmd "cd " full_path ";" "wget -O " dest " " u
                    msg bbl_actu() + " multi " str(n) + "." str(pfx) + " , " \
                        lvr["n"]        + " , " \
                        lvr["type_url"] + ", " \
                        str(poids)           + " Mo, " \
                        lvr["t"]
                    infos.m_g(msg)
                    msg_wget "wget -O " dest " " u
                    infos.m_g(msg_wget)
                    mpc.sub_proc(cmd)
                    dezipper_del(destfull_path)

                taille commun.du(full_path)
                taille_totale += taille
                tf1 '{:,}'.format(taille).replace(","" ")
                tf2 '{:,}'.format(taille_totale).replace(","" ")
                infos.m_g("--> {} Mo, total {} Mo".format(tf1tf2))
                infos.m_g("")
                if nb_downloads_max:
                    break
        mask "dl offline_download() {} terminés dans la biblio {}"
        infos.m_g(mask.format(n"/home/pi/xavbox_offline/" bbl_actu()))

    batch()


def present_hasard_biblios():
    """ écouter un livre présent au hasard
        en recherchant dans toutes les bibliothèques
        touches "1111"
    """

    def taille_livre_present():
        """ renvoie espace en Mo du livre présent
            attention :
                opération différente selon que c'est anarchie
                ou online
        """

    def phb_chercher(prononcer True):
        """ rechercher tous les livres présents
            dans les bibliothèques statiques
            un livre présent est symbolisé par la présence d'un sous-dossier
            dans le dossier de la bibliothèque
            doc : gangand.net/pp/projets/xavbox/adm/adm_aba.php#changement
        """
        cles list(cfg.BIB_DISPOS.keys())
        cles.sort()
        0
        infos.m_g("Bibliothèques, livres présents")
        total_Mo 0
        nb_total_presents 0
        nb_biblios_presents 0
        choix_hasard = []
        nb_livr_prezents_partout 0

        for cle_biblio in cles:
            += 1
            bib1 cfg.BIB_DISPOS[cle_biblio]
            cata_type bib1["cata_type"]
            catalogue bib1["catalogue"]
            nom       bib1["nom"]
            msg "vide"
            if cata_type == "statique":
            # cata_type = "tous":
            # if cata_type == "tous":
                dir_biblio cfg.ABA_DIR "/" cle_biblio  # /m/music/aba/audio_cite_classiques
                if commun.dir_exists(dir_biblio):
                    nb_dossiers nb_livres_prezents(cle_biblio)
                    nb_total_presents += nb_dossiers
                    if nb_dossiers 0:
                        nb_biblios_presents += 1
                        t1 commun.du(dir_biblio)
                        taille " (" str(t1) + " Mo)"
                        total_Mo += t1
                        sous_dossiers commun.folders_simple(dir_biblio)
                        for full_path in sous_dossiers:
                            if commun.du(full_path) > 0:
                                # on ne vérifie que la taille,
                                # pas la nature des fichiers
                                choix_hasard.append((cle_bibliofull_path))
                                nb_livr_prezents_partout += 1
                    else:
                        taille ""
                    msg str(nb_dossiers).rjust(2) + " présents" taille
            else:
                msg "dynamique"
            infos.g_m(str(n).rjust(2) + ". " nom.ljust(20) + " : " msg)
        if nb_livr_prezents_partout 0:
            infos.g_m("Taille totale : " str(total_Mo) + " Mo")
            mask "{} livres présents dans {} bibliothèques, qui occupent {} méga octets"
            msg mask.format(nb_total_presentsnb_biblios_presentstotal_Mo)
            if prononcer:
                infos.pdrNew([ ""msg ])
            else:
                infos.m_g(msg)
        return nb_livr_prezents_partoutchoix_hasard

    def phb_5_au_hasard(nb_livr_prezents_partoutchoix_hasardprononcer True):
        """ lister chaque biblio + livres présents
        """
        if nb_livr_prezents_partout 0:
            import random
            max_rand len(choix_hasard) - 1
            num_rand random.randint(0max_rand)
            try:
                cle_bibliofull_path choix_hasardnum_rand ]
            except IndexError:
                infos.m_g("index random = " str(num_rand))
                infos.m_g("longueur choix_hasard = " str(len(choix_hasard)))
            mask "biblio {}, dossier {}"
            msg mask.format(cle_bibliofull_path)
            infos.g_m(msg)
        splitter full_path.split("/")
        champ_n splitterlen(splitter) - ]   # programmer commun.basename(path)
        mask "Lecture du livre au hasard {}, de la bibliothèque {}"
        msg mask.format(champ_nbbl_nom(cle_biblio) )
        if prononcer:
            infos.pdrNew(msg)
        else:
            infos.g_m(msg)
        return cle_bibliochamp_n

    # parcourir toutes les bibliothèques au hasard
    # voir s'il y a des livres présents
    mpc.ferme_ton_clapet()
    msg "écoute d'un livre présent, choisi au hasard " \
        "des bibliothèques"
    infos.pdrNew([""msg])
    mpc.clear()

    Prononcer_True True
    nb_livr_prezents_partoutchoix_hasard phb_chercher(Prononcer_True)
    if nb_livr_prezents_partout == 0:
        msg "Aucun livre n'est présent " \
            "dans les bibliothèques, " bbl_nom() + ", " \
            "vous pouvez utiliser les touches 666 ou 6666 " \
            "pour télécharger des livres au hasard."
        infos.pdrNew(msg)
        return 0

    cle_bibliochamp_n phb_5_au_hasard(
        nb_livr_prezents_partoutchoix_hasardPrononcer_True)
    # lancer la lecture après mémorisation livre en cours
    if nb_livres_total() > 0:
        memo_pos_livre_aba()
        save_num4c_actuel(lv_num4c())
    infos.ini_set("aba_bbl_actuelle"cle_biblio)
    save_num4c_actuel(champ_n)
    charger_all_lvr()
    build_selektion()
    if infos.niveau_aide_voc() > 1:
        infos.pdrNew("Appuyez sur Entrée pour écouter le livre")
        input()
    lire_livre_actuel("lire")
    # touches "1111"


def rand10lvr():
    """ télécharge 10 nouveaux livres au hasard
        en arrière-plan
        pour lecture ultérieure
        touches "666"
    """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    NB_LIVRES_DL_HASARD 10
    if nb_livres_total() < NB_LIVRES_DL_HASARD:
        nb_downloads_random nb_livres_total()
    else:
        nb_downloads_random NB_LIVRES_DL_HASARD

    memo_pos_livre_aba()
    mpc.ferme_ton_clapet()

    # sortir si espace insuffisant
    espace_oks_espace commun.espace("suffisant")
    if not espace_ok:
        infos.pdrNew(message_espace_insuffisant(s_espace))
        # msg = "espace restant insuffisant pour télécharger, " + \
            # "veuillez libérer d'abord de l'espace."
        # infos.pdrNew(msg)
        # msg = "Pour cela, placez-vous sur un livre présent, " + \
            # "puis effacez-le avec 987, " + \
            # "ou bien 11 pour ouvrir le menu vocal, puis la touche 9, " + \
            # "pour supprimer plusieurs livres d'un coup."
        # infos.pdrNew(msg)
        return 0

    space_left commun.espace("libre")
    msg "eSpace libre, " str(space_left) + ", " \
        "tentative de téléchargement de 10 livres au hasard " \
        "dans la sélection"
    infos.pdrNew(msg)
    commun.faire_pause(4)
    # memo_lvr_actu = lv_livre_actuel()    # définit le livre actu
    # contrôler le nombre de livres au total d'abord
    nb_livres_finaux 0
    import random
    # TODO : s'assurer que le nombre au hasard n'a pas encore été choisi
    for in range(nb_downloads_random):
        num_rand random.randint(0nb_livres_total() - 1)
        infos.m_g("***** num_rand =" str(num_rand) + " *****")
        num4c_ze_actu lv_num4c(num_rand)
        save_num4c_actuel(num4c_ze_actu)
        if media_present(lv_livre_actuel()) == "absent_usb":
            lvr lv_livre_actuel()
            enqueue_download(bbl_actu(), lvr"rand10lvr")
            nb_livres_finaux += 1
        else:
            msg str(num4c_ze_actu) + \
                " présent, on en choisit un autre"
            infos.g_m(msg)
    infos.pdrNew(str(nb_livres_finaux) + " livres sont en cours " \
        "de téléchargement")


def rand10lvr_biblios():
    """ télécharger 10 livres au hasard
        de toutes les bibliothèques
        touches "6666"
    """
    # parcourir toutes les bibliothèques au hasard
    # voir s'il y a des livres présents
    msg "télécharger 10 livres au hasard " \
        "de toutes les bibliothèques"
    infos.pdrNew(msg " en cours")
    # prendre 10 fois une bibliothèque au hasard
    # programmer avec enqueue le téléchargement


def rand11lvr_biblios():
    """ télécharger 11 livres au hasard
        de toutes les bibliothèques
        touches "6666"
    """
    # parcourir toutes les bibliothèques au hasard
    # voir s'il y a des livres présents
    mpc.ferme_ton_clapet()
    mpc.clear()

    msg "Téléchargement de 11 livres au hasard " \
        "de toutes les bibliothèques"
    infos.pdrNew(msg " en cours")
    # prendre 11 fois une bibliothèque au hasard
    # programmer avec enqueue le téléchargement

    # sortir si espace insuffisant
    espace_oks_espace commun.espace("suffisant")
    if not espace_ok:
        infos.pdrNew(message_espace_insuffisant(s_espace))
        return 0

    nb_dl_books_partout 11
    cles list(cfg.BIB_DISPOS.keys())
    cles.sort()
    infos.m_g("Bibliothèques, {} livres au hasard partout".format(nb_dl_books_partout))

    import random
    nb_livres_programmes 0
    global all_lvr

    while True:
        if nb_livres_programmes == nb_dl_books_partout:
            break

        num_rand random.randint(0len(cles) - )
        cle_biblio cles[num_rand]
        bib1 cfg.BIB_DISPOS[cle_biblio]
        cata_type bib1["cata_type"]
        if cata_type == "statique":
            infos.ini_set("aba_bbl_actuelle"cle_biblio)
            if charger_all_lvr():
                # choisir un livre au hasard dans la bibliothèque activée
                nb_livres_programmes += 1
                num_rand_2 random.randint(0len(all_lvr) - )
                lvr_hasard all_lvrnum_rand_2 ]
                champ_n_hasard lvr_hasard"n" ]
                titre lvr_hasard"t" ]
                infos.m_g("nb_livres_programmes = " \
                    str(nb_livres_programmes) + \
                    ", " champ_n_hasard \
                    ", " titre)
                enqueue_download(cle_bibliolvr_hasard"silence")


def livre_prec():
    """ aller au livre suivant """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    memo_pos_livre_aba()
    num_livre_actuel ze__indice_actuel() - 1
    if num_livre_actuel == -1:
        num_livre_actuel nb_livres_total() - 1
    save_num4c_actuel(lv_num4c(num_livre_actuel))
    mpc.ferme_ton_clapet()
    lire_livre_actuel("annoncer")


def livre_suiv():
    """ aller au livre précédent """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    memo_pos_livre_aba()
    num_livre_actuel ze__indice_actuel() + 1
    if num_livre_actuel nb_livres_total() - 1:
        num_livre_actuel 0
    save_num4c_actuel(lv_num4c(num_livre_actuel))
    mpc.ferme_ton_clapet()
    lire_livre_actuel("annoncer")


def lire_livre_actuel(actiondirekt ''):
    """ lit le livre de numéro ze_num_actuel(),
        l'annonce, ou le télécharge
        quand on change de bibliothèque, le dernier livre lu
        dans la bibliothèque est retrouvé grâce à la valeur correspondante
        du fichier py_user.ini, exemples :
        num4c_litteaudio=0444|2020-02-25 17:00:10
        num4c_librivoxen=0053|2020-02-25 19:54:42
        num4c_cle_usb_anarchie=0012|2020-02-25 21:48:06
    """

    def final_annoncer(final_seek_read):
        """ renvoie une chaîne du type :
            - "livre présent, ..."
            - "livre à télécharger, ..."
        """
        # annoncer seulement le livre (cf. doc_05)
        infos.log("  --> TIP : Appuyer sur Entrée pour (re)lancer (débuter/terminer) la lecture de ce livre")
        infos.log("  -->       seek_read = " seek_read)
        # on prononce livre ou audiolivre si média présent sur clé usb,
        # référence s'il est absent
        msg1 final_resume_livre()

        bbll bbl_langue()
        if bbll == cfg.LG_FRANCAIS:
            msg_livre_present "livre présent "
            msg_livre_telech "livre à télécharger "
            msg_final_appui_sur_entree_download ", appuyez sur Entrée pour lancer le téléchargement"
            msg_final_appui_sur_entree_lire ", appuyez sur Entrée pour lancer la lecture"
        elif bbll == cfg.LG_ANGLAIS:
            msg_livre_present "book present "
            msg_livre_telech "book to download "
            msg_final_appui_sur_entree_download ", press enter to start the download"
            msg_final_appui_sur_entree_lire ", press Enter to start reading"
        elif bbll == cfg.LG_ALLEMAND:
            msg_livre_present "anwesendes Buch "
            msg_livre_telech "Buch zu herunterladen "
            msg_final_appui_sur_entree_download ", drücken Sie Enter zur Herunterladung"
            msg_final_appui_sur_entree_lire ", drücken Sie Enter zum Lesen"

        # bn = bbl_nom() + ", "
        bn "" # on raccourcit le message vocal
        if media_present(lv_livre_actuel()) == "présent_usb":
            return [
                msg_livre_present msg1[0],
                msg_livre_present msg1[1],
                bn msg_livre_present msg1[2] + msg_final_appui_sur_entree_lire,
                bn msg_livre_present msg1[3] + msg_final_appui_sur_entree_lire
                ]
        elif bbl_actu() == cfg.bbl_CLE_USB_ANARCHIE:
            msg_livre_present " livre sur clé "
            return [
                msg_livre_present msg1[0],
                msg_livre_present msg1[1],
                bn msg_livre_present msg1[2] + msg_final_appui_sur_entree_lire,
                bn msg_livre_present msg1[3] + msg_final_appui_sur_entree_lire
                ]
        else:
            return [
                msg_livre_telech msg1[0],
                msg_livre_telech msg1[1],
                bn msg_livre_telech msg1[2] + msg_final_appui_sur_entree_download,
                bn msg_livre_telech msg1[3] + msg_final_appui_sur_entree_download
                ]

    def final_lire(lvrpisteseek):
        """ lance le livre en cours, ou son téléchargement
            renvoie une chaîne du type :
            - "lecture du livre, ..."
            - "téléchargement programmé ..."
        """
        # infos
        arr_resume_livre final_resume_livre()
        # "777, 51 minutes, Dix années d'exil, de staël De"
        bbll bbl_langue()
        if ( (bbl_actu() == cfg.bbl_CLE_USB_ANARCHIEor
             (media_present(lvr) == "présent_usb") ):
            # clé usb ou media présent : on lit
            infos.m_g("final_lire, média présent pour " lvr["n"])
            arr_lecture lancer_lecture(lvrpisteseek)
            # au lieu de mémoriser le livre quand on quitte le module,
            # on le mémorise dès que la lecture commence
            save_num4c_actuel(lvr["n"])

            if bbll == cfg.LG_FRANCAIS:
                voc_lecture1 "lecture "
                voc_lecture2 "lecture du livre "
            elif bbll == cfg.LG_ANGLAIS:
                voc_lecture1 "book reading "
                voc_lecture2 "book is being read "
            elif bbll == cfg.LG_ALLEMAND:
                voc_lecture1 "Buchlesung "
                voc_lecture2 "wir lesen das Buch "
            arr_prononcer3 = [
                "",
                voc_lecture1 ", " arr_resume_livre[1] + ",, " arr_lecture[1],
                voc_lecture2 ", " arr_resume_livre[2] + ",, " arr_lecture[2],
                voc_lecture2 ", " arr_resume_livre[3] + ",, " arr_lecture[3]
                ]

        else:
            # media absent : on programme le téléchargement
            # est-ce qu'on mémorise le livre même si pas téléchargé ?
            # - oui , pour l'instant
            save_num4c_actuel(lvr["n"])
            b_espaces_espace commun.espace("suffisant")
            if b_espace == False:
                arr_prononcer3 message_espace_insuffisant(s_espace)
            else:
                enqueue_download(bbl_actu(), lvr)
                if bbll == cfg.LG_FRANCAIS:
                    voc_dl1 "téléchargement programmé "
                    voc_dl2 "téléchargement en arrière-plan du livre "
                elif bbll == cfg.LG_ANGLAIS:
                    voc_dl1 "download added to the queue "
                    voc_dl2 "background download for book "
                elif bbll == cfg.LG_ALLEMAND:
                    voc_dl1 "Herunterladung programmiert "
                    voc_dl2 "Herunterladung in Hintergrund programmiert "

                arr_prononcer3 = [
                    voc_dl1,
                    voc_dl1,
                    voc_dl2 arr_resume_livre[2],
                    voc_dl2 arr_resume_livre[3]
                    ]

        return arr_prononcer3

    def final_resume_livre():
        """ retourne une chaîne du type :
            - "La Maison à vapeur, de Jules VERNE"
            - "404, 14 heures , La Maison à vapeur, de Jules VERNE, lu par Orangeno"
        """
        # définir variables
        zero_off(lv_num4c())
        lv_titre()
        lv_auteur()
        lv_lecteur()
        lv_genre()

        t.replace("_"" "#     Raymond_Devos_-_Je_zappe_1992
                                # --> Raymond Devos - Je zappe 1992

        a.replace("_"" ")
        lu_par_lecteur ", Lu par " l
        bbll bbl_langue()
        if bbll == cfg.LG_ANGLAIS:
            if l.strip() == "":
                lu_par_lecteur ""
            else:
                lu_par_lecteur ", the reader is " l
        elif bbll == cfg.LG_ALLEMAND:
            if l.strip() == "":
                lu_par_lecteur ""
            else:
                lu_par_lecteur ", der Vorleser ist " l
        else:
            if l.strip() == "":
                lu_par_lecteur ""
            else:
                lu_par_lecteur ", lu par " l

         # on inverse les termes
         # ex : Allais Alphonse -> Alphonse Allais
        # ~ if a.find(" ") > 0:
            # ~ lva = a.split(" ", 2)
            # ~ a = lva[1] + " " + lva[0]
        duree_conviviale(lv_duree())
        # 457, 19 minutes, Ventre Saint-Gris, de  Brisay
        # 458, 16 minutes, La Dame de Bayard, de henry Brisay
        # 777, 51 minutes, Dix années d'exil, de staël De
        # 1536, 10 minutes, Comment on se venge, de Richard Lesclide
        # 2015, 28 minutes, Révélation magnétique, de Edgar Poe
        if bbll == cfg.LG_FRANCAIS:
            if a.strip() == "":
                ecrit_par ""
            else:
                ecrit_par ", de " a
            return [
                ", de " a,
                ", de " a,
                ", " ", " ", " ecrit_par lu_par_lecteur,
                ", " ", " ", " ecrit_par lu_par_lecteur
                ]
        elif bbll == cfg.LG_ANGLAIS:
            if a.strip() == "":
                ecrit_par ""
            else:
                ecrit_par ", author " a
            return [
                ", written by " a,
                ", written by " a,
                ", " ", " ecrit_par lu_par_lecteur,
                ", " ", " ecrit_par lu_par_lecteur
                ]
        elif bbll == cfg.LG_ALLEMAND:
            if a.strip() == "":
                ecrit_par ""
            else:
                ecrit_par ", der Autor ist " a
            return [
                ", geschrieben von " a,
                ", geschrieben von " a,
                ", " ", " ecrit_par lu_par_lecteur,
                ", " ", " ecrit_par lu_par_lecteur
                ]

    def horodatage_convivial(mn_sec):
        """ convertit horodatage en langage parlé """
        # ex :  0:05 devient  0 minute   5
        #      14:32 devient 14 minutes 32
        temps mn_sec.split(":")
        if len(temps) == 2:
            return "{} minutes {}".format(temps[0], temps[1])
        else:
            return ""

    def lancer_lecture(lvr_lecturepiste_lectureseek_lecture):
        """ lit un fichier mp3 ou un dossier de mp3
            renvoie liste chaînes du type :
            - "piste 3 sur 8"
            - "piste 3 sur 8, à 3 minutes 12"
        """
        if bbl_actu() == cfg.bbl_CLE_USB_ANARCHIE:
            # jouer la clé usb : ajouter à mpd chaque fichier mp3
            num_url 0
            for url in lvr_lecture["u"]:
                if num_url == 0:
                    infos.m_g("USB {}: mpc add 'file://{}'".format(num_urlurl))
                # IMPORTANT, mpd accepte les chemins absolus, comme :
                # mpc add "/m/music/type3/Z/Stefan Zweig/Partie 1"
                mpc.add("file://" url)
                # LDM = lecture des médias
                # documentation en cours d'écriture
                # cf. http://gangand.net/pp/projets/xavbox/adm.php#aba_lecture
                infos.m_g("LDM_mpc.add(\"file://" url "\")")
                num_url += 1
            infos.m_g("USB : ...")
            infos.m_g("USB {} : mpc add {}".format(num_urlurl))
        else:
            # ajouter le dossier, de fichier(s) mp3, du livre demandé
            bib_dir bbl_sub_folder()
            audio_files_folder bib_dir "/" lvr_lecture["n"]
            # TODO : voir si bien placé
            # il vaut mieux faire un update quand on crée un dossier
            # et qu'on y copie ou dézippe des nouveaux mp3
            mpc.update_wait(audio_files_folder)
            # ajouter et jouer fichier à position enregistrée
            mpc.add(audio_files_folder)
            infos.m_g("LDM_mpc.add(\"" audio_files_folder "\")")

        # lancer la lecture de la playliste du livre
        mpc.ferme_ton_clapet()
        mpc.play(piste_lecture)
        mpc.seek(seek_lecture)
        infos.m_g("inconnu ? => " mpc.sub_proc("mpc -f %file% | head -1"))

        # si une seule piste dans playlist, ne pas annoncer le n° de piste
        # ~ if int(mpc.playlist_longueur()) > 1:
        bbll bbl_langue()
        if bbll == cfg.LG_FRANCAIS:
            pistesur_totalat_time "piste "" sur "" à "
        elif bbll == cfg.LG_ANGLAIS:
            pistesur_totalat_time "track "" from "" at "
        elif bbll == cfg.LG_ALLEMAND:
            pistesur_totalat_time "Stück "" von "" an "
        msg_start piste piste_lecture sur_total mpc.playlist_longueur()

        if seek_lecture in """00:00:00"]:
            msg msg_start
        else:
            msg msg_start ", " at_time horodatage_convivial(seek_lecture)

        arr_prononcer2 =  [
            msg_start,
            msg_start,
            msg_start,
            msg_start
            ]
        return arr_prononcer2

    def last_pos(pos_lvr):
        """ position dans le morceau
            renvoie un tuple :
            position (numéro de piste), seek (02:38), ...
        """
        # "non lu" signifie "pas de mémorisation trouvée pour ce livre"
        piste1seek1seek1_readhoro1 "1""00:00:00""non lu"""

        mem_file bbl_pos_livres()
        if commun.file_exists(mem_file):
            with open(mem_file"r"as f:
                livres_commences f.readlines()
                for ligne in livres_commences:
                    ligne ligne.strip()
                    if ligne.find(pos_lvr["n"] + "|") == 0:
                        ligne.split("|")
                        piste1seek1horo1 t[1], t[2], t[3]
                        liste_str = [ str(ifor in range(11000) ]
                        if not piste1 in liste_str:
                            # pour éviter les valeurs du type::
                            # volume: 79%   repeat: off   random: off   single: off   consume: off
                            infos.log("ERR piste_memo BAD : " piste1)
                            piste1 "1"
                        seek1_read seek1 "/"
                        break
        infos.log("piste1, seek1, seek1_read, horo = {}, {}, {}, {}".format(piste1seek1seek1_readhoro1))
        # ex : 00:00:00, non lu
        #          0:10, 0:10/
        #          0:05, 0:05/
        return piste1seek1seek1_read

    def visu_nfo_livre(lv1nfo_seek_read):
        """ ligne infos livre
            renvoie une chaîne du type :
            livre  749 n=0749 mp3   0:14/ 0:1:00   1.2Mo DJ Rebel   Conversation dune pe
        """
        # TOUDOU_optionnel : indiquer par une lettre P = présent, A = absent (ou . = absent)
        #                    si le media est là
        #                    --> petit bip sonore genre pouce levé, pour dire que le media est là
        lv1u lv1["u"][0]
        tu lv1["type_url"]
        seek1 nfo_seek_read.rjust(7)

        lv1n zero_off(get_num4c_actuel()).rjust(4)
        lv1d lv1["d"].rjust(5)
        lv1p lv1["p"].rjust(5) + "Mo"
        lv1l lv1["l"][:9].ljust(10)
        lv1t = (lv1["t"] + "." 20)[0:20]
        lv1urls lv1u[:20] + " ... " lv1u[-20:]
        msg "VISU livre " " ".join((lv1ntuseek1lv1dlv1plv1llv1t))
        infos.g_m(msg)
        p0 "livre " lv1["n"] + ", " lv1["t"] + " de " lv1["a"]
        p1 "livre " lv1["n"] + ", " lv1["t"] + " de " lv1["a"]
        p2 "livre " lv1["n"] + ", " lv1["t"] + " de " lv1["a"]
        p3 "livre " lv1["n"] + ", " lv1["t"] + " de " lv1["a"]
        arr_prononcer1 = [ p0p1p2p3 ]
        return arr_prononcer1

    ####################################################################
    #                        lire_livre_actuel()                       #
    ####################################################################
    # la lecture est lancée si user appuie sur Entrée (entree_lire() ),
    # quand il entend le livre actu (Entrée fixe la variable action à "lire")
    # mpc.ferme_ton_clapet()          # coupe les messages vocaux en cours et à venir
    mpc.clear()                     # stoppe lecture actuelle
    mpc.random_off()                # lire un livre dans l'ordre
    lvr_actu lv_livre_actuel()    # définit le livre actu

    # cf. doc_01 et fichier lire_livre_actuel.odt sur /3en1
    pisteseekseek_read last_pos(lvr_actu)             # positionne dans le morceau, MUET
    visu_nfo_livre(lvr_actuseek_read)   # infos visuelles livre     , MUET
    global ouverture_biblio
    prendre_parole True
    if direkt == "direkt":
        prendre_parole False
        arr_prononcer final_lire(lvr_actupisteseek)
    elif action == "lire":
        if ouverture_biblio == True:
            arr_prononcer final_annoncer(seek_read)
        else:
            # prendre_parole = False
            arr_prononcer final_lire(lvr_actupisteseek)
            # ne rien prononcer quand on demande la lecture d'un livre
            # pour des infos, il y a les touches /, //, /*
            # le user a déjà entendu les infos du livre
    elif action == "annoncer":
        arr_prononcer final_annoncer(seek_read)
    else:
        arr_prononcer "erreur, l'action n'est ni lire, ni annoncer "

    if prendre_parole == True:
        infos.pdrNew(arr_prononcer)
    else:
        infos.m_g("PRENDRE PAROLE FALSE 2")
    # if prendre_parole == True:
        # if direkt == '':
            # infos.pdrNew(arr_prononcer, bbl_langue())

    # dès qu'un livre est lu ou annoncé,
    # la bibliothèque est au-delà du premier livre
    ouverture_biblio False


def loglevel():
    """ retourne le niveau du loglevel """
    return int(infos.ini_get("loglevel""0"))


def lv_data(champidx_ze None):
    """ champ du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
    """
    global sel_lvr
    if len(sel_lvr) == 0:
        # attention : si global sel_lvr est vide,
        # sel_lvr[idx_ze] renvoie un IndexError
        return "sel_lvr est vide"

    if idx_ze is None:
        idx_ze ze__indice_actuel()
    return sel_lvr[idx_ze][champ]


def lv_livre_actuel():
    """ renvoie le livre actuel """
    global sel_lvr
    # ~ print("ze__indice_actuel()=" + str(ze__indice_actuel()))
    return sel_lvr[ze__indice_actuel()]


def lv_auteur(idx_ze None):
    """ auteur du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "a" signifie poids
    """
    return lv_data("a"idx_ze)


def lv_duree(idx_ze None):
    """ durée du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "d" signifie poids
    """
    return lv_data("d"idx_ze)


def lv_fiche(idx_ze None):
    """ url fiche info html du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "f" signifie fiche
    """
    return lv_data("f"idx_ze)


def lv_genre(idx_ze None):
    """ genre du livre (roman, conte, ...)
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "y" signifie genre
    """
    return lv_data("y"idx_ze)


def lv_lecteur(idx_ze None):
    """ lecteur(s) ou lectrice(s) du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "l" signifie lecteur
    """
    return lv_data("l"idx_ze)


def lv_poids(idx_ze None):
    """ poids du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "p" signifie poids
    """
    return lv_data("p"idx_ze)


def lv_num4c(idx_ze None):
    """ num4C (numéro à 4 chiffres) du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "n" signifie numl4c
    """
    return lv_data("n"idx_ze)


def lv_titre(idx_ze None):
    """ titre du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "n" signifie numl4c
    """
    return lv_data("t"idx_ze)


def lv_type_url(idx_ze None):
    """ type d'url du livre
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        sera peut-être abandonné certainement
    """
    return lv_data("type_url"idx_ze)


def lv_url(idx_ze None):
    """ liste d'urls media du livre (éléments séparés par le signe '+'
        sel_lvr[ idx_ze ]          ou
        sel_lvr[ ze__indice_actuel() ]
        "u" signifie url
    """
    return lv_data("u"idx_ze)


def media_present(lvr):
    """ teste présence mp3 seul, ou d'un dossier zip
        renvoie une des 4 valeurs suivantes :
            "présent_usb",
            "absent_usb",
            "dépôt_local_présent"
            "dépôt_local_absent"
            "taille_était_nulle"

        fonctions utilisées dans deux autres fonctions seulement :
        rand10lvr()
        lire_livre_actuel.final_annoncer()

        TODO : un livre présent sur un dépôt local (/media/fbx/rouge)
        doit-il être considéré comme "présent" ou "dépôt_local"
    """
    bib_dir bbl_folder()
    # présence sur clé usb
    if commun.dir_exists(bib_dir "/" lvr["n"]):
        # # vérifier si fichier .mp3 de taille nulle
        cmd "du -sc {}/{}".format(bib_dirlvr["n"]) + \
            "/*mp3 2>/dev/null | tail -1 | awk '{print $1}'"
        taille_mp3 mpc.sub_proc(cmd)
        if taille_mp3 == "0":
            msg "Ce livre a rencontré un problème " \
                "de téléchargement, veuillez réessayer plus tard"
            infos.pdrNew(msg)
            mask_cmd "rm -rf {}/{}"
            cmd mask_cmd.format(bib_dirlvr["n"])
            # # suppression du dossier num4c
            mpc.sub_proc(cmd)
            return "taille_était_nulle"
        return "présent_usb"
    # présence dépôt local (nas freebox)
    elif commun.depot_local_present("aba"):
        if commun.dir_exists("/media/fbx/aba/" bbl_actu() + "/" lvr["n"]):
            return "dépôt_local_présent"
        else:
            return "dépôt_local_absent"
    # média absent partout : sur clé usb ET sur dépôt local
    else:
        infos.log("debug Dossier media absent " bib_dir "/" lvr["n"])
        return "absent_usb"


def memo_pos_livre_aba():
    """ mémorise le dernier moment lu du livre actuel (= livre quitté)
        càd position dans playlist et progression
    """

    def memo_pos_livres_maj(fichierecrire_ligne):
        """ mémorise le dernier moment de lecture
            d'un livre désigné par son num4c
            ex : memo_pos_livres_maj("/m/memo/aba/memo_pos_livres_litteratureaudio.txt", "0038|1|0:41|2020-02-20 07:43:49")
        """
        # attention : penser au cas où on a lu tout le livre
        le_numero ecrire_ligne.split("|")[0]

        infos.m_g("DEBUG_MEMO " fichier " " ecrire_ligne)

        if commun.file_exists(fichier):
            infos.g_m("le fichier " fichier " existe")
            s_num le_numero "|"  # "0152|"
            with open(fichier"r"as f:
                lignes f.readlines()

            f2 open(fichier"w")
            ligne_trouvee False
            for ligne in lignes:
                ligne ligne.strip()
                if ligne.find(s_num) == 0:
                    # mettre à jour ce livre
                    ligne_trouvee True
                    f2.write(ecrire_ligne '\n')
                    infos.g_m("cas1 = " ecrire_ligne)
                    infos.g_m("cas1 : on mémorise en mettant à jour")
                    infos.g_m("memoPosLivre cas1 [MAJ] : >>> " ecrire_ligne " <<<")
                elif len(ligne) > 5:
                    f2.write(ligne '\n')
                    infos.g_m("cas2 = " ligne)
            if not ligne_trouvee:
                # première mémorisation pour ce livre
                f2.write(ecrire_ligne '\n')
                infos.g_m("cas3 = " ecrire_ligne)
                infos.g_m("cas3 : on mémorise ce livre pour la première fois")
                infos.g_m("memoPosLivre cas3 [NEW] : >>> " ecrire_ligne " <<<")
        else:
            # 1er livre mémorisé
            f2 open(fichier"w")
            f2.write(ecrire_ligne '\n')
            infos.g_m("cas4 = " ecrire_ligne)
            infos.g_m("cas4 : on mémorise un premier livre")
            infos.g_m("memoPosLivre cas4 [+FILE] : >>> " ecrire_ligne " <<<")

        f2.close()
        infos.g_m("voir fichier " fichier)

    # mémoriser position et progression du livre quitté
    if nb_livres_total() == 0:
        infos.m_g("on ne mémorise rien, car nb livres total = 0")
        return 0

    s_piste str(mpc.playlist_position())
    if s_piste.startswith("volume:"):
        infos.g_m("s_piste = " s_piste)
        infos.g_m("on ne mémorise rien, car s_piste.startswith volume")
    else:
        # on mémorise le num4c, la piste, la progression, l'horodatage
        # 0418|1|0:11|2020-02-26 00:20:04
        s_horo infos.jour_heure_actu()
        infos.m_g("debug1")
        print("debug nb livres total = {}".format(nb_livres_total()))
        s_num lv_num4c()  # 0152 (colonne "n=" dans pipe.txt)
        infos.m_g("debug2")
        s_prog mpc.status_progression()[0]

        memo_ligne "{}|{}|{}|{}".format(s_nums_pistes_progs_horo)
        memo_pos_livres_maj(bbl_pos_livres(), memo_ligne)


def memo_stop_module():
    """ quitter module (touche 0) : mémoriser où on est """
    infos.m_g("aba memo_stop_module")
    memo_pos_livre_aba()


def menu_changer_biblio():
    """ changer de bibliothèque par un menu vocal """
    explication \
    "choisissez une bibliothèque de 1 à {0}, " \
    "pressez ensuite Entrée pour la lancer, " \
    "ou bien zéro pour sortir de ce menu, " \
    "vous êtes actuellement dans la bibliothèque {}".format(bbl_nom())
    commun.menu_vocal("aba_bibliotheques""bibliothèque"explication)


def menu_suppr_lvr():
    """ menu Entrée suppression livre(s) """
    msg "Appuyer sur 1 puis Entrée pour supprimer le livre présent"
    infos.pdrNew(msg)
    msg "ou bien sur 2 pour supprimer tous les livres présents"
    infos.pdrNew(msg)
    choix input()
    if choix == "1":
        supprimer_livre()   # on ne vérifie pas que le livre est présent
                            # cela permet de forcer la suppression du dossier
                            # s'il existe
    elif choix == "2":
        supprimer_livres()
    else:
        infos.pdrNew("Suppression annulée")


def menu_voc11():
    """ menu spécial aba """
    commun.menu_vocal("aba_mnu11""audiolivres")


def message_espace_insuffisant(s_espace):
    """ message générique aba d'espace insuffisant
        PAs de prononciation ici,
        renvoi d'un tableau de messages pour pdrNew
    """
    mega_octets_occupes commun.du(cfg.M_MUSIC "/aba")
    bbll bbl_langue()
    if bbll == cfg.LG_FRANCAIS:
        voc_dl2 "vous ne pouvez pas télécharger " \
            "de livre audio, " s_espace ", " \
            "essayez de faire de la place " \
            "en sélectionnant les livres présents avec les touches 111, " \
            "puis en les effaçant."
        # autre message possible :
        # espace restant insuffisant pour télécharger,
        # veuillez libérer d'abord de l'espace.
        # Pour cela, placez-vous sur un livre présent,
        # puis effacez-le avec 987, ou bien 11 pour
        # ouvrir le menu vocal, puis la touche 9,
        # pour supprimer plusieurs livres d'un coup.
        voc_dl1 voc_dl2
    elif bbll == cfg.LG_ANGLAIS:
        voc_dl1 "download not possible because space is missing "
        voc_dl2 "download not possible because space is missing "
    elif bbll == cfg.LG_ALLEMAND:
        voc_dl1 "Herunterladung unmöglich, Raum zu kurz "
        voc_dl2 "Herunterladung unmöglich, Raum zu kurz "
    # pas de téléchargement, ne pas annoncer le résumé du livre
    arr_msg_insuff = [
        voc_dl1,
        voc_dl1,
        voc_dl2,
        voc_dl2
        ]
    return arr_msg_insuff


def nb_livres_favoris():
    """ renvoie le nombre de livres consignés en favoris
        IMPORTANT : un favori peut être présent ou absent
    """
    num4c_trouves infos.ini_get("aba_favoris_" bbl_actu(), "")
    if num4c_trouves == "":
        return 0
    else:
        return len(num4c_trouves.split(","))


def nb_livres_prezents(cle_biblio None):
    """ renvoie le nombre (int) de sous-dossiers
        dans dir_search
        ou, si argument vide,
        dans bbl_folder() --> /m/music/aba/<folder>

        TODO : améliorer en vérifiant si dossiers de taille nulle existent
        TODO IMPORTANT : pas valable pour usb anarchie
    """
    if cle_biblio is None:
        cle_biblio bbl_actu()

    # cas particulier : usb anarchie
    if cle_biblio == cfg.bbl_CLE_USB_ANARCHIE:
        return nb_livres_total()

    # les autres cas
    dir_search bbl_folder(cle_biblio)
    if commun.dir_exists(dir_search):
        cmd "ls -1 {} | wc -l".format(dir_search)
        nb_dossiers int(mpc.sub_proc(cmd))
        return nb_dossiers
    else:
        return 0


def nb_livres_total():
    """ nombre de livres (lignes) da la sélection,
        càd du tableau global sel_lvr[]
    """
    global sel_lvr
    return len(sel_lvr)


def next_trk():
    """ aller au chapitre suivant """
    # la touche 6 a deux fonctions dans le module aba
    mpc.next_trk()
    infos.m_g("piste {}".format(mpc.playlist_position()))


def prev_trk():
    """ aller au chapitre précédent """
    mpc.prev_trk()
    infos.m_g("piste {}".format(mpc.playlist_position()))


def rechercher():
    """ rechercher un mot ou une partie de mots
        dans la colonne auteur, lecteur ou titre
    """
    def aide_alphabet():
        """ récite les codes de chaque lettre
        """
        # num2lettre = {
        #  "1":"a",  "2":"b",  "3":"c",  "4":"d",  "5":"e",
        #  "6":"f",  "7":"g",  "8":"h",  "9":"i", "10":"j",
        # "11":"k", "12":"l", "13":"m", "14":"n", "15":"o",
        # "16":"p", "17":"q", "18":"r", "19":"s", "20":"t",
        # "21":"u", "22":"v", "23":"w", "24":"x", "25":"y",
        # "26":"z" }
        # nombres = num2lettre.keys()

        # utiliser num2lettre et num2lettre.keys()
        pass


    mpc.ferme_ton_clapet()
    msg """ Appuyez sur 1, 2 ou 3, puis Entrée pour rechercher
    respectivement dans les auteurs, lecteurs et titres des livres
    """
    infos.pdrNew(msg)
    msg """ Appuyez sur 4 puis Entrée pour rechercher
    sur les trois colonnes, ou 0 puis Entrée pour annuler
    """
    infos.pdrNew(msg)

    # 1. demander le champ de recherche
    champ input()

    # 2. demander le mot de recherche
    mpc.ferme_ton_clapet()
    num2lettre = {
         "1":"a",  "2":"b",  "3":"c",  "4":"d",  "5":"e",
         "6":"f",  "7":"g",  "8":"h",  "9":"i""10":"j",
        "11":"k""12":"l""13":"m""14":"n""15":"o",
        "16":"p""17":"q""18":"r""19":"s""20":"t",
        "21":"u""22":"v""23":"w""24":"x""25":"y",
        "26":"z" }
    nombres num2lettre.keys()
    mot ""
    dans_quoi =  {
         "1":"auteurs",
         "2":"lecteurs",
         "3":"titres" }
    msg """ Pour définir le mot à rechercher,
        tapez 1 puis Entrée pour la lettre, a,
        10 puis Entrée pour la lettre, j,
        20 puis Entrée pour la lettre, t,
        puis Entrée tout court pour lancer la recherche
    """
    infos.pdrNew(msg)

    if champ in dans_quoi.keys():
        while True:
            carac input()
            mpc.ferme_ton_clapet()
            if carac == "":
                break
            if carac in nombres:
                # transformer 1 en a, 2 en b, 3 en c, ..., 25 en y, 26 en z
                lettre num2lettre[carac]
                mot mot lettre
                infos.pdrNew("lettre " lettre)
            else:
                infos.pdrNew("mauvais nombre, désolé")
        # recherche sur le mot
        champ_dmd dans_quoi[champ]
        infos.m_g("recherche du mot '{}' dans les {}".format(motchamp_dmd))

    # 3. lancer la recherche sur le champ demandé
    dans_champ =  {
         "1":"a",
         "2":"l",
         "3":"t" }
    champ_alt dans_champ[champ]
    occurences = {}
    # initialiser occurences{}
    if mot != "":
        for lvr in all_lvr:
            if mot.lower() in lvr[champ_alt].lower():
                occurences[lvr[champ_alt]] = 0
    0
    t_recherche = []
    if mot != "":
        for lvr in all_lvr:
            if mot.lower() in lvr[champ_alt].lower():
                += 1
                t_recherche.append(lvr["n"])
                occurences[lvr[champ_alt]] += 1
    nb_corresp_mot len(t_recherche)
    if nb_corresp_mot 0:
        mask "{} livres trouvés pour le mot {}"
        infos.pdrNew(mask.format(nb_corresp_motmot))

        mask "{} {} différents trouvés"
        infos.m_g(mask.format(len(occurences), champ_dmd))

        num4c_trouves ",".join(t_recherche)
        slct_get_build(num4c_trouves)
    else:
        infos.pdrNew("Aucun livre trouvé pour le mot demandé")
        num4c_trouves ""


def reprendre_direct():
    """ reprendre directement le dernier audiolivre
    """
    msg "reprendre directement le dernier audiolivre"
    infos.g_m(msg)
    reprendre("direkt")


def reprendre(direkt ''):
    """ lit dernier livre lu en quittant module """
    # ici, on définit simplement le numéro du livre à lire,
    # puis on lance lire_livre_actuel()

    def dev_afficher_numeros_utiles():
        """ affiche quelques nuémros de livres utiles de tests
        """
        msg "numéros de livres intéressants"
        infos.m_g(msg)
        numeros_audio_cite " courts : 642, 748, 823, zip : 762, 763, mp3 : 869"
        numeros_litteaudio " courts :  28, 47, 70, 71, 304, 305, 306"
        numeros_librivox_fr " 4"
        numeros_divers_src "4, 5, 6"
        msg "biblio audiocité   : " numeros_audio_cite
        msg "biblio littérature : " numeros_litteaudio
        msg "biblio librivox_fr : " numeros_librivox_fr
        msg "biblio divers      : " numeros_divers_src
        infos.m_g(msg)

    def init_reprendre(direkt ""):
        """ initialisation variables selon biblio actuelle """
        mpc.ferme_ton_clapet()
        if direkt == 'pas_lancement des audiolivres':
            pass
        elif direkt == '':
            msg "audiolivres"
            infos.g_m("DBG lancement des audiolivres")
            # infos.pdrNew(["", msg, msg, msg])
            # commun.faire_pause(3, True)    # pause de 2s

        infos.ini_set("py_current_module""aba")
        infos.ini_set("mode_consultation""consulter")
        bib_folder bbl_folder()
        # dossier /m/music/aba/perso_zip, /m/music/aba/litteratureaudio, ...
        cmd "[ -d " bib_folder " ] || mkdir -p " bib_folder
        retour_cmd mpc.sub_proc(cmd)

    def cnt_numeroter(quoiliste1nydplatufx None):
        """ renvoie la liste fournie, numérotée """
        # cnt = Créer Nouvelles Tables
        infos.m_g("===== " quoi " =====")
        1000
        liste2 = []
        dico = {}
        compter = {}
        for nom in liste1:
            += 1
            num3c str(n)[-3:]
            num_nom num3c ":" nom   # 001:abbé Ricard
            liste2.append(num_nom)
            dico[nom] = num3c
            compter[nom] = 0
            if 1004:
                infos.m_g(num_nom)
        infos.m_g("...")
        infos.m_g(num_nom)   # le dernier élément
        infos.m_g("")   # ligne vide

        if not nydplatufx is None:
            global all_lvr
            for in all_lvr:
                compter[L[nydplatufx]] += 1   # compter["Daniel Luttringer"] += 1
            return liste2dicocompter
        else:
            return liste2dico

    def cnt_pgcd(list_urls):
        """ renvoie la partie commune la plus longue
            pour tous les éléments
        """
        longueur_maxi 1000
        # longueur la plus courte du tableau
        for in list_urls:
            # ~ print(len(i))
            if len(i) < longueur_maxi:
                # ~ print(i)
                longueur_maxi len(i)
        # ~ m_g("longueur_maxi = " + str(longueur_maxi))
        # la chaîne commune n'excédera pas la longueur longueur_maxi
        use_previous_modele False
        debut_commun_a_tous ""
        for in range(longueur_maxi):
            modele list_urls[0][:i]
            # ~ print("modele1 = " + modele)
            for in list_urls:
                # ~ print("e[:i]  = " + e[:i])
                # ~ print("modele = " + modele)
                # ~ print()
                if e[:i] != modele:
                    # ce modèle ne marche plus
                    use_previous_modele True
                    break
            if use_previous_modele == True:
                debut_commun_a_tous modele_precedent
                break
            else:
                modele_precedent modele
        return debut_commun_a_tous

    def cnt_raccourcir_tab_url(arr_urlsclef):
        """ remplir tableau html
        """
        len_url len(arr_urls)
        if len_url == 0:
            # ~ return "&nbsp;"
            return "     <td align=center><small>" "&nbsp;" "</small></td>" '\n'

        # LE_MP3 sont habituellement unique,
        # parfois, il y a plusieurs liens
        if (clef == "LE_MP3"and (len_url 1):
            return "     <td align=center style='background: lightgreen;'><small>" str(len_url) + "</small></td>"
        else:
            return "     <td align=center><small>" str(len_url) + "</small></td>"

        return str(len_url)
        # ~ elif len_url == 1:
            # ~ return arr_urls[0]

        pgcd1 pgcd(arr_urls)
        arr2 = [ i.replace(pgcd1"[PC]"for in arr_urls ]
        return str(len_url)

    def creer_nouvelles_tables(biblioall_lvrlist_auteurslist_genreslist_lecteurslist_urllist_dureelist_poidslist_titre):
        """ créer trois tables auteurs, genres, lecteurs,
            et un nouveau catalogue aminci
            soit au total 4 nouveaux fichiers :
            tbl_catalog.txt, tbl_auteurs.txt, tbl_genres.txt, tbl_lecteurs.txt

            les 3 listes suivantes sont non triées, mais avec des noms d'auteurs uniques :
            list_auteurs, list_genres, list_lecteurs

            gain de place dans la base de données trop faible pour être intéressant
            par contre, permet de repérer/corriger les données rapidement
        """
        chemin cfg.BIB_DISPOS[biblio]["catalogue"]
        save_list_to_file(chemin "/tbl_auteurs_bruts.txt"list_auteurs)
        save_list_to_file(chemin "/tbl_lecteurs_bruts.txt"list_lecteurs)
        save_list_to_file(chemin "/tbl_genres_bruts.txt"list_genres)

        save_list_to_file(chemin "/tbl_url_bruts.txt"list_url)
        save_list_to_file(chemin "/tbl_duree_bruts.txt"list_duree)
        save_list_to_file(chemin "/tbl_poids_bruts.txt"list_poids)
        save_list_to_file(chemin "/tbl_titre_bruts.txt"list_titre)

        # numéroter les listes
        list_auteurs_num dico_auteurscompte_auteurs  cnt_numeroter("auteurs" list_auteurs"a")
        list_genres_num  dico_genrescompte_genres   cnt_numeroter("genres"  list_genres"y")
        list_lecteurs_numdico_lecteurscompte_lecteurs cnt_numeroter("lecteurs"list_lecteurs"l")

        list_url_num  dico_url   cnt_numeroter("url"  list_url)
        list_duree_numdico_duree cnt_numeroter("duree"list_duree)
        list_poids_numdico_poids cnt_numeroter("poids"list_poids)
        list_titre_numdico_titre cnt_numeroter("titre"list_titre)

        # enregistrer les listes numérotées sur fichier
        save_list_to_file(chemin "/tbl_auteurs.txt" list_auteurscompte_auteurs)
        save_list_to_file(chemin "/tbl_lecteurs.txt"list_lecteurscompte_lecteurs)
        save_list_to_file(chemin "/tbl_genres.txt"  list_genrescompte_genres)

        infos.m_g("Fichiers tbl créés.")

        # recréer catalog.txt --> tbl_catalog.txt
        # avec les colonnes a=, l=, y= (auteur, lecteur, genre)
        # comportant uniquement des numéros

        # 1. les dictionnaires :
        # dico_auteurs  : nomAuteur  --> numAuteur
        # dico_genres   : nomGenre   --> numGenre
        # dico_lecteurs : nomLecteur --> numLecteur

        new_catalogue = []
        for in all_lvr:
            # nydplatufx
            nomgenre_1dureepoidslecteur_1 L["n"], L["y"], L["d"], L["p"], L["l"]
            auteur_1titremedia_url1ficheextrait L["a"], L["t"], L["u"], L["f"], L["x"]

            # attention : media_url1 est une variable list
            auteur_2  dico_auteurs[auteur_1]
            genre_2   dico_genres[genre_1]
            lecteur_2 dico_lecteurs[lecteur_1]
            media_url2 "+".join(media_url1)     # il faudra raccourci avec radical commun (pgcd)

            masque "n={}|y={}|d={}|p={}|l={}|a={}|t={}|u={}|f={}|x={}"
            # ~ masque = "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}"

            # on utilise les index et les autres tables
            ligne_new masque.format(nomgenre_2dureepoidslecteur_2auteur_2titremedia_url2ficheextrait)
            # on n'utilise pas d'index
            ligne_new masque.format(nomgenre_1dureepoidslecteur_1auteur_1titremedia_url2ficheextrait)

            new_catalogue.append(ligne_new)
        save_list_to_file(chemin "/tbl_CATALOG.txt"new_catalogue)
        # source : catalog.txt
        # ~ n=0001|y=nouvelles        |d=    15|p=   14|l=Plumedencr          |a=morissette Joseph ferdinand        |t=Lucien et Marie-Louise                                      |u=https://archive.org/download/LucienEtMarieLouise/Lucien%20et%20Marie-Louise.mp3                |f=nouvelles/joseph-ferdinand--morissette-lucien-et-marie-louise.html
        # ~ n=0002|y=religions        |d= 01h25|p=   80|l=Léa                 |a=Abbé regnault P.a.                |t=Chemin de croix médité                                    |u=https://archive.org/compress/chemindecroixmedite/formats=VBR%20MP3&file=/chemindecroixmedite.zip                        |f=religions/p.a.-abbe-regnault-chemin-de-croix-medite.html
        # ~ n=0003|y=contes           |d=    02|p= 1,75|l=Sabine              |a=Abbé ricouard                     |t=Contes et légendes de la Seine-Maritime. La bal des fées  |u=https://www.archive.org/download/LeBalDesFees/Le_bal_des_fees.mp3                              |f=contes/abbe-ricouard-contes-et-legendes-de-la-seine-maritime--la-bal-des-fees.html

        # des    : tbl_catalog.txt
        # ~ n=0001|y=001|d=    15|p=   14|l=001|a=001|t=Lucien et Marie-Louise                                      |u=https://archive.org/download/LucienEtMarieLouise/Lucien%20et%20Marie-Louise.mp3                |f=nouvelles/joseph-ferdinand--morissette-lucien-et-marie-louise.html
        # ~ n=0002|y=002|d= 01h25|p=   80|l=002|a=002|t=Chemin de croix médité                                    |u=https://archive.org/compress/chemindecroixmedite/formats=VBR%20MP3&file=/chemindecroixmedite.zip                        |f=religions/p.a.-abbe-regnault-chemin-de-croix-medite.html
        # ~ n=0003|y=003|d=    02|p= 1,75|l=003|a=003|t=Contes et légendes de la Seine-Maritime. La bal des fées  |u=https://www.archive.org/download/LeBalDesFees/Le_bal_des_fees.mp3                              |f=contes/abbe-ricouard-contes-et-legendes-de-la-seine-maritime--la-bal-des-fees.html
        pass

    def save_list_to_file(fichierlist_quoidico_compte None):
        """
        """
        if dico_compte is None:

            list_quoi.sort()
            liste_triee "\n".join(list_quoi)
            with open(fichier"w"as f:
                corps "# fichier créé par aba.py\n" liste_triee
                f.write(corps)
        else:
            # comptabiliser pour chaque élément
            liste_triee list(list_quoi)
            with open(fichier"w"as f:
                for in list_quoi:
                    dico_compte[i]
                    # print(i + ":" + str(d), file=f)
                    f.write(":" str(d))
            f.close()

    def usb_dossiers_audio(src_dir):
        """ renvoie une liste de dossiers contenant des fichiers audio,
            sauf ceux commençant par /m/music,
            càd avec extensions mp3, m4a, ogg, wav, wma
            utile pour lire des audiolivres copiés de façon anarchique
            sur une clé usb

            output : list_audio_folders[]
        """
        # TODO : vérifier que les fichiers audio ne sont pas de taille nulle
        if cfg.CLE_USB_BROWSE_M_MUSIC == True:
            # on prend toute la clé
            list_folders commun.folders_recurse(src_dir)
        else:
            # on prend tout sauf /m/music
            list_folders_all commun.folders_recurse(src_dir)
            list_folders = []
            for in list_folders_all:
                # supprimer les dossiers commençant par /m/music/
                # car ils sont prévus pour être lus par mpd/jk2019 en natif
                if d.startswith(cfg.M_MUSIC):
                    pass
                else:
                    list_folders.append(d)

        list_audio_folders = []
        for un_dossier in list_folders:
            liste_fichiers commun.files_simple(un_dossier)
            nb_fichiers len(liste_fichiers)
            if nb_fichiers 0:
                # analyser les fichiers du dossier "un_dossier"
                nb_audio 0
                for in liste_fichiers:
                    i_low i.lower()
                    if (
                      (i_low.endswith(".mp3")) or
                      (i_low.endswith(".m4a")) or
                      (i_low.endswith(".ogg")) or
                      (i_low.endswith(".wav")) or
                      (i_low.endswith(".wma"))
                      ):
                        nb_audio += 1
                if nb_audio 0:
                    if loglevel() > 0:
                        infos.m_g("{:>3s} fichiers, {:>3d} audio, {}".format(str(nb_fichiers), nb_audioun_dossier))
                    list_audio_folders.append(un_dossier)
        return list_audio_folders

    def usb_charger_livres():
        """ chercher les livres sur la clé usb,
            création de la list all_lvr[]
        """
        msg "usb_charger_livres() recherchent tous les fichiers audio" \
            "sur la clé usb, à l'exception de ceux qui sont dans " \
            "/m/music. Ne prend pas en compte les fichiers .zip"
        infos.g_m(msg)
        msg "La bibliothgèque 'lire les livres de la clé u s b'" \
            "permet de lire les fichiers copiés de façon anarchique" \
            "par une bonne âme."
        infos.g_m(msg)
        msg "Il y aura peut-être une possibilité de fairte du ménage" \
            "sur une clé trop remplie."
        infos.g_m(msg)

        # dossiers avec fichiers audio
        m_audio_folders usb_dossiers_audio("/m")
        if len(m_audio_folders) == 0:
            return False
        masque "Nb dossiers contenant au moins un fichier audio, et hors du dossier /m/music : {}"
        print(masque.format(len(m_audio_folders)))

        # créer le tableau all_lvr []
        global all_lvr
        all_lvr = []
        # 10 champs = nydplatufx
        # n=0001|y=Romans|d=13:25:00|p=732|l=Victoria|a=MIRBEAU, Octave|t=Le Journal d'une femme de chambre|u=http://www.litteratureaudio.net/mp3/Octave_Mirbeau_-_Le_Journal_d_une_femme_de_chambre.zip|f=mirbeau-octave-le-journal-dune-femme-de-chambre.html|x=http://www.litteratureaudio.net/mp3/Octave_Mirbeau_-_Le_Journal_d_une_femme_de_chambre_Chap01.mp3
        # n=0002|y=Romans|d=5:30:00|p=|l=Jean-Luc Fischer|a=H. P. Lovecraft|t=L'Affaire Charles Dexter Ward|u=http://www.litteratureaudio.org/mp3/H_P_Lovecraft_L_Affaire_CDW.zip|f=lovecraft-howard-phillips-laffaire-charles-dexter-ward.html|x=http://www.litteratureaudio.org/mp3/H_P_Lovecraft_L_Affaire_CDW_Ch_I_1.mp3
        # n=0003|y=Romans|d=28:30:00|p=|l=Cocotte|a=DUMAS, Alexandre|t=Les Trois Mousquetaires|u=http://www.litteratureaudio.org/mp3/Alexandre_Dumas_-_Les_trois_Mousquetaires_chap0-10.zip+http://www.litteratureaudio.org/mp3/Alexandre_Dumas_-_Les_trois_Mousquetaires_chap11-20.zip+http://www.litteratureaudio.org/mp3/Alexandre_Dumas_-_Les_trois_Mousquetaires_chap21-30.zip+http://www.litteratureaudio.org/mp3/Alexandre_Dumas_-_Les_trois_Mousquetaires_chap31-40.zip+http://www.litteratureaudio.org/mp3/Alexandre_Dumas_-_Les_trois_Mousquetaires_chap41-50.zip+http://www.litteratureaudio.org/mp3/Alexandre_Dumas_-_Les_trois_Mousquetaires_chap51-60.zip+http://www.litteratureaudio.org/mp3/Alexandre_Dumas_-_Les_trois_Mousquetaires_chap61-68.zip|f=dumas-alexandre-les-trois-mousquetaires.html|x=http://www.litteratureaudio.org/mp3/Alexandre_Dumas_-_Les_Trois_Mousquetaires_Chap00_Introduction.mp3
        # n=0004|y=Romans|d=6:43:00|p=182|l=Damien Genevois|a=VERNE, Jules|t=Le Tour du monde en 80 jours|u=http://www.litteratureaudio.net/mp3/Jules_Verne_-_Le_Tour_du_monde_en_80_jours.zip|f=jules-verne-le-tour-du-monde-en-80-jours.html|x=
        # n=0005|y=Romans|d=19:0:00|p=|l=Monique Vincens|a=PROUST, Marcel|t=Du côté de chez Swann|u=http://www.litteratureaudio.net/mp3/Marcel_Proust_-_Du_Cote_de_chez_Swann_L1_Combray_V2.zip+http://www.litteratureaudio.net/mp3/Marcel_Proust_-_Un_Amour_de_Swann.zip+http://www.litteratureaudio.net/mp3/Marcel_Proust_-_Du_Cote_de_chez_Swann_L3_Nom_de_pays_-_le_nom.zip|f=proust-marcel-du-cote-de-chez-swann.html|x=http://www.litteratureaudio.net/mp3/Marcel_Proust_-_Du_Cote_de_chez_Swann_L1_Combray_Introduction.mp3
        num 0
        for audio_folder in m_audio_folders:
            files_audio_path_usb commun.files_audio_simple(audio_folder)
            num += 1
            num4c zero_on(num)
            genre ""
            duree ""
            poids ""
            lecteur ""
            # ci-dessous, ok pour dossier
            # Pierre-Gilles de Gennes/Laser fermeture éclair
            # auteur = mpc.sub_proc('basename "$(dirname "{}")"'.format(audio_folder))
            auteur ""
            titre mpc.sub_proc('basename "{}"'.format(audio_folder))
            arr_url_media files_audio_path_usb
            fiche ""
            extrait ""
            un_livre = {
                "n"num4c    # numéro à 4 chiffres
                "y"genre    # type
                "d"duree    # durée
                "p"poids    # poids (string : "5.78", "12", ...)
                "l"lecteur  # lecteur
                "a"auteur   # auteur
                "t"titre    # titre
                "u"arr_url_media# url mp3 ou zip
                "f"fiche    # url fiche
                "x"extrait  # extrait
                "type_url""mp3",  # type url : mp3, zip
                # "type_url": "usb_mp3",  # type url : mp3, zip
            }
            all_lvr.append(un_livre)
            infos.m_g("usb, titre " str(num) + " : " str(titre))
        infos.m_g("usb, titres trouvés sur la clé (hors /m/music) : " str(num))
        # creer_html_all_lvr(all_lvr, "/m/cle_usb_all_lvr.html")
        return True

    def perso_zip_charger_livres():
        """ chercher les livres zip sur http://gangand.net/...
            créer la list all_lvr[]
        """
        # procéder pour cette bibliothèque comme pour litterature audio, librivox ...
        # en créant le tableau all_lvr[] adéquat
        cmd 'wget -q http://gangand.net/aa/audiolivres/perso_zip -O - | grep zip | sort | grep -o -E "href=\\"[^>]*"'
        liste_brute_1 mpc.sub_proc(cmd)

        cmd_poids 'wget -q http://gangand.net/aa/audiolivres/perso_zip -O - | grep zip'
        les_poids mpc.sub_proc(cmd_poids)
        print("*" 30 " bbl_perso_zip " "*" 30)
        print(liste_brute_1)
        ############################################################
        # href="antoine_blondin_-_rive_gauche_avec_pierre_assouline.zip"
        # href="raymond_devos_-_mon_chien_c_est_quelqu_un.zip"
        # href="raymond_devos_-_un_ange_passe.zip"
        # href="raymond_devos_-_volume_1.zip"
        # href="San_Antonio_-_En_peignant_la_girafe.zip"
        ############################################################

        # TODO : récupérer le poids :
        # http://gangand.net/aa/audiolivres/perso_zip/?C=N;O=D

        print("*" 30 " bbl_perso_zip fin " "*" 30)

        liste_brute_2 liste_brute_1

        liste_brute_1 liste_brute_1.replace('"''')
        liste_brute_1 liste_brute_1.replace('href=''')
        noms_zip liste_brute_1.split("\n")

        liste_brute_2 liste_brute_2.replace('href="'"http://gangand.net/aa/audiolivres/perso_zip/")
        liste_brute_2 liste_brute_2.replace('"''')
        fichiers_zip liste_brute_2.split("\n")

        # fichiers zip en ligne
        if len(fichiers_zip) == 0:
            return False
        masque "Nb fichiers zip trouvés sur http://gangand.net/... : {}"
        print(masque.format(len(fichiers_zip)))

        # créer le tableau all_lvr []
        global all_lvr
        all_lvr = []
        num 0
        for un_zip in fichiers_zip:
            # un_zip est de la forme :
            # http://gangand.net/aa/audiolivres/perso_zip/antoine_blondin_-_rive_gauche_avec_pierre_assouline.zip
            auteur_titre noms_zip[num]
            new_num4c auteur_titre
            # auteur_titre est de la forme :
            # antoine_blondin_-_rive_gauche_avec_pierre_assouline.zip
            auteur_titre auteur_titre.replace(".zip""")
            auteur_titre auteur_titre.replace("_"" ")
            auteur_titre auteur_titre.replace("%c3%a9""é")
            auteur_titre auteur_titre.split(" - ",2)

            auteur "auteur zip"
            titre "titre zip"
            if len(auteur_titre) == 1:
                auteur "auteur inconnu"
                titre auteur_titre[0]
            elif len(auteur_titre) == 2:
                auteur auteur_titre[0]
                titre auteur_titre[1]
            num += 1
            num4c zero_on(num)
            # TODO : Raymond_Devos_-_Je_zappe_1992
            # au lieu de 0001
            # num4c = new_num4c.replace(".zip", "")
            genre ""
            duree ""
            poids ""
            lecteur ""
            arr_url_media = [ un_zip,]
            fiche ""
            extrait ""
            un_livre = {
                "n"num4c    # numéro à 4 chiffres
                "y"genre    # type
                "d"duree    # durée
                "p"poids    # poids (string : "5.78", "12", ...)
                "l"lecteur  # lecteur
                "a"auteur   # auteur
                "t"titre    # titre
                "u"arr_url_media# url mp3 ou zip
                "f"fiche    # url fiche
                "x"extrait  # extrait
                "type_url""zip",  # type url : mp3, zip
                # "type_url": "usb_mp3",  # type url : mp3, zip
            }
            all_lvr.append(un_livre)
        # creer_html_all_lvr(all_lvr, "/m/perso_zip_all_lvr.html")
        return True

    def creer_html_all_lvr(livr_brutsf_html):
        """ crée une vue du tableau dynamique
            des livres sur clé usb quelconque
            (càd une clé envoyée par un/une amie à France)
        """
        entete "<tr> \
                <td><b>num4c</b></td> \
                <td><b>genre</b></td> \
                <td><b>duree</b></td> \
                <td><b>poids</b></td> \
                <td><b>lecteur</b></td> \
                <td><b>auteur</b></td> \
                <td><b>titre</b></td> \
                <td><b>url[0]</b></td> \
                <td><b>fiche</b></td> \
                <td><b>extrait</b></td> \
            </tr>"
        corps_html "<html><head><title>Livres sur clé usb</title></head>" \
            "<body><center><table border=1 cellpadding=4 cellspacing=2>"
        corps_html += entete
        lignes ""
        for in livr_bruts:
            les_champs \
                (i["n"], i["y"], i["d"], i["p"], i["l"], i["a"], i["t"], i["u"][0], i["f"], i["x"])
            ligne "<tr><td>" \
                "</td><td>".join(les_champs) + \
                "</td></tr>" "\n"
            lignes += ligne
        corps_html += lignes
        corps_html += "</table></center></body></html>"
        with open(f_html"w"as f:
            f.write(corps_html)
        infos.m_g("Le fichier {} a été créé.".format(f_html))

    def verif_dir_audiolivres():
        """ si /m pas monté, on quitte
            si /m/memo/aba pas créable, on quitte
        """
        if commun.m_is_mounted() == False:
            msg "Problème, la clé u s b ne semble pas détectée, " \
                "abandon des audiolivres."
            infos.pdrNew(msg)
            return False

        # vérif "/m/memo/aba" existe (ABA_MEMO_DIR)
        dossier cfg.ABA_MEMO_DIR
        if not commun.dir_exists(dossier):
            commun.make_dir(dossier)
            if not commun.dir_exists(dossier):
                msg "Erreur lors de la création du dossier mémoire audiolivres"
                infos.pdrNew(msg)
                return False
        return True


    ########################################
    #            reprendre aba             #
    ########################################
    # mettre False ci-dessous pour ne pas
    # reprendre_directement la lecture ("annoncer" en fait)
    REPRENDRE_ANNONCER True
    # REPRENDRE_ANNONCER = False

    infos.m_g("B pour dbg_var_all_lvr(), étude variables ESSENTIELLES")

    mpc.ferme_ton_clapet()
    if verif_dir_audiolivres() == False:
        return 0

    init_reprendre(direkt)
    global all_lvr
    chargement_livres False
    if bbl_actu() == cfg.bbl_CLE_USB_ANARCHIE:
        chargement_livres usb_charger_livres()
        # DOKU_enregistrer_dans_tableur_html()
    elif bbl_actu() == cfg.bbl_PERSO_ZIP:
        chargement_livres perso_zip_charger_livres()
        # DOKU_enregistrer_dans_tableur_html()
    else:
        chargement_livres charger_all_lvr()
        # DOKU_enregistrer_dans_tableur_html()

    global ouverture_biblio
    if (chargement_livres == Trueand (direkt == 'direkt'):
        build_selektion()
        ouverture_biblio True
        lire_livre_actuel("lire"direkt)
    elif chargement_livres == True:
        bn bbl_nom()
        nb_ref len(all_lvr)
        nb_prez nb_livres_prezents()
        nb_favo nb_livres_favoris()
        space commun.du(bbl_folder())

        # découper les messages vocaux permet leur création plus rapide
        # et perturbent moins l'utilisateur en réduisant la durée
        # des moments de silence

        # "ouverture de la bibliothèque audiocité classiques"
        masque1 "bibliothèque {}"
        msg1 masque1.format(bn)
        masque2 "ouverture de la bibliothèque {}"
        msg2 masque2.format(bn)
        infos.pdrNew( [ msg1msg2] )

        # "2601 références disponibles dont 8 présentes et accessibles immédiatement"
        if bbl_actu() == cfg.bbl_CLE_USB_ANARCHIE:
            masque1 "{} éléments présents"
            msg1 masque1.formatnb_ref )
            msg2 msg1
            infos.pdrNew( [ msg1msg2] )
        else:
            if nb_prez 0:
                masque1 "{} éléments dont {} présents, qui occupent {} méga octets"
                msg1 masque1.formatnb_refnb_prezspace )
                masque2 "{} références disponibles dont {} présentes et accessibles immédiatement"
                msg2 masque2.formatnb_refnb_prez )
                infos.pdrNew( [ msg1msg2] )
            else:
                masque1 "{} éléments dont aucun présent"
                msg1 masque1.formatnb_ref )
                masque2 "{} références disponibles dont aucune téléchargée"
                msg2 masque2.formatnb_refnb_prez )
                infos.pdrNew( [ msg1msg2] )

        # 12 livres sont notés dans les favoris
        if nb_favo 0:
            if nb_favo == 1:
                msg1 "un livre en favori"
                msg2 "un seul livre a été enregistré comme favori"
            else:
                masque1 "{} livres en favori"
                msg1 masque1.formatnb_favo )
                masque2 "{} livres sont notés dans les favoris"
                msg2 masque2.formatnb_favo )
            infos.pdrNew( [ msg1msg2] )


        # "les livres présents occupent un espace de 92 méga octets"
        if nb_prez 0:
            msg1 ""
            if nb_prez == 1:
                masque2 "le seul livre présent occupe un espace de {} méga octets"
            elif nb_prez 1:
                masque2 "les livres présents occupent un espace de {} méga octets"
            msg2 masque2.formatspace )
            infos.pdrNew( [ msg1msg2] )

        # vérifier dès maintenant l'espace libre
        # et annoncer si insuffisant
        espace_suffisant_aba()
        # annoncer la bibliothèque ouverte

        build_selektion()
        ouverture_biblio True
        if REPRENDRE_ANNONCER == True:
            lire_livre_actuel("annoncer")
    else:
        msg "Pas de catalogue trouvé pour " bbl_actu() + ", " \
            "veuillez choisir une autre bibliothèque"
        infos.pdrNew(msg)
        menu_changer_biblio()


def stats_fichiers():
    """ stats tous fichiers """

    def stats_fich1(la_liste):
        """ infos sur medias, auteurs, ... """
        # calculer durée (minutes) et taille (Mo) totales
        taille_totale 0
        duree_totale 0
        dic_taille = {}
        dic_duree = {}
        for el in la_liste:
            taille_el float(el["p"])
            taille_totale += taille_el

            duree el["d"]
            find_h duree.find("h")
            if find_h > -1:
                # on a un format 1h ou bien 1h03
                heure int(duree[0:find_h])
                # ~ print("vérif " + duree)
                if duree[(find_h 1):] != '':
                    minutes int(duree[(find_h 1):])
                else:
                    minutes 0
                la_duree minutes heure 60
            else:
                la_duree int(duree)
            duree_totale += la_duree
            dic_duree[str(100000 la_duree) + el["n"]] = [el["d"], el["t"], el["a"]]
            dic_taille[str(200000 taille_el) + el["n"]] = [el["p"], el["t"], el["a"]]

        # cf. doc_03
        = [taille_totaleduree_totaledic_tailledic_duree]
        return r

    def stats_sel_lvr():
        """ statistiques sur fichiers url mp3 """
        global sel_lvr
        stats_fich1(sel_lvr)
        stats_voir_save("sel_lvr"len(sel_lvr), r)

    def stats_voir_save(titrelen_lister):
        """ afficher résultats """
        taille_totale r[0]
        duree_totale r[1]
        # dic_taille = r[2]
        # dic_duree = r[3]
        infos.m_g("========== Stats : " str(len_liste) + " fichiers " titre " ==========")
        taille_moyenne int(taille_totale len_liste)
        taille_totale int(taille_totale)
        infos.m_g("taille totale     = " str(taille_totale) + " Mo")
        infos.m_g("       moyenne    = " str(taille_moyenne) + " Mo")
        infos.m_g(
            "       moyenne    = " str(taille_totale) + " / " str(len_liste) + " = " str(taille_moyenne) + " Mo")
        infos.m_g("")

        duree_moyenne int(duree_totale len_liste)
        infos.m_g("durée totale  (mn)= " str(duree_totale) + " mn")
        infos.m_g("              (h) = " str(int(duree_totale 60)) + " h")
        infos.m_g("              (j) = " str(int(duree_totale 60 24)) + " j")
        infos.m_g("      moyenne (mn)= " str(duree_totale) + " / " str(len_liste) + " = " str(duree_moyenne) + " mn")
        infos.m_g("")

    stats_sel_lvr()


def trk_first():
    """ aller au premier chapitre """
    if mpc.trk_first():
        aba_infos_go()


def trk_last():
    """ aller au dernier chapitre """
    if mpc.trk_last():
        aba_infos_go()


def save_num4c_actuel(num4c):
    """ sauvegarde dans py_user.ini
        du champ "n" (num4c) du livre actuel + bibliothèque actuelle.
        ex : num4c_cle_usb_anarchie=0012
             <=> le champ "n" du livre actuel de la biblio cle_usb_anarchie est 0012
        ex : num4c_litteaudio=0244
             <=> le champ "n" du livre actuel de la biblio litteaudio est 0244

        valeur lue par la fonction get_num4c_actuel()
    """
    if nb_livres_total() == 0:
        return False

    var1 "aba_num4c_" bbl_actu()
    val1 num4c "|" infos.jour_heure_actu()
    infos.ini_set(var1val1)


def get_num4c_actuel():
    """ renvoie le num4c du livre actuel
        de la bibliothèque actuelle """
    # vérifier que le fichier .ini renvoie un num4c valide
    # càd dans sel_n2i.keys()
    num_horo infos.ini_get("aba_num4c_" bbl_actu(), "0001|")
    num4c num_horo.split("|")[0]
    global sel_n2i
    if num4c in sel_n2i.keys():
        return num4c
    else:
        # on renvoie le champ "n" de la 1ère ligne de sel_lvr (indice 0)
        if nb_livres_total() > 0:
            global sel_lvr
            infos.m_g("ERR num4c, 1er trouvé = {}".format(lv_num4c(0)))
            # infos.m_g("ERR : 3. get_num4c_actuel, dim sel_lvr[] = {}".format(len(sel_lvr)))
            return lv_num4c(0)
        else:
            return "sel_lvr vide"


def slct_auteur():    # OK
    """ lit seulement les livres du même auteur """
    def get_books():
        """ liste les livres favoris dans la biblio actuelle """
        # num4c_trouves = infos.ini_get("aba_favoris_" + bbl_actu(), "")
        # -------------------------------------------------------------
        # 0. rechercher le livre actuel
        # 1. rechercher l'auteur du livre actuel
        # 2. copier build_selektion pour trouver les livres du même auteur
        # -------------------------------------------------------------
        auteur_actuel lv_auteur()
        infos.pdrNew("Sélection des livres de l'auteur actuel, " auteur_actuel)

        # rechercher dans all_lvr le même auteur
        t_meme_auteur = []   # Tableau Même Auteur
        n_view 5
        mask "{}. auteur {}, livre {}, titre {}"
        for lv in all_lvr:
            if auteur_actuel == lv["a"]:
                t_meme_auteur.append(lv["n"])
                n_view -= 1
                if n_view 0:
                    infos.m_g(mask.format(n_viewlv["a"], lv["n"], lv["t"]))
        if len(t_meme_auteur) > 0:
            num4c_trouves ",".join(t_meme_auteur)
        else:
            num4c_trouves ""

        return slct_get_build(num4c_trouves)

    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    # slct_auteur()
    mpc.ferme_ton_clapet()
    nb_found get_books()
    if nb_found == 0:
        masque "Aucun livre du même auteur pour la bibliothèque {}"
        msg masque.format(bbl_nom())
    elif nb_found == 1:
        masque "Un seul livre de cet auteur pour la bibliothèque {}"
        msg masque.format(bbl_nom())
    else:
        masque "{} livres de cet auteur pour la bibliothèque {}"
        msg masque.format(nb_foundbbl_nom())
    infos.pdrNew(msg)


def slct_favoris():  # OK
    """ lit seulement les livres favoris """
    def get_books():
        """ liste les livres favoris dans la biblio actuelle """
        # selon le type de bibliothèque,
        # on va chercher les favoris à deux (ou trois ?) endroits différents

        # TODO :
        # 1. clé usb
        # --> les favoris sont stockés dans la clé usb

        # 2. perso en ligne
        # --> les favoris sont stockés sur la machine
        #     dans /home/pi, et pas /opt/jk2019 (car màj peut écraser les favoris)

        # 3. gratuits en ligne
        # --> les favoris sont stockés sur la machine
        #     dans /home/pi, et pas /opt/jk2019 (car màj peut écraser les favoris)
        # selon le type de bibliothèque,
        # on va chercher les favoris à deux (ou trois ?) endroits différents

        # TODO :
        # 1. clé usb
        # --> les favoris sont stockés dans la clé usb

        # 2. perso en ligne
        # --> les favoris sont stockés sur la machine
        #     dans /home/pi, et pas /opt/jk2019 (car màj peut écraser les favoris)

        # 3. gratuits en ligne
        # --> les favoris sont stockés sur la machine
        #     dans /home/pi, et pas /opt/jk2019 (car màj peut écraser les favoris)
        num4c_trouves infos.ini_get("aba_favoris_" bbl_actu(), "")
        return slct_get_build(num4c_trouves)

    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    # slct_favoris()
    mpc.ferme_ton_clapet()
    nb_found get_books()
    if nb_found == 0:
        masque "Aucun livre dans les favoris pour la bibliothèque {}"
        msg masque.format(bbl_nom())
    elif nb_found == 1:
        masque "sélection du seul livre inscrit dans la liste " \
            "des favoris pour la bibliothèque {}"
        msg masque.format(bbl_nom())
    else:
        masque "sélection des {} livres favoris de la bibliothèque {}"
        msg masque.format(nb_foundbbl_nom())
    infos.pdrNew(msg)


def slct_get_build(str_champs_n_trouves):
    """ lance build_selektion(str_champs_n_trouves)
        si str_champs_n_trouves pas vide, et
        renvoie le nombre de livres trouvés dans str_champs_n_trouves

        Est appelé par
            slct_auteur(),
            slct_favoris(),
            slct_lecteur(),
            slct_presents()
    """
    # bn = bbl_nom()
    if str_champs_n_trouves == "":
        # infos.m_g("Aucun livre favori pour la biblio " + bn)
        nb_trouves 0
    else:
        lst_champs_n str_champs_n_trouves.split(",")
        nb_trouves len(lst_champs_n)

    if nb_trouves 0:
        # msg = "livres favoris : " + ", ".join(lst_champs_n)
        # infos.m_g(msg)
        build_selektion(lst_champs_n# ICI, liste générée des livres sélectionnés
    # else:
        # msg = "Aucun livre favori dans la bibliothèque " + bn
        # infos.m_g(msg)
    return nb_trouves


def slct_presents(): # OK
    """ lit seulement les livres téléchargés """

    def get_books():
        """ liste les livres présents dans la biblio actuelle

            les livres présents sont les dossiers
            présents dans chaque dossier biblio
            /m/music/aba/audiocite
            /m/music/aba/litteratureaudio
            /m/music/aba/librivox_fr
            /m/music/aba/librivox_de
            /m/music/aba/vorleser

            renvoie un int : nombre de livres présents,
            soir le nb d'éléments de la liste lst_champs_n
        """
        # un livre est présent si son dossier l'est dans /m/music/aba/audiocite
        # si la biblio actuelle est "bib_audio_cite"
        dossiers_num4c commun.folders_simple_nom_seul(bbl_folder())
        str_dossiers_num4c ",".join(dossiers_num4c)
        return slct_get_build(str_dossiers_num4c)

    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    # slct_presents()
    mpc.ferme_ton_clapet()
    if bbl_actu() == cfg.bbl_CLE_USB_ANARCHIE:
        # tous les livres sont présents pour bbl_CLE_USB_ANARCHIE
        slct_tout("silent")
        nb_found nb_livres_total()
    else:
        nb_found get_books()

    if nb_found == 0:
        masque "Aucun livre présent sur la clé pour la bibliothèque {}"
        msg masque.format(bbl_nom())
    elif nb_found == 1:
        masque "sélection du seul livre présent sur la clé pour la bibliothèque {}"
        msg masque.format(bbl_nom())
    else:
        masque "sélection des {} livres présents sur la clé pour la bibliothèque {}"
        msg masque.format(nb_foundbbl_nom())
    infos.pdrNew(msg)


def slct_bannis():
    """ lit seulement les livres favoris """
    # TODO à continuer
    pass


def slct_lecteur():
    """ lit seulement les livres du lecteur actuel """
    def get_books():
        """ liste les livres du lecteur actuel dans la biblio actuelle """
        lecteur_actuel lv_lecteur()
        infos.pdrNew("Sélection des livres du lecteur actuel, " lecteur_actuel)
        # rechercher dans all_lvr le même auteur
        t_meme_lecteur = []   # Tableau Même Auteur
        n_view 5
        mask "{}.lecteur {}, livre {}, titre {}"
        for lv in all_lvr:
            if lecteur_actuel == lv["l"]:
                t_meme_lecteur.append(lv["n"])
                n_view -= 1
                if n_view 0:
                    infos.m_g(mask.format(n_viewlv["l"], lv["n"], lv["t"]))
        if len(t_meme_lecteur) > 0:
            num4c_trouves ",".join(t_meme_lecteur)
        else:
            num4c_trouves ""
        return slct_get_build(num4c_trouves)

    if nb_livres_total() == 0:      # empêche le plantage si on est
        choisir_autre_biblio_svp()  # dans une bibliothèque sans livre (usb par exemple)
        return 0

    # slct_lecteur()
    mpc.ferme_ton_clapet()
    nb_found get_books()
    if nb_found == 0:
        masque "Aucun livre du même lecteur pour la bibliothèque {}"
        msg masque.format(bbl_nom())
    elif nb_found == 1:
        masque "Un seul livre de ce lecteur pour la bibliothèque {}"
        msg masque.format(bbl_nom())
    else:
        masque "{} livres de ce lecteur pour la bibliothèque {}"
        msg masque.format(nb_foundbbl_nom())
    infos.pdrNew(msg)


def filtrer_bruts(champvaleur):
    """ renvoie une list de num4c pour le lecteur lecteur
    """
    global all_lvr
    renvoyer = []
    for in all_lvr:
        if i[champ] == valeur:
            renvoyer.append(i["n"])
    return renvoyer


def slct_genre():
    """ lit seulement les livres du genre actuel """
    # TODO à continuer
    pass


def slct_tout(silent ""):
    """ réinitialise la sélection """
    if nb_livres_total() == 0:
        choisir_autre_biblio_svp()
        return 0

    build_selektion()
    masque "sélection des {} livres de la bibliothèque {}"
    if silent == "":
        msg masque.format(nb_livres_total(), bbl_nom())
        infos.pdrNew(msg)


def wget_spider_taille(url):
    """ connaître la taille d'un fichier à télécharger """
    # cf. doc_02
    # utiliser wget --spider "http://www.litteratureaudio.org/mp3/Emile_Zola_-_La_debacle_P1.zip"
    # ...
    # Taille : 405411018 (387M) [application/zip]
    # Le fichier distant existe
    # ...
    cmd "wget --spider " url " 2> /tmp/spider.txt"
    mpc.sub_proc(cmd)
    with open("/tmp/spider.txt""r"as f:
        lignes f.readlines()
    for in lignes:
        if i.startswith("Taille : "):
            i.replace("Taille : """)
            x.split(" ")
            taille y[0]
            cmd "rm /tmp/spider.txt"
            mpc.sub_proc(cmd)
            return taille
    return 0


def ze_indice(num4c):
    """ indice de sel_lvr[] pour le num4c donné """
    global sel_n2i
    # num4c peut être égal à 'sel_lvr est vide'
    # si la biblio est vide (usb par exemple)
    return sel_n2i[num4c]


def ze__indice_actuel():
    """ indice de sel_lvr[] pour le num4c actuel """
    return ze_indiceget_num4c_actuel() )


def zero_on(s):
    """ formate un num4c sur 4 caractères """
    # inverse de zero_off()
    # attention : s est une string
    padder4 "000" str(s)
    return padder4[-4:]


def zero_off(n):
    """ enlève les 0 du début """
    # inverse : zero_on
    if len(str(n)) == 4:
        int(n) + 20000    #  0307 + 20000 = 20307
        20000         # 20307 - 20000 =   307
        return str(n)         #   307
    else:
        return n


def dbg_var():

    def header():
        return """
<!DOCTYPE html>
<html>
<head>
    <title>biblio """ bbl_actu() + """[]</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="http://gangand.net/pp/projets/xavbox/xavbox.css">
    <style>
html {
  scroll-behavior: smooth;
}
body{
    margin: 0;
    padding: 0;
    color: lightgrey;
    background-color: black;
    font-size: 1.11em;
    position: relative;
}
h2 {
    margin: 0 auto;
    text-align: center;
}
a { text-decoration: none;
    color: white;
}
a:link, a:visited {
    text-decoration: none;
    color: white;
}
.sticky1 {
    position: sticky;
    top: 0px;
    right: 0px;
    width: 600px;
/*
    background: #222;
    */
    opacity: 0.7;

}
a:hover {
    color: white;
}
    </style>
</head>
<body>
        """

    def menu():
        return """
            <div class=\"sticky1\">
            <table class=\"table_touches\">
                <tr>
                    <td><a href="#top" >Accueil   </a></td>
                    <td><a href="#sel_lvr"    >sel_lvr[...]      </a></td>
                    <td><a href="#sel_n2i"      >sel_n2i{...}        </a></td>
                    <td><a href="#all_lvr" >all_lvr[...]   </a></td>
                </tr>
            </table>
            </div>
        """

    def footer():
        return """</body>
</html>
    """

    global all_lvr
    p1 dbg_var_gene(all_lvr"all_lvr")
    global sel_lvr
    p2 dbg_var_gene(sel_lvr"sel_lvr")
    global sel_n2i
    p3 dbg_var_sel_n2i(sel_n2i"sel_n2i")

    full_page header() + \
        "<a name='top'><h2>" "Biblio " bbl_actu() + "</h2></a>" "<br>" \
        menu() + p2 p3 p1 footer()
    fichier "dbg_var/" bbl_actu() + ".html"
    with open(fichier"w"as f:
        f.write(full_page)
    infos.m_g("{} créé".format(fichier))


def dbg_var_gene(tableautitrelimite 12):
    """ enregistre les variables all_lvr[], sel_lvr[]
        et sel_n2i{} dans un fichier dbg_var_all_lvr.html
        pour bien comprendre leur interaction
    """
    def td(s):
        return "<td>" str(s) + "</td>"
    0
    corps ""
    for in tableau:
        ligne \
        "  <tr>" td(str(n)) + \
            td(i["n"]) + td(i["y"]) + td(i["d"]) + td(i["p"]) + td(i["l"]) + \
            td(i["a"]) + td(i["t"]) + \
            "<td><a title=\"" str(i["u"]) + "\">URIs</a></td>" \
            "<td><a title=\"" str(i["f"]) + "\">fiche info</a></td>" \
            td(i["x"]) + \
            td(i["type_url"]) + \
        "  </tr>"
        += 1
        corps corps ligne "\n"
        if == limite:
            break
    entete \
        "<tr>" \
            "<th><a title='indice tableau'>#</a></th>" \
            "<th><a title='num4c'>n</a></th>" \
            "<th><a title='type (roman, nouvelle, conte, ...)'>y</a></th>" \
            "<th><a title='durée'>d</a></th>" \
            "<th><a title='poids'>p</a></th>" \
            "<th><a title='lecteur'>l</a></th>" \
            "<th><a title='auteur'>a</a></th>" \
            "<th><a title='titre'>t</a></th>" \
            "<th><a title='URIs média'>u</a></th>" \
            "<th><a title='fiche information'>f</a></th>" \
            "<th><a title='extra'>x</a></th>" \
            "<th><a title='mp3 ou zip'>type_url</a></th>" \
        "</tr>"
    corps "<table class=\"table_touches\" style='margin: 15px auto 25px;'>" "\n" entete corps "\n" "</table>"
    aname "<a name='" titre "'><h2>" titre "[...]</h2></a>" "\n"
    return aname corps


def dbg_var_sel_n2i(tableautitrelimite 12):
    """ contenu de sel_n2i
    """
    def td(s):
        return "<td>" str(s) + "</td>"
    0
    corps ""
    tk list(tableau.keys())
    tk.sort()
    for in tk:
        rri "sel_n2i[" str(i) + "]"
        ri sel_n2i[i]
        mask "le livre de champ 'n' {} se trouve dans sel_lvr[{}]"
        if 1:
            mask "le livre {} est sel_lvr[{}]"
        signifie mask.format(str(i), ri)
        ligne \
        "  <tr>" \
            td(rri) + \
            td(ri) + \
            td(signifie) + \
        "  </tr>"
        += 1
        corps corps ligne "\n"
        if == limite:
            break
    entete \
        "<tr>" \
            "<th>sel_n2i[num4c]</th><th>Index sel_lvr</th><th>Signification</th>" \
        "</tr>"
    corps "<table class=\"table_touches\" style='margin: 15px auto 25px;'>" "\n" entete corps "\n" "</table>"
    aname "<a name='" titre "'><h2>" titre "{...}</h2></a>" "\n"
    return aname corps


if __name__ == "__main__":
    print("Bienvenue  dans  le module aba (Audiobooks gratuits en ligne)")