From e78de6f6de4a76e1ef282dc1956c4625dc8e67af Mon Sep 17 00:00:00 2001 From: Sahil Date: Tue, 12 Aug 2025 20:53:23 +0530 Subject: [PATCH 1/6] vtracer implementation in converter directory --- src/converters/main.ts | 6 +++ src/converters/vtracer.ts | 91 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/converters/vtracer.ts diff --git a/src/converters/main.ts b/src/converters/main.ts index 8c59e55..fc33318 100644 --- a/src/converters/main.ts +++ b/src/converters/main.ts @@ -21,6 +21,8 @@ import { convert as convertPotrace, properties as propertiesPotrace } from "./po import { convert as convertresvg, properties as propertiesresvg } from "./resvg"; import { convert as convertImage, properties as propertiesImage } from "./vips"; import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex"; +import {convert as convertVtracer, properties as propertiesVtracer} from './vtracer'; + // This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular @@ -117,6 +119,10 @@ const properties: Record< properties: propertiesPotrace, converter: convertPotrace, }, + vtracer: { + properties: propertiesVtracer, + converter: convertVtracer, + } }; function chunks(arr: T[], size: number): T[][] { diff --git a/src/converters/vtracer.ts b/src/converters/vtracer.ts new file mode 100644 index 0000000..b81febd --- /dev/null +++ b/src/converters/vtracer.ts @@ -0,0 +1,91 @@ +import { execFile as execFileOriginal } from "node:child_process"; +import { ExecFileFn } from "./types"; + +export const properties = { + from: { + images: ["jpg", "jpeg", "png", "bmp", "gif", "tiff", "tif", "webp"], + }, + to: { + images: ["svg"], + }, +}; + +export function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + options?: unknown, + execFile: ExecFileFn = execFileOriginal, // to make it mockable +): Promise { + return new Promise((resolve, reject) => { + // Build vtracer arguments + const args = ["--input", filePath, "--output", targetPath]; + + // Add option parameter if provided + if (options && typeof options === "object") { + const opts = options as Record; + + if (opts.colormode) { + args.push("--colormode", opts.colormode); + } + + if (opts.hierarchical) { + args.push("--hierarchical", opts.hierarchical); + } + + if (opts.mode) { + args.push("--mode", opts.mode); + } + + if (opts.filter_speckle) { + args.push("--filter_speckle", opts.filter_speckle); + } + + if (opts.color_precision) { + args.push("--color_precision", opts.color_precision); + } + + if (opts.layer_difference) { + args.push("--layer_difference", opts.layer_difference); + } + + if (opts.corner_threshold) { + args.push("--corner_threshold", opts.corner_threshold); + } + + if (opts.length_threshold) { + args.push("--length_threshold", opts.length_threshold); + } + + if (opts.max_iterations) { + args.push("--max_iterations", opts.max_iterations); + } + + if (opts.splice_threshold) { + args.push("--splice_threshold", opts.splice_threshold); + } + + if (opts.path_precision) { + args.push("--path_precision", opts.path_precision); + } + + execFile("vtracer", args, (error, stdout, stderr) => { + if (error) { + reject(`error: ${error}`); + return; + } + + if (stdout) { + console.log(`stdout: ${stdout}`); + } + + if (stderr) { + console.log(`stderr: ${stderr}`); + } + + resolve("Done"); + }); + } + }); +} From 76c840dbaa4a26d0623422b61581bb761ad6a6bc Mon Sep 17 00:00:00 2001 From: Sahil Date: Tue, 12 Aug 2025 22:26:39 +0530 Subject: [PATCH 2/6] feat: vtracer implemented and added docker file binaries install --- Dockerfile | 21 ++++++++++++++++++--- src/converters/vtracer.ts | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 306c5bd..0b57624 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ RUN bun run build # copy production dependencies and source code into final image FROM base AS release -# install additional dependencies +# install additional dependencies RUN apt-get update && apt-get install -y \ assimp-utils \ calibre \ @@ -75,15 +75,30 @@ RUN apt-get update && apt-get install -y \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/* +# Install VTracer binary (corrected version using tar.gz) +RUN ARCH=$(uname -m) && \ + if [ "$ARCH" = "aarch64" ]; then \ + VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \ + else \ + VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \ + fi && \ + echo "Downloading VTracer: $VTRACER_ASSET" && \ + curl -L -o /tmp/vtracer.tar.gz "https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \ + tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \ + mv /tmp/vtracer /usr/local/bin/vtracer && \ + chmod +x /usr/local/bin/vtracer && \ + rm /tmp/vtracer.tar.gz && \ + echo "VTracer installed successfully" && \ + vtracer --help + COPY --from=install /temp/prod/node_modules node_modules COPY --from=prerelease /app/public/generated.css /app/public/ COPY --from=prerelease /app/dist /app/dist -# COPY . . RUN mkdir data EXPOSE 3000/tcp -# used for calibre ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox" ENV NODE_ENV=production ENTRYPOINT [ "bun", "run", "dist/src/index.js" ] + diff --git a/src/converters/vtracer.ts b/src/converters/vtracer.ts index b81febd..db9d037 100644 --- a/src/converters/vtracer.ts +++ b/src/converters/vtracer.ts @@ -72,7 +72,7 @@ export function convert( execFile("vtracer", args, (error, stdout, stderr) => { if (error) { - reject(`error: ${error}`); + reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ''}`); return; } From 8650cf9a6364f2b240c14453787cc50473a4f80f Mon Sep 17 00:00:00 2001 From: Sahil Date: Wed, 13 Aug 2025 17:07:35 +0530 Subject: [PATCH 3/6] docs: add Vtracer to converter table --- README.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 446a4e9..57125ac 100644 --- a/README.md +++ b/README.md @@ -25,22 +25,23 @@ A self-hosted online file converter. Supports over a thousand different formats. ## Converters supported -| Converter | Use case | Converts from | Converts to | -| ------------------------------------------------ | ---------------- | ------------- | ----------- | -| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 | -| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 | -| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 | -| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 | -| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 | -| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 | -| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 | -| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 | -| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 | -| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 | -| [Inkscape](https://inkscape.org/) | Vector images | 7 | 17 | -| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 77 | 23 | -| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 | -| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 | +| Converter | Use case | Converts from | Converts to | +| -------------------------------------------------- | ---------------- | ------------- | ----------- | +| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 | +| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 | +| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 | +| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 | +| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 | +| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 | +| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 | +| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 | +| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 | +| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 | +| [Inkscape](https://inkscape.org/) | Vector images | 7 | 17 | +| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 77 | 23 | +| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 | +| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 | +| [VTracer](https://github.com/visioncortex/vtracer) | Raster to vector | 8 | 1 | From 2b784d1edc8e631a68ac654c943468b3202e2da0 Mon Sep 17 00:00:00 2001 From: Sahil Date: Wed, 13 Aug 2025 17:14:07 +0530 Subject: [PATCH 4/6] dockerfile: removing line spaces and adding original comments --- Dockerfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0b57624..372b91e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,7 +75,7 @@ RUN apt-get update && apt-get install -y \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/* -# Install VTracer binary (corrected version using tar.gz) +# Install VTracer binary RUN ARCH=$(uname -m) && \ if [ "$ARCH" = "aarch64" ]; then \ VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \ @@ -95,10 +95,11 @@ COPY --from=install /temp/prod/node_modules node_modules COPY --from=prerelease /app/public/generated.css /app/public/ COPY --from=prerelease /app/dist /app/dist +# COPY . . RUN mkdir data EXPOSE 3000/tcp +# used for calibre ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox" ENV NODE_ENV=production -ENTRYPOINT [ "bun", "run", "dist/src/index.js" ] - +ENTRYPOINT [ "bun", "run", "dist/src/index.js" ] \ No newline at end of file From 45a0540edf5ba50eb69e14e036a2aae8e3f08ac3 Mon Sep 17 00:00:00 2001 From: Sahil Date: Wed, 13 Aug 2025 17:55:45 +0530 Subject: [PATCH 5/6] vtracer: bug fix in vtracer.ts file and code refactor --- src/converters/vtracer.ts | 89 +++++++++++++-------------------------- 1 file changed, 29 insertions(+), 60 deletions(-) diff --git a/src/converters/vtracer.ts b/src/converters/vtracer.ts index db9d037..1c0d706 100644 --- a/src/converters/vtracer.ts +++ b/src/converters/vtracer.ts @@ -22,70 +22,39 @@ export function convert( // Build vtracer arguments const args = ["--input", filePath, "--output", targetPath]; - // Add option parameter if provided + // Add optional parameter if provided if (options && typeof options === "object") { const opts = options as Record; - if (opts.colormode) { - args.push("--colormode", opts.colormode); + const validOptions = [ + "colormode", "hierarchical", "mode", "filter_speckle", + "color_precision", "layer_difference", "corner_threshold", + "length_threshold", "max_iterations", "splice_threshold", + "path_precision", + ]; + + for (const option of validOptions) { + if(opts[option]){ + args.push(`--${option}`, opts[option]); + } } - - if (opts.hierarchical) { - args.push("--hierarchical", opts.hierarchical); - } - - if (opts.mode) { - args.push("--mode", opts.mode); - } - - if (opts.filter_speckle) { - args.push("--filter_speckle", opts.filter_speckle); - } - - if (opts.color_precision) { - args.push("--color_precision", opts.color_precision); - } - - if (opts.layer_difference) { - args.push("--layer_difference", opts.layer_difference); - } - - if (opts.corner_threshold) { - args.push("--corner_threshold", opts.corner_threshold); - } - - if (opts.length_threshold) { - args.push("--length_threshold", opts.length_threshold); - } - - if (opts.max_iterations) { - args.push("--max_iterations", opts.max_iterations); - } - - if (opts.splice_threshold) { - args.push("--splice_threshold", opts.splice_threshold); - } - - if (opts.path_precision) { - args.push("--path_precision", opts.path_precision); - } - - execFile("vtracer", args, (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ''}`); - return; - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.log(`stderr: ${stderr}`); - } - - resolve("Done"); - }); } + + execFile("vtracer", args, (error, stdout, stderr) => { + if(error){ + reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ''}`) + return; + } + + if(stdout){ + console.log(`stdout: ${stdout}`) + } + + if(stderr){ + console.log(`stderr: ${stderr}`) + } + + resolve("Done"); + }); }); } From 43524dcdb182eded90f3a5b4fbc699d7956e1d93 Mon Sep 17 00:00:00 2001 From: Sahil Date: Wed, 13 Aug 2025 18:26:17 +0530 Subject: [PATCH 6/6] Refactor and fix: clean up dockerfile and format done with fix in types --- Dockerfile | 5 +--- src/converters/main.ts | 5 ++-- src/converters/vtracer.ts | 50 +++++++++++++++++++++++++++------------ 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index 372b91e..4d96c83 100644 --- a/Dockerfile +++ b/Dockerfile @@ -82,14 +82,11 @@ RUN ARCH=$(uname -m) && \ else \ VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \ fi && \ - echo "Downloading VTracer: $VTRACER_ASSET" && \ curl -L -o /tmp/vtracer.tar.gz "https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \ tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \ mv /tmp/vtracer /usr/local/bin/vtracer && \ chmod +x /usr/local/bin/vtracer && \ - rm /tmp/vtracer.tar.gz && \ - echo "VTracer installed successfully" && \ - vtracer --help + rm /tmp/vtracer.tar.gz COPY --from=install /temp/prod/node_modules node_modules COPY --from=prerelease /app/public/generated.css /app/public/ diff --git a/src/converters/main.ts b/src/converters/main.ts index fc33318..f90a3fc 100644 --- a/src/converters/main.ts +++ b/src/converters/main.ts @@ -20,9 +20,8 @@ import { convert as convertPandoc, properties as propertiesPandoc } from "./pand import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace"; import { convert as convertresvg, properties as propertiesresvg } from "./resvg"; import { convert as convertImage, properties as propertiesImage } from "./vips"; +import { convert as convertVtracer, properties as propertiesVtracer } from "./vtracer"; import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex"; -import {convert as convertVtracer, properties as propertiesVtracer} from './vtracer'; - // This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular @@ -122,7 +121,7 @@ const properties: Record< vtracer: { properties: propertiesVtracer, converter: convertVtracer, - } + }, }; function chunks(arr: T[], size: number): T[][] { diff --git a/src/converters/vtracer.ts b/src/converters/vtracer.ts index 1c0d706..31ce110 100644 --- a/src/converters/vtracer.ts +++ b/src/converters/vtracer.ts @@ -10,6 +10,20 @@ export const properties = { }, }; +interface VTracerOptions { + colormode?: string; + hierarchical?: string; + mode?: string; + filter_speckle?: string | number; + color_precision?: string | number; + layer_difference?: string | number; + corner_threshold?: string | number; + length_threshold?: string | number; + max_iterations?: string | number; + splice_threshold?: string | number; + path_precision?: string | number; +} + export function convert( filePath: string, fileType: string, @@ -24,34 +38,40 @@ export function convert( // Add optional parameter if provided if (options && typeof options === "object") { - const opts = options as Record; - - const validOptions = [ - "colormode", "hierarchical", "mode", "filter_speckle", - "color_precision", "layer_difference", "corner_threshold", - "length_threshold", "max_iterations", "splice_threshold", + const opts = options as VTracerOptions; + const validOptions: Array = [ + "colormode", + "hierarchical", + "mode", + "filter_speckle", + "color_precision", + "layer_difference", + "corner_threshold", + "length_threshold", + "max_iterations", + "splice_threshold", "path_precision", ]; for (const option of validOptions) { - if(opts[option]){ - args.push(`--${option}`, opts[option]); - } + if (opts[option] !== undefined) { + args.push(`--${option}`, String(opts[option])); + } } } execFile("vtracer", args, (error, stdout, stderr) => { - if(error){ - reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ''}`) + if (error) { + reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ""}`); return; } - if(stdout){ - console.log(`stdout: ${stdout}`) + if (stdout) { + console.log(`stdout: ${stdout}`); } - if(stderr){ - console.log(`stderr: ${stderr}`) + if (stderr) { + console.log(`stderr: ${stderr}`); } resolve("Done");