1 Commits

Author SHA1 Message Date
C4illin
a8f2cd4e9e feat: poppler working for some formats 2024-05-30 12:06:02 +02:00
22 changed files with 232 additions and 438 deletions

View File

@@ -5,19 +5,19 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: npm # Maintain dependencies for npm
versioning-strategy: increase - package-ecosystem: "npm" # See documentation for possible values
directory: "/" versioning-strategy: increase
schedule: directory: "/" # Location of package manifests
interval: daily schedule:
commit-message: interval: "daily"
prefix: "build" # Maintain dependencies for GitHub Actions
include: "scope" - package-ecosystem: "github-actions"
open-pull-requests-limit: 10 directory: "/"
- package-ecosystem: github-actions schedule:
directory: "/" interval: "daily"
schedule: # Maintain dependencies for Docker
interval: weekly - package-ecosystem: "docker"
commit-message: directory: "/"
prefix: "build" schedule:
include: "scope" interval: "daily"

View File

@@ -14,7 +14,7 @@ jobs:
if: github.actor == 'dependabot[bot]' if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v1
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -58,7 +58,7 @@ jobs:
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push Docker image - name: Build and push Docker image
id: build-and-push id: build-and-push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v5
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}

View File

@@ -1,25 +0,0 @@
on:
push:
branches:
- main
permissions:
contents: write
pull-requests: write
name: release-please
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
# this assumes that you have created a personal access token
# (PAT) and configured it as a GitHub action secret named
# `MY_RELEASE_PLEASE_TOKEN` (this secret name is not important).
# token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}
# this is a built-in strategy in release-please, see "Action Inputs"
# for more options
release-type: node

View File

@@ -1,21 +0,0 @@
name: Remove Docker Tag
on:
workflow_dispatch:
jobs:
remove-docker-tag:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
# (required)
permissions:
contents: read
packages: write
steps:
- name: Remove Docker Tag
uses: ArchieAtkinson/remove-dockertag-action@v0.0
with:
tag_name: master
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,44 +0,0 @@
# Changelog
## [0.3.0](https://github.com/C4illin/ConvertX/compare/v0.2.0...v0.3.0) (2024-06-27)
### Features
* add version number to log ([4dcb796](https://github.com/C4illin/ConvertX/commit/4dcb796e1bd27badc078d0638076cd9f1e81c4a4)), closes [#44](https://github.com/C4illin/ConvertX/issues/44)
* change to xelatex ([fae2ba9](https://github.com/C4illin/ConvertX/commit/fae2ba9c54461dccdccd1bfb5e76398540d11d0b))
* print version of installed converters to log ([801cf28](https://github.com/C4illin/ConvertX/commit/801cf28d1e5edac9353b0b16be75a4fb48470b8a))
## [0.2.0](https://github.com/C4illin/ConvertX/compare/v0.1.2...v0.2.0) (2024-06-20)
### Features
* add libjxl for jpegxl conversion ([ff680cb](https://github.com/C4illin/ConvertX/commit/ff680cb29534a25c3148a90fd064bb86c71fb482))
* change from debian to alpine ([1316957](https://github.com/C4illin/ConvertX/commit/13169574f0134ae236f8d41287bb73930b575e82)), closes [#34](https://github.com/C4illin/ConvertX/issues/34)
## [0.1.2](https://github.com/C4illin/ConvertX/compare/v0.1.1...v0.1.2) (2024-06-10)
### Bug Fixes
* fix incorrect redirect ([25df58b](https://github.com/C4illin/ConvertX/commit/25df58ba82321aaa6617811a6995cb96c2a00a40)), closes [#23](https://github.com/C4illin/ConvertX/issues/23)
## [0.1.1](https://github.com/C4illin/ConvertX/compare/v0.1.0...v0.1.1) (2024-05-30)
### Bug Fixes
* :bug: make sure all redirects are 302 ([9970fd3](https://github.com/C4illin/ConvertX/commit/9970fd3f89190af96f8762edc3817d1e03082b3a)), closes [#12](https://github.com/C4illin/ConvertX/issues/12)
## 0.1.0 (2024-05-30)
### Features
* remove file from file list in index.html ([787ff97](https://github.com/C4illin/ConvertX/commit/787ff9741ecbbf4fb4c02b43bd22a214a173fd7b))
### Miscellaneous Chores
* release 0.1.0 ([54d9aec](https://github.com/C4illin/ConvertX/commit/54d9aecbf949689b12aa7e5e8e9be7b9032f4431))

View File

@@ -1,63 +0,0 @@
FROM oven/bun:1-debian as base
WORKDIR /app
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lockb /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lockb /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production
# FROM base AS install-libjxl-tools
# download
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
# FROM base AS prerelease
# COPY --from=install /temp/dev/node_modules node_modules
# COPY . .
# # [optional] tests & build
# ENV NODE_ENV=production
# RUN bun test
# RUN bun run build
# copy production dependencies and source code into final image
FROM base AS release
LABEL maintainer="Emrik Östling (C4illin)"
LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats."
LABEL repo="https://github.com/C4illin/ConvertX"
# install additional dependencies
RUN rm -rf /var/lib/apt/lists/partial && apt-get update -o Acquire::CompressionTypes::Order::=gz \
&& apt-get install -y \
pandoc \
texlive-latex-recommended \
texlive-fonts-recommended \
texlive-latex-extra \
ffmpeg \
graphicsmagick \
ghostscript \
libvips-tools
# # libjxl is not available in the official debian repositories
# RUN wget https://github.com/libjxl/libjxl/releases/download/v0.10.2/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -O /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz \
# && mkdir -p /tmp/libjxl \
# && tar -xvf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -C /tmp/libjxl \
# && dpkg -i /tmp/libjxl/libjxl_0.10.2_amd64.deb /tmp/libjxl/jxl_0.10.2_amd64.deb \
# && rm -rf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz /tmp/libjxl
COPY --from=install /temp/prod/node_modules node_modules
# COPY --from=prerelease /app/src/index.tsx /app/src/
# COPY --from=prerelease /app/package.json .
COPY . .
EXPOSE 3000/tcp
ENTRYPOINT [ "bun", "run", "./src/index.tsx" ]

View File

@@ -1,4 +1,4 @@
FROM oven/bun:1-alpine as base FROM oven/bun:1-debian as base
WORKDIR /app WORKDIR /app
# install dependencies into temp directory # install dependencies into temp directory
@@ -31,19 +31,16 @@ LABEL description="ConvertX: self-hosted online file converter supporting 700+ f
LABEL repo="https://github.com/C4illin/ConvertX" LABEL repo="https://github.com/C4illin/ConvertX"
# install additional dependencies # install additional dependencies
RUN apk --no-cache add \ RUN rm -rf /var/lib/apt/lists/partial && apt-get update -o Acquire::CompressionTypes::Order::=gz \
&& apt-get install -y \
pandoc \ pandoc \
texlive \ texlive-latex-recommended \
texlive-xetex \ texlive-fonts-recommended \
texmf-dist-latexextra \ texlive-latex-extra \
ffmpeg \ ffmpeg \
graphicsmagick \ graphicsmagick \
ghostscript \ ghostscript \
vips-tools \ libvips-tools
libjxl-tools
# this might be needed for some latex use cases, will add it if needed.
# texmf-dist-fontsextra \
COPY --from=install /temp/prod/node_modules node_modules COPY --from=install /temp/prod/node_modules node_modules
# COPY --from=prerelease /app/src/index.tsx /app/src/ # COPY --from=prerelease /app/src/index.tsx /app/src/
@@ -51,5 +48,4 @@ COPY --from=install /temp/prod/node_modules node_modules
COPY . . COPY . .
EXPOSE 3000/tcp EXPOSE 3000/tcp
ENV NODE_ENV=production
ENTRYPOINT [ "bun", "run", "./src/index.tsx" ] ENTRYPOINT [ "bun", "run", "./src/index.tsx" ]

View File

@@ -1,13 +1,8 @@
![ConvertX](images/logo.png) ![ConvertX](images/logo.png)
# ConvertX # ConvertX
[![Docker](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml/badge.svg?branch=main)](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml) [![Docker](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml/badge.svg?branch=main)](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml)
[![GitHub Release](https://img.shields.io/github/v/release/C4illin/ConvertX)](https://github.com/C4illin/ConvertX/pkgs/container/convertx)
![GitHub commits since latest release](https://img.shields.io/github/commits-since/C4illin/ConvertX/latest)
![GitHub repo size](https://img.shields.io/github/repo-size/C4illin/ConvertX)
![Docker container size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=latest&label=image+size&trim=)
![GitHub top language](https://img.shields.io/github/languages/top/C4illin/ConvertX)
A self-hosted online file converter. Supports 831 different formats. Written with TypeScript, Bun and Elysia. A self-hosted online file converter. Supports 831 different formats. Written with Typescript, Bun and Elysia.
## Features ## Features
@@ -17,14 +12,13 @@ A self-hosted online file converter. Supports 831 different formats. Written wit
## Converters supported ## Converters supported
| Converter | Use case | Converts from | Converts to | | Converter | Use case | Converts from | Converts to |
|------------------------------------------------------------------------------|---------------|---------------|-------------| |----------------|---------------|---------------|-------------|
| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 | | Vips | Images (fast) | 45 | 23 |
| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 | | PDFLaTeX | Documents | 1 | 1 |
| [XeLaTeX](https://tug.org/xetex/) | Documents | 1 | 1 | | Pandoc | Documents | 43 | 65 |
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 | | GraphicsMagick | Images | 166 | 133 |
| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 166 | 133 | | FFmpeg | Video | ~473 | ~280 |
| [FFmpeg](https://ffmpeg.org/) | Video | ~473 | ~280 |
<!-- many ffmpeg fileformats are duplicates --> <!-- many ffmpeg fileformats are duplicates -->
@@ -34,7 +28,7 @@ A self-hosted online file converter. Supports 831 different formats. Written wit
# docker-compose.yml # docker-compose.yml
services: services:
convertx: convertx:
image: ghcr.io/c4illin/convertx image: ghcr.io/c4illin/convertx:main
ports: ports:
- "3000:3000" - "3000:3000"
environment: # Defaults are listed below. All are optional. environment: # Defaults are listed below. All are optional.
@@ -66,7 +60,6 @@ Tutorial in french: https://belginux.com/installer-convertx-avec-docker/
- [ ] Divide index.tsx into smaller components - [ ] Divide index.tsx into smaller components
- [ ] Add tests - [ ] Add tests
- [ ] Add searchable list of formats - [ ] Add searchable list of formats
- [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible.
## Contributors ## Contributors

BIN
bun.lockb

Binary file not shown.

View File

@@ -2,7 +2,6 @@ services:
convertx: convertx:
build: build:
context: . context: .
# dockerfile: Debian.Dockerfile
volumes: volumes:
- ./data:/app/data - ./data:/app/data
environment: environment:

View File

@@ -1,6 +1,6 @@
{ {
"name": "convertx-frontend", "name": "convertx-frontend",
"version": "0.3.0", "version": "1.0.50",
"scripts": { "scripts": {
"dev": "bun run --watch src/index.tsx", "dev": "bun run --watch src/index.tsx",
"hot": "bun run --hot src/index.tsx", "hot": "bun run --hot src/index.tsx",
@@ -12,31 +12,29 @@
"@elysiajs/html": "^1.0.2", "@elysiajs/html": "^1.0.2",
"@elysiajs/jwt": "^1.0.2", "@elysiajs/jwt": "^1.0.2",
"@elysiajs/static": "^1.0.3", "@elysiajs/static": "^1.0.3",
"elysia": "^1.0.25" "elysia": "^1.0.22",
"node-poppler": "^7.2.0"
}, },
"module": "src/index.tsx", "module": "src/index.tsx",
"bun-create": { "bun-create": {
"start": "bun run src/index.tsx" "start": "bun run src/index.tsx"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.8.2", "@biomejs/biome": "1.7.3",
"@ianvs/prettier-plugin-sort-imports": "^4.3.0", "@ianvs/prettier-plugin-sort-imports": "^4.2.1",
"@kitajs/ts-html-plugin": "^4.0.1", "@kitajs/ts-html-plugin": "^4.0.1",
"@picocss/pico": "^2.0.6", "@picocss/pico": "^2.0.6",
"@total-typescript/ts-reset": "^0.5.1", "@total-typescript/ts-reset": "^0.5.1",
"@types/bun": "^1.1.6", "@types/bun": "^1.1.3",
"@types/eslint": "^8.56.10", "@types/eslint": "^8.56.10",
"@types/node": "^20.14.9", "@types/node": "^20.12.13",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/eslint-plugin": "^7.11.0",
"@typescript-eslint/parser": "^7.14.1", "@typescript-eslint/parser": "^7.11.0",
"cpy-cli": "^5.0.0", "cpy-cli": "^5.0.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.3.2", "prettier": "^3.2.5",
"typescript": "^5.5.2" "typescript": "^5.4.5"
}, }
"trustedDependencies": [
"@biomejs/biome"
]
} }

View File

@@ -1,71 +0,0 @@
import { exec } from "node:child_process";
// declare possible conversions
export const properties = {
from: {
jxl: ["jxl"],
images: [
"apng",
"exr",
"gif",
"jpeg",
"pam",
"pfm",
"pgm",
"pgx",
"png",
"ppm",
],
},
to: {
jxl: [
"apng",
"exr",
"gif",
"jpeg",
"pam",
"pfm",
"pgm",
"pgx",
"png",
"ppm",
],
images: ["jxl"],
},
};
export function convert(
filePath: string,
fileType: string,
convertTo: string,
targetPath: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
options?: any,
): Promise<string> {
let tool = "";
if (fileType === "jxl") {
tool = "djxl";
}
if (convertTo === "jxl") {
tool = "cjxl";
}
return new Promise((resolve, reject) => {
exec(`${tool} "${filePath}" "${targetPath}"`, (error, stdout, stderr) => {
if (error) {
reject(`error: ${error}`);
}
if (stdout) {
console.log(`stdout: ${stdout}`);
}
if (stderr) {
console.error(`stderr: ${stderr}`);
}
resolve("success");
});
});
}

View File

@@ -16,14 +16,11 @@ import {
} from "./graphicsmagick"; } from "./graphicsmagick";
import { import {
convert as convertxelatex, convert as convertPdflatex,
properties as propertiesxelatex, properties as propertiesPdflatex,
} from "./xelatex"; } from "./pdflatex";
import { import { convert as convertPoppler, properties as propertiesPoppler } from "./poppler";
convert as convertLibjxl,
properties as propertiesLibjxl,
} from "./libjxl";
import { normalizeFiletype } from "../helpers/normalizeFiletype"; import { normalizeFiletype } from "../helpers/normalizeFiletype";
@@ -55,17 +52,17 @@ const properties: {
) => any; ) => any;
}; };
} = { } = {
libjxl: {
properties: propertiesLibjxl,
converter: convertLibjxl,
},
vips: { vips: {
properties: propertiesImage, properties: propertiesImage,
converter: convertImage, converter: convertImage,
}, },
xelatex: { pdflatex: {
properties: propertiesxelatex, properties: propertiesPdflatex,
converter: convertxelatex, converter: convertPdflatex,
},
poppler: {
properties: propertiesPoppler,
converter: convertPoppler,
}, },
pandoc: { pandoc: {
properties: propertiesPandoc, properties: propertiesPandoc,
@@ -111,8 +108,9 @@ export async function mainConverter(
for (const key in converterObj.properties.from) { for (const key in converterObj.properties.from) {
if ( if (
converterObj?.properties?.from[key]?.includes(fileType) && // HOW??
converterObj?.properties?.to[key]?.includes(convertTo) converterObj.properties.from[key].includes(fileType) &&
converterObj.properties.to[key].includes(convertTo)
) { ) {
converterFunc = converterObj.converter; converterFunc = converterObj.converter;
break; break;
@@ -216,9 +214,9 @@ for (const converterName in properties) {
for (const key in converterProperties.to) { for (const key in converterProperties.to) {
if (allTargets[converterName]) { if (allTargets[converterName]) {
allTargets[converterName].push(...(converterProperties.to[key] || [])); allTargets[converterName].push(...converterProperties.to[key]);
} else { } else {
allTargets[converterName] = converterProperties.to[key] || []; allTargets[converterName] = converterProperties.to[key];
} }
} }
} }
@@ -237,9 +235,9 @@ for (const converterName in properties) {
for (const key in converterProperties.from) { for (const key in converterProperties.from) {
if (allInputs[converterName]) { if (allInputs[converterName]) {
allInputs[converterName].push(...(converterProperties.from[key] || [])); allInputs[converterName].push(...converterProperties.from[key]);
} else { } else {
allInputs[converterName] = converterProperties.from[key] || []; allInputs[converterName] = converterProperties.from[key];
} }
} }
} }

View File

@@ -127,15 +127,9 @@ export function convert(
// biome-ignore lint/suspicious/noExplicitAny: <explanation> // biome-ignore lint/suspicious/noExplicitAny: <explanation>
options?: any, options?: any,
): Promise<string> { ): Promise<string> {
// set xelatex here
const xelatex = ["pdf", "latex"];
let option = "";
if (xelatex.includes(convertTo)) {
option = "--pdf-engine=xelatex";
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec( exec(
`pandoc ${option} "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`, `pandoc "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`,
(error, stdout, stderr) => { (error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);

View File

@@ -19,13 +19,9 @@ export function convert(
): Promise<string> { ): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "") // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
const outputPath = targetPath const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "")
.split("/")
.slice(0, -1)
.join("/")
.replace("./", "");
exec( exec(
`latexmk -xelatex -interaction=nonstopmode -output-directory="${outputPath}" "${filePath}"`, `pdflatex -interaction=nonstopmode -output-directory="${outputPath}" "${filePath}"`,
(error, stdout, stderr) => { (error, stdout, stderr) => {
if (error) { if (error) {
reject(`error: ${error}`); reject(`error: ${error}`);

115
src/converters/poppler.ts Normal file
View File

@@ -0,0 +1,115 @@
const { Poppler } = require("node-poppler");
const poppler = new Poppler();
export const properties = {
from: {
text: ["pdf"],
},
to: {
text: [
"jpeg",
"png",
"tiff",
"eps",
"icc",
"pdf",
"svg",
"ps",
"html",
"text",
],
},
};
export function convert(
filePath: string,
fileType: string,
convertTo: string,
targetPath: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
options?: any,
): Promise<string> {
return new Promise((resolve, reject) => {
const cairoFiles = [
"jpeg",
"png",
"tiff",
"eps",
"icc",
"pdf",
"svg",
"ps",
];
if (cairoFiles.includes(convertTo)) {
const popplerOptions: {
jpegFile?: boolean;
pngFile?: boolean;
tiffFile?: boolean;
epsFile?: boolean;
iccFile?: boolean;
pdfFile?: boolean;
svgFile?: boolean;
psFile?: boolean;
} = {};
switch (convertTo) {
case "jpeg":
popplerOptions.jpegFile = true;
break;
case "png":
popplerOptions.pngFile = true;
break;
case "tiff":
popplerOptions.tiffFile = true;
break;
case "eps":
popplerOptions.epsFile = true;
break;
case "icc":
popplerOptions.iccFile = true;
break;
case "pdf":
popplerOptions.pdfFile = true;
break;
case "svg":
popplerOptions.svgFile = true;
break;
case "ps":
popplerOptions.psFile = true;
break;
default:
reject(`Invalid convertTo option: ${convertTo}`);
}
poppler
.pdfToCairo(filePath, targetPath, popplerOptions)
.then(() => {
resolve("success");
})
.catch((err: Error) => {
reject(err);
});
} else if (convertTo === "html") {
poppler
.pdfToHtml(filePath, targetPath)
.then(() => {
resolve("success");
})
.catch((err: Error) => {
reject(err);
});
} else if (convertTo === "text") {
poppler
.pdfToText(filePath, targetPath)
.then(() => {
resolve("success");
})
.catch((err: Error) => {
reject(err);
});
} else {
reject(`Invalid convertTo option: ${convertTo}`);
}
});
}

View File

@@ -25,6 +25,8 @@ export const normalizeOutputFiletype = (filetype: string): string => {
return "tex"; return "tex";
case "markdown": case "markdown":
return "md"; return "md";
case "text":
return "txt";
default: default:
return lowercaseFiletype; return lowercaseFiletype;
} }

View File

@@ -1,75 +0,0 @@
import { exec } from "node:child_process";
import { version } from "../../package.json";
console.log(`ConvertX v${version}`);
if (process.env.NODE_ENV === "production") {
exec("cat /etc/os-release", (error, stdout) => {
if (error) {
console.error("Not running on docker, this is not supported.");
}
if (stdout) {
console.log(stdout.split('PRETTY_NAME="')[1]?.split('"')[0]);
}
});
exec("pandoc -v", (error, stdout) => {
if (error) {
console.error("Pandoc is not installed.");
}
if (stdout) {
console.log(stdout.split("\n")[0]);
}
});
exec("ffmpeg -version", (error, stdout) => {
if (error) {
console.error("FFmpeg is not installed.");
}
if (stdout) {
console.log(stdout.split("\n")[0]);
}
});
exec("vips -v", (error, stdout) => {
if (error) {
console.error("Vips is not installed.");
}
if (stdout) {
console.log(stdout.split("\n")[0]);
}
});
exec("gm version", (error, stdout) => {
if (error) {
console.error("GraphicsMagick is not installed.");
}
if (stdout) {
console.log(stdout.split("\n")[0]);
}
});
exec("djxl --version", (error, stdout) => {
if (error) {
console.error("libjxl-tools is not installed.");
}
if (stdout) {
console.log(stdout.split("\n")[0]);
}
});
exec("xelatex -version", (error, stdout) => {
if (error) {
console.error("Tex Live with XeTeX is not installed.");
}
if (stdout) {
console.log(stdout.split("\n")[0]);
}
});
}

View File

@@ -19,10 +19,6 @@ import {
normalizeFiletype, normalizeFiletype,
normalizeOutputFiletype, normalizeOutputFiletype,
} from "./helpers/normalizeFiletype"; } from "./helpers/normalizeFiletype";
import "./helpers/printVersions";
const db = new Database("./data/mydb.sqlite", { create: true }); const db = new Database("./data/mydb.sqlite", { create: true });
const uploadsDir = "./data/uploads/"; const uploadsDir = "./data/uploads/";
@@ -125,7 +121,7 @@ const app = new Elysia()
) )
.get("/setup", ({ redirect }) => { .get("/setup", ({ redirect }) => {
if (!FIRST_RUN) { if (!FIRST_RUN) {
return redirect("/login", 302); return redirect("/login");
} }
return ( return (
@@ -168,7 +164,7 @@ const app = new Elysia()
}) })
.get("/register", ({ redirect }) => { .get("/register", ({ redirect }) => {
if (!ACCOUNT_REGISTRATION) { if (!ACCOUNT_REGISTRATION) {
return redirect("/login", 302); return redirect("/login");
} }
return ( return (
@@ -210,7 +206,7 @@ const app = new Elysia()
"/register", "/register",
async ({ body, set, redirect, jwt, cookie: { auth } }) => { async ({ body, set, redirect, jwt, cookie: { auth } }) => {
if (!ACCOUNT_REGISTRATION && !FIRST_RUN) { if (!ACCOUNT_REGISTRATION && !FIRST_RUN) {
return redirect("/login", 302); return redirect("/login");
} }
if (FIRST_RUN) { if (FIRST_RUN) {
@@ -257,13 +253,13 @@ const app = new Elysia()
sameSite: "strict", sameSite: "strict",
}); });
return redirect("/", 302); return redirect("/");
}, },
{ body: t.Object({ email: t.String(), password: t.String() }) }, { body: t.Object({ email: t.String(), password: t.String() }) },
) )
.get("/login", async ({ jwt, redirect, cookie: { auth } }) => { .get("/login", async ({ jwt, redirect, cookie: { auth } }) => {
if (FIRST_RUN) { if (FIRST_RUN) {
return redirect("/setup", 302); return redirect("/setup");
} }
// if already logged in, redirect to home // if already logged in, redirect to home
@@ -271,7 +267,7 @@ const app = new Elysia()
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (user) { if (user) {
return redirect("/", 302); return redirect("/");
} }
auth.remove(); auth.remove();
@@ -365,7 +361,7 @@ const app = new Elysia()
sameSite: "strict", sameSite: "strict",
}); });
return redirect("/", 302); return redirect("/");
}, },
{ body: t.Object({ email: t.String(), password: t.String() }) }, { body: t.Object({ email: t.String(), password: t.String() }) },
) )
@@ -374,27 +370,27 @@ const app = new Elysia()
auth.remove(); auth.remove();
} }
return redirect("/login", 302); return redirect("/login");
}) })
.post("/logoff", ({ redirect, cookie: { auth } }) => { .post("/logoff", ({ redirect, cookie: { auth } }) => {
if (auth?.value) { if (auth?.value) {
auth.remove(); auth.remove();
} }
return redirect("/login", 302); return redirect("/login");
}) })
.get("/", async ({ jwt, redirect, cookie: { auth, jobId } }) => { .get("/", async ({ jwt, redirect, cookie: { auth, jobId } }) => {
if (FIRST_RUN) { if (FIRST_RUN) {
return redirect("/setup", 302); return redirect("/setup");
} }
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
// validate jwt // validate jwt
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
// make sure user exists in db // make sure user exists in db
@@ -406,7 +402,7 @@ const app = new Elysia()
if (auth?.value) { if (auth?.value) {
auth.remove(); auth.remove();
} }
return redirect("/login", 302); return redirect("/login");
} }
// create a new job // create a new job
@@ -513,16 +509,16 @@ const app = new Elysia()
"/upload", "/upload",
async ({ body, redirect, jwt, cookie: { auth, jobId } }) => { async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
if (!jobId?.value) { if (!jobId?.value) {
return redirect("/", 302); return redirect("/");
} }
const existingJob = await db const existingJob = await db
@@ -530,7 +526,7 @@ const app = new Elysia()
.get(jobId.value, user.id); .get(jobId.value, user.id);
if (!existingJob) { if (!existingJob) {
return redirect("/", 302); return redirect("/");
} }
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
@@ -561,16 +557,16 @@ const app = new Elysia()
"/delete", "/delete",
async ({ body, redirect, jwt, cookie: { auth, jobId } }) => { async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
if (!jobId?.value) { if (!jobId?.value) {
return redirect("/", 302); return redirect("/");
} }
const existingJob = await db const existingJob = await db
@@ -578,7 +574,7 @@ const app = new Elysia()
.get(jobId.value, user.id); .get(jobId.value, user.id);
if (!existingJob) { if (!existingJob) {
return redirect("/", 302); return redirect("/");
} }
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
@@ -591,16 +587,16 @@ const app = new Elysia()
"/convert", "/convert",
async ({ body, redirect, jwt, cookie: { auth, jobId } }) => { async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
if (!jobId?.value) { if (!jobId?.value) {
return redirect("/", 302); return redirect("/");
} }
const existingJob = (await db const existingJob = (await db
@@ -608,7 +604,7 @@ const app = new Elysia()
.get(jobId.value, user.id)) as IJobs; .get(jobId.value, user.id)) as IJobs;
if (!existingJob) { if (!existingJob) {
return redirect("/", 302); return redirect("/");
} }
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
@@ -631,7 +627,7 @@ const app = new Elysia()
const fileNames = JSON.parse(body.file_names) as string[]; const fileNames = JSON.parse(body.file_names) as string[];
if (!Array.isArray(fileNames) || fileNames.length === 0) { if (!Array.isArray(fileNames) || fileNames.length === 0) {
return redirect("/", 302); return redirect("/");
} }
db.run( db.run(
@@ -681,7 +677,7 @@ const app = new Elysia()
}); });
// Redirect the client immediately // Redirect the client immediately
return redirect(`/results/${jobId.value}`, 302); return redirect(`/results/${jobId.value}`);
}, },
{ {
body: t.Object({ body: t.Object({
@@ -692,12 +688,12 @@ const app = new Elysia()
) )
.get("/history", async ({ jwt, redirect, cookie: { auth } }) => { .get("/history", async ({ jwt, redirect, cookie: { auth } }) => {
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
let userJobs = db let userJobs = db
@@ -755,7 +751,7 @@ const app = new Elysia()
"/results/:jobId", "/results/:jobId",
async ({ params, jwt, set, redirect, cookie: { auth, job_id } }) => { async ({ params, jwt, set, redirect, cookie: { auth, job_id } }) => {
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
if (job_id?.value) { if (job_id?.value) {
@@ -765,7 +761,7 @@ const app = new Elysia()
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
const job = (await db const job = (await db
@@ -850,7 +846,7 @@ const app = new Elysia()
"/progress/:jobId", "/progress/:jobId",
async ({ jwt, set, params, redirect, cookie: { auth, job_id } }) => { async ({ jwt, set, params, redirect, cookie: { auth, job_id } }) => {
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
if (job_id?.value) { if (job_id?.value) {
@@ -860,7 +856,7 @@ const app = new Elysia()
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
const job = (await db const job = (await db
@@ -938,12 +934,12 @@ const app = new Elysia()
"/download/:userId/:jobId/:fileName", "/download/:userId/:jobId/:fileName",
async ({ params, jwt, redirect, cookie: { auth } }) => { async ({ params, jwt, redirect, cookie: { auth } }) => {
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
const job = await db const job = await db
@@ -951,7 +947,7 @@ const app = new Elysia()
.get(user.id, params.jobId); .get(user.id, params.jobId);
if (!job) { if (!job) {
return redirect("/results", 302); return redirect("/results");
} }
// parse from url encoded string // parse from url encoded string
const userId = decodeURIComponent(params.userId); const userId = decodeURIComponent(params.userId);
@@ -964,12 +960,12 @@ const app = new Elysia()
) )
.get("/converters", async ({ jwt, redirect, cookie: { auth } }) => { .get("/converters", async ({ jwt, redirect, cookie: { auth } }) => {
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
return ( return (
@@ -1026,12 +1022,12 @@ const app = new Elysia()
async ({ params, jwt, redirect, cookie: { auth } }) => { async ({ params, jwt, redirect, cookie: { auth } }) => {
// TODO: Implement zip download // TODO: Implement zip download
if (!auth?.value) { if (!auth?.value) {
return redirect("/login", 302); return redirect("/login");
} }
const user = await jwt.verify(auth.value); const user = await jwt.verify(auth.value);
if (!user) { if (!user) {
return redirect("/login", 302); return redirect("/login");
} }
const job = await db const job = await db
@@ -1039,7 +1035,7 @@ const app = new Elysia()
.get(user.id, params.jobId); .get(user.id, params.jobId);
if (!job) { if (!job) {
return redirect("/results", 302); return redirect("/results");
} }
const userId = decodeURIComponent(params.userId); const userId = decodeURIComponent(params.userId);

View File

@@ -1,3 +1,9 @@
article {
/* height: 300px; */
/* width: 300px; */
}
div.icon { div.icon {
height: 100px; height: 100px;
width: 100px; width: 100px;