Découvrez comment j’ai créé mon App nocode YouTube Transcriber & Cutter avec l’IA
Résumé express
Dans cette vidéo, je vous dévoile les coulisses de la création de mon application innovante qui transcrit et découpe des vidéos YouTube grâce à l’intelligence artificielle. Je vous partage mon parcours, mes outils favoris, et mes astuces pour automatiser un processus souvent chronophage. Si vous êtes passionné par le développement web, l’IA et le montage vidéo, cet article est fait pour vous !
Pourquoi j’ai créé cette application
L’idée m’est venue en cherchant à optimiser mon temps de travail lors de l’analyse de contenus vidéo. Extraire des segments précis d’une vidéo manuellement est fastidieux et source d’erreurs. J’ai donc décidé de mettre à contribution mes compétences en Intelligence Artificielle pour concevoir une solution automatisée.
- Gain de temps : Automatiser la transcription et la découpe permet de se concentrer sur l’analyse du contenu plutôt que sur des tâches répétitives.
- Précision : L’utilisation de mots-clés et de repères précis garantit une découpe ciblée et pertinente.
- Accessibilité : Une interface simple et intuitive permet à tous, même sans compétences techniques, de bénéficier de cette technologie.
- Innovation : L’IA transforme des processus traditionnels en outils modernes et efficaces.
Les outils utilisés pour développer l’application
Pour mener à bien ce projet, ChatGPT o3-mini-high a choisi un ensemble d’outils performants et complémentaires :
- Flask : Ce micro-framework Python m’a permis de construire rapidement un backend robuste et flexible.
- yt-dlp : Une version améliorée de youtube-dl pour télécharger les vidéos de manière fiable.
- YouTubeTranscriptApi : Pour récupérer automatiquement les transcriptions en fonction de la langue sélectionnée.
- FFmpeg : L’outil incontournable pour le traitement vidéo, utilisé ici pour découper et fusionner les segments.
- HTML/CSS & JavaScript : Pour concevoir une interface utilisateur ergonomique et dynamique.
Les fonctionnalités clés de l’application
Transcription automatique
- Extraction intelligente : Grâce à YouTubeTranscriptApi, le transcript est récupéré en fonction de la langue choisie.
- Interface claire : Le transcript complet est affiché dans une zone dédiée, facilitant la relecture et l’analyse du contenu.
Découpe vidéo précise
- Mode “Between Precise Keywords” : Définissez une paire (ou plusieurs paires) de mots-clés pour extraire précisément le segment souhaité.
- Mode “Around KW” : Choisissez un mot-clé et définissez une durée avant et après pour capturer le contexte complet.
- Fusion des segments : Combinez automatiquement tous les segments découpés en un seul fichier vidéo, avec possibilité de supprimer les fichiers temporaires pour une gestion optimisée du stockage.
Interface utilisateur optimisée
- Formulaire intuitif : Une conception épurée et responsive qui s’adapte à tous les supports.
- Basculer facilement entre les options : Un script JavaScript permet de changer d’interface en fonction du mode de découpe sélectionné.
Étapes de développement et challenges rencontrés
Créer cette application n’a pas été un long fleuve tranquille. Voici quelques étapes clés et défis que j’ai dû relever :
- Conception de l’architecture : J’ai structuré l’application autour de Flask, en séparant clairement le backend de l’interface utilisateur. L’organisation du projet, avec notamment le fichier index.html dans un dossier dédié aux templates, a été primordiale pour la maintenabilité du code.
- Gestion des téléchargements et transcriptions : L’intégration de yt-dlp et de YouTubeTranscriptApi a nécessité une gestion fine des erreurs et une synchronisation précise entre le téléchargement de la vidéo et la récupération de ses métadonnées.
- Implémentation de la découpe vidéo : Utiliser FFmpeg pour découper les vidéos selon des repères textuels a demandé une manipulation précise des timestamps extraits du transcript, afin de gérer à la fois des coupes simples et des traitements complexes en batch.
- Amélioration de l’expérience utilisateur (UX) : J’ai mis un point d’honneur à concevoir une interface épurée avec des animations légères, pour rendre l’outil accessible même à ceux qui ne sont pas familiers avec la technologie.
- Tests et retours utilisateurs : Les phases de tests ont été essentielles pour affiner les fonctionnalités, corriger des bugs et intégrer des retours d’utilisateurs pour une interface encore plus fluide.
Impact et perspectives d’évolution
Depuis la mise en ligne, les retours ont été très positifs. Les utilisateurs apprécient la simplicité d’utilisation et la précision des découpes. Voici quelques axes d’amélioration envisagés pour l’avenir :
- Résumé automatique et indexation : Ajouter une fonctionnalité qui résume automatiquement le transcript et crée des chapitres pour faciliter la navigation.
- Export des sous-titres : Permettre l’export du transcript au format SRT ou VTT pour une intégration rapide aux vidéos.
- Améliorations UX : Continuer d’optimiser l’interface pour offrir une expérience toujours plus fluide, avec éventuellement une prévisualisation des segments découpés.
Mais pourquooooiiii ?
En créant cette application, j’ai voulu démontrer que l’innovation passe par l’automatisation et l’optimisation des tâches quotidiennes. Mon objectif est de rendre la manipulation de contenus vidéo plus accessible et efficace grâce à l’intelligence artificielle et aux outils modernes. Cette vidéo est une invitation à explorer ces technologies et à oser les intégrer dans vos projets, qu’ils soient personnels ou professionnels.
Si vous êtes intéressé par le développement d’outils innovants, je vous encourage à tester l’application, partager vos impressions et proposer des idées pour l’améliorer.
Les deux fichiers du projet
Avant d’utiliser cette application, assurez-vous d’avoir Python 3.6 (ou une version supérieure) installé. Ensuite, suivez ces instructions :
1. Installer les dépendances Python
Créez un fichier nommé requirements.txt avec le contenu suivant, puis copiez-collez ce contenu :
Flask
yt-dlp
youtube-transcript-api
Ensuite, ouvrez votre terminal et exécutez la commande suivante :
pip install -r requirements.txt
2. Installer FFmpeg
Selon votre système d’exploitation, copiez-collez l’une des commandes ci-dessous :
- Ubuntu/Debian :
sudo apt update && sudo apt install ffmpeg
- macOS (via Homebrew) :
brew install ffmpeg
- Windows : Téléchargez FFmpeg depuis le site officiel et ajoutez le dossier
binà votre variable d’environnement PATH.
Copiez-collez ces instructions dans votre terminal pour préparer votre environnement avant de lancer l’application. Ensuite télécharger les deux fichiers dans votre dossier d’application.
import os
import re
import json
import subprocess
from flask import Flask, render_template, request
from youtube_transcript_api import YouTubeTranscriptApi
app = Flask(__name__)
def extract_video_id(url):
"""Extract the 11-character YouTube video ID from the URL."""
pattern = r"(?:v=|\/)([a-zA-Z0-9_-]{11})"
match = re.search(pattern, url)
if match:
return match.group(1)
return None
def download_video(yt_url, output_file):
"""Download the YouTube video using yt-dlp."""
command = ["yt-dlp", "-f", "best", "-o", output_file, yt_url]
subprocess.run(command, check=True)
def get_video_length(yt_url):
"""Retrieve video length (in seconds) from yt-dlp metadata."""
command = ["yt-dlp", "--dump-json", yt_url]
result = subprocess.run(command, capture_output=True, text=True, check=True)
data = json.loads(result.stdout)
return data.get("duration", 0)
def cut_video(input_file, start_time, end_time, output_file):
"""Cut the video segment using FFmpeg from start_time to end_time."""
duration = end_time - start_time
command = [
"ffmpeg",
"-y", # Overwrite output file if exists
"-ss", str(start_time),
"-i", input_file,
"-t", str(duration),
"-c", "copy",
output_file
]
subprocess.run(command, check=True)
def merge_videos(video_files, merged_filename):
"""Merge video segments using FFmpeg's concat demuxer."""
list_filename = "segments_list.txt"
with open(list_filename, "w") as f:
for vf in video_files:
f.write(f"file '{os.path.abspath(vf)}'\n")
merge_cmd = [
"ffmpeg",
"-y", # Overwrite output file if exists
"-f", "concat",
"-safe", "0",
"-i", list_filename,
"-c", "copy",
merged_filename
]
subprocess.run(merge_cmd, check=True)
os.remove(list_filename)
@app.route("/", methods=["GET", "POST"])
def index():
message = ""
transcript_text = ""
cut_results = [] # Messages for each extracted segment
output_files = [] # List of extracted segment filenames
if request.method == "POST":
yt_url = request.form.get("yt_url")
cut_method = request.form.get("cut_method") # "between" or "around"
trans_lang = request.form.get("trans_lang", "en").strip() # Selected language
merge_option = request.form.get("merge_segments") # "on" if checked
delete_option = request.form.get("delete_segments") # "on" if checked
video_id = extract_video_id(yt_url)
if not video_id:
message = "Invalid URL. Please enter a valid YouTube video URL."
else:
try:
# Retrieve transcript using the selected language
transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=[trans_lang])
transcript_text = "\n".join([f"[{seg['start']:.2f}] {seg['text']}" for seg in transcript])
# Download the video and get its length
video_filename = f"{video_id}.mp4"
download_video(yt_url, video_filename)
video_length = get_video_length(yt_url)
if cut_method == "between":
# Option 1: Between Precise Keywords
keyword_pairs_input = request.form.get("keyword_pairs", "").strip()
if keyword_pairs_input:
pairs = []
for line in keyword_pairs_input.splitlines():
if "," in line:
start_kw, end_kw = line.split(",", 1)
pairs.append((start_kw.strip().lower(), end_kw.strip().lower()))
else:
start_kw = request.form.get("start_keyword", "").strip().lower()
end_kw = request.form.get("end_keyword", "").strip().lower()
if start_kw and end_kw:
pairs = [(start_kw, end_kw)]
else:
pairs = []
if not pairs:
message = "Please provide at least one valid keyword pair."
else:
for idx, (start_keyword, end_keyword) in enumerate(pairs, start=1):
start_index = None
end_index = None
for i, seg in enumerate(transcript):
text_lower = seg['text'].lower()
if start_index is None and start_keyword in text_lower:
start_index = i
if start_index is not None and end_keyword in text_lower:
end_index = i
break
if start_index is None:
cut_results.append(f"Pair {idx}: Start keyword '{start_keyword}' not found.")
continue
if end_index is None:
cut_results.append(f"Pair {idx}: End keyword '{end_keyword}' not found after start keyword.")
continue
seg_start = transcript[start_index]['start']
seg_end = transcript[end_index]['start']
output_filename = f"{video_id}_between_pair_{idx}.mp4"
cut_video(video_filename, seg_start, seg_end, output_filename)
output_files.append(output_filename)
cut_results.append(f"Pair {idx}: Segment created: {output_filename} (from {seg_start:.2f}s to {seg_end:.2f}s)")
message = "Processing complete for 'Between Precise Keywords' method."
elif cut_method == "around":
# Option 2: Around KW with separate durations
around_keywords_input = request.form.get("around_keywords", "").strip()
d_before_str = request.form.get("duration_before", "").strip()
d_after_str = request.form.get("duration_after", "").strip()
process_all = request.form.get("process_all") # "on" if checked
if not around_keywords_input or not d_before_str or not d_after_str:
message = "Please provide at least one keyword, a 'duration before', and a 'duration after' (in seconds) for the 'Around KW' option."
else:
try:
d_before = float(d_before_str)
d_after = float(d_after_str)
except ValueError:
message = "Both durations must be numeric values (in seconds)."
return render_template("index.html", message=message, transcript=transcript_text, cut_results=cut_results)
# Split keywords on commas (entered on one line)
keywords = [kw.strip().lower() for kw in around_keywords_input.split(",") if kw.strip()]
if not keywords:
message = "Please provide at least one valid keyword."
else:
for kw in keywords:
occurrences = []
for seg in transcript:
if kw in seg['text'].lower():
occurrences.append(seg['start'])
if not occurrences:
cut_results.append(f"Keyword '{kw}' not found in the transcript.")
continue
if process_all != "on":
occurrences = occurrences[:1]
for idx, kw_time in enumerate(occurrences, start=1):
start_time = max(0, kw_time - d_before)
end_time = min(video_length, kw_time + d_after)
output_filename = f"{video_id}_around_{kw}_{idx}.mp4"
cut_video(video_filename, start_time, end_time, output_filename)
output_files.append(output_filename)
cut_results.append(f"Around '{kw}' occurrence {idx}: Segment created: {output_filename} (from {start_time:.2f}s to {end_time:.2f}s)")
message = "Processing complete for 'Around KW' method."
else:
message = "Please choose a cutting method."
# If merge option is checked and there are output files, merge them.
if merge_option == "on" and output_files:
merged_filename = f"{video_id}_merged.mp4"
merge_videos(output_files, merged_filename)
cut_results.append(f"Merged video created: {merged_filename}")
message += " All segments have been merged."
# If delete option is also checked, remove all individual segments and downloaded video.
if delete_option == "on":
for file in output_files:
if os.path.exists(file):
os.remove(file)
if os.path.exists(video_filename):
os.remove(video_filename)
cut_results.append("All individual segment files and the downloaded video have been deleted.")
except Exception as e:
message = f"Error during processing: {e}"
return render_template("index.html", message=message, transcript=transcript_text, cut_results=cut_results)
if __name__ == "__main__":
app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>YouTube Transcriber & Cutter</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<style>
/* Reset & Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Roboto', sans-serif;
background: linear-gradient(135deg, #eef2f3, #8e9eab);
color: #333;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
padding: 30px;
}
h1 {
text-align: center;
margin-bottom: 20px;
font-size: 2.2em;
font-weight: 700;
color: #2c3e50;
}
h2 {
margin-bottom: 10px;
color: #2c3e50;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #2c3e50;
}
input[type="text"],
input[type="number"],
textarea,
select {
width: 100%;
padding: 12px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1em;
}
input[type="radio"],
input[type="checkbox"] {
margin-right: 5px;
}
input[type="submit"] {
width: 100%;
padding: 15px;
background: #3498db;
color: #fff;
border: none;
font-size: 1.1em;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s ease;
}
input[type="submit"]:hover {
background: #2980b9;
}
.result {
background: #f7f7f7;
border: 1px solid #ddd;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
}
.error {
color: #e74c3c;
margin-bottom: 20px;
font-weight: 500;
}
p {
margin-bottom: 15px;
}
</style>
</head>
<body>
<div class="container">
<h1>YouTube Transcriber & Cutter</h1>
<form method="post">
<label for="yt_url">Enter YouTube Video URL:</label>
<input type="text" id="yt_url" name="yt_url" placeholder="https://www.youtube.com/watch?v=..." required>
<label for="trans_lang">Select Transcription Language:</label>
<select name="trans_lang" id="trans_lang">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="zh">Chinese</option>
<option value="hi">Hindi</option>
<option value="ar">Arabic</option>
<option value="fr">French</option>
<option value="ru">Russian</option>
<option value="pt">Portuguese</option>
<option value="id">Indonesian</option>
<option value="bn">Bengali</option>
</select>
<label>Select Cutting Method:</label>
<input type="radio" id="between" name="cut_method" value="between" checked>
<label for="between" style="display: inline;">Between Precise Keywords</label><br>
<input type="radio" id="around" name="cut_method" value="around">
<label for="around" style="display: inline;">Around KW</label>
<br><br>
<!-- Fields for "Between Precise Keywords" option -->
<div id="between_fields">
<p>You can either enter a single keyword pair:</p>
<label for="start_keyword">Start Keyword:</label>
<input type="text" id="start_keyword" name="start_keyword" placeholder="e.g., Introduction">
<label for="end_keyword">End Keyword:</label>
<input type="text" id="end_keyword" name="end_keyword" placeholder="e.g., Conclusion">
<p>Or provide multiple keyword pairs (one pair per line in the format: start_keyword,end_keyword):</p>
<textarea id="keyword_pairs" name="keyword_pairs" rows="4" placeholder="e.g., Introduction, Conclusion Overview, Summary"></textarea>
</div>
<!-- Fields for "Around KW" option -->
<div id="around_fields" style="display:none;">
<p>Enter one or more keywords on a single line (separated by commas):</p>
<input type="text" id="around_keywords" name="around_keywords" placeholder="e.g., key insight, critical point">
<label for="duration_before">Duration Before Keyword (in seconds):</label>
<input type="number" id="duration_before" name="duration_before" placeholder="e.g., 5" step="0.1">
<label for="duration_after">Duration After Keyword (in seconds):</label>
<input type="number" id="duration_after" name="duration_after" placeholder="e.g., 10" step="0.1">
<input type="checkbox" id="process_all" name="process_all">
<label for="process_all" style="display: inline;">Process all keyword occurrences (if unchecked, only the first occurrence is processed)</label>
</div>
<!-- Merge option for both cutting methods -->
<div>
<input type="checkbox" id="merge_segments" name="merge_segments">
<label for="merge_segments" style="display: inline;">Merge all extracted segments into one video file</label>
</div>
<br>
<!-- Delete option (only applicable if merge is checked) -->
<div>
<input type="checkbox" id="delete_segments" name="delete_segments">
<label for="delete_segments" style="display: inline;">Delete individual segments and downloaded video after merging</label>
</div>
<br>
<input type="submit" value="Process Video">
</form>
{% if message %}
<p><strong>{{ message }}</strong></p>
{% endif %}
{% if transcript %}
<h2>Full Transcript:</h2>
<div class="result">
<textarea rows="15" readonly>{{ transcript }}</textarea>
</div>
{% endif %}
{% if cut_results %}
<h2>Video Cut Results:</h2>
<div class="result">
<ul>
{% for result in cut_results %}
<li>{{ result }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<script>
// Toggle form fields based on selected cutting method
const betweenRadio = document.getElementById("between");
const aroundRadio = document.getElementById("around");
const betweenFields = document.getElementById("between_fields");
const aroundFields = document.getElementById("around_fields");
function toggleFields() {
if (betweenRadio.checked) {
betweenFields.style.display = "block";
aroundFields.style.display = "none";
} else if (aroundRadio.checked) {
betweenFields.style.display = "none";
aroundFields.style.display = "block";
}
}
betweenRadio.addEventListener("change", toggleFields);
aroundRadio.addEventListener("change", toggleFields);
// Initialize on page load
toggleFields();
</script>
</body>
</html>




Laisser un commentaire