vidup 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/.DS_Store ADDED
Binary file
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ var M=Object.defineProperty;var a=(e,t)=>M(e,"name",{value:t,configurable:!0});import I from"node:path";import _ from"node:os";import O from"yargs";import{hideBin as S}from"yargs/helpers";import{l as h,s as $,a as E}from"../sync-fEVhgE0O.js";import"bunnycdn-stream";import"hasha";import"node:fs";import"node:fs/promises";import"node:url";import"ora";import"execa";import"ffmpeg-static";const v=a(e=>Number.isFinite(e)?e:0,"toZeroIfInfinity");function F(e){return{days:Math.trunc(e/864e5),hours:Math.trunc(e/36e5%24),minutes:Math.trunc(e/6e4%60),seconds:Math.trunc(e/1e3%60),milliseconds:Math.trunc(e%1e3),microseconds:Math.trunc(v(e*1e3)%1e3),nanoseconds:Math.trunc(v(e*1e6)%1e3)}}a(F,"parseNumber");function x(e){return{days:e/86400000n,hours:e/3600000n%24n,minutes:e/60000n%60n,seconds:e/1000n%60n,milliseconds:e%1000n,microseconds:0n,nanoseconds:0n}}a(x,"parseBigint");function k(e){switch(typeof e){case"number":{if(Number.isFinite(e))return F(e);break}case"bigint":return x(e)}throw new TypeError("Expected a finite number or bigint")}a(k,"parseMilliseconds");const C=a(e=>e===0||e===0n,"isZero"),B=a((e,t)=>t===1||t===1n?e:`${e}s`,"pluralize"),T=1e-7,z=24n*60n*60n*1000n;function P(e,t){const u=typeof e=="bigint";if(!u&&!Number.isFinite(e))throw new TypeError("Expected a finite number or bigint");t={...t};const f=e<0?"-":"";e=e<0?-e:e,t.colonNotation&&(t.compact=!1,t.formatSubMilliseconds=!1,t.separateMilliseconds=!1,t.verbose=!1),t.compact&&(t.unitCount=1,t.secondsDecimalDigits=0,t.millisecondsDecimalDigits=0);let d=[];const b=a((r,o)=>{const c=Math.floor(r*10**o+T);return(Math.round(c)/10**o).toFixed(o)},"floorDecimals"),s=a((r,o,c,n)=>{if(!((d.length===0||!t.colonNotation)&&C(r)&&!(t.colonNotation&&c==="m"))){if(n??=String(r),t.colonNotation){const l=n.includes(".")?n.split(".")[0].length:n.length,y=d.length>0?2:1;n="0".repeat(Math.max(0,y-l))+n}else n+=t.verbose?" "+B(o,r):c;d.push(n)}},"add"),i=k(e),m=BigInt(i.days);if(s(m/365n,"year","y"),s(m%365n,"day","d"),s(Number(i.hours),"hour","h"),s(Number(i.minutes),"minute","m"),t.separateMilliseconds||t.formatSubMilliseconds||!t.colonNotation&&e<1e3){const r=Number(i.seconds),o=Number(i.milliseconds),c=Number(i.microseconds),n=Number(i.nanoseconds);if(s(r,"second","s"),t.formatSubMilliseconds)s(o,"millisecond","ms"),s(c,"microsecond","\xB5s"),s(n,"nanosecond","ns");else{const l=o+c/1e3+n/1e6,y=typeof t.millisecondsDecimalDigits=="number"?t.millisecondsDecimalDigits:0,w=l>=1?Math.round(l):Math.ceil(l),g=y?l.toFixed(y):w;s(Number.parseFloat(g),"millisecond","ms",g)}}else{const r=(u?Number(e%z):e)/1e3%60,o=typeof t.secondsDecimalDigits=="number"?t.secondsDecimalDigits:1,c=b(r,o),n=t.keepDecimalsOnWholeSeconds?c:c.replace(/\.0+$/,"");s(Number.parseFloat(n),"second","s",n)}if(d.length===0)return f+"0"+(t.verbose?" milliseconds":"ms");const p=t.colonNotation?":":" ";return typeof t.unitCount=="number"&&(d=d.slice(0,Math.max(t.unitCount,1))),f+d.join(p)}a(P,"prettyMilliseconds");const N=_.homedir();function V(e){if(typeof e!="string")throw new TypeError(`Expected a string, got ${typeof e}`);return N?e.replace(/^~(?=$|\/|\\)/,N):e}a(V,"untildify");var A="1.0.5";const L=performance.now(),D=O(S(process.argv));await D.scriptName("vidup").usage("$0 [command]","Run a vidup command.").command(["$0 <directory> [options]","sync <directory> [options]"],"Synchronize a remote video streaming service to mirror the contents of a local directory. Warning: This command will irrevocably delete remote videos that are not present in the local directory.",e=>e.positional("directory",{default:void 0,demandOption:!0,describe:"The path to the local directory of video files to sync.",type:"string"}).option("service",{alias:"s",choices:["bunny","mux","cloudflare"],demandOption:!0,describe:"Streaming service / platform to sync local video files to. Only the Bunny.net streaming CDN is supported at this time.",type:"string"}).option("key",{default:void 0,demandOption:!0,describe:"Streaming service API access key.",type:"string"}).option("library",{default:void 0,demandOption:!0,describe:"Streaming service library ID.",type:"string"}).option("strip-metadata",{default:!1,describe:"Remove all metadata from the video files before uploading them to the streaming service. Warning: This will modify local videos in-place.",type:"boolean"}).option("dry-run",{alias:"d",default:!1,describe:"Perform a dry run without making any changes. Useful for testing and debugging. Pairs well with the `--json` command.",type:"boolean"}).option("json",{default:!1,describe:"Output the sync report as JSON.",type:"boolean"}).option("verbose",{default:!1,describe:"Enable verbose logging. All verbose logs and prefixed with their log level and are printed to `stderr` for ease of redirection.",type:"boolean"}).check(t=>{if(t.service==="bunny")return t;throw new Error(`Sorry, vidup doesn't yet implement synchronization support the ${t.service} streaming service. Only Bunny.net is supported at the moment. If you'd like to see support for another service, please open an issue or send a PR on GitHub: https://github.com/kitschpatrol/vidup/issues`)}),async({directory:e,dryRun:t,json:u,key:f,library:d,service:b,stripMetadata:s,verbose:i})=>{h.verbose=i;const m=I.resolve(V(e));h.info("Starting video synchronization..."),t&&h.warn("Dry run enabled, not making any changes");const p=s?await $(m,{dryRun:t,verbose:i}):[],r=await E(m,{credentials:{key:f,library:d},dryRun:t,service:b,verbose:i});if(u){for(const o of r)o.stripMetadata=o.localFile!==void 0&&p.includes(o.localFile);process.stdout.write(JSON.stringify(r,void 0,2)),process.stdout.write(`
3
+ `)}else{for(const o of p)process.stdout.write(`${t?"Found":"Stripped"} metadata: "${o}"
4
+ `);for(const o of r)process.stdout.write(`${o.action}: "${o.localFile}" Remote GUID: "${o.remoteId}"
5
+ `)}h.info(`Synchronized video in ${P(performance.now()-L)}`),process.exitCode=0}).demandCommand(1).alias("h","help").version(A).alias("v","version").help().wrap(process.stdout.isTTY?Math.min(120,D.terminalWidth()):0).parse();
@@ -0,0 +1,46 @@
1
+ type StripOptions = {
2
+ dryRun?: boolean;
3
+ verbose?: boolean;
4
+ };
5
+ /**
6
+ * Strip metadata from video files in a directory
7
+ * @returns List of file paths with metadata that were stripped
8
+ */
9
+ declare function stripVideoMetadataInDirectory(directory: string, options?: StripOptions): Promise<string[]>;
10
+
11
+ type Service = 'bunny' | 'cloudflare' | 'mux';
12
+ type SyncReport = Array<{
13
+ action: 'Create' | 'Delete' | 'None' | 'Update';
14
+ localFile?: string;
15
+ remoteId: string;
16
+ stripMetadata?: boolean;
17
+ }>;
18
+ type SyncOptions = {
19
+ credentials: {
20
+ key: string;
21
+ library: string;
22
+ };
23
+ dryRun?: boolean;
24
+ service: Service;
25
+ verbose?: boolean;
26
+ };
27
+ /**
28
+ * Synchronize a remote streaming service to mirror the contents of a local directory
29
+ * @returns Array of sync report entries describing what was changed (or will change if a dry run)
30
+ */
31
+ declare function syncVideoInDirectory(directory: string, options: SyncOptions): Promise<SyncReport>;
32
+
33
+ declare const log: {
34
+ verbose: boolean;
35
+ log(...data: unknown[]): void;
36
+ logPrefixed(prefix: string, ...data: unknown[]): void;
37
+ info(...data: unknown[]): void;
38
+ infoPrefixed(prefix: string, ...data: unknown[]): void;
39
+ infoSpin<T>(promise: Promise<T>, message: string): Promise<T>;
40
+ warn(...data: unknown[]): void;
41
+ warnPrefixed(prefix: string, ...data: unknown[]): void;
42
+ error(...data: unknown[]): void;
43
+ errorPrefixed(prefix: string, ...data: unknown[]): void;
44
+ };
45
+
46
+ export { type Service, type StripOptions, type SyncOptions, type SyncReport, log, stripVideoMetadataInDirectory, syncVideoInDirectory };
@@ -0,0 +1 @@
1
+ import{l as n,s as y,a as l}from"../sync-fEVhgE0O.js";import"bunnycdn-stream";import"hasha";import"node:fs";import"node:fs/promises";import"node:path";import"node:url";import"ora";import"execa";import"ffmpeg-static";export{n as log,y as stripVideoMetadataInDirectory,l as syncVideoInDirectory};
@@ -0,0 +1,7 @@
1
+ var Z=Object.defineProperty;var i=(t,e)=>Z(t,"name",{value:e,configurable:!0});import{BunnyCdnStream as ee}from"bunnycdn-stream";import{hash as te}from"hasha";import{createReadStream as k}from"node:fs";import H,{writeFile as oe,rename as re,readFile as ne}from"node:fs/promises";import w,{join as ie,dirname as se,basename as ae}from"node:path";import{fileURLToPath as le}from"node:url";import{oraPromise as ce}from"ora";import{execa as G}from"execa";import j from"ffmpeg-static";const V=10,L=i((t=0)=>e=>`\x1B[${e+t}m`,"wrapAnsi16"),W=i((t=0)=>e=>`\x1B[${38+t};5;${e}m`,"wrapAnsi256"),Y=i((t=0)=>(e,o,r)=>`\x1B[${38+t};2;${e};${o};${r}m`,"wrapAnsi16m"),c={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],gray:[90,39],grey:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgGray:[100,49],bgGrey:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};Object.keys(c.modifier);const de=Object.keys(c.color),fe=Object.keys(c.bgColor);[...de,...fe];function ue(){const t=new Map;for(const[e,o]of Object.entries(c)){for(const[r,n]of Object.entries(o))c[r]={open:`\x1B[${n[0]}m`,close:`\x1B[${n[1]}m`},o[r]=c[r],t.set(n[0],n[1]);Object.defineProperty(c,e,{value:o,enumerable:!1})}return Object.defineProperty(c,"codes",{value:t,enumerable:!1}),c.color.close="\x1B[39m",c.bgColor.close="\x1B[49m",c.color.ansi=L(),c.color.ansi256=W(),c.color.ansi16m=Y(),c.bgColor.ansi=L(V),c.bgColor.ansi256=W(V),c.bgColor.ansi16m=Y(V),Object.defineProperties(c,{rgbToAnsi256:{value(e,o,r){return e===o&&o===r?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(o/255*5)+Math.round(r/255*5)},enumerable:!1},hexToRgb:{value(e){const o=/[a-f\d]{6}|[a-f\d]{3}/i.exec(e.toString(16));if(!o)return[0,0,0];let[r]=o;r.length===3&&(r=[...r].map(l=>l+l).join(""));const n=Number.parseInt(r,16);return[n>>16&255,n>>8&255,n&255]},enumerable:!1},hexToAnsi256:{value:i(e=>c.rgbToAnsi256(...c.hexToRgb(e)),"value"),enumerable:!1},ansi256ToAnsi:{value(e){if(e<8)return 30+e;if(e<16)return 90+(e-8);let o,r,n;if(e>=232)o=((e-232)*10+8)/255,r=o,n=o;else{e-=16;const m=e%36;o=Math.floor(e/36)/5,r=Math.floor(m/6)/5,n=m%6/5}const l=Math.max(o,r,n)*2;if(l===0)return 30;let u=30+(Math.round(n)<<2|Math.round(r)<<1|Math.round(o));return l===2&&(u+=60),u},enumerable:!1},rgbToAnsi:{value:i((e,o,r)=>c.ansi256ToAnsi(c.rgbToAnsi256(e,o,r)),"value"),enumerable:!1},hexToAnsi:{value:i(e=>c.ansi256ToAnsi(c.hexToAnsi256(e)),"value"),enumerable:!1}}),c}i(ue,"assembleStyles");const g=ue(),C=(()=>{if(navigator.userAgentData){const t=navigator.userAgentData.brands.find(({brand:e})=>e==="Chromium");if(t&&t.version>93)return 3}return/\b(Chrome|Chromium)\//.test(navigator.userAgent)?1:0})(),J=C!==0&&{level:C,hasBasic:!0,has256:C>=2,has16m:C>=3},he={stdout:J,stderr:J};function me(t,e,o){let r=t.indexOf(e);if(r===-1)return t;const n=e.length;let l=0,u="";do u+=t.slice(l,r)+e+o,l=r+n,r=t.indexOf(e,l);while(r!==-1);return u+=t.slice(l),u}i(me,"stringReplaceAll");function ge(t,e,o,r){let n=0,l="";do{const u=t[r-1]==="\r";l+=t.slice(n,u?r-1:r)+e+(u?`\r
2
+ `:`
3
+ `)+o,n=r+1,r=t.indexOf(`
4
+ `,n)}while(r!==-1);return l+=t.slice(n),l}i(ge,"stringEncaseCRLFWithFirstIndex");const{stdout:K,stderr:_}=he,N=Symbol("GENERATOR"),$=Symbol("STYLER"),B=Symbol("IS_EMPTY"),z=["ansi","ansi","ansi256","ansi16m"],F=Object.create(null),pe=i((t,e={})=>{if(e.level&&!(Number.isInteger(e.level)&&e.level>=0&&e.level<=3))throw new Error("The `level` option should be an integer from 0 to 3");const o=K?K.level:0;t.level=e.level===void 0?o:e.level},"applyOptions"),we=i(t=>{const e=i((...o)=>o.join(" "),"chalk");return pe(e,t),Object.setPrototypeOf(e,T.prototype),e},"chalkFactory");function T(t){return we(t)}i(T,"createChalk"),Object.setPrototypeOf(T.prototype,Function.prototype);for(const[t,e]of Object.entries(g))F[t]={get(){const o=I(this,U(e.open,e.close,this[$]),this[B]);return Object.defineProperty(this,t,{value:o}),o}};F.visible={get(){const t=I(this,this[$],!0);return Object.defineProperty(this,"visible",{value:t}),t}};const D=i((t,e,o,...r)=>t==="rgb"?e==="ansi16m"?g[o].ansi16m(...r):e==="ansi256"?g[o].ansi256(g.rgbToAnsi256(...r)):g[o].ansi(g.rgbToAnsi(...r)):t==="hex"?D("rgb",e,o,...g.hexToRgb(...r)):g[o][t](...r),"getModelAnsi"),ye=["rgb","hex","ansi256"];for(const t of ye){F[t]={get(){const{level:o}=this;return function(...r){const n=U(D(t,z[o],"color",...r),g.color.close,this[$]);return I(this,n,this[B])}}};const e="bg"+t[0].toUpperCase()+t.slice(1);F[e]={get(){const{level:o}=this;return function(...r){const n=U(D(t,z[o],"bgColor",...r),g.bgColor.close,this[$]);return I(this,n,this[B])}}}}const be=Object.defineProperties(()=>{},{...F,level:{enumerable:!0,get(){return this[N].level},set(t){this[N].level=t}}}),U=i((t,e,o)=>{let r,n;return o===void 0?(r=t,n=e):(r=o.openAll+t,n=e+o.closeAll),{open:t,close:e,openAll:r,closeAll:n,parent:o}},"createStyler"),I=i((t,e,o)=>{const r=i((...n)=>ve(r,n.length===1?""+n[0]:n.join(" ")),"builder");return Object.setPrototypeOf(r,be),r[N]=t,r[$]=e,r[B]=o,r},"createBuilder"),ve=i((t,e)=>{if(t.level<=0||!e)return t[B]?"":e;let o=t[$];if(o===void 0)return e;const{openAll:r,closeAll:n}=o;if(e.includes("\x1B"))for(;o!==void 0;)e=me(e,o.close,o.open),o=o.parent;const l=e.indexOf(`
5
+ `);return l!==-1&&(e=ge(e,n,r,l)),r+e+n},"applyStyle");Object.defineProperties(T.prototype,F);const p=T();T({level:_?_.level:0});const q=process?.versions?.node!==void 0,d={verbose:!1,log(...t){if(!this.verbose)return;const e=p.gray("[Log]");q?console.warn(e,...t):console.log(e,...t)},logPrefixed(t,...e){this.info(p.blue(`[${t}]`),...e)},info(...t){if(!this.verbose)return;const e=p.green("[Info]");q?console.warn(e,...t):console.info(e,...t)},infoPrefixed(t,...e){this.info(p.blue(`[${t}]`),...e)},async infoSpin(t,e){return ce(t,{prefixText:p.green("[Info]"),stream:process.stderr,text:e})},warn(...t){console.warn(p.yellow("[Warning]"),...t)},warnPrefixed(t,...e){this.warn(p.blue(`[${t}]`),...e)},error(...t){console.error(p.red("[Error]"),...t)},errorPrefixed(t,...e){this.error(p.blue(`[${t}]`),...e)}};async function Q(t){return(await H.readdir(t)).filter(r=>{const n=w.extname(r);return[".mp4",".mov",".avi",".mkv"].includes(n)}).map(r=>w.join(t,r))}i(Q,"getVideosInDirectory");async function $e(t){if(j===null)throw new Error("ffmpeg-static failed to locate ffmpeg binary");try{const{stderr:e,stdout:o}=await G(j,["-i",t,"-f","ffmetadata","-"]);return o.includes("creation_time")||e.includes("creation_time")}catch(e){throw new Error(`Failed to check metadata for: ${t}:
6
+ ${String(e)}`)}}i($e,"videoHasMetadata");async function Fe(t){if(j===null)throw new Error("ffmpeg-static failed to locate ffmpeg binary");try{const e=w.extname(t).slice(1),o=`${t.slice(0,Math.max(0,t.lastIndexOf(".")))}.temp.${e}`;await G(j,["-i",t,"-map_metadata","-1","-c:v","copy","-c:a","copy",o]),await H.rename(o,t)}catch(e){throw new Error(`Failed to strip metadata from: ${t}:
7
+ ${String(e)}`)}}i(Fe,"stripVideoMetadata");async function xe(t,e={}){const{dryRun:o=!1,verbose:r=!1}=e,n=await Q(t);return Se(n,{dryRun:o,verbose:r})}i(xe,"stripVideoMetadataInDirectory");async function Se(t,e={}){const{dryRun:o=!1,verbose:r=!1}=e,n=d.verbose;d.verbose=r;const l=[];for(const u of t)await $e(u)&&l.push(u);if(!o){d.info(`Found ${l.length} videos with metadata to strip`);for(const u of l)console.log(`Stripping metadata from: ${u}`),await Fe(u)}return d.verbose=n,l}i(Se,"stripVideoMetadataInFiles");function Ee(t){const e=t instanceof URL?le(t):t.toString();return ie(se(e),`.${ae(e)}.tmp`)}i(Ee,"getTempFilename");async function Oe(t,e,o){for(let r=0;r<e;r++)try{return await t()}catch(n){if(r<e-1)await new Promise(l=>setTimeout(l,o));else throw n}}i(Oe,"retryAsyncOperation");class Ae{static{i(this,"Writer")}#e;#t;#o=!1;#n=null;#i=null;#s=null;#r=null;#a(e){return this.#r=e,this.#s||=new Promise((o,r)=>{this.#i=[o,r]}),new Promise((o,r)=>{this.#s?.then(o).catch(r)})}async#l(e){this.#o=!0;try{await oe(this.#t,e,"utf-8"),await Oe(async()=>{await re(this.#t,this.#e)},10,100),this.#n?.[0]()}catch(o){throw o instanceof Error&&this.#n?.[1](o),o}finally{if(this.#o=!1,this.#n=this.#i,this.#i=this.#s=null,this.#r!==null){const o=this.#r;this.#r=null,await this.write(o)}}}constructor(e){this.#e=e,this.#t=Ee(e)}async write(e){return this.#o?this.#a(e):this.#l(e)}}class Be{static{i(this,"TextFile")}#e;#t;constructor(e){this.#e=e,this.#t=new Ae(e)}async read(){let e;try{e=await ne(this.#e,"utf-8")}catch(o){if(o.code==="ENOENT")return null;throw o}return e}write(e){return this.#t.write(e)}}class Te{static{i(this,"DataFile")}#e;#t;#o;constructor(e,{parse:o,stringify:r}){this.#e=new Be(e),this.#t=o,this.#o=r}async read(){const e=await this.#e.read();return e===null?null:this.#t(e)}write(e){return this.#e.write(this.#o(e))}}class Pe extends Te{static{i(this,"JSONFile")}constructor(e){super(e,{parse:JSON.parse,stringify:i(o=>JSON.stringify(o,null,2),"stringify")})}}class Re{static{i(this,"Memory")}#e=null;read(){return Promise.resolve(this.#e)}write(e){return this.#e=e,Promise.resolve()}}function je(t,e){if(t===void 0)throw new Error("lowdb: missing adapter");if(e===void 0)throw new Error("lowdb: missing default data")}i(je,"checkArgs");class Ce{static{i(this,"Low")}adapter;data;constructor(e,o){je(e,o),this.adapter=e,this.data=o}async read(){const e=await this.adapter.read();e&&(this.data=e)}async write(){this.data&&await this.adapter.write(this.data)}async update(e){e(this.data),await this.write()}}async function Ie(t,e){const o=process.env.NODE_ENV==="test"?new Re:new Pe(t),r=new Ce(o,e);return await r.read(),r}i(Ie,"JSONFilePreset");async function Me(t,e){const{credentials:o,dryRun:r=!1,service:n,verbose:l=!1}=e,u=d.verbose;d.verbose=l;const m=[],P=await Q(t);d.info(`Found ${P.length} video files in directory "${t}"`);const f=await Ie(w.join(t,".vidup-state.json"),{syncState:[]});f.data.syncState=f.data.syncState.filter(s=>P.includes(w.join(t,s.filename)));for(const s of P){const a=w.basename(s),h=await te(s,{algorithm:"sha1"}),y=f.data.syncState.find(b=>b.filename===a);y?y.localHash=h:f.data.syncState.push({filename:a,localHash:h,remoteHash:{}})}if(r||await f.write(),n!=="bunny")throw new Error(`Streaming service not yet implemented: ${n}`);const x=new ee({apiKey:o.key,videoLibrary:o.library}),S=await x.listAllVideos();d.info(`Found ${S.length} video files on ${n} remote streaming service`);const X=S.filter(s=>{const a=f.data.syncState.find(h=>h.filename===s.title);return a!==void 0&&a.localHash===a.remoteHash[n]});for(const s of X)m.push({action:"None",localFile:s.title,remoteId:s.guid});const E=f.data.syncState.filter(s=>!S.some(a=>a.title===s.filename));for(const s of E)m.push({action:"Create",localFile:s.filename,remoteId:"Not yet uploaded (Dry run)"});const O=S.filter(s=>{const a=f.data.syncState.find(h=>h.filename===s.title);return a!==void 0&&a.localHash!==a.remoteHash[n]});for(const s of O)m.push({action:"Update",localFile:s.title,remoteId:"Not yet uploaded (Dry run)"});const R=S.filter(s=>!f.data.syncState.some(a=>a.filename===s.title));for(const s of R)m.push({action:"Delete",localFile:s.title,remoteId:s.guid});if(!r){f.data.lastRun=new Date,await f.write(),d.info("Synchronizing...");for(const[s,a]of R.entries()){if(s===0&&d.info(`Deleting ${R.length} remote videos...`),!(await d.infoSpin(x.deleteVideo(a.guid),`Deleting remote video ${s+1}/${R.length}: ${a.title}`)).success)throw new Error(`Failed to delete remote video: ${a.title}`);f.data.lastUpdate=new Date,await f.write()}for(const[s,a]of E.entries()){s===0&&d.info(`Uploading ${E.length} new local videos...`);const h=k(w.join(t,a.filename)),y=await d.infoSpin(x.createAndUploadVideo(h,{title:a.filename}),`Uploading new video ${s+1}/${E.length}: ${a.filename}`);d.info(`Remote video ${s+1}/${E.length} created with GUID: ${y.guid}`);const b=f.data.syncState.find(v=>v.filename===a.filename);if(b===void 0)throw new Error(`Failed to find state entry for: ${a.filename}`);b.remoteHash[n]=a.localHash,f.data.lastUpdate=new Date,await f.write();const A=m.find(v=>v.localFile===a.filename);if(A===void 0)throw new Error(`Failed to find report entry for: ${a.filename}`);A.remoteId=y.guid}for(const[s,a]of O.entries()){s===0&&(d.info(`Updating ${O.length} remote videos...`),d.warn("Bunny does not currently support updating videos in-place, so vidup will delete and re-uploading instead... this will change the video's GUID!"));const h=f.data.syncState.find(M=>M.filename===a.title);if(h===void 0)throw new Error(`Failed to find state entry for: ${a.title}`);if(!(await x.deleteVideo(a.guid)).success)throw new Error(`Failed to delete remote video before updating: ${a.title}`);const b=k(w.join(t,h.filename)),A=await d.infoSpin(x.createAndUploadVideo(b,{title:h.filename}),`Updating remote video ${s+1}/${O.length}: ${a.title}`);d.info(`Updated remote video ${s+1}/${O.length} created with GUID: ${A.guid}`),h.remoteHash[n]=h.localHash,f.data.lastUpdate=new Date,await f.write();const v=m.find(M=>M.localFile===h.filename);if(v===void 0)throw new Error(`Failed to find report entry for: ${h.filename}`);v.remoteId=A.guid}d.info(`All ${P.length} videos are now in sync!`)}return d.verbose=u,m}i(Me,"syncVideoInDirectory");export{Me as a,d as l,xe as s};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidup",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "description": "Synchronize a local directory of video files to remote streaming services.",
6
6
  "repository": {
@@ -18,19 +18,23 @@
18
18
  "url": "https://ericmika.com"
19
19
  },
20
20
  "license": "MIT",
21
- "packageManager": "pnpm@8.15.6+sha256.01c01eeb990e379b31ef19c03e9d06a14afa5250b82e81303f88721c99ff2e6f",
22
21
  "engines": {
23
22
  "node": ">=18.0.0",
24
- "pnpm": ">=8.0.0"
23
+ "pnpm": ">=9.0.0"
25
24
  },
26
25
  "bin": {
27
- "vidup": "bin/cli.js"
26
+ "vidup": "dist/bin/cli.js"
28
27
  },
29
28
  "main": "./dist/lib/index.js",
30
29
  "module": "./dist/lib/index.js",
30
+ "exports": {
31
+ ".": {
32
+ "import": "./dist/lib/index.js",
33
+ "types": "./dist/lib/index.d.ts"
34
+ }
35
+ },
31
36
  "types": "./dist/lib/index.d.ts",
32
37
  "files": [
33
- "bin/*",
34
38
  "dist/*"
35
39
  ],
36
40
  "keywords": [
@@ -43,36 +47,36 @@
43
47
  "video"
44
48
  ],
45
49
  "dependencies": {
46
- "bunnycdn-stream": "^2.2.3",
50
+ "@types/fs-extra": "^11.0.4",
51
+ "@types/yargs": "^17.0.33",
52
+ "bunnycdn-stream": "^2.4.0",
47
53
  "execa": "^8.0.1",
48
54
  "ffmpeg-static": "^5.2.0",
49
55
  "fs-extra": "^11.2.0",
50
56
  "hasha": "^6.0.0",
51
- "ora": "^8.0.1",
57
+ "ora": "^8.1.0",
52
58
  "yargs": "^17.7.2"
53
59
  },
54
60
  "devDependencies": {
55
- "@kitschpatrol/shared-config": "^4.6.2",
56
- "@types/fs-extra": "^11.0.4",
57
- "@types/node": "^20.12.5",
58
- "@types/yargs": "^17.0.32",
59
- "bumpp": "^9.4.0",
61
+ "@kitschpatrol/shared-config": "^4.7.5",
62
+ "@types/node": "^20.16.5",
63
+ "bumpp": "^9.5.2",
60
64
  "chalk": "^5.3.0",
61
65
  "lowdb": "^7.0.1",
62
- "pretty-ms": "^9.0.0",
63
- "tsup": "^8.0.2",
64
- "typescript": "^5.4.4",
66
+ "pkgroll": "^2.4.2",
67
+ "pretty-ms": "^9.1.0",
68
+ "rimraf": "^6.0.1",
69
+ "typescript": "^5.5.4",
65
70
  "untildify": "^5.0.0"
66
71
  },
67
72
  "publishConfig": {
68
73
  "access": "public"
69
74
  },
70
75
  "scripts": {
71
- "build": "tsup && tsc -p tsconfig.build.json",
72
- "clean": "git clean -fdX",
73
- "fix": "pnpm shared-config --fix",
74
- "lint": "pnpm shared-config --lint",
75
- "release": "pnpm bumpp -r --commit 'Release: %s' --tag 'v%s' && pnpm -r publish --otp $(op read 'op://Personal/Npmjs/one-time password?attribute=otp')",
76
- "test": "echo \"Error: no test specified\" && exit 1"
76
+ "build": "pkgroll --clean-dist --minify",
77
+ "clean": "rimraf ./pnpm-lock.yaml && git clean -fdX",
78
+ "fix": "shared-config --fix",
79
+ "lint": "shared-config --lint",
80
+ "release": "pnpm bumpp -r --commit 'Release: %s' --tag 'v%s' && pnpm build && pnpm publish --otp $(op read 'op://Personal/Npmjs/one-time password?attribute=otp')"
77
81
  }
78
82
  }
package/readme.md CHANGED
@@ -34,7 +34,7 @@ Currently, only Bunny.net's [Bunny Stream](https://bunny.net/stream/) service is
34
34
 
35
35
  ### Dependencies
36
36
 
37
- The vidup CLI tool requires Node 18+. The exported APIs for are ESM-only and share the Node 18+ requirement. Vidup is implemented in TypeScript and bundles type definitions.
37
+ The vidup CLI tool requires Node 18+. The exported APIs are ESM-only and share the Node 18+ requirement. Vidup is implemented in TypeScript and bundles type definitions.
38
38
 
39
39
  A [Bunny Stream](https://bunny.net/stream/) account, API key, and library ID are required.
40
40
 
@@ -109,17 +109,17 @@ vidup sync <directory> [options]
109
109
  | ------------------- | --------------------------------------------------------------------- | -------- |
110
110
  | `directory` | The path to the local directory of video files to sync. _(Required.)_ | `string` |
111
111
 
112
- | Option | Alias | Description | Type | Default |
113
- | ------------------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------- |
114
- | `--service` | `-s` | Streaming service / platform to sync local video files to. Only the Bunny.net streaming CDN is supported at this time. | `string` | |
115
- | `--key` | | Streaming service API access key. | `string` | |
116
- | `--library` | | Streaming service library ID. | `string` | |
117
- | `--strip-metadata` | | Remove all metadata from the video files before uploading them to the streaming service. Warning: This will modify local videos in-place. | `boolean` | `false` |
118
- | `--dry-run` | `-d` | Perform a dry run without making any changes. Useful for testing and debugging. Pairs well with the `--json` command. | `boolean` | `false` |
119
- | `--json` | | Output the sync report as JSON. | `boolean` | `false` |
120
- | `--verbose` | | Enable verbose logging. All verbose logs and prefixed with their log level and are printed to `stderr` for ease of redirection. | `boolean` | `false` |
121
- | `--help` | `-h` | Show help | `boolean` | |
122
- | `--version` | `-v` | Show version number | `boolean` | |
112
+ | Option | Description | Type | Default |
113
+ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ------- |
114
+ | `--service`<br>`-s` | Streaming service / platform to sync local video files to. Only the Bunny.net streaming CDN is supported at this time. | `"bunny"` `"mux"` `"cloudflare"` | |
115
+ | `--key` | Streaming service API access key. | `string` | |
116
+ | `--library` | Streaming service library ID. | `string` | |
117
+ | `--strip-metadata` | Remove all metadata from the video files before uploading them to the streaming service. Warning: This will modify local videos in-place. | `boolean` | `false` |
118
+ | `--dry-run`<br>`-d` | Perform a dry run without making any changes. Useful for testing and debugging. Pairs well with the `--json` command. | `boolean` | `false` |
119
+ | `--json` | Output the sync report as JSON. | `boolean` | `false` |
120
+ | `--verbose` | Enable verbose logging. All verbose logs and prefixed with their log level and are printed to `stderr` for ease of redirection. | `boolean` | `false` |
121
+ | `--help`<br>`-h` | Show help | `boolean` | |
122
+ | `--version`<br>`-v` | Show version number | `boolean` | |
123
123
 
124
124
  <!-- /cli-help -->
125
125
 
@@ -219,7 +219,7 @@ Video streaming providers have slow and clunky web UIs. I'd rather not maintain
219
219
 
220
220
  [@kitschpatrol](https://github.com/kitschpatrol)
221
221
 
222
- ## Acknowledgements
222
+ ## Acknowledgments
223
223
 
224
224
  Thanks to [@dan-online](https://github.com/dan-online) for his super-helpful [bunnycdn-stream](https://github.com/dan-online/bunnycdn-stream) library.
225
225
 
package/bin/cli.js DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env node
2
- var te=(t=0)=>e=>`\x1B[${e+t}m`,re=(t=0)=>e=>`\x1B[${38+t};5;${e}m`,oe=(t=0)=>(e,r,o)=>`\x1B[${38+t};2;${e};${r};${o}m`,u={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],gray:[90,39],grey:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgGray:[100,49],bgGrey:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}},dt=Object.keys(u.modifier),Se=Object.keys(u.color),Ne=Object.keys(u.bgColor),ft=[...Se,...Ne];function Ee(){let t=new Map;for(let[e,r]of Object.entries(u)){for(let[o,n]of Object.entries(r))u[o]={open:`\x1B[${n[0]}m`,close:`\x1B[${n[1]}m`},r[o]=u[o],t.set(n[0],n[1]);Object.defineProperty(u,e,{value:r,enumerable:!1})}return Object.defineProperty(u,"codes",{value:t,enumerable:!1}),u.color.close="\x1B[39m",u.bgColor.close="\x1B[49m",u.color.ansi=te(),u.color.ansi256=re(),u.color.ansi16m=oe(),u.bgColor.ansi=te(10),u.bgColor.ansi256=re(10),u.bgColor.ansi16m=oe(10),Object.defineProperties(u,{rgbToAnsi256:{value(e,r,o){return e===r&&r===o?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(r/255*5)+Math.round(o/255*5)},enumerable:!1},hexToRgb:{value(e){let r=/[a-f\d]{6}|[a-f\d]{3}/i.exec(e.toString(16));if(!r)return[0,0,0];let[o]=r;o.length===3&&(o=[...o].map(i=>i+i).join(""));let n=Number.parseInt(o,16);return[n>>16&255,n>>8&255,n&255]},enumerable:!1},hexToAnsi256:{value:e=>u.rgbToAnsi256(...u.hexToRgb(e)),enumerable:!1},ansi256ToAnsi:{value(e){if(e<8)return 30+e;if(e<16)return 90+(e-8);let r,o,n;if(e>=232)r=((e-232)*10+8)/255,o=r,n=r;else{e-=16;let y=e%36;r=Math.floor(e/36)/5,o=Math.floor(y/6)/5,n=y%6/5}let i=Math.max(r,o,n)*2;if(i===0)return 30;let a=30+(Math.round(n)<<2|Math.round(o)<<1|Math.round(r));return i===2&&(a+=60),a},enumerable:!1},rgbToAnsi:{value:(e,r,o)=>u.ansi256ToAnsi(u.rgbToAnsi256(e,r,o)),enumerable:!1},hexToAnsi:{value:e=>u.ansi256ToAnsi(u.hexToAnsi256(e)),enumerable:!1}}),u}var Fe=Ee(),v=Fe;import Y from"node:process";import Re from"node:os";import ne from"node:tty";function w(t,e=globalThis.Deno?globalThis.Deno.args:Y.argv){let r=t.startsWith("-")?"":t.length===1?"-":"--",o=e.indexOf(r+t),n=e.indexOf("--");return o!==-1&&(n===-1||o<n)}var{env:m}=Y,A;w("no-color")||w("no-colors")||w("color=false")||w("color=never")?A=0:(w("color")||w("colors")||w("color=true")||w("color=always"))&&(A=1);function Te(){if("FORCE_COLOR"in m)return m.FORCE_COLOR==="true"?1:m.FORCE_COLOR==="false"?0:m.FORCE_COLOR.length===0?1:Math.min(Number.parseInt(m.FORCE_COLOR,10),3)}function Ie(t){return t===0?!1:{level:t,hasBasic:!0,has256:t>=2,has16m:t>=3}}function Me(t,{streamIsTTY:e,sniffFlags:r=!0}={}){let o=Te();o!==void 0&&(A=o);let n=r?A:o;if(n===0)return 0;if(r){if(w("color=16m")||w("color=full")||w("color=truecolor"))return 3;if(w("color=256"))return 2}if("TF_BUILD"in m&&"AGENT_NAME"in m)return 1;if(t&&!e&&n===void 0)return 0;let i=n||0;if(m.TERM==="dumb")return i;if(Y.platform==="win32"){let a=Re.release().split(".");return Number(a[0])>=10&&Number(a[2])>=10586?Number(a[2])>=14931?3:2:1}if("CI"in m)return"GITHUB_ACTIONS"in m||"GITEA_ACTIONS"in m?3:["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI","BUILDKITE","DRONE"].some(a=>a in m)||m.CI_NAME==="codeship"?1:i;if("TEAMCITY_VERSION"in m)return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(m.TEAMCITY_VERSION)?1:0;if(m.COLORTERM==="truecolor"||m.TERM==="xterm-kitty")return 3;if("TERM_PROGRAM"in m){let a=Number.parseInt((m.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(m.TERM_PROGRAM){case"iTerm.app":return a>=3?3:2;case"Apple_Terminal":return 2}}return/-256(color)?$/i.test(m.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(m.TERM)||"COLORTERM"in m?1:i}function ie(t,e={}){let r=Me(t,{streamIsTTY:t&&t.isTTY,...e});return Ie(r)}var Ce={stdout:ie({isTTY:ne.isatty(1)}),stderr:ie({isTTY:ne.isatty(2)})},se=Ce;function ae(t,e,r){let o=t.indexOf(e);if(o===-1)return t;let n=e.length,i=0,a="";do a+=t.slice(i,o)+e+r,i=o+n,o=t.indexOf(e,i);while(o!==-1);return a+=t.slice(i),a}function le(t,e,r,o){let n=0,i="";do{let a=t[o-1]==="\r";i+=t.slice(n,a?o-1:o)+e+(a?`\r
3
- `:`
4
- `)+r,n=o+1,o=t.indexOf(`
5
- `,n)}while(o!==-1);return i+=t.slice(n),i}var{stdout:ce,stderr:de}=se,J=Symbol("GENERATOR"),T=Symbol("STYLER"),C=Symbol("IS_EMPTY"),fe=["ansi","ansi","ansi256","ansi16m"],I=Object.create(null),$e=(t,e={})=>{if(e.level&&!(Number.isInteger(e.level)&&e.level>=0&&e.level<=3))throw new Error("The `level` option should be an integer from 0 to 3");let r=ce?ce.level:0;t.level=e.level===void 0?r:e.level};var De=t=>{let e=(...r)=>r.join(" ");return $e(e,t),Object.setPrototypeOf(e,$.prototype),e};function $(t){return De(t)}Object.setPrototypeOf($.prototype,Function.prototype);for(let[t,e]of Object.entries(v))I[t]={get(){let r=P(this,K(e.open,e.close,this[T]),this[C]);return Object.defineProperty(this,t,{value:r}),r}};I.visible={get(){let t=P(this,this[T],!0);return Object.defineProperty(this,"visible",{value:t}),t}};var W=(t,e,r,...o)=>t==="rgb"?e==="ansi16m"?v[r].ansi16m(...o):e==="ansi256"?v[r].ansi256(v.rgbToAnsi256(...o)):v[r].ansi(v.rgbToAnsi(...o)):t==="hex"?W("rgb",e,r,...v.hexToRgb(...o)):v[r][t](...o),Ae=["rgb","hex","ansi256"];for(let t of Ae){I[t]={get(){let{level:r}=this;return function(...o){let n=K(W(t,fe[r],"color",...o),v.color.close,this[T]);return P(this,n,this[C])}}};let e="bg"+t[0].toUpperCase()+t.slice(1);I[e]={get(){let{level:r}=this;return function(...o){let n=K(W(t,fe[r],"bgColor",...o),v.bgColor.close,this[T]);return P(this,n,this[C])}}}}var Pe=Object.defineProperties(()=>{},{...I,level:{enumerable:!0,get(){return this[J].level},set(t){this[J].level=t}}}),K=(t,e,r)=>{let o,n;return r===void 0?(o=t,n=e):(o=r.openAll+t,n=e+r.closeAll),{open:t,close:e,openAll:o,closeAll:n,parent:r}},P=(t,e,r)=>{let o=(...n)=>Be(o,n.length===1?""+n[0]:n.join(" "));return Object.setPrototypeOf(o,Pe),o[J]=t,o[T]=e,o[C]=r,o},Be=(t,e)=>{if(t.level<=0||!e)return t[C]?"":e;let r=t[T];if(r===void 0)return e;let{openAll:o,closeAll:n}=r;if(e.includes("\x1B"))for(;r!==void 0;)e=ae(e,r.close,r.open),r=r.parent;let i=e.indexOf(`
6
- `);return i!==-1&&(e=le(e,n,o,i)),o+e+n};Object.defineProperties($.prototype,I);var _e=$(),xt=$({level:de?de.level:0});var O=_e;import{oraPromise as ke}from"ora";var me,ue=((me=process==null?void 0:process.versions)==null?void 0:me.node)!==void 0,Ve={verbose:!1,log(...t){if(!this.verbose)return;let e=O.gray("[Log]");ue?console.warn(e,...t):console.log(e,...t)},logPrefixed(t,...e){this.info(O.blue(`[${t}]`),...e)},info(...t){if(!this.verbose)return;let e=O.green("[Info]");ue?console.warn(e,...t):console.info(e,...t)},infoPrefixed(t,...e){this.info(O.blue(`[${t}]`),...e)},async infoSpin(t,e){return ke(t,{prefixText:O.green("[Info]"),stream:process.stderr,text:e})},warn(...t){console.warn(O.yellow("[Warning]"),...t)},warnPrefixed(t,...e){this.warn(O.blue(`[${t}]`),...e)},error(...t){console.error(O.red("[Error]"),...t)},errorPrefixed(t,...e){this.error(O.blue(`[${t}]`),...e)}},d=Ve;import{execa as pe}from"execa";import B from"ffmpeg-static";import he from"node:fs/promises";import z from"node:path";async function _(t){return(await he.readdir(t)).filter(o=>{let n=z.extname(o);return[".mp4",".mov",".avi",".mkv"].includes(n)}).map(o=>z.join(t,o))}async function ye(t){if(B===null)throw new Error("ffmpeg-static failed to locate ffmpeg binary");try{let{stderr:e,stdout:r}=await pe(B,["-i",t,"-f","ffmetadata","-"]);return r.includes("creation_time")||e.includes("creation_time")}catch(e){throw new Error(`Failed to check metadata for: ${t}:
7
- ${String(e)}`)}}async function ge(t){if(B===null)throw new Error("ffmpeg-static failed to locate ffmpeg binary");try{let e=z.extname(t).slice(1),r=`${t.slice(0,Math.max(0,t.lastIndexOf(".")))}.temp.${e}`;await pe(B,["-i",t,"-map_metadata","-1","-c:v","copy","-c:a","copy",r]),await he.rename(r,t)}catch(e){throw new Error(`Failed to strip metadata from: ${t}:
8
- ${String(e)}`)}}async function Z(t,e={}){let{dryRun:r=!1,verbose:o=!1}=e,n=await _(t);return je(n,{dryRun:r,verbose:o})}async function je(t,e={}){let{dryRun:r=!1,verbose:o=!1}=e,n=d.verbose;d.verbose=o;let i=[];for(let a of t)await ye(a)&&i.push(a);if(!r){d.info(`Found ${i.length} videos with metadata to strip`);for(let a of i)console.log(`Stripping metadata from: ${a}`),await ge(a)}return d.verbose=n,i}import{BunnyCdnStream as qe}from"bunnycdn-stream";import{hash as Qe}from"hasha";import{readFile as ze}from"node:fs/promises";import{rename as Ue,writeFile as Le}from"node:fs/promises";import{basename as Ge,dirname as He,join as Ye}from"node:path";import{fileURLToPath as Je}from"node:url";function We(t){let e=t instanceof URL?Je(t):t.toString();return Ye(He(e),`.${Ge(e)}.tmp`)}async function Ke(t,e,r){for(let o=0;o<e;o++)try{return await t()}catch(n){if(o<e-1)await new Promise(i=>setTimeout(i,r));else throw n}}var k=class{#e;#t;#r=!1;#n=null;#i=null;#s=null;#o=null;#a(e){return this.#o=e,this.#s||=new Promise((r,o)=>{this.#i=[r,o]}),new Promise((r,o)=>{var n;(n=this.#s)==null||n.then(r).catch(o)})}async#l(e){var r,o;this.#r=!0;try{await Le(this.#t,e,"utf-8"),await Ke(async()=>{await Ue(this.#t,this.#e)},10,100),(r=this.#n)==null||r[0]()}catch(n){throw n instanceof Error&&((o=this.#n)==null||o[1](n)),n}finally{if(this.#r=!1,this.#n=this.#i,this.#i=this.#s=null,this.#o!==null){let n=this.#o;this.#o=null,await this.write(n)}}}constructor(e){this.#e=e,this.#t=We(e)}async write(e){return this.#r?this.#a(e):this.#l(e)}};var V=class{#e;#t;constructor(e){this.#e=e,this.#t=new k(e)}async read(){let e;try{e=await ze(this.#e,"utf-8")}catch(r){if(r.code==="ENOENT")return null;throw r}return e}write(e){return this.#t.write(e)}};var j=class{#e;#t;#r;constructor(e,{parse:r,stringify:o}){this.#e=new V(e),this.#t=r,this.#r=o}async read(){let e=await this.#e.read();return e===null?null:this.#t(e)}write(e){return this.#e.write(this.#r(e))}};var U=class extends j{constructor(e){super(e,{parse:JSON.parse,stringify:r=>JSON.stringify(r,null,2)})}};var L=class{#e=null;read(){return Promise.resolve(this.#e)}write(e){return this.#e=e,Promise.resolve()}};function Ze(t,e){if(t===void 0)throw new Error("lowdb: missing adapter");if(e===void 0)throw new Error("lowdb: missing default data")}var G=class{adapter;data;constructor(e,r){Ze(e,r),this.adapter=e,this.data=r}async read(){let e=await this.adapter.read();e&&(this.data=e)}async write(){this.data&&await this.adapter.write(this.data)}async update(e){e(this.data),await this.write()}};async function be(t,e){let r=process.env.NODE_ENV==="test"?new L:new U(t),o=new G(r,e);return await o.read(),o}import{createReadStream as we}from"node:fs";import D from"node:path";async function q(t,e){let{credentials:r,dryRun:o=!1,service:n,verbose:i=!1}=e,a=d.verbose;d.verbose=i;let y=[],S=await _(t);d.info(`Found ${S.length} video files in directory "${t}"`);let c=await be(D.join(t,".vidup-state.json"),{syncState:[]});c.data.syncState=c.data.syncState.filter(s=>S.includes(D.join(t,s.filename)));for(let s of S){let l=D.basename(s),g=await Qe(s,{algorithm:"sha1"}),E=c.data.syncState.find(F=>F.filename===l);E?E.localHash=g:c.data.syncState.push({filename:l,localHash:g,remoteHash:{}})}if(o||await c.write(),n!=="bunny")throw new Error(`Streaming service not yet implemented: ${n}`);let p=new qe({apiKey:r.key,videoLibrary:r.library}),f=await p.listAllVideos();d.info(`Found ${f.length} video files on ${n} remote streaming service`);let h=f.filter(s=>{let l=c.data.syncState.find(g=>g.filename===s.title);return l!==void 0&&l.localHash===l.remoteHash[n]});for(let s of h)y.push({action:"None",localFile:s.title,remoteId:s.guid});let b=c.data.syncState.filter(s=>!f.some(l=>l.title===s.filename));for(let s of b)y.push({action:"Create",localFile:s.filename,remoteId:"Not yet uploaded (Dry run)"});let x=f.filter(s=>{let l=c.data.syncState.find(g=>g.filename===s.title);return l!==void 0&&l.localHash!==l.remoteHash[n]});for(let s of x)y.push({action:"Update",localFile:s.title,remoteId:"Not yet uploaded (Dry run)"});let N=f.filter(s=>!c.data.syncState.some(l=>l.filename===s.title));for(let s of N)y.push({action:"Delete",localFile:s.title,remoteId:s.guid});if(!o){c.data.lastRun=new Date,await c.write(),d.info("Synchronizing...");for(let[s,l]of N.entries()){if(s===0&&d.info(`Deleting ${N.length} remote videos...`),!(await d.infoSpin(p.deleteVideo(l.guid),`Deleting remote video ${s+1}/${N.length}: ${l.title}`)).success)throw new Error(`Failed to delete remote video: ${l.title}`);c.data.lastUpdate=new Date,await c.write()}for(let[s,l]of b.entries()){s===0&&d.info(`Uploading ${b.length} new local videos...`);let g=we(D.join(t,l.filename)),E=await d.infoSpin(p.createAndUploadVideo(g,{title:l.filename}),`Uploading new video ${s+1}/${b.length}: ${l.filename}`);d.info(`Remote video ${s+1}/${b.length} created with GUID: ${E.guid}`);let F=c.data.syncState.find(R=>R.filename===l.filename);if(F===void 0)throw new Error(`Failed to find state entry for: ${l.filename}`);F.remoteHash[n]=l.localHash,c.data.lastUpdate=new Date,await c.write();let M=y.find(R=>R.localFile===l.filename);if(M===void 0)throw new Error(`Failed to find report entry for: ${l.filename}`);M.remoteId=E.guid}for(let[s,l]of x.entries()){s===0&&(d.info(`Updating ${x.length} remote videos...`),d.warn("Bunny does not currently support updating videos in-place, so vidup will delete and re-uploading instead... this will change the video's GUID!"));let g=c.data.syncState.find(H=>H.filename===l.title);if(g===void 0)throw new Error(`Failed to find state entry for: ${l.title}`);if(!(await p.deleteVideo(l.guid)).success)throw new Error(`Failed to delete remote video before updating: ${l.title}`);let F=we(D.join(t,g.filename)),M=await d.infoSpin(p.createAndUploadVideo(F,{title:g.filename}),`Updating remote video ${s+1}/${x.length}: ${l.title}`);d.info(`Updated remote video ${s+1}/${x.length} created with GUID: ${M.guid}`),g.remoteHash[n]=g.localHash,c.data.lastUpdate=new Date,await c.write();let R=y.find(H=>H.localFile===g.filename);if(R===void 0)throw new Error(`Failed to find report entry for: ${g.filename}`);R.remoteId=M.guid}d.info(`All ${S.length} videos are now in sync!`)}return d.verbose=a,y}import st from"node:path";var ve=t=>Number.isFinite(t)?t:0;function Xe(t){return{days:Math.trunc(t/864e5),hours:Math.trunc(t/36e5%24),minutes:Math.trunc(t/6e4%60),seconds:Math.trunc(t/1e3%60),milliseconds:Math.trunc(t%1e3),microseconds:Math.trunc(ve(t*1e3)%1e3),nanoseconds:Math.trunc(ve(t*1e6)%1e3)}}function et(t){return{days:t/86400000n,hours:t/3600000n%24n,minutes:t/60000n%60n,seconds:t/1000n%60n,milliseconds:t%1000n,microseconds:0n,nanoseconds:0n}}function Q(t){switch(typeof t){case"number":{if(Number.isFinite(t))return Xe(t);break}case"bigint":return et(t)}throw new TypeError("Expected a finite number or bigint")}var tt=t=>t===0||t===0n,rt=(t,e)=>e===1||e===1n?t:`${t}s`,ot=1e-7,nt=24n*60n*60n*1000n;function X(t,e){let r=typeof t=="bigint";if(!r&&!Number.isFinite(t))throw new TypeError("Expected a finite number or bigint");e={...e},e.colonNotation&&(e.compact=!1,e.formatSubMilliseconds=!1,e.separateMilliseconds=!1,e.verbose=!1),e.compact&&(e.unitCount=1,e.secondsDecimalDigits=0,e.millisecondsDecimalDigits=0);let o=[],n=(c,p)=>{let f=Math.floor(c*10**p+ot);return(Math.round(f)/10**p).toFixed(p)},i=(c,p,f,h)=>{if(!((o.length===0||!e.colonNotation)&&tt(c)&&!(e.colonNotation&&f==="m"))){if(h=h??String(c),e.colonNotation){let b=h.includes(".")?h.split(".")[0].length:h.length,x=o.length>0?2:1;h="0".repeat(Math.max(0,x-b))+h}else h+=e.verbose?" "+rt(p,c):f;o.push(h)}},a=Q(t),y=BigInt(a.days);if(i(y/365n,"year","y"),i(y%365n,"day","d"),i(Number(a.hours),"hour","h"),i(Number(a.minutes),"minute","m"),e.separateMilliseconds||e.formatSubMilliseconds||!e.colonNotation&&t<1e3){let c=Number(a.seconds),p=Number(a.milliseconds),f=Number(a.microseconds),h=Number(a.nanoseconds);if(i(c,"second","s"),e.formatSubMilliseconds)i(p,"millisecond","ms"),i(f,"microsecond","\xB5s"),i(h,"nanosecond","ns");else{let b=p+f/1e3+h/1e6,x=typeof e.millisecondsDecimalDigits=="number"?e.millisecondsDecimalDigits:0,N=b>=1?Math.round(b):Math.ceil(b),s=x?b.toFixed(x):N;i(Number.parseFloat(s),"millisecond","ms",s)}}else{let c=(r?Number(t%nt):t)/1e3%60,p=typeof e.secondsDecimalDigits=="number"?e.secondsDecimalDigits:1,f=n(c,p),h=e.keepDecimalsOnWholeSeconds?f:f.replace(/\.0+$/,"");i(Number.parseFloat(h),"second","s",h)}if(o.length===0)return"0"+(e.verbose?" milliseconds":"ms");let S=e.colonNotation?":":" ";return typeof e.unitCount=="number"&&(o=o.slice(0,Math.max(e.unitCount,1))),o.join(S)}import it from"node:os";var xe=it.homedir();function ee(t){if(typeof t!="string")throw new TypeError(`Expected a string, got ${typeof t}`);return xe?t.replace(/^~(?=$|\/|\\)/,xe):t}import at from"yargs";import{hideBin as lt}from"yargs/helpers";var ct=performance.now(),Oe=at(lt(process.argv));await Oe.scriptName("vidup").usage("$0 [command]","Run a vidup command.").command(["$0 <directory> [options]","sync <directory> [options]"],"Synchronize a remote video streaming service to mirror the contents of a local directory. Warning: This command will irrevocably delete remote videos that are not present in the local directory.",t=>t.positional("directory",{default:void 0,demandOption:!0,describe:"The path to the local directory of video files to sync.",type:"string"}).option("service",{alias:"s",choices:["bunny","mux","cloudflare"],demandOption:!0,describe:"Streaming service / platform to sync local video files to. Only the Bunny.net streaming CDN is supported at this time.",type:"string"}).option("key",{default:void 0,demandOption:!0,describe:"Streaming service API access key.",type:"string"}).option("library",{default:void 0,demandOption:!0,describe:"Streaming service library ID.",type:"string"}).option("strip-metadata",{default:!1,describe:"Remove all metadata from the video files before uploading them to the streaming service. Warning: This will modify local videos in-place.",type:"boolean"}).option("dry-run",{alias:"d",default:!1,describe:"Perform a dry run without making any changes. Useful for testing and debugging. Pairs well with the `--json` command.",type:"boolean"}).option("json",{default:!1,describe:"Output the sync report as JSON.",type:"boolean"}).option("verbose",{default:!1,describe:"Enable verbose logging. All verbose logs and prefixed with their log level and are printed to `stderr` for ease of redirection.",type:"boolean"}).check(e=>{if(e.service==="bunny")return e;throw new Error(`Sorry, vidup doesn't yet implement synchronization support the ${e.service} streaming service. Only Bunny.net is supported at the moment. If you'd like to see support for another service, please open an issue or send a PR on GitHub: https://github.com/kitschpatrol/vidup/issues`)}),async({directory:t,dryRun:e,json:r,key:o,library:n,service:i,stripMetadata:a,verbose:y})=>{d.verbose=y;let S=st.resolve(ee(t));d.info("Starting video synchronization..."),e&&d.warn("Dry run enabled, not making any changes");let c=a?await Z(S,{dryRun:e,verbose:y}):[],p=await q(S,{credentials:{key:o,library:n},dryRun:e,service:i,verbose:y});if(r){for(let f of p)f.stripMetadata=f.localFile!==void 0&&c.includes(f.localFile);process.stdout.write(JSON.stringify(p,void 0,2)),process.stdout.write(`
9
- `)}else{for(let f of c)process.stdout.write(`${e?"Found":"Stripped"} metadata: "${f}"
10
- `);for(let f of p)process.stdout.write(`${f.action}: "${f.localFile}" Remote GUID: "${f.remoteId}"
11
- `)}d.info(`Synchronized video in ${X(performance.now()-ct)}`),process.exitCode=0}).demandCommand(1).alias("h","help").version().alias("v","version").help().wrap(process.stdout.isTTY?Math.min(120,Oe.terminalWidth()):0).parse();
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export { type StripOptions, stripVideoMetadataInDirectory } from './strip';
2
- export { type Service, type SyncOptions, type SyncReport, syncVideoInDirectory } from './sync';
3
- export { default as log } from './utilities/log';
package/dist/index.js DELETED
@@ -1,7 +0,0 @@
1
- var z=(t=0)=>e=>`\x1B[${e+t}m`,q=(t=0)=>e=>`\x1B[${38+t};5;${e}m`,Q=(t=0)=>(e,r,o)=>`\x1B[${38+t};2;${e};${r};${o}m`,c={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],gray:[90,39],grey:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgGray:[100,49],bgGrey:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}},Ke=Object.keys(c.modifier),he=Object.keys(c.color),ge=Object.keys(c.bgColor),We=[...he,...ge];function ye(){let t=new Map;for(let[e,r]of Object.entries(c)){for(let[o,n]of Object.entries(r))c[o]={open:`\x1B[${n[0]}m`,close:`\x1B[${n[1]}m`},r[o]=c[o],t.set(n[0],n[1]);Object.defineProperty(c,e,{value:r,enumerable:!1})}return Object.defineProperty(c,"codes",{value:t,enumerable:!1}),c.color.close="\x1B[39m",c.bgColor.close="\x1B[49m",c.color.ansi=z(),c.color.ansi256=q(),c.color.ansi16m=Q(),c.bgColor.ansi=z(10),c.bgColor.ansi256=q(10),c.bgColor.ansi16m=Q(10),Object.defineProperties(c,{rgbToAnsi256:{value(e,r,o){return e===r&&r===o?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(r/255*5)+Math.round(o/255*5)},enumerable:!1},hexToRgb:{value(e){let r=/[a-f\d]{6}|[a-f\d]{3}/i.exec(e.toString(16));if(!r)return[0,0,0];let[o]=r;o.length===3&&(o=[...o].map(a=>a+a).join(""));let n=Number.parseInt(o,16);return[n>>16&255,n>>8&255,n&255]},enumerable:!1},hexToAnsi256:{value:e=>c.rgbToAnsi256(...c.hexToRgb(e)),enumerable:!1},ansi256ToAnsi:{value(e){if(e<8)return 30+e;if(e<16)return 90+(e-8);let r,o,n;if(e>=232)r=((e-232)*10+8)/255,o=r,n=r;else{e-=16;let g=e%36;r=Math.floor(e/36)/5,o=Math.floor(g/6)/5,n=g%6/5}let a=Math.max(r,o,n)*2;if(a===0)return 30;let l=30+(Math.round(n)<<2|Math.round(o)<<1|Math.round(r));return a===2&&(l+=60),l},enumerable:!1},rgbToAnsi:{value:(e,r,o)=>c.ansi256ToAnsi(c.rgbToAnsi256(e,r,o)),enumerable:!1},hexToAnsi:{value:e=>c.ansi256ToAnsi(c.hexToAnsi256(e)),enumerable:!1}}),c}var we=ye(),h=we;import H from"node:process";import be from"node:os";import X from"node:tty";function m(t,e=globalThis.Deno?globalThis.Deno.args:H.argv){let r=t.startsWith("-")?"":t.length===1?"-":"--",o=e.indexOf(r+t),n=e.indexOf("--");return o!==-1&&(n===-1||o<n)}var{env:d}=H,M;m("no-color")||m("no-colors")||m("color=false")||m("color=never")?M=0:(m("color")||m("colors")||m("color=true")||m("color=always"))&&(M=1);function ve(){if("FORCE_COLOR"in d)return d.FORCE_COLOR==="true"?1:d.FORCE_COLOR==="false"?0:d.FORCE_COLOR.length===0?1:Math.min(Number.parseInt(d.FORCE_COLOR,10),3)}function xe(t){return t===0?!1:{level:t,hasBasic:!0,has256:t>=2,has16m:t>=3}}function Oe(t,{streamIsTTY:e,sniffFlags:r=!0}={}){let o=ve();o!==void 0&&(M=o);let n=r?M:o;if(n===0)return 0;if(r){if(m("color=16m")||m("color=full")||m("color=truecolor"))return 3;if(m("color=256"))return 2}if("TF_BUILD"in d&&"AGENT_NAME"in d)return 1;if(t&&!e&&n===void 0)return 0;let a=n||0;if(d.TERM==="dumb")return a;if(H.platform==="win32"){let l=be.release().split(".");return Number(l[0])>=10&&Number(l[2])>=10586?Number(l[2])>=14931?3:2:1}if("CI"in d)return"GITHUB_ACTIONS"in d||"GITEA_ACTIONS"in d?3:["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI","BUILDKITE","DRONE"].some(l=>l in d)||d.CI_NAME==="codeship"?1:a;if("TEAMCITY_VERSION"in d)return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(d.TEAMCITY_VERSION)?1:0;if(d.COLORTERM==="truecolor"||d.TERM==="xterm-kitty")return 3;if("TERM_PROGRAM"in d){let l=Number.parseInt((d.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(d.TERM_PROGRAM){case"iTerm.app":return l>=3?3:2;case"Apple_Terminal":return 2}}return/-256(color)?$/i.test(d.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(d.TERM)||"COLORTERM"in d?1:a}function Z(t,e={}){let r=Oe(t,{streamIsTTY:t&&t.isTTY,...e});return xe(r)}var Se={stdout:Z({isTTY:X.isatty(1)}),stderr:Z({isTTY:X.isatty(2)})},ee=Se;function te(t,e,r){let o=t.indexOf(e);if(o===-1)return t;let n=e.length,a=0,l="";do l+=t.slice(a,o)+e+r,a=o+n,o=t.indexOf(e,a);while(o!==-1);return l+=t.slice(a),l}function re(t,e,r,o){let n=0,a="";do{let l=t[o-1]==="\r";a+=t.slice(n,l?o-1:o)+e+(l?`\r
2
- `:`
3
- `)+r,n=o+1,o=t.indexOf(`
4
- `,n)}while(o!==-1);return a+=t.slice(n),a}var{stdout:oe,stderr:ne}=ee,Y=Symbol("GENERATOR"),x=Symbol("STYLER"),C=Symbol("IS_EMPTY"),ie=["ansi","ansi","ansi256","ansi16m"],O=Object.create(null),Re=(t,e={})=>{if(e.level&&!(Number.isInteger(e.level)&&e.level>=0&&e.level<=3))throw new Error("The `level` option should be an integer from 0 to 3");let r=oe?oe.level:0;t.level=e.level===void 0?r:e.level};var Ee=t=>{let e=(...r)=>r.join(" ");return Re(e,t),Object.setPrototypeOf(e,N.prototype),e};function N(t){return Ee(t)}Object.setPrototypeOf(N.prototype,Function.prototype);for(let[t,e]of Object.entries(h))O[t]={get(){let r=P(this,K(e.open,e.close,this[x]),this[C]);return Object.defineProperty(this,t,{value:r}),r}};O.visible={get(){let t=P(this,this[x],!0);return Object.defineProperty(this,"visible",{value:t}),t}};var J=(t,e,r,...o)=>t==="rgb"?e==="ansi16m"?h[r].ansi16m(...o):e==="ansi256"?h[r].ansi256(h.rgbToAnsi256(...o)):h[r].ansi(h.rgbToAnsi(...o)):t==="hex"?J("rgb",e,r,...h.hexToRgb(...o)):h[r][t](...o),Te=["rgb","hex","ansi256"];for(let t of Te){O[t]={get(){let{level:r}=this;return function(...o){let n=K(J(t,ie[r],"color",...o),h.color.close,this[x]);return P(this,n,this[C])}}};let e="bg"+t[0].toUpperCase()+t.slice(1);O[e]={get(){let{level:r}=this;return function(...o){let n=K(J(t,ie[r],"bgColor",...o),h.bgColor.close,this[x]);return P(this,n,this[C])}}}}var Fe=Object.defineProperties(()=>{},{...O,level:{enumerable:!0,get(){return this[Y].level},set(t){this[Y].level=t}}}),K=(t,e,r)=>{let o,n;return r===void 0?(o=t,n=e):(o=r.openAll+t,n=e+r.closeAll),{open:t,close:e,openAll:o,closeAll:n,parent:r}},P=(t,e,r)=>{let o=(...n)=>Ce(o,n.length===1?""+n[0]:n.join(" "));return Object.setPrototypeOf(o,Fe),o[Y]=t,o[x]=e,o[C]=r,o},Ce=(t,e)=>{if(t.level<=0||!e)return t[C]?"":e;let r=t[x];if(r===void 0)return e;let{openAll:o,closeAll:n}=r;if(e.includes("\x1B"))for(;r!==void 0;)e=te(e,r.close,r.open),r=r.parent;let a=e.indexOf(`
5
- `);return a!==-1&&(e=re(e,n,o,a)),o+e+n};Object.defineProperties(N.prototype,O);var Ne=N(),nt=N({level:ne?ne.level:0});var y=Ne;import{oraPromise as Ae}from"ora";var ae,se=((ae=process==null?void 0:process.versions)==null?void 0:ae.node)!==void 0,Ie={verbose:!1,log(...t){if(!this.verbose)return;let e=y.gray("[Log]");se?console.warn(e,...t):console.log(e,...t)},logPrefixed(t,...e){this.info(y.blue(`[${t}]`),...e)},info(...t){if(!this.verbose)return;let e=y.green("[Info]");se?console.warn(e,...t):console.info(e,...t)},infoPrefixed(t,...e){this.info(y.blue(`[${t}]`),...e)},async infoSpin(t,e){return Ae(t,{prefixText:y.green("[Info]"),stream:process.stderr,text:e})},warn(...t){console.warn(y.yellow("[Warning]"),...t)},warnPrefixed(t,...e){this.warn(y.blue(`[${t}]`),...e)},error(...t){console.error(y.red("[Error]"),...t)},errorPrefixed(t,...e){this.error(y.blue(`[${t}]`),...e)}},f=Ie;import{execa as le}from"execa";import B from"ffmpeg-static";import ce from"node:fs/promises";import W from"node:path";async function V(t){return(await ce.readdir(t)).filter(o=>{let n=W.extname(o);return[".mp4",".mov",".avi",".mkv"].includes(n)}).map(o=>W.join(t,o))}async function de(t){if(B===null)throw new Error("ffmpeg-static failed to locate ffmpeg binary");try{let{stderr:e,stdout:r}=await le(B,["-i",t,"-f","ffmetadata","-"]);return r.includes("creation_time")||e.includes("creation_time")}catch(e){throw new Error(`Failed to check metadata for: ${t}:
6
- ${String(e)}`)}}async function fe(t){if(B===null)throw new Error("ffmpeg-static failed to locate ffmpeg binary");try{let e=W.extname(t).slice(1),r=`${t.slice(0,Math.max(0,t.lastIndexOf(".")))}.temp.${e}`;await le(B,["-i",t,"-map_metadata","-1","-c:v","copy","-c:a","copy",r]),await ce.rename(r,t)}catch(e){throw new Error(`Failed to strip metadata from: ${t}:
7
- ${String(e)}`)}}async function $e(t,e={}){let{dryRun:r=!1,verbose:o=!1}=e,n=await V(t);return Me(n,{dryRun:r,verbose:o})}async function Me(t,e={}){let{dryRun:r=!1,verbose:o=!1}=e,n=f.verbose;f.verbose=o;let a=[];for(let l of t)await de(l)&&a.push(l);if(!r){f.info(`Found ${a.length} videos with metadata to strip`);for(let l of a)console.log(`Stripping metadata from: ${l}`),await fe(l)}return f.verbose=n,a}import{BunnyCdnStream as He}from"bunnycdn-stream";import{hash as Ye}from"hasha";import{readFile as Le}from"node:fs/promises";import{rename as Pe,writeFile as Be}from"node:fs/promises";import{basename as Ve,dirname as De,join as ke}from"node:path";import{fileURLToPath as _e}from"node:url";function je(t){let e=t instanceof URL?_e(t):t.toString();return ke(De(e),`.${Ve(e)}.tmp`)}async function Ue(t,e,r){for(let o=0;o<e;o++)try{return await t()}catch(n){if(o<e-1)await new Promise(a=>setTimeout(a,r));else throw n}}var D=class{#e;#t;#r=!1;#n=null;#i=null;#s=null;#o=null;#a(e){return this.#o=e,this.#s||=new Promise((r,o)=>{this.#i=[r,o]}),new Promise((r,o)=>{var n;(n=this.#s)==null||n.then(r).catch(o)})}async#l(e){var r,o;this.#r=!0;try{await Be(this.#t,e,"utf-8"),await Ue(async()=>{await Pe(this.#t,this.#e)},10,100),(r=this.#n)==null||r[0]()}catch(n){throw n instanceof Error&&((o=this.#n)==null||o[1](n)),n}finally{if(this.#r=!1,this.#n=this.#i,this.#i=this.#s=null,this.#o!==null){let n=this.#o;this.#o=null,await this.write(n)}}}constructor(e){this.#e=e,this.#t=je(e)}async write(e){return this.#r?this.#a(e):this.#l(e)}};var k=class{#e;#t;constructor(e){this.#e=e,this.#t=new D(e)}async read(){let e;try{e=await Le(this.#e,"utf-8")}catch(r){if(r.code==="ENOENT")return null;throw r}return e}write(e){return this.#t.write(e)}};var _=class{#e;#t;#r;constructor(e,{parse:r,stringify:o}){this.#e=new k(e),this.#t=r,this.#r=o}async read(){let e=await this.#e.read();return e===null?null:this.#t(e)}write(e){return this.#e.write(this.#r(e))}};var j=class extends _{constructor(e){super(e,{parse:JSON.parse,stringify:r=>JSON.stringify(r,null,2)})}};var U=class{#e=null;read(){return Promise.resolve(this.#e)}write(e){return this.#e=e,Promise.resolve()}};function Ge(t,e){if(t===void 0)throw new Error("lowdb: missing adapter");if(e===void 0)throw new Error("lowdb: missing default data")}var L=class{adapter;data;constructor(e,r){Ge(e,r),this.adapter=e,this.data=r}async read(){let e=await this.adapter.read();e&&(this.data=e)}async write(){this.data&&await this.adapter.write(this.data)}async update(e){e(this.data),await this.write()}};async function ue(t,e){let r=process.env.NODE_ENV==="test"?new U:new j(t),o=new L(r,e);return await o.read(),o}import{createReadStream as pe}from"node:fs";import A from"node:path";async function Je(t,e){let{credentials:r,dryRun:o=!1,service:n,verbose:a=!1}=e,l=f.verbose;f.verbose=a;let g=[],I=await V(t);f.info(`Found ${I.length} video files in directory "${t}"`);let u=await ue(A.join(t,".vidup-state.json"),{syncState:[]});u.data.syncState=u.data.syncState.filter(i=>I.includes(A.join(t,i.filename)));for(let i of I){let s=A.basename(i),p=await Ye(i,{algorithm:"sha1"}),w=u.data.syncState.find(b=>b.filename===s);w?w.localHash=p:u.data.syncState.push({filename:s,localHash:p,remoteHash:{}})}if(o||await u.write(),n!=="bunny")throw new Error(`Streaming service not yet implemented: ${n}`);let S=new He({apiKey:r.key,videoLibrary:r.library}),R=await S.listAllVideos();f.info(`Found ${R.length} video files on ${n} remote streaming service`);let me=R.filter(i=>{let s=u.data.syncState.find(p=>p.filename===i.title);return s!==void 0&&s.localHash===s.remoteHash[n]});for(let i of me)g.push({action:"None",localFile:i.title,remoteId:i.guid});let E=u.data.syncState.filter(i=>!R.some(s=>s.title===i.filename));for(let i of E)g.push({action:"Create",localFile:i.filename,remoteId:"Not yet uploaded (Dry run)"});let T=R.filter(i=>{let s=u.data.syncState.find(p=>p.filename===i.title);return s!==void 0&&s.localHash!==s.remoteHash[n]});for(let i of T)g.push({action:"Update",localFile:i.title,remoteId:"Not yet uploaded (Dry run)"});let $=R.filter(i=>!u.data.syncState.some(s=>s.filename===i.title));for(let i of $)g.push({action:"Delete",localFile:i.title,remoteId:i.guid});if(!o){u.data.lastRun=new Date,await u.write(),f.info("Synchronizing...");for(let[i,s]of $.entries()){if(i===0&&f.info(`Deleting ${$.length} remote videos...`),!(await f.infoSpin(S.deleteVideo(s.guid),`Deleting remote video ${i+1}/${$.length}: ${s.title}`)).success)throw new Error(`Failed to delete remote video: ${s.title}`);u.data.lastUpdate=new Date,await u.write()}for(let[i,s]of E.entries()){i===0&&f.info(`Uploading ${E.length} new local videos...`);let p=pe(A.join(t,s.filename)),w=await f.infoSpin(S.createAndUploadVideo(p,{title:s.filename}),`Uploading new video ${i+1}/${E.length}: ${s.filename}`);f.info(`Remote video ${i+1}/${E.length} created with GUID: ${w.guid}`);let b=u.data.syncState.find(v=>v.filename===s.filename);if(b===void 0)throw new Error(`Failed to find state entry for: ${s.filename}`);b.remoteHash[n]=s.localHash,u.data.lastUpdate=new Date,await u.write();let F=g.find(v=>v.localFile===s.filename);if(F===void 0)throw new Error(`Failed to find report entry for: ${s.filename}`);F.remoteId=w.guid}for(let[i,s]of T.entries()){i===0&&(f.info(`Updating ${T.length} remote videos...`),f.warn("Bunny does not currently support updating videos in-place, so vidup will delete and re-uploading instead... this will change the video's GUID!"));let p=u.data.syncState.find(G=>G.filename===s.title);if(p===void 0)throw new Error(`Failed to find state entry for: ${s.title}`);if(!(await S.deleteVideo(s.guid)).success)throw new Error(`Failed to delete remote video before updating: ${s.title}`);let b=pe(A.join(t,p.filename)),F=await f.infoSpin(S.createAndUploadVideo(b,{title:p.filename}),`Updating remote video ${i+1}/${T.length}: ${s.title}`);f.info(`Updated remote video ${i+1}/${T.length} created with GUID: ${F.guid}`),p.remoteHash[n]=p.localHash,u.data.lastUpdate=new Date,await u.write();let v=g.find(G=>G.localFile===p.filename);if(v===void 0)throw new Error(`Failed to find report entry for: ${p.filename}`);v.remoteId=F.guid}f.info(`All ${I.length} videos are now in sync!`)}return f.verbose=l,g}export{f as log,$e as stripVideoMetadataInDirectory,Je as syncVideoInDirectory};
package/dist/strip.d.ts DELETED
@@ -1,14 +0,0 @@
1
- export type StripOptions = {
2
- dryRun?: boolean;
3
- verbose?: boolean;
4
- };
5
- /**
6
- * Strip metadata from video files in a directory
7
- * @returns List of file paths with metadata that were stripped
8
- */
9
- export declare function stripVideoMetadataInDirectory(directory: string, options?: StripOptions): Promise<string[]>;
10
- /**
11
- * Strip metadata from multiple video files
12
- * @returns List of file paths with metadata that were stripped
13
- */
14
- export declare function stripVideoMetadataInFiles(files: string[], options?: StripOptions): Promise<string[]>;
package/dist/sync.d.ts DELETED
@@ -1,21 +0,0 @@
1
- export type Service = 'bunny' | 'cloudflare' | 'mux';
2
- export type SyncReport = Array<{
3
- action: 'Create' | 'Delete' | 'None' | 'Update';
4
- localFile?: string;
5
- remoteId: string;
6
- stripMetadata?: boolean;
7
- }>;
8
- export type SyncOptions = {
9
- credentials: {
10
- key: string;
11
- library: string;
12
- };
13
- dryRun?: boolean;
14
- service: Service;
15
- verbose?: boolean;
16
- };
17
- /**
18
- * Synchronize a remote streaming service to mirror the contents of a local directory
19
- * @returns Array of sync report entries describing what was changed (or will change if a dry run)
20
- */
21
- export declare function syncVideoInDirectory(directory: string, options: SyncOptions): Promise<SyncReport>;
@@ -1,13 +0,0 @@
1
- declare const log: {
2
- verbose: boolean;
3
- log(...data: unknown[]): void;
4
- logPrefixed(prefix: string, ...data: unknown[]): void;
5
- info(...data: unknown[]): void;
6
- infoPrefixed(prefix: string, ...data: unknown[]): void;
7
- infoSpin<T>(promise: Promise<T>, message: string): Promise<T>;
8
- warn(...data: unknown[]): void;
9
- warnPrefixed(prefix: string, ...data: unknown[]): void;
10
- error(...data: unknown[]): void;
11
- errorPrefixed(prefix: string, ...data: unknown[]): void;
12
- };
13
- export default log;
@@ -1,3 +0,0 @@
1
- export declare function getVideosInDirectory(directory: string): Promise<string[]>;
2
- export declare function videoHasMetadata(filename: string): Promise<boolean>;
3
- export declare function stripVideoMetadata(filename: string): Promise<void>;