j-r-b commited on
Commit
7c2ea61
·
verified ·
1 Parent(s): 162f606

Upload 7 files

Browse files
Files changed (7) hide show
  1. Dockerfile +19 -0
  2. README.md +49 -12
  3. package.json +19 -0
  4. public/index.html +48 -0
  5. public/script.js +234 -0
  6. public/style.css +94 -0
  7. server.js +75 -0
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy package files
6
+ COPY package.json ./
7
+
8
+ # Install dependencies
9
+ RUN npm install
10
+
11
+ # Copy application files
12
+ COPY server.js ./
13
+ COPY public ./public
14
+
15
+ # Expose the port
16
+ EXPOSE 7860
17
+
18
+ # Start the server
19
+ CMD ["node", "server.js"]
README.md CHANGED
@@ -1,12 +1,49 @@
1
- ---
2
- title: ResumeVideo
3
- emoji: 🏆
4
- colorFrom: pink
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: Résume des vidéos
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Résumé Vidéo YouTube
2
+
3
+ Cette application web permet de résumer automatiquement le contenu d'une vidéo YouTube à partir de sa transcription.
4
+
5
+ ## Fonctionnalités
6
+
7
+ - Extraction de la transcription de vidéos YouTube
8
+ - Résumé automatique par analyse statistique du texte
9
+ - Interface simple et réactive
10
+
11
+ ## Comment utiliser
12
+
13
+ 1. Entrez l'URL d'une vidéo YouTube dans le champ prévu
14
+ 2. Sélectionnez le nombre de phrases souhaitées pour le résumé
15
+ 3. Cliquez sur "Résumer la Vidéo"
16
+ 4. Consultez la transcription complète et le résumé généré
17
+
18
+ ## Limitations
19
+
20
+ - Fonctionne uniquement avec les vidéos YouTube disposant d'une transcription (activée par le créateur ou générée automatiquement par YouTube)
21
+ - L'algorithme de résumé est basique (sélection statistique des phrases importantes)
22
+ - Supporte principalement les textes en français
23
+
24
+ ## Technique
25
+
26
+ - Frontend: HTML, CSS, JavaScript vanilla
27
+ - Backend: Node.js avec Express
28
+ - Conteneurisation: Docker
29
+ - Déployé sur Hugging Face Spaces
30
+
31
+ ## Local Development
32
+
33
+ Pour exécuter l'application localement:
34
+
35
+ ```bash
36
+ # Cloner le dépôt
37
+ git clone [URL_DU_REPO]
38
+
39
+ # Aller dans le répertoire
40
+ cd huggingface
41
+
42
+ # Installer les dépendances
43
+ npm install
44
+
45
+ # Démarrer le serveur
46
+ npm start
47
+ ```
48
+
49
+ L'application sera disponible sur http://localhost:7860
package.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "resume-video-youtube",
3
+ "version": "1.0.0",
4
+ "description": "Application pour résumer des vidéos YouTube à partir de leur transcription",
5
+ "main": "server.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node server.js"
9
+ },
10
+ "dependencies": {
11
+ "express": "^4.18.2",
12
+ "youtube-transcript": "^1.0.6"
13
+ },
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "author": "",
18
+ "license": "MIT"
19
+ }
public/index.html ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Résumé Vidéo YouTube</title>
8
+ <link rel="stylesheet" href="style.css">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="container">
13
+ <h1>Résumé de Vidéo YouTube</h1>
14
+ <p>
15
+ Cette application utilise un algorithme JavaScript exécuté dans votre navigateur
16
+ pour résumer le contenu textuel (transcription) d'une vidéo YouTube.
17
+ </p>
18
+
19
+ <label for="youtube-url">URL de la vidéo YouTube :</label>
20
+ <input type="text" id="youtube-url" placeholder="Ex: https://www.youtube.com/watch?v=dQw4w9WgXcQ">
21
+
22
+ <label for="summary-length">Nombre de phrases pour le résumé :</label>
23
+ <input type="number" id="summary-length" value="5" min="1" max="20">
24
+
25
+ <button id="summarize-btn">Résumer la Vidéo</button>
26
+
27
+ <div id="loading" style="display:none;">Chargement de la transcription et résumé en cours...</div>
28
+ <div id="error-message" class="error" style="display:none;"></div>
29
+
30
+ <h2>Transcription Complète :</h2>
31
+ <div id="transcript-output" class="output-box">
32
+ <p>La transcription apparaîtra ici...</p>
33
+ </div>
34
+
35
+ <h2>Résumé :</h2>
36
+ <div id="summary-output" class="output-box">
37
+ <p>Le résumé apparaîtra ici...</p>
38
+ </div>
39
+
40
+ <footer>
41
+ <p>Déployé sur <a href="https://huggingface.co/spaces" target="_blank">Hugging Face Spaces</a></p>
42
+ </footer>
43
+ </div>
44
+
45
+ <script src="script.js"></script>
46
+ </body>
47
+
48
+ </html>
public/script.js ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const urlInput = document.getElementById('youtube-url');
3
+ const summaryLengthInput = document.getElementById('summary-length');
4
+ const summarizeBtn = document.getElementById('summarize-btn');
5
+ const transcriptOutput = document.getElementById('transcript-output');
6
+ const summaryOutput = document.getElementById('summary-output');
7
+ const loadingDiv = document.getElementById('loading');
8
+ const errorDiv = document.getElementById('error-message');
9
+
10
+ // Liste de mots vides (français, à étendre)
11
+ // Source simple, pour une meilleure qualité, utiliser une liste plus complète
12
+ const STOP_WORDS_FR = new Set([
13
+ "a", "afin", "ah", "ai", "aie", "aient", "aies", "ailleurs", "ainsi", "ait",
14
+ "alors", "au", "aucun", "aucune", "aujourd", "aujourd'hui", "auquel", "aura",
15
+ "aurai", "auraient", "aurais", "aurait", "auras", "aurez", "auriez", "aurions",
16
+ "aurons", "auront", "aussi", "autre", "autres", "autrui", "aux", "auxquelles",
17
+ "auxquels", "avaient", "avais", "avait", "avant", "avec", "avez", "aviez",
18
+ "avions", "avoir", "avons", "ayant", "ayez", "ayons", "b", "bah", "beaucoup",
19
+ "bien", "bon", "c", "ça", "car", "ce", "ceci", "cela", "celle", "celle-ci",
20
+ "celle-là", "celles", "celles-ci", "celles-là", "celui", "celui-ci", "celui-là",
21
+ "cent", "cents", "cependant", "certain", "certaine", "certaines", "certains",
22
+ "ces", "cet", "cette", "ceux", "ceux-ci", "ceux-là", "chacun", "chacune",
23
+ "chaque", "cher", "chère", "chères", "chers", "chez", "ci", "cinq", "cinquante",
24
+ "cinquième", "comme", "comment", "d", "dans", "de", "debout", "dedans", "dehors",
25
+ "delà", "depuis", "dernier", "dernière", "dernières", "derniers", "des", "dès",
26
+ "deux", "deuxième", "devant", "devers", "devra", "devrai", "devraient", "devrais",
27
+ "devrait", "devras", "devrez", "devriez", "devrions", "devrons", "devront", " διαφορετικα ",
28
+ "dix", "dix-huit", "dix-neuf", "dix-sept", "dixième", "donc", "dont", "douze",
29
+ "douzième", "du", "dû", "duquel", "durant", "e", "eh", "elle", "elle-même",
30
+ "elles", "elles-mêmes", "en", "encore", "enfin", "entre", "envers", "environ",
31
+ "es", "ès", "est", "et", "etant", "étaient", "étais", "était", "étant", "etc",
32
+ "été", "êtes", "être", "eu", "eue", "eues", "eurent", "eus", "eusse", "eussent",
33
+ "eusses", "eussiez", "eussions", "eut", "eût", "eûmes", "eûtes", "eux", "eux-mêmes",
34
+ "f", "faire", "fais", "faisaient", "faisais", "faisait", "faisant", "faisons",
35
+ "fait", "faites", "faudra", "faudrait", "faut", "fi", "flac", "floc", "fois",
36
+ "font", "force", "fors", "g", "gens", "h", "ha", "hé", "hein", "hélas", "hem",
37
+ "hep", "hi", "ho", "holà", "hop", "hormis", "hors", "hou", "houp", "hue", "hui",
38
+ "huit", "huitième", "hum", "hurrah", "i", "ici", "il", "ils", "j", "j'", "je",
39
+ "jusqu'", "jusqu'au", "jusqu'aux", "jusqu'à", "jusque", "k", "l", "la", "là",
40
+ "laquelle", "le", "lequel", "les", "lès", "lesquelles", "lesquels", "leur",
41
+ "leurs", "lez", "loin", "longtemps", "lors", "lorsque", "lui", "lui-même", "m",
42
+ "ma", "maint", "maintenant", "mais", "malgré", "me", "même", "mêmes", "merci",
43
+ "mes", "mien", "mienne", "miennes", "miens", "mille", "mince", "mine", "moi",
44
+ "moi-même", "moins", "mon", "mot", "moyennant", "n", "na", "ne", "néanmoins",
45
+ "neuf", "neuvième", "ni", "nombreuses", "nombreux", "non", "nos", "notre",
46
+ "nôtre", "nôtres", "nous", "nous-mêmes", "nul", "o", "ô", "où", "oui", "on",
47
+ "ont", "onze", "onzième", "or", "ou", "où", "outre", "p", "par", "parce", "parmi",
48
+ "partant", "particulièrement", "pas", "passé", "pendant", "personne", "peu",
49
+ "peut", "peuvent", "peux", "pf", "pff", "pfi", "pfu", "pif", "plein", "plus",
50
+ "plusieurs", "plutôt", "pour", "pourquoi", "pourra", "pourrai", "pourraient",
51
+ "pourrais", "pourrait", "pourras", "pourrez", "pourriez", "pourrions", "pourrons",
52
+ "pourront", "pouvait", "pouvez", " pouvions ", "pouvons", "premier", "première",
53
+ "premièrement", "près", "presque", "prouf", "psitt", "pu", "puis", "puisque",
54
+ "q", "qu'", "quand", "quant", "quanta", "quant-à-soi", "quarante", "quatorze",
55
+ "quatre", "quatre-vingt", "quatre-vingt-dix", "quatre-vingt-onze", "quatre-vingt-un",
56
+ "quatrième", "quatrièmement", "que", "quel", "quelconque", "quelle", "quelles",
57
+ "quelqu'un", "quelque", "quelques", "quels", "qui", "quiconque", "quinze",
58
+ "quoi", "quoique", "r", "revoici", "revoilà", "rien", "s", "sa", "sacrebleu",
59
+ "sans", "sapristi", "sauf", "se", "seize", "selon", "sept", "septième", "sera",
60
+ "serai", "seraient", "serais", "serait", "seras", "serez", "seriez", "serions",
61
+ "serons", "seront", "ses", "seulement", "si", "sien", "sienne", "siennes",
62
+ "siens", "sinon", "six", "sixième", "soi", "soi-même", "soient", "sois", "soit",
63
+ "soixante", "sommes", "son", "sont", "sous", "stop", "suis", "suite", "sur",
64
+ "surtout", "t", "ta", "tac", "tandis", "tant", "tardivement", "te", "tel",
65
+ "telle", "telles", "tels", "tenant", "tes", "tic", "tien", "tienne", "tiennes",
66
+ "tiens", "toc", "toi", "toi-même", "ton", "tos", "tôt", "toute", "toutefois",
67
+ "toutes", "tous", "tout", "treize", "trente", "très", "trois", "troisième",
68
+ "troisièmement", "trop", "tu", "u", "un", "une", "unes", "uns", "v", "va", "vais",
69
+ "valeur", "vas", "vé", "vers", "via", "vif", "vifs", "vingt", "vivat", "vive",
70
+ "vives", "voici", "voilà", "vont", "vos", "votre", "vôtre", "vôtres", "vous",
71
+ "vous-mêmes", "vs", "vu", "w", "x", "y", "z", "zut", "alors", "au", "aucuns", "aussi",
72
+ "autre", "avant", "avec", "avoir", "bon", "car", "ce", "cela", "ces", "ceux",
73
+ "chaque", "ci", "comme", "comment", "dans", "des", "du", "dedans", "dehors",
74
+ "depuis", "deux", "devrait", "doit", "donc", "dos", "droite", "début", "elle",
75
+ "elles", "en", "encore", "essai", "est", "et", "eu", "fait", "faites", "fois",
76
+ "font", "force", "haut", "hors", "ici", "il", "ils", "je", "juste", "la", "le",
77
+ "les", "leur", "là", "ma", "maintenant", "mais", "mes", "mine", "moins", "mon",
78
+ "mot", "même", "ni", "nommés", "notre", "nous", "nouveaux", "ou", "où", "par",
79
+ "parce", "parole", "pas", "personnes", "peut", "peu", "pièce", "plupart", "pour",
80
+ "pourquoi", "quand", "que", "quel", "quelle", "quelles", "quels", "qui", "sa",
81
+ "sans", "ses", "seulement", "si", "sien", "son", "sont", "sous", "soyez", "sujet",
82
+ "sur", "ta", "tandis", "tellement", "tels", "tes", "ton", "tous", "tout", "trop",
83
+ "très", "tu", "valeur", "voie", "voient", "vont", "vos", "votre", "vous", "vu",
84
+ "ça", "étaient", "état", "étions", "été", "être", "pour", "que", "qui", "il", "elle", "on", "y"
85
+ ]);
86
+
87
+
88
+ summarizeBtn.addEventListener('click', async () => {
89
+ const url = urlInput.value.trim();
90
+ const numSentences = parseInt(summaryLengthInput.value, 10);
91
+
92
+ if (!url) {
93
+ showError("Veuillez entrer une URL YouTube.");
94
+ return;
95
+ }
96
+ if (isNaN(numSentences) || numSentences < 1) {
97
+ showError("Veuillez entrer un nombre de phrases valide pour le résumé.");
98
+ return;
99
+ }
100
+
101
+ showLoading(true);
102
+ hideError();
103
+ transcriptOutput.innerHTML = "<p>Récupération de la transcription...</p>";
104
+ summaryOutput.innerHTML = "<p>En attente de la transcription...</p>";
105
+
106
+ try {
107
+ const response = await fetch(`/get-transcript?url=${encodeURIComponent(url)}`);
108
+ const data = await response.json();
109
+
110
+ if (!response.ok) {
111
+ throw new Error(data.error || `Erreur HTTP: ${response.status}`);
112
+ }
113
+
114
+ const fullTranscript = data.transcript;
115
+ if (!fullTranscript || fullTranscript.trim() === "") {
116
+ showError("La transcription est vide ou n'a pas pu être récupérée.");
117
+ transcriptOutput.innerHTML = "<p>Aucune transcription reçue.</p>";
118
+ summaryOutput.innerHTML = "<p>Impossible de résumer sans transcription.</p>";
119
+ showLoading(false);
120
+ return;
121
+ }
122
+
123
+ transcriptOutput.textContent = fullTranscript;
124
+
125
+ // "IA" locale : résumé
126
+ const summary = await summarizeText(fullTranscript, numSentences);
127
+ summaryOutput.textContent = summary;
128
+
129
+ } catch (err) {
130
+ console.error("Erreur dans le processus :", err);
131
+ showError(`Erreur: ${err.message}`);
132
+ transcriptOutput.innerHTML = "<p>Échec de la récupération.</p>";
133
+ summaryOutput.innerHTML = "<p>Échec du résumé.</p>";
134
+ } finally {
135
+ showLoading(false);
136
+ }
137
+ });
138
+
139
+ function showLoading(isLoading) {
140
+ loadingDiv.style.display = isLoading ? 'block' : 'none';
141
+ }
142
+
143
+ function showError(message) {
144
+ errorDiv.textContent = message;
145
+ errorDiv.style.display = 'block';
146
+ }
147
+
148
+ function hideError() {
149
+ errorDiv.style.display = 'none';
150
+ }
151
+
152
+ // --- Fonctions de résumé (l'"IA" locale) ---
153
+
154
+ function tokenize(text) {
155
+ // Simpliste : enlève la ponctuation basique et met en minuscule
156
+ return text.toLowerCase().replace(/[^\w\s'-]|_/g, "").replace(/\s+/g, " ").trim().split(' ');
157
+ }
158
+
159
+ function getSentences(text) {
160
+ // Segmentation basique par point, point d'interrogation, point d'exclamation.
161
+ // Peut être amélioré avec des expressions régulières plus complexes.
162
+ // Nettoie les timestamps typiques comme [00:00:00] ou (00:00:00)
163
+ const cleanedText = text.replace(/\[\d{2}:\d{2}:\d{2}\]/g, '')
164
+ .replace(/\(\d{2}:\d{2}:\d{2}\)/g, '');
165
+ return cleanedText.split(/[.!?]+\s*/).filter(s => s.trim().length > 0).map(s => s.trim());
166
+ }
167
+
168
+ function calculateWordFrequencies(words) {
169
+ const freq = {};
170
+ words.forEach(word => {
171
+ if (!STOP_WORDS_FR.has(word) && word.length > 2) { // Ignore les mots vides et très courts
172
+ freq[word] = (freq[word] || 0) + 1;
173
+ }
174
+ });
175
+ return freq;
176
+ }
177
+
178
+ function scoreSentences(sentences, wordFrequencies) {
179
+ const sentenceScores = [];
180
+ sentences.forEach((sentence, index) => {
181
+ const wordsInSentence = tokenize(sentence);
182
+ let score = 0;
183
+ wordsInSentence.forEach(word => {
184
+ if (wordFrequencies[word]) {
185
+ score += wordFrequencies[word];
186
+ }
187
+ });
188
+ // On pourrait ajouter un bonus pour la position (ex: premières phrases)
189
+ // if (index < 3) score *= 1.2; // Bonus pour les 3 premières phrases
190
+ sentenceScores.push({ sentence: sentence, score: score, index: index });
191
+ });
192
+ return sentenceScores;
193
+ }
194
+
195
+ async function summarizeText(text, numSentences) {
196
+ if (!text || text.trim() === "") return "Le texte fourni est vide.";
197
+
198
+ try {
199
+ // 1. Découper le texte en phrases
200
+ const sentences = getSentences(text);
201
+
202
+ if (sentences.length <= numSentences) {
203
+ return text; // Si le texte est déjà plus court que le résumé demandé, retourner tout
204
+ }
205
+
206
+ // 2. Tokeniser le texte pour l'analyse des fréquences
207
+ const words = tokenize(text);
208
+
209
+ // 3. Calculer les fréquences des mots (sauf mots vides)
210
+ const wordFrequencies = calculateWordFrequencies(words);
211
+
212
+ // 4. Calculer un score pour chaque phrase basé sur les mots importants
213
+ const sentenceScores = scoreSentences(sentences, wordFrequencies);
214
+
215
+ // 5. Trier les phrases par score (importance) décroissant
216
+ sentenceScores.sort((a, b) => b.score - a.score);
217
+
218
+ // 6. Prendre les N phrases les plus importantes
219
+ const topSentences = sentenceScores.slice(0, numSentences);
220
+
221
+ // 7. Retrier les phrases selon leur position originale dans le texte
222
+ topSentences.sort((a, b) => a.index - b.index);
223
+
224
+ // 8. Former le résumé final
225
+ const summary = topSentences.map(item => item.sentence).join('. ');
226
+
227
+ return summary + '.';
228
+
229
+ } catch (error) {
230
+ console.error("Erreur lors de la création du résumé:", error);
231
+ return "Une erreur s'est produite lors de la création du résumé.";
232
+ }
233
+ }
234
+ });
public/style.css ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: sans-serif;
3
+ line-height: 1.6;
4
+ margin: 0;
5
+ padding: 20px;
6
+ background-color: #f4f4f4;
7
+ color: #333;
8
+ }
9
+
10
+ .container {
11
+ max-width: 800px;
12
+ margin: auto;
13
+ background: #fff;
14
+ padding: 20px;
15
+ border-radius: 8px;
16
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
17
+ }
18
+
19
+ h1, h2 {
20
+ color: #333;
21
+ text-align: center;
22
+ }
23
+
24
+ label {
25
+ display: block;
26
+ margin-bottom: 5px;
27
+ font-weight: bold;
28
+ }
29
+
30
+ input[type="text"], input[type="number"] {
31
+ width: calc(100% - 22px);
32
+ padding: 10px;
33
+ margin-bottom: 15px;
34
+ border: 1px solid #ddd;
35
+ border-radius: 4px;
36
+ }
37
+
38
+ button {
39
+ display: block;
40
+ width: 100%;
41
+ padding: 10px;
42
+ background-color: #5cb85c;
43
+ color: white;
44
+ border: none;
45
+ border-radius: 4px;
46
+ cursor: pointer;
47
+ font-size: 16px;
48
+ }
49
+
50
+ button:hover {
51
+ background-color: #4cae4c;
52
+ }
53
+
54
+ .output-box {
55
+ margin-top: 20px;
56
+ padding: 15px;
57
+ background-color: #e9e9e9;
58
+ border: 1px solid #ccc;
59
+ border-radius: 4px;
60
+ min-height: 100px;
61
+ white-space: pre-wrap; /* Conserve les sauts de ligne et espaces */
62
+ word-wrap: break-word;
63
+ }
64
+
65
+ .error {
66
+ color: red;
67
+ background-color: #ffe0e0;
68
+ border: 1px solid red;
69
+ padding: 10px;
70
+ margin-top: 15px;
71
+ border-radius: 4px;
72
+ }
73
+
74
+ #loading {
75
+ text-align: center;
76
+ padding: 10px;
77
+ color: #555;
78
+ }
79
+
80
+ footer {
81
+ margin-top: 20px;
82
+ text-align: center;
83
+ font-size: 0.9em;
84
+ color: #777;
85
+ }
86
+
87
+ footer a {
88
+ color: #5cb85c;
89
+ text-decoration: none;
90
+ }
91
+
92
+ footer a:hover {
93
+ text-decoration: underline;
94
+ }
server.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ import { YoutubeTranscript } from 'youtube-transcript';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ // Configuration pour __dirname avec ES Modules
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ const app = express();
11
+ // Hugging Face Spaces utilise le port 7860 par défaut
12
+ const port = process.env.PORT || 7860;
13
+
14
+ // Servir les fichiers statiques du dossier 'public'
15
+ app.use(express.static(path.join(__dirname, 'public')));
16
+
17
+ app.get('/get-transcript', async (req, res) => {
18
+ const videoUrl = req.query.url;
19
+ if (!videoUrl) {
20
+ return res.status(400).json({ error: 'URL de la vidéo manquante' });
21
+ }
22
+
23
+ try {
24
+ // Essayer d'extraire l'ID de différentes formes d'URL YouTube
25
+ let videoId = '';
26
+ if (videoUrl.includes('v=')) {
27
+ videoId = videoUrl.split('v=')[1].split('&')[0];
28
+ } else if (videoUrl.includes('youtu.be/')) {
29
+ videoId = videoUrl.split('youtu.be/')[1].split('?')[0];
30
+ } else {
31
+ // On pourrait ajouter d'autres formats d'URL ici (shorts, etc.)
32
+ // Pour l'instant, on assume que c'est un ID direct si ce n'est pas une URL connue
33
+ videoId = videoUrl;
34
+ }
35
+
36
+ if (!videoId) {
37
+ return res.status(400).json({ error: "Impossible d'extraire l'ID de la vidéo depuis l'URL." });
38
+ }
39
+
40
+ console.log(`Fetching transcript for video ID: ${videoId}`);
41
+ const transcript = await YoutubeTranscript.fetchTranscript(videoId);
42
+
43
+ if (!transcript || transcript.length === 0) {
44
+ return res.status(404).json({ error: 'Transcription non trouvée ou vide pour cette vidéo.' });
45
+ }
46
+
47
+ // Concaténer les textes de la transcription
48
+ const fullText = transcript.map(item => item.text).join(' ');
49
+ res.json({ transcript: fullText });
50
+
51
+ } catch (error) {
52
+ console.error('Erreur lors de la récupération de la transcription:', error);
53
+ if (error.message && error.message.includes('Could not find transcripts')) {
54
+ return res.status(404).json({ error: "Aucune transcription disponible pour cette vidéo (elles sont peut-être désactivées ou n'existent pas en auto-généré)." });
55
+ }
56
+ if (error.message && error.message.includes('is not a valid video ID')) {
57
+ return res.status(400).json({ error: `L'ID vidéo extrait ('${error.message.split("'")[1]}') n'est pas valide. Vérifiez l'URL.` });
58
+ }
59
+ res.status(500).json({ error: 'Erreur interne du serveur lors de la récupération de la transcription.' });
60
+ }
61
+ });
62
+
63
+ // Route pour vérifier l'état du serveur (utile pour Hugging Face)
64
+ app.get('/health', (req, res) => {
65
+ res.status(200).json({ status: 'ok' });
66
+ });
67
+
68
+ // Route par défaut
69
+ app.get('/', (req, res) => {
70
+ res.sendFile(path.join(__dirname, 'public', 'index.html'));
71
+ });
72
+
73
+ app.listen(port, '0.0.0.0', () => {
74
+ console.log(`Serveur démarré sur http://0.0.0.0:${port}`);
75
+ });