// Browser-side preprocessing adapted from the Valen website's media.ts. function once(target, event, signal) { return new Promise((resolve, reject) => { const finish = error => { clearTimeout(timer);target.removeEventListener(event, success);target.removeEventListener('error', failure); signal.removeEventListener('abort', abort);error ? reject(error) : resolve(); }; const success = () => finish(); const failure = () => finish(new Error('image')); const abort = () => finish(new DOMException('Aborted','AbortError')); const timer = setTimeout(failure,20000); target.addEventListener(event,success,{once:true});target.addEventListener('error',failure,{once:true}); signal.addEventListener('abort',abort,{once:true});if(signal.aborted)abort(); }); } function jpeg(source, width, height) { if(!width||!height)throw new Error('image'); const scale=Math.min(1,1536/Math.max(width,height)); const canvas=document.createElement('canvas');canvas.width=Math.max(1,Math.round(width*scale));canvas.height=Math.max(1,Math.round(height*scale)); const ctx=canvas.getContext('2d');if(!ctx)throw new Error('image'); ctx.fillStyle='#fff';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.drawImage(source,0,0,canvas.width,canvas.height); return canvas.toDataURL('image/jpeg',.85); } export async function readMedia(file, signal) { const videoType=['video/mp4','video/webm'].includes(file.type); if(!videoType&&!['image/jpeg','image/png','image/webp'].includes(file.type))throw new Error('image'); if(file.size>(videoType?20:3)*1000000)throw new Error('size'); const url=URL.createObjectURL(file); try { if(!videoType) { const image=new Image();const loaded=once(image,'load',signal);image.src=url;await loaded; return {kind:'image',name:file.name,image:jpeg(image,image.naturalWidth,image.naturalHeight),objectUrl:url}; } const video=document.createElement('video');video.muted=true;video.playsInline=true;video.preload='auto'; try { const loaded=once(video,'loadeddata',signal);video.src=url;await loaded; if(!Number.isFinite(video.duration)||video.duration<=0)throw new Error('image'); if(video.duration/2>.01){const seeked=once(video,'seeked',signal);video.currentTime=video.duration/2;await seeked;} return {kind:'video',name:file.name,image:jpeg(video,video.videoWidth,video.videoHeight),video:url,objectUrl:url}; } finally {video.removeAttribute('src');video.load();} } catch(error) {URL.revokeObjectURL(url);throw error;} }