UNPKG

chrome-filmstrip

Version:

Generate a combined filmstrip video from multiple Chrome performance profiles

4 lines 5.19 kB
import { createRequire } from 'module'; const require=createRequire(import.meta.url); import{readFile as G}from"node:fs/promises";import{basename as J}from"node:path";import{createFFmpeg as M}from"@ffmpeg/ffmpeg";import b from"ora";var y=t=>t.charAt(0).toUpperCase()+t.substring(1),O=b({color:"cyan",spinner:"dots"});async function h({name:t,options:e},s){O.start(y(t)),e.onProgress({task:t,event:"start",progress:0});try{return await s()}finally{O.succeed(`Finished ${t}`),e.onProgress({task:t,event:"end",progress:100})}}var w,c=()=>w,S=t=>h({name:"loading ffmpeg",options:t},async()=>(w=M({log:!1}),await w.load(),c()));import{URL as C}from"node:url";var P=["fp","fcp","lcp","interactive"];function _(t){let s=t.traceEvents.find(p=>p.name==="navigationStart").args.data.documentLoaderURL,n=t.traceEvents.find(p=>p.name==="TracingStartedInBrowser").args.data.frames[0].url,o=new C(s||n);return{index:t.index,filename:t.filename,source:t.metadata.source,useCase:t.metadata.useCase,networkThrottling:t.metadata.networkThrottling,cpuThrottling:t.metadata.cpuThrottling,"url.href":o.href,"url.origin":o.origin,"url.hostname":o.hostname,"url.pathname":o.pathname,"url.search":o.search,"url.hash":o.hash,"url.port":o.port,"url.protocol":o.protocol}}function D(t,e){let s=t;for(let[r,n]of Object.entries(e))s=s.replaceAll(`{${r}}`,String(n));return s.replaceAll(":","\\:")}function $({title:t,metrics:e,profiles:s}){return s.map(r=>{let n=r.traceEvents,o=0,p=n.filter(i=>i.name==="Screenshot"),g=p.map((i,a)=>{let l=Buffer.from(i.args.snapshot,"base64"),F=a>0?p[a-1]:void 0,d=F?Math.round((i.ts-F.ts)/1e3):0;return o+=d,{data:l,durationMs:d,totalMs:o}}),u=n.find(i=>i.name==="navigationStart"),f=[];if(e.includes("fp")){let i=n.find(l=>l.name==="firstPaint"),a={ts:i.ts,name:"FP",value:(i.ts-u.ts)/1e3};f.push(a)}if(e.includes("fcp")){let i=n.find(l=>l.name==="firstContentfulPaint"),a={ts:i.ts,name:"FCP",value:(i.ts-u.ts)/1e3};f.push(a)}if(e.includes("lcp")){let i;for(let a=n.length-1;a>=0;--a)if(n[a].name==="largestContentfulPaint::Candidate"){let l=n[a];i={ts:l.ts,name:"LCP",value:(l.ts-u.ts)/1e3,type:l.args.data.type,size:l.args.data.size}}f.push(i)}if(e.includes("interactive")){let i=n.filter(a=>a.name==="domInteractive").map(a=>({ts:a.ts,name:"Interactive",value:(a.ts-u.ts)/1e3}))[0];f.push(i)}return{title:D(t,_(r)),frames:g,metrics:f.sort((i,a)=>i.value-a.value)}})}import{fetchFile as A}from"@ffmpeg/ffmpeg";import{resolve as U}from"path";import{fileURLToPath as I}from"url";var x="concat.txt",E="OpenSans.ttf",L=60,k="black",z="white",N=500,B=24;async function V(t,e){let s=[],r=e.scale*N,n=Math.round(e.scale*B);for await(let[o,p]of t.entries())await h({name:`rendering filmstrip #${o+1}`,options:e},async()=>{let g=[];for(let m of p.frames){let T=m.totalMs+".png";c().FS("writeFile",T,m.data),m.durationMs>0&&g.push(`duration ${m.durationMs}`),g.push(`file ${T}`)}c().FS("writeFile",x,g.join(` `));let u=`fontsize=${n}:fontcolor=${z}:fontfile=${E}`,f=e.metrics.length+1,i=n*2,a=n*2,l=n*(f+1),F=["format=yuva420p","settb=1/1000","setpts=PTS/1000",`framerate=fps=${L}:scene=0`,`scale=${`-1:${r}`}`,`pad=${`iw+${i}:ih+${a+l}`}:-1:${a}:color=${k}`,`drawtext=text='${p.title}':x=(w-tw)/2:y=(${a}-lh)/2:${u}`,`drawtext=text='%{pts\\:hms}':x=(w-tw)/2:y=${r+a+n/2}:${u}`,...p.metrics.map((m,T)=>`drawtext=text='${m.name}\\: ${m.value.toFixed(1)} ms':x=(w-tw)/2:y=${r+a+n/2+n*(T+1)}:enable='gte(t, ${m.value/1e3})':${u}`)],d=o+".mp4";await c().run("-f","concat","-i",x,"-vf",F.join(","),d),s.push(d)});return s}var j=(t,e)=>h({name:"rendering collage",options:e},async()=>{let s=[...t.flatMap(n=>["-i",n])];t.length>1&&s.push("-filter_complex",`hstack=inputs=${t.length}`);let r="output.mp4";return s.push(r),await c().run(...s),e.speed!==1&&await c().run("-i",r,"-vf",`setpts=PTS/${e.speed}`,r="output-new-speed.mp4"),e.format==="gif"?(await c().run("-i",r,"-vf","fps=30,scale=-1:-1:flags=lanczos,palettegen","palette.png"),await c().run("-i",r,"-i","palette.png","-lavfi","fps=30,scale=-1:-1:flags=lanczos[x];[x][1:v]paletteuse",r="output-final."+e.format)):await c().run("-i",r,r="output-final."+e.format),c().FS("readFile",r)});async function R(t,e){c().FS("writeFile",E,await A(U(I(import.meta.url),`../../static/${E}`)));let s=await V(t,e);return j(s,e)}var v={debug:!1,speed:1,scale:1,title:"Profile {index}: {url.hostname}{url.pathname}",onProgress:()=>{}};async function X(t){let e=await Promise.all(t.inputs.map(async(r,n)=>{let o=JSON.parse(await G(r,{encoding:"utf-8"}));return{index:n,filename:J(r),traceEvents:o.traceEvents??o,metadata:o.metadata??{source:"DevTools",useCase:"",networkThrottling:"",cpuThrottling:""}}})),s;return t.metrics===!0?s=P:t.metrics===!1?s=[]:s=t.metrics.filter(r=>P.includes(r)),{profiles:e,metrics:s,format:t.output.split(".").pop(),output:t.output,debug:t.debug??v.debug,onProgress:t.onProgress??v.onProgress,speed:t.speed??v.speed,scale:t.scale??v.scale,title:t.title??v.title}}async function mt(t){let e=await X(t);if(e.debug){let{profiles:n,...o}=e;console.debug(`Resolved Options: ${JSON.stringify(o,null,2)}`)}(await S(e)).setLogging(e.debug);let r=$(e);return R(r,e)}export{v as a,mt as b}; //# sourceMappingURL=chunk-SRYRJQGR.js.map