浏览代码

chore: format all files

C4illin 1 月之前
父节点
当前提交
1be11708c4
共有 45 个文件被更改,包括 2851 次插入3080 次删除
  1. 164 164
      .github/workflows/docker-publish.yml
  2. 9 9
      .github/workflows/dockerhub-description.yml
  3. 5 5
      .github/workflows/remove-docker-tag.yml
  4. 1 1
      .vscode/settings.json
  5. 61 95
      CHANGELOG.md
  6. 159 158
      README.md
  7. 2 5
      biome.json
  8. 19 19
      compose.yaml
  9. 66 66
      eslint.config.ts
  10. 5 2
      package.json
  11. 5 5
      postcss.config.js
  12. 1 1
      prettier.config.js
  13. 3 6
      public/script.js
  14. 2 5
      renovate.json
  15. 1 1
      reset.d.ts
  16. 3 17
      src/components/base.tsx
  17. 139 139
      src/converters/assimp.ts
  18. 12 16
      src/converters/dvisvgm.ts
  19. 754 756
      src/converters/ffmpeg.ts
  20. 336 340
      src/converters/graphicsmagick.ts
  21. 1 6
      src/converters/imagemagick.ts
  22. 12 16
      src/converters/inkscape.ts
  23. 13 29
      src/converters/libheif.ts
  24. 49 71
      src/converters/libjxl.ts
  25. 14 21
      src/converters/main.ts
  26. 162 162
      src/converters/pandoc.ts
  27. 37 25
      src/converters/potrace.ts
  28. 37 37
      src/converters/resvg.ts
  29. 138 142
      src/converters/vips.ts
  30. 44 53
      src/converters/xelatex.ts
  31. 4 7
      src/db/db.ts
  32. 1 1
      src/db/types.ts
  33. 2 4
      src/helpers/env.ts
  34. 37 37
      src/helpers/normalizeFiletype.ts
  35. 12 19
      src/index.tsx
  36. 63 71
      src/pages/chooseConverter.tsx
  37. 9 16
      src/pages/convert.tsx
  38. 37 35
      src/pages/deleteFile.tsx
  39. 27 31
      src/pages/download.tsx
  40. 31 57
      src/pages/history.tsx
  41. 30 39
      src/pages/listConverters.tsx
  42. 204 203
      src/pages/results.tsx
  43. 67 76
      src/pages/root.tsx
  44. 36 39
      src/pages/upload.tsx
  45. 37 73
      src/pages/user.tsx

+ 164 - 164
.github/workflows/docker-publish.yml

@@ -1,164 +1,164 @@
-name: Docker
-
-# thanks to https://github.com/sredevopsorg/multi-arch-docker-github-workflow
-
-on:
-  push:
-    branches: ["main"]
-    tags: ["v*.*.*"]
-  pull_request:
-    branches: ["main"]
-  workflow_dispatch:
-env:
-  GHCR_IMAGE: ghcr.io/c4illin/convertx
-  IMAGE_NAME: ${{ github.repository }}
-  DOCKERHUB_USERNAME: c4illin
-
-concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
-  cancel-in-progress: true
-
-jobs:
-  # The build job builds the Docker image for each platform specified in the matrix.
-  build:
-    strategy:
-      fail-fast: false
-      matrix:
-        platform:
-          - linux/amd64
-          - linux/arm64
-
-    permissions:
-      contents: write
-      packages: write
-      attestations: write
-      checks: write
-      actions: read
-
-    runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-24.04' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
-
-    name: Build Docker image for ${{ matrix.platform }}
-
-    steps:
-      - name: Prepare environment for current platform
-        # This step sets up the environment for the current platform being built.
-        # It replaces the '/' character in the platform name with '-' and sets it as an environment variable.
-        # This is useful for naming artifacts and other resources that cannot contain '/'.
-        # The environment variable PLATFORMS_PAIR will be used later in the workflow.
-        id: prepare
-        run: |
-          platform=${{ matrix.platform }}
-          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
-      - name: Checkout repository
-        uses: actions/checkout@v4
-
-      - name: Docker meta default
-        id: meta
-        uses: docker/metadata-action@v5
-        with:
-          images: ${{ env.GHCR_IMAGE }}
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3.10.0
-        with:
-          platforms: ${{ matrix.platform }}
-
-      - name: Login to GitHub Container Registry
-        # here we only login to ghcr.io since the this only pushes internal images
-        uses: docker/login-action@v3.4.0
-        with:
-          registry: ghcr.io
-          username: ${{ github.actor }}
-          password: ${{ secrets.GITHUB_TOKEN }}
-
-      - name: Build and push by digest
-        id: build
-        uses: docker/build-push-action@v6.18.0
-        env:
-          DOCKER_BUILDKIT: 1
-        with:
-          context: .
-          platforms: ${{ matrix.platform }}
-          labels: ${{ steps.meta.outputs.labels }}
-          annotations: ${{ steps.meta.outputs.annotations }}
-          outputs: type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
-          cache-from: type=gha,scope=${{ matrix.platform }}
-          cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
-
-      - name: Export digest
-        run: |
-          mkdir -p /tmp/digests
-          digest="${{ steps.build.outputs.digest }}"
-          touch "/tmp/digests/${digest#sha256:}"
-
-      - name: Upload digest
-        uses: actions/upload-artifact@v4
-        with:
-          name: digests-${{ env.PLATFORM_PAIR }}
-          path: /tmp/digests/*
-          if-no-files-found: error
-          retention-days: 1
-
-  merge:
-    name: Merge Docker manifests
-    runs-on: ubuntu-latest
-
-    permissions:
-      attestations: write
-      contents: read
-      packages: write
-
-    needs:
-      - build
-    steps:
-      - name: Download digests
-        uses: actions/download-artifact@v4
-        with:
-          path: /tmp/digests
-          pattern: digests-*
-          merge-multiple: true
-
-      - name: Extract Docker metadata
-        id: meta
-        uses: docker/metadata-action@v5
-        with:
-          images: |
-            ${{ env.GHCR_IMAGE }}
-            ${{ env.IMAGE_NAME }}
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
-
-      - name: Login to GitHub Container Registry
-        uses: docker/login-action@v3
-        with:
-          registry: ghcr.io
-          username: ${{ github.actor }}
-          password: ${{ secrets.GITHUB_TOKEN }}
-
-      - name: Login to Docker Hub
-        uses: docker/login-action@v3
-        with:
-          username: ${{ env.DOCKERHUB_USERNAME }}
-          password: ${{ secrets.DOCKERHUB_TOKEN }}
-
-      - name: Get execution timestamp with RFC3339 format
-        id: timestamp
-        run: |
-          echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT
-
-      - name: Create manifest list and push
-        working-directory: /tmp/digests
-        run: |
-          docker buildx imagetools create \
-            $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-            --annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \
-            --annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \
-            --annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \
-            --annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \
-            $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *)
-
-      - name: Inspect image
-        run: |
-          docker buildx imagetools inspect '${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.version }}'
+name: Docker
+
+# thanks to https://github.com/sredevopsorg/multi-arch-docker-github-workflow
+
+on:
+  push:
+    branches: ["main"]
+    tags: ["v*.*.*"]
+  pull_request:
+    branches: ["main"]
+  workflow_dispatch:
+env:
+  GHCR_IMAGE: ghcr.io/c4illin/convertx
+  IMAGE_NAME: ${{ github.repository }}
+  DOCKERHUB_USERNAME: c4illin
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  # The build job builds the Docker image for each platform specified in the matrix.
+  build:
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          - linux/amd64
+          - linux/arm64
+
+    permissions:
+      contents: write
+      packages: write
+      attestations: write
+      checks: write
+      actions: read
+
+    runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-24.04' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
+
+    name: Build Docker image for ${{ matrix.platform }}
+
+    steps:
+      - name: Prepare environment for current platform
+        # This step sets up the environment for the current platform being built.
+        # It replaces the '/' character in the platform name with '-' and sets it as an environment variable.
+        # This is useful for naming artifacts and other resources that cannot contain '/'.
+        # The environment variable PLATFORMS_PAIR will be used later in the workflow.
+        id: prepare
+        run: |
+          platform=${{ matrix.platform }}
+          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
+
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Docker meta default
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.GHCR_IMAGE }}
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3.10.0
+        with:
+          platforms: ${{ matrix.platform }}
+
+      - name: Login to GitHub Container Registry
+        # here we only login to ghcr.io since the this only pushes internal images
+        uses: docker/login-action@v3.4.0
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Build and push by digest
+        id: build
+        uses: docker/build-push-action@v6.18.0
+        env:
+          DOCKER_BUILDKIT: 1
+        with:
+          context: .
+          platforms: ${{ matrix.platform }}
+          labels: ${{ steps.meta.outputs.labels }}
+          annotations: ${{ steps.meta.outputs.annotations }}
+          outputs: type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true
+          cache-from: type=gha,scope=${{ matrix.platform }}
+          cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
+
+      - name: Export digest
+        run: |
+          mkdir -p /tmp/digests
+          digest="${{ steps.build.outputs.digest }}"
+          touch "/tmp/digests/${digest#sha256:}"
+
+      - name: Upload digest
+        uses: actions/upload-artifact@v4
+        with:
+          name: digests-${{ env.PLATFORM_PAIR }}
+          path: /tmp/digests/*
+          if-no-files-found: error
+          retention-days: 1
+
+  merge:
+    name: Merge Docker manifests
+    runs-on: ubuntu-latest
+
+    permissions:
+      attestations: write
+      contents: read
+      packages: write
+
+    needs:
+      - build
+    steps:
+      - name: Download digests
+        uses: actions/download-artifact@v4
+        with:
+          path: /tmp/digests
+          pattern: digests-*
+          merge-multiple: true
+
+      - name: Extract Docker metadata
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: |
+            ${{ env.GHCR_IMAGE }}
+            ${{ env.IMAGE_NAME }}
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Login to GitHub Container Registry
+        uses: docker/login-action@v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Login to Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ env.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+      - name: Get execution timestamp with RFC3339 format
+        id: timestamp
+        run: |
+          echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT
+
+      - name: Create manifest list and push
+        working-directory: /tmp/digests
+        run: |
+          docker buildx imagetools create \
+            $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+            --annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \
+            --annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \
+            --annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \
+            --annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \
+            $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *)
+
+      - name: Inspect image
+        run: |
+          docker buildx imagetools inspect '${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.version }}'

+ 9 - 9
.github/workflows/dockerhub-description.yml

@@ -15,13 +15,13 @@ jobs:
   dockerHubDescription:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
 
-    - name: Docker Hub Description
-      uses: peter-evans/dockerhub-description@v4
-      with:
-        username: ${{ env.DOCKERHUB_USERNAME }}
-        password: ${{ secrets.DOCKERHUB_TOKEN }}
-        repository: ${{ env.IMAGE_NAME }}
-        short-description: ${{ github.event.repository.description }}
-        enable-url-completion: true
+      - name: Docker Hub Description
+        uses: peter-evans/dockerhub-description@v4
+        with:
+          username: ${{ env.DOCKERHUB_USERNAME }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+          repository: ${{ env.IMAGE_NAME }}
+          short-description: ${{ github.event.repository.description }}
+          enable-url-completion: true

+ 5 - 5
.github/workflows/remove-docker-tag.yml

@@ -14,8 +14,8 @@ jobs:
       packages: write
 
     steps:
-    - name: Remove Docker Tag
-      uses: ArchieAtkinson/remove-dockertag-action@v0.0
-      with:
-        tag_name: master
-        github_token: ${{ secrets.GITHUB_TOKEN }}
+      - name: Remove Docker Tag
+        uses: ArchieAtkinson/remove-dockertag-action@v0.0
+        with:
+          tag_name: master
+          github_token: ${{ secrets.GITHUB_TOKEN }}

+ 1 - 1
.vscode/settings.json

@@ -1,4 +1,4 @@
 {
   "typescript.tsdk": "node_modules/typescript/lib",
   "typescript.enablePromptUseWorkspaceTsdk": true
-}
+}

+ 61 - 95
CHANGELOG.md

@@ -2,243 +2,209 @@
 
 ## [0.13.0](https://github.com/C4illin/ConvertX/compare/v0.12.1...v0.13.0) (2025-05-14)
 
-
 ### Features
 
-* add HIDE_HISTORY option to control visibility of history page ([9d1c931](https://github.com/C4illin/ConvertX/commit/9d1c93155cc33ed6c83f9e5122afff8f28d0e4bf))
-* add potrace converter ([bdbd4a1](https://github.com/C4illin/ConvertX/commit/bdbd4a122c09559b089b985ea12c5f3e085107da))
-* Add support for .HIF files ([70705c1](https://github.com/C4illin/ConvertX/commit/70705c1850d470296df85958c02a01fb5bc3a25f))
-* add support for drag/drop of images ([ff2ef74](https://github.com/C4illin/ConvertX/commit/ff2ef7413542cf10ba7a6e246763bcecd6829ec1))
-
+- add HIDE_HISTORY option to control visibility of history page ([9d1c931](https://github.com/C4illin/ConvertX/commit/9d1c93155cc33ed6c83f9e5122afff8f28d0e4bf))
+- add potrace converter ([bdbd4a1](https://github.com/C4illin/ConvertX/commit/bdbd4a122c09559b089b985ea12c5f3e085107da))
+- Add support for .HIF files ([70705c1](https://github.com/C4illin/ConvertX/commit/70705c1850d470296df85958c02a01fb5bc3a25f))
+- add support for drag/drop of images ([ff2ef74](https://github.com/C4illin/ConvertX/commit/ff2ef7413542cf10ba7a6e246763bcecd6829ec1))
 
 ### Bug Fixes
 
-* add timezone support ([4b5c732](https://github.com/C4illin/ConvertX/commit/4b5c732380bc844dccf340ea1eb4f8bfe3bb44a5)), closes [#258](https://github.com/C4illin/ConvertX/issues/258)
+- add timezone support ([4b5c732](https://github.com/C4illin/ConvertX/commit/4b5c732380bc844dccf340ea1eb4f8bfe3bb44a5)), closes [#258](https://github.com/C4illin/ConvertX/issues/258)
 
 ## [0.12.1](https://github.com/C4illin/ConvertX/compare/v0.12.0...v0.12.1) (2025-03-20)
 
-
 ### Bug Fixes
 
-* rollback to bun 1.2.2 ([cdae798](https://github.com/C4illin/ConvertX/commit/cdae798fcf5879e4adea87386a38748b9a1e1ddc))
+- rollback to bun 1.2.2 ([cdae798](https://github.com/C4illin/ConvertX/commit/cdae798fcf5879e4adea87386a38748b9a1e1ddc))
 
 ## [0.12.0](https://github.com/C4illin/ConvertX/compare/v0.11.1...v0.12.0) (2025-03-06)
 
-
 ### Features
 
-* added progress bar for file upload ([db60f35](https://github.com/C4illin/ConvertX/commit/db60f355b2973f43f8e5990e6fe4e351b959b659))
-* made every upload file independent ([cc54bdc](https://github.com/C4illin/ConvertX/commit/cc54bdcbe764c41cc3273485d072fd3178ad2dca))
-* replace exec with execFile ([9263d17](https://github.com/C4illin/ConvertX/commit/9263d17609dc4b2b367eb7fee67b3182e283b3a3))
-
+- added progress bar for file upload ([db60f35](https://github.com/C4illin/ConvertX/commit/db60f355b2973f43f8e5990e6fe4e351b959b659))
+- made every upload file independent ([cc54bdc](https://github.com/C4illin/ConvertX/commit/cc54bdcbe764c41cc3273485d072fd3178ad2dca))
+- replace exec with execFile ([9263d17](https://github.com/C4illin/ConvertX/commit/9263d17609dc4b2b367eb7fee67b3182e283b3a3))
 
 ### Bug Fixes
 
-* add libheif ([6b92540](https://github.com/C4illin/ConvertX/commit/6b9254047c0598963aee1d99e20ba1650a0368bf))
-* add libheif ([decfea5](https://github.com/C4illin/ConvertX/commit/decfea5dc9627b216bb276a9e1578c32cfa1deb6)), closes [#202](https://github.com/C4illin/ConvertX/issues/202)
-* added onerror log ([ae4bbc8](https://github.com/C4illin/ConvertX/commit/ae4bbc8baacbaf67763c62ea44140bb21cc17230))
-* refactored uploadFile to only accept a single file instead of multiple ([dc82a43](https://github.com/C4illin/ConvertX/commit/dc82a438d4104b79ff423d502a6779a43928968a))
-* update libheif to 1.19.5 ([fba5e21](https://github.com/C4illin/ConvertX/commit/fba5e212e8d0eaba8971e239e35aeb521f3cd813)), closes [#202](https://github.com/C4illin/ConvertX/issues/202)
+- add libheif ([6b92540](https://github.com/C4illin/ConvertX/commit/6b9254047c0598963aee1d99e20ba1650a0368bf))
+- add libheif ([decfea5](https://github.com/C4illin/ConvertX/commit/decfea5dc9627b216bb276a9e1578c32cfa1deb6)), closes [#202](https://github.com/C4illin/ConvertX/issues/202)
+- added onerror log ([ae4bbc8](https://github.com/C4illin/ConvertX/commit/ae4bbc8baacbaf67763c62ea44140bb21cc17230))
+- refactored uploadFile to only accept a single file instead of multiple ([dc82a43](https://github.com/C4illin/ConvertX/commit/dc82a438d4104b79ff423d502a6779a43928968a))
+- update libheif to 1.19.5 ([fba5e21](https://github.com/C4illin/ConvertX/commit/fba5e212e8d0eaba8971e239e35aeb521f3cd813)), closes [#202](https://github.com/C4illin/ConvertX/issues/202)
 
 ## [0.11.1](https://github.com/C4illin/ConvertX/compare/v0.11.0...v0.11.1) (2025-02-07)
 
-
 ### Bug Fixes
 
-* mobile view overflow ([bec58ac](https://github.com/C4illin/ConvertX/commit/bec58ac59f9600e35385b9e21d174f3ab1b42b1d))
+- mobile view overflow ([bec58ac](https://github.com/C4illin/ConvertX/commit/bec58ac59f9600e35385b9e21d174f3ab1b42b1d))
 
 ## [0.11.0](https://github.com/C4illin/ConvertX/compare/v0.10.1...v0.11.0) (2025-02-05)
 
-
 ### Features
 
-* add deps for vaapi ([2bbbd03](https://github.com/C4illin/ConvertX/commit/2bbbd03554d384a4488143f29e5fc863cfdf333b)), closes [#192](https://github.com/C4illin/ConvertX/issues/192)
-
+- add deps for vaapi ([2bbbd03](https://github.com/C4illin/ConvertX/commit/2bbbd03554d384a4488143f29e5fc863cfdf333b)), closes [#192](https://github.com/C4illin/ConvertX/issues/192)
 
 ### Bug Fixes
 
-* don't crash if file is not found ([16f27c1](https://github.com/C4illin/ConvertX/commit/16f27c13bbc1c0e5fa2316f3db11d0918524053b))
-* install numpy for inkscape ([0e61051](https://github.com/C4illin/ConvertX/commit/0e61051fc6be188164c3865b4fb579c140859fdc))
+- don't crash if file is not found ([16f27c1](https://github.com/C4illin/ConvertX/commit/16f27c13bbc1c0e5fa2316f3db11d0918524053b))
+- install numpy for inkscape ([0e61051](https://github.com/C4illin/ConvertX/commit/0e61051fc6be188164c3865b4fb579c140859fdc))
 
 ## [0.10.1](https://github.com/C4illin/ConvertX/compare/v0.10.0...v0.10.1) (2025-01-21)
 
-
 ### Bug Fixes
 
-* ffmpeg works without ffmpeg_args ([3b7ea88](https://github.com/C4illin/ConvertX/commit/3b7ea88b7382f7c21b120bdc9bda5bb10547f55d)), closes [#212](https://github.com/C4illin/ConvertX/issues/212)
+- ffmpeg works without ffmpeg_args ([3b7ea88](https://github.com/C4illin/ConvertX/commit/3b7ea88b7382f7c21b120bdc9bda5bb10547f55d)), closes [#212](https://github.com/C4illin/ConvertX/issues/212)
 
 ## [0.10.0](https://github.com/C4illin/ConvertX/compare/v0.9.0...v0.10.0) (2025-01-18)
 
-
 ### Features
 
-* add calibre ([03d3edf](https://github.com/C4illin/ConvertX/commit/03d3edfff65c252dd4b8922fc98257c089c1ff74)), closes [#191](https://github.com/C4illin/ConvertX/issues/191)
-
+- add calibre ([03d3edf](https://github.com/C4illin/ConvertX/commit/03d3edfff65c252dd4b8922fc98257c089c1ff74)), closes [#191](https://github.com/C4illin/ConvertX/issues/191)
 
 ### Bug Fixes
 
-* add FFMPEG_ARGS env variable ([f537c81](https://github.com/C4illin/ConvertX/commit/f537c81db7815df8017f834e3162291197e1c40f)), closes [#190](https://github.com/C4illin/ConvertX/issues/190)
-* add qt6-qtbase-private-dev from community repo ([95dbc9f](https://github.com/C4illin/ConvertX/commit/95dbc9f678bec7e6e2c03587e1473fb8ff708ea3))
-* skip account setup when ALLOW_UNAUTHENTICATED is true ([538c5b6](https://github.com/C4illin/ConvertX/commit/538c5b60c9e27a8184740305475245da79bae143))
+- add FFMPEG_ARGS env variable ([f537c81](https://github.com/C4illin/ConvertX/commit/f537c81db7815df8017f834e3162291197e1c40f)), closes [#190](https://github.com/C4illin/ConvertX/issues/190)
+- add qt6-qtbase-private-dev from community repo ([95dbc9f](https://github.com/C4illin/ConvertX/commit/95dbc9f678bec7e6e2c03587e1473fb8ff708ea3))
+- skip account setup when ALLOW_UNAUTHENTICATED is true ([538c5b6](https://github.com/C4illin/ConvertX/commit/538c5b60c9e27a8184740305475245da79bae143))
 
 ## [0.9.0](https://github.com/C4illin/ConvertX/compare/v0.8.1...v0.9.0) (2024-11-21)
 
-
 ### Features
 
-* add inkscape for vector images ([f3740e9](https://github.com/C4illin/ConvertX/commit/f3740e9ded100b8500f3613517960248bbd3c210))
-* Allow to chose webroot ([36cb6cc](https://github.com/C4illin/ConvertX/commit/36cb6cc589d80d0a87fa8dbe605db71a9a2570f9)), closes [#180](https://github.com/C4illin/ConvertX/issues/180)
-* disable convert when uploading ([58e220e](https://github.com/C4illin/ConvertX/commit/58e220e82d7f9c163d6ea4dc31092c08a3e254f4)), closes [#177](https://github.com/C4illin/ConvertX/issues/177)
-
+- add inkscape for vector images ([f3740e9](https://github.com/C4illin/ConvertX/commit/f3740e9ded100b8500f3613517960248bbd3c210))
+- Allow to chose webroot ([36cb6cc](https://github.com/C4illin/ConvertX/commit/36cb6cc589d80d0a87fa8dbe605db71a9a2570f9)), closes [#180](https://github.com/C4illin/ConvertX/issues/180)
+- disable convert when uploading ([58e220e](https://github.com/C4illin/ConvertX/commit/58e220e82d7f9c163d6ea4dc31092c08a3e254f4)), closes [#177](https://github.com/C4illin/ConvertX/issues/177)
 
 ### Bug Fixes
 
-* treat unknown as m4a ([1a442d6](https://github.com/C4illin/ConvertX/commit/1a442d6e69606afef63b1e7df36aa83d111fa23d)), closes [#178](https://github.com/C4illin/ConvertX/issues/178)
-* wait for both upload and selection ([4c05fd7](https://github.com/C4illin/ConvertX/commit/4c05fd72bbbf91ee02327f6fcbf749b78272376b)), closes [#177](https://github.com/C4illin/ConvertX/issues/177)
+- treat unknown as m4a ([1a442d6](https://github.com/C4illin/ConvertX/commit/1a442d6e69606afef63b1e7df36aa83d111fa23d)), closes [#178](https://github.com/C4illin/ConvertX/issues/178)
+- wait for both upload and selection ([4c05fd7](https://github.com/C4illin/ConvertX/commit/4c05fd72bbbf91ee02327f6fcbf749b78272376b)), closes [#177](https://github.com/C4illin/ConvertX/issues/177)
 
 ## [0.8.1](https://github.com/C4illin/ConvertX/compare/v0.8.0...v0.8.1) (2024-10-05)
 
-
 ### Bug Fixes
 
-* disable convert button when input is empty ([78844d7](https://github.com/C4illin/ConvertX/commit/78844d7bd55990789ed07c81e49043e688cbe656)), closes [#151](https://github.com/C4illin/ConvertX/issues/151)
-* resize to fit for ico ([b4e53db](https://github.com/C4illin/ConvertX/commit/b4e53dbb8e70b3a95b44e5b756759d16117a87e1)), closes [#157](https://github.com/C4illin/ConvertX/issues/157)
-* treat jfif as jpeg ([339b79f](https://github.com/C4illin/ConvertX/commit/339b79f786131deb93f0d5683e03178fdcab1ef5)), closes [#163](https://github.com/C4illin/ConvertX/issues/163)
+- disable convert button when input is empty ([78844d7](https://github.com/C4illin/ConvertX/commit/78844d7bd55990789ed07c81e49043e688cbe656)), closes [#151](https://github.com/C4illin/ConvertX/issues/151)
+- resize to fit for ico ([b4e53db](https://github.com/C4illin/ConvertX/commit/b4e53dbb8e70b3a95b44e5b756759d16117a87e1)), closes [#157](https://github.com/C4illin/ConvertX/issues/157)
+- treat jfif as jpeg ([339b79f](https://github.com/C4illin/ConvertX/commit/339b79f786131deb93f0d5683e03178fdcab1ef5)), closes [#163](https://github.com/C4illin/ConvertX/issues/163)
 
 ## [0.8.0](https://github.com/C4illin/ConvertX/compare/v0.7.0...v0.8.0) (2024-09-30)
 
-
 ### Features
 
-* add light theme, fixes [#156](https://github.com/C4illin/ConvertX/issues/156) ([72636c5](https://github.com/C4illin/ConvertX/commit/72636c5059ebf09c8fece2e268293650b2f8ccf6))
-
+- add light theme, fixes [#156](https://github.com/C4illin/ConvertX/issues/156) ([72636c5](https://github.com/C4illin/ConvertX/commit/72636c5059ebf09c8fece2e268293650b2f8ccf6))
 
 ### Bug Fixes
 
-* add support for usd for assimp, [#144](https://github.com/C4illin/ConvertX/issues/144) ([2057167](https://github.com/C4illin/ConvertX/commit/20571675766209ad1251f07e687d29a6791afc8b))
-* cleanup formats and add opus, fixes [#159](https://github.com/C4illin/ConvertX/issues/159) ([ae1dfaf](https://github.com/C4illin/ConvertX/commit/ae1dfafc9d9116a57b08c2f7fc326990e00824b0))
-* support .awb and clean up, fixes [#153](https://github.com/C4illin/ConvertX/issues/153), [#92](https://github.com/C4illin/ConvertX/issues/92) ([1c9e67f](https://github.com/C4illin/ConvertX/commit/1c9e67fc3201e0e5dee91e8981adf34daaabf33a))
+- add support for usd for assimp, [#144](https://github.com/C4illin/ConvertX/issues/144) ([2057167](https://github.com/C4illin/ConvertX/commit/20571675766209ad1251f07e687d29a6791afc8b))
+- cleanup formats and add opus, fixes [#159](https://github.com/C4illin/ConvertX/issues/159) ([ae1dfaf](https://github.com/C4illin/ConvertX/commit/ae1dfafc9d9116a57b08c2f7fc326990e00824b0))
+- support .awb and clean up, fixes [#153](https://github.com/C4illin/ConvertX/issues/153), [#92](https://github.com/C4illin/ConvertX/issues/92) ([1c9e67f](https://github.com/C4illin/ConvertX/commit/1c9e67fc3201e0e5dee91e8981adf34daaabf33a))
 
 ## [0.7.0](https://github.com/C4illin/ConvertX/compare/v0.6.0...v0.7.0) (2024-09-26)
 
-
 ### Features
 
-* Add support for 3d assets through assimp converter ([63a4328](https://github.com/C4illin/ConvertX/commit/63a4328d4a1e01df3e0ec4a877bad8c8ffe71129))
-
+- Add support for 3d assets through assimp converter ([63a4328](https://github.com/C4illin/ConvertX/commit/63a4328d4a1e01df3e0ec4a877bad8c8ffe71129))
 
 ### Bug Fixes
 
-* wrong layout on search with few options ([8817389](https://github.com/C4illin/ConvertX/commit/88173891ba2d69da46eda46f3f598a9b54f26f96))
+- wrong layout on search with few options ([8817389](https://github.com/C4illin/ConvertX/commit/88173891ba2d69da46eda46f3f598a9b54f26f96))
 
 ## [0.6.0](https://github.com/C4illin/ConvertX/compare/v0.5.0...v0.6.0) (2024-09-25)
 
-
 ### Features
 
-* ui remake with tailwind ([22f823c](https://github.com/C4illin/ConvertX/commit/22f823c535b20382981f86a13616b830a1f3392f))
-
+- ui remake with tailwind ([22f823c](https://github.com/C4illin/ConvertX/commit/22f823c535b20382981f86a13616b830a1f3392f))
 
 ### Bug Fixes
 
-* rename css file to force update cache, fixes [#141](https://github.com/C4illin/ConvertX/issues/141) ([47139a5](https://github.com/C4illin/ConvertX/commit/47139a550bd3d847da288c61bf8f88953b79c673))
+- rename css file to force update cache, fixes [#141](https://github.com/C4illin/ConvertX/issues/141) ([47139a5](https://github.com/C4illin/ConvertX/commit/47139a550bd3d847da288c61bf8f88953b79c673))
 
 ## [0.5.0](https://github.com/C4illin/ConvertX/compare/v0.4.1...v0.5.0) (2024-09-20)
 
-
 ### Features
 
-* add option to customize how often files are automatically deleted ([317c932](https://github.com/C4illin/ConvertX/commit/317c932c2a26280bf37ed3d3bf9b879413590f5a))
-
+- add option to customize how often files are automatically deleted ([317c932](https://github.com/C4illin/ConvertX/commit/317c932c2a26280bf37ed3d3bf9b879413590f5a))
 
 ### Bug Fixes
 
-* improve file name replacement logic ([60ba7c9](https://github.com/C4illin/ConvertX/commit/60ba7c93fbdc961f3569882fade7cc13dee7a7a5))
+- improve file name replacement logic ([60ba7c9](https://github.com/C4illin/ConvertX/commit/60ba7c93fbdc961f3569882fade7cc13dee7a7a5))
 
 ## [0.4.1](https://github.com/C4illin/ConvertX/compare/v0.4.0...v0.4.1) (2024-09-15)
 
-
 ### Bug Fixes
 
-* allow non lowercase true and false values, fixes [#122](https://github.com/C4illin/ConvertX/issues/122) ([bef1710](https://github.com/C4illin/ConvertX/commit/bef1710e3376baa7e25c107ded20a40d18b8c6b0))
+- allow non lowercase true and false values, fixes [#122](https://github.com/C4illin/ConvertX/issues/122) ([bef1710](https://github.com/C4illin/ConvertX/commit/bef1710e3376baa7e25c107ded20a40d18b8c6b0))
 
 ## [0.4.0](https://github.com/C4illin/ConvertX/compare/v0.3.3...v0.4.0) (2024-08-26)
 
-
 ### Features
 
-* add option for unauthenticated file conversions [#114](https://github.com/C4illin/ConvertX/issues/114) ([f0d0e43](https://github.com/C4illin/ConvertX/commit/f0d0e4392983c3e4c530304ea88e023fda9bcac0))
-* add resvg converter ([d5eeef9](https://github.com/C4illin/ConvertX/commit/d5eeef9f6884b2bb878508bed97ea9ceaa662995))
-* add robots.txt ([6597c1d](https://github.com/C4illin/ConvertX/commit/6597c1d7caeb4dfb6bc47b442e4dfc9840ad12b7))
-* Add search bar for formats ([53fff59](https://github.com/C4illin/ConvertX/commit/53fff594fc4d69306abcb2a5cad890fcd0953a58))
-
+- add option for unauthenticated file conversions [#114](https://github.com/C4illin/ConvertX/issues/114) ([f0d0e43](https://github.com/C4illin/ConvertX/commit/f0d0e4392983c3e4c530304ea88e023fda9bcac0))
+- add resvg converter ([d5eeef9](https://github.com/C4illin/ConvertX/commit/d5eeef9f6884b2bb878508bed97ea9ceaa662995))
+- add robots.txt ([6597c1d](https://github.com/C4illin/ConvertX/commit/6597c1d7caeb4dfb6bc47b442e4dfc9840ad12b7))
+- Add search bar for formats ([53fff59](https://github.com/C4illin/ConvertX/commit/53fff594fc4d69306abcb2a5cad890fcd0953a58))
 
 ### Bug Fixes
 
-* keep unauthenticated user logged in if allowed [#114](https://github.com/C4illin/ConvertX/issues/114) ([bc4ad49](https://github.com/C4illin/ConvertX/commit/bc4ad492852fad8cb832a0c03485cccdd7f7b117))
-* pdf support in vips ([8ca4f15](https://github.com/C4illin/ConvertX/commit/8ca4f1587df7f358893941c656d78d75f04dac93))
-* Slow click on conversion popup does not work ([4d9c4d6](https://github.com/C4illin/ConvertX/commit/4d9c4d64aa0266f3928935ada68d91ac81f638aa))
+- keep unauthenticated user logged in if allowed [#114](https://github.com/C4illin/ConvertX/issues/114) ([bc4ad49](https://github.com/C4illin/ConvertX/commit/bc4ad492852fad8cb832a0c03485cccdd7f7b117))
+- pdf support in vips ([8ca4f15](https://github.com/C4illin/ConvertX/commit/8ca4f1587df7f358893941c656d78d75f04dac93))
+- Slow click on conversion popup does not work ([4d9c4d6](https://github.com/C4illin/ConvertX/commit/4d9c4d64aa0266f3928935ada68d91ac81f638aa))
 
 ## [0.3.3](https://github.com/C4illin/ConvertX/compare/v0.3.2...v0.3.3) (2024-07-30)
 
-
 ### Bug Fixes
 
-* downgrade @elysiajs/html dependency to version 1.0.2 ([c714ade](https://github.com/C4illin/ConvertX/commit/c714ade3e23865ba6cfaf76c9e7259df1cda222c))
+- downgrade @elysiajs/html dependency to version 1.0.2 ([c714ade](https://github.com/C4illin/ConvertX/commit/c714ade3e23865ba6cfaf76c9e7259df1cda222c))
 
 ## [0.3.2](https://github.com/C4illin/ConvertX/compare/v0.3.1...v0.3.2) (2024-07-09)
 
-
 ### Bug Fixes
 
-* increase max request body to support large uploads ([3ae2db5](https://github.com/C4illin/ConvertX/commit/3ae2db5d9b36fe3dcd4372ddcd32aa573ea59aa6)), closes [#64](https://github.com/C4illin/ConvertX/issues/64)
+- increase max request body to support large uploads ([3ae2db5](https://github.com/C4illin/ConvertX/commit/3ae2db5d9b36fe3dcd4372ddcd32aa573ea59aa6)), closes [#64](https://github.com/C4illin/ConvertX/issues/64)
 
 ## [0.3.1](https://github.com/C4illin/ConvertX/compare/v0.3.0...v0.3.1) (2024-06-27)
 
-
 ### Bug Fixes
 
-* release releases ([4d4c13a](https://github.com/C4illin/ConvertX/commit/4d4c13a8d85ec7c9209ad41cdbea7d4380b0edbf))
+- release releases ([4d4c13a](https://github.com/C4illin/ConvertX/commit/4d4c13a8d85ec7c9209ad41cdbea7d4380b0edbf))
 
 ## [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))
+- 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)
+- 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)
+- 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)
+- :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))
-
+- 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))
+- release 0.1.0 ([54d9aec](https://github.com/C4illin/ConvertX/commit/54d9aecbf949689b12aa7e5e8e9be7b9032f4431))

+ 159 - 158
README.md

@@ -1,158 +1,159 @@
-![ConvertX](images/logo.png)
-
-# 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)
-[![ghcr.io Pulls](https://img.shields.io/badge/dynamic/json?logo=github&url=https%3A%2F%2Fipitio.github.io%2Fbackage%2FC4illin%2FConvertX%2Fconvertx.json&query=%24.downloads&label=ghcr.io%20pulls&cacheSeconds=14400)](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX)
-[![Docker Pulls](https://img.shields.io/docker/pulls/c4illin/convertx?style=flat&logo=docker&label=dockerhub%20pulls&link=https%3A%2F%2Fhub.docker.com%2Frepository%2Fdocker%2Fc4illin%2Fconvertx%2Fgeneral)](https://hub.docker.com/r/c4illin/convertx)
-[![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=)
-
-<a href="https://trendshift.io/repositories/13818" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13818" alt="C4illin%2FConvertX | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
-<!-- ![Dev image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=main&label=dev+image&trim=) -->
-
-A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia.
-
-## Features
-
-- Convert files to different formats
-- Process multiple files at once
-- Password protection
-- Multiple accounts
-
-## 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          |
-
-
-<!-- many ffmpeg fileformats are duplicates -->
-
-Any missing converter? Open an issue or pull request!
-
-## Deployment
-
-> [!WARNING]
-> If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true
-
-```yml
-# docker-compose.yml
-services:
-  convertx: 
-    image: ghcr.io/c4illin/convertx
-    container_name: convertx
-    restart: unless-stopped
-    ports:
-      - "3000:3000"
-    environment:
-      - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset
-    volumes:
-      - ./data:/app/data
-```
-
-or
-
-```bash
-docker run -p 3000:3000 -v ./data:/app/data ghcr.io/c4illin/convertx
-```
-
-Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account.
-
-If you get unable to open database file run `chown -R $USER:$USER path` on the path you choose.
-
-### Environment variables
-
-All are optional, JWT_SECRET is recommended to be set.
-
-| Name                      | Default | Description |
-|---------------------------|---------|-------------|
-| JWT_SECRET                | when unset it will use the value from randomUUID() | A long and secret string used to sign the JSON Web Token |
-| ACCOUNT_REGISTRATION      | false | Allow users to register accounts |
-| HTTP_ALLOWED              | false | Allow HTTP connections, only set this to true locally |
-| ALLOW_UNAUTHENTICATED     | false | Allow unauthenticated users to use the service, only set this to true locally |
-| AUTO_DELETE_EVERY_N_HOURS | 24 | Checks every n hours for files older then n hours and deletes them, set to 0 to disable |
-| WEBROOT                   |  | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" |
-| FFMPEG_ARGS               |  | Arguments to pass to ffmpeg, e.g. `-preset veryfast` |
-| HIDE_HISTORY              | false | Hide the history page |
-
-### Docker images
-
-There is a `:latest` tag that is updated with every release and a `:main` tag that is updated with every push to the main branch. `:latest` is recommended for normal use.
-
-The image is available on [GitHub Container Registry](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX) and [Docker Hub](https://hub.docker.com/r/c4illin/convertx).
-
-| Image | What it is |
-|-------|------------|
-| `image: ghcr.io/c4illin/convertx` | The latest release on ghcr |
-| `image: ghcr.io/c4illin/convertx:main` | The latest commit on ghcr |
-| `image: c4illin/convertx` | The latest release on docker hub |
-| `image: c4illin/convertx:main` | The latest commit on docker hub |
-
-![Release image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=latest&label=release+image&trim=)
-![Dev image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=main&label=dev+image&trim=)
-<!-- Dockerhub was introduced in 0.9.0 and older releases -->
-
-### Tutorial
-
-> [!NOTE]
-> These are written by other people, and may be outdated, incorrect or wrong.
-
-Tutorial in french: <https://belginux.com/installer-convertx-avec-docker/>
-
-Tutorial in chinese: <https://xzllll.com/24092901/>
-
-## Screenshots
-
-![ConvertX Preview](images/preview.png)
-
-## Development
-
-0. Install [Bun](https://bun.sh/) and Git
-1. Clone the repository
-2. `bun install`
-3. `bun run dev`
-
-Pull requests are welcome! See below and open issues for the list of todos.
-
-Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for commit messages.
-
-## Todo
-
-- [ ] Add options for converters
-- [ ] Add tests
-- [ ] Make errors logs visible from the web ui
-- [ ] Add more converters:
-  - [ ] [deark](https://github.com/jsummers/deark)
-  - [ ] LibreOffice
-
-## Contributors
-
-<a href="https://github.com/C4illin/ConvertX/graphs/contributors">
-  <img src="https://contrib.rocks/image?repo=C4illin/ConvertX" alt="Image with all contributors"/>
-</a>
-
-## Star History
-
-<a href="https://github.com/C4illin/ConvertX/stargazers">
- <picture>
-   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date&theme=dark" />
-   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" />
-   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" />
- </picture>
-</a>
+![ConvertX](images/logo.png)
+
+# 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)
+[![ghcr.io Pulls](https://img.shields.io/badge/dynamic/json?logo=github&url=https%3A%2F%2Fipitio.github.io%2Fbackage%2FC4illin%2FConvertX%2Fconvertx.json&query=%24.downloads&label=ghcr.io%20pulls&cacheSeconds=14400)](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX)
+[![Docker Pulls](https://img.shields.io/docker/pulls/c4illin/convertx?style=flat&logo=docker&label=dockerhub%20pulls&link=https%3A%2F%2Fhub.docker.com%2Frepository%2Fdocker%2Fc4illin%2Fconvertx%2Fgeneral)](https://hub.docker.com/r/c4illin/convertx)
+[![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=)
+
+<a href="https://trendshift.io/repositories/13818" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13818" alt="C4illin%2FConvertX | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
+
+<!-- ![Dev image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=main&label=dev+image&trim=) -->
+
+A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia.
+
+## Features
+
+- Convert files to different formats
+- Process multiple files at once
+- Password protection
+- Multiple accounts
+
+## 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          |
+
+<!-- many ffmpeg fileformats are duplicates -->
+
+Any missing converter? Open an issue or pull request!
+
+## Deployment
+
+> [!WARNING]
+> If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true
+
+```yml
+# docker-compose.yml
+services:
+  convertx:
+    image: ghcr.io/c4illin/convertx
+    container_name: convertx
+    restart: unless-stopped
+    ports:
+      - "3000:3000"
+    environment:
+      - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset
+    volumes:
+      - ./data:/app/data
+```
+
+or
+
+```bash
+docker run -p 3000:3000 -v ./data:/app/data ghcr.io/c4illin/convertx
+```
+
+Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account.
+
+If you get unable to open database file run `chown -R $USER:$USER path` on the path you choose.
+
+### Environment variables
+
+All are optional, JWT_SECRET is recommended to be set.
+
+| Name                      | Default                                            | Description                                                                                              |
+| ------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
+| JWT_SECRET                | when unset it will use the value from randomUUID() | A long and secret string used to sign the JSON Web Token                                                 |
+| ACCOUNT_REGISTRATION      | false                                              | Allow users to register accounts                                                                         |
+| HTTP_ALLOWED              | false                                              | Allow HTTP connections, only set this to true locally                                                    |
+| ALLOW_UNAUTHENTICATED     | false                                              | Allow unauthenticated users to use the service, only set this to true locally                            |
+| AUTO_DELETE_EVERY_N_HOURS | 24                                                 | Checks every n hours for files older then n hours and deletes them, set to 0 to disable                  |
+| WEBROOT                   |                                                    | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" |
+| FFMPEG_ARGS               |                                                    | Arguments to pass to ffmpeg, e.g. `-preset veryfast`                                                     |
+| HIDE_HISTORY              | false                                              | Hide the history page                                                                                    |
+
+### Docker images
+
+There is a `:latest` tag that is updated with every release and a `:main` tag that is updated with every push to the main branch. `:latest` is recommended for normal use.
+
+The image is available on [GitHub Container Registry](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX) and [Docker Hub](https://hub.docker.com/r/c4illin/convertx).
+
+| Image                                  | What it is                       |
+| -------------------------------------- | -------------------------------- |
+| `image: ghcr.io/c4illin/convertx`      | The latest release on ghcr       |
+| `image: ghcr.io/c4illin/convertx:main` | The latest commit on ghcr        |
+| `image: c4illin/convertx`              | The latest release on docker hub |
+| `image: c4illin/convertx:main`         | The latest commit on docker hub  |
+
+![Release image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=latest&label=release+image&trim=)
+![Dev image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=main&label=dev+image&trim=)
+
+<!-- Dockerhub was introduced in 0.9.0 and older releases -->
+
+### Tutorial
+
+> [!NOTE]
+> These are written by other people, and may be outdated, incorrect or wrong.
+
+Tutorial in french: <https://belginux.com/installer-convertx-avec-docker/>
+
+Tutorial in chinese: <https://xzllll.com/24092901/>
+
+## Screenshots
+
+![ConvertX Preview](images/preview.png)
+
+## Development
+
+0. Install [Bun](https://bun.sh/) and Git
+1. Clone the repository
+2. `bun install`
+3. `bun run dev`
+
+Pull requests are welcome! See below and open issues for the list of todos.
+
+Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for commit messages.
+
+## Todo
+
+- [ ] Add options for converters
+- [ ] Add tests
+- [ ] Make errors logs visible from the web ui
+- [ ] Add more converters:
+  - [ ] [deark](https://github.com/jsummers/deark)
+  - [ ] LibreOffice
+
+## Contributors
+
+<a href="https://github.com/C4illin/ConvertX/graphs/contributors">
+  <img src="https://contrib.rocks/image?repo=C4illin/ConvertX" alt="Image with all contributors"/>
+</a>
+
+## Star History
+
+<a href="https://github.com/C4illin/ConvertX/stargazers">
+ <picture>
+   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date&theme=dark" />
+   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" />
+   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" />
+ </picture>
+</a>

+ 2 - 5
biome.json

@@ -10,10 +10,7 @@
     "attributePosition": "auto"
   },
   "files": {
-    "ignore": [
-      "**/node_modules/**",
-      "**/pico.lime.min.css"
-    ]
+    "ignore": ["**/node_modules/**", "**/pico.lime.min.css"]
   },
   "organizeImports": {
     "enabled": true
@@ -72,4 +69,4 @@
       "attributePosition": "auto"
     }
   }
-}
+}

+ 19 - 19
compose.yaml

@@ -1,19 +1,19 @@
-services:
-  convertx:
-    build:
-      context: .
-      # dockerfile: Debian.Dockerfile
-    volumes:
-      - ./data:/app/data
-    environment: # Defaults are listed below. All are optional.
-      - ACCOUNT_REGISTRATION=true # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account)
-      - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default
-      - HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally
-      - ALLOW_UNAUTHENTICATED=false # allows anyone to use the service without logging in, only set this to true locally
-      - AUTO_DELETE_EVERY_N_HOURS=1 # checks every n hours for files older then n hours and deletes them, set to 0 to disable
-      # - FFMPEG_ARGS=-hwaccel vulkan # additional arguments to pass to ffmpeg
-      # - WEBROOT=/convertx # the root path of the web interface, leave empty to disable
-      # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
-      - TZ=Europe/Stockholm # set your timezone, defaults to UTC
-    ports:
-      - 3000:3000
+services:
+  convertx:
+    build:
+      context: .
+      # dockerfile: Debian.Dockerfile
+    volumes:
+      - ./data:/app/data
+    environment: # Defaults are listed below. All are optional.
+      - ACCOUNT_REGISTRATION=true # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account)
+      - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default
+      - HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally
+      - ALLOW_UNAUTHENTICATED=false # allows anyone to use the service without logging in, only set this to true locally
+      - AUTO_DELETE_EVERY_N_HOURS=1 # checks every n hours for files older then n hours and deletes them, set to 0 to disable
+      # - FFMPEG_ARGS=-hwaccel vulkan # additional arguments to pass to ffmpeg
+      # - WEBROOT=/convertx # the root path of the web interface, leave empty to disable
+      # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
+      - TZ=Europe/Stockholm # set your timezone, defaults to UTC
+    ports:
+      - 3000:3000

+ 66 - 66
eslint.config.ts

@@ -1,66 +1,66 @@
-import js from "@eslint/js";
-import eslintParserTypeScript from "@typescript-eslint/parser";
-import type { Linter } from "eslint";
-import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
-import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
-import globals from "globals";
-import tseslint from "typescript-eslint";
-
-export default [
-  js.configs.recommended,
-  ...tseslint.configs.recommended,
-  // ...tailwind.configs["flat/recommended"],
-  {
-    plugins: {
-      "simple-import-sort": simpleImportSortPlugin,
-      "better-tailwindcss": eslintPluginBetterTailwindcss,
-    },
-    ignores: ["**/node_modules/**"],
-    languageOptions: {
-      parser: eslintParserTypeScript,
-      parserOptions: {
-        project: true,
-        ecmaFeatures: {
-          jsx: true,
-        },
-      },
-      globals: {
-        ...globals.node,
-        ...globals.browser,
-      },
-    },
-    files: ["**/*.{js,mjs,cjs,jsx,tsx,ts}"],
-    settings: {
-      "better-tailwindcss": {
-        entryPoint: "src/main.css",
-      },
-    },
-    rules: {
-      ...eslintPluginBetterTailwindcss.configs["recommended-warn"]?.rules,
-      ...eslintPluginBetterTailwindcss.configs["stylistic-warn"]?.rules,
-      // "tailwindcss/classnames-order": "off",
-      "better-tailwindcss/multiline": [
-        "warn",
-        {
-          group: "newLine",
-          printWidth: 100,
-        },
-      ],
-      "better-tailwindcss/no-unregistered-classes": [
-        "warn",
-        {
-          ignore: [
-            "^group(?:\\/(\\S*))?$",
-            "^peer(?:\\/(\\S*))?$",
-            "select_container",
-            "convert_to_popup",
-            "convert_to_group",
-            "target",
-            "convert_to_target",
-            "job-details-toggle",
-          ],
-        },
-      ],
-    },
-  },
-] as Linter.Config[];
+import js from "@eslint/js";
+import eslintParserTypeScript from "@typescript-eslint/parser";
+import type { Linter } from "eslint";
+import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
+import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
+import globals from "globals";
+import tseslint from "typescript-eslint";
+
+export default [
+  js.configs.recommended,
+  ...tseslint.configs.recommended,
+  // ...tailwind.configs["flat/recommended"],
+  {
+    plugins: {
+      "simple-import-sort": simpleImportSortPlugin,
+      "better-tailwindcss": eslintPluginBetterTailwindcss,
+    },
+    ignores: ["**/node_modules/**"],
+    languageOptions: {
+      parser: eslintParserTypeScript,
+      parserOptions: {
+        project: true,
+        ecmaFeatures: {
+          jsx: true,
+        },
+      },
+      globals: {
+        ...globals.node,
+        ...globals.browser,
+      },
+    },
+    files: ["**/*.{js,mjs,cjs,jsx,tsx,ts}"],
+    settings: {
+      "better-tailwindcss": {
+        entryPoint: "src/main.css",
+      },
+    },
+    rules: {
+      ...(eslintPluginBetterTailwindcss.configs["recommended-warn"] ?? {}).rules,
+      ...(eslintPluginBetterTailwindcss.configs["stylistic-warn"] ?? {}).rules,
+      // "tailwindcss/classnames-order": "off",
+      "better-tailwindcss/multiline": [
+        "warn",
+        {
+          group: "newLine",
+          printWidth: 100,
+        },
+      ],
+      "better-tailwindcss/no-unregistered-classes": [
+        "warn",
+        {
+          ignore: [
+            "^group(?:\\/(\\S*))?$",
+            "^peer(?:\\/(\\S*))?$",
+            "select_container",
+            "convert_to_popup",
+            "convert_to_group",
+            "target",
+            "convert_to_target",
+            "job-details-toggle",
+          ],
+        },
+      ],
+    },
+  },
+] as Linter.Config[];

+ 5 - 2
package.json

@@ -4,12 +4,15 @@
   "scripts": {
     "dev": "bun run --watch src/index.tsx",
     "hot": "bun run --hot src/index.tsx",
-    "format": "eslint --fix .",
+    "format": "run-p 'format:*'",
+    "format:eslint": "eslint --fix .",
+    "format:prettier": "prettier --write .",
     "build": "bunx @tailwindcss/cli -i ./src/main.css -o ./public/generated.css",
     "lint": "run-p 'lint:*'",
     "lint:tsc": "tsc --noEmit",
     "lint:knip": "knip",
-    "lint:eslint": "eslint ."
+    "lint:eslint": "eslint .",
+    "lint:prettier": "prettier --check ."
   },
   "dependencies": {
     "@elysiajs/html": "^1.3.0",

+ 5 - 5
postcss.config.js

@@ -1,5 +1,5 @@
-export default {
-  plugins: {
-    "@tailwindcss/postcss": {},
-  },
-};
+export default {
+  plugins: {
+    "@tailwindcss/postcss": {},
+  },
+};

+ 1 - 1
prettier.config.js

@@ -3,7 +3,7 @@
  */
 const config = {
   arrowParens: "always",
-  printWidth: 80,
+  printWidth: 100,
   singleQuote: false,
   semi: true,
   tabWidth: 2,

+ 3 - 6
public/script.js

@@ -72,9 +72,7 @@ function handleFile(file) {
 const selectContainer = document.querySelector("form .select_container");
 
 const updateSearchBar = () => {
-  const convertToInput = document.querySelector(
-    "input[name='convert_to_search']",
-  );
+  const convertToInput = document.querySelector("input[name='convert_to_search']");
   const convertToPopup = document.querySelector(".convert_to_popup");
   const convertToGroupElements = document.querySelectorAll(".convert_to_group");
   const convertToGroups = {};
@@ -195,8 +193,7 @@ const deleteRow = (target) => {
     headers: {
       "Content-Type": "application/json",
     },
-  })
-    .catch((err) => console.log(err));
+  }).catch((err) => console.log(err));
 };
 
 const uploadFile = (file) => {
@@ -234,7 +231,7 @@ const uploadFile = (file) => {
     console.log(`upload progress (${file.name}):`, (100 * sent) / total);
 
     let progressbar = file.htmlRow.getElementsByTagName("progress");
-    progressbar[0].value = ((100 * sent) / total);
+    progressbar[0].value = (100 * sent) / total;
   };
 
   xhr.onerror = (e) => {

+ 2 - 5
renovate.json

@@ -1,11 +1,8 @@
 {
   "$schema": "https://docs.renovatebot.com/renovate-schema.json",
-  "extends": [
-    "config:recommended",
-    ":disableDependencyDashboard"
-  ],
+  "extends": ["config:recommended", ":disableDependencyDashboard"],
   "lockFileMaintenance": {
     "enabled": true,
     "automerge": true
   }
-}
+}

+ 1 - 1
reset.d.ts

@@ -1 +1 @@
-import "@total-typescript/ts-reset";
+import "@total-typescript/ts-reset";

+ 3 - 17
src/components/base.tsx

@@ -17,23 +17,9 @@ export const BaseHtml = ({
       <meta name="webroot" content={webroot} />
       <title safe>{title}</title>
       <link rel="stylesheet" href={`${webroot}/generated.css`} />
-      <link
-        rel="apple-touch-icon"
-        sizes="180x180"
-        href={`${webroot}/apple-touch-icon.png`}
-      />
-      <link
-        rel="icon"
-        type="image/png"
-        sizes="32x32"
-        href={`${webroot}/favicon-32x32.png`}
-      />
-      <link
-        rel="icon"
-        type="image/png"
-        sizes="16x16"
-        href={`${webroot}/favicon-16x16.png`}
-      />
+      <link rel="apple-touch-icon" sizes="180x180" href={`${webroot}/apple-touch-icon.png`} />
+      <link rel="icon" type="image/png" sizes="32x32" href={`${webroot}/favicon-32x32.png`} />
+      <link rel="icon" type="image/png" sizes="16x16" href={`${webroot}/favicon-16x16.png`} />
       <link rel="manifest" href={`${webroot}/site.webmanifest`} />
     </head>
     <body class="flex min-h-screen w-full flex-col bg-neutral-900 text-neutral-200">

+ 139 - 139
src/converters/assimp.ts

@@ -1,139 +1,139 @@
-import { execFile } from "node:child_process";
-
-export const properties = {
-  from: {
-    object: [
-      "3d",
-      "3ds",
-      "3mf",
-      "ac",
-      "ac3d",
-      "acc",
-      "amf",
-      "amj",
-      "ase",
-      "ask",
-      "assbin",
-      "b3d",
-      "blend",
-      "bsp",
-      "bvh",
-      "cob",
-      "csm",
-      "dae",
-      "dxf",
-      "enff",
-      "fbx",
-      "glb",
-      "gltf",
-      "hmb",
-      "hmp",
-      "ifc",
-      "ifczip",
-      "iqm",
-      "irr",
-      "irrmesh",
-      "lwo",
-      "lws",
-      "lxo",
-      "m3d",
-      "md2",
-      "md3",
-      "md5anim",
-      "md5camera",
-      "md5mesh",
-      "mdc",
-      "mdl",
-      "mesh.xml",
-      "mesh",
-      "mot",
-      "ms3d",
-      "ndo",
-      "nff",
-      "obj",
-      "off",
-      "ogex",
-      "pk3",
-      "ply",
-      "pmx",
-      "prj",
-      "q3o",
-      "q3s",
-      "raw",
-      "scn",
-      "sib",
-      "smd",
-      "step",
-      "stl",
-      "stp",
-      "ter",
-      "uc",
-      "usd",
-      "usda",
-      "usdc",
-      "usdz",
-      "vta",
-      "x",
-      "x3d",
-      "x3db",
-      "xgl",
-      "xml",
-      "zae",
-      "zgl",
-    ],
-  },
-  to: {
-    object: [
-      "3ds",
-      "3mf",
-      "assbin",
-      "assjson",
-      "assxml",
-      "collada",
-      "dae",
-      "fbx",
-      "fbxa",
-      "glb",
-      "glb2",
-      "gltf",
-      "gltf2",
-      "json",
-      "obj",
-      "objnomtl",
-      "pbrt",
-      "ply",
-      "plyb",
-      "stl",
-      "stlb",
-      "stp",
-      "x",
-    ],
-  },
-};
-
-export async function convert(
-  filePath: string,
-  fileType: string,
-  convertTo: string,
-  targetPath: string,
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  options?: unknown,
-): Promise<string> {
-  return new Promise((resolve, reject) => {
-    execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => {
-      if (error) {
-        reject(`error: ${error}`);
-      }
-
-      if (stdout) {
-        console.log(`stdout: ${stdout}`);
-      }
-
-      if (stderr) {
-        console.error(`stderr: ${stderr}`);
-      }
-
-      resolve("Done");
-    });
-  });
-}
+import { execFile } from "node:child_process";
+
+export const properties = {
+  from: {
+    object: [
+      "3d",
+      "3ds",
+      "3mf",
+      "ac",
+      "ac3d",
+      "acc",
+      "amf",
+      "amj",
+      "ase",
+      "ask",
+      "assbin",
+      "b3d",
+      "blend",
+      "bsp",
+      "bvh",
+      "cob",
+      "csm",
+      "dae",
+      "dxf",
+      "enff",
+      "fbx",
+      "glb",
+      "gltf",
+      "hmb",
+      "hmp",
+      "ifc",
+      "ifczip",
+      "iqm",
+      "irr",
+      "irrmesh",
+      "lwo",
+      "lws",
+      "lxo",
+      "m3d",
+      "md2",
+      "md3",
+      "md5anim",
+      "md5camera",
+      "md5mesh",
+      "mdc",
+      "mdl",
+      "mesh.xml",
+      "mesh",
+      "mot",
+      "ms3d",
+      "ndo",
+      "nff",
+      "obj",
+      "off",
+      "ogex",
+      "pk3",
+      "ply",
+      "pmx",
+      "prj",
+      "q3o",
+      "q3s",
+      "raw",
+      "scn",
+      "sib",
+      "smd",
+      "step",
+      "stl",
+      "stp",
+      "ter",
+      "uc",
+      "usd",
+      "usda",
+      "usdc",
+      "usdz",
+      "vta",
+      "x",
+      "x3d",
+      "x3db",
+      "xgl",
+      "xml",
+      "zae",
+      "zgl",
+    ],
+  },
+  to: {
+    object: [
+      "3ds",
+      "3mf",
+      "assbin",
+      "assjson",
+      "assxml",
+      "collada",
+      "dae",
+      "fbx",
+      "fbxa",
+      "glb",
+      "glb2",
+      "gltf",
+      "gltf2",
+      "json",
+      "obj",
+      "objnomtl",
+      "pbrt",
+      "ply",
+      "plyb",
+      "stl",
+      "stlb",
+      "stp",
+      "x",
+    ],
+  },
+};
+
+export async function convert(
+  filePath: string,
+  fileType: string,
+  convertTo: string,
+  targetPath: string,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  return new Promise((resolve, reject) => {
+    execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
+
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
+
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
+
+      resolve("Done");
+    });
+  });
+}

+ 12 - 16
src/converters/dvisvgm.ts

@@ -29,24 +29,20 @@ export function convert(
   }
 
   return new Promise((resolve, reject) => {
-    execFile(
-      "dvisvgm",
-      [...inputArgs, filePath, "-o", targetPath],
-      (error, stdout, stderr) => {
-        if (error) {
-          reject(`error: ${error}`);
-        }
+    execFile("dvisvgm", [...inputArgs, filePath, "-o", targetPath], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
 
-        if (stdout) {
-          console.log(`stdout: ${stdout}`);
-        }
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
 
-        if (stderr) {
-          console.error(`stderr: ${stderr}`);
-        }
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
 
-        resolve("Done");
-      },
-    );
+      resolve("Done");
+    });
   });
 }

+ 754 - 756
src/converters/ffmpeg.ts

@@ -1,756 +1,754 @@
-import { execFile } from "node:child_process";
-
-// This could be done dynamically by running `ffmpeg -formats` and parsing the output
-export const properties = {
-  from: {
-    muxer: [
-      "264",
-      "265",
-      "266",
-      "302",
-      "3dostr",
-      "3g2",
-      "3gp",
-      "4xm",
-      "669",
-      "722",
-      "aa",
-      "aa3",
-      "aac",
-      "aax",
-      "ac3",
-      "ac4",
-      "ace",
-      "acm",
-      "act",
-      "adf",
-      "adp",
-      "ads",
-      "adx",
-      "aea",
-      "afc",
-      "aiff",
-      "aix",
-      "al",
-      "alaw",
-      "alias_pix",
-      "alp",
-      "alsa",
-      "amf",
-      "amr",
-      "amrnb",
-      "amrwb",
-      "ams",
-      "anm",
-      "ans",
-      "apc",
-      "ape",
-      "apl",
-      "apm",
-      "apng",
-      "aptx",
-      "aptxhd",
-      "aqt",
-      "aqtitle",
-      "argo_asf",
-      "argo_brp",
-      "art",
-      "asc",
-      "asf",
-      "asf_o",
-      "ass",
-      "ast",
-      "au",
-      "av1",
-      "avc",
-      "avi",
-      "avif",
-      "avr",
-      "avs",
-      "avs2",
-      "avs3",
-      "awb",
-      "bcstm",
-      "bethsoftvid",
-      "bfi",
-      "bfstm",
-      "bin",
-      "bink",
-      "binka",
-      "bit",
-      "bitpacked",
-      "bmv",
-      "bmp",
-      "bonk",
-      "boa",
-      "brender_pix",
-      "brstm",
-      "c2",
-      "c93",
-      "caf",
-      "cavsvideo",
-      "cdata",
-      "cdg",
-      "cdxl",
-      "cgi",
-      "cif",
-      "cine",
-      "codec2",
-      "codec2raw",
-      "concat",
-      "cri",
-      "dash",
-      "dat",
-      "data",
-      "daud",
-      "dav",
-      "dbm",
-      "dcstr",
-      "dds",
-      "derf",
-      "dfpwm",
-      "dfa",
-      "dhav",
-      "dif",
-      "digi",
-      "dirac",
-      "diz",
-      "dmf",
-      "dnxhd",
-      "dpx_pipe",
-      "dsf",
-      "dsicin",
-      "dsm",
-      "dss",
-      "dtk",
-      "dtm",
-      "dts",
-      "dtshd",
-      "dv",
-      "dvbsub",
-      "dvbtxt",
-      "dxa",
-      "ea",
-      "eac3",
-      "ea_cdata",
-      "epaf",
-      "exr_pipe",
-      "f32be",
-      "f32le",
-      "ec3",
-      "evc",
-      "f4v",
-      "f64be",
-      "f64le",
-      "fap",
-      "far",
-      "fbdev",
-      "ffmetadata",
-      "filmstrip",
-      "film_cpk",
-      "fits",
-      "flac",
-      "flic",
-      "flm",
-      "flv",
-      "frm",
-      "fsb",
-      "fwse",
-      "g722",
-      "g723_1",
-      "g726",
-      "g726le",
-      "g729",
-      "gdm",
-      "gdv",
-      "genh",
-      "gif",
-      "gsm",
-      "gxf",
-      "h261",
-      "h263",
-      "h264",
-      "h265",
-      "h266",
-      "h26l",
-      "hca",
-      "hcom",
-      "hevc",
-      "hls",
-      "hnm",
-      "ice",
-      "ico",
-      "idcin",
-      "idf",
-      "idx",
-      "iec61883",
-      "iff",
-      "ifv",
-      "ilbc",
-      "image2",
-      "imf",
-      "imx",
-      "ingenient",
-      "ipmovie",
-      "ipu",
-      "ircam",
-      "ism",
-      "isma",
-      "ismv",
-      "iss",
-      "it",
-      "iv8",
-      "ivf",
-      "ivr",
-      "j2b",
-      "j2k",
-      "jack",
-      "jacosub",
-      "jv",
-      "jpegls",
-      "jpeg",
-      "jxl",
-      "kmsgrab",
-      "kux",
-      "kvag",
-      "lavfi",
-      "laf",
-      "lmlm4",
-      "loas",
-      "lrc",
-      "luodat",
-      "lvf",
-      "lxf",
-      "m15",
-      "m2a",
-      "m4a",
-      "m4b",
-      "m4v",
-      "mac",
-      "mca",
-      "mcc",
-      "mdl",
-      "med",
-      "microdvd",
-      "mj2",
-      "mjpeg",
-      "mjpg",
-      "mk3d",
-      "mka",
-      "mks",
-      "mkv",
-      "mlp",
-      "mlv",
-      "mm",
-      "mmcmp",
-      "mmf",
-      "mms",
-      "mo3",
-      "mod",
-      "mods",
-      "moflex",
-      "mov",
-      "mp2",
-      "mp3",
-      "mp4",
-      "mpa",
-      "mpc",
-      "mpc8",
-      "mpeg",
-      "mpg",
-      "mpjpeg",
-      "mpl2",
-      "mpo",
-      "mpsub",
-      "mptm",
-      "msbc",
-      "msf",
-      "msnwctcp",
-      "msp",
-      "mt2",
-      "mtaf",
-      "mtm",
-      "mtv",
-      "mulaw",
-      "musx",
-      "mv",
-      "mvi",
-      "mxf",
-      "mxg",
-      "nc",
-      "nfo",
-      "nist",
-      "nistsphere",
-      "nsp",
-      "nst",
-      "nsv",
-      "nut",
-      "nuv",
-      "obu",
-      "ogg",
-      "okt",
-      "oma",
-      "omg",
-      "opus",
-      "openal",
-      "oss",
-      "osq",
-      "paf",
-      "pdv",
-      "pam",
-      "pbm",
-      "pcx",
-      "pgmyuv",
-      "pgm",
-      "pgx",
-      "photocd",
-      "pictor",
-      "pjs",
-      "plm",
-      "pmp",
-      "png",
-      "ppm",
-      "pp",
-      "psd",
-      "psm",
-      "psp",
-      "psxstr",
-      "pt36",
-      "ptm",
-      "pulse",
-      "pva",
-      "pvf",
-      "qcif",
-      "qcp",
-      "qdraw",
-      "r3d",
-      "rawvideo",
-      "rco",
-      "rcv",
-      "realtext",
-      "redspark",
-      "rgb",
-      "rl2",
-      "rm",
-      "roq",
-      "rpl",
-      "rka",
-      "rsd",
-      "rso",
-      "rt",
-      "rtp",
-      "rtsp",
-      "s16be",
-      "s16le",
-      "s24be",
-      "s24le",
-      "s32be",
-      "s32le",
-      "s337m",
-      "s3m",
-      "s8",
-      "sami",
-      "sap",
-      "sb",
-      "sbc",
-      "sbg",
-      "scc",
-      "sdns",
-      "sdp",
-      "sdr2",
-      "sds",
-      "sdx",
-      "ser",
-      "sf",
-      "sfx",
-      "sfx2",
-      "sga",
-      "sgi",
-      "shn",
-      "siff",
-      "sln",
-      "smi",
-      "smjpeg",
-      "smk",
-      "smush",
-      "sndio",
-      "sol",
-      "son",
-      "sox",
-      "spdif",
-      "sph",
-      "srt",
-      "ss2",
-      "ssa",
-      "st26",
-      "stk",
-      "stl",
-      "stm",
-      "stp",
-      "str",
-      "sub",
-      "sup",
-      "svag",
-      "svg",
-      "svs",
-      "sw",
-      "swf",
-      "tak",
-      "tco",
-      "tedcaptions",
-      "thd",
-      "thp",
-      "tiertexseq",
-      "tif",
-      "tiff",
-      "tmv",
-      "truehd",
-      "tta",
-      "tty",
-      "txd",
-      "txt",
-      "ty",
-      "ty+",
-      "u16be",
-      "u16le",
-      "u24be",
-      "u24le",
-      "u32be",
-      "u32le",
-      "u8",
-      "ub",
-      "ul",
-      "ult",
-      "umx",
-      "usm",
-      "uw",
-      "v",
-      "v210",
-      "v210x",
-      "vag",
-      "vb",
-      "vc1",
-      "vc1test",
-      "vidc",
-      "video4linux2",
-      "viv",
-      "vividas",
-      "vivo",
-      "vmd",
-      "vobsub",
-      "voc",
-      "vpk",
-      "vplayer",
-      "vqe",
-      "vqf",
-      "vql",
-      "vt",
-      "vtt",
-      "vvc",
-      "w64",
-      "wa",
-      "wav",
-      "way",
-      "wc3movie",
-      "webm",
-      "webp",
-      "webvtt",
-      "wow",
-      "wsaud",
-      "wsd",
-      "wsvqa",
-      "wtv",
-      "wv",
-      "wve",
-      "x11grab",
-      "xa",
-      "xbin",
-      "xl",
-      "xm",
-      "xmd",
-      "xmv",
-      "xpk",
-      "xvag",
-      "xwma",
-      "y4m",
-      "yop",
-      "yuv",
-      "yuv10",
-    ],
-  },
-  to: {
-    muxer: [
-      "264",
-      "265",
-      "266",
-      "302",
-      "3g2",
-      "3gp",
-      "a64",
-      "aac",
-      "ac3",
-      "ac4",
-      "adts",
-      "adx",
-      "afc",
-      "aif",
-      "aifc",
-      "aiff",
-      "al",
-      "amr",
-      "amv",
-      "apm",
-      "apng",
-      "aptx",
-      "aptxhd",
-      "asf",
-      "ass",
-      "ast",
-      "au",
-      "aud",
-      "av1.mkv",
-      "av1.mp4",
-      "avi",
-      "avif",
-      "avs",
-      "avs2",
-      "avs3",
-      "bit",
-      "bmp",
-      "c2",
-      "caf",
-      "cavs",
-      "chk",
-      "cpk",
-      "cvg",
-      "dfpwm",
-      "dnxhd",
-      "dnxhr",
-      "dpx",
-      "drc",
-      "dts",
-      "dv",
-      "dvd",
-      "eac3",
-      "ec3",
-      "evc",
-      "exr",
-      "f4v",
-      "ffmeta",
-      "fits",
-      "flac",
-      "flm",
-      "flv",
-      "g722",
-      "gif",
-      "gsm",
-      "gxf",
-      "h261",
-      "h263",
-      "h264.mkv",
-      "h264.mp4",
-      "h265.mkv",
-      "h265.mp4",
-      "h266.mkv",
-      "hdr",
-      "hevc",
-      "ico",
-      "im1",
-      "im24",
-      "im8",
-      "ircam",
-      "isma",
-      "ismv",
-      "ivf",
-      "j2c",
-      "j2k",
-      "jls",
-      "jp2",
-      "jpeg",
-      "jpg",
-      "js",
-      "jss",
-      "jxl",
-      "latm",
-      "lbc",
-      "ljpg",
-      "loas",
-      "lrc",
-      "m1v",
-      "m2a",
-      "m2t",
-      "m2ts",
-      "m2v",
-      "m3u8",
-      "m4a",
-      "m4b",
-      "m4v",
-      "mjpeg",
-      "mjpg",
-      "mkv",
-      "mlp",
-      "mmf",
-      "mov",
-      "mp2",
-      "mp3",
-      "mp4",
-      "mpa",
-      "mpd",
-      "mpeg",
-      "mpg",
-      "msbc",
-      "mts",
-      "mxf",
-      "nut",
-      "obu",
-      "oga",
-      "ogg",
-      "ogv",
-      "oma",
-      "opus",
-      "pam",
-      "pbm",
-      "pcm",
-      "pcx",
-      "pfm",
-      "pgm",
-      "pgmyuv",
-      "phm",
-      "pix",
-      "png",
-      "ppm",
-      "psp",
-      "qoi",
-      "ra",
-      "ras",
-      "rco",
-      "rcv",
-      "rgb",
-      "rm",
-      "roq",
-      "rs",
-      "rso",
-      "sb",
-      "sbc",
-      "scc",
-      "sf",
-      "sgi",
-      "sox",
-      "spdif",
-      "spx",
-      "srt",
-      "ssa",
-      "sub",
-      "sun",
-      "sunras",
-      "sup",
-      "sw",
-      "swf",
-      "tco",
-      "tga",
-      "thd",
-      "tif",
-      "tiff",
-      "ts",
-      "tta",
-      "ttml",
-      "tun",
-      "ub",
-      "ul",
-      "uw",
-      "vag",
-      "vbn",
-      "vc1",
-      "vc2",
-      "vob",
-      "voc",
-      "vtt",
-      "vvc",
-      "w64",
-      "wav",
-      "wbmp",
-      "webm",
-      "webp",
-      "wma",
-      "wmv",
-      "wtv",
-      "wv",
-      "xbm",
-      "xface",
-      "xml",
-      "xwd",
-      "y",
-      "y4m",
-      "yuv",
-    ],
-  },
-};
-
-export async function convert(
-  filePath: string,
-  fileType: string,
-  convertTo: string,
-  targetPath: string,
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  options?: unknown,
-): Promise<string> {
-  let extraArgs: string[] = [];
-  let message = "Done";
-
-  if (convertTo === "ico") {
-    // make sure image is 256x256 or smaller
-    extraArgs = [
-      "-filter:v",
-      "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease",
-    ];
-    message = "Done: resized to 256x256";
-  }
-
-  if (convertTo.split(".").length > 1) {
-    // support av1.mkv and av1.mp4 and h265.mp4 etc.
-    const split = convertTo.split(".");
-    const codec_short = split[0];
-
-    switch (codec_short) {
-      case "av1":
-        extraArgs.push("-c:v", "libaom-av1");
-        break;
-      case "h264":
-        extraArgs.push("-c:v", "libx264");
-        break;
-      case "h265":
-        extraArgs.push("-c:v", "libx265");
-        break;
-      case "h266":
-        extraArgs.push("-c:v", "libx266");
-        break;
-    }
-  }
-
-  // Parse FFMPEG_ARGS environment variable into array
-  const ffmpegArgs = process.env.FFMPEG_ARGS
-    ? process.env.FFMPEG_ARGS.split(/\s+/)
-    : [];
-
-  return new Promise((resolve, reject) => {
-    execFile(
-      "ffmpeg",
-      [...ffmpegArgs, "-i", filePath, ...extraArgs, targetPath],
-      (error, stdout, stderr) => {
-        if (error) {
-          reject(`error: ${error}`);
-        }
-
-        if (stdout) {
-          console.log(`stdout: ${stdout}`);
-        }
-
-        if (stderr) {
-          console.error(`stderr: ${stderr}`);
-        }
-
-        resolve(message);
-      },
-    );
-  });
-}
+import { execFile } from "node:child_process";
+
+// This could be done dynamically by running `ffmpeg -formats` and parsing the output
+export const properties = {
+  from: {
+    muxer: [
+      "264",
+      "265",
+      "266",
+      "302",
+      "3dostr",
+      "3g2",
+      "3gp",
+      "4xm",
+      "669",
+      "722",
+      "aa",
+      "aa3",
+      "aac",
+      "aax",
+      "ac3",
+      "ac4",
+      "ace",
+      "acm",
+      "act",
+      "adf",
+      "adp",
+      "ads",
+      "adx",
+      "aea",
+      "afc",
+      "aiff",
+      "aix",
+      "al",
+      "alaw",
+      "alias_pix",
+      "alp",
+      "alsa",
+      "amf",
+      "amr",
+      "amrnb",
+      "amrwb",
+      "ams",
+      "anm",
+      "ans",
+      "apc",
+      "ape",
+      "apl",
+      "apm",
+      "apng",
+      "aptx",
+      "aptxhd",
+      "aqt",
+      "aqtitle",
+      "argo_asf",
+      "argo_brp",
+      "art",
+      "asc",
+      "asf",
+      "asf_o",
+      "ass",
+      "ast",
+      "au",
+      "av1",
+      "avc",
+      "avi",
+      "avif",
+      "avr",
+      "avs",
+      "avs2",
+      "avs3",
+      "awb",
+      "bcstm",
+      "bethsoftvid",
+      "bfi",
+      "bfstm",
+      "bin",
+      "bink",
+      "binka",
+      "bit",
+      "bitpacked",
+      "bmv",
+      "bmp",
+      "bonk",
+      "boa",
+      "brender_pix",
+      "brstm",
+      "c2",
+      "c93",
+      "caf",
+      "cavsvideo",
+      "cdata",
+      "cdg",
+      "cdxl",
+      "cgi",
+      "cif",
+      "cine",
+      "codec2",
+      "codec2raw",
+      "concat",
+      "cri",
+      "dash",
+      "dat",
+      "data",
+      "daud",
+      "dav",
+      "dbm",
+      "dcstr",
+      "dds",
+      "derf",
+      "dfpwm",
+      "dfa",
+      "dhav",
+      "dif",
+      "digi",
+      "dirac",
+      "diz",
+      "dmf",
+      "dnxhd",
+      "dpx_pipe",
+      "dsf",
+      "dsicin",
+      "dsm",
+      "dss",
+      "dtk",
+      "dtm",
+      "dts",
+      "dtshd",
+      "dv",
+      "dvbsub",
+      "dvbtxt",
+      "dxa",
+      "ea",
+      "eac3",
+      "ea_cdata",
+      "epaf",
+      "exr_pipe",
+      "f32be",
+      "f32le",
+      "ec3",
+      "evc",
+      "f4v",
+      "f64be",
+      "f64le",
+      "fap",
+      "far",
+      "fbdev",
+      "ffmetadata",
+      "filmstrip",
+      "film_cpk",
+      "fits",
+      "flac",
+      "flic",
+      "flm",
+      "flv",
+      "frm",
+      "fsb",
+      "fwse",
+      "g722",
+      "g723_1",
+      "g726",
+      "g726le",
+      "g729",
+      "gdm",
+      "gdv",
+      "genh",
+      "gif",
+      "gsm",
+      "gxf",
+      "h261",
+      "h263",
+      "h264",
+      "h265",
+      "h266",
+      "h26l",
+      "hca",
+      "hcom",
+      "hevc",
+      "hls",
+      "hnm",
+      "ice",
+      "ico",
+      "idcin",
+      "idf",
+      "idx",
+      "iec61883",
+      "iff",
+      "ifv",
+      "ilbc",
+      "image2",
+      "imf",
+      "imx",
+      "ingenient",
+      "ipmovie",
+      "ipu",
+      "ircam",
+      "ism",
+      "isma",
+      "ismv",
+      "iss",
+      "it",
+      "iv8",
+      "ivf",
+      "ivr",
+      "j2b",
+      "j2k",
+      "jack",
+      "jacosub",
+      "jv",
+      "jpegls",
+      "jpeg",
+      "jxl",
+      "kmsgrab",
+      "kux",
+      "kvag",
+      "lavfi",
+      "laf",
+      "lmlm4",
+      "loas",
+      "lrc",
+      "luodat",
+      "lvf",
+      "lxf",
+      "m15",
+      "m2a",
+      "m4a",
+      "m4b",
+      "m4v",
+      "mac",
+      "mca",
+      "mcc",
+      "mdl",
+      "med",
+      "microdvd",
+      "mj2",
+      "mjpeg",
+      "mjpg",
+      "mk3d",
+      "mka",
+      "mks",
+      "mkv",
+      "mlp",
+      "mlv",
+      "mm",
+      "mmcmp",
+      "mmf",
+      "mms",
+      "mo3",
+      "mod",
+      "mods",
+      "moflex",
+      "mov",
+      "mp2",
+      "mp3",
+      "mp4",
+      "mpa",
+      "mpc",
+      "mpc8",
+      "mpeg",
+      "mpg",
+      "mpjpeg",
+      "mpl2",
+      "mpo",
+      "mpsub",
+      "mptm",
+      "msbc",
+      "msf",
+      "msnwctcp",
+      "msp",
+      "mt2",
+      "mtaf",
+      "mtm",
+      "mtv",
+      "mulaw",
+      "musx",
+      "mv",
+      "mvi",
+      "mxf",
+      "mxg",
+      "nc",
+      "nfo",
+      "nist",
+      "nistsphere",
+      "nsp",
+      "nst",
+      "nsv",
+      "nut",
+      "nuv",
+      "obu",
+      "ogg",
+      "okt",
+      "oma",
+      "omg",
+      "opus",
+      "openal",
+      "oss",
+      "osq",
+      "paf",
+      "pdv",
+      "pam",
+      "pbm",
+      "pcx",
+      "pgmyuv",
+      "pgm",
+      "pgx",
+      "photocd",
+      "pictor",
+      "pjs",
+      "plm",
+      "pmp",
+      "png",
+      "ppm",
+      "pp",
+      "psd",
+      "psm",
+      "psp",
+      "psxstr",
+      "pt36",
+      "ptm",
+      "pulse",
+      "pva",
+      "pvf",
+      "qcif",
+      "qcp",
+      "qdraw",
+      "r3d",
+      "rawvideo",
+      "rco",
+      "rcv",
+      "realtext",
+      "redspark",
+      "rgb",
+      "rl2",
+      "rm",
+      "roq",
+      "rpl",
+      "rka",
+      "rsd",
+      "rso",
+      "rt",
+      "rtp",
+      "rtsp",
+      "s16be",
+      "s16le",
+      "s24be",
+      "s24le",
+      "s32be",
+      "s32le",
+      "s337m",
+      "s3m",
+      "s8",
+      "sami",
+      "sap",
+      "sb",
+      "sbc",
+      "sbg",
+      "scc",
+      "sdns",
+      "sdp",
+      "sdr2",
+      "sds",
+      "sdx",
+      "ser",
+      "sf",
+      "sfx",
+      "sfx2",
+      "sga",
+      "sgi",
+      "shn",
+      "siff",
+      "sln",
+      "smi",
+      "smjpeg",
+      "smk",
+      "smush",
+      "sndio",
+      "sol",
+      "son",
+      "sox",
+      "spdif",
+      "sph",
+      "srt",
+      "ss2",
+      "ssa",
+      "st26",
+      "stk",
+      "stl",
+      "stm",
+      "stp",
+      "str",
+      "sub",
+      "sup",
+      "svag",
+      "svg",
+      "svs",
+      "sw",
+      "swf",
+      "tak",
+      "tco",
+      "tedcaptions",
+      "thd",
+      "thp",
+      "tiertexseq",
+      "tif",
+      "tiff",
+      "tmv",
+      "truehd",
+      "tta",
+      "tty",
+      "txd",
+      "txt",
+      "ty",
+      "ty+",
+      "u16be",
+      "u16le",
+      "u24be",
+      "u24le",
+      "u32be",
+      "u32le",
+      "u8",
+      "ub",
+      "ul",
+      "ult",
+      "umx",
+      "usm",
+      "uw",
+      "v",
+      "v210",
+      "v210x",
+      "vag",
+      "vb",
+      "vc1",
+      "vc1test",
+      "vidc",
+      "video4linux2",
+      "viv",
+      "vividas",
+      "vivo",
+      "vmd",
+      "vobsub",
+      "voc",
+      "vpk",
+      "vplayer",
+      "vqe",
+      "vqf",
+      "vql",
+      "vt",
+      "vtt",
+      "vvc",
+      "w64",
+      "wa",
+      "wav",
+      "way",
+      "wc3movie",
+      "webm",
+      "webp",
+      "webvtt",
+      "wow",
+      "wsaud",
+      "wsd",
+      "wsvqa",
+      "wtv",
+      "wv",
+      "wve",
+      "x11grab",
+      "xa",
+      "xbin",
+      "xl",
+      "xm",
+      "xmd",
+      "xmv",
+      "xpk",
+      "xvag",
+      "xwma",
+      "y4m",
+      "yop",
+      "yuv",
+      "yuv10",
+    ],
+  },
+  to: {
+    muxer: [
+      "264",
+      "265",
+      "266",
+      "302",
+      "3g2",
+      "3gp",
+      "a64",
+      "aac",
+      "ac3",
+      "ac4",
+      "adts",
+      "adx",
+      "afc",
+      "aif",
+      "aifc",
+      "aiff",
+      "al",
+      "amr",
+      "amv",
+      "apm",
+      "apng",
+      "aptx",
+      "aptxhd",
+      "asf",
+      "ass",
+      "ast",
+      "au",
+      "aud",
+      "av1.mkv",
+      "av1.mp4",
+      "avi",
+      "avif",
+      "avs",
+      "avs2",
+      "avs3",
+      "bit",
+      "bmp",
+      "c2",
+      "caf",
+      "cavs",
+      "chk",
+      "cpk",
+      "cvg",
+      "dfpwm",
+      "dnxhd",
+      "dnxhr",
+      "dpx",
+      "drc",
+      "dts",
+      "dv",
+      "dvd",
+      "eac3",
+      "ec3",
+      "evc",
+      "exr",
+      "f4v",
+      "ffmeta",
+      "fits",
+      "flac",
+      "flm",
+      "flv",
+      "g722",
+      "gif",
+      "gsm",
+      "gxf",
+      "h261",
+      "h263",
+      "h264.mkv",
+      "h264.mp4",
+      "h265.mkv",
+      "h265.mp4",
+      "h266.mkv",
+      "hdr",
+      "hevc",
+      "ico",
+      "im1",
+      "im24",
+      "im8",
+      "ircam",
+      "isma",
+      "ismv",
+      "ivf",
+      "j2c",
+      "j2k",
+      "jls",
+      "jp2",
+      "jpeg",
+      "jpg",
+      "js",
+      "jss",
+      "jxl",
+      "latm",
+      "lbc",
+      "ljpg",
+      "loas",
+      "lrc",
+      "m1v",
+      "m2a",
+      "m2t",
+      "m2ts",
+      "m2v",
+      "m3u8",
+      "m4a",
+      "m4b",
+      "m4v",
+      "mjpeg",
+      "mjpg",
+      "mkv",
+      "mlp",
+      "mmf",
+      "mov",
+      "mp2",
+      "mp3",
+      "mp4",
+      "mpa",
+      "mpd",
+      "mpeg",
+      "mpg",
+      "msbc",
+      "mts",
+      "mxf",
+      "nut",
+      "obu",
+      "oga",
+      "ogg",
+      "ogv",
+      "oma",
+      "opus",
+      "pam",
+      "pbm",
+      "pcm",
+      "pcx",
+      "pfm",
+      "pgm",
+      "pgmyuv",
+      "phm",
+      "pix",
+      "png",
+      "ppm",
+      "psp",
+      "qoi",
+      "ra",
+      "ras",
+      "rco",
+      "rcv",
+      "rgb",
+      "rm",
+      "roq",
+      "rs",
+      "rso",
+      "sb",
+      "sbc",
+      "scc",
+      "sf",
+      "sgi",
+      "sox",
+      "spdif",
+      "spx",
+      "srt",
+      "ssa",
+      "sub",
+      "sun",
+      "sunras",
+      "sup",
+      "sw",
+      "swf",
+      "tco",
+      "tga",
+      "thd",
+      "tif",
+      "tiff",
+      "ts",
+      "tta",
+      "ttml",
+      "tun",
+      "ub",
+      "ul",
+      "uw",
+      "vag",
+      "vbn",
+      "vc1",
+      "vc2",
+      "vob",
+      "voc",
+      "vtt",
+      "vvc",
+      "w64",
+      "wav",
+      "wbmp",
+      "webm",
+      "webp",
+      "wma",
+      "wmv",
+      "wtv",
+      "wv",
+      "xbm",
+      "xface",
+      "xml",
+      "xwd",
+      "y",
+      "y4m",
+      "yuv",
+    ],
+  },
+};
+
+export async function convert(
+  filePath: string,
+  fileType: string,
+  convertTo: string,
+  targetPath: string,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  let extraArgs: string[] = [];
+  let message = "Done";
+
+  if (convertTo === "ico") {
+    // make sure image is 256x256 or smaller
+    extraArgs = [
+      "-filter:v",
+      "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease",
+    ];
+    message = "Done: resized to 256x256";
+  }
+
+  if (convertTo.split(".").length > 1) {
+    // support av1.mkv and av1.mp4 and h265.mp4 etc.
+    const split = convertTo.split(".");
+    const codec_short = split[0];
+
+    switch (codec_short) {
+      case "av1":
+        extraArgs.push("-c:v", "libaom-av1");
+        break;
+      case "h264":
+        extraArgs.push("-c:v", "libx264");
+        break;
+      case "h265":
+        extraArgs.push("-c:v", "libx265");
+        break;
+      case "h266":
+        extraArgs.push("-c:v", "libx266");
+        break;
+    }
+  }
+
+  // Parse FFMPEG_ARGS environment variable into array
+  const ffmpegArgs = process.env.FFMPEG_ARGS ? process.env.FFMPEG_ARGS.split(/\s+/) : [];
+
+  return new Promise((resolve, reject) => {
+    execFile(
+      "ffmpeg",
+      [...ffmpegArgs, "-i", filePath, ...extraArgs, targetPath],
+      (error, stdout, stderr) => {
+        if (error) {
+          reject(`error: ${error}`);
+        }
+
+        if (stdout) {
+          console.log(`stdout: ${stdout}`);
+        }
+
+        if (stderr) {
+          console.error(`stderr: ${stderr}`);
+        }
+
+        resolve(message);
+      },
+    );
+  });
+}

+ 336 - 340
src/converters/graphicsmagick.ts

@@ -1,340 +1,336 @@
-import { execFile } from "node:child_process";
-
-export const properties = {
-  from: {
-    image: [
-      "3fr",
-      "8bim",
-      "8bimtext",
-      "8bimwtext",
-      "app1",
-      "app1jpeg",
-      "art",
-      "arw",
-      "avs",
-      "b",
-      "bie",
-      "bigtiff",
-      "bmp",
-      "c",
-      "cals",
-      "caption",
-      "cin",
-      "cmyk",
-      "cmyka",
-      "cr2",
-      "crw",
-      "cur",
-      "cut",
-      "dcm",
-      "dcr",
-      "dcx",
-      "dng",
-      "dpx",
-      "epdf",
-      "epi",
-      "eps",
-      "epsf",
-      "epsi",
-      "ept",
-      "ept2",
-      "ept3",
-      "erf",
-      "exif",
-      "fax",
-      "file",
-      "fits",
-      "fractal",
-      "ftp",
-      "g",
-      "gif",
-      "gif87",
-      "gradient",
-      "gray",
-      "graya",
-      "heic",
-      "heif",
-      "hrz",
-      "http",
-      "icb",
-      "icc",
-      "icm",
-      "ico",
-      "icon",
-      "identity",
-      "image",
-      "iptc",
-      "iptctext",
-      "iptcwtext",
-      "jbg",
-      "jbig",
-      "jng",
-      "jnx",
-      "jpeg",
-      "jpg",
-      "k",
-      "k25",
-      "kdc",
-      "label",
-      "m",
-      "mac",
-      "map",
-      "mat",
-      "mef",
-      "miff",
-      "mng",
-      "mono",
-      "mpc",
-      "mrw",
-      "msl",
-      "mtv",
-      "mvg",
-      "nef",
-      "null",
-      "o",
-      "orf",
-      "otb",
-      "p7",
-      "pal",
-      "palm",
-      "pam",
-      "pbm",
-      "pcd",
-      "pcds",
-      "pct",
-      "pcx",
-      "pdb",
-      "pdf",
-      "pef",
-      "pfa",
-      "pfb",
-      "pgm",
-      "picon",
-      "pict",
-      "pix",
-      "plasma",
-      "png",
-      "png00",
-      "png24",
-      "png32",
-      "png48",
-      "png64",
-      "png8",
-      "pnm",
-      "ppm",
-      "ps",
-      "ptif",
-      "pwp",
-      "r",
-      "raf",
-      "ras",
-      "rgb",
-      "rgba",
-      "rla",
-      "rle",
-      "sct",
-      "sfw",
-      "sgi",
-      "sr2",
-      "srf",
-      "stegano",
-      "sun",
-      "svg",
-      "svgz",
-      "text",
-      "tga",
-      "tif",
-      "tiff",
-      "tile",
-      "tim",
-      "topol",
-      "ttf",
-      "txt",
-      "uyvy",
-      "vda",
-      "vicar",
-      "vid",
-      "viff",
-      "vst",
-      "wbmp",
-      "webp",
-      "wmf",
-      "wpg",
-      "x3f",
-      "xbm",
-      "xc",
-      "xcf",
-      "xmp",
-      "xpm",
-      "xv",
-      "xwd",
-      "y",
-      "yuv",
-    ],
-  },
-  to: {
-    image: [
-      "8bim",
-      "8bimtext",
-      "8bimwtext",
-      "app1",
-      "app1jpeg",
-      "art",
-      "avs",
-      "b",
-      "bie",
-      "bigtiff",
-      "bmp",
-      "bmp2",
-      "bmp3",
-      "brf",
-      "c",
-      "cals",
-      "cin",
-      "cmyk",
-      "cmyka",
-      "dcx",
-      "dpx",
-      "epdf",
-      "epi",
-      "eps",
-      "eps2",
-      "eps3",
-      "epsf",
-      "epsi",
-      "ept",
-      "ept2",
-      "ept3",
-      "exif",
-      "fax",
-      "fits",
-      "g",
-      "gif",
-      "gif87",
-      "gray",
-      "graya",
-      "histogram",
-      "html",
-      "icb",
-      "icc",
-      "icm",
-      "info",
-      "iptc",
-      "iptctext",
-      "iptcwtext",
-      "isobrl",
-      "isobrl6",
-      "jbg",
-      "jbig",
-      "jng",
-      "jpeg",
-      "k",
-      "m",
-      "m2v",
-      "map",
-      "mat",
-      "matte",
-      "miff",
-      "mng",
-      "mono",
-      "mpc",
-      "mpeg",
-      "mpg",
-      "msl",
-      "mtv",
-      "mvg",
-      "null",
-      "o",
-      "otb",
-      "p7",
-      "pal",
-      "pam",
-      "pbm",
-      "pcd",
-      "pcds",
-      "pcl",
-      "pct",
-      "pcx",
-      "pdb",
-      "pdf",
-      "pgm",
-      "picon",
-      "pict",
-      "png",
-      "png00",
-      "png24",
-      "png32",
-      "png48",
-      "png64",
-      "png8",
-      "pnm",
-      "ppm",
-      "preview",
-      "ps",
-      "ps2",
-      "ps3",
-      "ptif",
-      "r",
-      "ras",
-      "rgb",
-      "rgba",
-      "sgi",
-      "shtml",
-      "sun",
-      "text",
-      "tga",
-      "tiff",
-      "txt",
-      "ubrl",
-      "ubrl6",
-      "uil",
-      "uyvy",
-      "vda",
-      "vicar",
-      "vid",
-      "viff",
-      "vst",
-      "wbmp",
-      "webp",
-      "x",
-      "xbm",
-      "xmp",
-      "xpm",
-      "xv",
-      "xwd",
-      "y",
-      "yuv",
-    ],
-  },
-};
-
-export function convert(
-  filePath: string,
-  fileType: string,
-  convertTo: string,
-  targetPath: string,
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  options?: unknown,
-): Promise<string> {
-  return new Promise((resolve, reject) => {
-    execFile(
-      "gm",
-      ["convert", filePath, targetPath],
-      (error, stdout, stderr) => {
-        if (error) {
-          reject(`error: ${error}`);
-        }
-
-        if (stdout) {
-          console.log(`stdout: ${stdout}`);
-        }
-
-        if (stderr) {
-          console.error(`stderr: ${stderr}`);
-        }
-
-        resolve("Done");
-      },
-    );
-  });
-}
+import { execFile } from "node:child_process";
+
+export const properties = {
+  from: {
+    image: [
+      "3fr",
+      "8bim",
+      "8bimtext",
+      "8bimwtext",
+      "app1",
+      "app1jpeg",
+      "art",
+      "arw",
+      "avs",
+      "b",
+      "bie",
+      "bigtiff",
+      "bmp",
+      "c",
+      "cals",
+      "caption",
+      "cin",
+      "cmyk",
+      "cmyka",
+      "cr2",
+      "crw",
+      "cur",
+      "cut",
+      "dcm",
+      "dcr",
+      "dcx",
+      "dng",
+      "dpx",
+      "epdf",
+      "epi",
+      "eps",
+      "epsf",
+      "epsi",
+      "ept",
+      "ept2",
+      "ept3",
+      "erf",
+      "exif",
+      "fax",
+      "file",
+      "fits",
+      "fractal",
+      "ftp",
+      "g",
+      "gif",
+      "gif87",
+      "gradient",
+      "gray",
+      "graya",
+      "heic",
+      "heif",
+      "hrz",
+      "http",
+      "icb",
+      "icc",
+      "icm",
+      "ico",
+      "icon",
+      "identity",
+      "image",
+      "iptc",
+      "iptctext",
+      "iptcwtext",
+      "jbg",
+      "jbig",
+      "jng",
+      "jnx",
+      "jpeg",
+      "jpg",
+      "k",
+      "k25",
+      "kdc",
+      "label",
+      "m",
+      "mac",
+      "map",
+      "mat",
+      "mef",
+      "miff",
+      "mng",
+      "mono",
+      "mpc",
+      "mrw",
+      "msl",
+      "mtv",
+      "mvg",
+      "nef",
+      "null",
+      "o",
+      "orf",
+      "otb",
+      "p7",
+      "pal",
+      "palm",
+      "pam",
+      "pbm",
+      "pcd",
+      "pcds",
+      "pct",
+      "pcx",
+      "pdb",
+      "pdf",
+      "pef",
+      "pfa",
+      "pfb",
+      "pgm",
+      "picon",
+      "pict",
+      "pix",
+      "plasma",
+      "png",
+      "png00",
+      "png24",
+      "png32",
+      "png48",
+      "png64",
+      "png8",
+      "pnm",
+      "ppm",
+      "ps",
+      "ptif",
+      "pwp",
+      "r",
+      "raf",
+      "ras",
+      "rgb",
+      "rgba",
+      "rla",
+      "rle",
+      "sct",
+      "sfw",
+      "sgi",
+      "sr2",
+      "srf",
+      "stegano",
+      "sun",
+      "svg",
+      "svgz",
+      "text",
+      "tga",
+      "tif",
+      "tiff",
+      "tile",
+      "tim",
+      "topol",
+      "ttf",
+      "txt",
+      "uyvy",
+      "vda",
+      "vicar",
+      "vid",
+      "viff",
+      "vst",
+      "wbmp",
+      "webp",
+      "wmf",
+      "wpg",
+      "x3f",
+      "xbm",
+      "xc",
+      "xcf",
+      "xmp",
+      "xpm",
+      "xv",
+      "xwd",
+      "y",
+      "yuv",
+    ],
+  },
+  to: {
+    image: [
+      "8bim",
+      "8bimtext",
+      "8bimwtext",
+      "app1",
+      "app1jpeg",
+      "art",
+      "avs",
+      "b",
+      "bie",
+      "bigtiff",
+      "bmp",
+      "bmp2",
+      "bmp3",
+      "brf",
+      "c",
+      "cals",
+      "cin",
+      "cmyk",
+      "cmyka",
+      "dcx",
+      "dpx",
+      "epdf",
+      "epi",
+      "eps",
+      "eps2",
+      "eps3",
+      "epsf",
+      "epsi",
+      "ept",
+      "ept2",
+      "ept3",
+      "exif",
+      "fax",
+      "fits",
+      "g",
+      "gif",
+      "gif87",
+      "gray",
+      "graya",
+      "histogram",
+      "html",
+      "icb",
+      "icc",
+      "icm",
+      "info",
+      "iptc",
+      "iptctext",
+      "iptcwtext",
+      "isobrl",
+      "isobrl6",
+      "jbg",
+      "jbig",
+      "jng",
+      "jpeg",
+      "k",
+      "m",
+      "m2v",
+      "map",
+      "mat",
+      "matte",
+      "miff",
+      "mng",
+      "mono",
+      "mpc",
+      "mpeg",
+      "mpg",
+      "msl",
+      "mtv",
+      "mvg",
+      "null",
+      "o",
+      "otb",
+      "p7",
+      "pal",
+      "pam",
+      "pbm",
+      "pcd",
+      "pcds",
+      "pcl",
+      "pct",
+      "pcx",
+      "pdb",
+      "pdf",
+      "pgm",
+      "picon",
+      "pict",
+      "png",
+      "png00",
+      "png24",
+      "png32",
+      "png48",
+      "png64",
+      "png8",
+      "pnm",
+      "ppm",
+      "preview",
+      "ps",
+      "ps2",
+      "ps3",
+      "ptif",
+      "r",
+      "ras",
+      "rgb",
+      "rgba",
+      "sgi",
+      "shtml",
+      "sun",
+      "text",
+      "tga",
+      "tiff",
+      "txt",
+      "ubrl",
+      "ubrl6",
+      "uil",
+      "uyvy",
+      "vda",
+      "vicar",
+      "vid",
+      "viff",
+      "vst",
+      "wbmp",
+      "webp",
+      "x",
+      "xbm",
+      "xmp",
+      "xpm",
+      "xv",
+      "xwd",
+      "y",
+      "yuv",
+    ],
+  },
+};
+
+export function convert(
+  filePath: string,
+  fileType: string,
+  convertTo: string,
+  targetPath: string,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  return new Promise((resolve, reject) => {
+    execFile("gm", ["convert", filePath, targetPath], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
+
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
+
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
+
+      resolve("Done");
+    });
+  });
+}

+ 1 - 6
src/converters/imagemagick.ts

@@ -452,12 +452,7 @@ export function convert(
   let inputArgs: string[] = [];
 
   if (convertTo === "ico") {
-    outputArgs = [
-      "-define",
-      "icon:auto-resize=256,128,64,48,32,16",
-      "-background",
-      "none",
-    ];
+    outputArgs = ["-define", "icon:auto-resize=256,128,64,48,32,16", "-background", "none"];
 
     if (fileType === "svg") {
       // this might be a bit too much, but it works

+ 12 - 16
src/converters/inkscape.ts

@@ -36,24 +36,20 @@ export function convert(
   options?: unknown,
 ): Promise<string> {
   return new Promise((resolve, reject) => {
-    execFile(
-      "inkscape",
-      [filePath, "-o", targetPath],
-      (error, stdout, stderr) => {
-        if (error) {
-          reject(`error: ${error}`);
-        }
+    execFile("inkscape", [filePath, "-o", targetPath], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
 
-        if (stdout) {
-          console.log(`stdout: ${stdout}`);
-        }
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
 
-        if (stderr) {
-          console.error(`stderr: ${stderr}`);
-        }
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
 
-        resolve("Done");
-      },
-    );
+      resolve("Done");
+    });
   });
 }

+ 13 - 29
src/converters/libheif.ts

@@ -2,19 +2,7 @@ import { execFile } from "child_process";
 
 export const properties = {
   from: {
-    images: [
-      "avci",
-      "avcs",
-      "avif",
-      "h264",
-      "heic",
-      "heics",
-      "heif",
-      "heifs",
-      "hif",
-      "mkv",
-      "mp4",
-    ],
+    images: ["avci", "avcs", "avif", "h264", "heic", "heics", "heif", "heifs", "hif", "mkv", "mp4"],
   },
   to: {
     images: ["jpeg", "png", "y4m"],
@@ -30,24 +18,20 @@ export function convert(
   options?: unknown,
 ): Promise<string> {
   return new Promise((resolve, reject) => {
-    execFile(
-      "heif-convert",
-      [filePath, targetPath],
-      (error, stdout, stderr) => {
-        if (error) {
-          reject(`error: ${error}`);
-        }
+    execFile("heif-convert", [filePath, targetPath], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
 
-        if (stdout) {
-          console.log(`stdout: ${stdout}`);
-        }
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
 
-        if (stderr) {
-          console.error(`stderr: ${stderr}`);
-        }
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
 
-        resolve("Done");
-      },
-    );
+      resolve("Done");
+    });
   });
 }

+ 49 - 71
src/converters/libjxl.ts

@@ -1,71 +1,49 @@
-import { execFile } 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,
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  options?: unknown,
-): Promise<string> {
-  let tool = "";
-  if (fileType === "jxl") {
-    tool = "djxl";
-  }
-
-  if (convertTo === "jxl") {
-    tool = "cjxl";
-  }
-
-  return new Promise((resolve, reject) => {
-    execFile(tool, [filePath, targetPath], (error, stdout, stderr) => {
-      if (error) {
-        reject(`error: ${error}`);
-      }
-
-      if (stdout) {
-        console.log(`stdout: ${stdout}`);
-      }
-
-      if (stderr) {
-        console.error(`stderr: ${stderr}`);
-      }
-
-      resolve("Done");
-    });
-  });
-}
+import { execFile } 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,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  let tool = "";
+  if (fileType === "jxl") {
+    tool = "djxl";
+  }
+
+  if (convertTo === "jxl") {
+    tool = "cjxl";
+  }
+
+  return new Promise((resolve, reject) => {
+    execFile(tool, [filePath, targetPath], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
+
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
+
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
+
+      resolve("Done");
+    });
+  });
+}

+ 14 - 21
src/converters/main.ts

@@ -1,19 +1,21 @@
 import { normalizeFiletype } from "../helpers/normalizeFiletype";
 import { convert as convertassimp, properties as propertiesassimp } from "./assimp";
+import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
+import { convert as convertDvisvgm, properties as propertiesDvisvgm } from "./dvisvgm";
 import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg";
-import { convert as convertGraphicsmagick, properties as propertiesGraphicsmagick } from "./graphicsmagick";
+import {
+  convert as convertGraphicsmagick,
+  properties as propertiesGraphicsmagick,
+} from "./graphicsmagick";
+import { convert as convertImagemagick, properties as propertiesImagemagick } from "./imagemagick";
 import { convert as convertInkscape, properties as propertiesInkscape } from "./inkscape";
+import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif";
 import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl";
 import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
+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 convertxelatex, properties as propertiesxelatex } from "./xelatex";
-import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
-import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif";
-import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace";
-import { convert as convertImagemagick, properties as propertiesImagemagick } from "./imagemagick";
-import { convert as convertDvisvgm, properties as propertiesDvisvgm } from "./dvisvgm";
-
 
 // This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
 
@@ -113,7 +115,7 @@ export async function mainConverter(
 ) {
   const fileType = normalizeFiletype(fileTypeOriginal);
 
-  let converterFunc: typeof properties["libjxl"]["converter"] | undefined;
+  let converterFunc: (typeof properties)["libjxl"]["converter"] | undefined;
 
   if (converterName) {
     converterFunc = properties[converterName]?.converter;
@@ -139,20 +141,12 @@ export async function mainConverter(
   }
 
   if (!converterFunc) {
-    console.log(
-      `No available converter supports converting from ${fileType} to ${convertTo}.`,
-    );
+    console.log(`No available converter supports converting from ${fileType} to ${convertTo}.`);
     return "File type not supported";
   }
 
   try {
-    const result = await converterFunc(
-      inputFilePath,
-      fileType,
-      convertTo,
-      targetPath,
-      options,
-    );
+    const result = await converterFunc(inputFilePath, fileType, convertTo, targetPath, options);
 
     console.log(
       `Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
@@ -192,8 +186,7 @@ for (const converterName in properties) {
         possibleTargets[extension] = {};
       }
 
-      possibleTargets[extension][converterName] =
-        converterProperties.to[key] || [];
+      possibleTargets[extension][converterName] = converterProperties.to[key] || [];
     }
   }
 }
@@ -294,4 +287,4 @@ export const getAllInputs = (converter: string) => {
 // }
 
 // // print the number of unique Inputs and Outputs
-// console.log(`Unique Formats: ${uniqueFormats.size}`);
+// console.log(`Unique Formats: ${uniqueFormats.size}`);

+ 162 - 162
src/converters/pandoc.ts

@@ -1,162 +1,162 @@
-import { execFile } from "node:child_process";
-
-export const properties = {
-  from: {
-    text: [
-      "textile",
-      "tikiwiki",
-      "tsv",
-      "twiki",
-      "typst",
-      "vimwiki",
-      "biblatex",
-      "bibtex",
-      "bits",
-      "commonmark",
-      "commonmark_x",
-      "creole",
-      "csljson",
-      "csv",
-      "djot",
-      "docbook",
-      "docx",
-      "dokuwiki",
-      "endnotexml",
-      "epub",
-      "fb2",
-      "gfm",
-      "haddock",
-      "html",
-      "ipynb",
-      "jats",
-      "jira",
-      "json",
-      "latex",
-      "man",
-      "markdown",
-      "markdown_mmd",
-      "markdown_phpextra",
-      "markdown_strict",
-      "mediawiki",
-      "muse",
-      "pandoc native",
-      "opml",
-      "org",
-      "ris",
-      "rst",
-      "rtf",
-      "t2t",
-    ],
-  },
-  to: {
-    text: [
-      "tei",
-      "texinfo",
-      "textile",
-      "typst",
-      "xwiki",
-      "zimwiki",
-      "asciidoc",
-      "asciidoc_legacy",
-      "asciidoctor",
-      "beamer",
-      "biblatex",
-      "bibtex",
-      "chunkedhtml",
-      "commonmark",
-      "commonmark_x",
-      "context",
-      "csljson",
-      "djot",
-      "docbook",
-      "docbook4",
-      "docbook5",
-      "docx",
-      "dokuwiki",
-      "dzslides",
-      "epub",
-      "epub2",
-      "epub3",
-      "fb2",
-      "gfm",
-      "haddock",
-      "html",
-      "html4",
-      "html5",
-      "icml",
-      "ipynb",
-      "jats",
-      "jats_archiving",
-      "jats_articleauthoring",
-      "jats_publishing",
-      "jira",
-      "json",
-      "latex",
-      "man",
-      "markdown",
-      "markdown_mmd",
-      "markdown_phpextra",
-      "markdown_strict",
-      "markua",
-      "mediawiki",
-      "ms",
-      "muse",
-      "pandoc native",
-      "odt",
-      "opendocument",
-      "opml",
-      "org",
-      "pdf",
-      "plain",
-      "pptx",
-      "revealjs",
-      "rst",
-      "rtf",
-      "s5",
-      "slideous",
-      "slidy",
-    ],
-  },
-};
-
-export function convert(
-  filePath: string,
-  fileType: string,
-  convertTo: string,
-  targetPath: string,
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  options?: unknown,
-): Promise<string> {
-  // set xelatex here
-  const xelatex = ["pdf", "latex"];
-
-  // Build arguments array
-  const args: string[] = [];
-
-  if (xelatex.includes(convertTo)) {
-    args.push("--pdf-engine=xelatex");
-  }
-
-  args.push(filePath);
-  args.push("-f", fileType);
-  args.push("-t", convertTo);
-  args.push("-o", targetPath);
-
-  return new Promise((resolve, reject) => {
-    execFile("pandoc", args, (error, stdout, stderr) => {
-      if (error) {
-        reject(`error: ${error}`);
-      }
-
-      if (stdout) {
-        console.log(`stdout: ${stdout}`);
-      }
-
-      if (stderr) {
-        console.error(`stderr: ${stderr}`);
-      }
-
-      resolve("Done");
-    });
-  });
-}
+import { execFile } from "node:child_process";
+
+export const properties = {
+  from: {
+    text: [
+      "textile",
+      "tikiwiki",
+      "tsv",
+      "twiki",
+      "typst",
+      "vimwiki",
+      "biblatex",
+      "bibtex",
+      "bits",
+      "commonmark",
+      "commonmark_x",
+      "creole",
+      "csljson",
+      "csv",
+      "djot",
+      "docbook",
+      "docx",
+      "dokuwiki",
+      "endnotexml",
+      "epub",
+      "fb2",
+      "gfm",
+      "haddock",
+      "html",
+      "ipynb",
+      "jats",
+      "jira",
+      "json",
+      "latex",
+      "man",
+      "markdown",
+      "markdown_mmd",
+      "markdown_phpextra",
+      "markdown_strict",
+      "mediawiki",
+      "muse",
+      "pandoc native",
+      "opml",
+      "org",
+      "ris",
+      "rst",
+      "rtf",
+      "t2t",
+    ],
+  },
+  to: {
+    text: [
+      "tei",
+      "texinfo",
+      "textile",
+      "typst",
+      "xwiki",
+      "zimwiki",
+      "asciidoc",
+      "asciidoc_legacy",
+      "asciidoctor",
+      "beamer",
+      "biblatex",
+      "bibtex",
+      "chunkedhtml",
+      "commonmark",
+      "commonmark_x",
+      "context",
+      "csljson",
+      "djot",
+      "docbook",
+      "docbook4",
+      "docbook5",
+      "docx",
+      "dokuwiki",
+      "dzslides",
+      "epub",
+      "epub2",
+      "epub3",
+      "fb2",
+      "gfm",
+      "haddock",
+      "html",
+      "html4",
+      "html5",
+      "icml",
+      "ipynb",
+      "jats",
+      "jats_archiving",
+      "jats_articleauthoring",
+      "jats_publishing",
+      "jira",
+      "json",
+      "latex",
+      "man",
+      "markdown",
+      "markdown_mmd",
+      "markdown_phpextra",
+      "markdown_strict",
+      "markua",
+      "mediawiki",
+      "ms",
+      "muse",
+      "pandoc native",
+      "odt",
+      "opendocument",
+      "opml",
+      "org",
+      "pdf",
+      "plain",
+      "pptx",
+      "revealjs",
+      "rst",
+      "rtf",
+      "s5",
+      "slideous",
+      "slidy",
+    ],
+  },
+};
+
+export function convert(
+  filePath: string,
+  fileType: string,
+  convertTo: string,
+  targetPath: string,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  // set xelatex here
+  const xelatex = ["pdf", "latex"];
+
+  // Build arguments array
+  const args: string[] = [];
+
+  if (xelatex.includes(convertTo)) {
+    args.push("--pdf-engine=xelatex");
+  }
+
+  args.push(filePath);
+  args.push("-f", fileType);
+  args.push("-t", convertTo);
+  args.push("-o", targetPath);
+
+  return new Promise((resolve, reject) => {
+    execFile("pandoc", args, (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
+
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
+
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
+
+      resolve("Done");
+    });
+  });
+}

+ 37 - 25
src/converters/potrace.ts

@@ -5,33 +5,45 @@ export const properties = {
     images: ["pnm", "pbm", "pgm", "bmp"],
   },
   to: {
-    images: ["svg", "pdf", "pdfpage", "eps", "postscript", "ps", "dxf", "geojson", "pgm", "gimppath", "xfig"],
+    images: [
+      "svg",
+      "pdf",
+      "pdfpage",
+      "eps",
+      "postscript",
+      "ps",
+      "dxf",
+      "geojson",
+      "pgm",
+      "gimppath",
+      "xfig",
+    ],
   },
 };
 
 export function convert(
-    filePath: string,
-    fileType: string,
-    convertTo: string,
-    targetPath: string,
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    options?: unknown,
-  ): Promise<string> {
-    return new Promise((resolve, reject) => {
-      execFile("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => {
-        if (error) {
-          reject(`error: ${error}`);
-        }
-  
-        if (stdout) {
-          console.log(`stdout: ${stdout}`);
-        }
-  
-        if (stderr) {
-          console.error(`stderr: ${stderr}`);
-        }
-  
-        resolve("Done");
-      });
+  filePath: string,
+  fileType: string,
+  convertTo: string,
+  targetPath: string,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  return new Promise((resolve, reject) => {
+    execFile("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
+
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
+
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
+
+      resolve("Done");
     });
-  }
+  });
+}

+ 37 - 37
src/converters/resvg.ts

@@ -1,37 +1,37 @@
-import { execFile } from "node:child_process";
-
-export const properties = {
-  from: {
-    images: ["svg"],
-  },
-  to: {
-    images: ["png"],
-  },
-};
-
-export function convert(
-  filePath: string,
-  fileType: string,
-  convertTo: string,
-  targetPath: string,
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  options?: unknown,
-): Promise<string> {
-  return new Promise((resolve, reject) => {
-    execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => {
-      if (error) {
-        reject(`error: ${error}`);
-      }
-
-      if (stdout) {
-        console.log(`stdout: ${stdout}`);
-      }
-
-      if (stderr) {
-        console.error(`stderr: ${stderr}`);
-      }
-
-      resolve("Done");
-    });
-  });
-}
+import { execFile } from "node:child_process";
+
+export const properties = {
+  from: {
+    images: ["svg"],
+  },
+  to: {
+    images: ["png"],
+  },
+};
+
+export function convert(
+  filePath: string,
+  fileType: string,
+  convertTo: string,
+  targetPath: string,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  return new Promise((resolve, reject) => {
+    execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
+
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
+
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
+
+      resolve("Done");
+    });
+  });
+}

+ 138 - 142
src/converters/vips.ts

@@ -1,142 +1,138 @@
-import { execFile } from "node:child_process";
-
-// declare possible conversions
-export const properties = {
-  from: {
-    images: [
-      "avif",
-      "bif",
-      "csv",
-      "exr",
-      "fits",
-      "gif",
-      "hdr.gz",
-      "hdr",
-      "heic",
-      "heif",
-      "img.gz",
-      "img",
-      "j2c",
-      "j2k",
-      "jp2",
-      "jpeg",
-      "jpx",
-      "jxl",
-      "mat",
-      "mrxs",
-      "ndpi",
-      "nia.gz",
-      "nia",
-      "nii.gz",
-      "nii",
-      "pdf",
-      "pfm",
-      "pgm",
-      "pic",
-      "png",
-      "ppm",
-      "raw",
-      "scn",
-      "svg",
-      "svs",
-      "svslide",
-      "szi",
-      "tif",
-      "tiff",
-      "v",
-      "vips",
-      "vms",
-      "vmu",
-      "webp",
-      "zip",
-    ],
-  },
-  to: {
-    images: [
-      "avif",
-      "dzi",
-      "fits",
-      "gif",
-      "hdr.gz",
-      "heic",
-      "heif",
-      "img.gz",
-      "j2c",
-      "j2k",
-      "jp2",
-      "jpeg",
-      "jpx",
-      "jxl",
-      "mat",
-      "nia.gz",
-      "nia",
-      "nii.gz",
-      "nii",
-      "png",
-      "tiff",
-      "vips",
-      "webp",
-    ],
-  },
-  options: {
-    svg: {
-      scale: {
-        description: "Scale the image up or down",
-        type: "number",
-        default: 1,
-      },
-    },
-  },
-};
-
-export function convert(
-  filePath: string,
-  fileType: string,
-  convertTo: string,
-  targetPath: string,
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  options?: unknown,
-): Promise<string> {
-  // if (fileType === "svg") {
-  //   const scale = options.scale || 1;
-  //   const metadata = await sharp(filePath).metadata();
-
-  //   if (!metadata || !metadata.width || !metadata.height) {
-  //     throw new Error("Could not get metadata from image");
-  //   }
-
-  //   const newWidth = Math.round(metadata.width * scale);
-  //   const newHeight = Math.round(metadata.height * scale);
-
-  //   return await sharp(filePath)
-  //     .resize(newWidth, newHeight)
-  //     .toFormat(convertTo)
-  //     .toFile(targetPath);
-  // }
-  let action = "copy";
-  if (fileType === "pdf") {
-    action = "pdfload";
-  }
-
-  return new Promise((resolve, reject) => {
-    execFile(
-      "vips",
-      [action, filePath, targetPath],
-      (error, stdout, stderr) => {
-        if (error) {
-          reject(`error: ${error}`);
-        }
-
-        if (stdout) {
-          console.log(`stdout: ${stdout}`);
-        }
-
-        if (stderr) {
-          console.error(`stderr: ${stderr}`);
-        }
-
-        resolve("Done");
-      },
-    );
-  });
-}
+import { execFile } from "node:child_process";
+
+// declare possible conversions
+export const properties = {
+  from: {
+    images: [
+      "avif",
+      "bif",
+      "csv",
+      "exr",
+      "fits",
+      "gif",
+      "hdr.gz",
+      "hdr",
+      "heic",
+      "heif",
+      "img.gz",
+      "img",
+      "j2c",
+      "j2k",
+      "jp2",
+      "jpeg",
+      "jpx",
+      "jxl",
+      "mat",
+      "mrxs",
+      "ndpi",
+      "nia.gz",
+      "nia",
+      "nii.gz",
+      "nii",
+      "pdf",
+      "pfm",
+      "pgm",
+      "pic",
+      "png",
+      "ppm",
+      "raw",
+      "scn",
+      "svg",
+      "svs",
+      "svslide",
+      "szi",
+      "tif",
+      "tiff",
+      "v",
+      "vips",
+      "vms",
+      "vmu",
+      "webp",
+      "zip",
+    ],
+  },
+  to: {
+    images: [
+      "avif",
+      "dzi",
+      "fits",
+      "gif",
+      "hdr.gz",
+      "heic",
+      "heif",
+      "img.gz",
+      "j2c",
+      "j2k",
+      "jp2",
+      "jpeg",
+      "jpx",
+      "jxl",
+      "mat",
+      "nia.gz",
+      "nia",
+      "nii.gz",
+      "nii",
+      "png",
+      "tiff",
+      "vips",
+      "webp",
+    ],
+  },
+  options: {
+    svg: {
+      scale: {
+        description: "Scale the image up or down",
+        type: "number",
+        default: 1,
+      },
+    },
+  },
+};
+
+export function convert(
+  filePath: string,
+  fileType: string,
+  convertTo: string,
+  targetPath: string,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  // if (fileType === "svg") {
+  //   const scale = options.scale || 1;
+  //   const metadata = await sharp(filePath).metadata();
+
+  //   if (!metadata || !metadata.width || !metadata.height) {
+  //     throw new Error("Could not get metadata from image");
+  //   }
+
+  //   const newWidth = Math.round(metadata.width * scale);
+  //   const newHeight = Math.round(metadata.height * scale);
+
+  //   return await sharp(filePath)
+  //     .resize(newWidth, newHeight)
+  //     .toFormat(convertTo)
+  //     .toFile(targetPath);
+  // }
+  let action = "copy";
+  if (fileType === "pdf") {
+    action = "pdfload";
+  }
+
+  return new Promise((resolve, reject) => {
+    execFile("vips", [action, filePath, targetPath], (error, stdout, stderr) => {
+      if (error) {
+        reject(`error: ${error}`);
+      }
+
+      if (stdout) {
+        console.log(`stdout: ${stdout}`);
+      }
+
+      if (stderr) {
+        console.error(`stderr: ${stderr}`);
+      }
+
+      resolve("Done");
+    });
+  });
+}

+ 44 - 53
src/converters/xelatex.ts

@@ -1,53 +1,44 @@
-import { execFile } from "node:child_process";
-
-export const properties = {
-  from: {
-    text: ["tex", "latex"],
-  },
-  to: {
-    text: ["pdf"],
-  },
-};
-
-export function convert(
-  filePath: string,
-  fileType: string,
-  convertTo: string,
-  targetPath: string,
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  options?: unknown,
-): Promise<string> {
-  return new Promise((resolve, reject) => {
-    // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
-    const outputPath = targetPath
-      .split("/")
-      .slice(0, -1)
-      .join("/")
-      .replace("./", "");
-
-    execFile(
-      "latexmk",
-      [
-        "-xelatex",
-        "-interaction=nonstopmode",
-        `-output-directory=${outputPath}`,
-        filePath,
-      ],
-      (error, stdout, stderr) => {
-        if (error) {
-          reject(`error: ${error}`);
-        }
-
-        if (stdout) {
-          console.log(`stdout: ${stdout}`);
-        }
-
-        if (stderr) {
-          console.error(`stderr: ${stderr}`);
-        }
-
-        resolve("Done");
-      },
-    );
-  });
-}
+import { execFile } from "node:child_process";
+
+export const properties = {
+  from: {
+    text: ["tex", "latex"],
+  },
+  to: {
+    text: ["pdf"],
+  },
+};
+
+export function convert(
+  filePath: string,
+  fileType: string,
+  convertTo: string,
+  targetPath: string,
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  options?: unknown,
+): Promise<string> {
+  return new Promise((resolve, reject) => {
+    // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
+    const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "");
+
+    execFile(
+      "latexmk",
+      ["-xelatex", "-interaction=nonstopmode", `-output-directory=${outputPath}`, filePath],
+      (error, stdout, stderr) => {
+        if (error) {
+          reject(`error: ${error}`);
+        }
+
+        if (stdout) {
+          console.log(`stdout: ${stdout}`);
+        }
+
+        if (stderr) {
+          console.error(`stderr: ${stderr}`);
+        }
+
+        resolve("Done");
+      },
+    );
+  });
+}

+ 4 - 7
src/db/db.ts

@@ -1,4 +1,5 @@
 import { Database } from "bun:sqlite";
+
 const db = new Database("./data/mydb.sqlite", { create: true });
 
 if (!db.query("SELECT * FROM sqlite_master WHERE type='table'").get()) {
@@ -27,13 +28,9 @@ CREATE TABLE IF NOT EXISTS jobs (
 PRAGMA user_version = 1;`);
 }
 
-const dbVersion = (
-  db.query("PRAGMA user_version").get() as { user_version?: number }
-).user_version;
+const dbVersion = (db.query("PRAGMA user_version").get() as { user_version?: number }).user_version;
 if (dbVersion === 0) {
-  db.exec(
-    "ALTER TABLE file_names ADD COLUMN status TEXT DEFAULT 'not started';",
-  );
+  db.exec("ALTER TABLE file_names ADD COLUMN status TEXT DEFAULT 'not started';");
   db.exec("PRAGMA user_version = 1;");
   console.log("Updated database to version 1.");
 }
@@ -41,4 +38,4 @@ if (dbVersion === 0) {
 // enable WAL mode
 db.exec("PRAGMA journal_mode = WAL;");
 
-export default db;
+export default db;

+ 1 - 1
src/db/types.ts

@@ -20,4 +20,4 @@ export class User {
   id!: number;
   email!: string;
   password!: string;
-}
+}

+ 2 - 4
src/helpers/env.ts

@@ -1,8 +1,7 @@
 export const ACCOUNT_REGISTRATION =
   process.env.ACCOUNT_REGISTRATION?.toLowerCase() === "true" || false;
 
-export const HTTP_ALLOWED =
-  process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false;
+export const HTTP_ALLOWED = process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false;
 
 export const ALLOW_UNAUTHENTICATED =
   process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false;
@@ -11,7 +10,6 @@ export const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
   ? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
   : 24;
 
-export const HIDE_HISTORY =
-  process.env.HIDE_HISTORY?.toLowerCase() === "true" || false;
+export const HIDE_HISTORY = process.env.HIDE_HISTORY?.toLowerCase() === "true" || false;
 
 export const WEBROOT = process.env.WEBROOT ?? "";

+ 37 - 37
src/helpers/normalizeFiletype.ts

@@ -1,37 +1,37 @@
-export const normalizeFiletype = (filetype: string): string => {
-  const lowercaseFiletype = filetype.toLowerCase();
-
-  switch (lowercaseFiletype) {
-    case "jfif":
-    case "jpg":
-      return "jpeg";
-    case "htm":
-      return "html";
-    case "tex":
-      return "latex";
-    case "md":
-      return "markdown";
-    case "unknown":
-      return "m4a";
-    default:
-      return lowercaseFiletype;
-  }
-};
-
-export const normalizeOutputFiletype = (filetype: string): string => {
-  const lowercaseFiletype = filetype.toLowerCase();
-
-  switch (lowercaseFiletype) {
-    case "jpeg":
-      return "jpg";
-    case "latex":
-      return "tex";
-    case "markdown_phpextra":
-    case "markdown_strict":
-    case "markdown_mmd":
-    case "markdown":
-      return "md";
-    default:
-      return lowercaseFiletype;
-  }
-};
+export const normalizeFiletype = (filetype: string): string => {
+  const lowercaseFiletype = filetype.toLowerCase();
+
+  switch (lowercaseFiletype) {
+    case "jfif":
+    case "jpg":
+      return "jpeg";
+    case "htm":
+      return "html";
+    case "tex":
+      return "latex";
+    case "md":
+      return "markdown";
+    case "unknown":
+      return "m4a";
+    default:
+      return lowercaseFiletype;
+  }
+};
+
+export const normalizeOutputFiletype = (filetype: string): string => {
+  const lowercaseFiletype = filetype.toLowerCase();
+
+  switch (lowercaseFiletype) {
+    case "jpeg":
+      return "jpg";
+    case "latex":
+      return "tex";
+    case "markdown_phpextra":
+    case "markdown_strict":
+    case "markdown_mmd":
+    case "markdown":
+      return "md";
+    default:
+      return lowercaseFiletype;
+  }
+};

+ 12 - 19
src/index.tsx

@@ -2,22 +2,21 @@ import { rmSync } from "node:fs";
 import { mkdir } from "node:fs/promises";
 import { html } from "@elysiajs/html";
 import { staticPlugin } from "@elysiajs/static";
-
 import { Elysia } from "elysia";
 import "./helpers/printVersions";
+import db from "./db/db";
+import { Jobs } from "./db/types";
 import { AUTO_DELETE_EVERY_N_HOURS, WEBROOT } from "./helpers/env";
-import { user } from "./pages/user";
-import { root } from "./pages/root"
-import { upload } from "./pages/upload"
-import { history } from "./pages/history";
-import { convert } from "./pages/convert"
-import { download } from "./pages/download"
-import { results } from "./pages/results";
+import { chooseConverter } from "./pages/chooseConverter";
+import { convert } from "./pages/convert";
 import { deleteFile } from "./pages/deleteFile";
+import { download } from "./pages/download";
+import { history } from "./pages/history";
 import { listConverters } from "./pages/listConverters";
-import { chooseConverter } from "./pages/chooseConverter";
-import db from "./db/db";
-import { Jobs } from "./db/types";
+import { results } from "./pages/results";
+import { root } from "./pages/root";
+import { upload } from "./pages/upload";
+import { user } from "./pages/user";
 
 mkdir("./data", { recursive: true }).catch(console.error);
 
@@ -64,19 +63,13 @@ if (process.env.NODE_ENV !== "production") {
 
 app.listen(3000);
 
-console.log(
-  `🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`,
-);
+console.log(`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`);
 
 const clearJobs = () => {
   const jobs = db
     .query("SELECT * FROM jobs WHERE date_created < ?")
     .as(Jobs)
-    .all(
-      new Date(
-        Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000,
-      ).toISOString(),
-    );
+    .all(new Date(Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000).toISOString());
 
   for (const job of jobs) {
     // delete the directories

+ 63 - 71
src/pages/chooseConverter.tsx

@@ -1,75 +1,67 @@
+import { Html } from "@elysiajs/html";
 import Elysia, { t } from "elysia";
+import { getPossibleTargets } from "../converters/main";
 import { userService } from "./user";
-import { Html } from "@elysiajs/html";
-import {
-  getPossibleTargets,
-} from "../converters/main";
 
-export const chooseConverter = new Elysia()
-  .use(userService)
-  .post(
-    "/conversions",
-    ({ body }) => {
-      return (
-        <>
-          <article
-            class={`
-              convert_to_popup absolute z-2 m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
-              overflow-x-hidden overflow-y-auto rounded bg-neutral-800
-              sm:h-[30vh]
-            `}
-          >
-            {Object.entries(getPossibleTargets(body.fileType)).map(
-              ([converter, targets]) => (
-                <article
-                  class="convert_to_group flex w-full flex-col border-b border-neutral-700 p-4"
-                  data-converter={converter}
-                >
-                  <header class="mb-2 w-full text-xl font-bold" safe>
-                    {converter}
-                  </header>
-                  <ul class="convert_to_target flex flex-row flex-wrap gap-1">
-                    {targets.map((target) => (
-                      <button
-                        // https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
-                        tabindex={0}
-                        class={`
-                          target rounded bg-neutral-700 p-1 text-base
-                          hover:bg-neutral-600
-                        `}
-                        data-value={`${target},${converter}`}
-                        data-target={target}
-                        data-converter={converter}
-                        type="button"
-                        safe
-                      >
-                        {target}
-                      </button>
-                    ))}
-                  </ul>
-                </article>
-              ),
-            )}
-          </article>
+export const chooseConverter = new Elysia().use(userService).post(
+  "/conversions",
+  ({ body }) => {
+    return (
+      <>
+        <article
+          class={`
+            convert_to_popup absolute z-2 m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
+            overflow-x-hidden overflow-y-auto rounded bg-neutral-800
+            sm:h-[30vh]
+          `}
+        >
+          {Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
+            <article
+              class="convert_to_group flex w-full flex-col border-b border-neutral-700 p-4"
+              data-converter={converter}
+            >
+              <header class="mb-2 w-full text-xl font-bold" safe>
+                {converter}
+              </header>
+              <ul class="convert_to_target flex flex-row flex-wrap gap-1">
+                {targets.map((target) => (
+                  <button
+                    // https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
+                    tabindex={0}
+                    class={`
+                      target rounded bg-neutral-700 p-1 text-base
+                      hover:bg-neutral-600
+                    `}
+                    data-value={`${target},${converter}`}
+                    data-target={target}
+                    data-converter={converter}
+                    type="button"
+                    safe
+                  >
+                    {target}
+                  </button>
+                ))}
+              </ul>
+            </article>
+          ))}
+        </article>
 
-          <select name="convert_to" aria-label="Convert to" required hidden>
-            <option selected disabled value="">
-              Convert to
-            </option>
-            {Object.entries(getPossibleTargets(body.fileType)).map(
-              ([converter, targets]) => (
-                <optgroup label={converter}>
-                  {targets.map((target) => (
-                    <option value={`${target},${converter}`} safe>
-                      {target}
-                    </option>
-                  ))}
-                </optgroup>
-              ),
-            )}
-          </select>
-        </>
-      );
-    },
-    { body: t.Object({ fileType: t.String() }) },
-  )
+        <select name="convert_to" aria-label="Convert to" required hidden>
+          <option selected disabled value="">
+            Convert to
+          </option>
+          {Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
+            <optgroup label={converter}>
+              {targets.map((target) => (
+                <option value={`${target},${converter}`} safe>
+                  {target}
+                </option>
+              ))}
+            </optgroup>
+          ))}
+        </select>
+      </>
+    );
+  },
+  { body: t.Object({ fileType: t.String() }) },
+);

+ 9 - 16
src/pages/convert.tsx

@@ -3,14 +3,11 @@ import { Elysia, t } from "elysia";
 import sanitize from "sanitize-filename";
 import { outputDir, uploadsDir } from "..";
 import { mainConverter } from "../converters/main";
-import { WEBROOT } from "../helpers/env";
 import db from "../db/db";
-import {
-  normalizeFiletype,
-  normalizeOutputFiletype,
-} from "../helpers/normalizeFiletype";
-import { userService } from "./user";
 import { Jobs } from "../db/types";
+import { WEBROOT } from "../helpers/env";
+import { normalizeFiletype, normalizeOutputFiletype } from "../helpers/normalizeFiletype";
+import { userService } from "./user";
 
 export const convert = new Elysia().use(userService).post(
   "/convert",
@@ -44,10 +41,7 @@ export const convert = new Elysia().use(userService).post(
     try {
       await mkdir(userOutputDir, { recursive: true });
     } catch (error) {
-      console.error(
-        `Failed to create the output directory: ${userOutputDir}.`,
-        error,
-      );
+      console.error(`Failed to create the output directory: ${userOutputDir}.`, error);
     }
 
     const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? "");
@@ -62,9 +56,10 @@ export const convert = new Elysia().use(userService).post(
       return redirect(`${WEBROOT}/`, 302);
     }
 
-    db.query(
-      "UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2",
-    ).run(fileNames.length, jobId.value);
+    db.query("UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2").run(
+      fileNames.length,
+      jobId.value,
+    );
 
     const query = db.query(
       "INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)",
@@ -99,9 +94,7 @@ export const convert = new Elysia().use(userService).post(
       .then(() => {
         // All conversions are done, update the job status to 'completed'
         if (jobId.value) {
-          db.query("UPDATE jobs SET status = 'completed' WHERE id = ?1").run(
-            jobId.value,
-          );
+          db.query("UPDATE jobs SET status = 'completed' WHERE id = ?1").run(jobId.value);
         }
 
         // delete all uploaded files in userUploadsDir

+ 37 - 35
src/pages/deleteFile.tsx

@@ -1,39 +1,41 @@
-import { Elysia, t } from "elysia";
-import { userService } from "./user";
 import { unlink } from "node:fs/promises";
-import { WEBROOT } from "../helpers/env";
+import { Elysia, t } from "elysia";
 import { uploadsDir } from "..";
 import db from "../db/db";
+import { WEBROOT } from "../helpers/env";
+import { userService } from "./user";
+
+export const deleteFile = new Elysia().use(userService).post(
+  "/delete",
+  async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
+    if (!auth?.value) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
+
+    const user = await jwt.verify(auth.value);
+    if (!user) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
+
+    if (!jobId?.value) {
+      return redirect(`${WEBROOT}/`, 302);
+    }
+
+    const existingJob = await db
+      .query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
+      .get(jobId.value, user.id);
+
+    if (!existingJob) {
+      return redirect(`${WEBROOT}/`, 302);
+    }
+
+    const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
+
+    await unlink(`${userUploadsDir}${body.filename}`);
 
-export const deleteFile = new Elysia()
-  .use(userService)
-  .post(
-    "/delete",
-    async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
-      if (!auth?.value) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
-
-      const user = await jwt.verify(auth.value);
-      if (!user) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
-
-      if (!jobId?.value) {
-        return redirect(`${WEBROOT}/`, 302);
-      }
-
-      const existingJob = await db
-        .query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
-        .get(jobId.value, user.id);
-
-      if (!existingJob) {
-        return redirect(`${WEBROOT}/`, 302);
-      }
-
-      const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
-
-      await unlink(`${userUploadsDir}${body.filename}`);
-    },
-    { body: t.Object({ filename: t.String() }) },
-  )
+    return {
+      message: "File deleted successfully.",
+    };
+  },
+  { body: t.Object({ filename: t.String() }) },
+);

+ 27 - 31
src/pages/download.tsx

@@ -1,10 +1,9 @@
-
 import { Elysia } from "elysia";
-import { userService } from "./user";
-import { WEBROOT } from "../helpers/env";
 import sanitize from "sanitize-filename";
 import { outputDir } from "..";
 import db from "../db/db";
+import { WEBROOT } from "../helpers/env";
+import { userService } from "./user";
 
 export const download = new Elysia()
   .use(userService)
@@ -36,31 +35,28 @@ export const download = new Elysia()
       return Bun.file(filePath);
     },
   )
-  .get(
-    "/zip/:userId/:jobId",
-    async ({ params, jwt, redirect, cookie: { auth } }) => {
-      // TODO: Implement zip download
-      if (!auth?.value) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
-
-      const user = await jwt.verify(auth.value);
-      if (!user) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
-
-      const job = await db
-        .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
-        .get(user.id, params.jobId);
-
-      if (!job) {
-        return redirect(`${WEBROOT}/results`, 302);
-      }
-
-      // const userId = decodeURIComponent(params.userId);
-      // const jobId = decodeURIComponent(params.jobId);
-      // const outputPath = `${outputDir}${userId}/`{jobId}/);
-
-      // return Bun.zip(outputPath);
-    },
-  )
+  .get("/zip/:userId/:jobId", async ({ params, jwt, redirect, cookie: { auth } }) => {
+    // TODO: Implement zip download
+    if (!auth?.value) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
+
+    const user = await jwt.verify(auth.value);
+    if (!user) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
+
+    const job = await db
+      .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
+      .get(user.id, params.jobId);
+
+    if (!job) {
+      return redirect(`${WEBROOT}/results`, 302);
+    }
+
+    // const userId = decodeURIComponent(params.userId);
+    // const jobId = decodeURIComponent(params.jobId);
+    // const outputPath = `${outputDir}${userId}/`{jobId}/);
+
+    // return Bun.zip(outputPath);
+  });

+ 31 - 57
src/pages/history.tsx

@@ -1,11 +1,11 @@
+import { Html } from "@elysiajs/html";
 import { Elysia } from "elysia";
 import { BaseHtml } from "../components/base";
-import { Html } from "@elysiajs/html";
 import { Header } from "../components/header";
-import { userService } from "./user";
-import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, WEBROOT } from "../helpers/env";
-import { Filename, Jobs } from "../db/types";
 import db from "../db/db";
+import { Filename, Jobs } from "../db/types";
+import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, WEBROOT } from "../helpers/env";
+import { userService } from "./user";
 
 export const history = new Elysia()
   .use(userService)
@@ -23,17 +23,10 @@ export const history = new Elysia()
       return redirect(`${WEBROOT}/login`, 302);
     }
 
-    let userJobs = db
-      .query("SELECT * FROM jobs WHERE user_id = ?")
-      .as(Jobs)
-      .all(user.id)
-      .reverse();
+    let userJobs = db.query("SELECT * FROM jobs WHERE user_id = ?").as(Jobs).all(user.id).reverse();
 
     for (const job of userJobs) {
-      const files = db
-        .query("SELECT * FROM file_names WHERE job_id = ?")
-        .as(Filename)
-        .all(job.id);
+      const files = db.query("SELECT * FROM file_names WHERE job_id = ?").as(Filename).all(job.id);
 
       job.finished_files = files.length;
       job.files_detailed = files;
@@ -123,10 +116,7 @@ export const history = new Elysia()
                   {userJobs.map((job) => (
                     <>
                       <tr id={`job-row-${job.id}`}>
-                        <td
-                          class="job-details-toggle cursor-pointer"
-                          data-job-id={job.id}
-                        >
+                        <td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
                           <svg
                             id={`arrow-${job.id}`}
                             xmlns="http://www.w3.org/2000/svg"
@@ -143,9 +133,7 @@ export const history = new Elysia()
                             />
                           </svg>
                         </td>
-                        <td safe>
-                          {new Date(job.date_created).toLocaleTimeString()}
-                        </td>
+                        <td safe>{new Date(job.date_created).toLocaleTimeString()}</td>
                         <td>{job.num_files}</td>
                         <td class="max-sm:hidden">{job.finished_files}</td>
                         <td safe>{job.status}</td>
@@ -164,43 +152,29 @@ export const history = new Elysia()
                       <tr id={`details-${job.id}`} class="hidden">
                         <td colspan="6">
                           <div class="p-2 text-sm text-neutral-500">
-                            <div class="mb-1 font-semibold">
-                              Detailed File Information:
-                            </div>
-                            {job.files_detailed.map(
-                              (file: Filename) => (
-                                <div
-                                  class="flex items-center"
+                            <div class="mb-1 font-semibold">Detailed File Information:</div>
+                            {job.files_detailed.map((file: Filename) => (
+                              <div class="flex items-center">
+                                <span class="w-5/12 truncate" title={file.file_name} safe>
+                                  {file.file_name}
+                                </span>
+                                <svg
+                                  xmlns="http://www.w3.org/2000/svg"
+                                  viewBox="0 0 20 20"
+                                  fill="currentColor"
+                                  class="mx-2 inline-block h-4 w-4 text-neutral-500"
                                 >
-                                  <span
-                                    class="w-5/12 truncate"
-                                    title={file.file_name}
-                                    safe
-                                  >
-                                    {file.file_name}
-                                  </span>
-                                  <svg
-                                    xmlns="http://www.w3.org/2000/svg"
-                                    viewBox="0 0 20 20"
-                                    fill="currentColor"
-                                    class="mx-2 inline-block h-4 w-4 text-neutral-500"
-                                  >
-                                    <path
-                                      fill-rule="evenodd"
-                                      d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
-                                      clip-rule="evenodd"
-                                    />
-                                  </svg>
-                                  <span
-                                    class="w-5/12 truncate"
-                                    title={file.output_file_name}
-                                    safe
-                                  >
-                                    {file.output_file_name}
-                                  </span>
-                                </div>
-                              ),
-                            )}
+                                  <path
+                                    fill-rule="evenodd"
+                                    d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
+                                    clip-rule="evenodd"
+                                  />
+                                </svg>
+                                <span class="w-5/12 truncate" title={file.output_file_name} safe>
+                                  {file.output_file_name}
+                                </span>
+                              </div>
+                            ))}
                           </div>
                         </td>
                       </tr>
@@ -239,4 +213,4 @@ export const history = new Elysia()
         </>
       </BaseHtml>
     );
-  })
+  });

+ 30 - 39
src/pages/listConverters.tsx

@@ -1,13 +1,10 @@
-import Elysia from "elysia";
-import { userService } from "./user";
 import { Html } from "@elysiajs/html";
-import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
+import Elysia from "elysia";
 import { BaseHtml } from "../components/base";
 import { Header } from "../components/header";
-import {
-  getAllInputs,
-  getAllTargets,
-} from "../converters/main";
+import { getAllInputs, getAllTargets } from "../converters/main";
+import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
+import { userService } from "./user";
 
 export const listConverters = new Elysia()
   .use(userService)
@@ -24,11 +21,7 @@ export const listConverters = new Elysia()
     return (
       <BaseHtml webroot={WEBROOT} title="ConvertX | Converters">
         <>
-          <Header
-            webroot={WEBROOT}
-            allowUnauthenticated={ALLOW_UNAUTHENTICATED}
-            loggedIn
-          />
+          <Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
           <main
             class={`
               w-full flex-1 px-2
@@ -53,32 +46,30 @@ export const listConverters = new Elysia()
                   </tr>
                 </thead>
                 <tbody>
-                  {Object.entries(getAllTargets()).map(
-                    ([converter, targets]) => {
-                      const inputs = getAllInputs(converter);
-                      return (
-                        <tr>
-                          <td safe>{converter}</td>
-                          <td>
-                            Count: {inputs.length}
-                            <ul>
-                              {inputs.map((input) => (
-                                <li safe>{input}</li>
-                              ))}
-                            </ul>
-                          </td>
-                          <td>
-                            Count: {targets.length}
-                            <ul>
-                              {targets.map((target) => (
-                                <li safe>{target}</li>
-                              ))}
-                            </ul>
-                          </td>
-                        </tr>
-                      );
-                    },
-                  )}
+                  {Object.entries(getAllTargets()).map(([converter, targets]) => {
+                    const inputs = getAllInputs(converter);
+                    return (
+                      <tr>
+                        <td safe>{converter}</td>
+                        <td>
+                          Count: {inputs.length}
+                          <ul>
+                            {inputs.map((input) => (
+                              <li safe>{input}</li>
+                            ))}
+                          </ul>
+                        </td>
+                        <td>
+                          Count: {targets.length}
+                          <ul>
+                            {targets.map((target) => (
+                              <li safe>{target}</li>
+                            ))}
+                          </ul>
+                        </td>
+                      </tr>
+                    );
+                  })}
                 </tbody>
               </table>
             </article>
@@ -86,4 +77,4 @@ export const listConverters = new Elysia()
         </>
       </BaseHtml>
     );
-  })
+  });

+ 204 - 203
src/pages/results.tsx

@@ -1,214 +1,215 @@
-import { Elysia } from "elysia";
-import { userService } from "./user";
 import { Html } from "@elysiajs/html";
-import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
-import { Filename, Jobs } from "../db/types";
-import { Header } from "../components/header";
+import { Elysia } from "elysia";
 import { BaseHtml } from "../components/base";
+import { Header } from "../components/header";
 import db from "../db/db";
+import { Filename, Jobs } from "../db/types";
+import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
+import { userService } from "./user";
 
-function ResultsArticle({ job, files, outputPath }: { job: Jobs, files: Filename[], outputPath: string }) {
-  return (<article class="article">
-    <div class="mb-4 flex items-center justify-between">
-      <h1 class="text-xl">Results</h1>
-      <div>
-        <button
-          type="button"
-          class="float-right w-40 btn-primary"
-          onclick="downloadAll()"
-          {...(files.length !== job.num_files
-            ? { disabled: true, "aria-busy": "true" }
-            : "")}
-        >
-          {files.length === job.num_files
-            ? "Download All"
-            : "Converting..."}
-        </button>
-      </div>
-    </div>
-    <progress
-      max={job.num_files}
-      value={files.length}
-      class={`
-              mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0
-              bg-neutral-700 bg-none text-accent-500 accent-accent-500
-              [&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full
-              [&::-webkit-progress-value]:[background:none]
-              [&[value]::-webkit-progress-value]:bg-accent-500
-              [&[value]::-webkit-progress-value]:transition-[inline-size]
-            `}
-    />
-    <table class={`
-              w-full table-auto rounded bg-neutral-900 text-left
-              [&_td]:p-4
-              [&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
-            `}
-    >
-      <thead>
-        <tr>
-          <th class={`
-                    px-2 py-2
-                    sm:px-4
-                  `}
+function ResultsArticle({
+  job,
+  files,
+  outputPath,
+}: {
+  job: Jobs;
+  files: Filename[];
+  outputPath: string;
+}) {
+  return (
+    <article class="article">
+      <div class="mb-4 flex items-center justify-between">
+        <h1 class="text-xl">Results</h1>
+        <div>
+          <button
+            type="button"
+            class="float-right w-40 btn-primary"
+            onclick="downloadAll()"
+            {...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
           >
-            Converted File Name
-          </th>
-          <th class={`
-                    px-2 py-2
-                    sm:px-4
-                  `}
-          >
-            Status
-          </th>
-          <th class={`
-                    px-2 py-2
-                    sm:px-4
-                  `}
-          >
-            View
-          </th>
-          <th class={`
-                    px-2 py-2
-                    sm:px-4
-                  `}>
-            Download
-          </th>
-        </tr>
-      </thead>
-      <tbody>
-        {files.map((file) => (
+            {files.length === job.num_files ? "Download All" : "Converting..."}
+          </button>
+        </div>
+      </div>
+      <progress
+        max={job.num_files}
+        value={files.length}
+        class={`
+          mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0
+          bg-neutral-700 bg-none text-accent-500 accent-accent-500
+          [&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full
+          [&::-webkit-progress-value]:[background:none]
+          [&[value]::-webkit-progress-value]:bg-accent-500
+          [&[value]::-webkit-progress-value]:transition-[inline-size]
+        `}
+      />
+      <table
+        class={`
+          w-full table-auto rounded bg-neutral-900 text-left
+          [&_td]:p-4
+          [&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
+        `}
+      >
+        <thead>
           <tr>
-            <td safe class="max-w-[20vw] truncate">
-              {file.output_file_name}
-            </td>
-            <td safe>{file.status}</td>
-            <td>
-              <a
-                class={`
-                        text-accent-500 underline
-                        hover:text-accent-400
-                      `}
-                href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
-              >
-                View
-              </a>
-            </td>
-            <td>
-              <a
-                class={`
-                        text-accent-500 underline
-                        hover:text-accent-400
-                      `}
-                href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
-                download={file.output_file_name}
-              >
-                Download
-              </a>
-            </td>
+            <th
+              class={`
+                px-2 py-2
+                sm:px-4
+              `}
+            >
+              Converted File Name
+            </th>
+            <th
+              class={`
+                px-2 py-2
+                sm:px-4
+              `}
+            >
+              Status
+            </th>
+            <th
+              class={`
+                px-2 py-2
+                sm:px-4
+              `}
+            >
+              View
+            </th>
+            <th
+              class={`
+                px-2 py-2
+                sm:px-4
+              `}
+            >
+              Download
+            </th>
           </tr>
-        ))}
-      </tbody>
-    </table>
-  </article>
-  )
+        </thead>
+        <tbody>
+          {files.map((file) => (
+            <tr>
+              <td safe class="max-w-[20vw] truncate">
+                {file.output_file_name}
+              </td>
+              <td safe>{file.status}</td>
+              <td>
+                <a
+                  class={`
+                    text-accent-500 underline
+                    hover:text-accent-400
+                  `}
+                  href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
+                >
+                  View
+                </a>
+              </td>
+              <td>
+                <a
+                  class={`
+                    text-accent-500 underline
+                    hover:text-accent-400
+                  `}
+                  href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
+                  download={file.output_file_name}
+                >
+                  Download
+                </a>
+              </td>
+            </tr>
+          ))}
+        </tbody>
+      </table>
+    </article>
+  );
 }
 
 export const results = new Elysia()
   .use(userService)
-  .get(
-    "/results/:jobId",
-    async ({ params, jwt, set, redirect, cookie: { auth, job_id } }) => {
-      if (!auth?.value) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
-
-      if (job_id?.value) {
-        // clear the job_id cookie since we are viewing the results
-        job_id.remove();
-      }
-
-      const user = await jwt.verify(auth.value);
-      if (!user) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
-
-      const job = db
-        .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
-        .as(Jobs)
-        .get(user.id, params.jobId);
-
-      if (!job) {
-        set.status = 404;
-        return {
-          message: "Job not found.",
-        };
-      }
-
-      const outputPath = `${user.id}/${params.jobId}/`;
-
-      const files = db
-        .query("SELECT * FROM file_names WHERE job_id = ?")
-        .as(Filename)
-        .all(params.jobId);
-
-      return (
-        <BaseHtml webroot={WEBROOT} title="ConvertX | Result">
-          <>
-            <Header
-              webroot={WEBROOT}
-              allowUnauthenticated={ALLOW_UNAUTHENTICATED}
-              loggedIn
-            />
-            <main
-              class={`
-                w-full flex-1 px-2
-                sm:px-4
-              `}
-            >
-              <ResultsArticle job={job} files={files} outputPath={outputPath} />
-            </main>
-            <script src={`${WEBROOT}/results.js`} defer />
-          </>
-        </BaseHtml>
-      );
-    },
-  )
-  .post(
-    "/progress/:jobId",
-    async ({ jwt, set, params, redirect, cookie: { auth, job_id } }) => {
-      if (!auth?.value) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
-
-      if (job_id?.value) {
-        // clear the job_id cookie since we are viewing the results
-        job_id.remove();
-      }
-
-      const user = await jwt.verify(auth.value);
-      if (!user) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
-
-      const job = db
-        .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
-        .as(Jobs)
-        .get(user.id, params.jobId);
-
-      if (!job) {
-        set.status = 404;
-        return {
-          message: "Job not found.",
-        };
-      }
-
-      const outputPath = `${user.id}/${params.jobId}/`;
-
-      const files = db
-        .query("SELECT * FROM file_names WHERE job_id = ?")
-        .as(Filename)
-        .all(params.jobId);
-
-      return <ResultsArticle job={job} files={files} outputPath={outputPath} />;
-    },
-  )
+  .get("/results/:jobId", async ({ params, jwt, set, redirect, cookie: { auth, job_id } }) => {
+    if (!auth?.value) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
+
+    if (job_id?.value) {
+      // clear the job_id cookie since we are viewing the results
+      job_id.remove();
+    }
+
+    const user = await jwt.verify(auth.value);
+    if (!user) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
+
+    const job = db
+      .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
+      .as(Jobs)
+      .get(user.id, params.jobId);
+
+    if (!job) {
+      set.status = 404;
+      return {
+        message: "Job not found.",
+      };
+    }
+
+    const outputPath = `${user.id}/${params.jobId}/`;
+
+    const files = db
+      .query("SELECT * FROM file_names WHERE job_id = ?")
+      .as(Filename)
+      .all(params.jobId);
+
+    return (
+      <BaseHtml webroot={WEBROOT} title="ConvertX | Result">
+        <>
+          <Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
+          <main
+            class={`
+              w-full flex-1 px-2
+              sm:px-4
+            `}
+          >
+            <ResultsArticle job={job} files={files} outputPath={outputPath} />
+          </main>
+          <script src={`${WEBROOT}/results.js`} defer />
+        </>
+      </BaseHtml>
+    );
+  })
+  .post("/progress/:jobId", async ({ jwt, set, params, redirect, cookie: { auth, job_id } }) => {
+    if (!auth?.value) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
+
+    if (job_id?.value) {
+      // clear the job_id cookie since we are viewing the results
+      job_id.remove();
+    }
+
+    const user = await jwt.verify(auth.value);
+    if (!user) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
+
+    const job = db
+      .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
+      .as(Jobs)
+      .get(user.id, params.jobId);
+
+    if (!job) {
+      set.status = 404;
+      return {
+        message: "Job not found.",
+      };
+    }
+
+    const outputPath = `${user.id}/${params.jobId}/`;
+
+    const files = db
+      .query("SELECT * FROM file_names WHERE job_id = ?")
+      .as(Filename)
+      .all(params.jobId);
+
+    return <ResultsArticle job={job} files={files} outputPath={outputPath} />;
+  });

+ 67 - 76
src/pages/root.tsx

@@ -1,14 +1,20 @@
-import { Elysia } from "elysia";
+import { randomInt } from "node:crypto";
 import { Html } from "@elysiajs/html";
-import { FIRST_RUN, userService } from "./user";
-import { ACCOUNT_REGISTRATION, ALLOW_UNAUTHENTICATED, HIDE_HISTORY, HTTP_ALLOWED, WEBROOT } from "../helpers/env";
 import { JWTPayloadSpec } from "@elysiajs/jwt";
-import { randomInt } from "node:crypto";
+import { Elysia } from "elysia";
 import { BaseHtml } from "../components/base";
 import { Header } from "../components/header";
 import { getAllTargets } from "../converters/main";
 import db from "../db/db";
 import { User } from "../db/types";
+import {
+  ACCOUNT_REGISTRATION,
+  ALLOW_UNAUTHENTICATED,
+  HIDE_HISTORY,
+  HTTP_ALLOWED,
+  WEBROOT,
+} from "../helpers/env";
+import { FIRST_RUN, userService } from "./user";
 
 export const root = new Elysia()
   .use(userService)
@@ -27,10 +33,7 @@ export const root = new Elysia()
     let user: ({ id: string } & JWTPayloadSpec) | false = false;
     if (ALLOW_UNAUTHENTICATED) {
       const newUserId = String(
-        randomInt(
-          2 ** 24,
-          Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER),
-        ),
+        randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
       );
       const accessToken = await jwt.sign({
         id: newUserId,
@@ -54,20 +57,19 @@ export const root = new Elysia()
     } else if (auth?.value) {
       user = await jwt.verify(auth.value);
 
-      if (user !== false && user.id) {
-        if (Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED) {
-          // make sure user exists in db
-          const existingUser = db
-            .query("SELECT * FROM users WHERE id = ?")
-            .as(User)
-            .get(user.id);
+      if (
+        user !== false &&
+        user.id &&
+        (Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED)
+      ) {
+        // make sure user exists in db
+        const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
 
-          if (!existingUser) {
-            if (auth?.value) {
-              auth.remove();
-            }
-            return redirect(`${WEBROOT}/login`, 302);
+        if (!existingUser) {
+          if (auth?.value) {
+            auth.remove();
           }
+          return redirect(`${WEBROOT}/login`, 302);
         }
       }
     }
@@ -82,11 +84,9 @@ export const root = new Elysia()
       new Date().toISOString(),
     );
 
-    const id = (
-      db
-        .query("SELECT id FROM jobs WHERE user_id = ? ORDER BY id DESC")
-        .get(user.id) as { id: number }
-    ).id;
+    const { id } = db
+      .query("SELECT id FROM jobs WHERE user_id = ? ORDER BY id DESC")
+      .get(user.id) as { id: number };
 
     if (!jobId) {
       return { message: "Cookies should be enabled to use this app." };
@@ -172,62 +172,53 @@ export const root = new Elysia()
                       sm:h-[30vh]
                     `}
                   >
-                    {Object.entries(getAllTargets()).map(
-                      ([converter, targets]) => (
-                        <article
-                          class={`
-                            convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
-                          `}
-                          data-converter={converter}
-                        >
-                          <header class="mb-2 w-full text-xl font-bold" safe>
-                            {converter}
-                          </header>
-                          <ul class="convert_to_target flex flex-row flex-wrap gap-1">
-                            {targets.map((target) => (
-                              <button
-                                // https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
-                                tabindex={0}
-                                class={`
-                                  target rounded bg-neutral-700 p-1 text-base
-                                  hover:bg-neutral-600
-                                `}
-                                data-value={`${target},${converter}`}
-                                data-target={target}
-                                data-converter={converter}
-                                type="button"
-                                safe
-                              >
-                                {target}
-                              </button>
-                            ))}
-                          </ul>
-                        </article>
-                      ),
-                    )}
+                    {Object.entries(getAllTargets()).map(([converter, targets]) => (
+                      <article
+                        class={`
+                          convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
+                        `}
+                        data-converter={converter}
+                      >
+                        <header class="mb-2 w-full text-xl font-bold" safe>
+                          {converter}
+                        </header>
+                        <ul class="convert_to_target flex flex-row flex-wrap gap-1">
+                          {targets.map((target) => (
+                            <button
+                              // https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
+                              tabindex={0}
+                              class={`
+                                target rounded bg-neutral-700 p-1 text-base
+                                hover:bg-neutral-600
+                              `}
+                              data-value={`${target},${converter}`}
+                              data-target={target}
+                              data-converter={converter}
+                              type="button"
+                              safe
+                            >
+                              {target}
+                            </button>
+                          ))}
+                        </ul>
+                      </article>
+                    ))}
                   </article>
 
                   {/* Hidden element which determines the format to convert the file too and the converter to use */}
-                  <select
-                    name="convert_to"
-                    aria-label="Convert to"
-                    required
-                    hidden
-                  >
+                  <select name="convert_to" aria-label="Convert to" required hidden>
                     <option selected disabled value="">
                       Convert to
                     </option>
-                    {Object.entries(getAllTargets()).map(
-                      ([converter, targets]) => (
-                        <optgroup label={converter}>
-                          {targets.map((target) => (
-                            <option value={`${target},${converter}`} safe>
-                              {target}
-                            </option>
-                          ))}
-                        </optgroup>
-                      ),
-                    )}
+                    {Object.entries(getAllTargets()).map(([converter, targets]) => (
+                      <optgroup label={converter}>
+                        {targets.map((target) => (
+                          <option value={`${target},${converter}`} safe>
+                            {target}
+                          </option>
+                        ))}
+                      </optgroup>
+                    ))}
                   </select>
                 </div>
               </article>
@@ -246,4 +237,4 @@ export const root = new Elysia()
         </>
       </BaseHtml>
     );
-  })
+  });

+ 36 - 39
src/pages/upload.tsx

@@ -1,51 +1,48 @@
 import { Elysia, t } from "elysia";
-import { userService } from "./user";
+import db from "../db/db";
 import { WEBROOT } from "../helpers/env";
 import { uploadsDir } from "../index";
-import db from "../db/db";
-
+import { userService } from "./user";
 
-export const upload = new Elysia()
-  .use(userService)
-  .post(
-    "/upload",
-    async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
-      if (!auth?.value) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
+export const upload = new Elysia().use(userService).post(
+  "/upload",
+  async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
+    if (!auth?.value) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
 
-      const user = await jwt.verify(auth.value);
-      if (!user) {
-        return redirect(`${WEBROOT}/login`, 302);
-      }
+    const user = await jwt.verify(auth.value);
+    if (!user) {
+      return redirect(`${WEBROOT}/login`, 302);
+    }
 
-      if (!jobId?.value) {
-        return redirect(`${WEBROOT}/`, 302);
-      }
+    if (!jobId?.value) {
+      return redirect(`${WEBROOT}/`, 302);
+    }
 
-      const existingJob = await db
-        .query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
-        .get(jobId.value, user.id);
+    const existingJob = await db
+      .query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
+      .get(jobId.value, user.id);
 
-      if (!existingJob) {
-        return redirect(`${WEBROOT}/`, 302);
-      }
+    if (!existingJob) {
+      return redirect(`${WEBROOT}/`, 302);
+    }
 
-      const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
+    const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
 
-      if (body?.file) {
-        if (Array.isArray(body.file)) {
-          for (const file of body.file) {
-            await Bun.write(`${userUploadsDir}${file.name}`, file);
-          }
-        } else {
-          await Bun.write(`${userUploadsDir}${body.file["name"]}`, body.file);
+    if (body?.file) {
+      if (Array.isArray(body.file)) {
+        for (const file of body.file) {
+          await Bun.write(`${userUploadsDir}${file.name}`, file);
         }
+      } else {
+        await Bun.write(`${userUploadsDir}${body.file["name"]}`, body.file);
       }
-
-      return {
-        message: "Files uploaded successfully.",
-      };
-    },
-    { body: t.Object({ file: t.Files() }) },
-  )
+    }
+
+    return {
+      message: "Files uploaded successfully.",
+    };
+  },
+  { body: t.Object({ file: t.Files() }) },
+);

+ 37 - 73
src/pages/user.tsx

@@ -1,17 +1,22 @@
-import { Elysia, t } from "elysia";
+import { randomUUID } from "node:crypto";
 import { Html } from "@elysiajs/html";
+import { jwt } from "@elysiajs/jwt";
+import { Elysia, t } from "elysia";
 import { BaseHtml } from "../components/base";
 import { Header } from "../components/header";
-import { jwt } from "@elysiajs/jwt";
-import { randomUUID } from "node:crypto";
-
-import { ACCOUNT_REGISTRATION, WEBROOT, HIDE_HISTORY, ALLOW_UNAUTHENTICATED, HTTP_ALLOWED } from "../helpers/env";
 import db from "../db/db";
 import { User } from "../db/types";
+import {
+  ACCOUNT_REGISTRATION,
+  ALLOW_UNAUTHENTICATED,
+  HIDE_HISTORY,
+  HTTP_ALLOWED,
+  WEBROOT,
+} from "../helpers/env";
 
 export let FIRST_RUN = db.query("SELECT * FROM users").get() === null || false;
 
-export const userService = new Elysia({ name: 'user/service' })
+export const userService = new Elysia({ name: "user/service" })
   .use(
     jwt({
       name: "jwt",
@@ -25,35 +30,31 @@ export const userService = new Elysia({ name: 'user/service' })
   .model({
     signIn: t.Object({
       email: t.String(),
-      password: t.String()
+      password: t.String(),
     }),
   })
   .macro({
     isSignIn(enabled: boolean) {
-      if (!enabled) return
+      if (!enabled) return;
 
       return {
-        async beforeHandle({
-          status,
-          jwt,
-          cookie: { auth },
-        }) {
+        async beforeHandle({ status, jwt, cookie: { auth } }) {
           if (auth?.value) {
             const user = await jwt.verify(auth.value);
             return {
               success: true,
-              user
-            }
+              user,
+            };
           }
 
           return status(401, {
             success: false,
-            message: 'Unauthorized'
-          })
-        }
-      }
-    }
-  })
+            message: "Unauthorized",
+          });
+        },
+      };
+    },
+  });
 
 export const user = new Elysia()
   .use(userService)
@@ -72,9 +73,7 @@ export const user = new Elysia()
         >
           <h1 class="my-8 text-3xl">Welcome to ConvertX!</h1>
           <article class="article p-0">
-            <header class="w-full bg-neutral-800 p-4">
-              Create your account
-            </header>
+            <header class="w-full bg-neutral-800 p-4">Create your account</header>
             <form method="post" action={`${WEBROOT}/register`} class="p-4">
               <fieldset class="mb-4 flex flex-col gap-4">
                 <label class="flex flex-col gap-1">
@@ -166,11 +165,7 @@ export const user = new Elysia()
                     />
                   </label>
                 </fieldset>
-                <input
-                  type="submit"
-                  value="Register"
-                  class="w-full btn-primary"
-                />
+                <input type="submit" value="Register" class="w-full btn-primary" />
               </form>
             </article>
           </main>
@@ -189,9 +184,7 @@ export const user = new Elysia()
         FIRST_RUN = false;
       }
 
-      const existingUser = await db
-        .query("SELECT * FROM users WHERE email = ?")
-        .get(email);
+      const existingUser = await db.query("SELECT * FROM users WHERE email = ?").get(email);
       if (existingUser) {
         set.status = 400;
         return {
@@ -200,15 +193,9 @@ export const user = new Elysia()
       }
       const savedPassword = await Bun.password.hash(password);
 
-      db.query("INSERT INTO users (email, password) VALUES (?, ?)").run(
-        email,
-        savedPassword,
-      );
+      db.query("INSERT INTO users (email, password) VALUES (?, ?)").run(email, savedPassword);
 
-      const user = db
-        .query("SELECT * FROM users WHERE email = ?")
-        .as(User)
-        .get(email);
+      const user = db.query("SELECT * FROM users WHERE email = ?").as(User).get(email);
 
       if (!user) {
         set.status = 500;
@@ -239,7 +226,7 @@ export const user = new Elysia()
 
       return redirect(`${WEBROOT}/`, 302);
     },
-    { body: 'signIn' },
+    { body: "signIn" },
   )
   .get("/login", async ({ jwt, redirect, cookie: { auth } }) => {
     if (FIRST_RUN) {
@@ -308,11 +295,7 @@ export const user = new Elysia()
                       Register
                     </a>
                   ) : null}
-                  <input
-                    type="submit"
-                    value="Login"
-                    class="w-full btn-primary"
-                  />
+                  <input type="submit" value="Login" class="w-full btn-primary" />
                 </div>
               </form>
             </article>
@@ -324,10 +307,7 @@ export const user = new Elysia()
   .post(
     "/login",
     async function handler({ body, set, redirect, jwt, cookie: { auth } }) {
-      const existingUser = db
-        .query("SELECT * FROM users WHERE email = ?")
-        .as(User)
-        .get(body.email);
+      const existingUser = db.query("SELECT * FROM users WHERE email = ?").as(User).get(body.email);
 
       if (!existingUser) {
         set.status = 403;
@@ -336,10 +316,7 @@ export const user = new Elysia()
         };
       }
 
-      const validPassword = await Bun.password.verify(
-        body.password,
-        existingUser.password,
-      );
+      const validPassword = await Bun.password.verify(body.password, existingUser.password);
 
       if (!validPassword) {
         set.status = 403;
@@ -370,7 +347,7 @@ export const user = new Elysia()
 
       return redirect(`${WEBROOT}/`, 302);
     },
-    { body: 'signIn' },
+    { body: "signIn" },
   )
   .get("/logoff", ({ redirect, cookie: { auth } }) => {
     if (auth?.value) {
@@ -396,10 +373,7 @@ export const user = new Elysia()
       return redirect(`${WEBROOT}/`, 302);
     }
 
-    const userData = db
-      .query("SELECT * FROM users WHERE id = ?")
-      .as(User)
-      .get(user.id);
+    const userData = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
 
     if (!userData) {
       return redirect(`${WEBROOT}/`, 302);
@@ -459,11 +433,7 @@ export const user = new Elysia()
                   </label>
                 </fieldset>
                 <div role="group">
-                  <input
-                    type="submit"
-                    value="Update"
-                    class="w-full btn-primary"
-                  />
+                  <input type="submit" value="Update" class="w-full btn-primary" />
                 </div>
               </form>
             </article>
@@ -483,10 +453,7 @@ export const user = new Elysia()
       if (!user) {
         return redirect(`${WEBROOT}/login`, 302);
       }
-      const existingUser = db
-        .query("SELECT * FROM users WHERE id = ?")
-        .as(User)
-        .get(user.id);
+      const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
 
       if (!existingUser) {
         if (auth?.value) {
@@ -495,10 +462,7 @@ export const user = new Elysia()
         return redirect(`${WEBROOT}/login`, 302);
       }
 
-      const validPassword = await Bun.password.verify(
-        body.password,
-        existingUser.password,
-      );
+      const validPassword = await Bun.password.verify(body.password, existingUser.password);
 
       if (!validPassword) {
         set.status = 403;
@@ -542,4 +506,4 @@ export const user = new Elysia()
         password: t.String(),
       }),
     },
-  )
+  );