Compare commits
	
		
			217 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bba420386f | ||
| 
						 | 
					74df47531c | ||
| 
						 | 
					1adac8c31c | ||
| 
						 | 
					0579f1852b | ||
| 
						 | 
					52d4cc0d03 | ||
| 
						 | 
					2c68016ca6 | ||
| 
						 | 
					7914194856 | ||
| 
						 | 
					2dac7f1362 | ||
| 
						 | 
					a17e5fd614 | ||
| 
						 | 
					21994fb6a2 | ||
| 
						 | 
					a5eaaa422a | ||
| 
						 | 
					ff2ef74135 | ||
| 
						 | 
					70705c1850 | ||
| 
						 | 
					fd9c151e01 | ||
| 
						 | 
					4f0573963f | ||
| 
						 | 
					6bb6bce8a4 | ||
| 
						 | 
					448557bece | ||
| 
						 | 
					bdbd4a122c | ||
| 
						 | 
					cb9d0ec680 | ||
| 
						 | 
					fb60ef66f5 | ||
| 
						 | 
					c1ae43075f | ||
| 
						 | 
					377f69ae8d | ||
| 
						 | 
					cb131cd0a0 | ||
| 
						 | 
					fcc83c5ea8 | ||
| 
						 | 
					96d4717d13 | ||
| 
						 | 
					4d73bf9760 | ||
| 
						 | 
					725a94bc95 | ||
| 
						 | 
					0a366b447a | ||
| 
						 | 
					4a27a7bc03 | ||
| 
						 | 
					3ca5803bda | ||
| 
						 | 
					239041294c | ||
| 
						 | 
					31fdd8f214 | ||
| 
						 | 
					c3319c09eb | ||
| 
						 | 
					d460e94d52 | ||
| 
						 | 
					4b5c732380 | ||
| 
						 | 
					f42665ca40 | ||
| 
						 | 
					bed52cef17 | ||
| 
						 | 
					9d1c93155c | ||
| 
						 | 
					794cc7c474 | ||
| 
						 | 
					d7d584e497 | ||
| 
						 | 
					f5320df86e | ||
| 
						 | 
					056fd4ba93 | ||
| 
						 | 
					5b6e70eb3a | ||
| 
						 | 
					f437a8e7e2 | ||
| 
						 | 
					cdae798fcf | ||
| 
						 | 
					bcc827a81b | ||
| 
						 | 
					84274b9c55 | ||
| 
						 | 
					20c6f8249e | ||
| 
						 | 
					8f0ea2a592 | ||
| 
						 | 
					a29e4a930a | ||
| 
						 | 
					4549c96ae3 | ||
| 
						 | 
					bc64094c04 | ||
| 
						 | 
					fa58827ad5 | ||
| 
						 | 
					8f27be0e3d | ||
| 
						 | 
					df43df1178 | ||
| 
						 | 
					c2beb4a227 | ||
| 
						 | 
					9277c27a50 | ||
| 
						 | 
					171ecd6884 | ||
| 
						 | 
					dca29f7e5a | ||
| 
						 | 
					318acc20bd | ||
| 
						 | 
					f433493d57 | ||
| 
						 | 
					19970fc132 | ||
| 
						 | 
					24394ca3c5 | ||
| 
						 | 
					10ff0b464a | ||
| 
						 | 
					9263d17609 | ||
| 
						 | 
					c1b75a13fd | ||
| 
						 | 
					a8ed60d48f | ||
| 
						 | 
					dc82a438d4 | ||
| 
						 | 
					cc54bdcbe7 | ||
| 
						 | 
					ae4bbc8baa | ||
| 
						 | 
					ad98499da0 | ||
| 
						 | 
					db60f355b2 | ||
| 
						 | 
					eb91d8b298 | ||
| 
						 | 
					b8312be4b7 | ||
| 
						 | 
					326a8e3404 | ||
| 
						 | 
					f017e13ac1 | ||
| 
						 | 
					67a5fe353e | ||
| 
						 | 
					51d49d7ff3 | ||
| 
						 | 
					d42b820b36 | ||
| 
						 | 
					07d32776d3 | ||
| 
						 | 
					ef027e81b5 | ||
| 
						 | 
					a75e4b495d | ||
| 
						 | 
					fba5e212e8 | ||
| 
						 | 
					62f44fb052 | ||
| 
						 | 
					6b9254047c | ||
| 
						 | 
					decfea5dc9 | ||
| 
						 | 
					eacded6848 | ||
| 
						 | 
					279ca72c64 | ||
| 
						 | 
					b8fc9383ca | ||
| 
						 | 
					bec58ac59f | ||
| 
						 | 
					83d7126820 | ||
| 
						 | 
					f0e9c6d794 | ||
| 
						 | 
					0e61051fc6 | ||
| 
						 | 
					480ba77ebe | ||
| 
						 | 
					16f27c13bb | ||
| 
						 | 
					afe5c50d66 | ||
| 
						 | 
					72ea859ebb | ||
| 
						 | 
					8edf3834c4 | ||
| 
						 | 
					e595014fcd | ||
| 
						 | 
					8bebf7e569 | ||
| 
						 | 
					c825ec06e2 | ||
| 
						 | 
					8c75f273fb | ||
| 
						 | 
					0ba776c129 | ||
| 
						 | 
					2bbbd03554 | ||
| 
						 | 
					0a5d0487b1 | ||
| 
						 | 
					583cd2dd3b | ||
| 
						 | 
					e1f7fc1ecb | ||
| 
						 | 
					961a55cbe5 | ||
| 
						 | 
					cdf9bad903 | ||
| 
						 | 
					6769fa2f83 | ||
| 
						 | 
					3b7ea88b73 | ||
| 
						 | 
					59310c095d | ||
| 
						 | 
					c47f0c12fe | ||
| 
						 | 
					ca71a40485 | ||
| 
						 | 
					d4e8f376c1 | ||
| 
						 | 
					14c6ea1e6b | ||
| 
						 | 
					d6e4d8fbd6 | ||
| 
						 | 
					460bda62d5 | ||
| 
						 | 
					d2702ab673 | ||
| 
						 | 
					e2581f42f5 | ||
| 
						 | 
					f0fcfc159f | ||
| 
						 | 
					538c5b60c9 | ||
| 
						 | 
					2fabb7bbb2 | ||
| 
						 | 
					e7c34a9c94 | ||
| 
						 | 
					618f9fce5a | ||
| 
						 | 
					95dbc9f678 | ||
| 
						 | 
					aa87bc5c51 | ||
| 
						 | 
					815de531ed | ||
| 
						 | 
					cf2b026dc4 | ||
| 
						 | 
					9ce46aefba | ||
| 
						 | 
					98b2db7818 | ||
| 
						 | 
					0229851bf9 | ||
| 
						 | 
					9e15114fe8 | ||
| 
						 | 
					7f66a76bb0 | ||
| 
						 | 
					e9cc8392bb | ||
| 
						 | 
					d0b89ce74f | ||
| 
						 | 
					f537c81db7 | ||
| 
						 | 
					03d3edfff6 | ||
| 
						 | 
					447b4c5e5c | ||
| 
						 | 
					cb143209ae | ||
| 
						 | 
					9c24fe73b5 | ||
| 
						 | 
					19ae85424b | ||
| 
						 | 
					22fad99552 | ||
| 
						 | 
					8144bbef74 | ||
| 
						 | 
					aad6da0ae8 | ||
| 
						 | 
					c5f8162a22 | ||
| 
						 | 
					f0f30224b5 | ||
| 
						 | 
					d0d888e356 | ||
| 
						 | 
					2c64122224 | ||
| 
						 | 
					3b2eee96a9 | ||
| 
						 | 
					465aacbf9b | ||
| 
						 | 
					d1a2a66170 | ||
| 
						 | 
					4c05fd72bb | ||
| 
						 | 
					f04fe760e3 | ||
| 
						 | 
					834d19bcc6 | ||
| 
						 | 
					6808c4642c | ||
| 
						 | 
					d0ce307f94 | ||
| 
						 | 
					f3740e9ded | ||
| 
						 | 
					b485bc9445 | ||
| 
						 | 
					2d14c1bb26 | ||
| 
						 | 
					1a442d6e69 | ||
| 
						 | 
					2386543e5c | ||
| 
						 | 
					58e220e82d | ||
| 
						 | 
					24bea6e4d2 | ||
| 
						 | 
					43497ad8d1 | ||
| 
						 | 
					f22b61fe4c | ||
| 
						 | 
					5b08f4cd19 | ||
| 
						 | 
					1589f8d24e | ||
| 
						 | 
					7d1db72cf5 | ||
| 
						 | 
					53a8f66414 | ||
| 
						 | 
					36cb6cc589 | ||
| 
						 | 
					f3a4aece46 | ||
| 
						 | 
					580a6a869a | ||
| 
						 | 
					008eaac493 | ||
| 
						 | 
					b450623bb4 | ||
| 
						 | 
					8ac2ecb673 | ||
| 
						 | 
					0a10a56ae3 | ||
| 
						 | 
					9378ba9208 | ||
| 
						 | 
					0c586e324b | ||
| 
						 | 
					91c4a64284 | ||
| 
						 | 
					c599e98d9d | ||
| 
						 | 
					d2cd6706c9 | ||
| 
						 | 
					e8ed10dde8 | ||
| 
						 | 
					5fe0b79802 | ||
| 
						 | 
					34a6722a68 | ||
| 
						 | 
					5b0d769c63 | ||
| 
						 | 
					718401a28b | ||
| 
						 | 
					3112cd57f6 | ||
| 
						 | 
					410fc777a7 | ||
| 
						 | 
					8eed99e732 | ||
| 
						 | 
					663b1d4171 | ||
| 
						 | 
					c3067ca12d | ||
| 
						 | 
					4561ca3760 | ||
| 
						 | 
					698cce58ce | ||
| 
						 | 
					339b79f786 | ||
| 
						 | 
					4f98f778f0 | ||
| 
						 | 
					8479b33a47 | ||
| 
						 | 
					78844d7bd5 | ||
| 
						 | 
					64e4a271e1 | ||
| 
						 | 
					5fb8c3575b | ||
| 
						 | 
					a6b8bcecae | ||
| 
						 | 
					bc9c820820 | ||
| 
						 | 
					ee9207a7f4 | ||
| 
						 | 
					a34e215202 | ||
| 
						 | 
					b4e53dbb8e | ||
| 
						 | 
					b5e8d82bfa | ||
| 
						 | 
					5d9000bb33 | ||
| 
						 | 
					ccb065ef0f | ||
| 
						 | 
					883fad806b | ||
| 
						 | 
					feacd1b816 | ||
| 
						 | 
					094e7a0d1c | ||
| 
						 | 
					72636c5059 | ||
| 
						 | 
					291cfc80c6 | ||
| 
						 | 
					ae1dfafc9d | ||
| 
						 | 
					6caa583c35 | ||
| 
						 | 
					2057167576 | ||
| 
						 | 
					1c9e67fc32 | 
@@ -1,7 +0,0 @@
 | 
			
		||||
version = 1
 | 
			
		||||
 | 
			
		||||
[[analyzers]]
 | 
			
		||||
name = "javascript"
 | 
			
		||||
 | 
			
		||||
  [analyzers.meta]
 | 
			
		||||
  environment = ["nodejs"]
 | 
			
		||||
@@ -1,16 +1,19 @@
 | 
			
		||||
node_modules
 | 
			
		||||
Dockerfile*
 | 
			
		||||
docker-compose*
 | 
			
		||||
.dockerignore
 | 
			
		||||
.git
 | 
			
		||||
.gitignore
 | 
			
		||||
README.md
 | 
			
		||||
LICENSE
 | 
			
		||||
.vscode
 | 
			
		||||
Makefile
 | 
			
		||||
helm-charts
 | 
			
		||||
.env
 | 
			
		||||
.editorconfig
 | 
			
		||||
.idea
 | 
			
		||||
coverage*
 | 
			
		||||
data
 | 
			
		||||
.dockerignore
 | 
			
		||||
.editorconfig
 | 
			
		||||
.env
 | 
			
		||||
.git
 | 
			
		||||
.idea
 | 
			
		||||
.vscode
 | 
			
		||||
CHANGELOG.md
 | 
			
		||||
coverage*
 | 
			
		||||
data
 | 
			
		||||
docker-compose*
 | 
			
		||||
Dockerfile*
 | 
			
		||||
eslint.config.js
 | 
			
		||||
helm-charts
 | 
			
		||||
LICENSE
 | 
			
		||||
Makefile
 | 
			
		||||
node_modules
 | 
			
		||||
prettier.config.js
 | 
			
		||||
README.md
 | 
			
		||||
renovate.json
 | 
			
		||||
							
								
								
									
										15
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,15 @@
 | 
			
		||||
# These are supported funding model platforms
 | 
			
		||||
 | 
			
		||||
github: [C4illin] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
 | 
			
		||||
patreon: # Replace with a single Patreon username
 | 
			
		||||
open_collective: # Replace with a single Open Collective username
 | 
			
		||||
ko_fi: # Replace with a single Ko-fi username
 | 
			
		||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 | 
			
		||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 | 
			
		||||
liberapay: # Replace with a single Liberapay username
 | 
			
		||||
issuehunt: # Replace with a single IssueHunt username
 | 
			
		||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
 | 
			
		||||
polar: # Replace with a single Polar username
 | 
			
		||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
 | 
			
		||||
thanks_dev: # Replace with a single thanks.dev username
 | 
			
		||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
 | 
			
		||||
							
								
								
									
										149
									
								
								.github/workflows/docker-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,69 +1,80 @@
 | 
			
		||||
name: Docker
 | 
			
		||||
 | 
			
		||||
# This workflow uses actions that are not certified by GitHub.
 | 
			
		||||
# They are provided by a third-party and are governed by
 | 
			
		||||
# separate terms of service, privacy policy, and support
 | 
			
		||||
# documentation.
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ "main" ]
 | 
			
		||||
    # Publish semver tags as releases.
 | 
			
		||||
    tags: [ 'v*.*.*' ]
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches: [ "main" ]
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  # Use docker.io for Docker Hub if empty
 | 
			
		||||
  REGISTRY: ghcr.io
 | 
			
		||||
  # github.repository as <account>/<repo>
 | 
			
		||||
  IMAGE_NAME: ${{ github.repository }}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      packages: write
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout repository
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
 | 
			
		||||
      # Workaround: https://github.com/docker/build-push-action/issues/461
 | 
			
		||||
      - name: Setup Docker buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3
 | 
			
		||||
 | 
			
		||||
      # Login against a Docker registry except on PR
 | 
			
		||||
      # https://github.com/docker/login-action
 | 
			
		||||
      - name: Log into registry ${{ env.REGISTRY }}
 | 
			
		||||
        if: github.event_name != 'pull_request'
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      # Extract metadata (tags, labels) for Docker
 | 
			
		||||
      # https://github.com/docker/metadata-action
 | 
			
		||||
      - name: Extract Docker metadata
 | 
			
		||||
        id: meta
 | 
			
		||||
        uses: docker/metadata-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
 | 
			
		||||
 | 
			
		||||
      # Build and push Docker image with Buildx (don't push on PR)
 | 
			
		||||
      # https://github.com/docker/build-push-action
 | 
			
		||||
      - name: Build and push Docker image
 | 
			
		||||
        id: build-and-push
 | 
			
		||||
        uses: docker/build-push-action@v6
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          push: ${{ github.event_name != 'pull_request' }}
 | 
			
		||||
          tags: ${{ steps.meta.outputs.tags }}
 | 
			
		||||
          labels: ${{ steps.meta.outputs.labels }}
 | 
			
		||||
          cache-from: type=gha
 | 
			
		||||
          cache-to: type=gha,mode=max
 | 
			
		||||
name: Docker
 | 
			
		||||
 | 
			
		||||
# This workflow uses actions that are not certified by GitHub.
 | 
			
		||||
# They are provided by a third-party and are governed by
 | 
			
		||||
# separate terms of service, privacy policy, and support
 | 
			
		||||
# documentation.
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ "main" ]
 | 
			
		||||
    # Publish semver tags as releases.
 | 
			
		||||
    tags: [ 'v*.*.*' ]
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches: [ "main" ]
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  # Use docker.io for Docker Hub if empty
 | 
			
		||||
  REGISTRY: ghcr.io
 | 
			
		||||
  # github.repository as <account>/<repo>
 | 
			
		||||
  IMAGE_NAME: ${{ github.repository }}
 | 
			
		||||
  DOCKERHUB_USERNAME: c4illin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      packages: write
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout repository
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
 | 
			
		||||
      # Workaround: https://github.com/docker/build-push-action/issues/461
 | 
			
		||||
      - name: Setup Docker buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3
 | 
			
		||||
 | 
			
		||||
      # Login against a Docker registry except on PR
 | 
			
		||||
      # https://github.com/docker/login-action
 | 
			
		||||
      - name: Log into registry ${{ env.REGISTRY }}
 | 
			
		||||
        if: github.event_name != 'pull_request'
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ${{ env.REGISTRY }}
 | 
			
		||||
          username: ${{ github.actor }}
 | 
			
		||||
          password: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      
 | 
			
		||||
      - name: Login to Docker Hub
 | 
			
		||||
        if: github.event_name != 'pull_request'
 | 
			
		||||
        uses: docker/login-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ env.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      # Extract metadata (tags, labels) for Docker
 | 
			
		||||
      # https://github.com/docker/metadata-action
 | 
			
		||||
      - name: Extract Docker metadata
 | 
			
		||||
        id: meta
 | 
			
		||||
        uses: docker/metadata-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          images: |
 | 
			
		||||
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
 | 
			
		||||
            ${{ env.IMAGE_NAME }}
 | 
			
		||||
 | 
			
		||||
      # Build and push Docker image with Buildx (don't push on PR)
 | 
			
		||||
      # https://github.com/docker/build-push-action
 | 
			
		||||
      - name: Build and push Docker image
 | 
			
		||||
        id: build-and-push
 | 
			
		||||
        uses: docker/build-push-action@v6
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          push: ${{ github.event_name != 'pull_request' }}
 | 
			
		||||
          tags: ${{ steps.meta.outputs.tags }}
 | 
			
		||||
          labels: ${{ steps.meta.outputs.labels }}
 | 
			
		||||
          cache-from: type=gha
 | 
			
		||||
          cache-to: type=gha,mode=max
 | 
			
		||||
      
 | 
			
		||||
							
								
								
									
										27
									
								
								.github/workflows/dockerhub-description.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,27 @@
 | 
			
		||||
name: Update Docker Hub Description
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  IMAGE_NAME: ${{ github.repository }}
 | 
			
		||||
  DOCKERHUB_USERNAME: c4illin
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
    paths:
 | 
			
		||||
      - README.md
 | 
			
		||||
      - .github/workflows/dockerhub-description.yml
 | 
			
		||||
jobs:
 | 
			
		||||
  dockerHubDescription:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
    - 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
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -48,4 +48,4 @@ package-lock.json
 | 
			
		||||
/data
 | 
			
		||||
/Bruno
 | 
			
		||||
/tsconfig.tsbuildinfo
 | 
			
		||||
/src/public/generated.css
 | 
			
		||||
/public/generated.css
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										119
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,5 +1,124 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
## [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))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* 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))
 | 
			
		||||
 | 
			
		||||
## [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))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 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)
 | 
			
		||||
 | 
			
		||||
## [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))
 | 
			
		||||
 | 
			
		||||
## [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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 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))
 | 
			
		||||
 | 
			
		||||
## [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)
 | 
			
		||||
 | 
			
		||||
## [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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 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))
 | 
			
		||||
 | 
			
		||||
## [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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 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)
 | 
			
		||||
 | 
			
		||||
## [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)
 | 
			
		||||
 | 
			
		||||
## [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))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 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))
 | 
			
		||||
 | 
			
		||||
## [0.7.0](https://github.com/C4illin/ConvertX/compare/v0.6.0...v0.7.0) (2024-09-26)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						@@ -1,4 +1,4 @@
 | 
			
		||||
FROM oven/bun:1.1.29-alpine AS base
 | 
			
		||||
FROM oven/bun:1.2.2-alpine AS base
 | 
			
		||||
LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX"
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
@@ -6,12 +6,12 @@ WORKDIR /app
 | 
			
		||||
# this will cache them and speed up future builds
 | 
			
		||||
FROM base AS install
 | 
			
		||||
RUN mkdir -p /temp/dev
 | 
			
		||||
COPY package.json bun.lockb /temp/dev/
 | 
			
		||||
COPY package.json bun.lock /temp/dev/
 | 
			
		||||
RUN cd /temp/dev && bun install --frozen-lockfile
 | 
			
		||||
 | 
			
		||||
# install with --production (exclude devDependencies)
 | 
			
		||||
RUN mkdir -p /temp/prod
 | 
			
		||||
COPY package.json bun.lockb /temp/prod/
 | 
			
		||||
COPY package.json bun.lock /temp/prod/
 | 
			
		||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
 | 
			
		||||
 | 
			
		||||
FROM base AS builder
 | 
			
		||||
@@ -20,22 +20,18 @@ ENV PATH=/root/.cargo/bin:$PATH
 | 
			
		||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
 | 
			
		||||
RUN cargo install resvg
 | 
			
		||||
 | 
			
		||||
# copy node_modules from temp directory
 | 
			
		||||
# then copy all (non-ignored) project files into the image
 | 
			
		||||
FROM base AS prerelease
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY --from=install /temp/dev/node_modules node_modules
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
# # [optional] tests & build
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
# RUN bun test
 | 
			
		||||
# ENV NODE_ENV=production
 | 
			
		||||
RUN bun run build
 | 
			
		||||
 | 
			
		||||
# copy production dependencies and source code into final image
 | 
			
		||||
FROM base AS release
 | 
			
		||||
LABEL maintainer="Emrik Östling (C4illin)"
 | 
			
		||||
LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats."
 | 
			
		||||
LABEL repo="https://github.com/C4illin/ConvertX"
 | 
			
		||||
 | 
			
		||||
RUN apk --no-cache add libheif-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
 | 
			
		||||
 | 
			
		||||
# install additional dependencies
 | 
			
		||||
RUN apk --no-cache add  \
 | 
			
		||||
@@ -49,17 +45,25 @@ RUN apk --no-cache add  \
 | 
			
		||||
  vips-tools \
 | 
			
		||||
  vips-poppler \
 | 
			
		||||
  vips-jxl \
 | 
			
		||||
  vips-heif \
 | 
			
		||||
  vips-magick \
 | 
			
		||||
  libjxl-tools \
 | 
			
		||||
  assimp
 | 
			
		||||
  assimp \
 | 
			
		||||
  inkscape \
 | 
			
		||||
  poppler-utils \
 | 
			
		||||
  gcompat \
 | 
			
		||||
  libva-utils \
 | 
			
		||||
  py3-numpy \
 | 
			
		||||
  potrace
 | 
			
		||||
 | 
			
		||||
# RUN apk --no-cache add calibre@testing --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main/
 | 
			
		||||
 | 
			
		||||
# this might be needed for some latex use cases, will add it if needed.
 | 
			
		||||
#   texmf-dist-fontsextra \
 | 
			
		||||
 | 
			
		||||
COPY --from=install /temp/prod/node_modules node_modules
 | 
			
		||||
COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg
 | 
			
		||||
COPY --from=prerelease /app/src/public/generated.css /app/src/public/
 | 
			
		||||
# COPY --from=prerelease /app/src/index.tsx /app/src/
 | 
			
		||||
# COPY --from=prerelease /app/package.json .
 | 
			
		||||
COPY --from=prerelease /app/public/generated.css /app/public/
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
EXPOSE 3000/tcp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										268
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,111 +1,157 @@
 | 
			
		||||

 | 
			
		||||
# ConvertX
 | 
			
		||||
[](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml)
 | 
			
		||||
[](https://github.com/C4illin/ConvertX/pkgs/container/convertx)
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
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          |
 | 
			
		||||
| [Assimp](https://github.com/assimp/assimp)                                   | 3D Assets     | 70            | 24          |
 | 
			
		||||
| [XeLaTeX](https://tug.org/xetex/)                                            | LaTeX         | 1             | 1           |
 | 
			
		||||
| [Pandoc](https://pandoc.org/)                                                | Documents     | 43            | 65          |
 | 
			
		||||
| [GraphicsMagick](http://www.graphicsmagick.org/)                             | Images        | 166           | 133         |
 | 
			
		||||
| [FFmpeg](https://ffmpeg.org/)                                                | Video         | ~473          | ~280        |
 | 
			
		||||
 | 
			
		||||
<!-- many ffmpeg fileformats are duplicates -->
 | 
			
		||||
 | 
			
		||||
Any missing converter? Open an issue or pull request!
 | 
			
		||||
 | 
			
		||||
## Deployment
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
# docker-compose.yml
 | 
			
		||||
services:
 | 
			
		||||
  convertx: 
 | 
			
		||||
    image: ghcr.io/c4illin/convertx
 | 
			
		||||
    container_name: convertx
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    ports:
 | 
			
		||||
      - "3000:3000"
 | 
			
		||||
    environment: # Defaults are listed below. All are optional.
 | 
			
		||||
      - ACCOUNT_REGISTRATION=false # 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=24 # checks every n hours for files older then n hours and deletes them, set to 0 to disable
 | 
			
		||||
    volumes:
 | 
			
		||||
      - convertx:/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.
 | 
			
		||||
 | 
			
		||||
### Tutorial
 | 
			
		||||
 | 
			
		||||
Tutorial in french: https://belginux.com/installer-convertx-avec-docker/
 | 
			
		||||
 | 
			
		||||
## Screenshots
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
 | 
			
		||||
## Todo
 | 
			
		||||
- [x] Add messages for errors in converters
 | 
			
		||||
- [x] Add searchable list of formats
 | 
			
		||||
- [ ] Add options for converters
 | 
			
		||||
- [ ] Divide index.tsx into smaller components
 | 
			
		||||
- [ ] Add tests
 | 
			
		||||
- [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible.
 | 
			
		||||
- [ ] Make errors logs visible from the web ui
 | 
			
		||||
- [ ] Add more converters:
 | 
			
		||||
  - [ ] [deark](https://github.com/jsummers/deark)
 | 
			
		||||
  - [ ] LibreOffice
 | 
			
		||||
  - [ ] [dvisvgm](https://github.com/mgieseki/dvisvgm)
 | 
			
		||||
 | 
			
		||||
## Contributors
 | 
			
		||||
 | 
			
		||||
<a href="https://github.com/C4illin/ConvertX/graphs/contributors">
 | 
			
		||||
  <img src="https://contrib.rocks/image?repo=C4illin/ConvertX" />
 | 
			
		||||
</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
 | 
			
		||||
 | 
			
		||||
[](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml)
 | 
			
		||||
[](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX)
 | 
			
		||||
[](https://hub.docker.com/r/c4illin/convertx)
 | 
			
		||||
[](https://github.com/C4illin/ConvertX/pkgs/container/convertx)
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
<!--  -->
 | 
			
		||||
 | 
			
		||||
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           |
 | 
			
		||||
| [Pandoc](https://pandoc.org/)                                                | Documents     | 43            | 65          |
 | 
			
		||||
| [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          |
 | 
			
		||||
 | 
			
		||||
<!-- | [Calibre](https://calibre-ebook.com/)                                        | E-books       | 26            | 19          | -->
 | 
			
		||||
 | 
			
		||||
<!-- 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 |
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
<!-- 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
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
 | 
			
		||||
## Todo
 | 
			
		||||
 | 
			
		||||
- [x] Add messages for errors in converters
 | 
			
		||||
- [x] Add searchable list of formats
 | 
			
		||||
- [ ] Add options for converters
 | 
			
		||||
- [ ] Divide index.tsx into smaller components
 | 
			
		||||
- [ ] Add tests
 | 
			
		||||
- [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible.
 | 
			
		||||
- [ ] Make errors logs visible from the web ui
 | 
			
		||||
- [ ] Add more converters:
 | 
			
		||||
  - [ ] [deark](https://github.com/jsummers/deark)
 | 
			
		||||
  - [ ] LibreOffice
 | 
			
		||||
  - [ ] [dvisvgm](https://github.com/mgieseki/dvisvgm)
 | 
			
		||||
 | 
			
		||||
## 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>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								SECURITY.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,9 @@
 | 
			
		||||
# Security Policy
 | 
			
		||||
 | 
			
		||||
## Supported Versions
 | 
			
		||||
 | 
			
		||||
Only the latest release is supported
 | 
			
		||||
 | 
			
		||||
## Reporting a Vulnerability
 | 
			
		||||
 | 
			
		||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/C4illin/ConvertX/security/advisories/new) tab.
 | 
			
		||||
							
								
								
									
										33
									
								
								compose.yaml
									
									
									
									
									
								
							
							
						
						@@ -1,15 +1,18 @@
 | 
			
		||||
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=true # setting this to true is unsafe, only set this to true locally
 | 
			
		||||
      - ALLOW_UNAUTHENTICATED=true # 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
 | 
			
		||||
    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=true # setting this to true is unsafe, only set this to true locally
 | 
			
		||||
      - ALLOW_UNAUTHENTICATED=true # 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
 | 
			
		||||
    ports:
 | 
			
		||||
      - 3000:3000
 | 
			
		||||
 
 | 
			
		||||
@@ -1,55 +0,0 @@
 | 
			
		||||
import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
 | 
			
		||||
import { fixupPluginRules } from "@eslint/compat";
 | 
			
		||||
import js from "@eslint/js";
 | 
			
		||||
import deprecationPlugin from "eslint-plugin-deprecation";
 | 
			
		||||
import importPlugin from "eslint-plugin-import";
 | 
			
		||||
import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
 | 
			
		||||
import tailwind from "eslint-plugin-tailwindcss";
 | 
			
		||||
import globals from "globals";
 | 
			
		||||
import tseslint from "typescript-eslint";
 | 
			
		||||
 | 
			
		||||
export default tseslint.config(
 | 
			
		||||
  js.configs.recommended,
 | 
			
		||||
  importPlugin.flatConfigs.recommended,
 | 
			
		||||
  comments.recommended,
 | 
			
		||||
  ...tseslint.configs.recommended,
 | 
			
		||||
  ...tailwind.configs["flat/recommended"],
 | 
			
		||||
  {
 | 
			
		||||
    plugins: {
 | 
			
		||||
      "@typescript-eslint": tseslint.plugin,
 | 
			
		||||
      deprecation: fixupPluginRules(deprecationPlugin),
 | 
			
		||||
      import: fixupPluginRules(importPlugin),
 | 
			
		||||
      "simple-import-sort": simpleImportSortPlugin,
 | 
			
		||||
    },
 | 
			
		||||
    ignores: ["**/node_modules/**", "**/public/**"],
 | 
			
		||||
    languageOptions: {
 | 
			
		||||
      parserOptions: {
 | 
			
		||||
        projectService: true,
 | 
			
		||||
        tsconfigRootDir: import.meta.dirname,
 | 
			
		||||
        ecmaVersion: "latest",
 | 
			
		||||
        sourceType: "module",
 | 
			
		||||
        project: ["./tsconfig.json"],
 | 
			
		||||
      },
 | 
			
		||||
      globals: {
 | 
			
		||||
        ...globals.node,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    files: ["**/*.{js,mjs,cjs}"],
 | 
			
		||||
    rules: {
 | 
			
		||||
      "tailwindcss/no-custom-classname": [
 | 
			
		||||
        "error",
 | 
			
		||||
        {
 | 
			
		||||
          config: "./tailwind.config.js",
 | 
			
		||||
          whitelist: [
 | 
			
		||||
            "select_container",
 | 
			
		||||
            "convert_to_popup",
 | 
			
		||||
            "convert_to_group",
 | 
			
		||||
            "target",
 | 
			
		||||
            "convert_to_target",
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      "import/no-named-as-default": "off",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										57
									
								
								eslint.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,57 @@
 | 
			
		||||
import js from "@eslint/js";
 | 
			
		||||
import eslintParserTypeScript from "@typescript-eslint/parser";
 | 
			
		||||
import type { Linter } from "eslint";
 | 
			
		||||
import eslintPluginReadableTailwind from "eslint-plugin-readable-tailwind";
 | 
			
		||||
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,
 | 
			
		||||
      "readable-tailwind": eslintPluginReadableTailwind,
 | 
			
		||||
    },
 | 
			
		||||
    ignores: ["**/node_modules/**"],
 | 
			
		||||
    languageOptions: {
 | 
			
		||||
      parser: eslintParserTypeScript,
 | 
			
		||||
      parserOptions: {
 | 
			
		||||
        project: true,
 | 
			
		||||
        ecmaFeatures: {
 | 
			
		||||
          jsx: true,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      globals: {
 | 
			
		||||
        ...globals.node,
 | 
			
		||||
        ...globals.browser,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    files: ["**/*.{js,mjs,cjs,jsx,tsx,ts}"],
 | 
			
		||||
    rules: {
 | 
			
		||||
      ...eslintPluginReadableTailwind.configs.warning.rules,
 | 
			
		||||
      // "tailwindcss/classnames-order": "off",
 | 
			
		||||
      "readable-tailwind/multiline": [
 | 
			
		||||
        "warn",
 | 
			
		||||
        {
 | 
			
		||||
          group: "newLine",
 | 
			
		||||
          printWidth: 100,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      // "tailwindcss/no-custom-classname": [
 | 
			
		||||
      //   "warn",
 | 
			
		||||
      //   {
 | 
			
		||||
      //     whitelist: [
 | 
			
		||||
      //       "select_container",
 | 
			
		||||
      //       "convert_to_popup",
 | 
			
		||||
      //       "convert_to_group",
 | 
			
		||||
      //       "target",
 | 
			
		||||
      //       "convert_to_target",
 | 
			
		||||
      //     ],
 | 
			
		||||
      //   },
 | 
			
		||||
      // ],
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
] as Linter.Config[];
 | 
			
		||||
							
								
								
									
										61
									
								
								flake.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,61 @@
 | 
			
		||||
{
 | 
			
		||||
  "nodes": {
 | 
			
		||||
    "flake-utils": {
 | 
			
		||||
      "inputs": {
 | 
			
		||||
        "systems": "systems"
 | 
			
		||||
      },
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1731533236,
 | 
			
		||||
        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
 | 
			
		||||
        "owner": "numtide",
 | 
			
		||||
        "repo": "flake-utils",
 | 
			
		||||
        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "numtide",
 | 
			
		||||
        "repo": "flake-utils",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "nixpkgs": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1747179050,
 | 
			
		||||
        "narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
 | 
			
		||||
        "owner": "NixOS",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "NixOS",
 | 
			
		||||
        "ref": "nixos-unstable",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "root": {
 | 
			
		||||
      "inputs": {
 | 
			
		||||
        "flake-utils": "flake-utils",
 | 
			
		||||
        "nixpkgs": "nixpkgs"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "systems": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1681028828,
 | 
			
		||||
        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
 | 
			
		||||
        "owner": "nix-systems",
 | 
			
		||||
        "repo": "default",
 | 
			
		||||
        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
        "owner": "nix-systems",
 | 
			
		||||
        "repo": "default",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "root": "root",
 | 
			
		||||
  "version": 7
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								flake.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,60 @@
 | 
			
		||||
{
 | 
			
		||||
  description = "ConvertX";
 | 
			
		||||
 | 
			
		||||
  inputs = {
 | 
			
		||||
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
 | 
			
		||||
    flake-utils.url = "github:numtide/flake-utils";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  outputs = { self, nixpkgs, flake-utils, ... }:
 | 
			
		||||
    flake-utils.lib.eachDefaultSystem (system:
 | 
			
		||||
      let pkgs = import nixpkgs { inherit system; };
 | 
			
		||||
      appSrc = ./.;
 | 
			
		||||
 | 
			
		||||
      app = pkgs.dockerTools.buildLayeredImage {
 | 
			
		||||
        name = "convertx";
 | 
			
		||||
        tag = "latest";
 | 
			
		||||
 | 
			
		||||
        contents = [
 | 
			
		||||
          pkgs.bun
 | 
			
		||||
          pkgs.resvg
 | 
			
		||||
          pkgs.ffmpeg
 | 
			
		||||
          pkgs.graphicsmagick
 | 
			
		||||
          pkgs.ghostscript
 | 
			
		||||
          pkgs.vips
 | 
			
		||||
          pkgs.pandoc
 | 
			
		||||
          pkgs.texlive.combined.scheme-full
 | 
			
		||||
          pkgs.calibre
 | 
			
		||||
          pkgs.inkscape
 | 
			
		||||
          pkgs.poppler_utils
 | 
			
		||||
          pkgs.assimp
 | 
			
		||||
          pkgs.jxrlib
 | 
			
		||||
          pkgs.libheif
 | 
			
		||||
          pkgs.libjxl
 | 
			
		||||
          pkgs.python3Packages.numpy
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        config = {
 | 
			
		||||
          Env = [
 | 
			
		||||
            "NODE_ENV=production"
 | 
			
		||||
            "PATH=/bin:/usr/bin"
 | 
			
		||||
          ];
 | 
			
		||||
          WorkingDir = "/app";
 | 
			
		||||
          Cmd = [ "bun" "run" "./src/index.tsx" ];
 | 
			
		||||
          ExposedPorts = {
 | 
			
		||||
            "3000/tcp" = {};
 | 
			
		||||
          };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        extraCommands = ''
 | 
			
		||||
          export PATH=${pkgs.bun}/bin:$PATH
 | 
			
		||||
          mkdir -p /app
 | 
			
		||||
          cp -r ${./dist}/* /app/
 | 
			
		||||
        '';
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      in {
 | 
			
		||||
        packages.default = app;
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@@ -1,22 +1,24 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "convertx-frontend",
 | 
			
		||||
  "version": "0.7.0",
 | 
			
		||||
  "version": "0.13.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "bun run --watch src/index.tsx",
 | 
			
		||||
    "hot": "bun run --hot src/index.tsx",
 | 
			
		||||
    "format": "biome format --write ./src",
 | 
			
		||||
    "build": "postcss  ./src/main.css -o ./src/public/generated.css",
 | 
			
		||||
    "start": "bun run src/index.tsx",
 | 
			
		||||
    "format": "eslint --fix .",
 | 
			
		||||
    "build": "bunx @tailwindcss/cli -i ./src/main.css -o ./public/generated.css",
 | 
			
		||||
    "lint": "run-p 'lint:*'",
 | 
			
		||||
    "lint:tsc": "tsc --noEmit",
 | 
			
		||||
    "lint:knip": "knip",
 | 
			
		||||
    "lint:biome": "biome lint --error-on-warnings ./src"
 | 
			
		||||
    "lint:eslint": "eslint ."
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@elysiajs/cookie": "^0.8.0",
 | 
			
		||||
    "@elysiajs/html": "1.0.2",
 | 
			
		||||
    "@elysiajs/jwt": "^1.1.1",
 | 
			
		||||
    "@elysiajs/static": "1.0.3",
 | 
			
		||||
    "elysia": "^1.1.16"
 | 
			
		||||
    "@elysiajs/html": "^1.3.0",
 | 
			
		||||
    "@elysiajs/jwt": "^1.3.0",
 | 
			
		||||
    "@elysiajs/static": "^1.3.0",
 | 
			
		||||
    "@kitajs/html": "^4.2.9",
 | 
			
		||||
    "elysia": "^1.3.1",
 | 
			
		||||
    "sanitize-filename": "^1.6.3"
 | 
			
		||||
  },
 | 
			
		||||
  "module": "src/index.tsx",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
@@ -24,44 +26,30 @@
 | 
			
		||||
    "start": "bun run src/index.tsx"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@biomejs/biome": "1.9.2",
 | 
			
		||||
    "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0",
 | 
			
		||||
    "@eslint/compat": "^1.1.1",
 | 
			
		||||
    "@eslint/js": "^9.11.1",
 | 
			
		||||
    "@ianvs/prettier-plugin-sort-imports": "^4.3.1",
 | 
			
		||||
    "@kitajs/ts-html-plugin": "^4.1.0",
 | 
			
		||||
    "@picocss/pico": "^2.0.6",
 | 
			
		||||
    "@eslint/js": "^9.26.0",
 | 
			
		||||
    "@ianvs/prettier-plugin-sort-imports": "^4.4.1",
 | 
			
		||||
    "@kitajs/ts-html-plugin": "^4.1.1",
 | 
			
		||||
    "@tailwindcss/cli": "^4.1.6",
 | 
			
		||||
    "@tailwindcss/postcss": "^4.1.6",
 | 
			
		||||
    "@total-typescript/ts-reset": "^0.6.1",
 | 
			
		||||
    "@types/bun": "^1.1.10",
 | 
			
		||||
    "@types/eslint": "^9.6.1",
 | 
			
		||||
    "@types/bun": "^1.2.13",
 | 
			
		||||
    "@types/eslint-plugin-tailwindcss": "^3.17.0",
 | 
			
		||||
    "@types/eslint__js": "^8.42.3",
 | 
			
		||||
    "@types/node": "^22.6.1",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^8.7.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^8.7.0",
 | 
			
		||||
    "autoprefixer": "^10.4.20",
 | 
			
		||||
    "cssnano": "^7.0.6",
 | 
			
		||||
    "eslint": "^9.11.1",
 | 
			
		||||
    "eslint-config-prettier": "^9.1.0",
 | 
			
		||||
    "eslint-plugin-deprecation": "^3.0.0",
 | 
			
		||||
    "eslint-plugin-import": "^2.30.0",
 | 
			
		||||
    "eslint-plugin-isaacscript": "^4.0.0",
 | 
			
		||||
    "eslint-plugin-prettier": "^5.2.1",
 | 
			
		||||
    "@types/node": "^22.15.17",
 | 
			
		||||
    "autoprefixer": "^10.4.21",
 | 
			
		||||
    "cssnano": "^7.0.7",
 | 
			
		||||
    "eslint": "^9.26.0",
 | 
			
		||||
    "eslint-plugin-readable-tailwind": "^2.1.1",
 | 
			
		||||
    "eslint-plugin-simple-import-sort": "^12.1.1",
 | 
			
		||||
    "eslint-plugin-tailwindcss": "^3.17.4",
 | 
			
		||||
    "globals": "^15.9.0",
 | 
			
		||||
    "knip": "^5.30.5",
 | 
			
		||||
    "npm-run-all2": "^6.2.3",
 | 
			
		||||
    "postcss": "^8.4.47",
 | 
			
		||||
    "postcss-cli": "^11.0.0",
 | 
			
		||||
    "postcss-lightningcss": "^1.0.1",
 | 
			
		||||
    "prettier": "^3.3.3",
 | 
			
		||||
    "tailwind-scrollbar": "^3.1.0",
 | 
			
		||||
    "tailwindcss": "^3.4.13",
 | 
			
		||||
    "typescript": "^5.6.2",
 | 
			
		||||
    "typescript-eslint": "^8.7.0"
 | 
			
		||||
  },
 | 
			
		||||
  "trustedDependencies": [
 | 
			
		||||
    "@biomejs/biome"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
    "eslint-plugin-tailwindcss": "4.0.0-alpha.0",
 | 
			
		||||
    "globals": "^16.1.0",
 | 
			
		||||
    "knip": "^5.55.1",
 | 
			
		||||
    "npm-run-all2": "^8.0.1",
 | 
			
		||||
    "postcss": "^8.5.3",
 | 
			
		||||
    "postcss-cli": "^11.0.1",
 | 
			
		||||
    "prettier": "^3.5.3",
 | 
			
		||||
    "tailwind-scrollbar": "^4.0.2",
 | 
			
		||||
    "tailwindcss": "^4.1.6",
 | 
			
		||||
    "typescript": "^5.8.3",
 | 
			
		||||
    "typescript-eslint": "^8.32.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
module.exports = {
 | 
			
		||||
  plugins: {
 | 
			
		||||
    tailwindcss: {},
 | 
			
		||||
    autoprefixer: {},
 | 
			
		||||
    // eslint-disable-next-line no-undef
 | 
			
		||||
    ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								postcss.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,5 @@
 | 
			
		||||
export default {
 | 
			
		||||
  plugins: {
 | 
			
		||||
    "@tailwindcss/postcss": {},
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB  | 
| 
		 Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB  | 
| 
		 Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB  | 
| 
		 Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 476 B  | 
| 
		 Before Width: | Height: | Size: 960 B After Width: | Height: | Size: 960 B  | 
| 
		 Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB  | 
@@ -1,3 +1,5 @@
 | 
			
		||||
const webroot = document.querySelector("meta[name='webroot']").content;
 | 
			
		||||
 | 
			
		||||
window.downloadAll = function () {
 | 
			
		||||
  // Get all download links
 | 
			
		||||
  const downloadLinks = document.querySelectorAll("a[download]");
 | 
			
		||||
@@ -18,7 +20,7 @@ let progressElem = document.querySelector("progress");
 | 
			
		||||
const refreshData = () => {
 | 
			
		||||
  // console.log("Refreshing data...", progressElem.value, progressElem.max);
 | 
			
		||||
  if (progressElem.value !== progressElem.max) {
 | 
			
		||||
    fetch(`/progress/${jobId}`, {
 | 
			
		||||
    fetch(`${webroot}/progress/${jobId}`, {
 | 
			
		||||
      method: "POST",
 | 
			
		||||
    })
 | 
			
		||||
      .then((res) => res.text())
 | 
			
		||||
@@ -1,17 +1,74 @@
 | 
			
		||||
// Select the file input element
 | 
			
		||||
const webroot = document.querySelector("meta[name='webroot']").content;
 | 
			
		||||
const fileInput = document.querySelector('input[type="file"]');
 | 
			
		||||
const dropZone = document.getElementById("dropzone");
 | 
			
		||||
const convertButton = document.querySelector("input[type='submit']");
 | 
			
		||||
const fileNames = [];
 | 
			
		||||
let fileType;
 | 
			
		||||
let pendingFiles = 0;
 | 
			
		||||
let formatSelected = false;
 | 
			
		||||
 | 
			
		||||
dropZone.addEventListener("dragover", (e) => {
 | 
			
		||||
  e.preventDefault();
 | 
			
		||||
  dropZone.classList.add("dragover");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
dropZone.addEventListener("dragleave", (e) => {
 | 
			
		||||
dropZone.addEventListener("dragleave", () => {
 | 
			
		||||
  dropZone.classList.remove("dragover");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
dropZone.addEventListener("drop", (e) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    dropZone.classList.remove("dragover");
 | 
			
		||||
  
 | 
			
		||||
    const files = e.dataTransfer.files;
 | 
			
		||||
  
 | 
			
		||||
    if (files.length === 0) {
 | 
			
		||||
      console.warn("No files dropped — likely a URL or unsupported source.");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    for (const file of files) {
 | 
			
		||||
      console.log("Handling dropped file:", file.name);
 | 
			
		||||
      handleFile(file);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Extracted handleFile function for reusability in drag-and-drop and file input
 | 
			
		||||
function handleFile(file) {
 | 
			
		||||
  const fileList = document.querySelector("#file-list");
 | 
			
		||||
 | 
			
		||||
  const row = document.createElement("tr");
 | 
			
		||||
  row.innerHTML = `
 | 
			
		||||
    <td>${file.name}</td>
 | 
			
		||||
    <td><progress max="100"></progress></td>
 | 
			
		||||
    <td>${(file.size / 1024).toFixed(2)} kB</td>
 | 
			
		||||
    <td><a onclick="deleteRow(this)">Remove</a></td>
 | 
			
		||||
  `;
 | 
			
		||||
 | 
			
		||||
  if (!fileType) {
 | 
			
		||||
    fileType = file.name.split(".").pop();
 | 
			
		||||
    fileInput.setAttribute("accept", `.${fileType}`);
 | 
			
		||||
    setTitle();
 | 
			
		||||
 | 
			
		||||
    fetch(`${webroot}/conversions`, {
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      body: JSON.stringify({ fileType }),
 | 
			
		||||
      headers: { "Content-Type": "application/json" },
 | 
			
		||||
    })
 | 
			
		||||
      .then((res) => res.text())
 | 
			
		||||
      .then((html) => {
 | 
			
		||||
        selectContainer.innerHTML = html;
 | 
			
		||||
        updateSearchBar();
 | 
			
		||||
      })
 | 
			
		||||
      .catch(console.error);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fileList.appendChild(row);
 | 
			
		||||
  file.htmlRow = row;
 | 
			
		||||
  fileNames.push(file.name);
 | 
			
		||||
  uploadFile(file);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const selectContainer = document.querySelector("form .select_container");
 | 
			
		||||
 | 
			
		||||
const updateSearchBar = () => {
 | 
			
		||||
@@ -57,6 +114,10 @@ const updateSearchBar = () => {
 | 
			
		||||
      target.onmousedown = () => {
 | 
			
		||||
        convertToElement.value = target.dataset.value;
 | 
			
		||||
        convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`;
 | 
			
		||||
        formatSelected = true;
 | 
			
		||||
        if (pendingFiles === 0 && fileNames.length > 0) {
 | 
			
		||||
          convertButton.disabled = false;
 | 
			
		||||
        }
 | 
			
		||||
        showMatching("");
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
@@ -68,6 +129,12 @@ const updateSearchBar = () => {
 | 
			
		||||
    showMatching(e.target.value.toLowerCase());
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  convertToInput.addEventListener("search", () => {
 | 
			
		||||
    // when the user clears the search bar using the 'x' button
 | 
			
		||||
    convertButton.disabled = true;
 | 
			
		||||
    formatSelected = false;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  convertToInput.addEventListener("blur", (e) => {
 | 
			
		||||
    // Keep the popup open even when clicking on a target button
 | 
			
		||||
    // for a split second to allow the click to go through
 | 
			
		||||
@@ -87,64 +154,12 @@ const updateSearchBar = () => {
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// const convertFromSelect = document.querySelector("select[name='convert_from']");
 | 
			
		||||
 | 
			
		||||
// Add a 'change' event listener to the file input element
 | 
			
		||||
fileInput.addEventListener("change", (e) => {
 | 
			
		||||
  // console.log(e.target.files);
 | 
			
		||||
  // Get the selected files from the event target
 | 
			
		||||
  const files = e.target.files;
 | 
			
		||||
 | 
			
		||||
  // Select the file-list table
 | 
			
		||||
  const fileList = document.querySelector("#file-list");
 | 
			
		||||
 | 
			
		||||
  // Loop through the selected files
 | 
			
		||||
  for (const file of files) {
 | 
			
		||||
    // Create a new table row for each file
 | 
			
		||||
    const row = document.createElement("tr");
 | 
			
		||||
    row.innerHTML = `
 | 
			
		||||
      <td>${file.name}</td>
 | 
			
		||||
      <td>${(file.size / 1024).toFixed(2)} kB</td>
 | 
			
		||||
      <td><a class="secondary" onclick="deleteRow(this)" style="cursor: pointer">Remove</a></td>
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    if (!fileType) {
 | 
			
		||||
      fileType = file.name.split(".").pop();
 | 
			
		||||
      console.log("fileType", fileType);
 | 
			
		||||
      fileInput.setAttribute("accept", `.${fileType}`);
 | 
			
		||||
      setTitle();
 | 
			
		||||
 | 
			
		||||
      // choose the option that matches the file type
 | 
			
		||||
      // for (const option of convertFromSelect.children) {
 | 
			
		||||
      //   console.log(option.value);
 | 
			
		||||
      //   if (option.value === fileType) {
 | 
			
		||||
      //     option.selected = true;
 | 
			
		||||
      //   }
 | 
			
		||||
      // }
 | 
			
		||||
 | 
			
		||||
      fetch("/conversions", {
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        body: JSON.stringify({ fileType: fileType }),
 | 
			
		||||
        headers: {
 | 
			
		||||
          "Content-Type": "application/json",
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
        .then((res) => res.text())
 | 
			
		||||
        .then((html) => {
 | 
			
		||||
          selectContainer.innerHTML = html;
 | 
			
		||||
          updateSearchBar();
 | 
			
		||||
        })
 | 
			
		||||
        .catch((err) => console.log(err));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Append the row to the file-list table
 | 
			
		||||
    fileList.appendChild(row);
 | 
			
		||||
 | 
			
		||||
    // Append the file to the hidden input
 | 
			
		||||
    fileNames.push(file.name);
 | 
			
		||||
    handleFile(file);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uploadFiles(files);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const setTitle = () => {
 | 
			
		||||
@@ -153,6 +168,7 @@ const setTitle = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Add a onclick for the delete button
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
const deleteRow = (target) => {
 | 
			
		||||
  const filename = target.parentElement.parentElement.children[0].textContent;
 | 
			
		||||
  const row = target.parentElement.parentElement;
 | 
			
		||||
@@ -162,48 +178,75 @@ const deleteRow = (target) => {
 | 
			
		||||
  const index = fileNames.indexOf(filename);
 | 
			
		||||
  fileNames.splice(index, 1);
 | 
			
		||||
 | 
			
		||||
  // reset fileInput
 | 
			
		||||
  fileInput.value = "";
 | 
			
		||||
 | 
			
		||||
  // if fileNames is empty, reset fileType
 | 
			
		||||
  if (fileNames.length === 0) {
 | 
			
		||||
    fileType = null;
 | 
			
		||||
    fileInput.removeAttribute("accept");
 | 
			
		||||
    convertButton.disabled = true;
 | 
			
		||||
    setTitle();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fetch("/delete", {
 | 
			
		||||
  fetch(`${webroot}/delete`, {
 | 
			
		||||
    method: "POST",
 | 
			
		||||
    body: JSON.stringify({ filename: filename }),
 | 
			
		||||
    headers: {
 | 
			
		||||
      "Content-Type": "application/json",
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
    .then((res) => res.json())
 | 
			
		||||
    .then((data) => {
 | 
			
		||||
      console.log(data);
 | 
			
		||||
    })
 | 
			
		||||
    .catch((err) => console.log(err));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const uploadFiles = (files) => {
 | 
			
		||||
const uploadFile = (file) => {
 | 
			
		||||
  convertButton.disabled = true;
 | 
			
		||||
  convertButton.textContent = "Uploading...";
 | 
			
		||||
  pendingFiles += 1;
 | 
			
		||||
 | 
			
		||||
  const formData = new FormData();
 | 
			
		||||
  formData.append("file", file, file.name);
 | 
			
		||||
 | 
			
		||||
  for (const file of files) {
 | 
			
		||||
    formData.append("file", file, file.name);
 | 
			
		||||
  }
 | 
			
		||||
  let xhr = new XMLHttpRequest(); 
 | 
			
		||||
 | 
			
		||||
  fetch("/upload", {
 | 
			
		||||
    method: "POST",
 | 
			
		||||
    body: formData,
 | 
			
		||||
  })
 | 
			
		||||
    .then((res) => res.json())
 | 
			
		||||
    .then((data) => {
 | 
			
		||||
      console.log(data);
 | 
			
		||||
    })
 | 
			
		||||
    .catch((err) => console.log(err));
 | 
			
		||||
  xhr.open("POST", `${webroot}/upload`, true);
 | 
			
		||||
 | 
			
		||||
  xhr.onload = () => {
 | 
			
		||||
    let data = JSON.parse(xhr.responseText);
 | 
			
		||||
 | 
			
		||||
    pendingFiles -= 1;
 | 
			
		||||
    if (pendingFiles === 0) {
 | 
			
		||||
      if (formatSelected) {
 | 
			
		||||
        convertButton.disabled = false;
 | 
			
		||||
      }
 | 
			
		||||
      convertButton.textContent = "Convert";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //Remove the progress bar when upload is done
 | 
			
		||||
    let progressbar = file.htmlRow.getElementsByTagName("progress");
 | 
			
		||||
    progressbar[0].parentElement.remove();
 | 
			
		||||
    console.log(data);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  xhr.upload.onprogress = (e) => {
 | 
			
		||||
    let sent = e.loaded;
 | 
			
		||||
    let total = e.total;
 | 
			
		||||
    console.log(`upload progress (${file.name}):`, (100 * sent) / total);
 | 
			
		||||
 | 
			
		||||
    let progressbar = file.htmlRow.getElementsByTagName("progress");
 | 
			
		||||
    progressbar[0].value = ((100 * sent) / total);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  xhr.onerror = (e) => {
 | 
			
		||||
    console.log(e);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  xhr.send(formData);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const formConvert = document.querySelector("form[action='/convert']");
 | 
			
		||||
const formConvert = document.querySelector(`form[action='${webroot}/convert']`);
 | 
			
		||||
 | 
			
		||||
formConvert.addEventListener("submit", (e) => {
 | 
			
		||||
formConvert.addEventListener("submit", () => {
 | 
			
		||||
  const hiddenInput = document.querySelector("input[name='file_names']");
 | 
			
		||||
  hiddenInput.value = JSON.stringify(fileNames);
 | 
			
		||||
});
 | 
			
		||||
@@ -1,6 +1,11 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
 | 
			
		||||
  "extends": [
 | 
			
		||||
    "config:recommended"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
    "config:recommended",
 | 
			
		||||
    ":disableDependencyDashboard"
 | 
			
		||||
  ],
 | 
			
		||||
  "lockFileMaintenance": {
 | 
			
		||||
    "enabled": true,
 | 
			
		||||
    "automerge": true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +1,40 @@
 | 
			
		||||
export const BaseHtml = ({
 | 
			
		||||
  children,
 | 
			
		||||
  title = "ConvertX",
 | 
			
		||||
}: { children: JSX.Element; title?: string }) => (
 | 
			
		||||
  <html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
      <meta charset="UTF-8" />
 | 
			
		||||
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
      <title safe>{title}</title>
 | 
			
		||||
      <link rel="stylesheet" href="/generated.css" />
 | 
			
		||||
      <link
 | 
			
		||||
        rel="apple-touch-icon"
 | 
			
		||||
        sizes="180x180"
 | 
			
		||||
        href="/apple-touch-icon.png"
 | 
			
		||||
      />
 | 
			
		||||
      <link
 | 
			
		||||
        rel="icon"
 | 
			
		||||
        type="image/png"
 | 
			
		||||
        sizes="32x32"
 | 
			
		||||
        href="/favicon-32x32.png"
 | 
			
		||||
      />
 | 
			
		||||
      <link
 | 
			
		||||
        rel="icon"
 | 
			
		||||
        type="image/png"
 | 
			
		||||
        sizes="16x16"
 | 
			
		||||
        href="/favicon-16x16.png"
 | 
			
		||||
      />
 | 
			
		||||
      <link rel="manifest" href="/site.webmanifest" />
 | 
			
		||||
    </head>
 | 
			
		||||
    <body class="w-full bg-gray-900 text-gray-200">{children}</body>
 | 
			
		||||
  </html>
 | 
			
		||||
);
 | 
			
		||||
import { Html } from "@elysiajs/html";
 | 
			
		||||
 | 
			
		||||
export const BaseHtml = ({
 | 
			
		||||
  children,
 | 
			
		||||
  title = "ConvertX",
 | 
			
		||||
  webroot = "",
 | 
			
		||||
}: {
 | 
			
		||||
  children: JSX.Element;
 | 
			
		||||
  title?: string;
 | 
			
		||||
  webroot?: string;
 | 
			
		||||
}) => (
 | 
			
		||||
  <html lang="en">
 | 
			
		||||
    <head>
 | 
			
		||||
      <meta charset="UTF-8" />
 | 
			
		||||
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
      <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="manifest" href={`${webroot}/site.webmanifest`} />
 | 
			
		||||
    </head>
 | 
			
		||||
    <body class="w-full bg-neutral-900 text-neutral-200">{children}</body>
 | 
			
		||||
  </html>
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,48 @@
 | 
			
		||||
import { Html } from "@kitajs/html";
 | 
			
		||||
 | 
			
		||||
export const Header = ({
 | 
			
		||||
  loggedIn,
 | 
			
		||||
  accountRegistration,
 | 
			
		||||
}: { loggedIn?: boolean; accountRegistration?: boolean }) => {
 | 
			
		||||
  allowUnauthenticated,
 | 
			
		||||
  hideHistory,
 | 
			
		||||
  webroot = "",
 | 
			
		||||
}: {
 | 
			
		||||
  loggedIn?: boolean;
 | 
			
		||||
  accountRegistration?: boolean;
 | 
			
		||||
  allowUnauthenticated?: boolean;
 | 
			
		||||
  hideHistory?: boolean;
 | 
			
		||||
  webroot?: string;
 | 
			
		||||
}) => {
 | 
			
		||||
  let rightNav: JSX.Element;
 | 
			
		||||
  if (loggedIn) {
 | 
			
		||||
    rightNav = (
 | 
			
		||||
      <ul class="flex gap-4 ">
 | 
			
		||||
        <li>
 | 
			
		||||
          <a
 | 
			
		||||
            class="text-lime-600 transition-all hover:text-lime-500 hover:underline"
 | 
			
		||||
            href="/history">
 | 
			
		||||
            History
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <a
 | 
			
		||||
            class="text-lime-600 transition-all hover:text-lime-500 hover:underline"
 | 
			
		||||
            href="/logoff">
 | 
			
		||||
            Logout
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
      <ul class="flex gap-4">
 | 
			
		||||
        {!hideHistory && (
 | 
			
		||||
          <li>
 | 
			
		||||
            <a
 | 
			
		||||
              class={`
 | 
			
		||||
                text-accent-600 transition-all
 | 
			
		||||
                hover:text-accent-500 hover:underline
 | 
			
		||||
              `}
 | 
			
		||||
              href={`${webroot}/history`}
 | 
			
		||||
            >
 | 
			
		||||
              History
 | 
			
		||||
            </a>
 | 
			
		||||
          </li>
 | 
			
		||||
        )}
 | 
			
		||||
        {!allowUnauthenticated ? (
 | 
			
		||||
          <li>
 | 
			
		||||
            <a
 | 
			
		||||
              class={`
 | 
			
		||||
                text-accent-600 transition-all
 | 
			
		||||
                hover:text-accent-500 hover:underline
 | 
			
		||||
              `}
 | 
			
		||||
              href={`${webroot}/logoff`}
 | 
			
		||||
            >
 | 
			
		||||
              Logout
 | 
			
		||||
            </a>
 | 
			
		||||
          </li>
 | 
			
		||||
        ) : null}
 | 
			
		||||
      </ul>
 | 
			
		||||
    );
 | 
			
		||||
  } else {
 | 
			
		||||
@@ -27,16 +50,24 @@ export const Header = ({
 | 
			
		||||
      <ul class="flex gap-4">
 | 
			
		||||
        <li>
 | 
			
		||||
          <a
 | 
			
		||||
            class="text-lime-600 transition-all hover:text-lime-500 hover:underline"
 | 
			
		||||
            href="/login">
 | 
			
		||||
            class={`
 | 
			
		||||
              text-accent-600 transition-all
 | 
			
		||||
              hover:text-accent-500 hover:underline
 | 
			
		||||
            `}
 | 
			
		||||
            href={`${webroot}/login`}
 | 
			
		||||
          >
 | 
			
		||||
            Login
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {accountRegistration ? (
 | 
			
		||||
          <li>
 | 
			
		||||
            <a
 | 
			
		||||
              class="text-lime-600 transition-all hover:text-lime-500 hover:underline"
 | 
			
		||||
              href="/register">
 | 
			
		||||
              class={`
 | 
			
		||||
                text-accent-600 transition-all
 | 
			
		||||
                hover:text-accent-500 hover:underline
 | 
			
		||||
              `}
 | 
			
		||||
              href={`${webroot}/register`}
 | 
			
		||||
            >
 | 
			
		||||
              Register
 | 
			
		||||
            </a>
 | 
			
		||||
          </li>
 | 
			
		||||
@@ -47,11 +78,11 @@ export const Header = ({
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <header class="w-full p-4">
 | 
			
		||||
      <nav class="mx-auto flex max-w-4xl justify-between rounded bg-gray-900 p-4">
 | 
			
		||||
      <nav class="mx-auto flex max-w-4xl justify-between rounded-sm bg-neutral-900 p-4">
 | 
			
		||||
        <ul>
 | 
			
		||||
          <li>
 | 
			
		||||
            <strong>
 | 
			
		||||
              <a href="/">ConvertX</a>
 | 
			
		||||
              <a href={`${webroot}/`}>ConvertX</a>
 | 
			
		||||
            </strong>
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,139 +1,139 @@
 | 
			
		||||
import { exec } from "node:child_process";
 | 
			
		||||
 | 
			
		||||
// This could be done dynamically by running `ffmpeg -formats` and parsing the output
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
    muxer: [
 | 
			
		||||
        "3d",
 | 
			
		||||
        "3ds",
 | 
			
		||||
        "3mf",
 | 
			
		||||
        "ac",
 | 
			
		||||
        "ac3d",
 | 
			
		||||
        "acc",
 | 
			
		||||
        "amf",
 | 
			
		||||
        "ase",
 | 
			
		||||
        "ask",
 | 
			
		||||
        "assbin",
 | 
			
		||||
        "b3d",
 | 
			
		||||
        "blend",
 | 
			
		||||
        "bsp",
 | 
			
		||||
        "bvh",
 | 
			
		||||
        "cob",
 | 
			
		||||
        "csm",
 | 
			
		||||
        "dae",
 | 
			
		||||
        "dxf",
 | 
			
		||||
        "enff",
 | 
			
		||||
        "fbx",
 | 
			
		||||
        "glb",
 | 
			
		||||
        "gltf",
 | 
			
		||||
        "hmp",
 | 
			
		||||
        "ifc",
 | 
			
		||||
        "ifczip",
 | 
			
		||||
        "iqm",
 | 
			
		||||
        "irr",
 | 
			
		||||
        "irrmesh",
 | 
			
		||||
        "lwo",
 | 
			
		||||
        "lws",
 | 
			
		||||
        "lxo",
 | 
			
		||||
        "md2",
 | 
			
		||||
        "md3",
 | 
			
		||||
        "md5anim",
 | 
			
		||||
        "md5camera",
 | 
			
		||||
        "md5mesh",
 | 
			
		||||
        "mdc",
 | 
			
		||||
        "mdl",
 | 
			
		||||
        "mesh",
 | 
			
		||||
        "mesh.xml",
 | 
			
		||||
        "mot",
 | 
			
		||||
        "ms3d",
 | 
			
		||||
        "ndo",
 | 
			
		||||
        "nff",
 | 
			
		||||
        "obj",
 | 
			
		||||
        "off",
 | 
			
		||||
        "ogex",
 | 
			
		||||
        "pk3",
 | 
			
		||||
        "ply",
 | 
			
		||||
        "pmx",
 | 
			
		||||
        "prj",
 | 
			
		||||
        "q3o",
 | 
			
		||||
        "q3s",
 | 
			
		||||
        "raw",
 | 
			
		||||
        "scn",
 | 
			
		||||
        "sib",
 | 
			
		||||
        "smd",
 | 
			
		||||
        "step",
 | 
			
		||||
        "stl",
 | 
			
		||||
        "stp",
 | 
			
		||||
        "ter",
 | 
			
		||||
        "uc",
 | 
			
		||||
        "vta",
 | 
			
		||||
        "x",
 | 
			
		||||
        "x3d",
 | 
			
		||||
        "x3db",
 | 
			
		||||
        "xgl",
 | 
			
		||||
        "xml",
 | 
			
		||||
        "zae",
 | 
			
		||||
        "zgl",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  to: {
 | 
			
		||||
    muxer: [
 | 
			
		||||
        "collada",
 | 
			
		||||
        "x",
 | 
			
		||||
        "stp",
 | 
			
		||||
        "obj",
 | 
			
		||||
        "objnomtl",
 | 
			
		||||
        "stl",
 | 
			
		||||
        "stlb",
 | 
			
		||||
        "ply",
 | 
			
		||||
        "plyb",
 | 
			
		||||
        "3ds",
 | 
			
		||||
        "gltf2",
 | 
			
		||||
        "glb2",
 | 
			
		||||
        "gltf",
 | 
			
		||||
        "glb",
 | 
			
		||||
        "assbin",
 | 
			
		||||
        "assxml",
 | 
			
		||||
        "x3d",
 | 
			
		||||
        "fbx",
 | 
			
		||||
        "fbxa",
 | 
			
		||||
        "m3d",
 | 
			
		||||
        "m3da",
 | 
			
		||||
        "3mf",
 | 
			
		||||
        "pbrt",
 | 
			
		||||
        "assjson",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export async function convert(
 | 
			
		||||
  filePath: string,
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  // let command = "ffmpeg";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  const command = `assimp export "${filePath}" "${targetPath}"`;
 | 
			
		||||
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(command, (error, stdout, stderr) => {
 | 
			
		||||
      if (error) {
 | 
			
		||||
        reject(`error: ${error}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (stdout) {
 | 
			
		||||
        console.log(`stdout: ${stdout}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (stderr) {
 | 
			
		||||
        console.error(`stderr: ${stderr}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      resolve("success");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
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");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										84
									
								
								src/converters/calibre.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,84 @@
 | 
			
		||||
import { execFile } from "node:child_process";
 | 
			
		||||
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
    document: [
 | 
			
		||||
      "azw4",
 | 
			
		||||
      "chm",
 | 
			
		||||
      "cbr",
 | 
			
		||||
      "cbz",
 | 
			
		||||
      "cbt",
 | 
			
		||||
      "cba",
 | 
			
		||||
      "cb7",
 | 
			
		||||
      "djvu",
 | 
			
		||||
      "docx",
 | 
			
		||||
      "epub",
 | 
			
		||||
      "fb2",
 | 
			
		||||
      "htlz",
 | 
			
		||||
      "html",
 | 
			
		||||
      "lit",
 | 
			
		||||
      "lrf",
 | 
			
		||||
      "mobi",
 | 
			
		||||
      "odt",
 | 
			
		||||
      "pdb",
 | 
			
		||||
      "pdf",
 | 
			
		||||
      "pml",
 | 
			
		||||
      "rb",
 | 
			
		||||
      "rtf",
 | 
			
		||||
      "recipe",
 | 
			
		||||
      "snb",
 | 
			
		||||
      "tcr",
 | 
			
		||||
      "txt",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  to: {
 | 
			
		||||
    document: [
 | 
			
		||||
      "azw3",
 | 
			
		||||
      "docx",
 | 
			
		||||
      "epub",
 | 
			
		||||
      "fb2",
 | 
			
		||||
      "html",
 | 
			
		||||
      "htmlz",
 | 
			
		||||
      "lit",
 | 
			
		||||
      "lrf",
 | 
			
		||||
      "mobi",
 | 
			
		||||
      "oeb",
 | 
			
		||||
      "pdb",
 | 
			
		||||
      "pdf",
 | 
			
		||||
      "pml",
 | 
			
		||||
      "rb",
 | 
			
		||||
      "rtf",
 | 
			
		||||
      "snb",
 | 
			
		||||
      "tcr",
 | 
			
		||||
      "txt",
 | 
			
		||||
      "txtz",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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("ebook-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,339 +1,340 @@
 | 
			
		||||
import { exec } 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,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(
 | 
			
		||||
      `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("success");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
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");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								src/converters/inkscape.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,59 @@
 | 
			
		||||
import { execFile } from "node:child_process";
 | 
			
		||||
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
    images: ["svg", "pdf", "eps", "ps", "wmf", "emf", "png"],
 | 
			
		||||
  },
 | 
			
		||||
  to: {
 | 
			
		||||
    images: [
 | 
			
		||||
      "dxf",
 | 
			
		||||
      "emf",
 | 
			
		||||
      "eps",
 | 
			
		||||
      "fxg",
 | 
			
		||||
      "gpl",
 | 
			
		||||
      "hpgl",
 | 
			
		||||
      "html",
 | 
			
		||||
      "odg",
 | 
			
		||||
      "pdf",
 | 
			
		||||
      "png",
 | 
			
		||||
      "pov",
 | 
			
		||||
      "ps",
 | 
			
		||||
      "sif",
 | 
			
		||||
      "svg",
 | 
			
		||||
      "svgz",
 | 
			
		||||
      "tex",
 | 
			
		||||
      "wmf",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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(
 | 
			
		||||
      "inkscape",
 | 
			
		||||
      [filePath, "-o", targetPath],
 | 
			
		||||
      (error, stdout, stderr) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
          reject(`error: ${error}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (stdout) {
 | 
			
		||||
          console.log(`stdout: ${stdout}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (stderr) {
 | 
			
		||||
          console.error(`stderr: ${stderr}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        resolve("Done");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/converters/libheif.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,53 @@
 | 
			
		||||
import { execFile } from "child_process";
 | 
			
		||||
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
    images: [
 | 
			
		||||
      "avci",
 | 
			
		||||
      "avcs",
 | 
			
		||||
      "avif",
 | 
			
		||||
      "h264",
 | 
			
		||||
      "heic",
 | 
			
		||||
      "heics",
 | 
			
		||||
      "heif",
 | 
			
		||||
      "heifs",
 | 
			
		||||
      "hif",
 | 
			
		||||
      "mkv",
 | 
			
		||||
      "mp4",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  to: {
 | 
			
		||||
    images: ["jpeg", "png", "y4m"],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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(
 | 
			
		||||
      "heif-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,71 +1,71 @@
 | 
			
		||||
import { exec } from "node:child_process";
 | 
			
		||||
 | 
			
		||||
// declare possible conversions
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
    jxl: ["jxl"],
 | 
			
		||||
    images: [
 | 
			
		||||
      "apng",
 | 
			
		||||
      "exr",
 | 
			
		||||
      "gif",
 | 
			
		||||
      "jpeg",
 | 
			
		||||
      "pam",
 | 
			
		||||
      "pfm",
 | 
			
		||||
      "pgm",
 | 
			
		||||
      "pgx",
 | 
			
		||||
      "png",
 | 
			
		||||
      "ppm",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  to: {
 | 
			
		||||
    jxl: [
 | 
			
		||||
      "apng",
 | 
			
		||||
      "exr",
 | 
			
		||||
      "gif",
 | 
			
		||||
      "jpeg",
 | 
			
		||||
      "pam",
 | 
			
		||||
      "pfm",
 | 
			
		||||
      "pgm",
 | 
			
		||||
      "pgx",
 | 
			
		||||
      "png",
 | 
			
		||||
      "ppm",
 | 
			
		||||
    ],
 | 
			
		||||
    images: ["jxl"],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function convert(
 | 
			
		||||
  filePath: string,
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  let tool = "";
 | 
			
		||||
  if (fileType === "jxl") {
 | 
			
		||||
    tool = "djxl";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (convertTo === "jxl") {
 | 
			
		||||
    tool = "cjxl";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(`${tool} "${filePath}" "${targetPath}"`, (error, stdout, stderr) => {
 | 
			
		||||
      if (error) {
 | 
			
		||||
        reject(`error: ${error}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (stdout) {
 | 
			
		||||
        console.log(`stdout: ${stdout}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (stderr) {
 | 
			
		||||
        console.error(`stderr: ${stderr}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      resolve("success");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
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");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,67 +1,48 @@
 | 
			
		||||
import { 
 | 
			
		||||
  convert as convertImage,
 | 
			
		||||
  properties as propertiesImage
 | 
			
		||||
} from "./vips";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertPandoc,
 | 
			
		||||
  properties as propertiesPandoc,
 | 
			
		||||
} from "./pandoc";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertFFmpeg,
 | 
			
		||||
  properties as propertiesFFmpeg,
 | 
			
		||||
} from "./ffmpeg";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertGraphicsmagick,
 | 
			
		||||
  properties as propertiesGraphicsmagick,
 | 
			
		||||
} from "./graphicsmagick";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertxelatex,
 | 
			
		||||
  properties as propertiesxelatex,
 | 
			
		||||
} from "./xelatex";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertLibjxl,
 | 
			
		||||
  properties as propertiesLibjxl,
 | 
			
		||||
} from "./libjxl";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertresvg,
 | 
			
		||||
  properties as propertiesresvg,
 | 
			
		||||
} from "./resvg";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertassimp,
 | 
			
		||||
  properties as propertiesassimp,
 | 
			
		||||
} from "./assimp";
 | 
			
		||||
 | 
			
		||||
import { normalizeFiletype } from "../helpers/normalizeFiletype";
 | 
			
		||||
import { convert as convertassimp, properties as propertiesassimp } from "./assimp";
 | 
			
		||||
import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg";
 | 
			
		||||
import { convert as convertGraphicsmagick, properties as propertiesGraphicsmagick } from "./graphicsmagick";
 | 
			
		||||
import { convert as convertInkscape, properties as propertiesInkscape } from "./inkscape";
 | 
			
		||||
import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl";
 | 
			
		||||
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
 | 
			
		||||
 | 
			
		||||
const properties: Record<string, {
 | 
			
		||||
const properties: Record<
 | 
			
		||||
  string,
 | 
			
		||||
  {
 | 
			
		||||
    properties: {
 | 
			
		||||
      from: Record<string, string[]>;
 | 
			
		||||
      to: Record<string, string[]>;
 | 
			
		||||
      options?: Record<string, Record<string, {
 | 
			
		||||
      options?: Record<
 | 
			
		||||
        string,
 | 
			
		||||
        Record<
 | 
			
		||||
          string,
 | 
			
		||||
          {
 | 
			
		||||
            description: string;
 | 
			
		||||
            type: string;
 | 
			
		||||
            default: number;
 | 
			
		||||
          }>>;
 | 
			
		||||
          }
 | 
			
		||||
        >
 | 
			
		||||
      >;
 | 
			
		||||
    };
 | 
			
		||||
    converter: (
 | 
			
		||||
      filePath: string,
 | 
			
		||||
      fileType: string,
 | 
			
		||||
      convertTo: string,
 | 
			
		||||
      targetPath: string,
 | 
			
		||||
      // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
      options?: any,
 | 
			
		||||
      // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
    ) => any;
 | 
			
		||||
  }> = {
 | 
			
		||||
 | 
			
		||||
      options?: unknown,
 | 
			
		||||
    ) => unknown;
 | 
			
		||||
  }
 | 
			
		||||
> = {
 | 
			
		||||
  libjxl: {
 | 
			
		||||
    properties: propertiesLibjxl,
 | 
			
		||||
    converter: convertLibjxl,
 | 
			
		||||
@@ -74,10 +55,18 @@ const properties: Record<string, {
 | 
			
		||||
    properties: propertiesImage,
 | 
			
		||||
    converter: convertImage,
 | 
			
		||||
  },
 | 
			
		||||
  libheif: {
 | 
			
		||||
    properties: propertiesLibheif,
 | 
			
		||||
    converter: convertLibheif,
 | 
			
		||||
  },
 | 
			
		||||
  xelatex: {
 | 
			
		||||
    properties: propertiesxelatex,
 | 
			
		||||
    converter: convertxelatex,
 | 
			
		||||
  },
 | 
			
		||||
  // calibre: {
 | 
			
		||||
  //   properties: propertiesCalibre,
 | 
			
		||||
  //   converter: convertCalibre,
 | 
			
		||||
  // },
 | 
			
		||||
  pandoc: {
 | 
			
		||||
    properties: propertiesPandoc,
 | 
			
		||||
    converter: convertPandoc,
 | 
			
		||||
@@ -86,6 +75,10 @@ const properties: Record<string, {
 | 
			
		||||
    properties: propertiesGraphicsmagick,
 | 
			
		||||
    converter: convertGraphicsmagick,
 | 
			
		||||
  },
 | 
			
		||||
  inkscape: {
 | 
			
		||||
    properties: propertiesInkscape,
 | 
			
		||||
    converter: convertInkscape,
 | 
			
		||||
  },
 | 
			
		||||
  assimp: {
 | 
			
		||||
    properties: propertiesassimp,
 | 
			
		||||
    converter: convertassimp,
 | 
			
		||||
@@ -94,29 +87,28 @@ const properties: Record<string, {
 | 
			
		||||
    properties: propertiesFFmpeg,
 | 
			
		||||
    converter: convertFFmpeg,
 | 
			
		||||
  },
 | 
			
		||||
  potrace: {
 | 
			
		||||
    properties: propertiespotrace,
 | 
			
		||||
    converter: convertpotrace,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export async function mainConverter(
 | 
			
		||||
  inputFilePath: string,
 | 
			
		||||
  fileTypeOriginal: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  convertTo: any,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
  converterName?: string,
 | 
			
		||||
) {
 | 
			
		||||
  const fileType = normalizeFiletype(fileTypeOriginal);
 | 
			
		||||
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  let converterFunc: any;
 | 
			
		||||
  // let converterName = converterName;
 | 
			
		||||
  let converterFunc: typeof properties["libjxl"]["converter"] | undefined;
 | 
			
		||||
 | 
			
		||||
  if (converterName) {
 | 
			
		||||
    converterFunc = properties[converterName]?.converter;
 | 
			
		||||
  } else {
 | 
			
		||||
    // Iterate over each converter in properties
 | 
			
		||||
    // biome-ignore lint/style/noParameterAssign: <explanation>
 | 
			
		||||
    for (converterName in properties) {
 | 
			
		||||
      const converterObj = properties[converterName];
 | 
			
		||||
 | 
			
		||||
@@ -144,7 +136,7 @@ export async function mainConverter(
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    await converterFunc(
 | 
			
		||||
    const result = await converterFunc(
 | 
			
		||||
      inputFilePath,
 | 
			
		||||
      fileType,
 | 
			
		||||
      convertTo,
 | 
			
		||||
@@ -154,7 +146,13 @@ export async function mainConverter(
 | 
			
		||||
 | 
			
		||||
    console.log(
 | 
			
		||||
      `Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
 | 
			
		||||
      result,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (typeof result === "string") {
 | 
			
		||||
      return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return "Done";
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error(
 | 
			
		||||
@@ -190,9 +188,7 @@ for (const converterName in properties) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getPossibleTargets = (
 | 
			
		||||
  from: string,
 | 
			
		||||
): Record<string, string[]> => {
 | 
			
		||||
export const getPossibleTargets = (from: string): Record<string, string[]> => {
 | 
			
		||||
  const fromClean = normalizeFiletype(from);
 | 
			
		||||
 | 
			
		||||
  return possibleTargets[fromClean] || {};
 | 
			
		||||
@@ -216,6 +212,7 @@ for (const converterName in properties) {
 | 
			
		||||
}
 | 
			
		||||
possibleInputs.sort();
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
const getPossibleInputs = () => {
 | 
			
		||||
  return possibleInputs;
 | 
			
		||||
};
 | 
			
		||||
@@ -287,4 +284,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}`);
 | 
			
		||||
@@ -1,156 +1,162 @@
 | 
			
		||||
import { exec } 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,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  // set xelatex here
 | 
			
		||||
  const xelatex = ["pdf", "latex"];
 | 
			
		||||
  let option = "";
 | 
			
		||||
  if (xelatex.includes(convertTo)) {
 | 
			
		||||
    option = "--pdf-engine=xelatex";
 | 
			
		||||
  }
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(
 | 
			
		||||
      `pandoc ${option} "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`,
 | 
			
		||||
      (error, stdout, stderr) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
          reject(`error: ${error}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (stdout) {
 | 
			
		||||
          console.log(`stdout: ${stdout}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (stderr) {
 | 
			
		||||
          console.error(`stderr: ${stderr}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        resolve("success");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
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
									
								
								src/converters/potrace.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,37 @@
 | 
			
		||||
import { execFile } from "node:child_process";
 | 
			
		||||
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
    images: ["pnm", "pbm", "pgm", "bmp"],
 | 
			
		||||
  },
 | 
			
		||||
  to: {
 | 
			
		||||
    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");
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@@ -1,37 +1,37 @@
 | 
			
		||||
import { exec } from "node:child_process";
 | 
			
		||||
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
    images: ["svg"],
 | 
			
		||||
  },
 | 
			
		||||
  to: {
 | 
			
		||||
    images: ["png"],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function convert(
 | 
			
		||||
  filePath: string,
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(`resvg "${filePath}" "${targetPath}"`, (error, stdout, stderr) => {
 | 
			
		||||
      if (error) {
 | 
			
		||||
        reject(`error: ${error}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (stdout) {
 | 
			
		||||
        console.log(`stdout: ${stdout}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (stderr) {
 | 
			
		||||
        console.error(`stderr: ${stderr}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      resolve("success");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
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");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,141 +1,142 @@
 | 
			
		||||
import { exec } 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,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
): 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) => {
 | 
			
		||||
    exec(
 | 
			
		||||
      `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("success");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
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");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,53 @@
 | 
			
		||||
import { exec } 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,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
): 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("./", "");
 | 
			
		||||
    exec(
 | 
			
		||||
      `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("success");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
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");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,37 @@
 | 
			
		||||
export const normalizeFiletype = (filetype: string): string => {
 | 
			
		||||
  const lowercaseFiletype = filetype.toLowerCase();
 | 
			
		||||
 | 
			
		||||
  switch (lowercaseFiletype) {
 | 
			
		||||
    case "jpg":
 | 
			
		||||
      return "jpeg";
 | 
			
		||||
    case "htm":
 | 
			
		||||
      return "html";
 | 
			
		||||
    case "tex":
 | 
			
		||||
      return "latex";
 | 
			
		||||
    case "md":
 | 
			
		||||
      return "markdown";
 | 
			
		||||
    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":
 | 
			
		||||
      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;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { exec } from "node:child_process";
 | 
			
		||||
import { version } from "../../package.json";
 | 
			
		||||
 | 
			
		||||
console.log(`ConvertX v${version}`);
 | 
			
		||||
 | 
			
		||||
if (process.env.NODE_ENV === "production") {
 | 
			
		||||
@@ -53,6 +54,16 @@ if (process.env.NODE_ENV === "production") {
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  exec("inkscape --version", (error, stdout) => {
 | 
			
		||||
    if (error) {
 | 
			
		||||
      console.error("Inkscape is not installed.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (stdout) {
 | 
			
		||||
      console.log(stdout.split("\n")[0]);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  exec("djxl --version", (error, stdout) => {
 | 
			
		||||
    if (error) {
 | 
			
		||||
      console.error("libjxl-tools is not installed.");
 | 
			
		||||
@@ -89,7 +100,37 @@ if (process.env.NODE_ENV === "production") {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (stdout) {
 | 
			
		||||
      console.log(`assimp v${stdout.split("\n")[5]}`);
 | 
			
		||||
      console.log(`assimp ${stdout.split("\n")[5]}`);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  exec("ebook-convert --version", (error, stdout) => {
 | 
			
		||||
    if (error) {
 | 
			
		||||
      console.error("ebook-convert (calibre) is not installed");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (stdout) {
 | 
			
		||||
      console.log(stdout.split("\n")[0]);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  exec("heif-info -v", (error, stdout) => {
 | 
			
		||||
    if (error) {
 | 
			
		||||
      console.error("libheif is not installed");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (stdout) {
 | 
			
		||||
      console.log(`libheif v${stdout.split("\n")[0]}`);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  exec("potrace -v", (error, stdout) => {
 | 
			
		||||
    if (error) {
 | 
			
		||||
      console.error("potrace is not installed");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (stdout) {
 | 
			
		||||
      console.log(stdout.split("\n")[0]);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,11 @@
 | 
			
		||||
import tw from "tailwindcss";
 | 
			
		||||
import tailwind from "@tailwindcss/postcss";
 | 
			
		||||
import postcss from "postcss";
 | 
			
		||||
 | 
			
		||||
export const generateTailwind = async () => {
 | 
			
		||||
  const result = await Bun.file("./src/main.css")
 | 
			
		||||
    .text()
 | 
			
		||||
    .then((sourceText) => {
 | 
			
		||||
      const config = "./tailwind.config.js";
 | 
			
		||||
 | 
			
		||||
      return postcss([tw(config)]).process(sourceText, {
 | 
			
		||||
      return postcss([tailwind]).process(sourceText, {
 | 
			
		||||
        from: "./src/main.css",
 | 
			
		||||
        to: "./public/generated.css",
 | 
			
		||||
      });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										589
									
								
								src/index.tsx
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										84
									
								
								src/main.css
									
									
									
									
									
								
							
							
						
						@@ -1,12 +1,78 @@
 | 
			
		||||
@tailwind base;
 | 
			
		||||
@tailwind components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
@import "tailwindcss";
 | 
			
		||||
 | 
			
		||||
@layer components {
 | 
			
		||||
  .article {
 | 
			
		||||
    @apply p-4 mb-4 bg-gray-800/40 w-full mx-auto max-w-4xl rounded;
 | 
			
		||||
  }
 | 
			
		||||
  .btn-primary {
 | 
			
		||||
    @apply bg-lime-500 text-black rounded p-4 hover:bg-lime-400 cursor-pointer;
 | 
			
		||||
@plugin 'tailwind-scrollbar';
 | 
			
		||||
 | 
			
		||||
@theme {
 | 
			
		||||
  --color-contrast: rgba(var(--contrast));
 | 
			
		||||
  --color-neutral-900: rgba(var(--neutral-900));
 | 
			
		||||
  --color-neutral-800: rgba(var(--neutral-800));
 | 
			
		||||
  --color-neutral-700: rgba(var(--neutral-700));
 | 
			
		||||
  --color-neutral-600: rgba(var(--neutral-600));
 | 
			
		||||
  --color-neutral-500: rgba(var(--neutral-500));
 | 
			
		||||
  --color-neutral-400: rgba(var(--neutral-400));
 | 
			
		||||
  --color-neutral-300: rgba(var(--neutral-300));
 | 
			
		||||
  --color-neutral-200: rgba(var(--neutral-200));
 | 
			
		||||
  --color-neutral-100: rgba(var(--neutral-100));
 | 
			
		||||
  --color-accent-600: rgba(var(--accent-600));
 | 
			
		||||
  --color-accent-500: rgba(var(--accent-500));
 | 
			
		||||
  --color-accent-400: rgba(var(--accent-400));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  The default border color has changed to `currentColor` in Tailwind CSS v4,
 | 
			
		||||
  so we've added these compatibility styles to make sure everything still
 | 
			
		||||
  looks the same as it did with Tailwind CSS v3.
 | 
			
		||||
 | 
			
		||||
  If we ever want to remove these styles, we need to add an explicit border
 | 
			
		||||
  color utility to any element that depends on these defaults.
 | 
			
		||||
*/
 | 
			
		||||
@layer base {
 | 
			
		||||
  *,
 | 
			
		||||
  ::after,
 | 
			
		||||
  ::before,
 | 
			
		||||
  ::backdrop,
 | 
			
		||||
  ::file-selector-button {
 | 
			
		||||
    border-color: var(--color-gray-200, currentColor);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@utility article {
 | 
			
		||||
  @apply px-2 sm:px-4 py-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded-sm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@utility btn-primary {
 | 
			
		||||
  @apply bg-accent-500 text-contrast rounded-sm p-2 sm:p-4 hover:bg-accent-400 cursor-pointer transition-colors;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --contrast: 255, 255, 255;
 | 
			
		||||
  --neutral-900: 243, 244, 246;
 | 
			
		||||
  --neutral-800: 229, 231, 235;
 | 
			
		||||
  --neutral-700: 209, 213, 219;
 | 
			
		||||
  --neutral-600: 156, 163, 175;
 | 
			
		||||
  --neutral-500: 180, 180, 180;
 | 
			
		||||
  --neutral-400: 75, 85, 99;
 | 
			
		||||
  --neutral-300: 55, 65, 81;
 | 
			
		||||
  --neutral-200: 31, 41, 55;
 | 
			
		||||
  --neutral-100: 17, 24, 39;
 | 
			
		||||
  --accent-400: 132, 204, 22;
 | 
			
		||||
  --accent-500: 101, 163, 13;
 | 
			
		||||
  --accent-600: 77, 124, 15;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (prefers-color-scheme: dark) {
 | 
			
		||||
  :root {
 | 
			
		||||
    --contrast: 0, 0, 0;
 | 
			
		||||
    --neutral-900: 17, 24, 39;
 | 
			
		||||
    --neutral-800: 31, 41, 55;
 | 
			
		||||
    --neutral-700: 55, 65, 81;
 | 
			
		||||
    --neutral-600: 75, 85, 99;
 | 
			
		||||
    --neutral-500: 107, 114, 128;
 | 
			
		||||
    --neutral-300: 209, 213, 219;
 | 
			
		||||
    --neutral-400: 156, 163, 175;
 | 
			
		||||
    --neutral-200: 229, 231, 235;
 | 
			
		||||
    --accent-600: 101, 163, 13;
 | 
			
		||||
    --accent-500: 132, 204, 22;
 | 
			
		||||
    --accent-400: 163, 230, 53;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
/** @type {import('tailwindcss').Config} */
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
module.exports = {
 | 
			
		||||
  content: ['./src/**/*.{html,js,tsx,jsx,cjs,mjs}'],
 | 
			
		||||
  theme: {
 | 
			
		||||
    extend: {},
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [require('tailwind-scrollbar')],
 | 
			
		||||
}
 | 
			
		||||
@@ -27,4 +27,4 @@
 | 
			
		||||
    "noImplicitOverride": true
 | 
			
		||||
    // "noImplicitReturns": true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||