Spaces:
Running
Running
Download create.html from Wayed11/wheelie-app: direct link, hf CLI and curl.
- Browser
- Download file 45.3 kB
-
https://huggingface.co/spaces/Wayed11/wheelie-app/resolve/main/create.html
- Command line
-
hf download hf://spaces/Wayed11/wheelie-app/create.html
-
curl -L -o create.html https://huggingface.co/spaces/Wayed11/wheelie-app/resolve/main/create.html
45.3 kB
| <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><meta name="apple-mobile-web-app-capable" content="yes"><title>Wheelie Create</title> | |
| <style> | |
| *{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent} | |
| html,body{background:#010101;color:#fefefe;font-family:-apple-system,system-ui,sans-serif;height:100%} | |
| header{position:fixed;top:0;left:0;right:0;z-index:5;padding:calc(env(safe-area-inset-top) + 10px) 16px 10px;display:flex;align-items:center;gap:12px;background:linear-gradient(#010101,rgba(1,1,1,0))} | |
| header a{color:#fefefe;text-decoration:none;font-size:26px;line-height:1} | |
| header h1{font-size:20px;font-weight:800;letter-spacing:.5px} | |
| header h1 b{color:#FF6FB5} | |
| .tabs{margin-left:auto;display:flex;background:#1a1a1a;border-radius:20px;padding:3px} | |
| .tabs button{border:0;background:none;color:#aaa;font-weight:700;padding:7px 14px;border-radius:17px;font-size:14px} | |
| .tabs button.on{background:#FF6FB5;color:#010101} | |
| #feed{padding:calc(env(safe-area-inset-top) + 70px) 10px 200px;display:grid;grid-template-columns:1fr 1fr;gap:8px} | |
| .card{position:relative;border-radius:16px;overflow:hidden;background:#111;aspect-ratio:9/16} | |
| .card video,.card img{width:100%;height:100%;object-fit:cover;display:block} | |
| .card .busy{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;font-size:13px;color:#ccc;text-align:center;padding:10px} | |
| .ring{width:42px;height:42px;border-radius:50%;border:4px solid #222;border-top-color:#FF6FB5;border-right-color:#5BC0F8;animation:s 1s linear infinite} | |
| @keyframes s{to{transform:rotate(360deg)}} | |
| .empty{grid-column:1/3;text-align:center;color:#888;padding:80px 20px;font-size:16px;line-height:1.5} | |
| .empty b{display:block;font-size:26px;color:#fefefe;margin-bottom:8px} | |
| #full{position:fixed;inset:0;background:#010101;z-index:20;display:none;align-items:center;justify-content:center} | |
| #full video{width:100%;height:100%;object-fit:contain} | |
| #full .x{position:absolute;top:calc(env(safe-area-inset-top) + 12px);right:16px;font-size:30px;background:none;border:0;color:#fefefe;z-index:2} | |
| #full .act{position:absolute;bottom:calc(env(safe-area-inset-bottom) + 20px);left:0;right:0;display:flex;justify-content:center;gap:12px;z-index:2} | |
| #full .act button{border:0;border-radius:24px;padding:12px 20px;font-weight:800;font-size:15px;background:#FF6FB5;color:#010101} | |
| #full .act button.b{background:#5BC0F8} | |
| #bar{position:fixed;left:0;right:0;bottom:0;z-index:10;padding:10px 12px calc(env(safe-area-inset-bottom) + 12px);background:linear-gradient(rgba(1,1,1,0),#010101 30%)} | |
| .modes{display:flex;gap:8px;margin-bottom:10px;overflow-x:auto} | |
| .modes button{border:1px solid #333;background:#111;color:#ddd;border-radius:16px;padding:7px 14px;font-weight:700;font-size:13px;white-space:nowrap} | |
| .modes button.on{border-color:#5BC0F8;color:#5BC0F8} | |
| .box{display:flex;align-items:flex-end;gap:8px;background:#161616;border:1px solid #2a2a2a;border-radius:26px;padding:8px} | |
| .box textarea{flex:1;background:none;border:0;color:#fefefe;font-size:17px;resize:none;outline:none;max-height:120px;padding:8px 4px;font-family:inherit} | |
| .ib{width:42px;height:42px;border-radius:50%;border:0;background:#262626;color:#fefefe;font-size:20px;flex:none;display:flex;align-items:center;justify-content:center} | |
| .ib.go{background:linear-gradient(90deg,#FF6FB5 50%,#5BC0F8 50%);color:#010101;font-weight:900} | |
| .ib.rec{background:#FF6FB5} | |
| #thumb{display:none;align-items:center;gap:8px;margin-bottom:8px;font-size:13px;color:#ccc} | |
| #thumb img{width:44px;height:44px;border-radius:10px;object-fit:cover} | |
| #thumb button{background:none;border:0;color:#FF6FB5;font-weight:800} | |
| </style></head><body> | |
| <header><a href="./" aria-label="Back to Wheelie">‹</a><h1>Wheelie <b>Create</b></h1> | |
| <div class="tabs"><button id="tV" class="on">Video</button><button id="tW">9:16</button></div></header> | |
| <main id="feed"><div class="empty"><b>Make something.</b>Type or talk your idea. Add a picture to bring it to life. Every video comes with sound.</div></main> | |
| <div id="full"><button class="x" aria-label="Close">×</button><video playsinline controls></video><div class="act"><button id="ext">Keep going</button><button class="b" id="sv">Save</button></div></div> | |
| <div id="bar"><div id="thumb"><img alt=""><span>Starting from this picture</span><button id="rmImg">Remove</button></div> | |
| <div class="modes"><button class="on" data-m="normal">Normal</button><button data-m="fun">Fun</button><button data-m="cine">Cinematic</button><button data-m="custom">Custom</button></div> | |
| <div class="box"><button class="ib" id="pic" aria-label="Add picture">+</button><input type="file" id="file" accept="image/*" hidden> | |
| <textarea id="p" rows="1" placeholder="What should Wheelie make?"></textarea> | |
| <button class="ib" id="mic" aria-label="Talk">🎤</button><button class="ib go" id="go" aria-label="Make it">↑</button></div></div> | |
| <script> | |
| // Wheelie Create engine v5: Qwen still from her real Grok pictures (body + face) -> talking (LTX-2.5) or Wan 2.2 + MMAudio. | |
| const SB='https://yzzxvjrzniuhxebqqrsy.supabase.co',GEN=SB+'/functions/v1/gen-call',API=SB+'/functions/v1/wheelie-api',PUB=SB+'/storage/v1/object/public/public/'; | |
| const QWEN='https://qwen-qwen-image-edit-2511.hf.space',WAN='https://zerogpu-aoti-wan2-2-fp8da-aoti-faster.hf.space',MMA='https://hkchengrex-mmaudio.hf.space',MIX='https://wayed11-wheelie-mix.hf.space',FLUX='https://wayed11-wheelie-image.hf.space'; | |
| const REF=SB+'/storage/v1/object/public/public/gen/ref/body.webp',S2V='https://wan-ai-wan2-2-s2v.hf.space',LTX='https://lightricks-ltx-2-5.hf.space'; | |
| const BODY=PUB+'gen/ref/body.webp',FACE=PUB+'gen/ref/face.webp'; | |
| const LOOK='The same young woman from image 1 and image 2, exactly the same character: her exact face from image 2 (freckles, small beauty mark under her eye, thick dark brows, heavy-lidded brown eyes with winged liner, full lips, curly dark brown messy high bun, small silver hoops), her exact rainbow striped chunky knit sweater, dark jeans, purple high-top sneakers, and her exact black power wheelchair with rainbow LED wheel lights and a coffee cup in the cup holder from image 1. She drives it herself.'; | |
| const LOOKS='Same stylized 3D animated film look, lighting quality and rendering style as the reference images. No text, no watermark.'; | |
| const VNEG='watermark, logo, captions, text, teenager, child, crop top, bare midriff, crying, static frame, blurry, jitter, morphing face, extra fingers, a person pushing the wheelchair'; | |
| const MODES={normal:' Smooth natural motion.',fun:' Playful, bouncy, funny comic timing, energetic camera.',cine:' Epic slow cinematic camera move, dramatic lighting.',custom:''}; | |
| let mode='normal',img=null,useHer=true,wide=false,last=null,lastText=''; | |
| const $=s=>document.querySelector(s),feed=$('#feed'); | |
| const FD=(p,u)=>({path:p,url:u||(p.startsWith('http')?p:undefined),meta:{_type:'gradio.FileData'}}); | |
| document.querySelector('.empty').innerHTML='<b>Make something.</b>Type or talk your idea. Wheelie stars by default. Put her line in quotes and she says it. Every video comes with sound.'; | |
| function showThumb(){const t=$('#thumb');if(img){$('#thumb img').src=URL.createObjectURL(img);$('#thumb span').textContent='Starting from your picture';t.style.display='flex'}else if(useHer){$('#thumb img').src=REF;$('#thumb span').textContent='Starring Wheelie';t.style.display='flex'}else t.style.display='none'} | |
| showThumb(); | |
| document.querySelectorAll('.modes button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.modes button').forEach(x=>x.classList.remove('on'));b.classList.add('on');mode=b.dataset.m}); | |
| $('#tW').onclick=()=>{wide=!wide;$('#tW').textContent=wide?'16:9':'9:16'}; | |
| $('#pic').onclick=()=>$('#file').click(); | |
| $('#file').onchange=e=>{const f=e.target.files[0];if(!f)return;img=f;showThumb()}; | |
| $('#rmImg').onclick=()=>{if(img){img=null;$('#file').value=''}else useHer=false;showThumb()}; | |
| const p=$('#p');p.oninput=()=>{p.style.height='auto';p.style.height=p.scrollHeight+'px'}; | |
| const SR=window.SpeechRecognition||window.webkitSpeechRecognition;let rec=null; | |
| $('#mic').onclick=()=>{if(!SR){p.focus();return}if(rec){rec.stop();return}rec=new SR();rec.lang='en-US';rec.interimResults=true;$('#mic').classList.add('rec'); | |
| rec.onresult=e=>{p.value=[...e.results].map(r=>r[0].transcript).join('');p.oninput()};rec.onend=()=>{$('#mic').classList.remove('rec');rec=null};rec.start()}; | |
| async function up(space,blob,name){const fd=new FormData();fd.append('files',blob,name);const r=await fetch(space+'/gradio_api/upload',{method:'POST',body:fd});const j=await r.json();return j[0]} | |
| async function run(space,fn,data,save,tries=2){let e='';for(let i=0;i<tries;i++){try{const r=await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'run',space,fn,data,save})});const j=await r.json();if(j.ok&&(!save||String(j.saved).startsWith('http')))return j;e=j.error||j.saved||'failed'}catch(x){e=String(x)}}throw new Error(e)} | |
| async function long(space,fn,data,save){ | |
| const s=await (await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'start',space,fn,data})})).json(); | |
| if(!s.event_id)throw new Error('start'); | |
| const r=await fetch(space+'/gradio_api/call/'+fn+'/'+s.event_id);const rd=r.body.getReader(),dec=new TextDecoder();let t=''; | |
| while(true){const {value,done}=await rd.read();if(done)break;t+=dec.decode(value,{stream:true});if(t.length>60000)t=t.slice(-30000);const a=Math.max(t.lastIndexOf('event: complete'),t.lastIndexOf('event: error'));if(a>=0&&t.indexOf('\n\n',a)>0)break} | |
| const m=t.match(/event: complete\s*\ndata: (.*)/);if(!m)throw new Error('render'); | |
| const sv=await (await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'save',space,out:JSON.parse(m[1]),save})})).json(); | |
| if(!sv.ok)throw new Error('save');return sv.saved; | |
| } | |
| function lineOf(t){const m=t.match(/["“]([^"”]{2,240})["”]/);return m?m[1].trim():''} | |
| function soundOf(t){return 'Natural cinematic sound effects and ambience that match this scene: '+t.replace(/["“][^"”]*["”]/g,'').slice(0,300)} | |
| function card(){const e=feed.querySelector('.empty');if(e)e.remove();const c=document.createElement('div');c.className='card';c.innerHTML='<div class="busy"><div class="ring"></div><span>Wheelie is on it…</span></div>';feed.prepend(c);return c} | |
| async function make(text,opt){ | |
| opt=opt||{};const c=card(),msg=c.querySelector('span'),id=Date.now().toString(36)+Math.random().toString(36).slice(2,6); | |
| const say=m=>{msg.textContent=m}; | |
| try{ | |
| const scene=text.replace(/["“][^"”]*["”]/g,' ').replace(/\s+/g,' ').trim()||'she looks straight into the camera'; | |
| const line=lineOf(text); | |
| let start; | |
| if(opt.frame){say('Picking up where she left off…');const pth=await up(WAN,opt.frame,'frame.png');start=FD(pth)} | |
| else{ | |
| say('Drawing the first frame…'); | |
| const h=wide?720:1280,w=wide?1280:720; | |
| let still; | |
| if(img||useHer){ | |
| let refs=[{image:FD(BODY,BODY),caption:null},{image:FD(FACE,FACE),caption:null}]; | |
| if(img){const pth=await up(QWEN,img,'ref.png');refs=[{image:FD(pth),caption:null}]} | |
| const pr=(useHer&&!img?LOOK+' ':'Keep the exact people, faces and art style from the reference image. ')+'New scene: '+scene+'. '+(wide?'Horizontal 16:9.':'Vertical 9:16.')+' '+LOOKS; | |
| still=(await run(QWEN,'infer',[refs,pr,0,true,4.0,30,h,w,false],'gen/'+id+'-still.webp')).saved; | |
| }else{ | |
| still=(await run(FLUX,'infer',[(scene+'. '+LOOKS).slice(0,1500),0,true,w,h,4],'gen/'+id+'-still.webp')).saved; | |
| } | |
| c.innerHTML='<img src="'+still+'" alt=""><div class="busy" style="background:rgba(1,1,1,.45)"><div class="ring"></div><span>Bringing it to life…</span></div>'; | |
| start=FD(still,still); | |
| } | |
| const m2=c.querySelector('span')||msg;const say2=t=>{m2.textContent=t}; | |
| let out; | |
| if(line){ | |
| const TALK='https://victor-longcat-video-avatar-1-5.hf.space'; | |
| say2('Recording her line…'); | |
| const v=await (await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'speaktest',voice:'wheelie',text:line,save:id})})).json(); | |
| if(!v.ok)throw new Error('voice'); | |
| const wav=PUB+'gen/ari/'+id+'.wav'; | |
| say2('She\'s saying it in her voice… (a few minutes)'); | |
| const tp='A stylized 3D animated young woman in a power wheelchair talks to the camera, lips moving clearly with every word. Her face stays deadpan: flat, unimpressed, slightly amused, no big smile, no big eyebrow raise. Her body is relaxed and moves naturally, like a normal person. She sits normally in her chair. '+scene+'.'+(mode==='custom'?'':MODES[mode])+' Same face, hair, outfit and animation style as the image in every frame.'; | |
| out=await long(TALK,'generate',[start,FD(wav,wav),tp.slice(0,1200),'720p',42,'Clean speech (fast)','DBCache faster'],'gen/'+id+'-talk.mp4'); | |
| }else{ | |
| say2('Bringing it to life…'); | |
| const motion=scene+'.'+(mode==='custom'?'':MODES[mode])+' Stylized 3D animated film, same face and style every frame.'; | |
| const vid=(await run(WAN,'generate_video',[start,motion.slice(0,1200),6,VNEG,5,1,1,42,true],'gen/'+id+'.mp4')).saved; | |
| say2('Adding the sound…'); | |
| out=vid; | |
| try{out=(await run(MMA,'video_to_audio',[FD(vid,vid),soundOf(text),'music, speech, talking, distortion',-1,25,4.5,5],'gen/'+id+'-s.mp4')).saved}catch(_){} | |
| } | |
| c.innerHTML='<video src="'+out+'" crossorigin="anonymous" playsinline muted loop autoplay></video>'; | |
| c.onclick=()=>open(out,text); | |
| }catch(_){c.innerHTML='<div class="busy">That one didn\'t come through. Tap to run it again.</div>';c.onclick=()=>{c.remove();make(text,opt)}} | |
| } | |
| function open(url,text){last=url;lastText=text||'';const f=$('#full'),v=f.querySelector('video');v.crossOrigin='anonymous';v.src=url;v.muted=false;f.style.display='flex';v.play()} | |
| $('#full .x').onclick=()=>{const f=$('#full');f.querySelector('video').pause();f.style.display='none'}; | |
| $('#sv').onclick=()=>{window.open(last+'?download=wheelie.mp4','_blank')}; | |
| $('#ext').onclick=async()=>{const v=$('#full video');v.pause(); | |
| try{if(v.duration)v.currentTime=Math.max(0,v.duration-0.05);await new Promise(r=>setTimeout(r,350));const cv=document.createElement('canvas');cv.width=v.videoWidth;cv.height=v.videoHeight;cv.getContext('2d').drawImage(v,0,0);const b=await new Promise(r=>cv.toBlob(r,'image/png'));$('#full .x').onclick();make(p.value.trim()||lastText||'She keeps going, same scene, natural continuation',{frame:b});p.value='';p.oninput()}catch(_){$('#full .x').onclick()}}; | |
| $('#go').onclick=()=>{const t=p.value.trim();if(!t&&!img)return;make(t||'She comes alive with cinematic motion');p.value='';p.oninput()}; | |
| </script><script type="text/plain"> | |
| // Wheelie Create engine v4: Qwen still from her full picture -> talking (LTX-2.5, mouth + voice together) or Wan 2.2 motion + MMAudio. | |
| const SB='https://yzzxvjrzniuhxebqqrsy.supabase.co',GEN=SB+'/functions/v1/gen-call',API=SB+'/functions/v1/wheelie-api',PUB=SB+'/storage/v1/object/public/public/'; | |
| const QWEN='https://qwen-qwen-image-edit-2511.hf.space',WAN='https://zerogpu-aoti-wan2-2-fp8da-aoti-faster.hf.space',MMA='https://hkchengrex-mmaudio.hf.space',MIX='https://wayed11-wheelie-mix.hf.space',FLUX='https://wayed11-wheelie-image.hf.space'; | |
| const REF='https://wayed11-wheelie-app.static.hf.space/wheelie-full.webp',S2V='https://wan-ai-wan2-2-s2v.hf.space',LTX='https://lightricks-ltx-2-5.hf.space'; | |
| const LOOK='Keep this exact woman from the reference image: same adult face, freckles, small beauty mark under one eye, thick low dark brows, heavy-lidded brown eyes, dark brown curly messy high bun, silver hoops, deadpan. Same rainbow chunky knit sweater fully covering her waist, dark jeans, purple high-tops. Same black Quantum Q6 Edge power wheelchair with rainbow LEDs, joystick on the right, she drives it herself.'; | |
| const LOOKS='High-end cinematic 3D render, lifelike skin texture, real cinematic lighting, depth of field, film-grade detail. Not cartoonish, not anime, no text, no watermark.'; | |
| const VNEG='cartoonish, kids cartoon, chibi, anime, plastic skin, watermark, logo, captions, text, teenager, child, crop top, bare midriff, crying, static frame, blurry, jitter, morphing face, extra fingers, a person pushing the wheelchair'; | |
| const MODES={normal:' Smooth natural motion.',fun:' Playful, bouncy, funny comic timing, energetic camera.',cine:' Epic slow cinematic camera move, dramatic lighting.',custom:''}; | |
| let mode='normal',img=null,useHer=true,wide=false,last=null,lastText=''; | |
| const $=s=>document.querySelector(s),feed=$('#feed'); | |
| const FD=(p,u)=>({path:p,url:u||(p.startsWith('http')?p:undefined),meta:{_type:'gradio.FileData'}}); | |
| document.querySelector('.empty').innerHTML='<b>Make something.</b>Type or talk your idea. Wheelie stars by default. Put her line in quotes and she says it. Every video comes with sound.'; | |
| function showThumb(){const t=$('#thumb');if(img){$('#thumb img').src=URL.createObjectURL(img);$('#thumb span').textContent='Starting from your picture';t.style.display='flex'}else if(useHer){$('#thumb img').src=REF;$('#thumb span').textContent='Starring Wheelie';t.style.display='flex'}else t.style.display='none'} | |
| showThumb(); | |
| document.querySelectorAll('.modes button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.modes button').forEach(x=>x.classList.remove('on'));b.classList.add('on');mode=b.dataset.m}); | |
| $('#tW').onclick=()=>{wide=!wide;$('#tW').textContent=wide?'16:9':'9:16'}; | |
| $('#pic').onclick=()=>$('#file').click(); | |
| $('#file').onchange=e=>{const f=e.target.files[0];if(!f)return;img=f;showThumb()}; | |
| $('#rmImg').onclick=()=>{if(img){img=null;$('#file').value=''}else useHer=false;showThumb()}; | |
| const p=$('#p');p.oninput=()=>{p.style.height='auto';p.style.height=p.scrollHeight+'px'}; | |
| const SR=window.SpeechRecognition||window.webkitSpeechRecognition;let rec=null; | |
| $('#mic').onclick=()=>{if(!SR){p.focus();return}if(rec){rec.stop();return}rec=new SR();rec.lang='en-US';rec.interimResults=true;$('#mic').classList.add('rec'); | |
| rec.onresult=e=>{p.value=[...e.results].map(r=>r[0].transcript).join('');p.oninput()};rec.onend=()=>{$('#mic').classList.remove('rec');rec=null};rec.start()}; | |
| async function up(space,blob,name){const fd=new FormData();fd.append('files',blob,name);const r=await fetch(space+'/gradio_api/upload',{method:'POST',body:fd});const j=await r.json();return j[0]} | |
| async function run(space,fn,data,save,tries=2){let e='';for(let i=0;i<tries;i++){try{const r=await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'run',space,fn,data,save})});const j=await r.json();if(j.ok&&(!save||String(j.saved).startsWith('http')))return j;e=j.error||j.saved||'failed'}catch(x){e=String(x)}}throw new Error(e)} | |
| async function long(space,fn,data,save){ | |
| const s=await (await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'start',space,fn,data})})).json(); | |
| if(!s.event_id)throw new Error('start'); | |
| const r=await fetch(space+'/gradio_api/call/'+fn+'/'+s.event_id);const rd=r.body.getReader(),dec=new TextDecoder();let t=''; | |
| while(true){const {value,done}=await rd.read();if(done)break;t+=dec.decode(value,{stream:true});if(t.length>60000)t=t.slice(-30000);const a=Math.max(t.lastIndexOf('event: complete'),t.lastIndexOf('event: error'));if(a>=0&&t.indexOf('\n\n',a)>0)break} | |
| const m=t.match(/event: complete\s*\ndata: (.*)/);if(!m)throw new Error('render'); | |
| const sv=await (await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'save',space,out:JSON.parse(m[1]),save})})).json(); | |
| if(!sv.ok)throw new Error('save');return sv.saved; | |
| } | |
| function lineOf(t){const m=t.match(/["“]([^"”]{2,240})["”]/);return m?m[1].trim():''} | |
| function soundOf(t){return 'Natural cinematic sound effects and ambience that match this scene: '+t.replace(/["“][^"”]*["”]/g,'').slice(0,300)} | |
| function card(){const e=feed.querySelector('.empty');if(e)e.remove();const c=document.createElement('div');c.className='card';c.innerHTML='<div class="busy"><div class="ring"></div><span>Wheelie is on it…</span></div>';feed.prepend(c);return c} | |
| async function make(text,opt){ | |
| opt=opt||{};const c=card(),msg=c.querySelector('span'),id=Date.now().toString(36)+Math.random().toString(36).slice(2,6); | |
| const say=m=>{msg.textContent=m}; | |
| try{ | |
| const scene=text.replace(/["“][^"”]*["”]/g,' ').replace(/\s+/g,' ').trim()||'she looks straight into the camera'; | |
| const line=lineOf(text); | |
| let start; | |
| if(opt.frame){say('Picking up where she left off…');const pth=await up(WAN,opt.frame,'frame.png');start=FD(pth)} | |
| else{ | |
| say('Drawing the first frame…'); | |
| const h=wide?720:1280,w=wide?1280:720; | |
| let still; | |
| if(img||useHer){ | |
| let ref=FD(REF,REF);if(img){const pth=await up(QWEN,img,'ref.png');ref=FD(pth)} | |
| const pr=(useHer&&!img?LOOK+' ':'Keep the exact people, faces and style from the reference image. ')+'NEW SCENE: '+scene+'. '+(wide?'Horizontal 16:9. ':'Vertical 9:16. ')+LOOKS; | |
| still=(await run(QWEN,'infer',[[{image:ref,caption:null}],pr,0,true,4.0,20,h,w,false],'gen/'+id+'-still.webp')).saved; | |
| }else{ | |
| still=(await run(FLUX,'infer',[(scene+'. '+LOOKS).slice(0,1500),0,true,w,h,4],'gen/'+id+'-still.webp')).saved; | |
| } | |
| c.innerHTML='<img src="'+still+'" alt=""><div class="busy" style="background:rgba(1,1,1,.45)"><div class="ring"></div><span>Bringing it to life…</span></div>'; | |
| start=FD(still,still); | |
| } | |
| const m2=c.querySelector('span')||msg;const say2=t=>{m2.textContent=t}; | |
| let out; | |
| if(line){ | |
| say2('She\'s saying it…'); | |
| const words=line.split(/\s+/).length,longLine=words>12; | |
| const tp=scene+'. She looks into the camera and talks, her lips clearly moving with every word. She says in a warm, dry New York accent, young woman\'s voice: "'+line+'"'+(mode==='custom'?'':MODES[mode])+' Natural sound of the scene behind her. Her face, hair, outfit, wheelchair and art style stay exactly the same as the image in every frame.'; | |
| const hh=wide?832:1472,ww=wide?1472:832; | |
| out=(await run(LTX,'run',[tp.slice(0,1500),start,hh,ww,Math.min(5,Math.max(3,words/2.4+1.2)),42,'conv',longLine,true,false],'gen/'+id+'-talk.mp4')).saved; | |
| }else{ | |
| say2('Bringing it to life…'); | |
| const motion=scene+'.'+(mode==='custom'?'':MODES[mode])+' Cinematic, same face every frame.'; | |
| const vid=(await run(WAN,'generate_video',[start,motion.slice(0,1200),6,VNEG,5,1,1,42,true],'gen/'+id+'.mp4')).saved; | |
| say2('Adding the sound…'); | |
| out=vid; | |
| try{out=(await run(MMA,'video_to_audio',[FD(vid,vid),soundOf(text),'music, speech, talking, distortion',-1,25,4.5,5],'gen/'+id+'-s.mp4')).saved}catch(_){} | |
| } | |
| c.innerHTML='<video src="'+out+'" crossorigin="anonymous" playsinline muted loop autoplay></video>'; | |
| c.onclick=()=>open(out,text); | |
| }catch(_){c.innerHTML='<div class="busy">That one didn\'t come through. Tap to run it again.</div>';c.onclick=()=>{c.remove();make(text,opt)}} | |
| } | |
| function open(url,text){last=url;lastText=text||'';const f=$('#full'),v=f.querySelector('video');v.crossOrigin='anonymous';v.src=url;v.muted=false;f.style.display='flex';v.play()} | |
| $('#full .x').onclick=()=>{const f=$('#full');f.querySelector('video').pause();f.style.display='none'}; | |
| $('#sv').onclick=()=>{window.open(last+'?download=wheelie.mp4','_blank')}; | |
| $('#ext').onclick=async()=>{const v=$('#full video');v.pause(); | |
| try{if(v.duration)v.currentTime=Math.max(0,v.duration-0.05);await new Promise(r=>setTimeout(r,350));const cv=document.createElement('canvas');cv.width=v.videoWidth;cv.height=v.videoHeight;cv.getContext('2d').drawImage(v,0,0);const b=await new Promise(r=>cv.toBlob(r,'image/png'));$('#full .x').onclick();make(p.value.trim()||lastText||'She keeps going, same scene, natural continuation',{frame:b});p.value='';p.oninput()}catch(_){$('#full .x').onclick()}}; | |
| $('#go').onclick=()=>{const t=p.value.trim();if(!t&&!img)return;make(t||'She comes alive with cinematic motion');p.value='';p.oninput()}; | |
| </script><script type="text/plain"> | |
| // Wheelie Create engine v3: Qwen still from her full picture -> talking (Wan S2V + her voice) or Wan 2.2 motion -> MMAudio scene sound. | |
| const SB='https://yzzxvjrzniuhxebqqrsy.supabase.co',GEN=SB+'/functions/v1/gen-call',API=SB+'/functions/v1/wheelie-api',PUB=SB+'/storage/v1/object/public/public/'; | |
| const QWEN='https://qwen-qwen-image-edit-2511.hf.space',WAN='https://zerogpu-aoti-wan2-2-fp8da-aoti-faster.hf.space',MMA='https://hkchengrex-mmaudio.hf.space',MIX='https://wayed11-wheelie-mix.hf.space',FLUX='https://wayed11-wheelie-image.hf.space'; | |
| const REF='https://wayed11-wheelie-app.static.hf.space/wheelie-full.webp',S2V='https://wan-ai-wan2-2-s2v.hf.space'; | |
| const LOOK='Keep this exact woman from the reference image: same adult face, freckles, small beauty mark under one eye, thick low dark brows, heavy-lidded brown eyes, dark brown curly messy high bun, silver hoops, deadpan. Same rainbow chunky knit sweater fully covering her waist, dark jeans, purple high-tops. Same black Quantum Q6 Edge power wheelchair with rainbow LEDs, joystick on the right, she drives it herself.'; | |
| const LOOKS='High-end cinematic 3D render, lifelike skin texture, real cinematic lighting, depth of field, film-grade detail. Not cartoonish, not anime, no text, no watermark.'; | |
| const VNEG='cartoonish, kids cartoon, chibi, anime, plastic skin, watermark, logo, captions, text, teenager, child, crop top, bare midriff, crying, static frame, blurry, jitter, morphing face, extra fingers, a person pushing the wheelchair'; | |
| const MODES={normal:' Smooth natural motion.',fun:' Playful, bouncy, funny comic timing, energetic camera.',cine:' Epic slow cinematic camera move, dramatic lighting.',custom:''}; | |
| let mode='normal',img=null,useHer=true,wide=false,last=null,lastText=''; | |
| const $=s=>document.querySelector(s),feed=$('#feed'); | |
| const FD=(p,u)=>({path:p,url:u||(p.startsWith('http')?p:undefined),meta:{_type:'gradio.FileData'}}); | |
| document.querySelector('.empty').innerHTML='<b>Make something.</b>Type or talk your idea. Wheelie stars by default. Put her line in quotes and she says it. Every video comes with sound.'; | |
| function showThumb(){const t=$('#thumb');if(img){$('#thumb img').src=URL.createObjectURL(img);$('#thumb span').textContent='Starting from your picture';t.style.display='flex'}else if(useHer){$('#thumb img').src=REF;$('#thumb span').textContent='Starring Wheelie';t.style.display='flex'}else t.style.display='none'} | |
| showThumb(); | |
| document.querySelectorAll('.modes button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.modes button').forEach(x=>x.classList.remove('on'));b.classList.add('on');mode=b.dataset.m}); | |
| $('#tW').onclick=()=>{wide=!wide;$('#tW').textContent=wide?'16:9':'9:16'}; | |
| $('#pic').onclick=()=>$('#file').click(); | |
| $('#file').onchange=e=>{const f=e.target.files[0];if(!f)return;img=f;showThumb()}; | |
| $('#rmImg').onclick=()=>{if(img){img=null;$('#file').value=''}else useHer=false;showThumb()}; | |
| const p=$('#p');p.oninput=()=>{p.style.height='auto';p.style.height=p.scrollHeight+'px'}; | |
| const SR=window.SpeechRecognition||window.webkitSpeechRecognition;let rec=null; | |
| $('#mic').onclick=()=>{if(!SR){p.focus();return}if(rec){rec.stop();return}rec=new SR();rec.lang='en-US';rec.interimResults=true;$('#mic').classList.add('rec'); | |
| rec.onresult=e=>{p.value=[...e.results].map(r=>r[0].transcript).join('');p.oninput()};rec.onend=()=>{$('#mic').classList.remove('rec');rec=null};rec.start()}; | |
| async function up(space,blob,name){const fd=new FormData();fd.append('files',blob,name);const r=await fetch(space+'/gradio_api/upload',{method:'POST',body:fd});const j=await r.json();return j[0]} | |
| async function run(space,fn,data,save,tries=2){let e='';for(let i=0;i<tries;i++){try{const r=await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'run',space,fn,data,save})});const j=await r.json();if(j.ok&&(!save||String(j.saved).startsWith('http')))return j;e=j.error||j.saved||'failed'}catch(x){e=String(x)}}throw new Error(e)} | |
| async function long(space,fn,data,save){ | |
| const s=await (await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'start',space,fn,data})})).json(); | |
| if(!s.event_id)throw new Error('start'); | |
| const r=await fetch(space+'/gradio_api/call/'+fn+'/'+s.event_id);const rd=r.body.getReader(),dec=new TextDecoder();let t=''; | |
| while(true){const {value,done}=await rd.read();if(done)break;t+=dec.decode(value,{stream:true});if(t.length>60000)t=t.slice(-30000);const a=Math.max(t.lastIndexOf('event: complete'),t.lastIndexOf('event: error'));if(a>=0&&t.indexOf('\n\n',a)>0)break} | |
| const m=t.match(/event: complete\s*\ndata: (.*)/);if(!m)throw new Error('render'); | |
| const sv=await (await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'save',space,out:JSON.parse(m[1]),save})})).json(); | |
| if(!sv.ok)throw new Error('save');return sv.saved; | |
| } | |
| function lineOf(t){const m=t.match(/["“]([^"”]{2,240})["”]/);return m?m[1].trim():''} | |
| function soundOf(t){return 'Natural cinematic sound effects and ambience that match this scene: '+t.replace(/["“][^"”]*["”]/g,'').slice(0,300)} | |
| function card(){const e=feed.querySelector('.empty');if(e)e.remove();const c=document.createElement('div');c.className='card';c.innerHTML='<div class="busy"><div class="ring"></div><span>Wheelie is on it…</span></div>';feed.prepend(c);return c} | |
| async function make(text,opt){ | |
| opt=opt||{};const c=card(),msg=c.querySelector('span'),id=Date.now().toString(36)+Math.random().toString(36).slice(2,6); | |
| const say=m=>{msg.textContent=m}; | |
| try{ | |
| const scene=text.replace(/["“][^"”]*["”]/g,' ').replace(/\s+/g,' ').trim()||'she looks straight into the camera'; | |
| const line=lineOf(text); | |
| let start; | |
| if(opt.frame){say('Picking up where she left off…');const pth=await up(WAN,opt.frame,'frame.png');start=FD(pth)} | |
| else{ | |
| say('Drawing the first frame…'); | |
| const h=wide?720:1280,w=wide?1280:720; | |
| let still; | |
| if(img||useHer){ | |
| let ref=FD(REF,REF);if(img){const pth=await up(QWEN,img,'ref.png');ref=FD(pth)} | |
| const pr=(useHer&&!img?LOOK+' ':'Keep the exact people, faces and style from the reference image. ')+'NEW SCENE: '+scene+'. '+(wide?'Horizontal 16:9. ':'Vertical 9:16. ')+LOOKS; | |
| still=(await run(QWEN,'infer',[[{image:ref,caption:null}],pr,0,true,4.0,20,h,w,false],'gen/'+id+'-still.webp')).saved; | |
| }else{ | |
| still=(await run(FLUX,'infer',[(scene+'. '+LOOKS).slice(0,1500),0,true,w,h,4],'gen/'+id+'-still.webp')).saved; | |
| } | |
| c.innerHTML='<img src="'+still+'" alt=""><div class="busy" style="background:rgba(1,1,1,.45)"><div class="ring"></div><span>Bringing it to life…</span></div>'; | |
| start=FD(still,still); | |
| } | |
| const m2=c.querySelector('span')||msg;const say2=t=>{m2.textContent=t}; | |
| let out; | |
| if(line){ | |
| say2('Recording her line…'); | |
| const v=await (await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'speaktest',voice:'wheelie',text:line,save:id})})).json(); | |
| if(!v.ok)throw new Error('voice'); | |
| const wav=PUB+'gen/ari/'+id+'.wav'; | |
| say2('She\'s saying it… (about 4 min)'); | |
| const talk=await long(S2V,'predict',[start,FD(wav,wav),'480P'],'gen/'+id+'-talk.mp4'); | |
| out=talk; | |
| say2('Adding the scene sound…'); | |
| try{const amb=(await run(MMA,'video_to_audio',[FD(talk,talk),soundOf(text),'music, speech, talking, voice, distortion',-1,25,4.5,8],'gen/'+id+'-s.mp4')).saved; | |
| out=(await run(MIX,'mix',[amb,wav,0,1.6],'gen/'+id+'-final.mp4')).saved}catch(_){} | |
| }else{ | |
| say2('Bringing it to life…'); | |
| const motion=scene+'.'+(mode==='custom'?'':MODES[mode])+' Cinematic, same face every frame.'; | |
| const vid=(await run(WAN,'generate_video',[start,motion.slice(0,1200),6,VNEG,5,1,1,42,true],'gen/'+id+'.mp4')).saved; | |
| say2('Adding the sound…'); | |
| out=vid; | |
| try{out=(await run(MMA,'video_to_audio',[FD(vid,vid),soundOf(text),'music, speech, talking, distortion',-1,25,4.5,5],'gen/'+id+'-s.mp4')).saved}catch(_){} | |
| } | |
| c.innerHTML='<video src="'+out+'" crossorigin="anonymous" playsinline muted loop autoplay></video>'; | |
| c.onclick=()=>open(out,text); | |
| }catch(_){c.innerHTML='<div class="busy">That one didn\'t come through. Tap to run it again.</div>';c.onclick=()=>{c.remove();make(text,opt)}} | |
| } | |
| function open(url,text){last=url;lastText=text||'';const f=$('#full'),v=f.querySelector('video');v.crossOrigin='anonymous';v.src=url;v.muted=false;f.style.display='flex';v.play()} | |
| $('#full .x').onclick=()=>{const f=$('#full');f.querySelector('video').pause();f.style.display='none'}; | |
| $('#sv').onclick=()=>{window.open(last+'?download=wheelie.mp4','_blank')}; | |
| $('#ext').onclick=async()=>{const v=$('#full video');v.pause(); | |
| try{if(v.duration)v.currentTime=Math.max(0,v.duration-0.05);await new Promise(r=>setTimeout(r,350));const cv=document.createElement('canvas');cv.width=v.videoWidth;cv.height=v.videoHeight;cv.getContext('2d').drawImage(v,0,0);const b=await new Promise(r=>cv.toBlob(r,'image/png'));$('#full .x').onclick();make(p.value.trim()||lastText||'She keeps going, same scene, natural continuation',{frame:b});p.value='';p.oninput()}catch(_){$('#full .x').onclick()}}; | |
| $('#go').onclick=()=>{const t=p.value.trim();if(!t&&!img)return;make(t||'She comes alive with cinematic motion');p.value='';p.oninput()}; | |
| </script><script type="text/plain"> | |
| // Wheelie Create engine v2: Qwen still (her locked look) -> Wan 2.2 motion -> MMAudio scene sound -> her voice on top. | |
| const SB='https://yzzxvjrzniuhxebqqrsy.supabase.co',GEN=SB+'/functions/v1/gen-call',API=SB+'/functions/v1/wheelie-api',PUB=SB+'/storage/v1/object/public/public/'; | |
| const QWEN='https://qwen-qwen-image-edit-2511.hf.space',WAN='https://zerogpu-aoti-wan2-2-fp8da-aoti-faster.hf.space',MMA='https://hkchengrex-mmaudio.hf.space',MIX='https://wayed11-wheelie-mix.hf.space',FLUX='https://wayed11-wheelie-image.hf.space'; | |
| const REF='https://wayed11-wheelie-app.static.hf.space/wheelie.jpg'; | |
| const LOOK='Keep this exact woman from the reference image: same adult face, freckles, small beauty mark under one eye, thick low dark brows, heavy-lidded brown eyes, dark brown curly messy high bun, silver hoops, deadpan. Same rainbow chunky knit sweater fully covering her waist, dark jeans, purple high-tops. Same black Quantum Q6 Edge power wheelchair with rainbow LEDs, joystick on the right, she drives it herself.'; | |
| const LOOKS='High-end cinematic 3D render, lifelike skin texture, real cinematic lighting, depth of field, film-grade detail. Not cartoonish, not anime, no text, no watermark.'; | |
| const VNEG='cartoonish, kids cartoon, chibi, anime, plastic skin, watermark, logo, captions, text, teenager, child, crop top, bare midriff, crying, static frame, blurry, jitter, morphing face, extra fingers, a person pushing the wheelchair'; | |
| const MODES={normal:' Smooth natural motion.',fun:' Playful, bouncy, funny comic timing, energetic camera.',cine:' Epic slow cinematic camera move, dramatic lighting.',custom:''}; | |
| let mode='normal',img=null,useHer=true,wide=false,last=null,lastText=''; | |
| const $=s=>document.querySelector(s),feed=$('#feed'); | |
| const FD=(p,u)=>({path:p,url:u||(p.startsWith('http')?p:undefined),meta:{_type:'gradio.FileData'}}); | |
| document.querySelector('.empty').innerHTML='<b>Make something.</b>Type or talk your idea. Wheelie stars by default. Put her line in quotes and she says it. Every video comes with sound.'; | |
| function showThumb(){const t=$('#thumb');if(img){$('#thumb img').src=URL.createObjectURL(img);$('#thumb span').textContent='Starting from your picture';t.style.display='flex'}else if(useHer){$('#thumb img').src=REF;$('#thumb span').textContent='Starring Wheelie';t.style.display='flex'}else t.style.display='none'} | |
| showThumb(); | |
| document.querySelectorAll('.modes button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.modes button').forEach(x=>x.classList.remove('on'));b.classList.add('on');mode=b.dataset.m}); | |
| $('#tW').onclick=()=>{wide=!wide;$('#tW').textContent=wide?'16:9':'9:16'}; | |
| $('#pic').onclick=()=>$('#file').click(); | |
| $('#file').onchange=e=>{const f=e.target.files[0];if(!f)return;img=f;showThumb()}; | |
| $('#rmImg').onclick=()=>{if(img){img=null;$('#file').value=''}else useHer=false;showThumb()}; | |
| const p=$('#p');p.oninput=()=>{p.style.height='auto';p.style.height=p.scrollHeight+'px'}; | |
| const SR=window.SpeechRecognition||window.webkitSpeechRecognition;let rec=null; | |
| $('#mic').onclick=()=>{if(!SR){p.focus();return}if(rec){rec.stop();return}rec=new SR();rec.lang='en-US';rec.interimResults=true;$('#mic').classList.add('rec'); | |
| rec.onresult=e=>{p.value=[...e.results].map(r=>r[0].transcript).join('');p.oninput()};rec.onend=()=>{$('#mic').classList.remove('rec');rec=null};rec.start()}; | |
| async function up(space,blob,name){const fd=new FormData();fd.append('files',blob,name);const r=await fetch(space+'/gradio_api/upload',{method:'POST',body:fd});const j=await r.json();return j[0]} | |
| async function run(space,fn,data,save,tries=2){let e='';for(let i=0;i<tries;i++){try{const r=await fetch(GEN,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({op:'run',space,fn,data,save})});const j=await r.json();if(j.ok&&(!save||String(j.saved).startsWith('http')))return j;e=j.error||j.saved||'failed'}catch(x){e=String(x)}}throw new Error(e)} | |
| function lineOf(t){const m=t.match(/["“]([^"”]{2,240})["”]/);return m?m[1].trim():''} | |
| function soundOf(t){return 'Natural cinematic sound effects and ambience that match this scene: '+t.replace(/["“][^"”]*["”]/g,'').slice(0,300)} | |
| function card(){const e=feed.querySelector('.empty');if(e)e.remove();const c=document.createElement('div');c.className='card';c.innerHTML='<div class="busy"><div class="ring"></div><span>Wheelie is on it…</span></div>';feed.prepend(c);return c} | |
| async function make(text,opt){ | |
| opt=opt||{};const c=card(),msg=c.querySelector('span'),id=Date.now().toString(36)+Math.random().toString(36).slice(2,6); | |
| const say=m=>{msg.textContent=m}; | |
| try{ | |
| const scene=text.replace(/["“][^"”]*["”]/g,' ').replace(/\s+/g,' ').trim()||'she looks straight into the camera'; | |
| const line=lineOf(text); | |
| let start; | |
| if(opt.frame){say('Picking up where she left off…');const pth=await up(WAN,opt.frame,'frame.png');start=FD(pth)} | |
| else{ | |
| say('Drawing the first frame…'); | |
| const h=wide?720:1280,w=wide?1280:720; | |
| let still; | |
| if(img||useHer){ | |
| let ref=FD(REF,REF);if(img){const pth=await up(QWEN,img,'ref.png');ref=FD(pth)} | |
| const pr=(useHer&&!img?LOOK+' ':'Keep the exact people, faces and style from the reference image. ')+'NEW SCENE: '+scene+'. '+(wide?'Horizontal 16:9. ':'Vertical 9:16. ')+LOOKS; | |
| still=(await run(QWEN,'infer',[[{image:ref,caption:null}],pr,0,true,4.0,20,h,w,false],'gen/'+id+'-still.webp')).saved; | |
| }else{ | |
| still=(await run(FLUX,'infer',[(scene+'. '+LOOKS).slice(0,1500),0,true,w,h,4],'gen/'+id+'-still.webp')).saved; | |
| } | |
| c.innerHTML='<img src="'+still+'" alt=""><div class="busy" style="background:rgba(1,1,1,.45)"><div class="ring"></div><span>Bringing it to life…</span></div>'; | |
| start=FD(still,still); | |
| } | |
| const m2=c.querySelector('span')||msg;const say2=t=>{m2.textContent=t}; | |
| say2('Bringing it to life…'); | |
| const motion=scene+'.'+(mode==='custom'?'':MODES[mode])+' Cinematic, same face every frame.'; | |
| const vid=(await run(WAN,'generate_video',[start,motion.slice(0,1200),6,VNEG,5,1,1,42,true],'gen/'+id+'.mp4')).saved; | |
| say2('Adding the sound…'); | |
| let out=vid; | |
| try{out=(await run(MMA,'video_to_audio',[FD(vid,vid),soundOf(text),'music, speech, talking, distortion',-1,25,4.5,5],'gen/'+id+'-s.mp4')).saved}catch(_){} | |
| if(line){ | |
| say2('Recording her line…'); | |
| try{ | |
| const v=await (await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'speaktest',voice:'wheelie',text:line,save:id})})).json(); | |
| if(v.ok)out=(await run(MIX,'mix',[out,PUB+'gen/ari/'+id+'.wav',0.5,1.6],'gen/'+id+'-final.mp4')).saved; | |
| }catch(_){} | |
| } | |
| c.innerHTML='<video src="'+out+'" crossorigin="anonymous" playsinline muted loop autoplay></video>'; | |
| c.onclick=()=>open(out,text); | |
| }catch(_){c.innerHTML='<div class="busy">That one didn\'t come through. Tap to run it again.</div>';c.onclick=()=>{c.remove();make(text,opt)}} | |
| } | |
| function open(url,text){last=url;lastText=text||'';const f=$('#full'),v=f.querySelector('video');v.crossOrigin='anonymous';v.src=url;v.muted=false;f.style.display='flex';v.play()} | |
| $('#full .x').onclick=()=>{const f=$('#full');f.querySelector('video').pause();f.style.display='none'}; | |
| $('#sv').onclick=()=>{window.open(last+'?download=wheelie.mp4','_blank')}; | |
| $('#ext').onclick=async()=>{const v=$('#full video');v.pause(); | |
| try{if(v.duration)v.currentTime=Math.max(0,v.duration-0.05);await new Promise(r=>setTimeout(r,350));const cv=document.createElement('canvas');cv.width=v.videoWidth;cv.height=v.videoHeight;cv.getContext('2d').drawImage(v,0,0);const b=await new Promise(r=>cv.toBlob(r,'image/png'));$('#full .x').onclick();make(p.value.trim()||lastText||'She keeps going, same scene, natural continuation',{frame:b});p.value='';p.oninput()}catch(_){$('#full .x').onclick()}}; | |
| $('#go').onclick=()=>{const t=p.value.trim();if(!t&&!img)return;make(t||'She comes alive with cinematic motion');p.value='';p.oninput()}; | |
| </script><script type="text/plain"> | |
| const ENG='https://wayed11-wheelie-ovi.hf.space'; | |
| const STYLE=' Vibrant 3D animated cartoon style, cinematic lighting, smooth expressive motion, rich synced sound effects and music.'; | |
| const MODES={normal:'',fun:' Playful, bouncy, funny, energetic.',cine:' Epic cinematic camera move, dramatic lighting, film score.',custom:''}; | |
| let mode='normal',img=null,wide=false,last=null; | |
| const $=s=>document.querySelector(s),feed=$('#feed'); | |
| document.querySelectorAll('.modes button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.modes button').forEach(x=>x.classList.remove('on'));b.classList.add('on');mode=b.dataset.m}); | |
| $('#tW').onclick=()=>{wide=!wide;$('#tW').textContent=wide?'16:9':'9:16'}; | |
| $('#pic').onclick=()=>$('#file').click(); | |
| $('#file').onchange=e=>{const f=e.target.files[0];if(!f)return;img=f;$('#thumb img').src=URL.createObjectURL(f);$('#thumb').style.display='flex'}; | |
| $('#rmImg').onclick=()=>{img=null;$('#thumb').style.display='none';$('#file').value=''}; | |
| const p=$('#p');p.oninput=()=>{p.style.height='auto';p.style.height=p.scrollHeight+'px'}; | |
| const SR=window.SpeechRecognition||window.webkitSpeechRecognition;let rec=null; | |
| $('#mic').onclick=()=>{if(!SR){p.focus();return}if(rec){rec.stop();return}rec=new SR();rec.lang='en-US';rec.interimResults=true;$('#mic').classList.add('rec'); | |
| rec.onresult=e=>{p.value=[...e.results].map(r=>r[0].transcript).join('');p.oninput()};rec.onend=()=>{$('#mic').classList.remove('rec');rec=null};rec.start()}; | |
| async function upload(blob,name){const fd=new FormData();fd.append('files',blob,name||'frame.png');const r=await fetch(ENG+'/gradio_api/upload',{method:'POST',body:fd});const j=await r.json();return {path:j[0],meta:{_type:'gradio.FileData'},orig_name:name||'frame.png'}} | |
| function card(){const e=feed.querySelector('.empty');if(e)e.remove();const c=document.createElement('div');c.className='card';c.innerHTML='<div class="busy"><div class="ring"></div><span>Wheelie is making it…</span></div>';feed.prepend(c);return c} | |
| async function make(text,startBlob){ | |
| const c=card(),msg=c.querySelector('span'); | |
| try{ | |
| const ff=startBlob?await upload(startBlob,'start.png'):null; | |
| const prompt=text+(mode==='custom'?'':MODES[mode]+STYLE); | |
| const h=wide?512:768,w=wide?768:512; | |
| const r=await fetch(ENG+'/gradio_api/call/generate_scene',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({data:[prompt+' <AUDCAP>Clear synced voices, sound effects and music that match the scene.<ENDAUDCAP>',30,ff]})}); | |
| const {event_id}=await r.json();if(!event_id)throw 0; | |
| msg.textContent='Rendering video and sound…'; | |
| const s=await fetch(ENG+'/gradio_api/call/generate_scene/'+event_id);const t=await s.text(); | |
| const m=t.match(/event: complete\s*\ndata: (.*)/);if(!m)throw 0; | |
| const d=JSON.parse(m[1]);const v=(Array.isArray(d)?d[0]:d);const url=(v&&(v.url||(v.video&&v.video.url)))||''; | |
| if(!url)throw 0; | |
| c.innerHTML='<video src="'+url+'" playsinline muted loop autoplay></video>'; | |
| c.onclick=()=>open(url); | |
| }catch(_){c.innerHTML='<div class="busy">That one didn\'t come through. Tap to try again.</div>';c.onclick=()=>{c.remove();make(text,startBlob)}} | |
| } | |
| function open(url){last=url;const f=$('#full'),v=f.querySelector('video');v.src=url;v.muted=false;f.style.display='flex';v.play()} | |
| $('#full .x').onclick=()=>{const f=$('#full');f.querySelector('video').pause();f.style.display='none'}; | |
| $('#sv').onclick=()=>{const a=document.createElement('a');a.href=last;a.download='wheelie.mp4';a.click()}; | |
| $('#ext').onclick=async()=>{const v=$('#full video');v.pause();const cv=document.createElement('canvas');cv.width=v.videoWidth;cv.height=v.videoHeight; | |
| try{cv.getContext('2d').drawImage(v,0,0);const b=await new Promise(r=>cv.toBlob(r,'image/png'));$('#full .x').onclick();make(p.value.trim()||'Continue the scene naturally',b)}catch(_){$('#full .x').onclick()}}; | |
| $('#go').onclick=()=>{const t=p.value.trim();if(!t&&!img)return;make(t||'Make this image come alive with cinematic motion',img);p.value='';p.oninput()}; | |
| </script></body></html> |