|
|
from flask import Flask, render_template, request, jsonify, session, send_file |
|
|
from werkzeug.utils import secure_filename |
|
|
import os |
|
|
from google import genai |
|
|
from google.genai import types |
|
|
import io |
|
|
from reportlab.lib.pagesizes import letter |
|
|
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer |
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle |
|
|
from reportlab.lib.units import inch |
|
|
from datetime import datetime |
|
|
import secrets |
|
|
import re |
|
|
|
|
|
app = Flask(__name__) |
|
|
app.secret_key = secrets.token_hex(16) |
|
|
app.config['UPLOAD_FOLDER'] = 'uploads' |
|
|
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 |
|
|
|
|
|
|
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) |
|
|
|
|
|
|
|
|
GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY', 'YOUR_API_KEY_HERE') |
|
|
client = genai.Client(api_key=GEMINI_API_KEY) |
|
|
|
|
|
|
|
|
MAX_VIDEO_TOKENS = 600000 |
|
|
|
|
|
ALLOWED_EXTENSIONS = { |
|
|
'pdf': 'application/pdf', |
|
|
'mp3': 'audio/mp3', |
|
|
'mp4': 'video/mp4', |
|
|
'wav': 'audio/wav', |
|
|
'txt': 'text/plain', |
|
|
'doc': 'application/msword', |
|
|
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' |
|
|
} |
|
|
|
|
|
SUMMARY_TYPES = { |
|
|
'court': 'Fais un résumé très court (2-3 paragraphes maximum) des points clés essentiels.', |
|
|
'moyen': 'Fais un résumé détaillé structuré avec les points principaux et sous-points importants.', |
|
|
'detaille': 'Fais un résumé exhaustif et détaillé avec tous les points importants, citations clés et analyse approfondie.' |
|
|
} |
|
|
|
|
|
def allowed_file(filename): |
|
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS |
|
|
|
|
|
def get_mime_type(filename): |
|
|
ext = filename.rsplit('.', 1)[1].lower() |
|
|
return ALLOWED_EXTENSIONS.get(ext, 'application/octet-stream') |
|
|
|
|
|
def is_youtube_url(url): |
|
|
"""Vérifie si l'URL est une URL YouTube valide""" |
|
|
youtube_regex = r'(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)[\w-]+' |
|
|
return bool(re.match(youtube_regex, url)) |
|
|
|
|
|
def estimate_video_tokens(video_duration_seconds, fps=1, use_low_resolution=False): |
|
|
"""Estime le nombre de tokens pour une vidéo""" |
|
|
|
|
|
tokens_per_frame = 66 if use_low_resolution else 258 |
|
|
|
|
|
tokens_per_second_audio = 32 |
|
|
|
|
|
tokens_per_second = (tokens_per_frame * fps) + tokens_per_second_audio |
|
|
return video_duration_seconds * tokens_per_second |
|
|
|
|
|
def generate_summary(file_path, filename, summary_type): |
|
|
"""Génère un résumé avec l'API Gemini""" |
|
|
mime_type = get_mime_type(filename) |
|
|
prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen']) |
|
|
|
|
|
try: |
|
|
|
|
|
uploaded_file = client.files.upload(file=file_path) |
|
|
|
|
|
|
|
|
response = client.models.generate_content( |
|
|
model="gemini-2.5-flash", |
|
|
contents=[uploaded_file, prompt] |
|
|
) |
|
|
|
|
|
return response.text |
|
|
except Exception as e: |
|
|
return f"Erreur lors de la génération du résumé: {str(e)}" |
|
|
|
|
|
def generate_summary_from_youtube(youtube_url, summary_type): |
|
|
"""Génère un résumé à partir d'une URL YouTube""" |
|
|
prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen']) |
|
|
|
|
|
try: |
|
|
|
|
|
model_info = client.models.get(model="gemini-2.5-flash") |
|
|
context_window = model_info.input_token_limit |
|
|
|
|
|
print(f"Context window: {context_window} tokens") |
|
|
|
|
|
|
|
|
response = client.models.generate_content( |
|
|
model='gemini-2.5-flash', |
|
|
contents=types.Content( |
|
|
parts=[ |
|
|
types.Part( |
|
|
file_data=types.FileData(file_uri=youtube_url) |
|
|
), |
|
|
types.Part(text=prompt) |
|
|
] |
|
|
) |
|
|
) |
|
|
|
|
|
return response.text |
|
|
except Exception as e: |
|
|
error_msg = str(e) |
|
|
|
|
|
if 'token' in error_msg.lower() or 'too large' in error_msg.lower(): |
|
|
return f"Erreur: La vidéo est trop longue (dépasse la limite de {MAX_VIDEO_TOKENS:,} tokens). Veuillez utiliser une vidéo plus courte." |
|
|
return f"Erreur lors de la génération du résumé: {error_msg}" |
|
|
|
|
|
def create_pdf(summary_text, original_filename, summary_type): |
|
|
"""Crée un PDF du résumé""" |
|
|
buffer = io.BytesIO() |
|
|
doc = SimpleDocTemplate(buffer, pagesize=letter) |
|
|
styles = getSampleStyleSheet() |
|
|
story = [] |
|
|
|
|
|
|
|
|
title_style = ParagraphStyle( |
|
|
'CustomTitle', |
|
|
parent=styles['Heading1'], |
|
|
fontSize=18, |
|
|
spaceAfter=30 |
|
|
) |
|
|
title = Paragraph(f"Résumé: {original_filename}", title_style) |
|
|
story.append(title) |
|
|
|
|
|
|
|
|
info_style = styles['Normal'] |
|
|
info = Paragraph(f"Type: {summary_type.upper()} | Date: {datetime.now().strftime('%d/%m/%Y %H:%M')}", info_style) |
|
|
story.append(info) |
|
|
story.append(Spacer(1, 0.3*inch)) |
|
|
|
|
|
|
|
|
for paragraph in summary_text.split('\n\n'): |
|
|
if paragraph.strip(): |
|
|
p = Paragraph(paragraph.replace('\n', '<br/>'), styles['Normal']) |
|
|
story.append(p) |
|
|
story.append(Spacer(1, 0.2*inch)) |
|
|
|
|
|
doc.build(story) |
|
|
buffer.seek(0) |
|
|
return buffer |
|
|
|
|
|
@app.route('/') |
|
|
def index(): |
|
|
if 'history' not in session: |
|
|
session['history'] = [] |
|
|
return render_template('index.html') |
|
|
|
|
|
@app.route('/upload', methods=['POST']) |
|
|
def upload_file(): |
|
|
youtube_url = request.form.get('youtube_url', '').strip() |
|
|
summary_type = request.form.get('summary_type', 'moyen') |
|
|
|
|
|
|
|
|
if youtube_url: |
|
|
if not is_youtube_url(youtube_url): |
|
|
return jsonify({'error': 'URL YouTube invalide'}), 400 |
|
|
|
|
|
try: |
|
|
summary = generate_summary_from_youtube(youtube_url, summary_type) |
|
|
|
|
|
|
|
|
if summary.startswith("Erreur:"): |
|
|
return jsonify({'error': summary}), 400 |
|
|
|
|
|
|
|
|
if 'history' not in session: |
|
|
session['history'] = [] |
|
|
|
|
|
history_item = { |
|
|
'id': len(session['history']), |
|
|
'filename': 'Vidéo YouTube', |
|
|
'summary_type': summary_type, |
|
|
'summary': summary, |
|
|
'date': datetime.now().strftime('%d/%m/%Y %H:%M'), |
|
|
'filepath': youtube_url |
|
|
} |
|
|
|
|
|
session['history'].append(history_item) |
|
|
session.modified = True |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'summary': summary, |
|
|
'history_id': history_item['id'] |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
return jsonify({'error': str(e)}), 500 |
|
|
|
|
|
|
|
|
if 'file' not in request.files: |
|
|
return jsonify({'error': 'Aucun fichier ou URL YouTube fourni'}), 400 |
|
|
|
|
|
file = request.files['file'] |
|
|
|
|
|
if file.filename == '': |
|
|
return jsonify({'error': 'Nom de fichier vide'}), 400 |
|
|
|
|
|
if not allowed_file(file.filename): |
|
|
return jsonify({'error': 'Type de fichier non supporté'}), 400 |
|
|
|
|
|
filename = secure_filename(file.filename) |
|
|
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) |
|
|
file.save(filepath) |
|
|
|
|
|
try: |
|
|
|
|
|
summary = generate_summary(filepath, filename, summary_type) |
|
|
|
|
|
|
|
|
if 'history' not in session: |
|
|
session['history'] = [] |
|
|
|
|
|
history_item = { |
|
|
'id': len(session['history']), |
|
|
'filename': filename, |
|
|
'summary_type': summary_type, |
|
|
'summary': summary, |
|
|
'date': datetime.now().strftime('%d/%m/%Y %H:%M'), |
|
|
'filepath': filepath |
|
|
} |
|
|
|
|
|
session['history'].append(history_item) |
|
|
session.modified = True |
|
|
|
|
|
return jsonify({ |
|
|
'success': True, |
|
|
'summary': summary, |
|
|
'history_id': history_item['id'] |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
return jsonify({'error': str(e)}), 500 |
|
|
finally: |
|
|
|
|
|
if os.path.exists(filepath): |
|
|
os.remove(filepath) |
|
|
|
|
|
@app.route('/download/<int:history_id>') |
|
|
def download_pdf(history_id): |
|
|
if 'history' not in session or history_id >= len(session['history']): |
|
|
return jsonify({'error': 'Résumé non trouvé'}), 404 |
|
|
|
|
|
item = session['history'][history_id] |
|
|
pdf_buffer = create_pdf(item['summary'], item['filename'], item['summary_type']) |
|
|
|
|
|
return send_file( |
|
|
pdf_buffer, |
|
|
mimetype='application/pdf', |
|
|
as_attachment=True, |
|
|
download_name=f"resume_{item['filename']}.pdf" |
|
|
) |
|
|
|
|
|
@app.route('/history') |
|
|
def get_history(): |
|
|
return jsonify(session.get('history', [])) |
|
|
|
|
|
@app.route('/clear-history', methods=['POST']) |
|
|
def clear_history(): |
|
|
session['history'] = [] |
|
|
return jsonify({'success': True}) |
|
|
|
|
|
if __name__ == '__main__': |
|
|
app.run(debug=True) |