20 lines
34 KiB
JavaScript
20 lines
34 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
import{createRequire as Xu}from"node:module";var qu=Object.create;var{getPrototypeOf:Zu,defineProperty:qD,getOwnPropertyNames:Lu}=Object;var Qu=Object.prototype.hasOwnProperty;var g=(u,D,F)=>{F=u!=null?qu(Zu(u)):{};let C=D||!u||!u.__esModule?qD(F,"default",{value:u,enumerable:!0}):F;for(let E of Lu(u))if(!Qu.call(C,E))qD(C,E,{get:()=>u[E],enumerable:!0});return C};var I=(u,D)=>()=>(D||u((D={exports:{}}).exports,D),D.exports);var N=Xu(import.meta.url);var RD=I((hF,ID)=>{class GD{constructor(u,D,F){this.etaBufferLength=u||100,this.valueBuffer=[F],this.timeBuffer=[D],this.eta="0"}update(u,D,F){this.valueBuffer.push(D),this.timeBuffer.push(u),this.calculate(F-D)}getTime(){return this.eta}calculate(u){let D=this.valueBuffer.length,F=Math.min(this.etaBufferLength,D),C=this.valueBuffer[D-1]-this.valueBuffer[D-F],E=this.timeBuffer[D-1]-this.timeBuffer[D-F],A=C/E;this.valueBuffer=this.valueBuffer.slice(-this.etaBufferLength),this.timeBuffer=this.timeBuffer.slice(-this.etaBufferLength);let B=Math.ceil(u/A/1000);if(isNaN(B))this.eta="NULL";else if(!isFinite(B))this.eta="INF";else if(B>1e7)this.eta="INF";else if(B<0)this.eta=0;else this.eta=B}}ID.exports=GD});var a=I((PF,xD)=>{var k=N("readline");class $D{constructor(u){this.stream=u,this.linewrap=!0,this.dy=0}cursorSave(){if(!this.stream.isTTY)return;this.stream.write("\x1B7")}cursorRestore(){if(!this.stream.isTTY)return;this.stream.write("\x1B8")}cursor(u){if(!this.stream.isTTY)return;if(u)this.stream.write("\x1B[?25h");else this.stream.write("\x1B[?25l")}cursorTo(u=null,D=null){if(!this.stream.isTTY)return;k.cursorTo(this.stream,u,D)}cursorRelative(u=null,D=null){if(!this.stream.isTTY)return;this.dy=this.dy+D,k.moveCursor(this.stream,u,D)}cursorRelativeReset(){if(!this.stream.isTTY)return;k.moveCursor(this.stream,0,-this.dy),k.cursorTo(this.stream,0,null),this.dy=0}clearRight(){if(!this.stream.isTTY)return;k.clearLine(this.stream,1)}clearLine(){if(!this.stream.isTTY)return;k.clearLine(this.stream,0)}clearBottom(){if(!this.stream.isTTY)return;k.clearScreenDown(this.stream)}newline(){this.stream.write(`
|
||
|
|
`),this.dy++}write(u,D=!1){if(this.linewrap===!0&&D===!1)this.stream.write(u.substr(0,this.getWidth()));else this.stream.write(u)}lineWrapping(u){if(!this.stream.isTTY)return;if(this.linewrap=u,u)this.stream.write("\x1B[?7h");else this.stream.write("\x1B[?7l")}isTTY(){return this.stream.isTTY===!0}getWidth(){return this.stream.columns||(this.stream.isTTY?80:200)}}xD.exports=$D});var UD=I((fF,HD)=>{HD.exports=({onlyFirst:u=!1}={})=>{let D=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(D,u?void 0:"g")}});var _D=I((cF,WD)=>{var Vu=UD();WD.exports=(u)=>typeof u==="string"?u.replace(Vu(),""):u});var yD=I((gF,p)=>{var kD=(u)=>{if(Number.isNaN(u))return!1;if(u>=4352&&(u<=4447||u===9001||u===9002||11904<=u&&u<=12871&&u!==12351||12880<=u&&u<=19903||19968<=u&&u<=42182||43360<=u&&u<=43388||44032<=u&&u<=55203||63744<=u&&u<=64255||65040<=u&&u<=65049||65072<=u&&u<=65131||65281<=u&&u<=65376||65504<=u&&u<=65510||110592<=u&&u<=110593||127488<=u&&u<=127569||131072<=u&&u<=262141))return!0;return!1};p.exports=kD;p.exports.default=kD});var MD=I((mF,VD)=>{VD.exports=function(){return/\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F|\uD83D\uDC68(?:\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFB|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|[\u2695\u2696\u2708]\uFE0F|\uD83D[\uDC66\uDC67]|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708])\uFE0F|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C[\uDFFB-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)\uD83C\uDFFB|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB\uDFFC])|\uD83D\uDC69(?:\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E
|
||
|
|
${B}`))})})}async function m(u){return new Promise((D,F)=>{let C=w("MP4Box",u),E="",A="";C.stdout.on("data",(B)=>{E+=B.toString()}),C.stderr.on("data",(B)=>{A+=B.toString()}),C.on("error",(B)=>{F(Error(`MP4Box error: ${B.message}`))}),C.on("close",(B)=>{if(B===0)D();else F(Error(`MP4Box failed with exit code ${B}
|
||
|
|
${A||E}`))})})}import{spawn as Yu}from"node:child_process";async function b(u){return new Promise((D,F)=>{let C=Yu("ffprobe",["-v","error","-select_streams","v:0","-show_entries","stream=width,height,duration,r_frame_rate,codec_name","-select_streams","a:0","-show_entries","stream=bit_rate","-show_entries","format=duration","-of","json",u]),E="";C.stdout.on("data",(A)=>{E+=A.toString()}),C.on("error",(A)=>{F(Error(`ffprobe error: ${A.message}`))}),C.on("close",(A)=>{if(A!==0){F(Error(`ffprobe failed with exit code ${A}`));return}try{let B=JSON.parse(E),q=B.streams.find((R)=>R.width!==void 0),Q=B.streams.find((R)=>R.bit_rate!==void 0&&R.width===void 0),Z=B.format,[L,K]=q.r_frame_rate.split("/").map(Number),X=L/K,G=parseFloat(q.duration||Z.duration||"0"),Y=Q?.bit_rate?Math.round(parseInt(Q.bit_rate)/1000):void 0;D({width:q.width,height:q.height,duration:G,fps:X,codec:q.codec_name,audioBitrate:Y})}catch(B){F(Error(`Failed to parse ffprobe output: ${B}`))}})})}function v(u,D=256){if(!u)return`${D}k`;let F=Math.min(u,D);if(F<=64)return"64k";if(F<=96)return"96k";if(F<=128)return"128k";if(F<=192)return"192k";return"256k"}function h(u){let D=Math.floor(u/3600),F=Math.floor(u%3600/60),C=u%60;return`${String(D).padStart(2,"0")}:${String(F).padStart(2,"0")}:${C.toFixed(3).padStart(6,"0")}`}import{mkdir as Ju,exists as Ku}from"node:fs/promises";async function P(u){if(!await Ku(u))await Ju(u,{recursive:!0})}var ZD=[{name:"1080p",width:1920,height:1080,videoBitrate:"5000k",audioBitrate:"256k"},{name:"720p",width:1280,height:720,videoBitrate:"3000k",audioBitrate:"256k"},{name:"480p",width:854,height:480,videoBitrate:"1500k",audioBitrate:"256k"},{name:"360p",width:640,height:360,videoBitrate:"800k",audioBitrate:"256k"}];function d(u,D){return ZD.filter((F)=>{return F.width<=u&&F.height<=D})}import{join as W}from"node:path";import{readdir as Gu,unlink as LD,rmdir as Iu}from"node:fs/promises";async function QD(u,D,F,C){let{width:E,height:A,interval:B,columns:q}=C,Q=W(D,".thumbnails_temp");await Bun.write(W(Q,".keep"),"");let Z=W(Q,"thumb_%04d.jpg");await z(["-i",u,"-vf",`fps=1/${B},scale=${E}:${A}`,"-q:v","5",Z]);let K=(await Gu(Q)).filter((x)=>x.startsWith("thumb_")&&x.endsWith(".jpg")).sort();if(K.length===0)throw Error("No thumbnails generated");let X=K.length,G=Math.ceil(X/q),Y=W(D,"thumbnails.jpg"),R=`tile=${q}x${G}`;await z(["-i",Z,"-filter_complex",R,"-q:v","5",Y]);let $=W(D,"thumbnails.vtt"),j=Ru(X,B,E,A,q,"thumbnails.jpg");await Bun.write($,j);for(let x of K)await LD(W(Q,x));return await LD(W(Q,".keep")),await Iu(Q),{spritePath:Y,vttPath:$}}function Ru(u,D,F,C,E,A){let B=`WEBVTT
|
||
|
|
|
||
|
|
`;for(let q=0;q<u;q++){let Q=q*D,Z=(q+1)*D,L=Math.floor(q/E),X=q%E*F,G=L*C;B+=`${h(Q)} --> ${h(Z)}
|
||
|
|
`,B+=`${A}#xywh=${X},${G},${F},${C}
|
||
|
|
|
||
|
|
`}return B}import{join as $u}from"node:path";async function XD(u,D,F,C,E,A,B,q,Q,Z,L){let K=$u(D,`video_${F.name}.mp4`),X=["-y","-i",u,"-c:v",C];if(C==="h264_nvenc")X.push("-rc:v","vbr"),X.push("-preset",E),X.push("-2pass","0");else X.push("-preset",E);X.push("-b:v",F.videoBitrate,"-maxrate",F.videoBitrate,"-bufsize",`${parseInt(F.videoBitrate)*2}k`);let G=Math.round(q*B);X.push("-g",String(G),"-keyint_min",String(G),"-sc_threshold","0");let Y=[`scale=${F.width}:${F.height}`];if(Z){if(Z.deinterlace)Y.push("yadif");if(Z.denoise)Y.push("hqdn3d");if(Z.customFilters)Y.push(...Z.customFilters)}X.push("-vf",Y.join(","));let R=parseInt(F.audioBitrate)||256,$=v(Q,R);if(X.push("-c:a","aac","-b:a",$),Z?.audioNormalize)X.push("-af","loudnorm");return X.push("-f","mp4",K),await z(X,L,A),K}async function YD(u,D,F,C,E,A,B,q,Q,Z,L,K,X){let G=new Map;if(Z&&F.length>1)for(let Y=0;Y<F.length;Y+=L){let R=F.slice(Y,Y+L),$=R.map((x)=>XD(u,D,x,C,E,A,B,q,Q,K,(S)=>{if(X)X(x.name,S)}));(await Promise.all($)).forEach((x,S)=>{let T=R[S];G.set(T.name,x)})}else for(let Y of F){let R=await XD(u,D,Y,C,E,A,B,q,Q,K,($)=>{if(X)X(Y.name,$)});G.set(Y.name,R)}return G}import{join as _}from"node:path";async function JD(u,D,F,C){let E=_(D,"manifest.mpd"),A=["-dash",String(C*1000),"-frag",String(C*1000),"-rap","-segment-name","$RepresentationID$_$Number$","-out",E];for(let B of F){let q=u.get(B.name);if(!q)throw Error(`MP4 file not found for profile: ${B.name}`);if(A.push(`${q}#video:id=${B.name}`),B===F[0])A.push(`${q}#audio:id=audio`)}return await m(A),await xu(D,F),await Hu(E,F),E}async function xu(u,D){let{readdir:F,rename:C,mkdir:E}=await import("node:fs/promises");for(let q of D){let Q=_(u,q.name);await E(Q,{recursive:!0})}let A=_(u,"audio");await E(A,{recursive:!0});let B=await F(u);for(let q of B){if(q==="manifest.mpd")continue;if(q.startsWith("audio_")||q==="audio_init.m4s"){let Q=_(u,q),Z=_(A,q);await C(Q,Z);continue}for(let Q of D)if(q.startsWith(`${Q.name}_`)){let Z=_(u,q),L=_(u,Q.name,q);await C(Z,L);break}}}async function Hu(u,D){let{readFile:F,writeFile:C}=await import("node:fs/promises"),E=await F(u,"utf-8");E=E.replace(/media="\$RepresentationID\$_\$Number\$\.m4s"/g,'media="$RepresentationID$/$RepresentationID$_$Number$.m4s"'),E=E.replace(/initialization="\$RepresentationID\$_\.mp4"/g,'initialization="$RepresentationID$/$RepresentationID$_.mp4"'),await C(u,E,"utf-8")}async function l(u){let{input:D,outputDir:F,segmentDuration:C=2,profiles:E,useNvenc:A,generateThumbnails:B=!0,thumbnailConfig:q={},parallel:Q=!0,onProgress:Z}=u,L=KD("/tmp",`dash-converter-${_u()}`);await P(L);try{return await yu(D,F,L,C,E,A,B,q,Q,Z)}finally{try{await ku(L,{recursive:!0,force:!0})}catch(K){console.warn(`Warning: Failed to cleanup temp directory: ${L}`)}}}async function yu(u,D,F,C,E,A,B,q,Q,Z){if(!await y())throw Error("FFmpeg is not installed or not in PATH");if(!await V())throw Error("MP4Box is not installed or not in PATH. Install gpac package.");let L=(H,U,BD,O)=>{if(Z)Z({stage:H,percent:U,message:BD,currentProfile:O})};L("analyzing",0,"Analyzing input video...");let K=await b(u),X=A!==!1?await M():!1,G=A===!0?!0:A===!1?!1:X;if(A===!0&&!X)throw Error("NVENC requested but not available. Check NVIDIA drivers and GPU support.");let Y=E||d(K.width,K.height);if(Y.length===0)throw Error("No suitable profiles found for input video resolution");let R=Uu(u,Wu(u)),$=KD(D,R);await P($),L("analyzing",20,`Using ${G?"NVENC":"CPU"} encoding`,void 0);let j=G?"h264_nvenc":"libx264",x=G?"p4":"medium",S=G?3:2;L("encoding",25,`Stage 1: Encoding ${Y.length} profiles to MP4...`);let T=await YD(u,F,Y,j,x,K.duration,C,K.fps||25,K.audioBitrate,Q,S,void 0,(H,U)=>{let O=25+Y.findIndex((Au)=>Au.name===H)/Y.length*40,AD=U/100*(40/Y.length);if(L("encoding",O+AD,`Encoding ${H}...`,H),Z)Z({stage:"encoding",percent:O+AD,currentProfile:H,profilePercent:U,message:`Encoding ${H}...`})});L("encoding",65,"Stage 1 complete: All profiles encoded"),L("encoding",70,"Stage 2: Creating DASH with MP4Box...");let Eu=await JD(T,$,Y,C),Bu=Array.from(T.values());L("encoding",80,"Stage 2 complete: DASH create
|
||
|
|
`);var uu=await y(),Fu=await M(),Cu=await V();console.log(`FFmpeg: ${uu?"✅":"❌"}`);console.log(`NVENC: ${Fu?"✅ (GPU acceleration)":"⚠️ (CPU only)"}`);console.log(`MP4Box: ${Cu?"✅":"❌"}
|
||
|
|
`);if(!uu)console.error("❌ FFmpeg not found. Please install FFmpeg first."),process.exit(1);if(!Cu)console.error("❌ MP4Box not found. Please install: sudo pacman -S gpac"),process.exit(1);console.log(`\uD83D\uDCF9 Input: ${FD}`);console.log(`\uD83D\uDCC1 Output: ${Du}
|
||
|
|
`);console.log(`\uD83D\uDE80 Starting conversion...
|
||
|
|
`);var c=new uD.default.MultiBar({format:"{stage} | {bar} | {percentage}% | {name}",barCompleteChar:"█",barIncompleteChar:"░",hideCursor:!0,clearOnComplete:!1,stopOnComplete:!0},uD.default.Presets.shades_classic),o={},DD=null;try{let u=await l({input:FD,outputDir:Du,segmentDuration:2,useNvenc:Fu,generateThumbnails:!0,parallel:!0,onProgress:(D)=>{let F=D.stage==="encoding"?"Encoding":D.stage==="thumbnails"?"Thumbnails":D.stage==="manifest"?"Manifest":D.stage==="analyzing"?"Analyzing":"Complete";if(D.stage==="encoding"&&D.currentProfile){if(!o[D.currentProfile])o[D.currentProfile]=c.create(100,0,{stage:"Encode",name:D.currentProfile});let C=D.profilePercent??D.percent;o[D.currentProfile].update(C,{stage:"Encode",name:D.currentProfile})}if(!DD)DD=c.create(100,0,{stage:F,name:"Overall"});DD.update(D.percent,{stage:F,name:D.message||"Overall"})}});if(c.stop(),console.log(`
|
||
|
|
✅ Conversion completed successfully!
|
||
|
|
`),console.log("\uD83D\uDCCA Results:"),console.log(` Manifest: ${u.manifestPath}`),console.log(` Duration: ${u.duration.toFixed(2)}s`),console.log(` Profiles: ${u.profiles.map((D)=>D.name).join(", ")}`),console.log(` Encoder: ${u.usedNvenc?"⚡ NVENC (GPU)":"\uD83D\uDD27 libx264 (CPU)"}`),u.thumbnailSpritePath)console.log(` Thumbnails: ${u.thumbnailSpritePath}`),console.log(` VTT file: ${u.thumbnailVttPath}`);console.log(`
|
||
|
|
\uD83C\uDF89 Done! You can now use the manifest file in your video player.`)}catch(u){c.stop(),console.error(`
|
||
|
|
|
||
|
|
❌ Error during conversion:`),console.error(u),process.exit(1)}
|