diwash-barla commited on
Commit
423659f
·
1 Parent(s): dc2e689

old recovery

Browse files
Files changed (6) hide show
  1. app.py +56 -129
  2. client_secret.json +1 -0
  3. engine.py +31 -176
  4. faltuapp.py +202 -0
  5. oldapp.py +0 -129
  6. oldengn.py +0 -442
app.py CHANGED
@@ -1,53 +1,53 @@
 
 
 
 
1
  import os
 
 
 
 
 
2
  import uuid
3
  import threading
4
- import zipfile
5
- import shutil
6
- from dotenv import load_dotenv
7
- from flask import Flask, request, jsonify, render_template, send_from_directory, send_file
8
  from werkzeug.utils import secure_filename
9
 
 
10
  from engine import (
11
- init_db, create_task, get_task, get_db_connection, run_ai_engine_worker,
12
- generate_script_with_ai, UPLOAD_FOLDER, OUTPUT_FOLDER, GoogleDriveHelper
 
 
13
  )
14
 
15
- load_dotenv()
16
-
17
- app = Flask(__name__)
18
- app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
19
- app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER
20
- init_db()
21
 
22
  # ==============================================================================
23
- # PWA, Static File, and Favicon Routes
24
  # ==============================================================================
25
- @app.route('/favicon.ico')
26
- def favicon():
27
- return send_from_directory('static', 'favicon.png')
 
 
28
 
29
- @app.route('/manifest.json')
30
- def serve_manifest():
31
- return send_from_directory('static', 'manifest.json')
32
 
33
- @app.route('/sw.js')
34
- def serve_sw():
35
- return send_from_directory('static', 'sw.js')
36
 
37
- @app.route('/icon-512x512.png')
38
- def serve_icon_512():
39
- return send_from_directory('static', 'icon-512x512.png')
40
 
41
- @app.route('/icon-192x192.png')
42
- def serve_icon_192():
43
- return send_from_directory('static', 'icon-192x192.png')
 
44
 
45
- # ==============================================================================
46
- # Main Application Routes
47
- # ==============================================================================
48
  @app.route('/')
49
  def home():
50
- return render_template('home.html')
51
 
52
  @app.route('/process', methods=['POST'])
53
  def process_video():
@@ -72,7 +72,7 @@ def process_video():
72
  args=(task_id, script_text, script_file_path, orientation, max_clip_length, mute_final_video)
73
  )
74
  thread.start()
75
- return render_template('processing.html', task_id=task_id)
76
 
77
  @app.route('/generate-script', methods=['POST'])
78
  def generate_script():
@@ -93,110 +93,37 @@ def progress(task_id):
93
  task = get_task(task_id)
94
  if not task:
95
  return jsonify({'status': 'error', 'log': 'Task not found.'})
96
- # task object ko dictionary me convert karein
97
- task_dict = dict(task)
98
- # Timestamp ko string me convert karein agar woh object hai
99
- if 'creation_timestamp' in task_dict and task_dict['creation_timestamp'] is not None:
100
- task_dict['creation_timestamp'] = str(task_dict['creation_timestamp'])
101
- return jsonify(task_dict)
102
 
 
 
 
103
 
104
- @app.route('/result/<task_id>')
105
- def result(task_id):
106
- task = get_task(task_id)
107
- if not task or task['status'] != 'complete':
108
- return "Task not found or not complete.", 404
109
- return render_template('result.html', task=dict(task))
110
-
111
- # Yeh route ab direct file serve nahi karega, isliye ise hata sakte hain ya aise hi chhod sakte hain
112
  @app.route('/outputs/<path:filename>')
113
  def serve_output_file(filename):
114
- return "Direct file access is disabled. Please use history.", 403
115
 
116
- # ==============================================================================
117
- # History and Bulk Action Routes (Updated)
118
- # ==============================================================================
119
  @app.route('/history')
120
  def history():
121
- conn = get_db_connection()
122
- # Sirf complete tasks dikhayein, naye se purane kram me
123
- tasks = conn.execute("SELECT * FROM tasks WHERE status = 'complete' ORDER BY creation_timestamp DESC").fetchall()
124
- conn.close()
125
-
126
  history_items = []
127
- for task in tasks:
128
- item = {
129
- 'task_id': task['id'],
130
- 'video_link': task['video_share_link'],
131
- 'report_link': task['report_share_link'],
132
- 'timestamp': task['creation_timestamp']
133
- }
134
- history_items.append(item)
135
-
136
- return render_template('history.html', items=history_items)
137
-
138
- @app.route('/history/delete', methods=['POST'])
139
- def delete_history_items():
140
- data = request.get_json()
141
- task_ids = data.get('task_ids', [])
142
- if not task_ids:
143
- return jsonify({'error': 'No task IDs provided'}), 400
144
-
145
  try:
146
- gdrive = GoogleDriveHelper()
147
- conn = get_db_connection()
148
- for task_id in task_ids:
149
- task = conn.execute("SELECT video_file_id, report_file_id FROM tasks WHERE id = ?", (task_id,)).fetchone()
150
- if task:
151
- if task['video_file_id']:
152
- gdrive.delete_file(task['video_file_id'])
153
- if task['report_file_id']:
154
- gdrive.delete_file(task['report_file_id'])
155
- conn.execute("DELETE FROM tasks WHERE id = ?", (task_id,))
156
- conn.commit()
157
- conn.close()
158
- return jsonify({'success': True, 'message': f'{len(task_ids)} items deleted.'})
159
- except Exception as e:
160
- print(f"Error during bulk delete: {e}")
161
- return jsonify({'error': str(e)}), 500
162
-
163
- @app.route('/history/download', methods=['POST'])
164
- def download_history_items():
165
- data = request.get_json()
166
- task_ids = data.get('task_ids', [])
167
- if not task_ids:
168
- return jsonify({'error': 'No task IDs provided'}), 400
169
-
170
- temp_dir = os.path.join(app.config['UPLOAD_FOLDER'], f"download_{uuid.uuid4()}")
171
- os.makedirs(temp_dir, exist_ok=True)
172
- zip_path = os.path.join(app.config['UPLOAD_FOLDER'], f"download_{uuid.uuid4()}.zip")
173
-
174
- try:
175
- gdrive = GoogleDriveHelper()
176
- conn = get_db_connection()
177
- for task_id in task_ids:
178
- task = conn.execute("SELECT video_file_id, report_file_id FROM tasks WHERE id = ?", (task_id,)).fetchone()
179
- if task:
180
- if task['video_file_id']:
181
- gdrive.download_file(task['video_file_id'], os.path.join(temp_dir, f"{task_id}_video.mp4"))
182
- if task['report_file_id']:
183
- gdrive.download_file(task['report_file_id'], os.path.join(temp_dir, f"{task_id}_report.json"))
184
- conn.close()
185
-
186
- with zipfile.ZipFile(zip_path, 'w') as zipf:
187
- for root, dirs, files in os.walk(temp_dir):
188
- for file in files:
189
- zipf.write(os.path.join(root, file), arcname=file)
190
-
191
- return send_file(zip_path, as_attachment=True, download_name='AI_Videos_Export.zip')
192
- except Exception as e:
193
- print(f"Error during bulk download: {e}")
194
- return jsonify({'error': str(e)}), 500
195
- finally:
196
- if os.path.exists(temp_dir):
197
- shutil.rmtree(temp_dir)
198
- if os.path.exists(zip_path):
199
- os.remove(zip_path)
200
-
201
  if __name__ == '__main__':
 
202
  app.run(host='0.0.0.0', port=5000, debug=True)
 
1
+ # ==============================================================================
2
+ # app.py - [FINAL CORRECTED VERSION FOR HUGGING FACE v2]
3
+ # CHANGE: init_db() ko app startup par call kiya gaya hai.
4
+ # ==============================================================================
5
  import os
6
+ from dotenv import load_dotenv
7
+
8
+ # Ye line aapke local .env file se variables load karegi (Hugging Face par iska koi asar nahi)
9
+ load_dotenv()
10
+
11
  import uuid
12
  import threading
13
+ from flask import Flask, request, jsonify, render_template_string, send_from_directory
 
 
 
14
  from werkzeug.utils import secure_filename
15
 
16
+ # engine.py se zaroori functions AUR FOLDER PATHS import karein
17
  from engine import (
18
+ init_db, create_task, get_task, run_ai_engine_worker,
19
+ generate_script_with_ai,
20
+ UPLOAD_FOLDER,
21
+ OUTPUT_FOLDER
22
  )
23
 
 
 
 
 
 
 
24
 
25
  # ==============================================================================
26
+ # 1. HTML Templates
27
  # ==============================================================================
28
+ MODERN_CSS_BASE = """<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet"><style>:root{--bg-color:#1a1a2e;--card-bg-color:rgba(22,22,46,0.7);--primary-color:#e94560;--secondary-color:#0f3460;--text-color:#f0f0f0;--text-muted-color:#a0a0a0;--border-color:rgba(233,69,96,0.2);--gradient:linear-gradient(90deg,#e94560,#a63f82)}body{font-family:'Poppins',sans-serif;background-color:var(--bg-color);background-image:linear-gradient(315deg,var(--bg-color) 0%,var(--secondary-color) 74%);color:var(--text-color);padding-top:20px;padding-bottom:20px}.card{background:var(--card-bg-color);border:1px solid var(--border-color);border-radius:16px;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 8px 32px 0 rgba(0,0,0,0.37);animation:fadeInUp 0.5s ease-out}h1,h2,h3,h4,h5,h6{background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-weight:700}.text-muted{color:var(--text-muted-color) !important}.form-control,.form-select{background-color:rgba(0,0,0,0.2);color:var(--text-color);border:1px solid var(--border-color);border-radius:8px}.form-control:focus,.form-select:focus{background-color:rgba(0,0,0,0.3);color:var(--text-color);border-color:var(--primary-color);box-shadow:0 0 0 .25rem rgba(233,69,96,0.25)}.form-control::placeholder{color:var(--text-muted-color);opacity:.7}.btn{border:none;border-radius:8px;font-weight:600;padding:12px 24px;transition:transform .1s ease-out,box-shadow .2s ease-out,filter .2s ease-out}.btn:hover{transform:translateY(-3px);box-shadow:0 5px 15px rgba(0,0,0,.3)}.btn:active{transform:translateY(0) scale(.98);filter:brightness(1.2)}.btn-primary{background:var(--gradient);color:#fff}.btn-primary:active{box-shadow:0 0 5px var(--primary-color),0 0 25px var(--primary-color),0 0 50px var(--primary-color)}.btn-secondary:active,.btn-dark:active{box-shadow:0 0 5px rgba(255,255,255,.8),0 0 25px rgba(255,255,255,.5)}@keyframes fadeInUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}</style>"""
29
+ HTML_HOME = """<!DOCTYPE html><html lang="hi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Sparkling Gyan AI - होम</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">""" + MODERN_CSS_BASE + """<style>.ai-generator-controls{border:1px solid var(--border-color);border-radius:12px;padding:20px;margin-top:20px;background-color:rgba(0,0,0,.1)}</style></head><body><div class="container my-5"><div class="card p-4 p-md-5"><div class="d-flex justify-content-between align-items-center mb-3"><h1 class="mb-0">✨ Sparkling Gyan AI</h1><a href="/history" class="btn btn-secondary"><i class="fas fa-history"></i> हिस्ट्री देखें</a></div><p class="text-center text-muted mb-4">एक उन्नत, ऑटोमेटिक वीडियो जेनरेटर</p><form id="upload-form" action="/process" method="POST" enctype="multipart/form-data"><div class="mb-3"><label for="script_text" class="form-label fs-5 fw-bold text-light">१. कहानी लिखें</label><textarea class="form-control" id="script_text" name="script_text" rows="8" placeholder="यहाँ कोई टॉपिक लिखें (जैसे 'चंद्रमा के रहस्य') और नीचे 'AI से स्क्रिप्ट बनाएँ' बटन दबाएँ, या अपनी पूरी स्क्रिप्ट यहाँ पेस्ट करें..."></textarea></div><div class="ai-generator-controls"><div class="row align-items-end"><div class="col-md-5"><label for="video-length-select" class="form-label text-light">वीडियो की लंबाई</label><select id="video-length-select" class="form-select"><option value="short">छोटी (~30 सेकंड)</option><option value="medium" selected>मध्यम (~1 मिनट)</option><option value="long">लंबी (~2 मिनट)</option></select></div><div class="col-md-7 d-grid"><button type="button" id="generate-script-btn" class="btn btn-dark"><i class="fas fa-magic"></i> AI से स्क्रिप्ट बनाएँ</button></div></div><div id="ai-status" class="mt-2"></div></div><div class="text-center text-muted my-3">या</div><div class="mb-3"><label for="script_file" class="form-label">एक ऑडियो स्क्रिप्ट फ़ाइल अपलोड करें</label><input class="form-control" type="file" id="script_file" name="script_file" accept="audio/*"></div><h5 class="mt-4 mb-3 text-light">२. अतिरिक्त विकल्प</h5><div class="row mb-3"><div class="col-md-6"><label class="form-label">वीडियो ओरिएंटेशन</label><div class="form-check"><input class="form-check-input" type="radio" name="orientation" id="orientation_horizontal" value="horizontal" checked><label class="form-check-label" for="orientation_horizontal">Landscape (16:9)</label></div><div class="form-check"><input class="form-check-input" type="radio" name="orientation" id="orientation_vertical" value="vertical"><label class="form-check-label" for="orientation_vertical">Portrait (9:16)</label></div></div><div class="col-md-6"><label for="max_clip_length" class="form-label">अधिकतम क्लिप लंबाई (सेकंड)</label><input type="number" class="form-control" id="max_clip_length" name="max_clip_length" value="15" min="3"></div></div><div class="form-check mb-4"><input class="form-check-input" type="checkbox" value="true" id="mute_final_video" name="mute_final_video"><label class="form-check-label" for="mute_final_video">ऑडियो/वॉयसओवर ट्रैक को म्यूट करें</label></div><div class="d-grid mt-4"><button type="submit" class="btn btn-primary btn-lg">🚀 वीडियो बनाना शुरू करें!</button></div></form></div></div><script>document.addEventListener("DOMContentLoaded",()=>{const e=document.getElementById("generate-script-btn"),t=document.getElementById("script_text"),o=document.getElementById("video-length-select"),n=document.getElementById("ai-status");e.addEventListener("click",async()=>{const a=t.value.trim();if(!a)return void(n.innerHTML='<p class="text-danger">कृपया स्क्रिप्ट बनाने के लिए कोई टॉपिक लिखें।</p>');const i=o.value;e.disabled=!0,e.innerHTML='<i class="fas fa-spinner fa-spin"></i> जेनरेट हो रहा है...',n.innerHTML="<p>🧠 AI आपके लिए एक बेहतरीन स्क्रिप्ट लिख रहा है, कृपया प्रतीक्षा करें...</p>";try{const r=await fetch("/generate-script",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({topic:a,video_length:i})}),c=await r.json();if(!r.ok)throw new Error(c.error||"सर्वर से कोई अज्ञात त्रुटि हुई।");t.value=c.script,n.innerHTML='<p style="color: #2ea043;">✅ AI ने स्क्रिप्ट तैयार कर दी है! अब आप वीडियो बना सकते हैं।</p>'}catch(d){console.error("Script Generation Error:",d),n.innerHTML=`<p class="text-danger">स्क्रिप्ट बनाने में विफल: ${d.message}</p>`}finally{e.disabled=!1,e.innerHTML='<i class="fas fa-magic"></i> AI से स्क्रिप्ट बनाएँ'}})})</script></body></html>"""
30
+ HTML_PROCESSING = """<!DOCTYPE html><html lang="hi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>प्रोसेसिंग...</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">""" + MODERN_CSS_BASE + """<style>body{display:flex;align-items:center;justify-content:center;min-height:100vh}#log-output{white-space:pre-wrap;background-color:rgba(0,0,0,.3);font-family:monospace;max-height:400px;overflow-y:auto;border-radius:8px;border:1px solid var(--border-color)}.progress-bar{background:var(--gradient)}.progress{height:25px;border-radius:8px;background-color:rgba(0,0,0,.3)}</style></head><body><div class="container"><div class="card p-4 p-md-5"><h2 class="text-center">मिशन कंट्रोल</h2><p id="status-text" class="text-center text-muted">प्रक्रिया शुरू हो रही है...</p><div class="progress mb-3" role="progressbar"><div class="progress-bar progress-bar-striped progress-bar-animated" id="progress-bar" style="width:0%">0%</div></div><pre id="log-output" class="p-3"></pre></div></div><script>const taskId="{{task_id}}",progressBar=document.getElementById("progress-bar"),statusText=document.getElementById("status-text"),logOutput=document.getElementById("log-output"),pollingInterval=setInterval(async()=>{try{const e=await fetch(`/progress/${taskId}`),t=await e.json(),o=t.log.trim().split("\\n").pop();statusText.innerText=o||"प्रतीक्षा में...",progressBar.style.width=t.progress+"%",progressBar.innerText=t.progress+"%",logOutput.innerText=t.log,logOutput.scrollTop=logOutput.scrollHeight,"complete"===t.status?(clearInterval(pollingInterval),window.location.href=`/result/${t.output_filename}`):"error"===t.status&&(clearInterval(pollingInterval),statusText.innerText="कोई त्रुटि हुई! विवरण के लिए लॉग देखें।",progressBar.classList.remove("bg-success"),progressBar.classList.add("bg-danger"))}catch(e){clearInterval(pollingInterval),statusText.innerText="सर्वर से कनेक्शन टूट गया।"}},1500)</script></body></html>"""
31
+ HTML_RESULT = """<!DOCTYPE html><html lang="hi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>आपकी वीडियो तैयार है!</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">""" + MODERN_CSS_BASE + """<style>body{display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center}video{width:100%;max-width:800px;border-radius:12px;border:1px solid var(--border-color)}</style></head><body><div class="container"><div class="card p-4 p-md-5"><h1 class="mb-3">🎉 मिशन पूरा हुआ!</h1><p class="text-muted mb-4">आपकी फाइनल वीडियो नीचे है।</p><video controls autoplay muted loop><source src="/outputs/{{ filename }}" type="video/mp4"> आपका ब्राउज़र वीडियो टैग को सपोर्ट नहीं करता। </video><div class="d-grid gap-2 d-md-flex justify-content-md-center mt-4"><a href="/outputs/{{ filename }}" class="btn btn-primary" download><i class="fas fa-download"></i> वीडियो डाउनलोड करें</a> <a href="/" class="btn btn-secondary">एक और वीडियो बनाएँ</a></div></div></div></body></html>"""
32
+ HTML_HISTORY = """<!DOCTYPE html><html lang="hi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>वीडियो हिस्ट्री</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">""" + MODERN_CSS_BASE + """<style>.history-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(350px,1fr));gap:20px}.card{transition:transform .3s ease}.card:hover{transform:translateY(-5px)}video{width:100%;border-radius:8px;margin-top:10px;border:1px solid var(--border-color)}.actions a{margin-right:10px;margin-bottom:10px}</style></head><body><div class="container"><a href="/" class="btn btn-secondary mb-4"><i class="fas fa-arrow-left"></i> वापस मुख्य पेज पर</a><h1 class="text-center mb-5">बनाए गए वीडियो की हिस्ट्री</h1> {% if items %}<div class="history-grid"> {% for item in items %}<div class="card"><div class="card-body"><h5 class="card-title">टास्क आईडी:</h5><p class="card-text text-muted" style="font-size:.8em">{{ item.task_id }}</p><video controls preload="metadata"><source src="/outputs/{{ item.video }}" type="video/mp4"></video><div class="actions mt-3"><a href="/outputs/{{ item.video }}" class="btn btn-primary" download><i class="fas fa-download"></i> डाउनलोड</a> {% if item.report %}<a href="/outputs/{{ item.report }}" class="btn btn-dark" target="_blank"><i class="fas fa-file-alt"></i> रिपोर्ट देखें</a> {% endif %}</div></div></div> {% endfor %}</div> {% else %}<div class="text-center card p-5"><h2>कोई हिस्ट्री नहीं मिली</h2><p class="text-muted">आपने अभी तक कोई वीडियो नहीं बनाया है।</p></div> {% endif %}</div></body></html>"""
33
 
 
 
 
34
 
35
+ # ==============================================================================
36
+ # Flask Application Setup
37
+ # ==============================================================================
38
 
39
+ app = Flask(__name__)
40
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
41
+ app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER
42
 
43
+ # <<<--- YAHAN EK LINE JODI GAYI HAI --- >>>
44
+ # Database table banayein agar pehle se nahi bani hai
45
+ init_db()
46
+ # <<<--- BADLAV YAHAN KHATM HOTA HAI --- >>>
47
 
 
 
 
48
  @app.route('/')
49
  def home():
50
+ return render_template_string(HTML_HOME)
51
 
52
  @app.route('/process', methods=['POST'])
53
  def process_video():
 
72
  args=(task_id, script_text, script_file_path, orientation, max_clip_length, mute_final_video)
73
  )
74
  thread.start()
75
+ return render_template_string(HTML_PROCESSING, task_id=task_id)
76
 
77
  @app.route('/generate-script', methods=['POST'])
78
  def generate_script():
 
93
  task = get_task(task_id)
94
  if not task:
95
  return jsonify({'status': 'error', 'log': 'Task not found.'})
96
+ return jsonify(dict(task))
 
 
 
 
 
97
 
98
+ @app.route('/result/<filename>')
99
+ def result(filename):
100
+ return render_template_string(HTML_RESULT, filename=filename)
101
 
 
 
 
 
 
 
 
 
102
  @app.route('/outputs/<path:filename>')
103
  def serve_output_file(filename):
104
+ return send_from_directory(app.config['OUTPUT_FOLDER'], filename)
105
 
 
 
 
106
  @app.route('/history')
107
  def history():
108
+ output_dir = app.config['OUTPUT_FOLDER']
 
 
 
 
109
  history_items = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  try:
111
+ all_files = sorted(os.listdir(output_dir), reverse=True)
112
+ for filename in all_files:
113
+ if filename.endswith('_final_video.mp4'):
114
+ task_id = filename.replace('_final_video.mp4', '')
115
+ report_filename = f"{task_id}_report.json"
116
+ item = {
117
+ 'video': filename,
118
+ 'task_id': task_id,
119
+ 'report': report_filename if report_filename in all_files else None
120
+ }
121
+ history_items.append(item)
122
+ except FileNotFoundError:
123
+ print(f"'{output_dir}' directory not found.")
124
+ return render_template_string(HTML_HISTORY, items=history_items)
125
+
126
+ # Is block ko ab production mein ignore kar diya jayega
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  if __name__ == '__main__':
128
+ init_db()
129
  app.run(host='0.0.0.0', port=5000, debug=True)
client_secret.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"installed":{"client_id":"370383806325-5oll4ae3ms62b3f2h3lj86gm61dovico.apps.googleusercontent.com","project_id":"lunar-arc-476205-b5","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}}
engine.py CHANGED
@@ -1,3 +1,9 @@
 
 
 
 
 
 
1
  import os
2
  import time
3
  import json
@@ -11,45 +17,30 @@ import shutil
11
  import re
12
  from gtts import gTTS
13
  from werkzeug.utils import secure_filename
14
- from google.oauth2 import service_account
15
- from googleapiclient.discovery import build
16
- from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload
17
- import io
18
 
19
  # ==============================================================================
20
- # 1. Global Setup and Database Functions
21
  # ==============================================================================
22
- APP_ROOT = os.path.dirname(os.path.abspath(__file__))
 
 
 
23
  DATA_FOLDER = os.path.join(APP_ROOT, 'data')
24
  UPLOAD_FOLDER = os.path.join(APP_ROOT, 'uploads')
25
  OUTPUT_FOLDER = os.path.join(APP_ROOT, 'outputs')
 
26
  DATABASE_FILE = os.path.join(DATA_FOLDER, 'tasks.db')
27
-
28
- # Ensure directories exist
29
- os.makedirs(DATA_FOLDER, exist_ok=True)
30
- os.makedirs(UPLOAD_FOLDER, exist_ok=True)
31
- os.makedirs(OUTPUT_FOLDER, exist_ok=True)
32
-
33
 
34
  def get_db_connection():
 
35
  conn = sqlite3.connect(DATABASE_FILE, check_same_thread=False)
36
  conn.row_factory = sqlite3.Row
37
  return conn
38
 
39
  def init_db():
40
  conn = get_db_connection()
41
- conn.execute('''
42
- CREATE TABLE IF NOT EXISTS tasks (
43
- id TEXT PRIMARY KEY,
44
- status TEXT NOT NULL,
45
- progress INTEGER NOT NULL,
46
- log TEXT,
47
- creation_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
48
- video_file_id TEXT,
49
- video_share_link TEXT,
50
- report_file_id TEXT,
51
- report_share_link TEXT
52
- )''')
53
  conn.commit()
54
  conn.close()
55
 
@@ -74,18 +65,22 @@ def update_task_log(task_id, message, progress):
74
  conn.commit()
75
  conn.close()
76
 
77
- def update_task_final_status(task_id, status, error_message=None):
78
  conn = get_db_connection()
79
  current_log = conn.execute('SELECT log FROM tasks WHERE id = ?', (task_id,)).fetchone()['log']
80
  if status == 'error':
81
  final_log = current_log + f"\n\n🚨 FATAL ERROR: {error_message}"
82
- conn.execute('UPDATE tasks SET status = ?, log = ?, progress = ? WHERE id = ?', (status, final_log, 100, task_id))
83
- # 'complete' status is now handled separately after gdrive upload
 
 
84
  conn.commit()
85
  conn.close()
86
 
87
-
88
  def load_api_keys(prefix):
 
 
 
89
  try:
90
  prefix_lower = prefix.lower()
91
  keys = [v for k, v in os.environ.items() if k.lower().startswith(prefix_lower)]
@@ -97,10 +92,9 @@ def load_api_keys(prefix):
97
  return []
98
 
99
  # ==============================================================================
100
- # 2. API and Worker Classes
 
101
  # ==============================================================================
102
-
103
- # ... (GroqAPI, PexelsAPI, PixabayAPI, GeminiTeam, VideoAssembler classes yahan pehle jaise hi rahengi) ...
104
  class GroqAPI:
105
  def __init__(self, api_keys): self.api_keys, self.api_url, self.model, self._key_index = api_keys, "https://api.groq.com/openai/v1/audio/transcriptions", "whisper-large-v3", 0
106
  def transcribe_audio(self, audio_path):
@@ -307,75 +301,9 @@ class VideoAssembler:
307
  '-c:a', 'aac', '-shortest', self.output_path
308
  ]
309
  self._run_ffmpeg_command(command)
310
- # ==============================================================================
311
- # 2.5. Google Drive Helper Class (नया सेक्शन)
312
- # ==============================================================================
313
- class GoogleDriveHelper:
314
- SCOPES = ['https://www.googleapis.com/auth/drive']
315
- # <<<--- YAHAN APNI GOOGLE DRIVE FOLDER ID DALEN --- >>>
316
- # Example: '1gOmqyt6IirhGCrXpKhHeU3_zWKpi-tCK'
317
- FOLDER_ID = '1gOmqyt6IirhGCrXpKhHeU3_zWKpi-tCK'
318
-
319
- def __init__(self):
320
- self.creds = self._get_credentials()
321
- self.service = build('drive', 'v3', credentials=self.creds)
322
- print("✅ Google Drive Helper सफलतापूर्वक शुरू हुआ।")
323
-
324
- def _get_credentials(self):
325
- creds_json_str = os.getenv('GOOGLE_CREDENTIALS_JSON')
326
- if not creds_json_str:
327
- raise Exception("Google Drive credentials secret (GOOGLE_CREDENTIALS_JSON) nahi mila.")
328
- try:
329
- creds_info = json.loads(creds_json_str)
330
- return service_account.Credentials.from_service_account_info(creds_info, scopes=self.SCOPES)
331
- except json.JSONDecodeError:
332
- raise Exception("GOOGLE_CREDENTIALS_JSON secret me JSON format apekshit hai.")
333
-
334
- def upload_file(self, file_path, file_name):
335
- print(f"-> Google Drive पर '{file_name}' अपलोड किया जा रहा है...")
336
- file_metadata = {'name': file_name, 'parents': [self.FOLDER_ID]}
337
- media = MediaFileUpload(file_path, resumable=True)
338
- file = self.service.files().create(body=file_metadata, media_body=media, fields='id, webViewLink, webContentLink').execute()
339
- file_id = file.get('id')
340
- print(f"-> फ़ाइल सफलतापूर्वक अपलोड हुई। ID: {file_id}")
341
- print("-> अनुमतियाँ (Permissions) सेट की जा रही हैं...")
342
- self.service.permissions().create(fileId=file_id, body={'type': 'anyone', 'role': 'reader'}).execute()
343
- print("-> अनुमतियाँ सफलतापूर्वक सेट हुईं।")
344
- return {
345
- 'id': file_id,
346
- 'view_link': file.get('webViewLink'),
347
- 'download_link': file.get('webContentLink')
348
- }
349
-
350
- def delete_file(self, file_id):
351
- try:
352
- print(f"-> Google Drive से फ़ाइल ID: {file_id} को हटाया जा रहा है...")
353
- self.service.files().delete(fileId=file_id).execute()
354
- print("-> फ़ाइल सफलतापूर्वक हटा दी गई।")
355
- return True
356
- except Exception as e:
357
- print(f"🚨 Google Drive से फ़ाइल हटाने में त्रुटि: {e}")
358
- return False
359
-
360
- def download_file(self, file_id, save_path):
361
- try:
362
- print(f"-> Google Drive से फ़ाइल ID: {file_id} को डाउनलोड किया जा रहा है...")
363
- request = self.service.files().get_media(fileId=file_id)
364
- fh = io.FileIO(save_path, 'wb')
365
- downloader = MediaIoBaseDownload(fh, request)
366
- done = False
367
- while done is False:
368
- status, done = downloader.next_chunk()
369
- print(f" -> डाउनलोड {int(status.progress() * 100)}% पूरा हुआ।")
370
- print(f"-> फ़ाइल सफलतापूर्वक '{save_path}' पर डाउनलोड हो गई।")
371
- return save_path
372
- except Exception as e:
373
- print(f"🚨 Google Drive से फ़ाइल डाउनलोड करने में त्रुटि: {e}")
374
- return None
375
-
376
 
377
  # ==============================================================================
378
- # 3. AI Engine Worker (Updated)
379
  # ==============================================================================
380
  def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, max_clip_length, mute_final_video):
381
  log = lambda message, progress: update_task_log(task_id, message, progress)
@@ -384,11 +312,7 @@ def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, ma
384
  log("Step 0: API Keys की पुष्टि...", 2)
385
  gemini_keys, pexels_keys, pixabay_keys, groq_keys = load_api_keys("Gemini_Key"), load_api_keys("Pexels_Key"), load_api_keys("Pixabay_Key"), load_api_keys("Groq_Key")
386
  if not all([gemini_keys, pexels_keys, pixabay_keys, groq_keys]): raise Exception("API Key Error: Missing one or more required keys.")
387
- gemini = GeminiTeam(api_keys=gemini_keys)
388
- gdrive = GoogleDriveHelper()
389
- log("-> सभी जरूरी API कीज मौजूद हैं।", 5)
390
-
391
- # ... (Step 1 se 5 ka code pehle jaisa hi rahega) ...
392
  log("Step 1: स्क्रिप्ट तैयार की जा रही है...", 8)
393
  os.makedirs(temp_dir, exist_ok=True); narration_audio_path = ""
394
  if script_file_path:
@@ -485,44 +409,16 @@ def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, ma
485
  output_filename = f"{task_id}_final_video.mp4"; output_path = os.path.join(OUTPUT_FOLDER, output_filename)
486
  assembler = VideoAssembler(final_gapless_timeline, narration_audio_path, output_path, width, height, mute_final_video, temp_dir)
487
  assembler.assemble_video(log)
488
-
489
- report_file_path = os.path.join(OUTPUT_FOLDER, f'{task_id}_report.json')
490
  try:
491
- log("-> अंतिम विस्तृत रिपोर्ट बनाई जा रही है...", 99)
492
  report_data = { "full_transcribed_script": full_script_text, "groq_word_timestamps": word_timestamps, "timestamps_with_pauses_added": timestamps_with_pauses, "gemini_scene_analysis_and_downloads": successful_scenes, "gemini_raw_timeline": final_timeline, "processed_gapless_timeline": final_gapless_timeline, }
 
493
  with open(report_file_path, 'w', encoding='utf-8') as f: json.dump(report_data, f, ensure_ascii=False, indent=4)
494
  log(f"-> विस्तृत रिपोर्ट सफलतापूर्वक '{report_file_path}' में सहेजी गई।", 99)
495
  except Exception as e: log(f"🚨 चेतावनी: विस्तृत रिपोर्ट सहेजने में विफल: {e}", 99)
496
-
497
- log("Step 6: Google Drive पर अंतिम फ़ाइलें अपलोड की जा रही हैं...", 99)
498
- video_upload_result = gdrive.upload_file(output_path, f"{task_id}_final_video.mp4")
499
- report_upload_result = None
500
- if os.path.exists(report_file_path):
501
- report_upload_result = gdrive.upload_file(report_file_path, f"{task_id}_report.json")
502
-
503
- log("-> डेटाबेस में अंतिम जानकारी सहेजी जा रही है...", 99)
504
- conn = get_db_connection()
505
- final_log = get_task(task_id)['log'] + "🎉 मिशन पूरा हुआ! फ़ाइलें Google Drive पर सुरक्षित हैं।"
506
- conn.execute('''
507
- UPDATE tasks
508
- SET status = ?, progress = ?, log = ?,
509
- video_file_id = ?, video_share_link = ?,
510
- report_file_id = ?, report_share_link = ?
511
- WHERE id = ?
512
- ''', ('complete', 100, final_log,
513
- video_upload_result['id'], video_upload_result['view_link'],
514
- report_upload_result['id'] if report_upload_result else None,
515
- report_upload_result['view_link'] if report_upload_result else None,
516
- task_id))
517
- conn.commit()
518
- conn.close()
519
-
520
- log("Step 7: पुरानी वीडियो को साफ़ किया जा रहा है (ऑटो-क्लीनअप)...", 100)
521
- cleanup_old_videos(gdrive)
522
-
523
  except Exception as e:
524
- import traceback
525
- traceback.print_exc()
526
  update_task_final_status(task_id, 'error', error_message=str(e))
527
  finally:
528
  if os.path.exists(temp_dir):
@@ -530,12 +426,6 @@ def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, ma
530
  shutil.rmtree(temp_dir)
531
  log(f"-> Temporary files aur folder '{temp_dir}' ko saaf kar diya gaya hai.", 100)
532
  except Exception as e: print(f"Cleanup Error: {e}")
533
- # Clean up local output files after upload
534
- local_video = os.path.join(OUTPUT_FOLDER, f"{task_id}_final_video.mp4")
535
- local_report = os.path.join(OUTPUT_FOLDER, f"{task_id}_report.json")
536
- if os.path.exists(local_video): os.remove(local_video)
537
- if os.path.exists(local_report): os.remove(local_report)
538
-
539
 
540
  # ==============================================================================
541
  # 4. AI Script Generation Function
@@ -550,38 +440,3 @@ def generate_script_with_ai(topic, video_length):
550
  return script
551
  except Exception as e:
552
  raise e
553
-
554
- # ==============================================================================
555
- # 5. Auto-Cleanup Function (New)
556
- # ==============================================================================
557
- def cleanup_old_videos(gdrive_helper, keep_latest=20):
558
- print(f"-> ऑटो-क्लीनअप शुरू: नवीनतम {keep_latest} वीडियो को छोड़कर ���ाकी सब हटाए जाएंगे।")
559
- try:
560
- conn = get_db_connection()
561
- tasks_to_delete = conn.execute(
562
- "SELECT * FROM tasks WHERE status = 'complete' ORDER BY creation_timestamp DESC LIMIT -1 OFFSET ?",
563
- (keep_latest,)
564
- ).fetchall()
565
-
566
- if not tasks_to_delete:
567
- print("-> हटाने के लिए कोई पुरानी वीडियो नहीं मिली।")
568
- conn.close()
569
- return
570
-
571
- print(f"-> हटाने के लिए {len(tasks_to_delete)} पुरानी वीडियो मिलीं।")
572
- for task in tasks_to_delete:
573
- task_id = task['id']
574
- print(f" -> टास्क हटाया जा रहा है: {task_id}")
575
-
576
- if task['video_file_id']:
577
- gdrive_helper.delete_file(task['video_file_id'])
578
- if task['report_file_id']:
579
- gdrive_helper.delete_file(task['report_file_id'])
580
-
581
- conn.execute("DELETE FROM tasks WHERE id = ?", (task_id,))
582
- conn.commit()
583
- print(f" -> टास्क {task_id} सफलतापूर्वक हटा दिया गया।")
584
-
585
- conn.close()
586
- except Exception as e:
587
- print(f"🚨 ऑटो-क्लीनअप के दौरान त्रुटि: {e}")
 
1
+ # ==============================================================================
2
+ # engine.py - [FINAL CORRECTED VERSION FOR HUGGING FACE]
3
+ # CHANGE 1: API Keys environment variables se load hongi.
4
+ # CHANGE 2: Sabhi folder paths (data, uploads, outputs) absolute hain.
5
+ # ==============================================================================
6
+ # engine.py के शुरुआत में
7
  import os
8
  import time
9
  import json
 
17
  import re
18
  from gtts import gTTS
19
  from werkzeug.utils import secure_filename
 
 
 
 
20
 
21
  # ==============================================================================
22
+ # 1. Global Setup and Database Functions (FINAL CORRECTED VERSION)
23
  # ==============================================================================
24
+ # <<<--- YAHAN SE BADLAV SHURU HAI --- >>>
25
+ # प्रोजेक्ट की रूट डायरेक्टरी को एब्सोल्यूट पाथ के रूप में सेट करें
26
+ APP_ROOT = '/code'
27
+ # सभी ज़रूरी फोल्डरों के लिए एब्सोल्यूट पाथ बनाएँ
28
  DATA_FOLDER = os.path.join(APP_ROOT, 'data')
29
  UPLOAD_FOLDER = os.path.join(APP_ROOT, 'uploads')
30
  OUTPUT_FOLDER = os.path.join(APP_ROOT, 'outputs')
31
+ # डेटाबेस फाइल का पूरा एब्सोल्यूट पाथ सेट करें
32
  DATABASE_FILE = os.path.join(DATA_FOLDER, 'tasks.db')
33
+ # <<<--- BADLAV YAHAN KHATM HOTA HAI --- >>>
 
 
 
 
 
34
 
35
  def get_db_connection():
36
+ # अब यह सही पाथ का उपयोग करेगा: /code/data/tasks.db
37
  conn = sqlite3.connect(DATABASE_FILE, check_same_thread=False)
38
  conn.row_factory = sqlite3.Row
39
  return conn
40
 
41
  def init_db():
42
  conn = get_db_connection()
43
+ conn.execute('CREATE TABLE IF NOT EXISTS tasks (id TEXT PRIMARY KEY, status TEXT NOT NULL, progress INTEGER NOT NULL, log TEXT, output_filename TEXT)')
 
 
 
 
 
 
 
 
 
 
 
44
  conn.commit()
45
  conn.close()
46
 
 
65
  conn.commit()
66
  conn.close()
67
 
68
+ def update_task_final_status(task_id, status, error_message=None, output_filename=None):
69
  conn = get_db_connection()
70
  current_log = conn.execute('SELECT log FROM tasks WHERE id = ?', (task_id,)).fetchone()['log']
71
  if status == 'error':
72
  final_log = current_log + f"\n\n🚨 FATAL ERROR: {error_message}"
73
+ conn.execute('UPDATE tasks SET status = ?, log = ? WHERE id = ?', (status, final_log, task_id))
74
+ elif status == 'complete':
75
+ final_log = current_log + "🎉 मिशन पूरा हुआ!"
76
+ conn.execute('UPDATE tasks SET status = ?, progress = ?, output_filename = ?, log = ? WHERE id = ?', (status, 100, output_filename, final_log, task_id))
77
  conn.commit()
78
  conn.close()
79
 
 
80
  def load_api_keys(prefix):
81
+ """
82
+ सिस्टम के एनवायरनमेंट वेरिएबल्स से API कीज़ लोड करता है।
83
+ """
84
  try:
85
  prefix_lower = prefix.lower()
86
  keys = [v for k, v in os.environ.items() if k.lower().startswith(prefix_lower)]
 
92
  return []
93
 
94
  # ==============================================================================
95
+ # 2. All API and Worker Classes
96
+ # (Is section mein koi badlav nahi hai)
97
  # ==============================================================================
 
 
98
  class GroqAPI:
99
  def __init__(self, api_keys): self.api_keys, self.api_url, self.model, self._key_index = api_keys, "https://api.groq.com/openai/v1/audio/transcriptions", "whisper-large-v3", 0
100
  def transcribe_audio(self, audio_path):
 
301
  '-c:a', 'aac', '-shortest', self.output_path
302
  ]
303
  self._run_ffmpeg_command(command)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
  # ==============================================================================
306
+ # 3. AI इंजन का मुख्य वर्कर
307
  # ==============================================================================
308
  def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, max_clip_length, mute_final_video):
309
  log = lambda message, progress: update_task_log(task_id, message, progress)
 
312
  log("Step 0: API Keys की पुष्टि...", 2)
313
  gemini_keys, pexels_keys, pixabay_keys, groq_keys = load_api_keys("Gemini_Key"), load_api_keys("Pexels_Key"), load_api_keys("Pixabay_Key"), load_api_keys("Groq_Key")
314
  if not all([gemini_keys, pexels_keys, pixabay_keys, groq_keys]): raise Exception("API Key Error: Missing one or more required keys.")
315
+ gemini = GeminiTeam(api_keys=gemini_keys); log("-> सभी जरूरी API कीज मौजूद हैं।", 5)
 
 
 
 
316
  log("Step 1: स्क्रिप्ट तैयार की जा रही है...", 8)
317
  os.makedirs(temp_dir, exist_ok=True); narration_audio_path = ""
318
  if script_file_path:
 
409
  output_filename = f"{task_id}_final_video.mp4"; output_path = os.path.join(OUTPUT_FOLDER, output_filename)
410
  assembler = VideoAssembler(final_gapless_timeline, narration_audio_path, output_path, width, height, mute_final_video, temp_dir)
411
  assembler.assemble_video(log)
412
+ log("-> अंतिम विस्तृत रिपोर्ट बनाई जा रही है...", 99)
 
413
  try:
 
414
  report_data = { "full_transcribed_script": full_script_text, "groq_word_timestamps": word_timestamps, "timestamps_with_pauses_added": timestamps_with_pauses, "gemini_scene_analysis_and_downloads": successful_scenes, "gemini_raw_timeline": final_timeline, "processed_gapless_timeline": final_gapless_timeline, }
415
+ report_file_path = os.path.join(OUTPUT_FOLDER, f'{task_id}_report.json')
416
  with open(report_file_path, 'w', encoding='utf-8') as f: json.dump(report_data, f, ensure_ascii=False, indent=4)
417
  log(f"-> विस्तृत रिपोर्ट सफलतापूर्वक '{report_file_path}' में सहेजी गई।", 99)
418
  except Exception as e: log(f"🚨 चेतावनी: विस्तृत रिपोर्ट सहेजने में विफल: {e}", 99)
419
+ update_task_final_status(task_id, 'complete', output_filename=output_filename)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  except Exception as e:
421
+ import traceback; traceback.print_exc()
 
422
  update_task_final_status(task_id, 'error', error_message=str(e))
423
  finally:
424
  if os.path.exists(temp_dir):
 
426
  shutil.rmtree(temp_dir)
427
  log(f"-> Temporary files aur folder '{temp_dir}' ko saaf kar diya gaya hai.", 100)
428
  except Exception as e: print(f"Cleanup Error: {e}")
 
 
 
 
 
 
429
 
430
  # ==============================================================================
431
  # 4. AI Script Generation Function
 
440
  return script
441
  except Exception as e:
442
  raise e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
faltuapp.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import threading
4
+ import zipfile
5
+ import shutil
6
+ from dotenv import load_dotenv
7
+ from flask import Flask, request, jsonify, render_template, send_from_directory, send_file
8
+ from werkzeug.utils import secure_filename
9
+
10
+ from engine import (
11
+ init_db, create_task, get_task, get_db_connection, run_ai_engine_worker,
12
+ generate_script_with_ai, UPLOAD_FOLDER, OUTPUT_FOLDER
13
+ )
14
+
15
+ load_dotenv()
16
+
17
+ app = Flask(__name__)
18
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
19
+ app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER
20
+ init_db()
21
+
22
+ # ==============================================================================
23
+ # PWA, Static File, and Favicon Routes
24
+ # ==============================================================================
25
+ @app.route('/favicon.ico')
26
+ def favicon():
27
+ return send_from_directory('static', 'favicon.png')
28
+
29
+ @app.route('/manifest.json')
30
+ def serve_manifest():
31
+ return send_from_directory('static', 'manifest.json')
32
+
33
+ @app.route('/sw.js')
34
+ def serve_sw():
35
+ return send_from_directory('static', 'sw.js')
36
+
37
+ @app.route('/icon-512x512.png')
38
+ def serve_icon_512():
39
+ return send_from_directory('static', 'icon-512x512.png')
40
+
41
+ @app.route('/icon-192x192.png')
42
+ def serve_icon_192():
43
+ return send_from_directory('static', 'icon-192x192.png')
44
+
45
+ # ==============================================================================
46
+ # Main Application Routes
47
+ # ==============================================================================
48
+ @app.route('/')
49
+ def home():
50
+ return render_template('home.html')
51
+
52
+ @app.route('/process', methods=['POST'])
53
+ def process_video():
54
+ task_id = str(uuid.uuid4())
55
+ create_task(task_id)
56
+ script_text = request.form.get('script_text')
57
+ script_file = request.files.get('script_file')
58
+ orientation = request.form.get('orientation', 'horizontal')
59
+ max_clip_length = int(request.form.get('max_clip_length', 15))
60
+ mute_final_video = request.form.get('mute_final_video') == 'true'
61
+ script_file_path = None
62
+ if script_file and script_file.filename:
63
+ filename = secure_filename(script_file.filename)
64
+ task_upload_dir = os.path.join(app.config['UPLOAD_FOLDER'], task_id)
65
+ os.makedirs(task_upload_dir, exist_ok=True)
66
+ script_file_path = os.path.join(task_upload_dir, filename)
67
+ script_file.save(script_file_path)
68
+ if not script_text and not script_file_path:
69
+ return "Please provide a script text or an audio file.", 400
70
+ thread = threading.Thread(
71
+ target=run_ai_engine_worker,
72
+ args=(task_id, script_text, script_file_path, orientation, max_clip_length, mute_final_video)
73
+ )
74
+ thread.start()
75
+ return render_template('processing.html', task_id=task_id)
76
+
77
+ @app.route('/generate-script', methods=['POST'])
78
+ def generate_script():
79
+ data = request.get_json()
80
+ topic = data.get('topic')
81
+ video_length = data.get('video_length')
82
+ if not topic or not video_length:
83
+ return jsonify({'error': 'Topic and video length are required.'}), 400
84
+ try:
85
+ generated_script = generate_script_with_ai(topic, video_length)
86
+ return jsonify({'script': generated_script})
87
+ except Exception as e:
88
+ print(f"Error during script generation: {e}")
89
+ return jsonify({'error': f"AI से संपर्क करने में विफल: {str(e)}"}), 500
90
+
91
+ @app.route('/progress/<task_id>')
92
+ def progress(task_id):
93
+ task = get_task(task_id)
94
+ if not task:
95
+ return jsonify({'status': 'error', 'log': 'Task not found.'})
96
+ # task object ko dictionary me convert karein
97
+ task_dict = dict(task)
98
+ # Timestamp ko string me convert karein agar woh object hai
99
+ if 'creation_timestamp' in task_dict and task_dict['creation_timestamp'] is not None:
100
+ task_dict['creation_timestamp'] = str(task_dict['creation_timestamp'])
101
+ return jsonify(task_dict)
102
+
103
+
104
+ @app.route('/result/<task_id>')
105
+ def result(task_id):
106
+ task = get_task(task_id)
107
+ if not task or task['status'] != 'complete':
108
+ return "Task not found or not complete.", 404
109
+ return render_template('result.html', task=dict(task))
110
+
111
+ # Yeh route ab direct file serve nahi karega, isliye ise hata sakte hain ya aise hi chhod sakte hain
112
+ @app.route('/outputs/<path:filename>')
113
+ def serve_output_file(filename):
114
+ return "Direct file access is disabled. Please use history.", 403
115
+
116
+ # ==============================================================================
117
+ # History and Bulk Action Routes (Updated)
118
+ # ==============================================================================
119
+ @app.route('/history')
120
+ def history():
121
+ conn = get_db_connection()
122
+ # Sirf complete tasks dikhayein, naye se purane kram me
123
+ tasks = conn.execute("SELECT * FROM tasks WHERE status = 'complete' ORDER BY creation_timestamp DESC").fetchall()
124
+ conn.close()
125
+
126
+ history_items = []
127
+ for task in tasks:
128
+ item = {
129
+ 'task_id': task['id'],
130
+ 'video_link': task['video_share_link'],
131
+ 'report_link': task['report_share_link'],
132
+ 'timestamp': task['creation_timestamp']
133
+ }
134
+ history_items.append(item)
135
+
136
+ return render_template('history.html', items=history_items)
137
+
138
+ @app.route('/history/delete', methods=['POST'])
139
+ def delete_history_items():
140
+ data = request.get_json()
141
+ task_ids = data.get('task_ids', [])
142
+ if not task_ids:
143
+ return jsonify({'error': 'No task IDs provided'}), 400
144
+
145
+ try:
146
+ gdrive = GoogleDriveHelper()
147
+ conn = get_db_connection()
148
+ for task_id in task_ids:
149
+ task = conn.execute("SELECT video_file_id, report_file_id FROM tasks WHERE id = ?", (task_id,)).fetchone()
150
+ if task:
151
+ if task['video_file_id']:
152
+ gdrive.delete_file(task['video_file_id'])
153
+ if task['report_file_id']:
154
+ gdrive.delete_file(task['report_file_id'])
155
+ conn.execute("DELETE FROM tasks WHERE id = ?", (task_id,))
156
+ conn.commit()
157
+ conn.close()
158
+ return jsonify({'success': True, 'message': f'{len(task_ids)} items deleted.'})
159
+ except Exception as e:
160
+ print(f"Error during bulk delete: {e}")
161
+ return jsonify({'error': str(e)}), 500
162
+
163
+ @app.route('/history/download', methods=['POST'])
164
+ def download_history_items():
165
+ data = request.get_json()
166
+ task_ids = data.get('task_ids', [])
167
+ if not task_ids:
168
+ return jsonify({'error': 'No task IDs provided'}), 400
169
+
170
+ temp_dir = os.path.join(app.config['UPLOAD_FOLDER'], f"download_{uuid.uuid4()}")
171
+ os.makedirs(temp_dir, exist_ok=True)
172
+ zip_path = os.path.join(app.config['UPLOAD_FOLDER'], f"download_{uuid.uuid4()}.zip")
173
+
174
+ try:
175
+ gdrive = GoogleDriveHelper()
176
+ conn = get_db_connection()
177
+ for task_id in task_ids:
178
+ task = conn.execute("SELECT video_file_id, report_file_id FROM tasks WHERE id = ?", (task_id,)).fetchone()
179
+ if task:
180
+ if task['video_file_id']:
181
+ gdrive.download_file(task['video_file_id'], os.path.join(temp_dir, f"{task_id}_video.mp4"))
182
+ if task['report_file_id']:
183
+ gdrive.download_file(task['report_file_id'], os.path.join(temp_dir, f"{task_id}_report.json"))
184
+ conn.close()
185
+
186
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
187
+ for root, dirs, files in os.walk(temp_dir):
188
+ for file in files:
189
+ zipf.write(os.path.join(root, file), arcname=file)
190
+
191
+ return send_file(zip_path, as_attachment=True, download_name='AI_Videos_Export.zip')
192
+ except Exception as e:
193
+ print(f"Error during bulk download: {e}")
194
+ return jsonify({'error': str(e)}), 500
195
+ finally:
196
+ if os.path.exists(temp_dir):
197
+ shutil.rmtree(temp_dir)
198
+ if os.path.exists(zip_path):
199
+ os.remove(zip_path)
200
+
201
+ if __name__ == '__main__':
202
+ app.run(host='0.0.0.0', port=5000, debug=True)
oldapp.py DELETED
@@ -1,129 +0,0 @@
1
- # ==============================================================================
2
- # app.py - [FINAL CORRECTED VERSION FOR HUGGING FACE v2]
3
- # CHANGE: init_db() ko app startup par call kiya gaya hai.
4
- # ==============================================================================
5
- import os
6
- from dotenv import load_dotenv
7
-
8
- # Ye line aapke local .env file se variables load karegi (Hugging Face par iska koi asar nahi)
9
- load_dotenv()
10
-
11
- import uuid
12
- import threading
13
- from flask import Flask, request, jsonify, render_template_string, send_from_directory
14
- from werkzeug.utils import secure_filename
15
-
16
- # engine.py se zaroori functions AUR FOLDER PATHS import karein
17
- from engine import (
18
- init_db, create_task, get_task, run_ai_engine_worker,
19
- generate_script_with_ai,
20
- UPLOAD_FOLDER,
21
- OUTPUT_FOLDER
22
- )
23
-
24
-
25
- # ==============================================================================
26
- # 1. HTML Templates
27
- # ==============================================================================
28
- MODERN_CSS_BASE = """<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet"><style>:root{--bg-color:#1a1a2e;--card-bg-color:rgba(22,22,46,0.7);--primary-color:#e94560;--secondary-color:#0f3460;--text-color:#f0f0f0;--text-muted-color:#a0a0a0;--border-color:rgba(233,69,96,0.2);--gradient:linear-gradient(90deg,#e94560,#a63f82)}body{font-family:'Poppins',sans-serif;background-color:var(--bg-color);background-image:linear-gradient(315deg,var(--bg-color) 0%,var(--secondary-color) 74%);color:var(--text-color);padding-top:20px;padding-bottom:20px}.card{background:var(--card-bg-color);border:1px solid var(--border-color);border-radius:16px;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);box-shadow:0 8px 32px 0 rgba(0,0,0,0.37);animation:fadeInUp 0.5s ease-out}h1,h2,h3,h4,h5,h6{background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-weight:700}.text-muted{color:var(--text-muted-color) !important}.form-control,.form-select{background-color:rgba(0,0,0,0.2);color:var(--text-color);border:1px solid var(--border-color);border-radius:8px}.form-control:focus,.form-select:focus{background-color:rgba(0,0,0,0.3);color:var(--text-color);border-color:var(--primary-color);box-shadow:0 0 0 .25rem rgba(233,69,96,0.25)}.form-control::placeholder{color:var(--text-muted-color);opacity:.7}.btn{border:none;border-radius:8px;font-weight:600;padding:12px 24px;transition:transform .1s ease-out,box-shadow .2s ease-out,filter .2s ease-out}.btn:hover{transform:translateY(-3px);box-shadow:0 5px 15px rgba(0,0,0,.3)}.btn:active{transform:translateY(0) scale(.98);filter:brightness(1.2)}.btn-primary{background:var(--gradient);color:#fff}.btn-primary:active{box-shadow:0 0 5px var(--primary-color),0 0 25px var(--primary-color),0 0 50px var(--primary-color)}.btn-secondary:active,.btn-dark:active{box-shadow:0 0 5px rgba(255,255,255,.8),0 0 25px rgba(255,255,255,.5)}@keyframes fadeInUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}</style>"""
29
- HTML_HOME = """<!DOCTYPE html><html lang="hi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Sparkling Gyan AI - होम</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">""" + MODERN_CSS_BASE + """<style>.ai-generator-controls{border:1px solid var(--border-color);border-radius:12px;padding:20px;margin-top:20px;background-color:rgba(0,0,0,.1)}</style></head><body><div class="container my-5"><div class="card p-4 p-md-5"><div class="d-flex justify-content-between align-items-center mb-3"><h1 class="mb-0">✨ Sparkling Gyan AI</h1><a href="/history" class="btn btn-secondary"><i class="fas fa-history"></i> हिस्ट्री देखें</a></div><p class="text-center text-muted mb-4">एक उन्नत, ऑटोमेटिक वीडियो जेनरेटर</p><form id="upload-form" action="/process" method="POST" enctype="multipart/form-data"><div class="mb-3"><label for="script_text" class="form-label fs-5 fw-bold text-light">१. कहानी लिखें</label><textarea class="form-control" id="script_text" name="script_text" rows="8" placeholder="यहाँ कोई टॉपिक लिखें (जैसे 'चंद्रमा के रहस्य') और नीचे 'AI से स्क्रिप्ट बनाएँ' बटन दबाएँ, या अपनी पूरी स्क्रिप्ट यहाँ पेस्ट करें..."></textarea></div><div class="ai-generator-controls"><div class="row align-items-end"><div class="col-md-5"><label for="video-length-select" class="form-label text-light">वीडियो की लंबाई</label><select id="video-length-select" class="form-select"><option value="short">छोटी (~30 सेकंड)</option><option value="medium" selected>मध्यम (~1 मिनट)</option><option value="long">लंबी (~2 मिनट)</option></select></div><div class="col-md-7 d-grid"><button type="button" id="generate-script-btn" class="btn btn-dark"><i class="fas fa-magic"></i> AI से स्क्रिप्ट बनाएँ</button></div></div><div id="ai-status" class="mt-2"></div></div><div class="text-center text-muted my-3">या</div><div class="mb-3"><label for="script_file" class="form-label">एक ऑडियो स्क्रिप्ट फ़ाइल अपलोड करें</label><input class="form-control" type="file" id="script_file" name="script_file" accept="audio/*"></div><h5 class="mt-4 mb-3 text-light">२. अतिरिक्त विकल्प</h5><div class="row mb-3"><div class="col-md-6"><label class="form-label">वीडियो ओरिएंटेशन</label><div class="form-check"><input class="form-check-input" type="radio" name="orientation" id="orientation_horizontal" value="horizontal" checked><label class="form-check-label" for="orientation_horizontal">Landscape (16:9)</label></div><div class="form-check"><input class="form-check-input" type="radio" name="orientation" id="orientation_vertical" value="vertical"><label class="form-check-label" for="orientation_vertical">Portrait (9:16)</label></div></div><div class="col-md-6"><label for="max_clip_length" class="form-label">अधिकतम क्लिप लंबाई (सेकंड)</label><input type="number" class="form-control" id="max_clip_length" name="max_clip_length" value="15" min="3"></div></div><div class="form-check mb-4"><input class="form-check-input" type="checkbox" value="true" id="mute_final_video" name="mute_final_video"><label class="form-check-label" for="mute_final_video">ऑडियो/वॉयसओवर ट्रैक को म्यूट करें</label></div><div class="d-grid mt-4"><button type="submit" class="btn btn-primary btn-lg">🚀 वीडियो बनाना शुरू करें!</button></div></form></div></div><script>document.addEventListener("DOMContentLoaded",()=>{const e=document.getElementById("generate-script-btn"),t=document.getElementById("script_text"),o=document.getElementById("video-length-select"),n=document.getElementById("ai-status");e.addEventListener("click",async()=>{const a=t.value.trim();if(!a)return void(n.innerHTML='<p class="text-danger">कृपया स्क्रिप्ट बनाने के लिए कोई टॉपिक लिखें।</p>');const i=o.value;e.disabled=!0,e.innerHTML='<i class="fas fa-spinner fa-spin"></i> जेनरेट हो रहा है...',n.innerHTML="<p>🧠 AI आपके लिए एक बेहतरीन स्क्रिप्ट लिख रहा है, कृपया प्रतीक्षा करें...</p>";try{const r=await fetch("/generate-script",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({topic:a,video_length:i})}),c=await r.json();if(!r.ok)throw new Error(c.error||"सर्वर से कोई अज्ञात त्रुटि हुई।");t.value=c.script,n.innerHTML='<p style="color: #2ea043;">✅ AI ने स्क्रिप्ट तैयार कर दी है! अब आप वीडियो बना सकते हैं।</p>'}catch(d){console.error("Script Generation Error:",d),n.innerHTML=`<p class="text-danger">स्क्रिप्ट बनाने में विफल: ${d.message}</p>`}finally{e.disabled=!1,e.innerHTML='<i class="fas fa-magic"></i> AI से स्क्रिप्ट बनाएँ'}})})</script></body></html>"""
30
- HTML_PROCESSING = """<!DOCTYPE html><html lang="hi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>प्रोसेसिंग...</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">""" + MODERN_CSS_BASE + """<style>body{display:flex;align-items:center;justify-content:center;min-height:100vh}#log-output{white-space:pre-wrap;background-color:rgba(0,0,0,.3);font-family:monospace;max-height:400px;overflow-y:auto;border-radius:8px;border:1px solid var(--border-color)}.progress-bar{background:var(--gradient)}.progress{height:25px;border-radius:8px;background-color:rgba(0,0,0,.3)}</style></head><body><div class="container"><div class="card p-4 p-md-5"><h2 class="text-center">मिशन कंट्रोल</h2><p id="status-text" class="text-center text-muted">प्रक्रिया शुरू हो रही है...</p><div class="progress mb-3" role="progressbar"><div class="progress-bar progress-bar-striped progress-bar-animated" id="progress-bar" style="width:0%">0%</div></div><pre id="log-output" class="p-3"></pre></div></div><script>const taskId="{{task_id}}",progressBar=document.getElementById("progress-bar"),statusText=document.getElementById("status-text"),logOutput=document.getElementById("log-output"),pollingInterval=setInterval(async()=>{try{const e=await fetch(`/progress/${taskId}`),t=await e.json(),o=t.log.trim().split("\\n").pop();statusText.innerText=o||"प्रतीक्षा में...",progressBar.style.width=t.progress+"%",progressBar.innerText=t.progress+"%",logOutput.innerText=t.log,logOutput.scrollTop=logOutput.scrollHeight,"complete"===t.status?(clearInterval(pollingInterval),window.location.href=`/result/${t.output_filename}`):"error"===t.status&&(clearInterval(pollingInterval),statusText.innerText="कोई त्रुटि हुई! विवरण के लिए लॉग देखें।",progressBar.classList.remove("bg-success"),progressBar.classList.add("bg-danger"))}catch(e){clearInterval(pollingInterval),statusText.innerText="सर्वर से कनेक्शन टूट गया।"}},1500)</script></body></html>"""
31
- HTML_RESULT = """<!DOCTYPE html><html lang="hi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>आपकी वीडियो तैयार है!</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">""" + MODERN_CSS_BASE + """<style>body{display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center}video{width:100%;max-width:800px;border-radius:12px;border:1px solid var(--border-color)}</style></head><body><div class="container"><div class="card p-4 p-md-5"><h1 class="mb-3">🎉 मिशन पूरा हुआ!</h1><p class="text-muted mb-4">आपकी फाइनल वीडियो नीचे है।</p><video controls autoplay muted loop><source src="/outputs/{{ filename }}" type="video/mp4"> आपका ब्राउज़र वीडियो टैग को सपोर्ट नहीं करता। </video><div class="d-grid gap-2 d-md-flex justify-content-md-center mt-4"><a href="/outputs/{{ filename }}" class="btn btn-primary" download><i class="fas fa-download"></i> वीडियो डाउनलोड करें</a> <a href="/" class="btn btn-secondary">एक और वीडियो बनाएँ</a></div></div></div></body></html>"""
32
- HTML_HISTORY = """<!DOCTYPE html><html lang="hi"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>वीडियो हिस्ट्री</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">""" + MODERN_CSS_BASE + """<style>.history-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(350px,1fr));gap:20px}.card{transition:transform .3s ease}.card:hover{transform:translateY(-5px)}video{width:100%;border-radius:8px;margin-top:10px;border:1px solid var(--border-color)}.actions a{margin-right:10px;margin-bottom:10px}</style></head><body><div class="container"><a href="/" class="btn btn-secondary mb-4"><i class="fas fa-arrow-left"></i> वापस मुख्य पेज पर</a><h1 class="text-center mb-5">बनाए गए वीडियो की हिस्ट्री</h1> {% if items %}<div class="history-grid"> {% for item in items %}<div class="card"><div class="card-body"><h5 class="card-title">टास्क आईडी:</h5><p class="card-text text-muted" style="font-size:.8em">{{ item.task_id }}</p><video controls preload="metadata"><source src="/outputs/{{ item.video }}" type="video/mp4"></video><div class="actions mt-3"><a href="/outputs/{{ item.video }}" class="btn btn-primary" download><i class="fas fa-download"></i> डाउनलोड</a> {% if item.report %}<a href="/outputs/{{ item.report }}" class="btn btn-dark" target="_blank"><i class="fas fa-file-alt"></i> रिपोर्ट देखें</a> {% endif %}</div></div></div> {% endfor %}</div> {% else %}<div class="text-center card p-5"><h2>कोई हिस्ट्री नहीं मिली</h2><p class="text-muted">आपने अभी तक कोई वीडियो नहीं बनाया है।</p></div> {% endif %}</div></body></html>"""
33
-
34
-
35
- # ==============================================================================
36
- # Flask Application Setup
37
- # ==============================================================================
38
-
39
- app = Flask(__name__)
40
- app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
41
- app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER
42
-
43
- # <<<--- YAHAN EK LINE JODI GAYI HAI --- >>>
44
- # Database table banayein agar pehle se nahi bani hai
45
- init_db()
46
- # <<<--- BADLAV YAHAN KHATM HOTA HAI --- >>>
47
-
48
- @app.route('/')
49
- def home():
50
- return render_template_string(HTML_HOME)
51
-
52
- @app.route('/process', methods=['POST'])
53
- def process_video():
54
- task_id = str(uuid.uuid4())
55
- create_task(task_id)
56
- script_text = request.form.get('script_text')
57
- script_file = request.files.get('script_file')
58
- orientation = request.form.get('orientation', 'horizontal')
59
- max_clip_length = int(request.form.get('max_clip_length', 15))
60
- mute_final_video = request.form.get('mute_final_video') == 'true'
61
- script_file_path = None
62
- if script_file and script_file.filename:
63
- filename = secure_filename(script_file.filename)
64
- task_upload_dir = os.path.join(app.config['UPLOAD_FOLDER'], task_id)
65
- os.makedirs(task_upload_dir, exist_ok=True)
66
- script_file_path = os.path.join(task_upload_dir, filename)
67
- script_file.save(script_file_path)
68
- if not script_text and not script_file_path:
69
- return "Please provide a script text or an audio file.", 400
70
- thread = threading.Thread(
71
- target=run_ai_engine_worker,
72
- args=(task_id, script_text, script_file_path, orientation, max_clip_length, mute_final_video)
73
- )
74
- thread.start()
75
- return render_template_string(HTML_PROCESSING, task_id=task_id)
76
-
77
- @app.route('/generate-script', methods=['POST'])
78
- def generate_script():
79
- data = request.get_json()
80
- topic = data.get('topic')
81
- video_length = data.get('video_length')
82
- if not topic or not video_length:
83
- return jsonify({'error': 'Topic and video length are required.'}), 400
84
- try:
85
- generated_script = generate_script_with_ai(topic, video_length)
86
- return jsonify({'script': generated_script})
87
- except Exception as e:
88
- print(f"Error during script generation: {e}")
89
- return jsonify({'error': f"AI से संपर्क करने में विफल: {str(e)}"}), 500
90
-
91
- @app.route('/progress/<task_id>')
92
- def progress(task_id):
93
- task = get_task(task_id)
94
- if not task:
95
- return jsonify({'status': 'error', 'log': 'Task not found.'})
96
- return jsonify(dict(task))
97
-
98
- @app.route('/result/<filename>')
99
- def result(filename):
100
- return render_template_string(HTML_RESULT, filename=filename)
101
-
102
- @app.route('/outputs/<path:filename>')
103
- def serve_output_file(filename):
104
- return send_from_directory(app.config['OUTPUT_FOLDER'], filename)
105
-
106
- @app.route('/history')
107
- def history():
108
- output_dir = app.config['OUTPUT_FOLDER']
109
- history_items = []
110
- try:
111
- all_files = sorted(os.listdir(output_dir), reverse=True)
112
- for filename in all_files:
113
- if filename.endswith('_final_video.mp4'):
114
- task_id = filename.replace('_final_video.mp4', '')
115
- report_filename = f"{task_id}_report.json"
116
- item = {
117
- 'video': filename,
118
- 'task_id': task_id,
119
- 'report': report_filename if report_filename in all_files else None
120
- }
121
- history_items.append(item)
122
- except FileNotFoundError:
123
- print(f"'{output_dir}' directory not found.")
124
- return render_template_string(HTML_HISTORY, items=history_items)
125
-
126
- # Is block ko ab production mein ignore kar diya jayega
127
- if __name__ == '__main__':
128
- init_db()
129
- app.run(host='0.0.0.0', port=5000, debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
oldengn.py DELETED
@@ -1,442 +0,0 @@
1
- # ==============================================================================
2
- # engine.py - [FINAL CORRECTED VERSION FOR HUGGING FACE]
3
- # CHANGE 1: API Keys environment variables se load hongi.
4
- # CHANGE 2: Sabhi folder paths (data, uploads, outputs) absolute hain.
5
- # ==============================================================================
6
- # engine.py के शुरुआत में
7
- import os
8
- import time
9
- import json
10
- import uuid
11
- import threading
12
- import subprocess
13
- import requests
14
- import sqlite3
15
- import random
16
- import shutil
17
- import re
18
- from gtts import gTTS
19
- from werkzeug.utils import secure_filename
20
-
21
- # ==============================================================================
22
- # 1. Global Setup and Database Functions (FINAL CORRECTED VERSION)
23
- # ==============================================================================
24
- # <<<--- YAHAN SE BADLAV SHURU HAI --- >>>
25
- # प्रोजेक्ट की रूट डायरेक्टरी को एब्सोल्यूट पाथ के रूप में सेट करें
26
- APP_ROOT = '/code'
27
- # सभी ज़रूरी फोल्डरों के लिए एब्सोल्यूट पाथ बनाएँ
28
- DATA_FOLDER = os.path.join(APP_ROOT, 'data')
29
- UPLOAD_FOLDER = os.path.join(APP_ROOT, 'uploads')
30
- OUTPUT_FOLDER = os.path.join(APP_ROOT, 'outputs')
31
- # डेटाबेस फाइल का पूरा एब्सोल्यूट पाथ सेट करें
32
- DATABASE_FILE = os.path.join(DATA_FOLDER, 'tasks.db')
33
- # <<<--- BADLAV YAHAN KHATM HOTA HAI --- >>>
34
-
35
- def get_db_connection():
36
- # अब यह सही पाथ का उपयोग करेगा: /code/data/tasks.db
37
- conn = sqlite3.connect(DATABASE_FILE, check_same_thread=False)
38
- conn.row_factory = sqlite3.Row
39
- return conn
40
-
41
- def init_db():
42
- conn = get_db_connection()
43
- conn.execute('CREATE TABLE IF NOT EXISTS tasks (id TEXT PRIMARY KEY, status TEXT NOT NULL, progress INTEGER NOT NULL, log TEXT, output_filename TEXT)')
44
- conn.commit()
45
- conn.close()
46
-
47
- def create_task(task_id):
48
- log_message = "मिशन शुरू हो रहा है...\n"
49
- conn = get_db_connection()
50
- conn.execute('INSERT INTO tasks (id, status, progress, log) VALUES (?, ?, ?, ?)', (task_id, 'processing', 0, log_message))
51
- conn.commit()
52
- conn.close()
53
-
54
- def get_task(task_id):
55
- conn = get_db_connection()
56
- task = conn.execute('SELECT * FROM tasks WHERE id = ?', (task_id,)).fetchone()
57
- conn.close()
58
- return task
59
-
60
- def update_task_log(task_id, message, progress):
61
- conn = get_db_connection()
62
- current_log = conn.execute('SELECT log FROM tasks WHERE id = ?', (task_id,)).fetchone()['log']
63
- new_log = current_log + message + "\n"
64
- conn.execute('UPDATE tasks SET log = ?, progress = ? WHERE id = ?', (new_log, progress, task_id))
65
- conn.commit()
66
- conn.close()
67
-
68
- def update_task_final_status(task_id, status, error_message=None, output_filename=None):
69
- conn = get_db_connection()
70
- current_log = conn.execute('SELECT log FROM tasks WHERE id = ?', (task_id,)).fetchone()['log']
71
- if status == 'error':
72
- final_log = current_log + f"\n\n🚨 FATAL ERROR: {error_message}"
73
- conn.execute('UPDATE tasks SET status = ?, log = ? WHERE id = ?', (status, final_log, task_id))
74
- elif status == 'complete':
75
- final_log = current_log + "🎉 मिशन पूरा हुआ!"
76
- conn.execute('UPDATE tasks SET status = ?, progress = ?, output_filename = ?, log = ? WHERE id = ?', (status, 100, output_filename, final_log, task_id))
77
- conn.commit()
78
- conn.close()
79
-
80
- def load_api_keys(prefix):
81
- """
82
- सिस्टम के एनवायरनमेंट वेरिएबल्स से API कीज़ लोड करता है।
83
- """
84
- try:
85
- prefix_lower = prefix.lower()
86
- keys = [v for k, v in os.environ.items() if k.lower().startswith(prefix_lower)]
87
- if not keys:
88
- print(f"🚨 चेतावनी: '{prefix}' से शुरू होने वाला कोई एनवायरनमेंट वेरिएबल नहीं मिला।")
89
- return keys
90
- except Exception as e:
91
- print(f"🚨 एनवायरनमेंट वेरिएबल्स लोड करते समय त्रुटि: {e}")
92
- return []
93
-
94
- # ==============================================================================
95
- # 2. All API and Worker Classes
96
- # (Is section mein koi badlav nahi hai)
97
- # ==============================================================================
98
- class GroqAPI:
99
- def __init__(self, api_keys): self.api_keys, self.api_url, self.model, self._key_index = api_keys, "https://api.groq.com/openai/v1/audio/transcriptions", "whisper-large-v3", 0
100
- def transcribe_audio(self, audio_path):
101
- if not self.api_keys: raise Exception("Groq API key not found.")
102
- api_key = self.api_keys[self._key_index % len(self.api_keys)]; self._key_index += 1
103
- data = {'model': self.model, 'response_format': 'verbose_json', 'timestamp_granularities[]': 'word'}
104
- headers = {'Authorization': f'Bearer {api_key}'}
105
- try:
106
- with open(audio_path, 'rb') as audio_file:
107
- files = {'file': (os.path.basename(audio_path), audio_file, 'audio/mpeg')}; print(f"-> Groq API को शब्द-स्तर पर टाइमस्टैम्प के लिए भेजा जा रहा है...")
108
- response = requests.post(self.api_url, headers=headers, data=data, files=files, timeout=120); response.raise_for_status()
109
- words_data = response.json().get('words', []); print(f"-> ट्रांसक्रिप्शन सफल: {len(words_data)} शब्दों के टाइमस्टैम्प मिले।"); return words_data
110
- except Exception as e: raise Exception(f"Groq API Error: {e}")
111
-
112
- class PexelsAPI:
113
- def __init__(self, api_keys):
114
- if not api_keys: raise Exception("Pexels API key not found.")
115
- self.api_key = api_keys[0]; self.api_url = "https://api.pexels.com/videos/search"
116
- def search_and_download(self, query, download_path, orientation, search_page=1):
117
- print(f"-> Pexels पर खोजा जा रहा है (Direct API): '{query}' (Page: {search_page}, Orientation: {orientation})")
118
- headers = {'Authorization': self.api_key}; params = {'query': query, 'page': search_page, 'per_page': 1, 'orientation': orientation}
119
- try:
120
- response = requests.get(self.api_url, headers=headers, params=params, timeout=60); response.raise_for_status(); data = response.json()
121
- if not data.get('videos'): print(f"-> Pexels पर '{query}' के लिए कोई परिणाम नहीं मिला।"); return None
122
- video_data = data['videos'][0]; video_files = video_data.get('video_files', []); best_link = None
123
- for video_file in video_files:
124
- if video_file.get('quality') == 'hd': best_link = video_file.get('link'); break
125
- if not best_link and video_files: best_link = video_files[0].get('link')
126
- if not best_link: print(f"-> Pexels परिणाम में कोई डाउनलोड करने योग्य लिंक नहीं मिला।"); return None
127
- print(f"-> Pexels से वीडियो डाउनलोड किया जा रहा है..."); download_response = requests.get(best_link, stream=True, timeout=60); download_response.raise_for_status()
128
- with open(download_path, 'wb') as f:
129
- for chunk in download_response.iter_content(chunk_size=8192): f.write(chunk)
130
- print(f"-> सफलतापूर्वक सहेजा गया: {download_path}"); return download_path
131
- except requests.exceptions.RequestException as e: print(f"🚨 Pexels API में त्रुटि: {e}"); return None
132
- except Exception as e: print(f"🚨 Pexels वीडियो डाउनलोड करने में अज्ञात त्रुटि: {e}"); return None
133
-
134
- class PixabayAPI:
135
- def __init__(self, api_keys):
136
- if not api_keys: raise Exception("Pixabay API key not found.")
137
- self.api_key = api_keys[0]; self.api_url = "https://pixabay.com/api/videos/"
138
- def search_and_download(self, query, download_path, orientation, max_clip_length, search_index=0):
139
- print(f"-> Pixabay पर खोजा जा रहा है: '{query}' (Index: {search_index})")
140
- params = {'key': self.api_key, 'q': query, 'per_page': 5, 'orientation': orientation, 'max_duration': int(max_clip_length)}
141
- try:
142
- response = requests.get(self.api_url, params=params, timeout=60); response.raise_for_status(); results = response.json()
143
- if not results['hits'] or len(results['hits']) <= search_index: print(f"-> Pixabay पर '{query}' के लिए index {search_index} पर कोई परिणाम नहीं मिला।"); return None
144
- video_url = results['hits'][search_index]['videos']['medium']['url']; print(f"-> Pixabay से वीडियो डाउनलोड किया जा रहा है...")
145
- response = requests.get(video_url, stream=True, timeout=60); response.raise_for_status()
146
- with open(download_path, 'wb') as f:
147
- for chunk in response.iter_content(chunk_size=8192): f.write(chunk)
148
- print(f"-> सफलतापूर्वक सहेजा गया: {download_path}"); return download_path
149
- except Exception as e: print(f"🚨 Pixabay API में त्रुटि: {e}"); return None
150
-
151
- class GeminiTeam:
152
- MODELS_LIST_URL = "https://generativelanguage.googleapis.com/v1beta/models"
153
- def __init__(self, api_keys):
154
- self.api_keys = api_keys
155
- if not self.api_keys: raise Exception("Gemini API key not found.")
156
- self.model_name = self._find_best_model()
157
- if not self.model_name: raise Exception("Could not dynamically find a suitable Gemini 'flash' model from any of the provided keys.")
158
- self.api_url = f"https://generativelanguage.googleapis.com/v1beta/{self.model_name}:generateContent"
159
- print(f"✅ स्मार्ट मॉडल हंटर सफल: '{self.model_name}' का उपयोग किया जाएगा।")
160
- def _find_best_model(self):
161
- print("-> स्मार्ट मॉडल हंटर: सबसे अच्छे 'gemini-*-flash' मॉडल को खोजा जा रहा है...")
162
- for api_key in self.api_keys:
163
- try:
164
- print(f"-> API Key के अंतिम 4 अक्षरों से कोशिश की जा रही है: ...{api_key[-4:]}")
165
- response = requests.get(f"{self.MODELS_LIST_URL}?key={api_key}", timeout=20); response.raise_for_status(); data = response.json()
166
- available_models = [m['name'] for m in data.get('models', []) if 'flash' in m['name'] and 'generateContent' in m.get('supportedGenerationMethods', []) and 'exp' not in m['name']]
167
- if not available_models: continue
168
- available_models.sort(reverse=True); print(f"-> उपलब्ध 'flash' मॉडल मिले: {available_models}"); return available_models[0]
169
- except requests.exceptions.RequestException as e: print(f"🚨 API Key ...{api_key[-4:]} के साथ त्रुटि: {e}. अगली की आजमाई जा रही है..."); continue
170
- print("🚨 स्मार्ट मॉडल हंटर में गंभीर त्रुटि: कोई भी Gemini API Key काम नहीं कर रही है।"); return None
171
- def _make_resilient_api_call(self, prompt, timeout=120):
172
- headers = {'Content-Type': 'application/json'}; payload = {'contents': [{'parts': [{'text': prompt}]}]}
173
- for api_key in self.api_keys:
174
- try:
175
- print(f"-> Gemini को अनुरोध भेजा जा रहा है (Key: ...{api_key[-4:]}, Model: {self.model_name.split('/')[-1]})")
176
- response = requests.post(f"{self.api_url}?key={api_key}", headers=headers, json=payload, timeout=timeout); response.raise_for_status(); result = response.json()
177
- if 'candidates' not in result or not result['candidates']: print(f"🚨 चेतावनी: Key ...{api_key[-4:]} से कोई कैंडिडेट नहीं मिला (संभवतः सुरक्षा ब्लॉक)। अगली की आजमाई जा रही है..."); continue
178
- return result
179
- except requests.exceptions.RequestException as e: print(f"🚨 API कॉल में त्रुटि (Key: ...{api_key[-4:]}): {e}. अगली की आजमाई जा रही है...");
180
- raise Exception("Gemini API Error: All available API keys failed. Please check your keys and quotas.")
181
- def extract_keywords(self, script_text):
182
- prompt = f"""You are a search query expert. Analyze the script below and for each scene, create a JSON object. Each object must contain: 1. "scene_description": A brief description of the scene. 2. "primary_query": A highly creative, emotional, and cinematic search query in English. This is the main attempt. 3. "fallback_query": A simple, literal, and direct search query in English. Use this if the primary query fails. RULES: - Your response MUST be ONLY a JSON list of objects. - All queries must be in English. Script: "{script_text}" Example: [ {{"scene_description": "A person looking at a mountain.", "primary_query": "inspirational mountain peak cinematic hope", "fallback_query": "man looking at mountain"}} ] Generate the JSON:"""
183
- result = self._make_resilient_api_call(prompt)
184
- json_str = result['candidates'][0]['content']['parts'][0]['text']
185
- clean_str = json_str[json_str.find('['):json_str.rfind(']') + 1]; scenes = json.loads(clean_str)
186
- try:
187
- log_file_path = os.path.join(OUTPUT_FOLDER, 'gemini_analysis_log.json')
188
- with open(log_file_path, 'w', encoding='utf-8') as f: json.dump(scenes, f, ensure_ascii=False, indent=4)
189
- print(f"-> Gemini का विश्लेषण सफलतापूर्वक '{log_file_path}' में सहेजा गया।")
190
- except Exception as e: print(f"🚨 चेतावनी: Gemini विश्लेषण लॉग करने में विफल: {e}")
191
- print(f"-> Gemini ने सफलतापूर्वक {len(scenes)} प्राथमिक/फ़ॉलबैक दृश्य निकाले।"); return scenes
192
- def create_master_timeline(self, word_timestamps, enriched_scenes_with_paths):
193
- full_script_text = " ".join([word['word'] for word in word_timestamps]); total_duration = word_timestamps[-1]['end'] if word_timestamps else 0
194
- prompt = f"""You are an expert AI video editor. Create a frame-perfect timeline JSON.
195
- Assets:
196
- 1. **Full Script:** "{full_script_text}"
197
- 2. **Total Audio Duration:** {total_duration:.2f} seconds.
198
- 3. **Available Scene Clips:** {json.dumps(enriched_scenes_with_paths, indent=2)}
199
- 4. **Word-Level Timestamps (with Pauses):** {json.dumps(word_timestamps, indent=2)}.
200
-
201
- RULES:
202
- 1. Your response MUST be ONLY a list of JSON objects.
203
- 2. Each object must have "start", "end", "matched_clip", and "start_offset_seconds".
204
- 3. **CRITICAL:** The timeline MUST cover the entire audio duration from 0 to {total_duration:.2f} seconds. There should be NO GAPS.
205
- 4. **CRITICAL:** You MUST use each video from the 'Available Scene Clips' list only once. Do not repeat clips.
206
- 5. **NEW CRITICAL RULE:** In the 'Word-Level Timestamps', you will find special words like '[PAUSE]'. This represents a deliberate silence in the narration. Treat this as a creative opportunity! It is the perfect moment for a beautiful transition between two clips or to let a cinematic shot play out for its full emotional impact. DO NOT repeat the previous clip to fill a pause. Use the pause to enhance the video's pacing.
207
-
208
- Create the final timeline JSON:"""
209
- result = self._make_resilient_api_call(prompt, timeout=180)
210
- json_str = result['candidates'][0]['content']['parts'][0]['text']
211
- clean_str = json_str[json_str.find('['):json_str.rfind(']') + 1]; final_timeline = json.loads(clean_str)
212
- print(f"-> Gemini Master Editor ने सफलतापूर्वक {len(final_timeline)} क्लिप्स की टाइमलाइन और ऑफसेट बना दी है।"); return final_timeline
213
- def generate_script(self, topic, video_length):
214
- word_count_map = {"short": "~75 शब्द", "medium": "~150 शब्द", "long": "~300 शब्द"}; target_word_count = word_count_map.get(video_length, "~150 शब्द")
215
- prompt = f"""आप 'स्पार्कलिंग ज्ञान' के लिए एक विशेषज्ञ हिंदी स्क्रिप्ट राइटर हैं।
216
- विषय: "{topic}".
217
- निर्देश:
218
- 1. इस विषय पर एक आकर्षक, {target_word_count} की स्क्रिप्ट लिखें।
219
- 2. भाषा सरल और बोलचाल वाली हो।
220
- 3. हर 2-3 लाइनों के बाद एक नया विज़ुअल या सीन दिखाया जा सके, इस तरह से लिखें।
221
- 4. **CRITICAL RULE:** आपका आउटपुट सिर्फ और सिर्फ बोले जाने वाले डायलॉग्स (narration) होने चाहिए। किसी भी तरह के विज़ुअल निर्देश, सीन डिस्क्रिप्शन या ब्रैकेट () [] में लिखी कोई भी जानकारी आउटपुट में नहीं होनी चाहिए। सिर्फ वो टेक्स्ट दें जो ऑडियो में बोला जाएगा।
222
-
223
- अब, स्क्रिप्ट लिखें:"""
224
- result = self._make_resilient_api_call(prompt)
225
- generated_script = result['candidates'][0]['content']['parts'][0]['text']
226
- print("-> Gemini ने सफलतापूर्वक स्क्रिप्ट जेनरेट कर दी है।"); return generated_script.strip()
227
-
228
- class VideoAssembler:
229
- TRANSITION_DURATION = 0.5
230
- def __init__(self, timeline, narration_audio, output_path, width, height, mute_audio, temp_dir):
231
- self.timeline = timeline; self.narration_audio = narration_audio; self.output_path = output_path; self.width = width; self.height = height; self.mute_audio = mute_audio
232
- self.temp_dir = temp_dir
233
- def _run_ffmpeg_command(self, command, suppress_errors=False):
234
- process = subprocess.run(command, capture_output=True, text=True)
235
- if not suppress_errors and process.returncode != 0:
236
- error_details = f"Return Code {process.returncode}"
237
- if process.returncode == -9: error_details += " (SIGKILL): Process was killed, likely due to excessive memory usage."
238
- raise Exception(f"FFmpeg Error ({error_details}):\nSTDERR:\n{process.stderr}")
239
- return process
240
- def assemble_video(self, log_callback):
241
- if not self.timeline: return
242
- log_callback("-> Stage 1/3: सभी क्लिप्स को व्यक्तिगत रूप से तैयार किया जा रहा है...", 91)
243
- prepared_clips = []
244
- for i, item in enumerate(self.timeline):
245
- input_clip_path = item['matched_clip']
246
- try:
247
- ffprobe_command = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', input_clip_path]
248
- duration_proc = self._run_ffmpeg_command(ffprobe_command)
249
- actual_clip_duration = float(duration_proc.stdout.strip())
250
- except Exception as e:
251
- log_callback(f"🚨 चेतावनी: क्लिप {os.path.basename(input_clip_path)} की अवधि का पता नहीं लगाया जा सका, इसे छोड़ दिया जाएगा। त्रुटि: {e}", 91)
252
- continue
253
- start_offset = float(item.get('start_offset_seconds', 0.0))
254
- if start_offset >= actual_clip_duration:
255
- log_callback(f" -> 🚨 चेतावनी: AI द्वारा दिया गया स्टार्ट ऑफसेट ({start_offset}s) क्लिप की वास्तविक लंबाई ({actual_clip_duration:.2f}s) से अधिक है। ऑफसेट को 0 पर रीसेट किया जा रहा है।", 91)
256
- start_offset = 0.0
257
- is_last_clip = (i == len(self.timeline) - 1)
258
- overlap = 0 if is_last_clip else self.TRANSITION_DURATION
259
- duration = (float(item['end']) - float(item['start'])) + overlap
260
- if duration <= 0: continue
261
- output_clip_path = os.path.join(self.temp_dir, f"prepared_{i:03d}.mp4")
262
- command = [
263
- 'ffmpeg', '-y', '-ss', str(start_offset), '-i', input_clip_path, '-t', str(duration),
264
- '-vf', f"scale='w={self.width}:h={self.height}:force_original_aspect_ratio=increase',crop={self.width}:{self.height},setsar=1,fps=30",
265
- '-c:v', 'libx264', '-preset', 'ultrafast', '-an', '-threads', '1', output_clip_path
266
- ]
267
- self._run_ffmpeg_command(command)
268
- prepared_clips.append(output_clip_path)
269
- log_callback("-> Stage 2/3: क्लिप्स को ट्रांजीशन के साथ जोड़ा जा रहा है...", 94)
270
- if not prepared_clips: raise Exception("कोई भी क्लिप सफलतापूर्वक तैयार नहीं हो सकी।")
271
- if len(prepared_clips) == 1:
272
- shutil.copy(prepared_clips[0], self.output_path)
273
- transitioned_video_path = self.output_path
274
- else:
275
- current_video = prepared_clips[0]
276
- for i in range(len(prepared_clips) - 1):
277
- next_video = prepared_clips[i+1]
278
- output_path = os.path.join(self.temp_dir, f"transition_{i:03d}.mp4")
279
- total_transitions = len(prepared_clips) - 1
280
- progress = 94 + int((i / total_transitions) * 4) if total_transitions > 0 else 94
281
- log_callback(f" -> ट्रांजीशन बनाया जा रहा है: क्लिप {i+1} और {i+2}", progress)
282
- ffprobe_command = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', current_video]
283
- duration_proc = self._run_ffmpeg_command(ffprobe_command)
284
- transition_offset = float(duration_proc.stdout.strip()) - self.TRANSITION_DURATION
285
- command = [
286
- 'ffmpeg', '-y', '-i', current_video, '-i', next_video,
287
- '-filter_complex', f"[0:v][1:v]xfade=transition=fade:duration={self.TRANSITION_DURATION}:offset={transition_offset},format=yuv420p",
288
- '-c:v', 'libx264', '-preset', 'ultrafast', output_path
289
- ]
290
- self._run_ffmpeg_command(command)
291
- current_video = output_path
292
- transitioned_video_path = current_video
293
- log_callback("-> Stage 3/3: फाइनल वीडियो में ऑडियो जोड़ा जा रहा है...", 98)
294
- audio_input = [] if self.mute_audio else ['-i', self.narration_audio]
295
- audio_map = ['-an'] if self.mute_audio else ['-map', '0:v:0', '-map', '1:a:0']
296
- command = [
297
- 'ffmpeg', '-y', '-i', transitioned_video_path,
298
- ] + audio_input + [
299
- '-c:v', 'copy',
300
- ] + audio_map + [
301
- '-c:a', 'aac', '-shortest', self.output_path
302
- ]
303
- self._run_ffmpeg_command(command)
304
-
305
- # ==============================================================================
306
- # 3. AI इंजन का मुख्य वर्कर
307
- # ==============================================================================
308
- def run_ai_engine_worker(task_id, script_text, script_file_path, orientation, max_clip_length, mute_final_video):
309
- log = lambda message, progress: update_task_log(task_id, message, progress)
310
- temp_dir = os.path.join(UPLOAD_FOLDER, task_id)
311
- try:
312
- log("Step 0: API Keys की पुष्टि...", 2)
313
- gemini_keys, pexels_keys, pixabay_keys, groq_keys = load_api_keys("Gemini_Key"), load_api_keys("Pexels_Key"), load_api_keys("Pixabay_Key"), load_api_keys("Groq_Key")
314
- if not all([gemini_keys, pexels_keys, pixabay_keys, groq_keys]): raise Exception("API Key Error: Missing one or more required keys.")
315
- gemini = GeminiTeam(api_keys=gemini_keys); log("-> सभी जरूरी API कीज मौजूद हैं।", 5)
316
- log("Step 1: स्क्रिप्ट तैयार की जा रही है...", 8)
317
- os.makedirs(temp_dir, exist_ok=True); narration_audio_path = ""
318
- if script_file_path:
319
- narration_audio_path = script_file_path; log("-> ऑडियो फ़ाइल प्राप्त हुई।", 12)
320
- else:
321
- cleaned_script_for_tts = re.sub(r'\[.*?\]|\(.*?\)', '', script_text)
322
- full_script_text_for_tts = cleaned_script_for_tts.strip()
323
- narration_audio_path = os.path.join(temp_dir, "narration.mp3")
324
- gTTS(full_script_text_for_tts, lang='hi').save(narration_audio_path)
325
- log(f"-> TTS ke liye saaf script bheji gayi: '{full_script_text_for_tts}'", 12)
326
- log("-> टेक्स्ट से ऑडियो सफलतापूर्वक बनाया गया।", 12)
327
- log("Step 2: ऑडियो का सटीक विश्लेषण (Groq)...", 15)
328
- groq_api = GroqAPI(api_keys=groq_keys); word_timestamps = groq_api.transcribe_audio(narration_audio_path)
329
- if not word_timestamps: raise Exception("Transcription failed or returned no words.")
330
- log("-> Smart Pause Detector: ऑडियो में लंबी chuppi खोजी जा रही है...", 20)
331
- timestamps_with_pauses = []
332
- pause_threshold = 1.5
333
- pause_count = 0
334
- if word_timestamps:
335
- timestamps_with_pauses.append(word_timestamps[0])
336
- for i in range(len(word_timestamps) - 1):
337
- current_word_end = float(word_timestamps[i]['end'])
338
- next_word_start = float(word_timestamps[i+1]['start'])
339
- gap = next_word_start - current_word_end
340
- if gap > pause_threshold:
341
- pause_event = {'word': '[PAUSE]', 'start': current_word_end, 'end': next_word_start}
342
- timestamps_with_pauses.append(pause_event)
343
- pause_count += 1
344
- timestamps_with_pauses.append(word_timestamps[i+1])
345
- if pause_count > 0:
346
- log(f"-> Pause Detector ne सफलतापूर्वक {pause_count} pauses jode. Ab AI inka creative istemaal karega.", 22)
347
- else:
348
- log("-> Pause Detector ko koi lamba pause nahi mila. Sab theek hai.", 22)
349
- full_script_text = " ".join([word['word'] for word in timestamps_with_pauses]); log(f"-> पूर्ण स्क्रिप्ट (pauses ke saath): '{full_script_text}'", 25)
350
- log("Step 3: Contextual वीडियो खोजे जा रहे हैं (Smart Search)...", 30)
351
- scenes_from_gemini = gemini.extract_keywords(full_script_text)
352
- pexels = PexelsAPI(api_keys=pexels_keys); pixabay = PixabayAPI(api_keys=pixabay_keys)
353
- for i, scene in enumerate(scenes_from_gemini):
354
- scene['downloaded_path'] = None; primary_query = scene.get('primary_query'); fallback_query = scene.get('fallback_query')
355
- log(f"-> Scene {i+1} ('{scene['scene_description'][:20]}...') के लिए वीडियो खोजा जा रहा है...", 30 + i * 5)
356
- filename = f"scene_{i+1}_{secure_filename(primary_query)[:20]}.mp4"; download_path = os.path.join(temp_dir, filename)
357
- log(f" -> प्राथमिक कोशिश (Primary): '{primary_query}'...", 30 + i * 5)
358
- for attempt in range(2):
359
- path = pexels.search_and_download(primary_query, download_path, orientation, search_page=attempt + 1)
360
- if path: scene['downloaded_path'] = path; break
361
- path = pixabay.search_and_download(primary_query, download_path, orientation, max_clip_length, search_index=attempt)
362
- if path: scene['downloaded_path'] = path; break
363
- if not scene.get('downloaded_path'):
364
- log(f" -> प्राथमिक कोशिश विफल। फ़ॉलबैक कोशिश (Fallback): '{fallback_query}'...", 30 + i * 5)
365
- for attempt in range(2):
366
- path = pexels.search_and_download(fallback_query, download_path, orientation, search_page=attempt + 1)
367
- if path: scene['downloaded_path'] = path; break
368
- path = pixabay.search_and_download(fallback_query, download_path, orientation, max_clip_length, search_index=attempt)
369
- if path: scene['downloaded_path'] = path; break
370
- if scene['downloaded_path']: log(f"-> Scene {i+1} के लिए वीडियो सफलतापूर्वक डाउनलोड हुआ: {os.path.basename(scene['downloaded_path'])}", 30 + i * 5)
371
- else: log(f"🚨 चेतावनी: Scene {i+1} के लिए कोई भी वीडियो नहीं मिला (Primary और Fallback दोनों विफल)।", 30 + i * 5)
372
- successful_scenes = [scene for scene in scenes_from_gemini if scene.get('downloaded_path')]
373
- if not successful_scenes: raise Exception("Could not download any videos for the given script.")
374
- log(f"-> {len(successful_scenes)} वीडियो क्लिप्स सफलतापूर्वक डाउनलोड हुए।", 60)
375
- log("Step 4: मास्टर टाइमलाइन और इंटेलिजेंट क्लिप ट्रिमिंग की जा रही है...", 75)
376
- final_timeline = gemini.create_master_timeline(timestamps_with_pauses, successful_scenes); log(f"-> मास्टर टाइमलाइन AI से प्राप्त हुई।", 85)
377
- log("-> मास्टर टाइमलाइन को सत्यापित और डी-डुप्लिकेट किया जा रहा है...", 86)
378
- validated_timeline = []
379
- used_clips = set()
380
- for clip in final_timeline:
381
- path_value = clip.get('matched_clip')
382
- actual_path = None
383
- if isinstance(path_value, str):
384
- actual_path = path_value
385
- elif isinstance(path_value, dict):
386
- actual_path = path_value.get('downloaded_path')
387
- if actual_path and isinstance(actual_path, str) and os.path.exists(actual_path) and actual_path not in used_clips:
388
- clip['matched_clip'] = actual_path
389
- validated_timeline.append(clip)
390
- used_clips.add(actual_path)
391
- else:
392
- log(f"🚨 चेतावनी: टाइमलाइन में एक अमान्य या डुप्लिकेट क्लिप को अनदेखा किया जा रहा है: {os.path.basename(actual_path or 'Invalid Path')}", 87)
393
- if not validated_timeline:
394
- raise Exception("Timeline verification failed. No valid, unique clips found on disk.")
395
- log("-> टाइमलाइन में गैप्स की जाँच और उन्हें ठीक किया जा रहा है...", 88)
396
- final_gapless_timeline = []; total_duration = word_timestamps[-1]['end'] if word_timestamps else 0
397
- validated_timeline.sort(key=lambda x: float(x['start']))
398
- for i, clip in enumerate(validated_timeline):
399
- if i < len(validated_timeline) - 1:
400
- current_clip_end = float(clip['end']); next_clip_start = float(validated_timeline[i+1]['start'])
401
- if current_clip_end < next_clip_start: log(f" -> Gap मिला: क्लिप {i+1} को {current_clip_end} से {next_clip_start} तक बढ़ाया जा रहा है।", 88); clip['end'] = next_clip_start
402
- elif i == len(validated_timeline) - 1:
403
- last_clip_end = float(clip['end'])
404
- if last_clip_end < total_duration: log(f" -> अंतिम क्लिप को ऑडियो के अंत तक बढ़ाया जा रहा है: {last_clip_end} से {total_duration}", 88); clip['end'] = total_duration
405
- final_gapless_timeline.append(clip)
406
- log(f"-> गैप-फिलिंग सफल। {len(final_gapless_timeline)} क्लिप्स की एक सहज टाइमलाइन तैयार है।", 89)
407
- log("Step 5: फाइनल वीडियो को रेंडर किया जा रहा है (Cinematic Transitions)...", 90)
408
- width, height = (1080, 1920) if orientation == 'vertical' else (1920, 1080)
409
- output_filename = f"{task_id}_final_video.mp4"; output_path = os.path.join(OUTPUT_FOLDER, output_filename)
410
- assembler = VideoAssembler(final_gapless_timeline, narration_audio_path, output_path, width, height, mute_final_video, temp_dir)
411
- assembler.assemble_video(log)
412
- log("-> अंतिम विस्तृत रिपोर्ट बनाई जा रही है...", 99)
413
- try:
414
- report_data = { "full_transcribed_script": full_script_text, "groq_word_timestamps": word_timestamps, "timestamps_with_pauses_added": timestamps_with_pauses, "gemini_scene_analysis_and_downloads": successful_scenes, "gemini_raw_timeline": final_timeline, "processed_gapless_timeline": final_gapless_timeline, }
415
- report_file_path = os.path.join(OUTPUT_FOLDER, f'{task_id}_report.json')
416
- with open(report_file_path, 'w', encoding='utf-8') as f: json.dump(report_data, f, ensure_ascii=False, indent=4)
417
- log(f"-> विस्तृत रिपोर्ट सफलतापूर्वक '{report_file_path}' में सहेजी गई।", 99)
418
- except Exception as e: log(f"🚨 चेतावनी: विस्तृत रिपोर्ट सहेजने में विफल: {e}", 99)
419
- update_task_final_status(task_id, 'complete', output_filename=output_filename)
420
- except Exception as e:
421
- import traceback; traceback.print_exc()
422
- update_task_final_status(task_id, 'error', error_message=str(e))
423
- finally:
424
- if os.path.exists(temp_dir):
425
- try:
426
- shutil.rmtree(temp_dir)
427
- log(f"-> Temporary files aur folder '{temp_dir}' ko saaf kar diya gaya hai.", 100)
428
- except Exception as e: print(f"Cleanup Error: {e}")
429
-
430
- # ==============================================================================
431
- # 4. AI Script Generation Function
432
- # ==============================================================================
433
- def generate_script_with_ai(topic, video_length):
434
- print(f"-> AI स्क्रिप्ट जेनरेटर शुरू हुआ: Topic='{topic}', Length='{video_length}'")
435
- try:
436
- gemini_keys = load_api_keys("Gemini_Key")
437
- if not gemini_keys: raise Exception("Gemini API key not found for script generation.")
438
- gemini_agent = GeminiTeam(api_keys=gemini_keys)
439
- script = gemini_agent.generate_script(topic, video_length)
440
- return script
441
- except Exception as e:
442
- raise e