| <!DOCTYPE html> |
| <html lang="fa" dir="rtl"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>متحرک سازی تصاویر با هوش مصنوعی</title> |
| <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet"> |
| <style> |
| :root { |
| --app-font: 'Vazirmatn', sans-serif; |
| --app-bg: #F8F9FC; |
| --panel-bg: #FFFFFF; |
| --panel-border: #EAEFF7; |
| --input-bg: #F6F8FB; |
| --input-border: #E1E7EF; |
| --text-primary: #1A202C; |
| --text-secondary: #626F86; |
| --text-tertiary: #8A94A6; |
| --accent-primary: #4A6CFA; |
| --accent-primary-hover: #3553D6; |
| --accent-primary-glow: rgba(74, 108, 250, 0.25); |
| --accent-secondary: #0FD4A8; |
| --success-color: #38A169; |
| --danger-color: #e53e3e; |
| --danger-color-hover: #c53030; |
| --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03); |
| --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04); |
| --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05); |
| --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05); |
| --radius-card: 24px; |
| --radius-btn: 14px; |
| --radius-input: 12px; |
| --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); |
| |
| --igadlm-alpha-color-primary: #5A67D8; |
| --igadlm-alpha-color-secondary: #805AD5; |
| --igadlm-alpha-color-primary-darker: #4C51BF; |
| --igadlm-alpha-color-secondary-darker: #6B46C1; |
| --igadlm-alpha-color-text-light: #4A5568; |
| --igadlm-alpha-color-warning-bg: #FFFBEB; |
| --igadlm-alpha-color-warning-border: #FBBF24; |
| --igadlm-alpha-color-warning-text: #B45309; |
| --igadlm-alpha-border-radius-md: 8px; |
| --igadlm-alpha-border-radius-lg: 12px; |
| } |
| |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } } |
| @keyframes igadlmAlphaV2fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } |
| |
| |
| @keyframes pulse-glow { |
| 0%, 100% { box-shadow: 0 0 20px var(--accent-primary-glow), 0 0 30px rgba(15, 212, 168, 0.2); } |
| 50% { box-shadow: 0 0 35px rgba(74, 108, 250, 0.4), 0 0 50px rgba(15, 212, 168, 0.3); } |
| } |
| @keyframes wave { |
| 0% { transform: scale(0.5); opacity: 1; } |
| 100% { transform: scale(2.5); opacity: 0; } |
| } |
| |
| |
| @keyframes fill-progress { from { width: 0%; } to { width: 100%; } } |
| |
| body { font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary); margin: 0; padding: 2.5rem 1rem; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; } |
| .container { max-width: 820px; width: 100%; } |
| header { position: relative; text-align: center; margin-bottom: 2.5rem; padding: 2rem 0; animation: fadeIn 0.8s 0.1s ease-out backwards; overflow: hidden; } |
| #neural-network-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; opacity: 0.7; } |
| .header-content { position: relative; z-index: 2; } |
| |
| |
| .animation-visualizer { |
| width: 140px; |
| height: 140px; |
| margin: 0 auto 1.5rem; |
| position: relative; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| .image-placeholder { |
| width: 90px; |
| height: 90px; |
| background: linear-gradient(45deg, #e0e7ff, #f0f9ff); |
| border-radius: 20px; |
| border: 2px solid rgba(74, 108, 250, 0.2); |
| box-shadow: 0 8px 25px rgba(26, 32, 44, 0.08); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| animation: pulse-glow 5s ease-in-out infinite; |
| position: relative; |
| z-index: 2; |
| } |
| .image-placeholder svg { |
| width: 40px; |
| height: 40px; |
| color: var(--accent-primary); |
| opacity: 0.8; |
| } |
| .wave-ring { |
| position: absolute; |
| width: 90px; |
| height: 90px; |
| border-radius: 20px; |
| border: 2px solid var(--accent-primary); |
| animation: wave 3s infinite cubic-bezier(0.65, 0, 0.35, 1); |
| z-index: 1; |
| } |
| .wave-ring.two { |
| animation-delay: -1.5s; |
| border-color: var(--accent-secondary); |
| } |
| |
| h1 { font-size: 2.8rem; font-weight: 800; margin: 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px; } |
| .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0.5rem; } |
| .instruction { font-size: 0.95rem; color: var(--text-tertiary); margin-top: 0.75rem; font-weight: 500; } |
| main { padding: 3rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); animation: fadeIn 0.8s 0.3s ease-out backwards; } |
| .form-group { margin-bottom: 2.5rem; } |
| .form-group:last-child { margin-bottom: 0; } |
| .form-label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.2em; margin-bottom: 1.2rem; } |
| .form-label svg { width: 24px; height: 24px; color: var(--accent-primary); } |
| #image-drop-zone { position: relative; border: 2px dashed var(--input-border); border-radius: var(--radius-input); padding: 2.5rem; text-align: center; cursor: pointer; transition: var(--transition-smooth); background-color: var(--input-bg); min-height: 200px; display: flex; flex-direction: column; justify-content: center; align-items: center; overflow: hidden; } |
| #image-drop-zone.drag-over, #image-drop-zone:hover:not(.has-image) { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); } |
| #image-drop-zone.has-image { border-style: solid; border-color: var(--success-color); padding: 0; cursor: default; } |
| .upload-content { display: flex; flex-direction: column; align-items: center; gap: 1rem; } |
| .upload-icon svg { width: 48px; height: 48px; color: var(--accent-primary); stroke-width: 1.5; opacity: 0.8; } |
| #image-drop-zone p { margin: 0; color: var(--text-secondary); font-weight: 500; } |
| #imagePreview { display: none; width: 100%; height: 100%; object-fit: contain; position: absolute; top: 0; left: 0; } |
| #image-drop-zone.has-image .upload-content { display: none; } |
| #image-drop-zone.has-image #imagePreview { display: block; } |
| textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 120px; resize: vertical; } |
| textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); } |
| #generateButton { display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem; font-size: 1.2rem; font-weight: 700; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px rgba(15, 212, 168, 0.25); margin-top: 1.5rem; } |
| #generateButton svg { width: 24px; height: 24px; margin-left: 4px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.5)); } |
| #generateButton:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px rgba(15, 212, 168, 0.3); } |
| #generateButton:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; opacity: 0.7; } |
| #result-container { min-height: 350px; position: relative; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); display: flex; flex-direction: column; align-items: center; justify-content: center; } |
| #result-container.active { border-style: solid; border-color: var(--panel-border); } |
| #statusSection, #outputSection, #criticalErrorSection { display: none; width: 100%; } |
| #statusSection.active, #outputSection.active, #criticalErrorSection.active { display: block; animation: fadeIn 0.5s; } |
| #statusMessages { max-height: 150px; overflow-y: auto; width: 100%; padding: 0.5rem; margin-bottom: 1rem; } |
| .status-message { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 1rem; margin-bottom: 0.5rem; border-radius: var(--radius-input); font-size: 0.9rem; background: var(--panel-bg); border: 1px solid var(--panel-border); color: var(--text-secondary); } |
| .status-message.success { border-left: 4px solid var(--success-color); color: var(--success-color); } |
| .status-message.error { border-left: 4px solid var(--danger-color); color: var(--danger-color); } |
| .status-icon { width: 18px; height: 18px; } |
| #aiLoader { display: none; align-items: center; justify-content: center; } |
| .generator-container { position: relative; width: 400px; max-width: 100%; height: 300px; border: 2px solid #38bdf8; border-radius: 20px; overflow: hidden; box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); animation: pulse-loader 5s infinite cubic-bezier(0.4, 0, 0.6, 1); background-color: #161b22; color: #f0f6fc; } |
| @keyframes pulse-loader { 0% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } 50% { box-shadow: 0 0 60px rgba(56, 189, 248, 0.7); } 100% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } } |
| .noise-layer, .sketch-layer, .building-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } |
| .noise-layer { background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="none"/><filter id="noise"><feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="4" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(%23noise)" opacity="0.6"/></svg>') repeat; opacity: 1; animation: fade-noise 7s infinite ease-in-out; } |
| @keyframes fade-noise { 0% { opacity: 1; filter: blur(5px); } 30% { opacity: 0.8; filter: blur(2px); } 100% { opacity: 0; filter: blur(0px); } } |
| .sketch-layer { filter: grayscale(1) contrast(1.5) blur(3px); opacity: 0; animation: reveal-sketch 7s infinite ease-in-out; } |
| @keyframes reveal-sketch { 0% { opacity: 0; } 20% { opacity: 1; } 60% { opacity: 0.5; } 100% { opacity: 0; } } |
| .building-layer { filter: blur(15px); opacity: 0; animation: denoise-color 7s infinite ease-in-out; } |
| @keyframes denoise-color { 0% { opacity: 0; } 40% { opacity: 0.6; filter: blur(5px); } 100% { opacity: 1; filter: blur(0px); } } |
| .pixel-grid { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: repeating-linear-gradient(0deg, transparent 0 1px, rgba(255,255,255,0.1) 1px 2px), repeating-linear-gradient(90deg, transparent 0 1px, rgba(255,255,255,0.1) 1px 2px); opacity: 1; animation: dissolve-grid 7s infinite ease-in-out; } |
| @keyframes dissolve-grid { 0% { opacity: 1; } 70% { opacity: 0.5; } 100% { opacity: 0; } } |
| .particles { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle, rgba(56, 189, 248, 0.2) 0%, transparent 50%); animation: flow-particles 7s infinite cubic-bezier(0.4, 0, 0.6, 1); } |
| @keyframes flow-particles { 0% { transform: translate(0, 0) scale(1); opacity: 0.5; } 50% { transform: translate(10px, -15px) scale(1.05); opacity: 0.8; } 100% { transform: translate(0, 0) scale(1); opacity: 0.5; } } |
| .text-overlay { position: absolute; top: 45%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: 700; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); animation: glow-text 7s infinite ease-in-out; font-family: var(--app-font); width: 90%; text-align: center;} |
| @keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } } |
| .progress-bar { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); } |
| .progress-bar.animate-progress { animation: fill-progress 60s linear forwards; } |
| #outputVideo { width: 100%; max-width: 500px; border-radius: var(--radius-input); margin: 1rem auto; display: block; background-color: #000; box-shadow: var(--shadow-md); } |
| #finalSeed { text-align: center; color: var(--text-tertiary); font-size: 0.85rem; margin-top: 1rem; } |
| .video-controls { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; } |
| .video-button { padding: 0.7rem 1.5rem; border-radius: var(--radius-btn); border: none; cursor: pointer; font-family: var(--app-font); font-weight: 600; font-size: 1rem; transition: var(--transition-smooth); display: flex; align-items: center; gap: 0.5rem; } |
| .video-button.primary { background-color: var(--accent-primary); color: white; } |
| .video-button.primary:hover { background-color: var(--accent-primary-hover); transform: translateY(-2px); } |
| .video-button:not(.primary) { background-color: var(--input-bg); color: var(--text-secondary); border: 1px solid var(--input-border); } |
| .video-button:not(.primary):hover { background-color: var(--panel-border); color: var(--text-primary); } |
| .gpu-error-message-container { text-align: right; background-color: var(--igadlm-alpha-color-warning-bg); padding: 25px; border-radius: var(--igadlm-alpha-border-radius-lg); box-shadow: var(--shadow-lg); border: 2px solid var(--igadlm-alpha-color-warning-border); animation: igadlmAlphaV2fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) both; } |
| .gpu-error-message-container h2 { font-size: 1.2em; color: var(--igadlm-alpha-color-warning-text); margin-bottom: 15px; font-weight: 700; display: flex; align-items: center; border-bottom: 1px solid var(--igadlm-alpha-color-warning-border); padding-bottom: 12px; } |
| .gpu-error-message-container h2 .icon { margin-left: 10px; font-size: 1.3em; } |
| .gpu-error-message-container .error-content { font-size: 0.9rem; color: var(--igadlm-alpha-color-text-light); line-height: 1.7; word-break: break-word; white-space: pre-line; } |
| .gpu-error-message-container .error-content strong.error-message-title { font-weight: 600; color: var(--igadlm-alpha-color-warning-text); display: block; margin-bottom: 8px;} |
| .gpu-error-message-container .error-content ul { list-style-type: "▫️ "; padding-right: 20px; margin-top: 10px; } |
| .gpu-error-message-container .error-content li { margin-bottom: 6px; } |
| .gpu-error-actions { display: flex; gap: 15px; margin-top: 25px; } |
| .gpu-error-actions .action-button { padding: 12px 20px; border: none; border-radius: var(--igadlm-alpha-border-radius-md); font-size: 0.95em; font-weight: 600; cursor: pointer; font-family: inherit; width: 100%; box-shadow: var(--shadow-md); transition: var(--transition-smooth); text-transform: none; letter-spacing: normal; } |
| .gpu-error-actions .action-button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: var(--shadow-lg); } |
| .gpu-error-actions .action-button.back-button { background-color: var(--panel-bg); color: var(--text-tertiary); border: 1px solid var(--panel-border); flex-grow: 0.45; } |
| .gpu-error-actions .action-button.back-button:hover:not(:disabled) { background-color: var(--input-bg); } |
| .gpu-error-actions .action-button.retry-button { background: linear-gradient(60deg, var(--igadlm-alpha-color-primary) 0%, var(--igadlm-alpha-color-secondary) 100%); color: white; flex-grow: 1; } |
| .gpu-error-actions .action-button.retry-button:hover:not(:disabled) { background: linear-gradient(60deg, var(--igadlm-alpha-color-primary-darker) 0%, var(--igadlm-alpha-color-secondary-darker) 100%); } |
| |
| @media (max-width: 768px) { |
| main { padding: 1.5rem; } h1 { font-size: 2.2rem; } |
| .generator-container { height: 250px; } |
| .text-overlay { font-size: 18px; } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <header> |
| <canvas id="neural-network-canvas"></canvas> |
| <div class="header-content"> |
| <div class="animation-visualizer"> |
| <div class="image-placeholder"> |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> |
| <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect> |
| <circle cx="8.5" cy="8.5" r="1.5"></circle> |
| <polyline points="21 15 16 10 5 21"></polyline> |
| </svg> |
| </div> |
| <div class="wave-ring"></div> |
| <div class="wave-ring two"></div> |
| </div> |
| <h1>متحرک سازی تصاویر</h1> |
| <p class="subtitle">به تصاویر ثابت خود جان ببخشید و آنها را با هوش مصنوعی متحرک کنید</p> |
| <p class="instruction">تصویر خود را آپلود کنید، توضیح دهید چگونه باید حرکت کند، و جادو را تماشا کنید.</p> |
| </div> |
| </header> |
|
|
| <main> |
| <div class="form-group"> |
| <div class="form-label"> |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg> |
| ۱. تصویر خود را انتخاب کنید |
| </div> |
| <label id="image-drop-zone" for="imageFile"> |
| <div class="upload-content"> |
| <div class="upload-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div> |
| <p>فایل تصویر را اینجا بکشید یا برای انتخاب کلیک کنید</p> |
| </div> |
| <img id="imagePreview" src="" alt="Preview"> |
| </label> |
| <input type="file" id="imageFile" accept="image/jpeg, image/png, image/webp" hidden> |
| </div> |
| |
| <div class="form-group"> |
| <label for="prompt" class="form-label"> |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg> |
| ۲. دستور حرکت را بنویسید |
| </label> |
| <textarea id="prompt" rows="4" placeholder="مثال: حرکت ابرها، موج زدن آب، وزش ملایم باد در موها، زوم آهسته به بیرون"></textarea> |
| <button id="generateButton"> |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/></svg> |
| <span>شروع متحرک سازی</span> |
| </button> |
| </div> |
|
|
| <div class="form-group"> |
| <div class="form-label"> |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/><path d="M21 14l-1.5 3-3-1.5 3-3 1.5 3z"/><path d="M19.5 2.5l-3 1.5 1.5 3 3-1.5-1.5-3z"/></svg> |
| ۳. نتیجه را ببینید |
| </div> |
| <div id="result-container"> |
| <div id="statusSection"> |
| <div id="statusMessages"></div> |
| <div id="aiLoader" style="display: none;"> |
| <div class="generator-container"> |
| <div class="noise-layer"></div> |
| <div class="sketch-layer"></div> |
| <div class="building-layer"></div> |
| <div class="pixel-grid"></div> |
| <div class="particles"></div> |
| <div class="text-overlay">در حال متحرک سازی تصویر...</div> |
| <div class="progress-bar"></div> |
| </div> |
| </div> |
| </div> |
|
|
| <div id="outputSection"> |
| <video id="outputVideo" controls preload="metadata" playsinline loop autoplay muted></video> |
| <p id="finalSeed"></p> |
| <div class="video-controls"> |
| <button id="btnDownloadVideo" class="video-button primary" style="display: none;"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg> |
| دانلود انیمیشن |
| </button> |
| <button id="btnRestart" class="video-button"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg> |
| انیمیشن جدید |
| </button> |
| </div> |
| </div> |
| |
| <div id="criticalErrorSection"> |
| |
| </div> |
| </div> |
| </div> |
| </main> |
| </div> |
| |
| |
| <script type="module"> |
| import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; |
| |
| |
| (function() { |
| |
| const resultContainer = document.getElementById('result-container'); |
| const imageFileInput = document.getElementById('imageFile'); |
| const imagePreview = document.getElementById('imagePreview'); |
| const imageDropZone = document.getElementById('image-drop-zone'); |
| const promptInput = document.getElementById('prompt'); |
| const generateButton = document.getElementById('generateButton'); |
| const outputVideo = document.getElementById('outputVideo'); |
| const finalSeedElement = document.getElementById('finalSeed'); |
| const criticalErrorSection = document.getElementById('criticalErrorSection'); |
| const statusSection = document.getElementById('statusSection'); |
| const statusMessagesDiv = document.getElementById('statusMessages'); |
| const aiLoader = document.getElementById('aiLoader'); |
| const loaderProgressBar = document.querySelector('#aiLoader .progress-bar'); |
| const loaderTextOverlay = document.querySelector('#aiLoader .text-overlay'); |
| const outputSection = document.getElementById('outputSection'); |
| const btnRestart = document.getElementById('btnRestart'); |
| const btnDownloadVideo = document.getElementById('btnDownloadVideo'); |
| |
| |
| const VIDEO_SPACE_NAME = "zerogpu-aoti/wan2-2-fp8da-aoti-faster"; |
| const TRANSLATOR_SPACE_URL_BASE = "https://hamed744-translate-tts-aloha.hf.space/gradio_api"; |
| const FN_INDEX_TRANSLATE_SPEAK = 1; |
| const DEFAULT_TTS_VOICE_TRANSLATOR = "انگلیسی (آمریکا) - جنی (زن)"; |
| const DEFAULT_NEGATIVE_PROMPT = "bad quality, ugly, blurry, watermark, text, signature"; |
| const DEFAULT_PROMPT_TEXT = ""; |
| const TRANSPARENT_PIXEL_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; |
| const DEFAULT_VIDEO_DURATION = 5.1; |
| |
| |
| let lastAttemptedVideoPayload = null; |
| |
| |
| function generateRandomHash(length = 11) { |
| return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15).substring(0, length - 7); |
| } |
| |
| async function retryLastAttempt() { |
| if (!lastAttemptedVideoPayload || !lastAttemptedVideoPayload.imageSourceFile || !lastAttemptedVideoPayload.persianPrompt) { |
| showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد. لطفاً از نو شروع کنید."); |
| return; |
| } |
| |
| hideCriticalError(); |
| clearStatusMessages(); |
| generateButton.disabled = true; |
| resultContainer.classList.add('active'); |
| statusSection.classList.add('active'); |
| aiLoader.style.display = 'flex'; |
| loaderProgressBar.classList.add('animate-progress'); |
| outputSection.classList.remove('active'); |
| addStatusMessage('شروع تلاش مجدد...', 'info'); |
| |
| const { persianPrompt, imageSourceFile } = lastAttemptedVideoPayload; |
| |
| try { |
| const englishPromptForVideo = await translatePersianToEnglish(persianPrompt); |
| addStatusMessage(`ترجمه مجدد: ${englishPromptForVideo}`, 'info'); |
| |
| setAiLoaderText("در حال متحرک سازی تصویر..."); |
| |
| const client = await Client.connect(VIDEO_SPACE_NAME); |
| const result = await client.predict("/generate_video", { |
| input_image: imageSourceFile, |
| prompt: englishPromptForVideo, |
| steps: 6, |
| negative_prompt: DEFAULT_NEGATIVE_PROMPT, |
| duration_seconds: DEFAULT_VIDEO_DURATION, |
| guidance_scale: 1, |
| guidance_scale_2: 1, |
| seed: 0, |
| randomize_seed: true |
| }); |
| |
| handleSuccessfulVideoGeneration(result); |
| |
| } catch (errorCaught) { |
| const isTranslationErr = errorCaught.message.toLowerCase().includes("مترجم"); |
| showCriticalError(`خطا در تلاش مجدد: ${errorCaught.message}`, isTranslationErr); |
| } |
| } |
| |
| function showCriticalError(message, isTranslationError = false) { |
| const lowerCaseMessage = (message || "").toLowerCase(); |
| |
| if (lowerCaseMessage.includes("gpu") || lowerCaseMessage.includes("quota") || lowerCaseMessage.includes("exceeded") || lowerCaseMessage.includes("limit") || lowerCaseMessage.includes("capacity") || lowerCaseMessage.includes("queue full")) { |
| const detailedGpuErrorHtml = ` |
| <div class="gpu-error-message-container"> |
| <h2><span class="icon">💡</span> راهنمای رفع محدودیت انیمیشن</h2> |
| <div class="error-content"> |
| <strong class="error-message-title">ارور محدودیت استفاده از GPU</strong> |
| <p>مدلهای هوش مصنوعی برای اجرا به کارتهای گرافیک بسیار قدرتمند نیاز دارند که این امر گاهی محدودیتهایی جی پی یو را در استفاده از آنها ایجاد میکند. با این حال، خبر خوب این است که این محدودیتها با استفاده از یک ترفند ساده قابل رفع هستند.</p> |
| <p><strong>برای رفع محدودیت هر بار اینگونه عمل کنید:</strong></p> |
| <ul> |
| <li>اگر از اینترنت سیمکارت استفاده میکنید، فیلترشکن خود را حتما خاموش کنید.</li> |
| <li>به مدت ۱۰ تا ۱۵ ثانیه، گوشی خود را در حالت هواپیما قرار دهید و سپس آن را غیرفعال کنید. این محدودیت با همین روش برداشته میشود</li> |
| <li>اگر از وای فای استفاده میکنید، مودم خود را یک بار خاموش و روشن کنید (بهتر است برای استفاده نامحدود از برنامه از اینترنت سیم کارت استفاده کنید).</li> |
| </ul> |
| <p>خلاصه: هربار این صفحه اومد اینگونه عمل کنید از اینترنت سیم کارت استفاده کنید، فیلتر شکن خاموش باشه، یک یا دوبار حالت هواپیما رو روشن و خاموش کنید. تلاش مجدداً بزنید این ارور بر طرف میشه، با این روش میشه بصورت نامحدود با این برنامه انیمیشن ساخت☘️</p> |
| </div> |
| <div class="gpu-error-actions"> |
| <button class="action-button back-button" id="gpuErrorGoBackBtn">بازگشت</button> |
| <button class="action-button retry-button" id="gpuErrorRetryBtn">تلاش مجدد</button> |
| </div> |
| </div>`; |
| criticalErrorSection.innerHTML = detailedGpuErrorHtml; |
| document.getElementById('gpuErrorGoBackBtn').addEventListener('click', hideCriticalError); |
| document.getElementById('gpuErrorRetryBtn').addEventListener('click', retryLastAttempt); |
| } else { |
| criticalErrorSection.innerHTML = ` |
| <div id="criticalErrorMessage" style="color: var(--danger-color); font-weight: 500; margin-bottom: 1.5rem; line-height: 1.6;"><p>${message}</p></div> |
| <div class="video-controls"> |
| <button id="simpleErrorGoBackBtn" class="video-button">بازگشت</button> |
| <button id="simpleErrorRetryBtn" class="video-button primary">تلاش مجدد</button> |
| </div>`; |
| document.getElementById('simpleErrorGoBackBtn').addEventListener('click', hideCriticalError); |
| document.getElementById('simpleErrorRetryBtn').addEventListener('click', retryLastAttempt); |
| } |
| |
| resultContainer.classList.add('active'); |
| criticalErrorSection.classList.add('active'); |
| statusSection.classList.remove('active'); |
| outputSection.classList.remove('active'); |
| aiLoader.style.display = 'none'; |
| generateButton.disabled = false; |
| } |
| |
| function hideCriticalError() { |
| criticalErrorSection.classList.remove('active'); |
| criticalErrorSection.innerHTML = ''; |
| } |
| |
| function setAiLoaderText(text) { |
| if (loaderTextOverlay) loaderTextOverlay.textContent = text; |
| } |
| |
| function addStatusMessage(message, type = 'info') { |
| if (criticalErrorSection.classList.contains('active') && type === 'error') { |
| return; |
| } |
| resultContainer.classList.add('active'); |
| statusSection.classList.add('active'); |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `status-message ${type}`; |
| const iconSvg = type === 'success' |
| ? '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>' |
| : type === 'error' |
| ? '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>' |
| : '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>'; |
| messageDiv.innerHTML = `${iconSvg}<span class="status-text">${message}</span>`; |
| statusMessagesDiv.insertBefore(messageDiv, statusMessagesDiv.firstChild); |
| statusMessagesDiv.scrollTop = 0; |
| } |
| |
| function clearStatusMessages() { |
| statusMessagesDiv.innerHTML = ''; |
| if(loaderProgressBar) { |
| loaderProgressBar.classList.remove('animate-progress'); |
| loaderProgressBar.style.width = '0%'; |
| } |
| aiLoader.style.display = 'none'; |
| if (outputVideo.src && outputVideo.src.startsWith('blob:')) { |
| URL.revokeObjectURL(outputVideo.src); |
| } |
| outputVideo.src = ''; |
| finalSeedElement.textContent = ''; |
| btnDownloadVideo.style.display = 'none'; |
| } |
| |
| function initializeForm() { |
| hideCriticalError(); |
| resultContainer.classList.remove('active'); |
| statusSection.classList.remove('active'); |
| outputSection.classList.remove('active'); |
| clearStatusMessages(); |
| lastAttemptedVideoPayload = null; |
| imageFileInput.value = ''; |
| imagePreview.src = TRANSPARENT_PIXEL_SRC; |
| imageDropZone.classList.remove('has-image'); |
| promptInput.value = DEFAULT_PROMPT_TEXT; |
| generateButton.disabled = false; |
| } |
| |
| async function translatePersianToEnglish(persianText) { |
| let currentTranslatorSessionHash = generateRandomHash(); |
| const payload = { |
| data: [persianText, DEFAULT_TTS_VOICE_TRANSLATOR, 0, 0, 0], |
| event_data: null, |
| fn_index: FN_INDEX_TRANSLATE_SPEAK, |
| session_hash: currentTranslatorSessionHash |
| }; |
| |
| if (aiLoader.style.display !== 'none') { |
| setAiLoaderText("در حال ترجمه دستور شما..."); |
| } |
| |
| try { |
| const joinResponse = await fetch(`${TRANSLATOR_SPACE_URL_BASE}/queue/join?`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(payload) |
| }); |
| if (!joinResponse.ok) throw new Error(`خطای اتصال به سرور مترجم: ${joinResponse.status}`); |
| const joinData = await joinResponse.json(); |
| if (!joinData.event_id) throw new Error("پاسخ نامعتبر از سرور مترجم."); |
| |
| return new Promise((resolve, reject) => { |
| const eventSource = new EventSource(`${TRANSLATOR_SPACE_URL_BASE}/queue/data?session_hash=${currentTranslatorSessionHash}`); |
| eventSource.onmessage = function(event) { |
| const data = JSON.parse(event.data); |
| if (data.msg === "process_completed") { |
| eventSource.close(); |
| if (data.success && data.output?.data?.[0]) { |
| resolve(data.output.data[0]); |
| } else { |
| reject(new Error(data.output?.error || "ترجمه ناموفق بود.")); |
| } |
| } else if (data.msg === "process_starts") { |
| if (aiLoader.style.display !== 'none') setAiLoaderText("ترجمه در سرور آغاز شد..."); |
| } |
| }; |
| eventSource.onerror = (err) => { |
| eventSource.close(); |
| reject(new Error("خطا در ارتباط با سرور مترجم.")); |
| }; |
| }); |
| } catch (error) { |
| console.error("خطای کلی در ترجمه:", error); |
| throw error; |
| } |
| } |
| |
| function handleSuccessfulVideoGeneration(result) { |
| aiLoader.style.display = 'none'; |
| if (!result.data?.[0]?.video?.url) { |
| showCriticalError('پاسخ دریافتی از سرور انیمیشن معتبر نیست.'); |
| return; |
| } |
| |
| addStatusMessage('انیمیشن شما آماده شد! 🎉', 'success'); |
| const videoUrl = result.data[0].video.url; |
| const usedSeed = result.data[1]; |
| |
| setTimeout(() => { |
| statusSection.classList.remove('active'); |
| outputSection.classList.add('active'); |
| outputVideo.src = videoUrl; |
| outputVideo.load(); |
| finalSeedElement.textContent = `Seed: ${usedSeed}`; |
| prepareCustomDownloadLink(videoUrl); |
| }, 300); |
| |
| generateButton.disabled = false; |
| } |
| |
| function prepareCustomDownloadLink(videoUrl) { |
| btnDownloadVideo.style.display = 'flex'; |
| btnDownloadVideo.onclick = () => { |
| const link = document.createElement('a'); |
| link.href = videoUrl; |
| link.target = '_blank'; |
| link.download = `animated_image_${Date.now()}.mp4`; |
| document.body.appendChild(link); |
| link.click(); |
| document.body.removeChild(link); |
| }; |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', initializeForm); |
| |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { imageDropZone.addEventListener(eventName, ev => { ev.preventDefault(); ev.stopPropagation(); }); }); |
| ['dragenter', 'dragover'].forEach(eventName => { imageDropZone.addEventListener(eventName, () => { if (!imageDropZone.classList.contains('has-image')) { imageDropZone.classList.add('drag-over'); } }); }); |
| ['dragleave', 'drop'].forEach(eventName => { imageDropZone.addEventListener(eventName, () => { imageDropZone.classList.remove('drag-over'); }); }); |
| imageDropZone.addEventListener('drop', e => { |
| if (!imageDropZone.classList.contains('has-image') && e.dataTransfer.files.length > 0) { |
| imageFileInput.files = e.dataTransfer.files; |
| const changeEvent = new Event('change', { bubbles: true }); |
| imageFileInput.dispatchEvent(changeEvent); |
| } |
| }); |
| |
| btnRestart.addEventListener('click', initializeForm); |
| |
| imageFileInput.addEventListener('change', function(event) { |
| const file = event.target.files[0]; |
| hideCriticalError(); |
| if (file) { |
| if (!file.type.startsWith('image/')) { |
| addStatusMessage('فرمت نامعتبر', 'error'); |
| imageFileInput.value = ''; |
| imagePreview.src = TRANSPARENT_PIXEL_SRC; |
| imageDropZone.classList.remove('has-image'); |
| } else { |
| const reader = new FileReader(); |
| reader.onload = (e) => { |
| imagePreview.src = e.target.result; |
| imageDropZone.classList.add('has-image'); |
| }; |
| reader.readAsDataURL(file); |
| } |
| } else { |
| imagePreview.src = TRANSPARENT_PIXEL_SRC; |
| imageDropZone.classList.remove('has-image'); |
| } |
| }); |
| |
| generateButton.addEventListener('click', async () => { |
| const persianPrompt = promptInput.value.trim(); |
| const imageFile = imageFileInput.files[0]; |
| |
| if (!persianPrompt) { |
| addStatusMessage('لطفاً دستور حرکت را وارد کنید.', 'error'); |
| promptInput.focus(); |
| return; |
| } |
| if (!imageFile) { |
| addStatusMessage('لطفاً تصویر انتخاب کنید.', 'error'); |
| return; |
| } |
| |
| clearStatusMessages(); |
| hideCriticalError(); |
| generateButton.disabled = true; |
| resultContainer.classList.add('active'); |
| statusSection.classList.add('active'); |
| aiLoader.style.display = 'flex'; |
| loaderProgressBar.classList.add('animate-progress'); |
| outputSection.classList.remove('active'); |
| addStatusMessage('در حال آماده سازی...', 'info'); |
| |
| lastAttemptedVideoPayload = { |
| persianPrompt: persianPrompt, |
| imageSourceFile: imageFile |
| }; |
| |
| try { |
| const englishPrompt = await translatePersianToEnglish(persianPrompt); |
| addStatusMessage(`ترجمه: ${englishPrompt}`, 'info'); |
| |
| setAiLoaderText("در حال متحرک سازی تصویر..."); |
| |
| const client = await Client.connect(VIDEO_SPACE_NAME); |
| const result = await client.predict("/generate_video", { |
| input_image: imageFile, |
| prompt: englishPrompt, |
| steps: 6, |
| negative_prompt: DEFAULT_NEGATIVE_PROMPT, |
| duration_seconds: DEFAULT_VIDEO_DURATION, |
| guidance_scale: 1, |
| guidance_scale_2: 1, |
| seed: 0, |
| randomize_seed: true |
| }); |
| |
| handleSuccessfulVideoGeneration(result); |
| |
| } catch (errorCaught) { |
| const isTranslationErr = errorCaught.message.toLowerCase().includes("مترجم"); |
| showCriticalError(`خطا: ${errorCaught.message}`, isTranslationErr); |
| } |
| }); |
| })(); |
| </script> |
|
|
| <script> |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| const canvas = document.getElementById('neural-network-canvas'); |
| if (!canvas) return; |
| const header = canvas.parentElement; |
| const ctx = canvas.getContext('2d'); |
| let particles = []; |
| const particleCount = 20; const maxDistance = 100; |
| const computedStyles = getComputedStyle(document.documentElement); |
| const particleColor = computedStyles.getPropertyValue('--accent-primary').trim(); |
| const lineColor = computedStyles.getPropertyValue('--text-tertiary').trim(); |
| function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); } |
| class Particle { constructor() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * 0.3; this.vy = (Math.random() - 0.5) * 0.3; this.radius = 1.2; } update() { this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x > canvas.width) this.vx *= -1; if (this.y < 0 || this.y > canvas.height) this.vy *= -1; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = particleColor; ctx.fill(); } } |
| function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } } |
| function connectParticles() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < maxDistance) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = lineColor; ctx.lineWidth = 0.2; ctx.globalAlpha = 1 - distance / maxDistance; ctx.stroke(); } } } ctx.globalAlpha = 1; } |
| function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); } |
| window.addEventListener('resize', resizeCanvas); resizeCanvas(); animate(); |
| }); |
| </script> |
| </body> |
| </html> |