tester343 commited on
Commit
c6e766b
·
verified ·
1 Parent(s): a0157a2

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +131 -134
app_enhanced.py CHANGED
@@ -1,81 +1,72 @@
1
- import spaces # <--- MUST BE LINE 1
2
- import torch
3
  import os
4
  import time
5
  import threading
6
  import json
7
  import traceback
8
- import logging
9
  import string
10
  import random
11
  import shutil
12
  import cv2
13
  import numpy as np
14
- import srt
15
- from flask import Flask, jsonify, request, send_from_directory, send_file
16
 
17
  # ======================================================
18
- # 🧠 ZERO GPU FUNCTIONS (Must be top-level)
19
  # ======================================================
20
 
21
  @spaces.GPU(duration=120)
22
  def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages, panels_per_page_req):
23
- """ZeroGPU task to extract frames and process video"""
24
  cap = cv2.VideoCapture(video_path)
25
- if not cap.isOpened(): raise Exception("Cannot open video")
26
 
27
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
28
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
29
  duration = total_frames / fps
30
- cap.release()
31
-
32
- # Create dummy subtitle logic for this standalone version
33
  target_pages = int(target_pages)
34
  panels_per_page = int(panels_per_page_req)
35
- total_panels_needed = target_pages * panels_per_page
36
-
37
- # Uniformly pick timestamps
38
- times = np.linspace(1, max(1, duration-1), total_panels_needed)
39
 
 
 
40
  frame_metadata = {}
41
- cap = cv2.VideoCapture(video_path)
42
- frame_files_ordered = []
43
 
44
- for i, t in enumerate(times):
45
- cap.set(cv2.CAP_PROP_POS_MSEC, t * 1000)
46
  ret, frame = cap.read()
47
  if ret:
48
  fname = f"frame_{i:04d}.png"
49
- p = os.path.join(frames_dir, fname)
50
- cv2.imwrite(p, frame)
51
- frame_metadata[fname] = {'dialogue': f"Dialogue {i+1}", 'time': t}
52
- frame_files_ordered.append(fname)
53
  cap.release()
54
 
55
  with open(metadata_path, 'w') as f:
56
  json.dump(frame_metadata, f)
57
-
58
  pages = []
59
  for i in range(target_pages):
60
- start_idx = i * panels_per_page
61
- end_idx = start_idx + panels_per_page
62
- p_frames = frame_files_ordered[start_idx:end_idx]
63
- if p_frames:
64
- pg_panels = [{'image': f} for f in p_frames]
65
- # Create a simple bubble for each panel
66
- pg_bubbles = [{'dialog': frame_metadata[f]['dialogue'], 'type': 'speech'} for f in p_frames]
67
- pages.append({'panels': pg_panels, 'bubbles': pg_bubbles})
68
  return pages
69
 
70
  @spaces.GPU
71
  def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
72
- """ZeroGPU task to shift frame time slightly"""
73
  with open(metadata_path, 'r') as f: meta = json.load(f)
74
  t = meta[fname]['time']
75
  cap = cv2.VideoCapture(video_path)
76
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
77
- offset = (1.0/fps) * (1 if direction == 'forward' else -1)
78
- new_t = max(0, t + offset)
79
  cap.set(cv2.CAP_PROP_POS_MSEC, new_t * 1000)
80
  ret, frame = cap.read()
81
  cap.release()
@@ -83,161 +74,166 @@ def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
83
  cv2.imwrite(os.path.join(frames_dir, fname), frame)
84
  meta[fname]['time'] = new_t
85
  with open(metadata_path, 'w') as f: json.dump(meta, f)
86
- return {"success": True}
87
- return {"success": False}
88
 
89
  # ======================================================
90
- # 💾 APP CONFIG & STORAGE
91
  # ======================================================
92
- BASE_STORAGE_PATH = '/data' if os.path.exists('/data') else '.'
93
- BASE_USER_DIR = os.path.join(BASE_STORAGE_PATH, "userdata")
94
  os.makedirs(BASE_USER_DIR, exist_ok=True)
95
 
96
  app = Flask(__name__)
97
 
98
- class EnhancedComicGenerator:
99
  def __init__(self, sid):
100
  self.sid = sid
101
- self.user_dir = os.path.join(BASE_USER_DIR, sid)
102
- self.video_path = os.path.join(self.user_dir, 'uploaded.mp4')
103
- self.frames_dir = os.path.join(self.user_dir, 'frames')
104
- self.output_dir = os.path.join(self.user_dir, 'output')
105
- os.makedirs(self.frames_dir, exist_ok=True)
106
- os.makedirs(self.output_dir, exist_ok=True)
107
- self.metadata_path = os.path.join(self.frames_dir, 'frame_metadata.json')
 
 
 
108
 
109
- def run(self, target_pages, panels_per_page):
110
  try:
111
- self.write_status("Running on GPU...", 20)
112
- data = generate_comic_gpu(self.video_path, self.user_dir, self.frames_dir, self.metadata_path, target_pages, panels_per_page)
113
- with open(os.path.join(self.output_dir, 'pages.json'), 'w') as f:
114
  json.dump(data, f)
115
- self.write_status("Complete!", 100)
116
  except Exception as e:
117
- self.write_status(f"Error: {str(e)}", -1)
118
-
119
- def write_status(self, msg, prog):
120
- with open(os.path.join(self.output_dir, 'status.json'), 'w') as f:
121
- json.dump({'message': msg, 'progress': prog}, f)
122
 
123
  # ======================================================
124
- # 🌐 UI & TEMPLATE (With Vertical Tilt)
125
  # ======================================================
126
  INDEX_HTML = '''
127
  <!DOCTYPE html><html><head><title>Vertical Tilt Comic</title>
128
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
129
  <style>
130
- body { background: #f0f0f0; font-family: sans-serif; margin: 0; padding: 20px; }
131
  .comic-page {
132
- background: white; width: 800px; height: 1000px; margin: 20px auto;
133
- position: relative; overflow: hidden; border: 5px solid black;
134
  }
135
- .comic-grid { width: 100%; height: 100%; position: relative; background: #000; }
136
  .panel { position: absolute; top:0; left:0; width:100%; height:100%; overflow:hidden; }
137
 
138
- /* VERTICAL TILT CLIPPING */
139
  .layout-slant .panel:nth-child(1) {
140
- z-index: 2; clip-path: polygon(0 0, var(--split-t, 45%) 0, var(--split-b, 55%) 100%, 0 100%);
 
141
  }
142
  .layout-slant .panel:nth-child(2) {
143
- z-index: 1; clip-path: polygon(var(--split-t, 45%) 0, 100% 0, 100% 100%, var(--split-b, 55%) 100%);
 
144
  }
145
 
146
  .panel img { width: 100%; height: 100%; object-fit: cover; }
147
 
148
- /* DRAG HANDLES */
149
- .handle {
150
- position: absolute; width: 30px; height: 30px; background: red;
151
  border-radius: 50%; z-index: 100; cursor: ew-resize; border: 3px solid white;
152
  }
153
- .handle.top { top: -15px; left: var(--split-t); transform: translateX(-50%); }
154
- .handle.bottom { bottom: -15px; left: var(--split-b); transform: translateX(-50%); }
155
 
156
- .controls { position: fixed; right: 20px; top: 20px; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.2); }
157
- .speech-bubble { position: absolute; padding: 10px; background: white; border: 2px solid black; border-radius: 50%; cursor: move; z-index: 50; transform: translate(-50%, -50%); }
158
  </style></head><body>
159
 
160
- <div id="setup">
161
- <input type="file" id="vid">
162
- <button onclick="start()">Generate Comic</button>
163
- <p id="status"></p>
 
164
  </div>
165
 
166
- <div id="editor" style="display:none">
167
- <div id="pages"></div>
168
  <div class="controls">
169
- <button onclick="download()">Download PNG</button>
170
- <p>Drag the red circles to tilt the panels vertically.</p>
 
 
171
  </div>
172
  </div>
173
 
174
  <script>
175
- let sid = "S" + Date.now();
176
- let isDragging = false;
177
- let currentHandle = null;
178
 
179
- async function start() {
180
- const file = document.getElementById('vid').files[0];
181
- if(!file) return alert("Select video");
182
- const fd = new FormData(); fd.append('file', file); fd.append('target_pages', 2); fd.append('panels_per_page', 2);
183
- await fetch(`/uploader?sid=${sid}`, {method:'POST', body:fd});
184
- document.getElementById('status').innerText = "Processing on ZeroGPU...";
185
 
186
- const check = setInterval(async () => {
 
187
  const r = await fetch(`/status?sid=${sid}`);
188
  const d = await r.json();
189
- document.getElementById('status').innerText = d.message;
190
- if(d.progress >= 100) { clearInterval(check); loadEditor(); }
191
  }, 2000);
192
  }
193
 
194
- async function loadEditor() {
195
  const r = await fetch(`/output/pages.json?sid=${sid}`);
196
  const data = await r.json();
197
- const container = document.getElementById('pages');
 
198
  data.forEach(p => {
199
- const page = document.createElement('div');
200
- page.className = 'comic-page';
201
  const grid = document.createElement('div');
202
- grid.className = 'comic-grid layout-slant';
203
- grid.style.setProperty('--split-t', '45%');
204
- grid.style.setProperty('--split-b', '55%');
205
 
206
- const hT = document.createElement('div'); hT.className = 'handle top';
207
- hT.onmousedown = () => { isDragging = true; currentHandle = {grid, key: '--split-t'}; };
208
- const hB = document.createElement('div'); hB.className = 'handle bottom';
209
- hB.onmousedown = () => { isDragging = true; currentHandle = {grid, key: '--split-b'}; };
 
210
 
211
  p.panels.forEach(pan => {
212
- const pDiv = document.createElement('div'); pDiv.className = 'panel';
213
- pDiv.innerHTML = `<img src="/frames/${pan.image}?sid=${sid}">`;
214
- grid.appendChild(pDiv);
 
215
  });
216
-
217
- grid.appendChild(hT); grid.appendChild(hB);
218
- page.appendChild(grid);
219
- container.appendChild(page);
220
  });
221
- document.getElementById('setup').style.display = 'none';
222
- document.getElementById('editor').style.display = 'block';
 
223
  }
224
 
225
  window.onmousemove = (e) => {
226
- if(!isDragging) return;
227
- const rect = currentHandle.grid.getBoundingClientRect();
228
  let x = ((e.clientX - rect.left) / rect.width) * 100;
229
- x = Math.max(10, Math.min(90, x));
230
- currentHandle.grid.style.setProperty(currentHandle.key, x + '%');
231
  };
232
- window.onmouseup = () => isDragging = false;
233
 
234
- async function download() {
235
- const pages = document.querySelectorAll('.comic-page');
236
- for(let i=0; i<pages.length; i++) {
237
- const dataUrl = await htmlToImage.toPng(pages[i]);
238
- const link = document.createElement('a');
239
- link.download = `page-${i+1}.png`; link.href = dataUrl; link.click();
240
- }
241
  }
242
  </script></body></html>
243
  '''
@@ -253,22 +249,24 @@ def index(): return INDEX_HTML
253
  def uploader():
254
  sid = request.args.get('sid')
255
  f = request.files['file']
256
- gen = EnhancedComicGenerator(sid)
257
- f.save(gen.video_path)
258
- threading.Thread(target=gen.run, args=(2, 2)).start()
259
- return jsonify({'status': 'ok'})
260
 
261
  @app.route('/status')
262
  def get_status():
263
  sid = request.args.get('sid')
264
- path = os.path.join(BASE_USER_DIR, sid, 'output', 'status.json')
265
- if os.path.exists(path): return send_file(path)
266
- return jsonify({'message': 'Initializing...', 'progress': 0})
 
 
267
 
268
  @app.route('/output/<path:filename>')
269
  def get_output(filename):
270
  sid = request.args.get('sid')
271
- return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'output'), filename)
272
 
273
  @app.route('/frames/<path:filename>')
274
  def get_frame(filename):
@@ -276,5 +274,4 @@ def get_frame(filename):
276
  return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'frames'), filename)
277
 
278
  if __name__ == '__main__':
279
- # On Hugging Face, the port must be 7860
280
  app.run(host='0.0.0.0', port=7860)
 
1
+ import spaces # <--- MUST BE LINE 1
2
+ from flask import Flask, jsonify, request, send_from_directory, send_file
3
  import os
4
  import time
5
  import threading
6
  import json
7
  import traceback
 
8
  import string
9
  import random
10
  import shutil
11
  import cv2
12
  import numpy as np
 
 
13
 
14
  # ======================================================
15
+ # 🧠 ZEROGPU FUNCTIONS (Defined early for detection)
16
  # ======================================================
17
 
18
  @spaces.GPU(duration=120)
19
  def generate_comic_gpu(video_path, user_dir, frames_dir, metadata_path, target_pages, panels_per_page_req):
20
+ """ZeroGPU function to extract frames and metadata"""
21
  cap = cv2.VideoCapture(video_path)
22
+ if not cap.isOpened(): raise Exception("Video open failed")
23
 
24
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
25
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
26
  duration = total_frames / fps
27
+
 
 
28
  target_pages = int(target_pages)
29
  panels_per_page = int(panels_per_page_req)
30
+ total_panels = target_pages * panels_per_page
 
 
 
31
 
32
+ # Simple logic to grab frames at intervals
33
+ timestamps = np.linspace(0.5, max(0.5, duration - 0.5), total_panels)
34
  frame_metadata = {}
35
+ frame_files = []
 
36
 
37
+ for i, ts in enumerate(timestamps):
38
+ cap.set(cv2.CAP_PROP_POS_MSEC, ts * 1000)
39
  ret, frame = cap.read()
40
  if ret:
41
  fname = f"frame_{i:04d}.png"
42
+ cv2.imwrite(os.path.join(frames_dir, fname), frame)
43
+ frame_metadata[fname] = {'time': ts, 'dialogue': f"Dialogue {i+1}"}
44
+ frame_files.append(fname)
 
45
  cap.release()
46
 
47
  with open(metadata_path, 'w') as f:
48
  json.dump(frame_metadata, f)
49
+
50
  pages = []
51
  for i in range(target_pages):
52
+ start = i * panels_per_page
53
+ end = start + panels_per_page
54
+ subset = frame_files[start:end]
55
+ if subset:
56
+ pages.append({
57
+ 'panels': [{'image': f} for f in subset],
58
+ 'bubbles': [{'dialog': frame_metadata[f]['dialogue'], 'type': 'speech'} for f in subset]
59
+ })
60
  return pages
61
 
62
  @spaces.GPU
63
  def regen_frame_gpu(video_path, frames_dir, metadata_path, fname, direction):
64
+ """Adjusts frame time forward or backward"""
65
  with open(metadata_path, 'r') as f: meta = json.load(f)
66
  t = meta[fname]['time']
67
  cap = cv2.VideoCapture(video_path)
68
  fps = cap.get(cv2.CAP_PROP_FPS) or 25
69
+ new_t = max(0, t + (1.0/fps * (1 if direction == 'forward' else -1)))
 
70
  cap.set(cv2.CAP_PROP_POS_MSEC, new_t * 1000)
71
  ret, frame = cap.read()
72
  cap.release()
 
74
  cv2.imwrite(os.path.join(frames_dir, fname), frame)
75
  meta[fname]['time'] = new_t
76
  with open(metadata_path, 'w') as f: json.dump(meta, f)
77
+ return True
78
+ return False
79
 
80
  # ======================================================
81
+ # ⚙️ CONFIG & STORAGE
82
  # ======================================================
83
+ STORAGE = '/data' if os.path.exists('/data') else '.'
84
+ BASE_USER_DIR = os.path.join(STORAGE, "userdata")
85
  os.makedirs(BASE_USER_DIR, exist_ok=True)
86
 
87
  app = Flask(__name__)
88
 
89
+ class ComicGenerator:
90
  def __init__(self, sid):
91
  self.sid = sid
92
+ self.dir = os.path.join(BASE_USER_DIR, sid)
93
+ self.v_path = os.path.join(self.dir, 'video.mp4')
94
+ self.f_dir = os.path.join(self.dir, 'frames')
95
+ self.o_dir = os.path.join(self.dir, 'out')
96
+ for d in [self.f_dir, self.o_dir]: os.makedirs(d, exist_ok=True)
97
+ self.meta_p = os.path.join(self.f_dir, 'meta.json')
98
+
99
+ def update_status(self, msg, prog):
100
+ with open(os.path.join(self.o_dir, 'status.json'), 'w') as f:
101
+ json.dump({'message': msg, 'progress': prog}, f)
102
 
103
+ def process(self, pgs, ppp):
104
  try:
105
+ self.update_status("GPU Processing...", 20)
106
+ data = generate_comic_gpu(self.v_path, self.dir, self.f_dir, self.meta_p, pgs, ppp)
107
+ with open(os.path.join(self.o_dir, 'pages.json'), 'w') as f:
108
  json.dump(data, f)
109
+ self.update_status("Done!", 100)
110
  except Exception as e:
111
+ self.update_status(f"Error: {str(e)}", -1)
 
 
 
 
112
 
113
  # ======================================================
114
+ # 🌐 UI TEMPLATE (VERTICAL TILT)
115
  # ======================================================
116
  INDEX_HTML = '''
117
  <!DOCTYPE html><html><head><title>Vertical Tilt Comic</title>
118
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.11.11/html-to-image.min.js"></script>
119
  <style>
120
+ body { font-family: sans-serif; background: #eef2f3; padding: 20px; }
121
  .comic-page {
122
+ width: 800px; height: 1000px; background: white; margin: 20px auto;
123
+ position: relative; border: 6px solid black; overflow: hidden;
124
  }
125
+ .grid { width: 100%; height: 100%; position: relative; background: #000; }
126
  .panel { position: absolute; top:0; left:0; width:100%; height:100%; overflow:hidden; }
127
 
128
+ /* VERTICAL TILT CLIPPING (Left/Right Split) */
129
  .layout-slant .panel:nth-child(1) {
130
+ z-index: 2;
131
+ clip-path: polygon(0 0, var(--v-split-top, 45%) 0, var(--v-split-bottom, 55%) 100%, 0 100%);
132
  }
133
  .layout-slant .panel:nth-child(2) {
134
+ z-index: 1;
135
+ clip-path: polygon(var(--v-split-top, 45%) 0, 100% 0, 100% 100%, var(--v-split-bottom, 55%) 100%);
136
  }
137
 
138
  .panel img { width: 100%; height: 100%; object-fit: cover; }
139
 
140
+ /* TILT HANDLES (Top and Bottom) */
141
+ .v-handle {
142
+ position: absolute; width: 30px; height: 30px; background: #ff4757;
143
  border-radius: 50%; z-index: 100; cursor: ew-resize; border: 3px solid white;
144
  }
145
+ .v-handle.top { top: -15px; left: var(--v-split-top, 45%); transform: translateX(-50%); }
146
+ .v-handle.bottom { bottom: -15px; left: var(--v-split-bottom, 55%); transform: translateX(-50%); }
147
 
148
+ .bubble { position: absolute; padding: 10px; background: white; border: 2px solid black; border-radius: 20px; cursor: move; z-index: 50; transform: translate(-50%, -50%); }
149
+ .controls { position: fixed; right: 20px; top: 20px; width: 200px; background: white; padding: 15px; border-radius: 10px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
150
  </style></head><body>
151
 
152
+ <div id="upload-view" style="text-align:center; padding-top: 100px;">
153
+ <h1>🎬 Vertical Tilt Comic Generator</h1>
154
+ <input type="file" id="vid-file"><br><br>
155
+ <button onclick="startProcessing()" style="padding:10px 20px; cursor:pointer;">Generate</button>
156
+ <p id="status-msg"></p>
157
  </div>
158
 
159
+ <div id="editor-view" style="display:none">
160
+ <div id="page-container"></div>
161
  <div class="controls">
162
+ <h3>Editor</h3>
163
+ <p style="font-size: 12px;">Drag the red circles at the <b>top</b> and <b>bottom</b> to tilt the vertical divider.</p>
164
+ <button onclick="exportPNG()">Export Page 1</button>
165
+ <button onclick="location.reload()">Start Over</button>
166
  </div>
167
  </div>
168
 
169
  <script>
170
+ let sid = "SID_" + Math.random().toString(36).substr(2, 9);
171
+ let draggingHandle = null;
 
172
 
173
+ async function startProcessing() {
174
+ const file = document.getElementById('vid-file').files[0];
175
+ if(!file) return alert("Select a video");
176
+ const fd = new FormData(); fd.append('file', file);
177
+ document.getElementById('status-msg').innerText = "Uploading & Waiting for ZeroGPU...";
 
178
 
179
+ await fetch(`/uploader?sid=${sid}`, {method:'POST', body:fd});
180
+ const timer = setInterval(async () => {
181
  const r = await fetch(`/status?sid=${sid}`);
182
  const d = await r.json();
183
+ document.getElementById('status-msg').innerText = d.message;
184
+ if(d.progress >= 100) { clearInterval(timer); showEditor(); }
185
  }, 2000);
186
  }
187
 
188
+ async function showEditor() {
189
  const r = await fetch(`/output/pages.json?sid=${sid}`);
190
  const data = await r.json();
191
+ const container = document.getElementById('page-container');
192
+
193
  data.forEach(p => {
194
+ const pg = document.createElement('div');
195
+ pg.className = 'comic-page';
196
  const grid = document.createElement('div');
197
+ grid.className = 'grid layout-slant';
198
+ grid.style.setProperty('--v-split-top', '45%');
199
+ grid.style.setProperty('--v-split-bottom', '55%');
200
 
201
+ const hTop = document.createElement('div'); hTop.className = 'v-handle top';
202
+ const hBot = document.createElement('div'); hBot.className = 'v-handle bottom';
203
+
204
+ hTop.onmousedown = () => draggingHandle = { grid, key: '--v-split-top' };
205
+ hBot.onmousedown = () => draggingHandle = { grid, key: '--v-split-bottom' };
206
 
207
  p.panels.forEach(pan => {
208
+ const pnl = document.createElement('div');
209
+ pnl.className = 'panel';
210
+ pnl.innerHTML = `<img src="/frames/${pan.image}?sid=${sid}">`;
211
+ grid.appendChild(pnl);
212
  });
213
+
214
+ grid.appendChild(hTop); grid.appendChild(hBot);
215
+ pg.appendChild(grid);
216
+ container.appendChild(pg);
217
  });
218
+
219
+ document.getElementById('upload-view').style.display = 'none';
220
+ document.getElementById('editor-view').style.display = 'block';
221
  }
222
 
223
  window.onmousemove = (e) => {
224
+ if(!draggingHandle) return;
225
+ const rect = draggingHandle.grid.getBoundingClientRect();
226
  let x = ((e.clientX - rect.left) / rect.width) * 100;
227
+ x = Math.max(5, Math.min(95, x));
228
+ draggingHandle.grid.style.setProperty(draggingHandle.key, x + '%');
229
  };
230
+ window.onmouseup = () => draggingHandle = null;
231
 
232
+ async function exportPNG() {
233
+ const node = document.querySelector('.comic-page');
234
+ const dataUrl = await htmlToImage.toPng(node);
235
+ const link = document.createElement('a');
236
+ link.download = 'comic-page.png'; link.href = dataUrl; link.click();
 
 
237
  }
238
  </script></body></html>
239
  '''
 
249
  def uploader():
250
  sid = request.args.get('sid')
251
  f = request.files['file']
252
+ gen = ComicGenerator(sid)
253
+ f.save(gen.v_path)
254
+ threading.Thread(target=gen.process, args=(2, 2)).start()
255
+ return jsonify({'ok': True})
256
 
257
  @app.route('/status')
258
  def get_status():
259
  sid = request.args.get('sid')
260
+ try:
261
+ with open(os.path.join(BASE_USER_DIR, sid, 'out', 'status.json'), 'r') as f:
262
+ return f.read()
263
+ except:
264
+ return jsonify({'message': 'Initializing...', 'progress': 0})
265
 
266
  @app.route('/output/<path:filename>')
267
  def get_output(filename):
268
  sid = request.args.get('sid')
269
+ return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'out'), filename)
270
 
271
  @app.route('/frames/<path:filename>')
272
  def get_frame(filename):
 
274
  return send_from_directory(os.path.join(BASE_USER_DIR, sid, 'frames'), filename)
275
 
276
  if __name__ == '__main__':
 
277
  app.run(host='0.0.0.0', port=7860)