Extraction multimédia à partir de YouTube

E

Streamlit Cloud est une solution particulièrement intéressante pour héberger gratuitement des applications Python. Simple à utiliser, directement connecté à GitHub, il permet de mettre en production très rapidement des projets basés sur l’interface graphique de Streamlit. Les dépendances sont directement installée et indiquée depuis un fichier requirements.txt. Pour l’utilisateur, “l’expérience” s’apparente à du “no code”, puisqu’il peut exécuter l’application directement à partir d’une simple URL (par exemple) : https://europresse-to-iramuteq.streamlit.app/

Cependant, certains types d’applications rencontrent des blocages sur Streamlit Cloud, notamment celles utilisant ffmpeg et la librairie yt-dlp pour extraire ou télécharger des ressources vidéo ou audio en ligne.
Premièrement, ffmpeg n’est pas une librairie Python mais un programme externe ; il faudrait donc pouvoir l’installer directement sur le serveur de Streamlit Cloud, ce qui est impossible car l’utilisateur (vous) n’a pas les droits d’administration du serveur.
Deuxièmement, même si une solution de contournement semble exister en utilisant la librairie ffmpeg-python, sa mise en œuvre reste complexe. Pour ma part, malgré plusieurs essais, j’ai rencontré des difficultés à faire fonctionner cette option.
Mes recherches sur les forums dédiés à Streamlit Cloud n’ont pas été concluantes : certains “posts” sur les forum datant de 2023/2024 expliquent la démarche, mais en 2025, beaucoup des applications qui semblaient avoir résolu le problème ne fonctionnent plus.

L’objectif ici est de proposer un script capable d’extraire, à partir d’une vidéo YouTube (url), plusieurs types de données dans une perspective d’analyse multimodale :

  • La vidéo complète au format mp4,
  • Les pistes audio aux formats mp3 et wav,
  • La vidéo mp4 découpée selon un intervalle défini par l’utilisateur,
  • Les fichiers audio extraits selon cet intervalle,
  • L’ensemble des images de la vidéo capturées à une fréquence de 1 image par seconde ou 25 images par seconde.
Paramétrage de l’intervalle

 

Concernant le paramétrage de l’intervalle, l’interface permet de lire la vidéo directement afin de repérer précisément les moments souhaités.

Les librairies à installer

pip install streamlit # version locale de streamlit, ne pas confondre avec le serveur StreamlitCloud
pip install yt-dlp

La principale difficulté reste l’installation de ffmpeg, qui doit être installé en local sur votre machine pour garantir le bon fonctionnement de l’application.

L’erreur 403 avec yt-dlp

L’erreur 403 signifie que l’accès à la ressource est interdit par le serveur de YouTube qui vous détecte comme un “robot”.
Sur Streamlit Cloud, cela se produit car le serveur distant n’a pas les cookies (identité de votre navigateur) nécessaires pour authentifier votre requête.

À partir d’une URL YouTube saisie dans une script hébergé sur Streamlit Cloud

 

Ainsi, le script peut parfaitement fonctionner lorsqu’on importe directement un fichier source (mp4, mp3, etc.), alors que l’extraction depuis une URL YouTube ne fonctionne plus dans le contexte de StreamliCloud..

Même script mais avec l’import du fichier mp3 (et non l’url YouTube)

 

Fichier cookies.txt

Le script prend en charge, si nécessaire, l’utilisation d’un fichier cookies extrait de votre navigateur, permettant à YouTube de vous identifier comme un (vrai) utilisateur.
Si un message d’erreur apparaît indiquant que YouTube vous bloque l’accès : relancez l’application (important !), puis ajoutez votre fichier cookies depuis votre disque local.
Cette méthode fonctionne à condition que le fichier ait été généré avec Firefox et que l’application soit exécutée dans ce même navigateur.
Pour générer un cookies, il suffit d’installer l’extension (gratuite) Get cookies.txt.

À quoi sert ffmpeg ?

ffmpeg n’est pas une librairie Python, mais un utilitaire en ligne de commande, indispensable pour traiter de l’audio et de la vidéo. Il permet par exemple :

  • D’extraire l’audio d’une vidéo,
  • De découper une vidéo selon un intervalle temporel,
  • De capturer des images à une fréquence donnée (1fps, 25fps…).

L’installation de ffmpeg sur macOS nécessite l’outil Homebrew, qui est un gestionnaire de paquets.

Ouvrez votre Terminal (Applications → Utilitaires → Terminal) et collez cette commande :

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Puis, toujours dans votre terminal tapez la commande :

brew install ffmpeg

Et voila, le tour est joué ! Vous pouvez vérifier la version de ffmpeg :

ffmpeg -version

 

 

Le code source

Pour lancer le script dans votre terminal de votre éditeur de code tapez : streamlit run main.py

Le code source sur Github.

########
# Extraction multimédia - en local
# wwww.codeandcortex.fr
########

# ---------------- Imports ----------------
import streamlit as st
import tempfile
import os
import zipfile
import subprocess
from yt_dlp import YoutubeDL

# ---------------- Fonctions ----------------

# Fonction pour vider le cache
def vider_cache():
    st.cache_data.clear()

# Fonction pour définir un répertoire temporaire
def definir_repertoire_travail_temporaire():
    return tempfile.mkdtemp()

# Fonction pour télécharger une vidéo YouTube
def telecharger_video(url, repertoire, cookies_path=None):
    st.write("Téléchargement de la vidéo en cours...")

    # User-Agent Firefox fixé
    user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:115.0) Gecko/20100101 Firefox/115.0"

    ydl_opts = {
        'outtmpl': os.path.join(repertoire, '%(title)s.%(ext)s'),
        'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4',
        'merge_output_format': 'mp4',
        'noplaylist': True,
        'quiet': True,
        'no_warnings': True,
        'user_agent': user_agent,
    }

    if cookies_path:
        ydl_opts['cookiefile'] = cookies_path

    try:
        with YoutubeDL(ydl_opts) as ydl:
            info = ydl.extract_info(url, download=True)
            video_title = info.get('title', 'video')
            video_path = os.path.join(repertoire, f"{video_title}.mp4")
        return video_path, video_title, None
    except Exception as e:
        return None, None, str(e)

# Fonction pour extraire toutes les ressources
def extraire_ressources(video_path, repertoire, debut, fin, video_title, fps_choice, extraire_toute_video):
    ressources = {}

    try:
        ressources['video_complet'] = video_path

        # Extraction audio complet
        mp3_path = os.path.join(repertoire, f"{video_title}.mp3")
        wav_path = os.path.join(repertoire, f"{video_title}.wav")
        subprocess.run(["ffmpeg", "-i", video_path, "-vn", "-acodec", "libmp3lame", "-q:a", "2", mp3_path], check=True)
        subprocess.run(["ffmpeg", "-i", video_path, "-vn", "-acodec", "pcm_s16le", wav_path], check=True)
        ressources['audio_mp3_complet'] = mp3_path
        ressources['audio_wav_complet'] = wav_path

        # Extraction vidéo extrait
        extrait_video_path = os.path.join(repertoire, f"{video_title}_extrait.mp4")
        subprocess.run(["ffmpeg", "-y", "-ss", str(debut), "-to", str(fin), "-i", video_path, "-c", "copy", extrait_video_path], check=True)
        ressources['video_extrait'] = extrait_video_path

        # Extraction audio extrait
        extrait_mp3_path = os.path.join(repertoire, f"{video_title}_extrait.mp3")
        extrait_wav_path = os.path.join(repertoire, f"{video_title}_extrait.wav")
        subprocess.run(["ffmpeg", "-y", "-ss", str(debut), "-to", str(fin), "-i", video_path, "-vn", "-acodec", "libmp3lame", "-q:a", "2", extrait_mp3_path], check=True)
        subprocess.run(["ffmpeg", "-y", "-ss", str(debut), "-to", str(fin), "-i", video_path, "-vn", "-acodec", "pcm_s16le", extrait_wav_path], check=True)
        ressources['audio_mp3_extrait'] = extrait_mp3_path
        ressources['audio_wav_extrait'] = extrait_wav_path

        # Extraction images
        images_repertoire = os.path.join(repertoire, f"images_{fps_choice}fps_{video_title}")
        os.makedirs(images_repertoire, exist_ok=True)
        output_pattern = os.path.join(images_repertoire, "image_%04d.jpg")

        cmd = [
            "ffmpeg", "-y",
            "-i", video_path,
            "-vf", f"fps={fps_choice},scale=1920:1080",
            "-q:v", "1",
            output_pattern
        ]

        if not extraire_toute_video:
            cmd = [
                "ffmpeg", "-y",
                "-ss", str(debut),
                "-to", str(fin),
                "-i", video_path,
                "-vf", f"fps={fps_choice},scale=1920:1080",
                "-q:v", "1",
                output_pattern
            ]

        subprocess.run(cmd, check=True)

        # Zipper les images
        zip_images_path = images_repertoire + ".zip"
        with zipfile.ZipFile(zip_images_path, 'w') as zipf:
            for root, dirs, files in os.walk(images_repertoire):
                for file in files:
                    file_path = os.path.join(root, file)
                    arcname = os.path.relpath(file_path, images_repertoire)
                    zipf.write(file_path, arcname)
        ressources['images_zip'] = zip_images_path

        return ressources, None

    except Exception as e:
        return None, str(e)

# Fonction pour créer un zip général de toutes les ressources
def creer_zip_global(ressources, repertoire):
    zip_path = os.path.join(repertoire, "ressources_completes.zip")
    with zipfile.ZipFile(zip_path, 'w') as zipf:
        for nom, chemin in ressources.items():
            if os.path.exists(chemin):
                zipf.write(chemin, os.path.basename(chemin))
    return zip_path

# ---------------- Interface utilisateur ----------------

st.title("Extraction multimédia (mp4 - mp3 - wav - images) depuis YouTube")
st.markdown("**[www.codeandcortex.fr](https://www.codeandcortex.fr)**")

st.markdown("""
➡ Entrez une URL YouTube.  
➡ **Renseignez votre fichier cookies.txt** (optionnel) : fonctionne sans... tant que YouTube ne le demande pas.  
➡ Le script téléchargera la **vidéo complète** au format .mp4  
➡ Ensuite, il extraira automatiquement :
- L'audio complet (.mp3 et .wav)
- Un extrait vidéo (.mp4 selon l'intervalle défini)
- Un extrait audio (.mp3 et .wav)
- Des images extraites (1 ou 25 fps, toute la vidéo ou intervalle)

Enfin, vous pourrez télécharger toutes les ressources dans un seul fichier ZIP.
""")

# Nettoyage du cache au démarrage
vider_cache()

# Initialiser la session Streamlit
if 'video_path' not in st.session_state:
    st.session_state['video_path'] = None
if 'video_title' not in st.session_state:
    st.session_state['video_title'] = None
if 'repertoire_travail' not in st.session_state:
    st.session_state['repertoire_travail'] = None

# Entrée URL
url = st.text_input("Entrez l'URL de la vidéo YouTube :")
cookies_file = st.file_uploader("Uploader votre fichier cookies.txt (optionnel)", type=["txt"])

# Téléchargement vidéo
if st.button("Télécharger la vidéo"):
    if url:
        st.session_state['repertoire_travail'] = definir_repertoire_travail_temporaire()

        cookies_path = None
        if cookies_file:
            cookies_temp_path = os.path.join(st.session_state['repertoire_travail'], "cookies.txt")
            with open(cookies_temp_path, "wb") as f:
                f.write(cookies_file.read())
            cookies_path = cookies_temp_path

        video_path, video_title, erreur = telecharger_video(url, st.session_state['repertoire_travail'], cookies_path=cookies_path)

        if erreur:
            st.error(f"Erreur lors du téléchargement : {erreur}")
        else:
            st.success(f"Téléchargement réussi : {video_title}")
            st.session_state['video_path'] = video_path
            st.session_state['video_title'] = video_title
    else:
        st.error("Veuillez entrer une URL valide.")

# Extraction des ressources
if st.session_state['video_path']:
    st.markdown("---")
    st.subheader("Lecture de la vidéo téléchargée")
    st.video(st.session_state['video_path'])

    st.markdown("---")
    st.subheader("Paramètres d'extraction")

    col1, col2 = st.columns(2)
    debut = col1.number_input("Début de l'intervalle (en secondes)", min_value=0, value=0)
    fin = col2.number_input("Fin de l'intervalle (en secondes)", min_value=1, value=10)

    fps_choice = st.selectbox("Fréquence d'images pour l'extraction :", options=[1, 25])

    extraire_toute_video = st.checkbox("Extraire toutes les images de toute la vidéo", value=False)

    if st.button("Extraire toutes les ressources"):
        ressources, erreur = extraire_ressources(
            st.session_state['video_path'],
            st.session_state['repertoire_travail'],
            debut,
            fin,
            st.session_state['video_title'],
            fps_choice,
            extraire_toute_video
        )

        if erreur:
            st.error(f"Erreur lors de l'extraction : {erreur}")
        else:
            zip_global_path = creer_zip_global(ressources, st.session_state['repertoire_travail'])
            st.success("Toutes les ressources ont été extraites avec succès !")

            with open(zip_global_path, "rb") as f:
                st.download_button(
                    label="Télécharger toutes les ressources (ZIP)",
                    data=f,
                    file_name="ressources_completes.zip",
                    mime="application/zip"
                )

 

A propos de l'auteur

Stéphane Meurisse

Ajouter un commentaire

Stéphane Meurisse