| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Flask Video İşleme API</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;500;700&display=swap'); |
| |
| :root { |
| --space-purple: #5d3fd3; |
| --deep-space: #1a103d; |
| --neon-purple: #9c27b0; |
| --cosmic-pink: #e91e63; |
| --starry-night: #0f0524; |
| } |
| |
| body { |
| font-family: 'Rajdhani', sans-serif; |
| background: linear-gradient(135deg, var(--deep-space), var(--starry-night)); |
| color: white; |
| min-height: 100vh; |
| background-image: |
| radial-gradient(circle at 10% 20%, rgba(156, 39, 176, 0.15) 0%, transparent 20%), |
| radial-gradient(circle at 90% 30%, rgba(93, 63, 211, 0.15) 0%, transparent 25%), |
| radial-gradient(circle at 50% 80%, rgba(233, 30, 99, 0.15) 0%, transparent 30%); |
| } |
| |
| h1, h2, h3 { |
| font-family: 'Orbitron', sans-serif; |
| text-transform: uppercase; |
| } |
| |
| .container { |
| max-width: 1200px; |
| } |
| |
| .section { |
| background: rgba(26, 16, 61, 0.7); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(156, 39, 176, 0.3); |
| border-radius: 16px; |
| box-shadow: 0 0 20px rgba(156, 39, 176, 0.3); |
| transition: all 0.3s ease; |
| } |
| |
| .section:hover { |
| box-shadow: 0 0 30px rgba(156, 39, 176, 0.5); |
| transform: translateY(-2px); |
| } |
| |
| input, button, select, textarea { |
| background: rgba(26, 16, 61, 0.5); |
| border: 1px solid var(--space-purple); |
| color: white; |
| transition: all 0.3s ease; |
| } |
| |
| input:focus, button:focus, select:focus, textarea:focus { |
| outline: none; |
| border-color: var(--neon-purple); |
| box-shadow: 0 0 10px rgba(156, 39, 176, 0.5); |
| } |
| |
| button { |
| background: linear-gradient(45deg, var(--space-purple), var(--neon-purple)); |
| text-transform: uppercase; |
| letter-spacing: 1px; |
| font-weight: bold; |
| } |
| |
| button:hover { |
| background: linear-gradient(45deg, var(--neon-purple), var(--cosmic-pink)); |
| transform: translateY(-2px); |
| box-shadow: 0 5px 15px rgba(156, 39, 176, 0.4); |
| } |
| |
| .status-processing { |
| color: #ff9800; |
| } |
| |
| .status-completed { |
| color: #4caf50; |
| } |
| |
| .status-failed { |
| color: #f44336; |
| } |
| |
| .status-queued { |
| color: #2196f3; |
| } |
| |
| .download-link { |
| color: var(--neon-purple); |
| text-decoration: none; |
| font-weight: bold; |
| } |
| |
| .download-link:hover { |
| color: var(--cosmic-pink); |
| text-decoration: underline; |
| } |
| |
| table { |
| border-collapse: separate; |
| border-spacing: 0; |
| width: 100%; |
| } |
| |
| th { |
| background: rgba(93, 63, 211, 0.2); |
| position: sticky; |
| top: 0; |
| backdrop-filter: blur(5px); |
| } |
| |
| td, th { |
| padding: 12px 15px; |
| text-align: left; |
| border-bottom: 1px solid rgba(156, 39, 176, 0.2); |
| } |
| |
| tr:hover { |
| background: rgba(93, 63, 211, 0.1); |
| } |
| |
| .glow-text { |
| text-shadow: 0 0 10px rgba(156, 39, 176, 0.7); |
| } |
| |
| .file-input-wrapper { |
| position: relative; |
| overflow: hidden; |
| display: inline-block; |
| width: 100%; |
| } |
| |
| .file-input-wrapper input[type=file] { |
| position: absolute; |
| left: 0; |
| top: 0; |
| opacity: 0; |
| width: 100%; |
| height: 100%; |
| cursor: pointer; |
| } |
| |
| .file-input-label { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| padding: 12px; |
| border: 2px dashed var(--space-purple); |
| border-radius: 8px; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| } |
| |
| .file-input-label:hover { |
| border-color: var(--neon-purple); |
| background: rgba(93, 63, 211, 0.1); |
| } |
| |
| .file-input-label i { |
| margin-right: 10px; |
| color: var(--neon-purple); |
| } |
| |
| .pulse { |
| animation: pulse 2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { |
| box-shadow: 0 0 0 0 rgba(156, 39, 176, 0.4); |
| } |
| 70% { |
| box-shadow: 0 0 0 10px rgba(156, 39, 176, 0); |
| } |
| 100% { |
| box-shadow: 0 0 0 0 rgba(156, 39, 176, 0); |
| } |
| } |
| |
| .cosmic-divider { |
| height: 2px; |
| background: linear-gradient(90deg, transparent, var(--space-purple), var(--neon-purple), transparent); |
| margin: 20px 0; |
| border: none; |
| } |
| |
| @media (max-width: 768px) { |
| .section { |
| padding: 15px; |
| } |
| |
| table { |
| display: block; |
| overflow-x: auto; |
| } |
| } |
| </style> |
| </head> |
| <body class="p-4 md:p-8"> |
| <div class="container mx-auto"> |
| <div class="text-center mb-10"> |
| <h1 class="text-4xl md:text-5xl font-bold mb-4 glow-text text-purple-400"> |
| <i class="fas fa-rocket mr-3"></i>Flask Video İşleme API |
| </h1> |
| <p class="text-lg text-purple-200">Uzay temalı video işleme platformu</p> |
| </div> |
|
|
| |
| <div class="section p-6 mb-8"> |
| <h2 class="text-2xl font-bold mb-6 text-purple-300 flex items-center"> |
| <i class="fas fa-upload mr-3"></i>Video Yükle ve İşle |
| </h2> |
| <form id="uploadForm" enctype="multipart/form-data" class="space-y-6"> |
| <div class="file-input-wrapper"> |
| <label for="videoFile" class="file-input-label"> |
| <i class="fas fa-video text-xl"></i> |
| <span id="fileName">Video Dosyası Seçin</span> |
| </label> |
| <input type="file" id="videoFile" name="video" accept="video/*" required> |
| </div> |
| <button type="submit" class="w-full py-3 px-6 rounded-lg font-bold pulse"> |
| <i class="fas fa-cogs mr-2"></i>Yükle ve İşle |
| </button> |
| </form> |
| <div id="uploadStatus" class="mt-4 p-3 rounded-lg"></div> |
| </div> |
|
|
| <hr class="cosmic-divider my-10"> |
|
|
| |
| <div class="section p-6 mb-8"> |
| <h2 class="text-2xl font-bold mb-6 text-purple-300 flex items-center"> |
| <i class="fas fa-search mr-3"></i>İşlem Durumu Sorgula |
| </h2> |
| <div class="flex flex-col md:flex-row gap-4"> |
| <div class="flex-grow relative"> |
| <i class="fas fa-id-card absolute left-3 top-3 text-purple-400"></i> |
| <input type="text" id="taskIdInput" placeholder="İşlem ID'sini buraya yapıştır" |
| class="w-full pl-10 pr-4 py-2 rounded-lg"> |
| </div> |
| <button onclick="checkStatus()" class="md:w-auto py-2 px-6 rounded-lg font-bold"> |
| <i class="fas fa-sync-alt mr-2"></i>Durumu Sorgula |
| </button> |
| </div> |
| <div id="taskStatus" class="mt-4 p-4 rounded-lg bg-gray-800 bg-opacity-50"></div> |
| </div> |
|
|
| <hr class="cosmic-divider my-10"> |
|
|
| |
| <div class="section p-6"> |
| <h2 class="text-2xl font-bold mb-6 text-purple-300 flex items-center"> |
| <i class="fas fa-list-alt mr-3"></i>Tüm İşlem Durumları <span class="text-sm ml-2 text-purple-200">(Otomatik Yenilenir)</span> |
| </h2> |
| <div class="overflow-x-auto rounded-lg"> |
| <table id="allStatusesTable" class="w-full"> |
| <thead> |
| <tr class="text-purple-300"> |
| <th class="py-3"><i class="fas fa-fingerprint mr-2"></i>ID</th> |
| <th class="py-3"><i class="fas fa-file-video mr-2"></i>Orijinal Dosya</th> |
| <th class="py-3"><i class="fas fa-clock mr-2"></i>Yükleme Zamanı</th> |
| <th class="py-3"><i class="fas fa-info-circle mr-2"></i>Durum</th> |
| <th class="py-3"><i class="fas fa-stopwatch mr-2"></i>İşlem Süresi (s)</th> |
| <th class="py-3"><i class="fas fa-exclamation-triangle mr-2"></i>Hata</th> |
| <th class="py-3"><i class="fas fa-download mr-2"></i>İndir</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr><td colspan="7" class="text-center py-4 text-red-400"><i class="fas fa-exclamation-circle mr-2"></i>Tüm işlem durumları yüklenemedi. Sunucu hatası olabilir.</td></tr> |
| </tbody> |
| </table> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| const uploadForm = document.getElementById('uploadForm'); |
| const videoFile = document.getElementById('videoFile'); |
| const uploadStatusDiv = document.getElementById('uploadStatus'); |
| const taskIdInput = document.getElementById('taskIdInput'); |
| const taskStatusDiv = document.getElementById('taskStatus'); |
| const allStatusesTableBody = document.querySelector('#allStatusesTable tbody'); |
| const fileNameSpan = document.getElementById('fileName'); |
| |
| |
| videoFile.addEventListener('change', function() { |
| if (this.files.length) { |
| fileNameSpan.textContent = this.files[0].name; |
| } else { |
| fileNameSpan.textContent = 'Video Dosyası Seçin'; |
| } |
| }); |
| |
| |
| uploadForm.addEventListener('submit', async (e) => { |
| e.preventDefault(); |
| if (!videoFile.files.length) { |
| uploadStatusDiv.innerHTML = '<i class="fas fa-exclamation-circle mr-2"></i>Lütfen bir video dosyası seçin.'; |
| uploadStatusDiv.className = 'mt-4 p-3 rounded-lg bg-red-900 bg-opacity-30 text-red-300'; |
| return; |
| } |
| |
| const formData = new FormData(); |
| formData.append('video', videoFile.files[0]); |
| |
| uploadStatusDiv.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Video yükleniyor ve işleniyor... Lütfen bekleyin.'; |
| uploadStatusDiv.className = 'mt-4 p-3 rounded-lg bg-blue-900 bg-opacity-30 text-blue-300'; |
| |
| try { |
| const response = await fetch('/upload-and-process', { |
| method: 'POST', |
| body: formData |
| }); |
| const result = await response.json(); |
| if (response.ok) { |
| uploadStatusDiv.innerHTML = `<i class="fas fa-check-circle mr-2"></i>Başarılı! İşlem ID: <strong>${result.task_id}</strong>. Durum: <span class="status-${result.status}">${result.status}</span>`; |
| uploadStatusDiv.className = 'mt-4 p-3 rounded-lg bg-green-900 bg-opacity-30 text-green-300'; |
| taskIdInput.value = result.task_id; |
| fetchAllStatuses(); |
| } else { |
| uploadStatusDiv.innerHTML = `<i class="fas fa-times-circle mr-2"></i>Hata: ${result.error || 'Bilinmeyen hata'}`; |
| uploadStatusDiv.className = 'mt-4 p-3 rounded-lg bg-red-900 bg-opacity-30 text-red-300'; |
| } |
| } catch (error) { |
| uploadStatusDiv.innerHTML = `<i class="fas fa-times-circle mr-2"></i>Ağ hatası: ${error.message}`; |
| uploadStatusDiv.className = 'mt-4 p-3 rounded-lg bg-red-900 bg-opacity-30 text-red-300'; |
| console.error('Upload Error:', error); |
| } |
| }); |
| |
| |
| async function checkStatus() { |
| const taskId = taskIdInput.value.trim(); |
| if (!taskId) { |
| taskStatusDiv.innerHTML = '<i class="fas fa-exclamation-circle mr-2"></i>Lütfen bir İşlem ID girin.'; |
| taskStatusDiv.className = 'mt-4 p-4 rounded-lg bg-red-900 bg-opacity-30 text-red-300'; |
| return; |
| } |
| |
| taskStatusDiv.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Durum sorgulanıyor...'; |
| taskStatusDiv.className = 'mt-4 p-4 rounded-lg bg-blue-900 bg-opacity-30 text-blue-300'; |
| |
| try { |
| const response = await fetch(`/status/${taskId}`); |
| const status = await response.json(); |
| if (response.ok) { |
| taskStatusDiv.innerHTML = ` |
| <div class="space-y-2"> |
| <div><i class="fas fa-info-circle mr-2 text-purple-400"></i><strong>Durum:</strong> <span class="status-${status.status}">${status.status}</span></div> |
| <div><i class="fas fa-file-video mr-2 text-purple-400"></i><strong>Orijinal Dosya:</strong> ${status.original_filename}</div> |
| <div><i class="fas fa-calendar-alt mr-2 text-purple-400"></i><strong>Yükleme Zamanı:</strong> ${status.uploaded_at_readable || 'N/A'}</div> |
| ${status.start_time_readable ? `<div><i class="fas fa-play mr-2 text-purple-400"></i><strong>Başlama Zamanı:</strong> ${status.start_time_readable}</div>` : ''} |
| ${status.end_time_readable ? `<div><i class="fas fa-stop mr-2 text-purple-400"></i><strong>Bitiş Zamanı:</strong> ${status.end_time_readable}</div>` : ''} |
| ${status.duration_seconds !== null ? `<div><i class="fas fa-hourglass-half mr-2 text-purple-400"></i><strong>İşlem Süresi:</strong> ${status.duration_seconds} saniye</div>` : ''} |
| ${status.error ? `<div><i class="fas fa-exclamation-triangle mr-2 text-red-400"></i><strong>Hata:</strong> <span class="text-red-300">${status.error}</span></div>` : ''} |
| ${status.status === 'completed' ? `<div class="mt-3"><a href="/download/${status.output_filename}" target="_blank" class="download-link py-2 px-4 rounded-lg inline-block"><i class="fas fa-download mr-2"></i>İndir (${status.output_filename})</a></div>` : ''} |
| </div> |
| `; |
| taskStatusDiv.className = 'mt-4 p-4 rounded-lg bg-gray-800 bg-opacity-50'; |
| } else { |
| taskStatusDiv.innerHTML = `<i class="fas fa-times-circle mr-2"></i>Hata: ${status.error || 'İşlem ID bulunamadı.'}`; |
| taskStatusDiv.className = 'mt-4 p-4 rounded-lg bg-red-900 bg-opacity-30 text-red-300'; |
| } |
| } catch (error) { |
| taskStatusDiv.innerHTML = `<i class="fas fa-times-circle mr-2"></i>Ağ hatası: ${error.message}`; |
| taskStatusDiv.className = 'mt-4 p-4 rounded-lg bg-red-900 bg-opacity-30 text-red-300'; |
| console.error('Status Check Error:', error); |
| } |
| } |
| |
| |
| async function fetchAllStatuses() { |
| try { |
| const response = await fetch('/all-statuses'); |
| const statuses = await response.json(); |
| |
| allStatusesTableBody.innerHTML = ''; |
| |
| if (statuses.length === 0) { |
| const row = allStatusesTableBody.insertRow(); |
| const cell = row.insertCell(0); |
| cell.colSpan = 7; |
| cell.className = 'text-center py-4 text-purple-300'; |
| cell.innerHTML = '<i class="fas fa-info-circle mr-2"></i>Henüz işlenmiş video yok.'; |
| return; |
| } |
| |
| statuses.forEach(status => { |
| const row = allStatusesTableBody.insertRow(); |
| row.className = 'hover:bg-purple-900 hover:bg-opacity-10'; |
| |
| |
| const idCell = row.insertCell(0); |
| idCell.className = 'font-mono text-purple-300'; |
| idCell.textContent = status.id.substring(0, 8) + '...'; |
| |
| |
| row.insertCell(1).textContent = status.original_filename; |
| |
| |
| row.insertCell(2).textContent = status.uploaded_at_readable || 'N/A'; |
| |
| |
| const statusCell = row.insertCell(3); |
| statusCell.textContent = status.status; |
| statusCell.className = `status-${status.status} font-bold`; |
| |
| |
| const durationCell = row.insertCell(4); |
| durationCell.className = 'text-center'; |
| durationCell.textContent = status.duration_seconds !== null ? status.duration_seconds : '-'; |
| |
| |
| const errorCell = row.insertCell(5); |
| errorCell.textContent = status.error || '-'; |
| if (status.error) { |
| errorCell.className = 'text-red-400'; |
| } |
| |
| |
| const downloadCell = row.insertCell(6); |
| downloadCell.className = 'text-center'; |
| if (status.status === 'completed' && status.output_filename) { |
| const downloadLink = document.createElement('a'); |
| downloadLink.href = `/download/${status.output_filename}`; |
| downloadLink.className = 'download-link px-3 py-1 rounded-lg inline-block'; |
| downloadLink.innerHTML = '<i class="fas fa-download mr-1"></i>İndir'; |
| downloadLink.target = '_blank'; |
| downloadCell.appendChild(downloadLink); |
| } else { |
| downloadCell.textContent = '-'; |
| } |
| }); |
| } catch (error) { |
| console.error('Tüm durumları getirirken hata oluştu:', error); |
| const row = allStatusesTableBody.insertRow(); |
| const cell = row.insertCell(0); |
| cell.colSpan = 7; |
| cell.className = 'text-center py-4 text-red-400'; |
| cell.innerHTML = '<i class="fas fa-exclamation-circle mr-2"></i>Tüm işlem durumları yüklenemedi. Sunucu hatası olabilir.'; |
| } |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| fetchAllStatuses(); |
| setInterval(fetchAllStatuses, 5000); |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=tgatech/dsa" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |