diff --git a/Dockerfile b/Dockerfile index 306c5bd..4d96c83 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,6 +75,19 @@ RUN apt-get update && apt-get install -y \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/* +# Install VTracer binary +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 && \ + 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 + 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 @@ -86,4 +99,4 @@ 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 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 | diff --git a/src/converters/main.ts b/src/converters/main.ts index 8c59e55..f90a3fc 100644 --- a/src/converters/main.ts +++ b/src/converters/main.ts @@ -20,6 +20,7 @@ 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"; // 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 +118,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..31ce110 --- /dev/null +++ b/src/converters/vtracer.ts @@ -0,0 +1,80 @@ +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"], + }, +}; + +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, + 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 optional parameter if provided + if (options && typeof options === "object") { + 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] !== undefined) { + args.push(`--${option}`, String(opts[option])); + } + } + } + + 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"); + }); + }); +}