| import json |
| import os |
| import re |
| from flask import Flask, render_template_string, send_from_directory |
|
|
| |
|
|
| |
| FOLDERS = { |
| 'baseline': 'baseline', |
| 'teacache': 'teacache', |
| 'magcache': 'magcache', |
| 'ours': 'ours' |
| } |
|
|
| PROMPT_FILE = 'prompts.json' |
|
|
| |
|
|
| app = Flask(__name__) |
| BASE_DIR = os.path.abspath(os.path.dirname(__file__)) |
|
|
| def get_prompts(): |
| path = os.path.join(BASE_DIR, PROMPT_FILE) |
| if not os.path.exists(path): |
| return [] |
| |
| with open(path, 'r', encoding='utf-8') as f: |
| try: |
| content = json.load(f) |
| if isinstance(content, list): return content |
| except: |
| f.seek(0) |
| lines = [line.strip() for line in f if line.strip()] |
| return lines |
|
|
| def find_file_by_id(folder, video_id): |
| dir_path = os.path.join(BASE_DIR, folder) |
| if not os.path.exists(dir_path): return None |
| |
| for fname in os.listdir(dir_path): |
| if fname.startswith(video_id) and fname.lower().endswith('.mp4'): |
| return fname |
| return None |
|
|
| def build_tasks(): |
| tasks = [] |
| prompts = get_prompts() |
| |
| baseline_path = os.path.join(BASE_DIR, FOLDERS['baseline']) |
| if not os.path.exists(baseline_path): |
| print("Baseline folder missing!") |
| return [] |
|
|
| baseline_files = sorted([f for f in os.listdir(baseline_path) if f.lower().endswith('.mp4')]) |
| id_regex = re.compile(r'^(\d{4})') |
|
|
| for i, b_file in enumerate(baseline_files): |
| match = id_regex.match(b_file) |
| if not match: continue |
| |
| vid_id = match.group(1) |
| prompt_text = prompts[i] if i < len(prompts) else "(No prompt)" |
|
|
| tea_file = find_file_by_id(FOLDERS['teacache'], vid_id) |
| mag_file = find_file_by_id(FOLDERS['magcache'], vid_id) |
| our_file = find_file_by_id(FOLDERS['ours'], vid_id) |
|
|
| tasks.append({ |
| "index": i + 1, |
| "id": vid_id, |
| "prompt": prompt_text, |
| "baseline": f"videos/{FOLDERS['baseline']}/{b_file}", |
| "teacache": f"videos/{FOLDERS['teacache']}/{tea_file}" if tea_file else "", |
| "magcache": f"videos/{FOLDERS['magcache']}/{mag_file}" if mag_file else "", |
| "ours": f"videos/{FOLDERS['ours']}/{our_file}" if our_file else "" |
| }) |
|
|
| print(f"Loaded {len(tasks)} videos.") |
| return tasks |
|
|
| TASKS = build_tasks() |
|
|
| |
|
|
| HTML = """ |
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>2x2 Visualizer</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <style> |
| body { background-color: #0f172a; color: #e2e8f0; font-family: sans-serif; } |
| |
| /* Darker theme to make videos pop */ |
| .video-box { |
| background: #1e293b; |
| padding: 0.5rem; |
| border-radius: 0.5rem; |
| height: 100%; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| video { |
| width: 100%; |
| height: auto; |
| border-radius: 0.25rem; |
| background: #000; |
| flex-grow: 1; |
| } |
| |
| .label { |
| text-align: center; |
| font-weight: bold; |
| font-size: 1rem; |
| margin-bottom: 0.5rem; |
| color: #94a3b8; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| |
| /* Highlight 'Ours' */ |
| .highlight { border: 2px solid #3b82f6; background: #1e3a8a; } |
| .highlight .label { color: #60a5fa; } |
| |
| .prompt-box { |
| background: #1e293b; |
| border-bottom: 1px solid #334155; |
| padding: 1rem; |
| position: sticky; |
| top: 0; |
| z-index: 50; |
| } |
| </style> |
| </head> |
| <body class="h-screen flex flex-col overflow-hidden"> |
| |
| <div class="prompt-box flex flex-col md:flex-row gap-4 items-center flex-none shadow-lg"> |
| <div class="flex-none w-full md:w-48 space-y-2"> |
| <div class="flex justify-between items-center text-xs text-gray-400 font-mono"> |
| <span>ID: <span id="vid-id" class="text-white">--</span></span> |
| <span><span id="idx" class="text-white">0</span> / <span id="total">0</span></span> |
| </div> |
| <div class="flex gap-2"> |
| <button onclick="move(-1)" class="flex-1 bg-gray-700 hover:bg-gray-600 text-white py-2 rounded font-bold text-sm transition">Prev</button> |
| <button onclick="move(1)" class="flex-1 bg-blue-600 hover:bg-blue-500 text-white py-2 rounded font-bold text-sm transition">Next</button> |
| </div> |
| </div> |
| |
| <div class="flex-grow overflow-y-auto max-h-20 w-full"> |
| <p id="prompt-text" class="text-sm md:text-lg text-gray-200 leading-snug font-light">Loading...</p> |
| </div> |
| </div> |
| |
| <div class="flex-grow overflow-y-auto p-4"> |
| <div class="w-full h-full max-w-[1920px] mx-auto"> |
| |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4 h-full"> |
| |
| <div class="video-box"> |
| <div class="label">Baseline</div> |
| <video id="v-baseline" controls loop muted playsinline></video> |
| </div> |
| |
| <div class="video-box"> |
| <div class="label">TeaCache</div> |
| <video id="v-teacache" controls loop muted playsinline></video> |
| </div> |
| |
| <div class="video-box"> |
| <div class="label">MagCache</div> |
| <video id="v-magcache" controls loop muted playsinline></video> |
| </div> |
| |
| <div class="video-box highlight"> |
| <div class="label">Ours</div> |
| <video id="v-ours" controls loop muted playsinline></video> |
| </div> |
| |
| </div> |
| </div> |
| </div> |
| |
| <script> |
| const tasks = {{ tasks_json | safe }}; |
| let current = 0; |
| |
| const els = { |
| prompt: document.getElementById('prompt-text'), |
| id: document.getElementById('vid-id'), |
| idx: document.getElementById('idx'), |
| total: document.getElementById('total'), |
| v1: document.getElementById('v-baseline'), |
| v2: document.getElementById('v-teacache'), |
| v3: document.getElementById('v-magcache'), |
| v4: document.getElementById('v-ours') |
| }; |
| |
| els.total.innerText = tasks.length; |
| |
| function loadTask(index) { |
| if (tasks.length === 0) return; |
| |
| if (index < 0) index = tasks.length - 1; |
| if (index >= tasks.length) index = 0; |
| current = index; |
| |
| const t = tasks[current]; |
| |
| els.prompt.innerText = t.prompt; |
| els.id.innerText = t.id; |
| els.idx.innerText = t.index; |
| |
| els.v1.src = t.baseline; |
| els.v2.src = t.teacache; |
| els.v3.src = t.magcache; |
| els.v4.src = t.ours; |
| |
| [els.v1, els.v2, els.v3, els.v4].forEach(v => { |
| if(v.src) { |
| v.currentTime = 0; |
| v.play().catch(e => {}); |
| } |
| }); |
| } |
| |
| function move(dir) { loadTask(current + dir); } |
| |
| document.addEventListener('keydown', e => { |
| if (e.key === "ArrowLeft") move(-1); |
| if (e.key === "ArrowRight") move(1); |
| }); |
| |
| if (tasks.length > 0) loadTask(0); |
| else els.prompt.innerText = "No tasks found. Check console."; |
| </script> |
| </body> |
| </html> |
| """ |
|
|
| @app.route('/') |
| def home(): |
| return render_template_string(HTML, tasks_json=json.dumps(TASKS)) |
|
|
| @app.route('/videos/<folder>/<path:filename>') |
| def serve_file(folder, filename): |
| if folder in FOLDERS: |
| return send_from_directory(os.path.join(BASE_DIR, FOLDERS[folder]), filename) |
| return "Error", 404 |
|
|
| if __name__ == '__main__': |
| print("Starting server...") |
| app.run(port=5001, debug=True) |