Files
TiktokUI/scripts/generate-videos.mjs
hexin-codex-bot c9606a6d0f
Build and deploy Docker / deploy (push) Failing after 7m7s
feat: add generated local videos and portable build setup
2026-10-08 14:18:38 +08:00

38 lines
4.1 KiB
JavaScript

import { createCanvas } from '@napi-rs/canvas';
import installer from '@ffmpeg-installer/ffmpeg';
import { spawn } from 'node:child_process';
import { mkdir, writeFile } from 'node:fs/promises';
import { once } from 'node:events';
const ffmpeg=installer.path;
const W=960,H=960,FPS=24,SECONDS=8;
await mkdir('public/videos',{recursive:true});
const canvas=createCanvas(W,H), c=canvas.getContext('2d');
function gradient(top,bottom) {const g=c.createLinearGradient(0,0,0,H);g.addColorStop(0,top);g.addColorStop(1,bottom);c.fillStyle=g;c.fillRect(0,0,W,H);}
function hill(y,color,phase,height){c.beginPath();c.moveTo(0,H);for(let x=0;x<=W;x+=4){c.lineTo(x,y+Math.sin(x/190+phase)*height+Math.cos(x/77+phase)*height*.22);}c.lineTo(W,H);c.fillStyle=color;c.fill();}
function draw(scene,t){
if(scene==='afterglow'){
gradient('#273d65','#f9b990');const glow=c.createRadialGradient(560,335,10,560,335,370);glow.addColorStop(0,'#ffd9a4aa');glow.addColorStop(1,'#ffd9a400');c.fillStyle=glow;c.fillRect(0,0,W,H);
c.fillStyle='#ffe1ad';c.beginPath();c.arc(560,338+Math.sin(t)*3,68,0,Math.PI*2);c.fill();
for(let i=0;i<7;i++)hill(490+i*66,['#b798aa','#947f9d','#706d8e','#52617a','#3c5267','#2c4255','#1b303e'][i],i*.8+Math.sin(t)*.025,42+i*10);
c.strokeStyle='#262f49';c.lineWidth=2;for(let i=0;i<6;i++){let x=130+i*25+Math.sin(t)*28,y=300+Math.sin(i)*14;c.beginPath();c.moveTo(x-6,y);c.quadraticCurveTo(x,y-6,x+6,y);c.quadraticCurveTo(x+12,y-5,x+18,y);c.stroke();}
} else if(scene==='ocean'){
gradient('#456f8a','#f5d4b5');c.fillStyle='#f9e2bf';c.beginPath();c.arc(660,260,44,0,7);c.fill();
const sea=c.createLinearGradient(0,400,0,H);sea.addColorStop(0,'#438597');sea.addColorStop(1,'#092e44');c.fillStyle=sea;c.fillRect(0,400,W,560);
for(let row=0;row<75;row++){let y=405+row*7.7;c.beginPath();for(let x=0;x<=W;x+=6)c.lineTo(x,y+Math.sin(x/(60-row*.5)+t*2+row*.55)*(2+row*.10));c.strokeStyle=`rgba(174,229,221,${.12+row/280})`;c.lineWidth=1+row/60;c.stroke();}
for(let i=0;i<90;i++){let y=410+i*5,x=660+Math.sin(i*4+t)*i*.7;c.fillStyle=`rgba(255,219,158,${.28-i*.002})`;c.fillRect(x,y,10+i*.8,2);}
hill(860,'#172e3c',2,60);hill(930,'#10232e',4,65);
} else {
gradient('#0c112a','#4f315d');c.fillStyle='#d9c9ff';c.beginPath();c.arc(700,210,48,0,7);c.fill();
for(let i=0;i<90;i++){let x=(i*137.3)%W,y=(i*51.7)%450;c.globalAlpha=.3+.5*Math.abs(Math.sin(t+i));c.fillRect(x,y,2,2);}c.globalAlpha=1;
for(let i=0;i<24;i++){let x=i*44-30,h=80+((i*137)%210);c.fillStyle=i%2?'#171c38':'#1b2343';c.fillRect(x,620-h,40,h);for(let j=0;j<9;j++)for(let k=0;k<3;k++){if((i+j+k)%3){c.fillStyle=(i+j)%2?'#ce84bc':'#63b9c6';c.globalAlpha=.45;c.fillRect(x+7+k*10,625-h+j*22,3,7);}}}c.globalAlpha=1;
c.fillStyle='#0d1326';c.fillRect(0,620,W,340);c.strokeStyle='#734277';for(let i=-10;i<=10;i++){c.beginPath();c.moveTo(480+i*9,620);c.lineTo(480+i*150,H);c.stroke();}for(let i=0;i<18;i++){let p=((i/18+t/(Math.PI*2))%1);let y=620+p*p*340;c.strokeStyle=`rgba(176,93,164,${p*.6})`;c.beginPath();c.moveTo(0,y);c.lineTo(W,y);c.stroke();}
c.strokeStyle='#ff709d';c.lineWidth=3;c.beginPath();c.moveTo(440,620);c.lineTo(135,H);c.stroke();c.strokeStyle='#6cdbdf';c.beginPath();c.moveTo(520,620);c.lineTo(825,H);c.stroke();
}
}
for(const scene of ['afterglow','ocean','night']){
const child=spawn(ffmpeg,['-y','-f','image2pipe','-vcodec','png','-r',String(FPS),'-i','pipe:0','-f','lavfi','-i',`sine=frequency=${scene==='afterglow'?220:scene==='ocean'?174:261}:sample_rate=44100`,'-t',String(SECONDS),'-af','volume=0.025,afade=t=in:d=1,afade=t=out:st=7:d=1','-c:v','libx264','-preset','fast','-crf','24','-pix_fmt','yuv420p','-c:a','aac','-movflags','+faststart',`public/videos/${scene}.mp4`],{stdio:['pipe','ignore','pipe']});
let errors='';child.stderr.on('data',d=>errors+=d);const finished=once(child,'close');
for(let frame=0;frame<FPS*SECONDS;frame++){draw(scene,frame/(FPS*SECONDS)*Math.PI*2);if(frame===0)await writeFile(`public/videos/${scene}.jpg`,canvas.toBuffer('image/jpeg'));if(!child.stdin.write(canvas.toBuffer('image/png')))await once(child.stdin,'drain');}
child.stdin.end();const [code]=await finished;if(code!==0)throw new Error(errors);console.log(`Generated ${scene}.mp4`);
}