PageSourceSearch

https://www.yunso.net/assets/xyso/tools/apps/shared/processors-media.js

js yunso.net collected 2026-09-30 04:54:36 UTC 19,860 bytes, 49 lines download raw bytes

1(function(){
2  'use strict';
3  const U=window.LocalFlowProcessorUtils,P=window.LocalFlowProcessors.handlers;
4  const out=(text,name,extra)=>U.result(text,name,extra);
5  const files=env=>U.ensureFiles(env.state);
6  const imageFiles=env=>files(env).filter(file=>file.type.startsWith('image/')||/\.(?:png|jpe?g|webp|gif|bmp|heic|heif)$/i.test(file.name));
7  const mimeFrom=mode=>mode==='jpg'||mode==='jpeg'?'image/jpeg':mode==='webp'?'image/webp':'image/png';
8  const canvasResult=async(canvas,name,type='image/png',quality=.9)=>{const blob=await U.canvasBlob(canvas,type,quality);return U.blob(blob,name,'图片处理完成。',{previewUrl:canvas.toDataURL(type,quality)});};
9  const processImages=async(env,transform,name='images.zip')=>{const list=imageFiles(env);if(!list.length)throw new Error('请先打开图片。');const entries=[];for(let i=0;i<list.length;i++){const result=await transform(list[i],i,list),ext=result.type==='image/jpeg'?'jpg':result.type==='image/webp'?'webp':'png';entries.push({name:result.name||`${list[i].name.replace(/\.[^.]+$/,'')}.${ext}`,data:result.blob});env.progress((i+1)/list.length*100,`正在处理 ${i+1}/${list.length}`);}if(entries.length===1)return U.blob(entries[0].data,entries[0].name,'图片处理完成。',{previewUrl:URL.createObjectURL(entries[0].data)});return U.zip(entries,name);};
10
11  P['image-compress']=async env=>processImages(env,async file=>{const canvas=await U.canvasFromFile(file),type=mimeFrom(env.mode==='original'?U.ext(file):env.mode),quality=Math.max(.05,Math.min(1,Number(env.option)||.78));return {blob:await U.canvasBlob(canvas,type,quality),type};},'compressed-images.zip');
12  P['image-resize']=async env=>processImages(env,async file=>{const source=await U.canvasFromFile(file),parts=String(env.option||'').split(/[x×,,\s]+/).map(Number),width=Math.max(1,parts[0]||Math.round(source.width*(Number(env.option2)||50)/100)),height=Math.max(1,parts[1]||Math.round(source.height*width/source.width)),canvas=document.createElement('canvas');canvas.width=width;canvas.height=height;canvas.getContext('2d').drawImage(source,0,0,width,height);return {blob:await U.canvasBlob(canvas,'image/png'),type:'image/png'};},'resized-images.zip');
13  P['image-background-white']=async env=>processImages(env,async file=>{const source=await U.canvasFromFile(file),canvas=document.createElement('canvas');canvas.width=source.width;canvas.height=source.height;const ctx=canvas.getContext('2d');ctx.fillStyle=env.option||'#ffffff';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.drawImage(source,0,0);const type=env.mode==='png'?'image/png':'image/jpeg';return {blob:await U.canvasBlob(canvas,type,Number(env.option2)||.92),type};},'white-background-images.zip');
14  P['image-cover-maker']=async env=>processImages(env,async file=>{const source=await U.canvasFromFile(file),ratio=env.mode==='square'?1:env.mode==='landscape'?16/9:3/4,target=String(env.option||'1200x1600').split(/[x×]/).map(Number),width=target[0]||Math.round((target[1]||1600)*ratio),height=target[1]||Math.round(width/ratio),srcRatio=source.width/source.height;let sx=0,sy=0,sw=source.width,sh=source.height;if(srcRatio>ratio){sw=source.height*ratio;sx=(source.width-sw)/2;}else{sh=source.width/ratio;sy=(source.height-sh)/2;}const canvas=document.createElement('canvas');canvas.width=width;canvas.height=height;canvas.getContext('2d').drawImage(source,sx,sy,sw,sh,0,0,width,height);return {blob:await U.canvasBlob(canvas,'image/jpeg',Number(env.option2)||.92),type:'image/jpeg'};},'cover-images.zip');
15  P['image-exif-clean']=async env=>processImages(env,async file=>{const canvas=await U.canvasFromFile(file),type=mimeFrom(env.mode);return {blob:await U.canvasBlob(canvas,type,Number(env.option)||.92),type};},'privacy-clean-images.zip');
16  P['image-nine-grid']=async env=>{const source=await U.canvasFromFile(imageFiles(env)[0]),rows=Math.max(1,Math.min(10,Number(env.option)||3)),cols=Math.max(1,Math.min(10,Number(env.option2)||3)),cellW=Math.floor(source.width/cols),cellH=Math.floor(source.height/rows),entries=[];for(let y=0;y<rows;y++)for(let x=0;x<cols;x++){const canvas=document.createElement('canvas');canvas.width=cellW;canvas.height=cellH;canvas.getContext('2d').drawImage(source,x*cellW,y*cellH,cellW,cellH,0,0,cellW,cellH);entries.push({name:`tile-${y+1}-${x+1}.png`,data:await U.canvasBlob(canvas,'image/png')});}return U.zip(entries,'image-grid.zip');};
17  P['image-watermark']=async env=>processImages(env,async file=>{const canvas=await U.canvasFromFile(file),ctx=canvas.getContext('2d'),text=env.option||'LocalFlow',size=Math.max(12,Math.round(canvas.width/25));ctx.save();ctx.globalAlpha=Math.max(.05,Math.min(1,Number(env.option2)||.35));ctx.fillStyle='#ffffff';ctx.strokeStyle='rgba(0,0,0,.45)';ctx.lineWidth=Math.max(1,size/18);ctx.font=`600 ${size}px system-ui`;if(env.mode==='tile'){for(let y=size;y<canvas.height;y+=size*4)for(let x=0;x<canvas.width;x+=size*8){ctx.save();ctx.translate(x,y);ctx.rotate(-Math.PI/8);ctx.strokeText(text,0,0);ctx.fillText(text,0,0);
17ctx.restore();}}else{const width=ctx.measureText(text).width,x=canvas.width-width-size,y=canvas.height-size;ctx.strokeText(text,x,y);ctx.fillText(text,x,y);}ctx.restore();return {blob:await U.canvasBlob(canvas,'image/png'),type:'image/png'};},'watermarked-images.zip');
18  P['image-target-kb']=async env=>processImages(env,async file=>{let canvas=await U.canvasFromFile(file),target=Math.max(10,Number(env.option)||200)*1024,quality=.92,blob=await U.canvasBlob(canvas,'image/jpeg',quality);for(let i=0;i<12&&blob.size>target;i++){quality=Math.max(.25,quality*.82);if(quality<=.3&&blob.size>target){const scale=Math.sqrt(target/blob.size)*.96,next=document.createElement('canvas');next.width=Math.max(1,Math.round(canvas.width*scale));next.height=Math.max(1,Math.round(canvas.height*scale));next.getContext('2d').drawImage(canvas,0,0,next.width,next.height);canvas=next;}blob=await U.canvasBlob(canvas,'image/jpeg',quality);}return {blob,type:'image/jpeg'};},'target-size-images.zip');
19  P['image-color-palette']=async env=>{const canvas=await U.canvasFromFile(imageFiles(env)[0]),ctx=canvas.getContext('2d'),sample=document.createElement('canvas');sample.width=64;sample.height=64;sample.getContext('2d').drawImage(canvas,0,0,64,64);const data=sample.getContext('2d').getImageData(0,0,64,64).data,map={};for(let i=0;i<data.length;i+=16){const key=[data[i],data[i+1],data[i+2]].map(x=>Math.round(x/32)*32>255?255:Math.round(x/32)*32).join(',');map[key]=(map[key]||0)+1;}const count=Math.max(2,Math.min(32,Number(env.option)||8)),colors=Object.entries(map).sort((a,b)=>b[1]-a[1]).slice(0,count).map(([key,n])=>{const rgb=key.split(',').map(Number),hex='#'+rgb.map(x=>x.toString(16).padStart(2,'0')).join('');return {hex,n};});return out(colors.map(x=>x.hex).join('\n'),'palette.txt',{html:`<div class="lfx-palette">${colors.map(x=>`<div style="background:${x.hex}"><span>${x.hex}</span></div>`).join('')}</div>`});};
20  P['image-convert']=async env=>processImages(env,async file=>{const canvas=await U.canvasFromFile(file),type=mimeFrom(env.mode),blob=await U.canvasBlob(canvas,type,Number(env.option)||.9);return {blob,type};},'converted-images.zip');
21  P['image-crop']=async env=>processImages(env,async file=>{const source=await U.canvasFromFile(file),[x=0,y=0,w=source.width,h=source.height]=String(env.option||'').split(/[,,\s]+/).map(Number),canvas=document.createElement('canvas');canvas.width=Math.max(1,Math.min(source.width-x,w));canvas.height=Math.max(1,Math.min(source.height-y,h));canvas.getContext('2d').drawImage(source,x,y,canvas.width,canvas.height,0,0,canvas.width,canvas.height);return {blob:await U.canvasBlob(canvas,mimeFrom(env.mode)),type:mimeFrom(env.mode)};},'cropped-images.zip');
22  P['image-exif']=async env=>{if(env.mode==='clean')return P['image-exif-clean'](env);await U.load('Exifr');const rows=[];for(const file of imageFiles(env)){const data=await window.exifr.parse(file,{gps:true,tiff:true,exif:true,icc:false,iptc:true})||{};rows.push({file:U.path(file),metadata:data});}return out(JSON.stringify(rows,null,2),'image-metadata.json');};
23  P['image-heic']=async env=>{await U.load('Heic2Any');const entries=[];for(const file of files(env)){const converted=await window.heic2any({blob:file,toType:env.mode==='png'?'image/png':'image/jpeg',quality:Number(env.option)||.9}),list=Array.isArray(converted)?converted:[converted];list.forEach((blob,i)=>entries.push({name:`${file.name.replace(/\.[^.]+$/,'')}${list.length>1?'-'+(i+1):''}.${env.mode==='png'?'png':'jpg'}`,data:blob}));}return entries.length===1?U.blob(entries[0].data,entries[0].name):U.zip(entries,'converted-heic.zip');};
24  P['image-id-photo']=async env=>processImages(env,async file=>{const source=await U.canvasFromFile(file),size=env.mode==='one-inch'?[295,413]:env.mode==='two-inch'?[413,626]:String(env.option||'295x413').split(/[x×]/).map(Number),width=size[0]||295,height=size[1]||413,ratio=width/height,srcRatio=source.width/source.height;let sx=0,sy=0,sw=source.width,sh=source.height;if(srcRatio>ratio){sw=sh*ratio;sx=(source.width-sw)/2;}else{sh=sw/ratio;sy=(source.height-sh)/2;}const canvas=document.createElement('canvas');canvas.width=width;canvas.height=height;const ctx=canvas.getContext('2d');ctx.fillStyle=env.option2||'#ffffff';ctx.fillRect(0,0,width,height);ctx.drawImage(source,sx,sy,sw,sh,0,0,width,height);return {blob:await U.canvasBlob(canvas,'image/jpeg',.94),type:'image/jpeg'};},'id-photos.zip');
25  P['image-ocr']=async env=>{await U.load('Tesseract');const rows=[];for(let i=0;i<imageFiles(env).length;i++){const file=imageFiles(env)[i],result=await window.Tesseract.recognize(file,env.option||'chi_sim+eng',{logger:m=>{if(m.progress)env.progress(((i+m.progress)/imageFiles(env).length)*100,m.status||'OCR');}}),text=result.data.text.trim();rows.push(env.option2==='text'?text:`--- ${file.name} ---\n${text}`);}return out(rows.join('\n\n'),'ocr-text.txt');};
26  P['image-scan-correct']=async env=>processImages(env,async file=>{const source=await U.canvasFromFile(file),angle=(Number(env.option)||0)*Math.PI/180,cos=Math.abs(Math.cos(angle)),sin=Math.abs(Math.sin(angle)),canvas=document.createElement('canvas');canvas.width=Math.ceil(source.width*cos+source.height*sin);canvas.height=Math.ceil(source.width*sin+source.height*cos);const ctx=canvas.getContext('2d');ctx.fillStyle='#fff';ctx.fillRect(0,0,canvas.width,canvas.height);ctx.translate(canvas.width/2,canvas.height/2);ctx.rotate(angle);
26ctx.filter=`contrast(${Math.max(50,Number(env.option2)||125)}%) grayscale(${env.mode==='gray'?100:0}%)`;ctx.drawImage(source,-source.width/2,-source.height/2);return {blob:await U.canvasBlob(canvas,'image/jpeg',.94),type:'image/jpeg'};},'corrected-scans.zip');
27  P['image-stitch']=async env=>{const list=imageFiles(env),canvases=[];for(const file of list)canvases.push(await U.canvasFromFile(file));const vertical=env.mode!=='horizontal',width=vertical?Math.max(...canvases.map(c=>c.width)):canvases.reduce((n,c)=>n+c.width,0),height=vertical?canvases.reduce((n,c)=>n+c.height,0):Math.max(...canvases.map(c=>c.height)),canvas=document.createElement('canvas');canvas.width=width;canvas.height=height;const ctx=canvas.getContext('2d');ctx.fillStyle=env.option||'#ffffff';ctx.fillRect(0,0,width,height);let offset=0;canvases.forEach(c=>{ctx.drawImage(c,vertical?0:offset,vertical?offset:0);offset+=vertical?c.height:c.width;});return canvasResult(canvas,'stitched-image.png');};
28  P['image-sprite']=async env=>{const list=imageFiles(env),canvases=[];for(const file of list)canvases.push(await U.canvasFromFile(file));const cols=Math.max(1,Number(env.option)||Math.ceil(Math.sqrt(canvases.length))),cellW=Math.max(...canvases.map(c=>c.width)),cellH=Math.max(...canvases.map(c=>c.height)),rows=Math.ceil(canvases.length/cols),canvas=document.createElement('canvas');canvas.width=cellW*cols;canvas.height=cellH*rows;const ctx=canvas.getContext('2d'),css=[];ctx.clearRect(0,0,canvas.width,canvas.height);canvases.forEach((c,i)=>{const x=i%cols*cellW,y=Math.floor(i/cols)*cellH;ctx.drawImage(c,x,y);css.push(`.sprite-${i+1}{width:${c.width}px;height:${c.height}px;background:url(sprite.png) -${x}px -${y}px}`);});const blob=await U.canvasBlob(canvas,'image/png');return U.zip([{name:'sprite.png',data:blob},{name:'sprite.css',data:css.join('\n')}],'image-sprite.zip');};
29
30  P['qr-tool']=async env=>{await U.load('QRCode');const size=Math.max(128,Math.min(1024,Number(env.option)||280));let value=String(env.state.input||'').trim();if(env.mode==='wifi')value=`WIFI:T:WPA;S:${value};P:${env.option2};;`;if(env.mode==='email')value=`mailto:${value}`;if(!value)throw new Error('请输入二维码内容。');const box=document.createElement('div');new window.QRCode(box,{text:value,width:size,height:size,correctLevel:window.QRCode.CorrectLevel?.M});await new Promise(resolve=>setTimeout(resolve,50));const canvas=box.querySelector('canvas'),img=box.querySelector('img'),blob=canvas?await U.canvasBlob(canvas,'image/png'):await(await fetch(img.src)).blob();return U.blob(blob,'qrcode.png',value,{previewUrl:canvas?canvas.toDataURL('image/png'):img.src});};
31  P['barcode-generator']=async env=>{await U.load('BwipJs');const canvas=document.createElement('canvas');window.bwipjs.toCanvas(canvas,{bcid:env.mode||'code128',text:U.ensureInput(env.state),scale:Math.max(1,Number(env.option)||3),height:Math.max(5,Number(env.option2)||12),includetext:true,textxalign:'center'});return canvasResult(canvas,'barcode.png');};
32  const scanCode=async env=>{await U.load('ZXing');const file=imageFiles(env)[0],url=URL.createObjectURL(file),img=new Image();img.src=url;await img.decode();try{const reader=new window.ZXing.BrowserMultiFormatReader(),result=await reader.decodeFromImageElement(img);return out(JSON.stringify({text:result.getText(),format:String(result.getBarcodeFormat()),file:file.name},null,2),'code-result.json');}finally{URL.revokeObjectURL(url);}};
33  P['qr-reader']=scanCode;P['image-barcode-scan']=scanCode;
34
35  const encodeWav=(buffer,start=0,end=buffer.length,effects={})=>{const channels=buffer.numberOfChannels,rate=buffer.sampleRate,length=end-start,bytes=44+length*channels*2,array=new ArrayBuffer(bytes),view=new DataView(array),write=(offset,text)=>[...text].forEach((ch,i)=>view.setUint8(offset+i,ch.charCodeAt(0)));let peak=0;for(let i=start;i<end;i++)for(let c=0;c<channels;c++)peak=Math.max(peak,Math.abs(buffer.getChannelData(c)[i]));const gain=(effects.normalize&&peak>.000001?Math.min(4,.98/peak):1)*Math.max(0,Math.min(2,Number(effects.volume)||1)),fadeIn=Math.max(0,Math.min(length,Math.round((Number(effects.fadeIn)||0)*rate))),fadeOut=Math.max(0,Math.min(length,Math.round((Number(effects.fadeOut)||0)*rate)));write(0,'RIFF');view.setUint32(4,bytes-8,true);write(8,'WAVE');write(12,'fmt ');view.setUint32(16,16,true);view.setUint16(20,1,true);view.setUint16(22,channels,true);view.setUint32(24,rate,true);view.setUint32(28,rate*channels*2,true);view.setUint16(32,channels*2,true);view.setUint16(34,16,true);write(36,'data');view.setUint32(40,length*channels*2,true);let offset=44;for(let i=0;i<length;i++){let envelope=1;if(fadeIn)envelope=Math.min(envelope,(i+1)/fadeIn);if(fadeOut)envelope=Math.min(envelope,(length-i)/fadeOut);for(let c=0;c<channels;c++){const sample=Math.max(-1,Math.min(1,buffer.getChannelData(c)[start+i]*gain*envelope));view.setInt16(offset,sample<0?sample*0x8000:sample*0x7fff,true);offset+=2;}}return new Blob([array],{type:'audio/wav'});};
36  P['audio-trim']=async env=>{const file=files(env)[0],context=new AudioContext(),buffer=await context.decodeAudioData(await file.arrayBuffer()),start=Math.max(0,Number(env.option)||0),end=Math.min(buffer.duration,Number(env.option2)||buffer.duration),effects=(()=>{try{return JSON.parse(env.option3||'{}')}catch{return {}}})();if(end<=start)throw new Error('结束时间必须大于开始时间。');const blob=encodeWav(buffer,Math.floor(start*buffer.sampleRate),Math.floor(end*buffer.sampleRate),effects);await context.close();return U.blob(blob,`${file.name.replace(/\.[^.]+$/,'')}-enhanced.wav`,`${start.toFixed(2)}–${end.toFixed(2)} 秒 · ${effects.normalize?'已标准化 · ':''}WAV 输出。`);};
37  const recorder=async(env,display)=>{const state=env.state;if(env.mode==='start'||env.mode==='record'){if(state.recorder?.state==='recording')throw new Error('正在录制。');if(!navigator.mediaDevices?.getDisplayMedia)throw new Error('当前浏览器不支持屏幕捕获。');const withAudio=env.option!=='no-audio',stream=display?await navigator.mediaDevices.getDisplayMedia({video:true,audio:withAudio}):await navigator.mediaDevices.getUserMedia({audio:true}
37),chunks=[],mime=display?(withAudio&&MediaRecorder.isTypeSupported('video/webm;codecs=vp9,opus')?'video/webm;codecs=vp9,opus':'video/webm'):'audio/webm';if(!window.MediaRecorder||!MediaRecorder.isTypeSupported(mime)){stream.getTracks().forEach(track=>track.stop());throw new Error('当前浏览器不支持可用的 WebM 编码。');}const rec=new MediaRecorder(stream,{mimeType:mime});rec.ondataavailable=e=>{if(e.data.size)chunks.push(e.data);};rec.start(500);state.recorder=rec;state.recordChunks=chunks;state.recordStream=stream;state.recordStartedAt=Date.now();return out('录制已开始。','recording-status.txt');}const rec=state.recorder;if(!rec)throw new Error('当前没有进行中的录制。');if(env.mode==='pause'){if(rec.state!=='recording')throw new Error('当前录音已经暂停。');rec.pause();return out('录音已暂停。','recording-status.txt');}if(env.mode==='resume'){if(rec.state!=='paused')throw new Error('当前录音没有处于暂停状态。');rec.resume();return out('录音已继续。','recording-status.txt');}if(rec.state!=='recording'&&rec.state!=='paused')throw new Error('当前没有进行中的录制。');const blob=await new Promise(resolve=>{rec.onstop=()=>resolve(new Blob(state.recordChunks,{type:rec.mimeType}));rec.stop();});state.recordStream?.getTracks().forEach(track=>track.stop());state.recorder=null;return U.blob(blob,display?'screen-recording.webm':'audio-recording.webm','录制已停止并生成新文件。');};
38  P['audio-record']=env=>recorder(env,false);P['screen-record']=env=>recorder(env,true);
39
40  function detectScanBounds(source){
41    const g=source.getContext('2d'),w=source.width,h=source.height,data=g.getImageData(0,0,w,h).data,step=Math.max(1,Math.floor(Math.max(w,h)/1000));
42    let minX=w,minY=h,maxX=-1,maxY=-1,found=0;
43    for(let y=0;y<h;y+=step)for(let x=0;x<w;x+=step){const i=(y*w+x)*4;if(data[i+3]<20)continue;const lum=.299*data[i]+.587*data[i+1]+.114*data[i+2];if(lum<242){minX=Math.min(minX,x);minY=Math.min(minY,y);maxX=Math.max(maxX,x);maxY=Math.max(maxY,y);found++;}}
44    if(!found)return {x:0,y:0,w,h};
45    const pad=Math.max(8,Math.round(Math.min(w,h)*.03)),x=Math.max(0,minX-pad),y=Math.max(0,minY-pad),right=Math.min(w,maxX+step+pad),bottom=Math.min(h,maxY+step+pad),bw=right-x,bh=bottom-y;
46    return bw/w<.25||bh/h<.25?{x:0,y:0,w,h}:{x,y,w:bw,h:bh};
47  }
48  P['image-scan-correct']=async env=>processImages(env,async file=>{const source=await U.canvasFromFile(file),auto=!!env.root?.querySelector('#iscAutoCrop')?.checked,crop=auto?detectScanBounds(source):{x:0,y:0,w:source.width,h:source.height},angle=(Number(env.option)||0)*Math.PI/180,cos=Math.abs(Math.cos(angle)),sin=Math.abs(Math.sin(angle)),canvas=document.createElement('canvas');canvas.width=Math.ceil(crop.w*cos+crop.h*sin);canvas.height=Math.ceil(crop.w*sin+crop.h*cos);const g=canvas.getContext('2d');g.fillStyle='#fff';g.fillRect(0,0,canvas.width,canvas.height);g.translate(canvas.width/2,canvas.height/2);g.rotate(angle);g.filter=`contrast(${Math.max(50,Number(env.option2)||125)}%) grayscale(${env.mode==='gray'?100:0}%)`;g.drawImage(source,crop.x,crop.y,crop.w,crop.h,-crop.w/2,-crop.h/2,crop.w,crop.h);return {blob:await U.canvasBlob(canvas,'image/jpeg',.94),type:'image/jpeg'};},'corrected-scans.zip');
49})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.