@barbapapazes/video-toolkit 0.11.0 → 0.12.0

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/cli.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import{basename as e,dirname as t,extname as n,isAbsolute as r,join as i,resolve as a}from"node:path";import o from"node:process";import{intro as s,isCancel as c,log as l,outro as u,select as d,spinner as f,text as p}from"@clack/prompts";import{cac as m}from"cac";import{homedir as h}from"node:os";import{loadConfig as g}from"c12";import{copyFileSync as _,createReadStream as ee,existsSync as v,mkdirSync as y,readFileSync as b,readdirSync as x,statSync as S,unlinkSync as C,writeFileSync as w}from"node:fs";import{format as T,parse as E}from"date-fns";import{Buffer as D}from"node:buffer";import{ofetch as O}from"ofetch";import k from"sharp";import{execSync as A}from"node:child_process";import te from"openai";var j=`0.11.0`;const M={openaiApiKey:``,language:`fr`,model:`whisper-1`,templatesDir:i(h(),`.config`,`video-toolkit`,`templates`)};async function N(){let{config:e}=await g({name:`video-toolkit`,defaults:M,globalRc:!0});return{openaiApiKey:e.openaiApiKey||``,language:e.language||`fr`,model:e.model||`whisper-1`,templatesDir:e.templatesDir}}function P(t){let r=a(t),o=e(r,n(r)),s=a(r,`..`),c=i(s,`${o}_audio.mp3`),l=i(s,`${o}.srt`),u=i(s,`thumbnails`);v(u)||y(u,{recursive:!0});let d=[`first`,`25`,`50`,`75`,`last`];return{videoPath:r,audioPath:c,srtPath:l,thumbnailTempPaths:d.map(e=>i(u,`${o}_thumbnail_${e}_temp.png`)),thumbnailPaths:d.map(e=>i(u,`${o}_thumbnail_${e}.png`))}}function F(e){return r(e)?e:a(o.cwd(),e)}function I(e){let t=F(e);if(!v(t))return[];try{return x(t,{withFileTypes:!0}).filter(e=>e.isFile()&&n(e.name).toLowerCase()===`.svg`).map(e=>e.name.replace(/\.svg$/i,``))}catch{return[]}}function L(e,t){return i(F(t),`${e}.svg`)}function R(){let e=i(h(),`.config`,`video-toolkit`,`fonts`);return v(e)||y(e,{recursive:!0}),e}function z(){let e=R(),n=t(e),r=i(n,`fonts.conf`);v(r)||w(r,`<?xml version="1.0"?>
2
+ import{t as e}from"./config-Cuh3n7mj.mjs";import t from"node:process";import{intro as n,isCancel as r,log as i,outro as a,select as o,spinner as s,text as c}from"@clack/prompts";import{cac as l}from"cac";import{copyFileSync as u,createReadStream as d,existsSync as f,mkdirSync as p,readFileSync as m,readdirSync as h,statSync as g,unlinkSync as _,writeFileSync as v}from"node:fs";import{basename as y,dirname as b,extname as x,isAbsolute as S,join as C,resolve as w}from"node:path";import{Buffer as T}from"node:buffer";import{homedir as ee}from"node:os";import{ofetch as E}from"ofetch";import D from"sharp";import{format as O,parse as k}from"date-fns";import{execSync as A}from"node:child_process";import j from"openai";var M=`0.12.0`;function N(e){return S(e)?e:w(t.cwd(),e)}function P(e){let t=N(e);if(!f(t))return[];try{return h(t,{withFileTypes:!0}).filter(e=>e.isFile()&&x(e.name).toLowerCase()===`.svg`).map(e=>e.name.replace(/\.svg$/i,``))}catch{return[]}}function F(e,t){return C(N(t),`${e}.svg`)}function I(){let e=C(ee(),`.config`,`video-toolkit`,`fonts`);return f(e)||p(e,{recursive:!0}),e}function te(){let e=I(),n=b(e),r=C(n,`fonts.conf`);f(r)||v(r,`<?xml version="1.0"?>
3
3
  <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
4
4
  <fontconfig>
5
5
  <dir>${e}</dir>
6
- <cachedir>${i(n,`fontcache`)}</cachedir>
7
- </fontconfig>`,`utf-8`),o.env.FONTCONFIG_PATH=n,o.platform===`darwin`&&(o.env.PANGOCAIRO_BACKEND=`fontconfig`)}async function B(e,t){try{let n=await O(`https://fonts.googleapis.com/css2?family=${e.replace(/\s+/g,`+`)}:wght@${t}&display=swap`),r=n.match(/url\((https:\/\/fonts\.gstatic\.com\/[^)]+)\)/),a=n.match(/format\('([^']+)'\)/);if(!r||!a)return null;let o=r[1],s=a[1],c=await O(o,{responseType:`arrayBuffer`}),l=i(R(),`${e.replace(/\s+/g,``)}-${t}.${s===`woff2`?`woff2`:s===`woff`?`woff`:`ttf`}`);return w(l,D.from(c)),console.warn(`Downloaded and saved "${e}" weight ${t} to ${l}`),l}catch(n){return console.warn(`Could not download font "${e}" weight ${t} from Google Fonts:`,n),null}}function V(e,t){let n=new Set,r=RegExp(`<text[^>]*font-family="${t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}"[^>]*>`,`g`),i=r.exec(e);for(;i!==null;){let t=i[0].match(/font-weight="(\d+)"/);t?n.add(t[1]):n.add(`400`),i=r.exec(e)}return n.size>0?Array.from(n):[`400`]}async function H(r,i){let a=t(i),o=R(),s=/font-family="([^"]+)"/g,c=new Set,l=s.exec(r);for(;l!==null;)c.add(l[1]),l=s.exec(r);if(c.size===0)return r;for(let t of c){let i=t.replace(/\s+/g,``),s=!1;try{let t=x(a,{withFileTypes:!0});for(let r of t){if(!r.isFile())continue;let t=r.name,a=n(t).toLowerCase();[`.ttf`,`.woff`,`.woff2`].includes(a)&&e(t,a).toLowerCase().includes(i.toLowerCase())&&(s=!0)}}catch{}if(!s)try{let e=x(o,{withFileTypes:!0});for(let t of e)if(t.isFile()&&t.name.toLowerCase().includes(i.toLowerCase())){s=!0;break}}catch{}if(!s){console.warn(`Font "${t}" not found, downloading from Google Fonts...`);let e=V(r,t);for(let n of e)await B(t,n)}}return r}async function U(e,t,n){let i=r(e)?e:L(e,n);if(!v(i))throw Error(`SVG template not found at ${i}. Please ensure the template exists in ${F(n)}.`);let a;try{a=b(i,`utf-8`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to load SVG template from ${i}: ${t}`)}await H(a,i);let o=t.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&apos;`);return a.replace(/\{\{text\}\}/g,o)}async function W(e,t,n,r,i){try{z();let a=await U(r,n,i),o=await k(e).metadata();if(!o.width||!o.height)throw Error(`Could not determine image dimensions`);let s=await k(D.from(a),{density:300}).resize(o.width,o.height,{fit:`fill`}).png().toBuffer();await k(e).composite([{input:s,blend:`over`}]).toFile(t)}catch(e){throw console.error(`Error adding text to image:`,e),e}}function G(e=o.cwd()){let t=[];try{let r=x(e).filter(t=>S(i(e,t)).isDirectory()&&/^\d{4}$/.test(t));for(let a of r){let r=i(e,a),o=x(r).filter(e=>S(i(r,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of o){let o=i(r,e),s=x(o).filter(e=>S(i(o,e)).isDirectory()&&/^\d{2}$/.test(e));for(let r of s){let s=i(o,r),c=x(s,{withFileTypes:!0}).filter(e=>e.isFile()&&n(e.name).toLowerCase()===`.mp4`).map(e=>e.name);if(c.length>0){let n=`${a}-${e}-${r}`;t.push({path:s,date:new Date(n),displayPath:`${a}/${e}/${r}`,videoFile:c[0]})}}}}}catch(e){return l.error(`Error reading directories: ${e}`),[]}return t.sort((e,t)=>t.date.getTime()-e.date.getTime()).slice(0,8)}async function K(e=o.cwd()){let t=G(e);t.length===0&&(l.error(`No dated folders with video files found.`),l.info(`Expected structure: YYYY/MM/DD/*.mp4`),o.exit(1));let r=t.map(e=>({label:`${e.displayPath} (${e.videoFile})`,value:e.path,hint:`Process video from ${e.displayPath}`}));r.push({label:`Custom date`,value:`custom`,hint:`Enter a specific date`});let a=await d({message:`Select a content folder with video:`,options:r});if(c(a)&&(l.error(`Operation cancelled.`),o.exit(0)),a===`custom`){let t=await p({message:`Enter date (YYYY-MM-DD):`,placeholder:T(new Date,`yyyy-MM-dd`),validate:e=>{if(!e)return`Date is required`;if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return`Invalid date format. Please use YYYY-MM-DD`;try{let t=E(e,`yyyy-MM-dd`,new Date);if(Number.isNaN(t.getTime()))return`Invalid date`}catch{return`Invalid date`}}});c(t)&&(l.error(`Operation cancelled.`),o.exit(0));let r=E(t,`yyyy-MM-dd`,new Date),a=i(e,T(r,`yyyy`),T(r,`MM`),T(r,`dd`));v(a)||(l.error(`Folder does not exist: ${a}`),o.exit(1));let s=x(a,{withFileTypes:!0}).filter(e=>e.isFile()&&n(e.name).toLowerCase()===`.mp4`).map(e=>e.name);return s.length===0&&(l.error(`No .mp4 files found in: ${a}`),o.exit(1)),{folderPath:a,videoPath:i(a,s[0])}}let s=t.find(e=>e.path===a);s||(l.error(`Selected folder not found.`),o.exit(1));let u=i(s.path,s.videoFile);return{folderPath:s.path,videoPath:u}}async function q(){let e=await p({message:`Enter text for the thumbnail (or press Enter to skip):`,placeholder:`Optional thumbnail text`});if(typeof e!=`symbol`)return e.trim()||void 0}async function J(e){let t=I(e);if(t.length===0){l.error(`No templates found in ${e}`),l.info(`Please add SVG templates to ${e} before proceeding.`);return}let n=await d({message:`Choose a template:`,options:t.map(e=>({label:e,value:e}))});if(typeof n!=`symbol`)return n}function Y(e,t){let n=`ffmpeg -i ${e} -q:a 0 -map a ${t}`;try{A(n,{stdio:`inherit`})}catch(e){console.error(`Error extracting audio:`,e)}}function X(e){try{let t=A(`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${e}"`,{encoding:`utf-8`});return Number.parseFloat(t.trim())}catch(e){throw console.error(`Error getting video duration:`,e),e}}function Z(e,t,n=`first`){let r=``;r=n===`first`?`ffmpeg -i "${e}" -vf "select=eq(n\\,0)" -vframes 1 "${t}" -y`:n===`last`?`ffmpeg -sseof -0.1 -i "${e}" -vframes 1 "${t}" -y`:`ffmpeg -ss ${X(e)*(Number.parseInt(n)/100)} -i "${e}" -vframes 1 "${t}" -y`;try{A(r,{stdio:`inherit`})}catch(e){throw console.error(`Error extracting thumbnail:`,e),e}}async function ne(e,t){return await new te({apiKey:t.openaiApiKey}).audio.transcriptions.create({file:ee(e),model:t.model,language:t.language,response_format:`srt`})}async function re(e,t){let n=f();n.start(`Extracting audio...`);try{Y(e,t),n.stop(`Audio extracted successfully`)}catch(e){n.stop(`Audio extraction failed`),l.error(`Error during audio extraction: ${e}`),o.exit(1)}}async function ie(e,t,n){let r=f();r.start(`Generating transcription with OpenAI...`);try{w(t,await ne(e,n)),r.stop(`Transcription generated successfully`)}catch(t){r.stop(`Transcription generation failed`),l.error(`Error during transcription generation: ${t}`);try{C(e)}catch{}o.exit(1)}}async function ae(e,t,n){let r=f(),i=n===`first`?`start`:n===`last`?`end`:`${n}%`;r.start(`Extracting thumbnail at ${i}...`);try{Z(e,t,n),r.stop(`Thumbnail extracted successfully`)}catch(e){r.stop(`Thumbnail extraction failed`),l.error(`Error during thumbnail extraction: ${e}`),o.exit(1)}}async function oe(e,t,n,r,i){let a=f();a.start(`Adding text to thumbnail...`);try{await W(e,t,n,r,i),a.stop(`Text added to thumbnail successfully`)}catch(e){a.stop(`Failed to add text to thumbnail`),l.error(`Error adding text to thumbnail: ${e}`),o.exit(1)}}function Q(e,t=`temporary file`){try{C(e)}catch{l.warn(`Could not delete ${t}: ${e}`)}}async function se(e){s(`Video Toolkit - Add Template`);let t=await N(),n=a(e);v(n)||(l.error(`Source file not found: ${n}`),o.exit(1)),n.toLowerCase().endsWith(`.svg`)||(l.error(`Source file must be an SVG file`),o.exit(1));let r=await p({message:`Enter a name for this template:`,placeholder:`e.g., series-x, youtube-intro`,validate:e=>{if(!e||e.trim()===``)return`Template name is required`;if(!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(e))return`Template name should start and end with lowercase letters or numbers, and can contain hyphens in between`}});typeof r==`symbol`&&(l.error(`Template name is required. Exiting.`),o.exit(1));let i=F(t.templatesDir);v(i)||(y(i,{recursive:!0}),l.success(`Created templates directory: ${i}`));let c=a(i,`${r}.svg`);v(c)&&l.warn(`Template '${r}' already exists. Overwriting...`);try{_(n,c),u(`✓ Template '${r}' added successfully at ${c}`)}catch(e){let t=e instanceof Error?e.message:String(e);l.error(`Failed to copy template: ${t}`),o.exit(1)}}const $=m(`video-toolkit`);async function ce(t){let n=await N();n.openaiApiKey||(l.error(`OpenAI API key is required. Please set it in:`),l.error(` - Config file: ~/.video-toolkitrc`),o.exit(1));let{videoPath:r,audioPath:i,srtPath:a,thumbnailTempPaths:c,thumbnailPaths:d}=P(t);s(`Video Toolkit - Processing: ${e(r)}`),await re(r,i),await ie(i,a,n),Q(i,`audio file`);let f=await q();if(f){let e=await J(n.templatesDir);if(!e){l.warn(`No template selected. Skipping thumbnail generation.`),u(`✓ Transcription saved to: ${a}`);return}let t=[`first`,`25`,`50`,`75`,`last`];for(let i=0;i<t.length;i++)await ae(r,c[i],t[i]),await oe(c[i],d[i],f,e,n.templatesDir),Q(c[i],`temporary thumbnail`);u(`✓ Transcription saved to: ${a}\n✓ Thumbnails saved:\n - ${d.join(`
8
- - `)}`)}else u(`✓ Transcription saved to: ${a}`)}$.command(`[path]`,`Process a video from dated content folders`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(e,t)=>{let n=e||t?.path||o.cwd();s(`Video Toolkit - Process video from content folder`);let{videoPath:r}=await K(n);await ce(r)}),$.command(`add-template <file>`,`Add an SVG template to the templates directory`).action(async e=>{await se(e)}),$.help(),$.version(j),$.parse();export{};
6
+ <cachedir>${C(n,`fontcache`)}</cachedir>
7
+ </fontconfig>`,`utf-8`),t.env.FONTCONFIG_PATH=n,t.platform===`darwin`&&(t.env.PANGOCAIRO_BACKEND=`fontconfig`)}async function ne(e,t){try{let n=await E(`https://fonts.googleapis.com/css2?family=${e.replace(/\s+/g,`+`)}:wght@${t}&display=swap`),r=n.match(/url\((https:\/\/fonts\.gstatic\.com\/[^)]+)\)/),i=n.match(/format\('([^']+)'\)/);if(!r||!i)return null;let a=r[1],o=i[1],s=await E(a,{responseType:`arrayBuffer`}),c=C(I(),`${e.replace(/\s+/g,``)}-${t}.${o===`woff2`?`woff2`:o===`woff`?`woff`:`ttf`}`);return v(c,T.from(s)),console.warn(`Downloaded and saved "${e}" weight ${t} to ${c}`),c}catch(n){return console.warn(`Could not download font "${e}" weight ${t} from Google Fonts:`,n),null}}function re(e,t){let n=new Set,r=RegExp(`<text[^>]*font-family="${t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}"[^>]*>`,`g`),i=r.exec(e);for(;i!==null;){let t=i[0].match(/font-weight="(\d+)"/);t?n.add(t[1]):n.add(`400`),i=r.exec(e)}return n.size>0?Array.from(n):[`400`]}async function L(e,t){let n=b(t),r=I(),i=/font-family="([^"]+)"/g,a=new Set,o=i.exec(e);for(;o!==null;)a.add(o[1]),o=i.exec(e);if(a.size===0)return e;for(let t of a){let i=t.replace(/\s+/g,``),a=!1;try{let e=h(n,{withFileTypes:!0});for(let t of e){if(!t.isFile())continue;let e=t.name,n=x(e).toLowerCase();[`.ttf`,`.woff`,`.woff2`].includes(n)&&y(e,n).toLowerCase().includes(i.toLowerCase())&&(a=!0)}}catch{}if(!a)try{let e=h(r,{withFileTypes:!0});for(let t of e)if(t.isFile()&&t.name.toLowerCase().includes(i.toLowerCase())){a=!0;break}}catch{}if(!a){console.warn(`Font "${t}" not found, downloading from Google Fonts...`);let n=re(e,t);for(let e of n)await ne(t,e)}}return e}async function R(e,t,n){let r=S(e)?e:F(e,n);if(!f(r))throw Error(`SVG template not found at ${r}. Please ensure the template exists in ${N(n)}.`);let i;try{i=m(r,`utf-8`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to load SVG template from ${r}: ${t}`)}await L(i,r);let a=t.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&apos;`);return i.replace(/\{\{text\}\}/g,a)}async function z(e,t,n,r,i){try{te();let a=await R(r,n,i),o=await D(e).metadata();if(!o.width||!o.height)throw Error(`Could not determine image dimensions`);let s=await D(T.from(a),{density:300}).resize(o.width,o.height,{fit:`fill`}).png().toBuffer();await D(e).composite([{input:s,blend:`over`}]).toFile(t)}catch(e){throw console.error(`Error adding text to image:`,e),e}}async function B(r){n(`Video Toolkit - Add Template`);let o=await e(),s=w(r);f(s)||(i.error(`Source file not found: ${s}`),t.exit(1)),s.toLowerCase().endsWith(`.svg`)||(i.error(`Source file must be an SVG file`),t.exit(1));let l=await c({message:`Enter a name for this template:`,placeholder:`e.g., series-x, youtube-intro`,validate:e=>{if(!e||e.trim()===``)return`Template name is required`;if(!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(e))return`Template name should start and end with lowercase letters or numbers, and can contain hyphens in between`}});typeof l==`symbol`&&(i.error(`Template name is required. Exiting.`),t.exit(1));let d=N(o.templatesDir);f(d)||(p(d,{recursive:!0}),i.success(`Created templates directory: ${d}`));let m=w(d,`${l}.svg`);f(m)&&i.warn(`Template '${l}' already exists. Overwriting...`);try{u(s,m),a(`✓ Template '${l}' added successfully at ${m}`)}catch(e){let n=e instanceof Error?e.message:String(e);i.error(`Failed to copy template: ${n}`),t.exit(1)}}function V(e){let t=w(e),n=y(t,x(t)),r=w(t,`..`),i=C(r,`${n}_audio.mp3`),a=C(r,`${n}.srt`),o=C(r,`thumbnails`);f(o)||p(o,{recursive:!0});let s=[`first`,`25`,`50`,`75`,`last`];return{videoPath:t,audioPath:i,srtPath:a,thumbnailTempPaths:s.map(e=>C(o,`${n}_thumbnail_${e}_temp.png`)),thumbnailPaths:s.map(e=>C(o,`${n}_thumbnail_${e}.png`))}}function H(e=t.cwd()){let n=[];try{let t=h(e).filter(t=>g(C(e,t)).isDirectory()&&/^\d{4}$/.test(t));for(let r of t){let t=C(e,r),i=h(t).filter(e=>g(C(t,e)).isDirectory()&&/^\d{2}$/.test(e));for(let e of i){let i=C(t,e),a=h(i).filter(e=>g(C(i,e)).isDirectory()&&/^\d{2}$/.test(e));for(let t of a){let a=C(i,t),o=h(a,{withFileTypes:!0}).filter(e=>e.isFile()&&x(e.name).toLowerCase()===`.mp4`).map(e=>e.name);if(o.length>0){let i=`${r}-${e}-${t}`;n.push({path:a,date:new Date(i),displayPath:`${r}/${e}/${t}`,videoFile:o[0]})}}}}}catch(e){return i.error(`Error reading directories: ${e}`),[]}return n.sort((e,t)=>t.date.getTime()-e.date.getTime()).slice(0,8)}async function U(e=t.cwd()){let n=H(e);n.length===0&&(i.error(`No dated folders with video files found.`),i.info(`Expected structure: YYYY/MM/DD/*.mp4`),t.exit(1));let a=n.map(e=>({label:`${e.displayPath} (${e.videoFile})`,value:e.path,hint:`Process video from ${e.displayPath}`}));a.push({label:`Custom date`,value:`custom`,hint:`Enter a specific date`});let s=await o({message:`Select a content folder with video:`,options:a});if(r(s)&&(i.error(`Operation cancelled.`),t.exit(0)),s===`custom`){let n=await c({message:`Enter date (YYYY-MM-DD):`,placeholder:O(new Date,`yyyy-MM-dd`),validate:e=>{if(!e)return`Date is required`;if(!/^\d{4}-\d{2}-\d{2}$/.test(e))return`Invalid date format. Please use YYYY-MM-DD`;try{let t=k(e,`yyyy-MM-dd`,new Date);if(Number.isNaN(t.getTime()))return`Invalid date`}catch{return`Invalid date`}}});r(n)&&(i.error(`Operation cancelled.`),t.exit(0));let a=k(n,`yyyy-MM-dd`,new Date),o=C(e,O(a,`yyyy`),O(a,`MM`),O(a,`dd`));f(o)||(i.error(`Folder does not exist: ${o}`),t.exit(1));let s=h(o,{withFileTypes:!0}).filter(e=>e.isFile()&&x(e.name).toLowerCase()===`.mp4`).map(e=>e.name);return s.length===0&&(i.error(`No .mp4 files found in: ${o}`),t.exit(1)),{folderPath:o,videoPath:C(o,s[0])}}let l=n.find(e=>e.path===s);l||(i.error(`Selected folder not found.`),t.exit(1));let u=C(l.path,l.videoFile);return{folderPath:l.path,videoPath:u}}async function W(){let e=await c({message:`Enter text for the thumbnail (or press Enter to skip):`,placeholder:`Optional thumbnail text`});if(typeof e!=`symbol`)return e.trim()||void 0}async function G(e){let t=P(e);if(t.length===0){i.error(`No templates found in ${e}`),i.info(`Please add SVG templates to ${e} before proceeding.`);return}let n=await o({message:`Choose a template:`,options:t.map(e=>({label:e,value:e}))});if(typeof n!=`symbol`)return n}function K(e,t){let n=`ffmpeg -i ${e} -q:a 0 -map a ${t}`;try{A(n,{stdio:`inherit`})}catch(e){console.error(`Error extracting audio:`,e)}}function q(e){try{let t=A(`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${e}"`,{encoding:`utf-8`});return Number.parseFloat(t.trim())}catch(e){throw console.error(`Error getting video duration:`,e),e}}function J(e,t,n=`first`){let r=``;r=n===`first`?`ffmpeg -i "${e}" -vf "select=eq(n\\,0)" -vframes 1 "${t}" -y`:n===`last`?`ffmpeg -sseof -0.1 -i "${e}" -vframes 1 "${t}" -y`:`ffmpeg -ss ${q(e)*(Number.parseInt(n)/100)} -i "${e}" -vframes 1 "${t}" -y`;try{A(r,{stdio:`inherit`})}catch(e){throw console.error(`Error extracting thumbnail:`,e),e}}async function Y(e,t){return await new j({apiKey:t.openaiApiKey}).audio.transcriptions.create({file:d(e),model:t.model,language:t.language,response_format:`srt`})}async function ie(e,n){let r=s();r.start(`Extracting audio...`);try{K(e,n),r.stop(`Audio extracted successfully`)}catch(e){r.stop(`Audio extraction failed`),i.error(`Error during audio extraction: ${e}`),t.exit(1)}}async function ae(e,n,r){let a=s();a.start(`Generating transcription with OpenAI...`);try{v(n,await Y(e,r)),a.stop(`Transcription generated successfully`)}catch(n){a.stop(`Transcription generation failed`),i.error(`Error during transcription generation: ${n}`);try{_(e)}catch{}t.exit(1)}}async function oe(e,n,r){let a=s(),o=r===`first`?`start`:r===`last`?`end`:`${r}%`;a.start(`Extracting thumbnail at ${o}...`);try{J(e,n,r),a.stop(`Thumbnail extracted successfully`)}catch(e){a.stop(`Thumbnail extraction failed`),i.error(`Error during thumbnail extraction: ${e}`),t.exit(1)}}async function se(e,n,r,a,o){let c=s();c.start(`Adding text to thumbnail...`);try{await z(e,n,r,a,o),c.stop(`Text added to thumbnail successfully`)}catch(e){c.stop(`Failed to add text to thumbnail`),i.error(`Error adding text to thumbnail: ${e}`),t.exit(1)}}function X(e,t=`temporary file`){try{_(e)}catch{i.warn(`Could not delete ${t}: ${e}`)}}const Z=[`first`,`25`,`50`,`75`,`last`];async function Q(e,t,n,r,i,a){for(let o=0;o<Z.length;o++)await oe(e,t[o],Z[o]),await se(t[o],n[o],r,i,a),X(t[o],`temporary thumbnail`)}async function ce(e,t){let{videoPath:r,thumbnailTempPaths:o,thumbnailPaths:s}=V(e);n(`Video Toolkit - Generate Thumbnails: ${y(r)}`);let c=await W();if(!c){i.warn(`No text provided. Thumbnails require text overlay.`),a(`Operation cancelled`);return}let l=await G(t.templatesDir);if(!l){i.warn(`No template selected. Cannot generate thumbnails.`),a(`Operation cancelled`);return}await Q(r,o,s,c,l,t.templatesDir),a(`✓ Thumbnails saved:\n - ${s.join(`
8
+ - `)}`)}async function le(e,t){let{videoPath:r,audioPath:o,srtPath:s,thumbnailTempPaths:c,thumbnailPaths:l}=V(e);n(`Video Toolkit - Processing: ${y(r)}`),await ie(r,o),await ae(o,s,t),X(o,`audio file`);let u=await W();if(u){let e=await G(t.templatesDir);if(!e){i.warn(`No template selected. Skipping thumbnail generation.`),a(`✓ Transcription saved to: ${s}`);return}await Q(r,c,l,u,e,t.templatesDir),a(`✓ Transcription saved to: ${s}\n✓ Thumbnails saved:\n - ${l.join(`
9
+ - `)}`)}else a(`✓ Transcription saved to: ${s}`)}const $=l(`video-toolkit`);$.command(`[path]`,`Process a video from dated content folders`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(r,a)=>{let o=r||a?.path||t.cwd();n(`Video Toolkit - Process video from content folder`);let s=await e();s.openaiApiKey||(i.error(`OpenAI API key is required. Please set it in:`),i.error(` - Config file: ~/.video-toolkitrc`),t.exit(1));let{videoPath:c}=await U(o);await le(c,s)}),$.command(`add-template <file>`,`Add an SVG template to the templates directory`).action(async e=>{await B(e)}),$.command(`generate-thumbnail [path]`,`Generate thumbnails for a video without regenerating transcription`).option(`--path <path>`,`Base path for content directory (defaults to current directory)`).action(async(n,r)=>{let i=n||r?.path||t.cwd(),a=await e(),{videoPath:o}=await U(i);await ce(o,a)}),$.help(),$.version(M),$.parse();export{};
@@ -0,0 +1 @@
1
+ import{join as e}from"node:path";import{homedir as t}from"node:os";import{loadConfig as n}from"c12";const r={openaiApiKey:``,language:`fr`,model:`whisper-1`,templatesDir:e(t(),`.config`,`video-toolkit`,`templates`)};async function i(){let{config:e}=await n({name:`video-toolkit`,defaults:r,globalRc:!0});return{openaiApiKey:e.openaiApiKey||``,language:e.language||`fr`,model:e.model||`whisper-1`,templatesDir:e.templatesDir}}export{i as t};
package/dist/index.d.mts CHANGED
@@ -24,8 +24,12 @@ interface Config {
24
24
  */
25
25
  templatesDir?: string;
26
26
  }
27
+ type ResolvedConfig = Required<Config>;
28
+ //#endregion
29
+ //#region src/utils/config.d.ts
30
+ declare function loadVideoToolkitConfig(): Promise<ResolvedConfig>;
27
31
  //#endregion
28
32
  //#region src/index.d.ts
29
33
  declare function defineConfig(config: Config): Config;
30
34
  //#endregion
31
- export { defineConfig };
35
+ export { type Config, type ResolvedConfig, defineConfig, loadVideoToolkitConfig };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- function e(e){return e}export{e as defineConfig};
1
+ import{t as e}from"./config-Cuh3n7mj.mjs";function t(e){return e}export{t as defineConfig,e as loadVideoToolkitConfig};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@barbapapazes/video-toolkit",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "author": "Estéban Soubiran <esteban@soubiran.dev>",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/Barbapapazes",