Update app.py
Browse files
app.py
CHANGED
|
@@ -11,20 +11,24 @@ from reportlab.lib.units import inch
|
|
| 11 |
from datetime import datetime
|
| 12 |
import secrets
|
| 13 |
import re
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
app = Flask(__name__)
|
| 16 |
app.secret_key = secrets.token_hex(16)
|
| 17 |
app.config['UPLOAD_FOLDER'] = 'uploads'
|
| 18 |
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB max
|
|
|
|
| 19 |
|
| 20 |
-
# Créer
|
| 21 |
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
| 22 |
|
| 23 |
# Configuration Gemini
|
| 24 |
GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY', 'YOUR_API_KEY_HERE')
|
| 25 |
client = genai.Client(api_key=GEMINI_API_KEY)
|
| 26 |
|
| 27 |
-
# Limite de tokens pour les vidéos
|
| 28 |
MAX_VIDEO_TOKENS = 600000
|
| 29 |
|
| 30 |
ALLOWED_EXTENSIONS = {
|
|
@@ -43,6 +47,113 @@ SUMMARY_TYPES = {
|
|
| 43 |
'detaille': 'Fais un résumé exhaustif et détaillé avec tous les points importants, citations clés et analyse approfondie.'
|
| 44 |
}
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
def allowed_file(filename):
|
| 47 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
| 48 |
|
|
@@ -55,47 +166,68 @@ def is_youtube_url(url):
|
|
| 55 |
youtube_regex = r'(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)[\w-]+'
|
| 56 |
return bool(re.match(youtube_regex, url))
|
| 57 |
|
| 58 |
-
def
|
| 59 |
-
"""
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
def
|
| 69 |
-
"""
|
| 70 |
-
mime_type = get_mime_type(filename)
|
| 71 |
-
prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen'])
|
| 72 |
-
|
| 73 |
try:
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
uploaded_file = client.files.upload(file=file_path)
|
| 76 |
|
|
|
|
|
|
|
| 77 |
# Génère le résumé
|
| 78 |
response = client.models.generate_content(
|
| 79 |
model="gemini-2.5-flash",
|
| 80 |
contents=[uploaded_file, prompt]
|
| 81 |
)
|
| 82 |
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
| 84 |
except Exception as e:
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
def generate_summary_from_youtube(youtube_url, summary_type):
|
| 88 |
-
"""Génère un résumé à partir d'une URL YouTube"""
|
| 89 |
-
prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen'])
|
| 90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
# Vérifier les informations du modèle
|
| 93 |
model_info = client.models.get(model="gemini-2.5-flash")
|
| 94 |
context_window = model_info.input_token_limit
|
| 95 |
|
| 96 |
-
|
| 97 |
|
| 98 |
-
# Générer le contenu
|
| 99 |
response = client.models.generate_content(
|
| 100 |
model='gemini-2.5-flash',
|
| 101 |
contents=types.Content(
|
|
@@ -108,13 +240,19 @@ def generate_summary_from_youtube(youtube_url, summary_type):
|
|
| 108 |
)
|
| 109 |
)
|
| 110 |
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
| 112 |
except Exception as e:
|
| 113 |
error_msg = str(e)
|
| 114 |
-
# Vérifier si l'erreur est liée à la limite de tokens
|
| 115 |
if 'token' in error_msg.lower() or 'too large' in error_msg.lower():
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
def create_pdf(summary_text, original_filename, summary_type):
|
| 120 |
"""Crée un PDF du résumé"""
|
|
@@ -123,7 +261,6 @@ def create_pdf(summary_text, original_filename, summary_type):
|
|
| 123 |
styles = getSampleStyleSheet()
|
| 124 |
story = []
|
| 125 |
|
| 126 |
-
# Titre
|
| 127 |
title_style = ParagraphStyle(
|
| 128 |
'CustomTitle',
|
| 129 |
parent=styles['Heading1'],
|
|
@@ -133,13 +270,11 @@ def create_pdf(summary_text, original_filename, summary_type):
|
|
| 133 |
title = Paragraph(f"Résumé: {original_filename}", title_style)
|
| 134 |
story.append(title)
|
| 135 |
|
| 136 |
-
# Info
|
| 137 |
info_style = styles['Normal']
|
| 138 |
info = Paragraph(f"Type: {summary_type.upper()} | Date: {datetime.now().strftime('%d/%m/%Y %H:%M')}", info_style)
|
| 139 |
story.append(info)
|
| 140 |
story.append(Spacer(1, 0.3*inch))
|
| 141 |
|
| 142 |
-
# Contenu du résumé
|
| 143 |
for paragraph in summary_text.split('\n\n'):
|
| 144 |
if paragraph.strip():
|
| 145 |
p = Paragraph(paragraph.replace('\n', '<br/>'), styles['Normal'])
|
|
@@ -150,14 +285,17 @@ def create_pdf(summary_text, original_filename, summary_type):
|
|
| 150 |
buffer.seek(0)
|
| 151 |
return buffer
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
@app.route('/')
|
| 154 |
def index():
|
| 155 |
-
if 'history' not in session:
|
| 156 |
-
session['history'] = []
|
| 157 |
return render_template('index.html')
|
| 158 |
|
| 159 |
@app.route('/upload', methods=['POST'])
|
| 160 |
def upload_file():
|
|
|
|
| 161 |
youtube_url = request.form.get('youtube_url', '').strip()
|
| 162 |
summary_type = request.form.get('summary_type', 'moyen')
|
| 163 |
|
|
@@ -166,39 +304,30 @@ def upload_file():
|
|
| 166 |
if not is_youtube_url(youtube_url):
|
| 167 |
return jsonify({'error': 'URL YouTube invalide'}), 400
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
'date': datetime.now().strftime('%d/%m/%Y %H:%M'),
|
| 186 |
-
'filepath': youtube_url
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
-
session['history'].append(history_item)
|
| 190 |
-
session.modified = True
|
| 191 |
-
|
| 192 |
-
return jsonify({
|
| 193 |
-
'success': True,
|
| 194 |
-
'summary': summary,
|
| 195 |
-
'history_id': history_item['id']
|
| 196 |
-
})
|
| 197 |
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
-
# Traitement fichier
|
| 202 |
if 'file' not in request.files:
|
| 203 |
return jsonify({'error': 'Aucun fichier ou URL YouTube fourni'}), 400
|
| 204 |
|
|
@@ -211,65 +340,142 @@ def upload_file():
|
|
| 211 |
return jsonify({'error': 'Type de fichier non supporté'}), 400
|
| 212 |
|
| 213 |
filename = secure_filename(file.filename)
|
| 214 |
-
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
| 215 |
file.save(filepath)
|
| 216 |
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
})
|
| 242 |
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
@app.route('/download/<int:history_id>')
|
| 251 |
-
def download_pdf(history_id):
|
| 252 |
-
if 'history' not in session or history_id >= len(session['history']):
|
| 253 |
-
return jsonify({'error': 'Résumé non trouvé'}), 404
|
| 254 |
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
return send_file(
|
| 259 |
pdf_buffer,
|
| 260 |
mimetype='application/pdf',
|
| 261 |
as_attachment=True,
|
| 262 |
-
download_name=f"resume_{
|
| 263 |
)
|
| 264 |
|
| 265 |
-
@app.route('/
|
| 266 |
-
def
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
-
@app.route('/clear-
|
| 270 |
-
def
|
| 271 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
return jsonify({'success': True})
|
| 273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
if __name__ == '__main__':
|
| 275 |
app.run(debug=True)
|
|
|
|
| 11 |
from datetime import datetime
|
| 12 |
import secrets
|
| 13 |
import re
|
| 14 |
+
import sqlite3
|
| 15 |
+
import threading
|
| 16 |
+
import time
|
| 17 |
+
import uuid
|
| 18 |
|
| 19 |
app = Flask(__name__)
|
| 20 |
app.secret_key = secrets.token_hex(16)
|
| 21 |
app.config['UPLOAD_FOLDER'] = 'uploads'
|
| 22 |
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB max
|
| 23 |
+
app.config['DATABASE'] = 'tasks.db'
|
| 24 |
|
| 25 |
+
# Créer les dossiers nécessaires
|
| 26 |
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
| 27 |
|
| 28 |
# Configuration Gemini
|
| 29 |
GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY', 'YOUR_API_KEY_HERE')
|
| 30 |
client = genai.Client(api_key=GEMINI_API_KEY)
|
| 31 |
|
|
|
|
| 32 |
MAX_VIDEO_TOKENS = 600000
|
| 33 |
|
| 34 |
ALLOWED_EXTENSIONS = {
|
|
|
|
| 47 |
'detaille': 'Fais un résumé exhaustif et détaillé avec tous les points importants, citations clés et analyse approfondie.'
|
| 48 |
}
|
| 49 |
|
| 50 |
+
# =======================
|
| 51 |
+
# Base de données
|
| 52 |
+
# =======================
|
| 53 |
+
|
| 54 |
+
def init_db():
|
| 55 |
+
"""Initialise la base de données"""
|
| 56 |
+
conn = sqlite3.connect(app.config['DATABASE'])
|
| 57 |
+
c = conn.cursor()
|
| 58 |
+
c.execute('''
|
| 59 |
+
CREATE TABLE IF NOT EXISTS tasks (
|
| 60 |
+
task_id TEXT PRIMARY KEY,
|
| 61 |
+
user_session TEXT,
|
| 62 |
+
filename TEXT,
|
| 63 |
+
summary_type TEXT,
|
| 64 |
+
status TEXT,
|
| 65 |
+
progress INTEGER,
|
| 66 |
+
summary TEXT,
|
| 67 |
+
error TEXT,
|
| 68 |
+
created_at TEXT,
|
| 69 |
+
completed_at TEXT,
|
| 70 |
+
source_type TEXT,
|
| 71 |
+
source_path TEXT
|
| 72 |
+
)
|
| 73 |
+
''')
|
| 74 |
+
conn.commit()
|
| 75 |
+
conn.close()
|
| 76 |
+
|
| 77 |
+
def get_db_connection():
|
| 78 |
+
"""Retourne une connexion à la base de données"""
|
| 79 |
+
conn = sqlite3.connect(app.config['DATABASE'])
|
| 80 |
+
conn.row_factory = sqlite3.Row
|
| 81 |
+
return conn
|
| 82 |
+
|
| 83 |
+
def create_task(user_session, filename, summary_type, source_type, source_path):
|
| 84 |
+
"""Crée une nouvelle tâche dans la base de données"""
|
| 85 |
+
task_id = str(uuid.uuid4())
|
| 86 |
+
conn = get_db_connection()
|
| 87 |
+
conn.execute('''
|
| 88 |
+
INSERT INTO tasks (task_id, user_session, filename, summary_type, status,
|
| 89 |
+
progress, created_at, source_type, source_path)
|
| 90 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 91 |
+
''', (task_id, user_session, filename, summary_type, 'pending', 0,
|
| 92 |
+
datetime.now().isoformat(), source_type, source_path))
|
| 93 |
+
conn.commit()
|
| 94 |
+
conn.close()
|
| 95 |
+
return task_id
|
| 96 |
+
|
| 97 |
+
def update_task_status(task_id, status, progress=None, summary=None, error=None):
|
| 98 |
+
"""Met à jour le statut d'une tâche"""
|
| 99 |
+
conn = get_db_connection()
|
| 100 |
+
|
| 101 |
+
if status == 'completed' or status == 'failed':
|
| 102 |
+
if progress is None:
|
| 103 |
+
progress = 100 if status == 'completed' else 0
|
| 104 |
+
conn.execute('''
|
| 105 |
+
UPDATE tasks
|
| 106 |
+
SET status = ?, progress = ?, summary = ?, error = ?, completed_at = ?
|
| 107 |
+
WHERE task_id = ?
|
| 108 |
+
''', (status, progress, summary, error, datetime.now().isoformat(), task_id))
|
| 109 |
+
else:
|
| 110 |
+
query = 'UPDATE tasks SET status = ?'
|
| 111 |
+
params = [status]
|
| 112 |
+
|
| 113 |
+
if progress is not None:
|
| 114 |
+
query += ', progress = ?'
|
| 115 |
+
params.append(progress)
|
| 116 |
+
|
| 117 |
+
query += ' WHERE task_id = ?'
|
| 118 |
+
params.append(task_id)
|
| 119 |
+
|
| 120 |
+
conn.execute(query, params)
|
| 121 |
+
|
| 122 |
+
conn.commit()
|
| 123 |
+
conn.close()
|
| 124 |
+
|
| 125 |
+
def get_task(task_id):
|
| 126 |
+
"""Récupère une tâche par son ID"""
|
| 127 |
+
conn = get_db_connection()
|
| 128 |
+
task = conn.execute('SELECT * FROM tasks WHERE task_id = ?', (task_id,)).fetchone()
|
| 129 |
+
conn.close()
|
| 130 |
+
return task
|
| 131 |
+
|
| 132 |
+
def get_user_tasks(user_session):
|
| 133 |
+
"""Récupère toutes les tâches d'un utilisateur"""
|
| 134 |
+
conn = get_db_connection()
|
| 135 |
+
tasks = conn.execute(
|
| 136 |
+
'SELECT * FROM tasks WHERE user_session = ? ORDER BY created_at DESC',
|
| 137 |
+
(user_session,)
|
| 138 |
+
).fetchall()
|
| 139 |
+
conn.close()
|
| 140 |
+
return tasks
|
| 141 |
+
|
| 142 |
+
def delete_old_tasks():
|
| 143 |
+
"""Supprime les tâches de plus de 7 jours"""
|
| 144 |
+
conn = get_db_connection()
|
| 145 |
+
seven_days_ago = datetime.now().timestamp() - (7 * 24 * 60 * 60)
|
| 146 |
+
conn.execute('''
|
| 147 |
+
DELETE FROM tasks
|
| 148 |
+
WHERE datetime(created_at) < datetime(?, 'unixepoch')
|
| 149 |
+
''', (seven_days_ago,))
|
| 150 |
+
conn.commit()
|
| 151 |
+
conn.close()
|
| 152 |
+
|
| 153 |
+
# =======================
|
| 154 |
+
# Fonctions utilitaires
|
| 155 |
+
# =======================
|
| 156 |
+
|
| 157 |
def allowed_file(filename):
|
| 158 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
| 159 |
|
|
|
|
| 166 |
youtube_regex = r'(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)[\w-]+'
|
| 167 |
return bool(re.match(youtube_regex, url))
|
| 168 |
|
| 169 |
+
def get_session_id():
|
| 170 |
+
"""Récupère ou crée un ID de session"""
|
| 171 |
+
if 'session_id' not in session:
|
| 172 |
+
session['session_id'] = str(uuid.uuid4())
|
| 173 |
+
return session['session_id']
|
| 174 |
+
|
| 175 |
+
# =======================
|
| 176 |
+
# Traitement en arrière-plan
|
| 177 |
+
# =======================
|
| 178 |
+
|
| 179 |
+
def process_file_background(task_id, file_path, filename, summary_type):
|
| 180 |
+
"""Traite un fichier en arrière-plan"""
|
|
|
|
|
|
|
|
|
|
| 181 |
try:
|
| 182 |
+
update_task_status(task_id, 'processing', progress=10)
|
| 183 |
+
|
| 184 |
+
mime_type = get_mime_type(filename)
|
| 185 |
+
prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen'])
|
| 186 |
+
|
| 187 |
+
update_task_status(task_id, 'processing', progress=30)
|
| 188 |
+
|
| 189 |
+
# Upload le fichier
|
| 190 |
uploaded_file = client.files.upload(file=file_path)
|
| 191 |
|
| 192 |
+
update_task_status(task_id, 'processing', progress=60)
|
| 193 |
+
|
| 194 |
# Génère le résumé
|
| 195 |
response = client.models.generate_content(
|
| 196 |
model="gemini-2.5-flash",
|
| 197 |
contents=[uploaded_file, prompt]
|
| 198 |
)
|
| 199 |
|
| 200 |
+
summary = response.text
|
| 201 |
+
|
| 202 |
+
update_task_status(task_id, 'completed', progress=100, summary=summary)
|
| 203 |
+
|
| 204 |
except Exception as e:
|
| 205 |
+
update_task_status(task_id, 'failed', error=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
+
finally:
|
| 208 |
+
# Nettoie le fichier
|
| 209 |
+
if os.path.exists(file_path):
|
| 210 |
+
try:
|
| 211 |
+
os.remove(file_path)
|
| 212 |
+
except:
|
| 213 |
+
pass
|
| 214 |
+
|
| 215 |
+
def process_youtube_background(task_id, youtube_url, summary_type):
|
| 216 |
+
"""Traite une vidéo YouTube en arrière-plan"""
|
| 217 |
try:
|
| 218 |
+
update_task_status(task_id, 'processing', progress=10)
|
| 219 |
+
|
| 220 |
+
prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen'])
|
| 221 |
+
|
| 222 |
+
update_task_status(task_id, 'processing', progress=30)
|
| 223 |
+
|
| 224 |
# Vérifier les informations du modèle
|
| 225 |
model_info = client.models.get(model="gemini-2.5-flash")
|
| 226 |
context_window = model_info.input_token_limit
|
| 227 |
|
| 228 |
+
update_task_status(task_id, 'processing', progress=50)
|
| 229 |
|
| 230 |
+
# Générer le contenu
|
| 231 |
response = client.models.generate_content(
|
| 232 |
model='gemini-2.5-flash',
|
| 233 |
contents=types.Content(
|
|
|
|
| 240 |
)
|
| 241 |
)
|
| 242 |
|
| 243 |
+
summary = response.text
|
| 244 |
+
|
| 245 |
+
update_task_status(task_id, 'completed', progress=100, summary=summary)
|
| 246 |
+
|
| 247 |
except Exception as e:
|
| 248 |
error_msg = str(e)
|
|
|
|
| 249 |
if 'token' in error_msg.lower() or 'too large' in error_msg.lower():
|
| 250 |
+
error_msg = f"La vidéo est trop longue (dépasse la limite de {MAX_VIDEO_TOKENS:,} tokens). Veuillez utiliser une vidéo plus courte."
|
| 251 |
+
update_task_status(task_id, 'failed', error=error_msg)
|
| 252 |
+
|
| 253 |
+
# =======================
|
| 254 |
+
# Fonction PDF
|
| 255 |
+
# =======================
|
| 256 |
|
| 257 |
def create_pdf(summary_text, original_filename, summary_type):
|
| 258 |
"""Crée un PDF du résumé"""
|
|
|
|
| 261 |
styles = getSampleStyleSheet()
|
| 262 |
story = []
|
| 263 |
|
|
|
|
| 264 |
title_style = ParagraphStyle(
|
| 265 |
'CustomTitle',
|
| 266 |
parent=styles['Heading1'],
|
|
|
|
| 270 |
title = Paragraph(f"Résumé: {original_filename}", title_style)
|
| 271 |
story.append(title)
|
| 272 |
|
|
|
|
| 273 |
info_style = styles['Normal']
|
| 274 |
info = Paragraph(f"Type: {summary_type.upper()} | Date: {datetime.now().strftime('%d/%m/%Y %H:%M')}", info_style)
|
| 275 |
story.append(info)
|
| 276 |
story.append(Spacer(1, 0.3*inch))
|
| 277 |
|
|
|
|
| 278 |
for paragraph in summary_text.split('\n\n'):
|
| 279 |
if paragraph.strip():
|
| 280 |
p = Paragraph(paragraph.replace('\n', '<br/>'), styles['Normal'])
|
|
|
|
| 285 |
buffer.seek(0)
|
| 286 |
return buffer
|
| 287 |
|
| 288 |
+
# =======================
|
| 289 |
+
# Routes
|
| 290 |
+
# =======================
|
| 291 |
+
|
| 292 |
@app.route('/')
|
| 293 |
def index():
|
|
|
|
|
|
|
| 294 |
return render_template('index.html')
|
| 295 |
|
| 296 |
@app.route('/upload', methods=['POST'])
|
| 297 |
def upload_file():
|
| 298 |
+
user_session = get_session_id()
|
| 299 |
youtube_url = request.form.get('youtube_url', '').strip()
|
| 300 |
summary_type = request.form.get('summary_type', 'moyen')
|
| 301 |
|
|
|
|
| 304 |
if not is_youtube_url(youtube_url):
|
| 305 |
return jsonify({'error': 'URL YouTube invalide'}), 400
|
| 306 |
|
| 307 |
+
# Créer la tâche
|
| 308 |
+
task_id = create_task(
|
| 309 |
+
user_session=user_session,
|
| 310 |
+
filename='Vidéo YouTube',
|
| 311 |
+
summary_type=summary_type,
|
| 312 |
+
source_type='youtube',
|
| 313 |
+
source_path=youtube_url
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
# Lancer le traitement en arrière-plan
|
| 317 |
+
thread = threading.Thread(
|
| 318 |
+
target=process_youtube_background,
|
| 319 |
+
args=(task_id, youtube_url, summary_type)
|
| 320 |
+
)
|
| 321 |
+
thread.daemon = True
|
| 322 |
+
thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
+
return jsonify({
|
| 325 |
+
'success': True,
|
| 326 |
+
'task_id': task_id,
|
| 327 |
+
'message': 'Traitement en cours...'
|
| 328 |
+
})
|
| 329 |
|
| 330 |
+
# Traitement fichier
|
| 331 |
if 'file' not in request.files:
|
| 332 |
return jsonify({'error': 'Aucun fichier ou URL YouTube fourni'}), 400
|
| 333 |
|
|
|
|
| 340 |
return jsonify({'error': 'Type de fichier non supporté'}), 400
|
| 341 |
|
| 342 |
filename = secure_filename(file.filename)
|
| 343 |
+
filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"{uuid.uuid4()}_{filename}")
|
| 344 |
file.save(filepath)
|
| 345 |
|
| 346 |
+
# Créer la tâche
|
| 347 |
+
task_id = create_task(
|
| 348 |
+
user_session=user_session,
|
| 349 |
+
filename=filename,
|
| 350 |
+
summary_type=summary_type,
|
| 351 |
+
source_type='file',
|
| 352 |
+
source_path=filepath
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
# Lancer le traitement en arrière-plan
|
| 356 |
+
thread = threading.Thread(
|
| 357 |
+
target=process_file_background,
|
| 358 |
+
args=(task_id, filepath, filename, summary_type)
|
| 359 |
+
)
|
| 360 |
+
thread.daemon = True
|
| 361 |
+
thread.start()
|
| 362 |
+
|
| 363 |
+
return jsonify({
|
| 364 |
+
'success': True,
|
| 365 |
+
'task_id': task_id,
|
| 366 |
+
'message': 'Traitement en cours...'
|
| 367 |
+
})
|
| 368 |
+
|
| 369 |
+
@app.route('/task/<task_id>')
|
| 370 |
+
def get_task_status(task_id):
|
| 371 |
+
"""Récupère le statut d'une tâche"""
|
| 372 |
+
task = get_task(task_id)
|
| 373 |
+
|
| 374 |
+
if not task:
|
| 375 |
+
return jsonify({'error': 'Tâche non trouvée'}), 404
|
| 376 |
+
|
| 377 |
+
return jsonify({
|
| 378 |
+
'task_id': task['task_id'],
|
| 379 |
+
'filename': task['filename'],
|
| 380 |
+
'summary_type': task['summary_type'],
|
| 381 |
+
'status': task['status'],
|
| 382 |
+
'progress': task['progress'],
|
| 383 |
+
'summary': task['summary'],
|
| 384 |
+
'error': task['error'],
|
| 385 |
+
'created_at': task['created_at'],
|
| 386 |
+
'completed_at': task['completed_at']
|
| 387 |
+
})
|
| 388 |
+
|
| 389 |
+
@app.route('/tasks')
|
| 390 |
+
def get_all_tasks():
|
| 391 |
+
"""Récupère toutes les tâches de l'utilisateur"""
|
| 392 |
+
user_session = get_session_id()
|
| 393 |
+
tasks = get_user_tasks(user_session)
|
| 394 |
+
|
| 395 |
+
task_list = []
|
| 396 |
+
for task in tasks:
|
| 397 |
+
task_list.append({
|
| 398 |
+
'task_id': task['task_id'],
|
| 399 |
+
'filename': task['filename'],
|
| 400 |
+
'summary_type': task['summary_type'],
|
| 401 |
+
'status': task['status'],
|
| 402 |
+
'progress': task['progress'],
|
| 403 |
+
'summary': task['summary'],
|
| 404 |
+
'error': task['error'],
|
| 405 |
+
'created_at': task['created_at'],
|
| 406 |
+
'completed_at': task['completed_at']
|
| 407 |
})
|
| 408 |
|
| 409 |
+
return jsonify(task_list)
|
| 410 |
+
|
| 411 |
+
@app.route('/download/<task_id>')
|
| 412 |
+
def download_pdf(task_id):
|
| 413 |
+
"""Télécharge le PDF d'une tâche"""
|
| 414 |
+
task = get_task(task_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 415 |
|
| 416 |
+
if not task:
|
| 417 |
+
return jsonify({'error': 'Tâche non trouvée'}), 404
|
| 418 |
+
|
| 419 |
+
if task['status'] != 'completed':
|
| 420 |
+
return jsonify({'error': 'Résumé non encore disponible'}), 400
|
| 421 |
+
|
| 422 |
+
pdf_buffer = create_pdf(task['summary'], task['filename'], task['summary_type'])
|
| 423 |
|
| 424 |
return send_file(
|
| 425 |
pdf_buffer,
|
| 426 |
mimetype='application/pdf',
|
| 427 |
as_attachment=True,
|
| 428 |
+
download_name=f"resume_{task['filename']}.pdf"
|
| 429 |
)
|
| 430 |
|
| 431 |
+
@app.route('/delete-task/<task_id>', methods=['DELETE'])
|
| 432 |
+
def delete_task(task_id):
|
| 433 |
+
"""Supprime une tâche"""
|
| 434 |
+
user_session = get_session_id()
|
| 435 |
+
task = get_task(task_id)
|
| 436 |
+
|
| 437 |
+
if not task:
|
| 438 |
+
return jsonify({'error': 'Tâche non trouvée'}), 404
|
| 439 |
+
|
| 440 |
+
if task['user_session'] != user_session:
|
| 441 |
+
return jsonify({'error': 'Non autorisé'}), 403
|
| 442 |
+
|
| 443 |
+
conn = get_db_connection()
|
| 444 |
+
conn.execute('DELETE FROM tasks WHERE task_id = ?', (task_id,))
|
| 445 |
+
conn.commit()
|
| 446 |
+
conn.close()
|
| 447 |
+
|
| 448 |
+
return jsonify({'success': True})
|
| 449 |
|
| 450 |
+
@app.route('/clear-tasks', methods=['POST'])
|
| 451 |
+
def clear_all_tasks():
|
| 452 |
+
"""Supprime toutes les tâches de l'utilisateur"""
|
| 453 |
+
user_session = get_session_id()
|
| 454 |
+
|
| 455 |
+
conn = get_db_connection()
|
| 456 |
+
conn.execute('DELETE FROM tasks WHERE user_session = ?', (user_session,))
|
| 457 |
+
conn.commit()
|
| 458 |
+
conn.close()
|
| 459 |
+
|
| 460 |
return jsonify({'success': True})
|
| 461 |
|
| 462 |
+
# =======================
|
| 463 |
+
# Initialisation
|
| 464 |
+
# =======================
|
| 465 |
+
|
| 466 |
+
# Initialiser la base de données au démarrage
|
| 467 |
+
with app.app_context():
|
| 468 |
+
init_db()
|
| 469 |
+
|
| 470 |
+
# Nettoyage périodique des anciennes tâches
|
| 471 |
+
def cleanup_old_tasks():
|
| 472 |
+
while True:
|
| 473 |
+
time.sleep(86400) # Toutes les 24 heures
|
| 474 |
+
delete_old_tasks()
|
| 475 |
+
|
| 476 |
+
cleanup_thread = threading.Thread(target=cleanup_old_tasks)
|
| 477 |
+
cleanup_thread.daemon = True
|
| 478 |
+
cleanup_thread.start()
|
| 479 |
+
|
| 480 |
if __name__ == '__main__':
|
| 481 |
app.run(debug=True)
|