mirror of
				https://github.com/C4illin/ConvertX.git
				synced 2025-10-31 03:53:30 +00:00 
			
		
		
		
	Compare commits
	
		
			97 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | f8f90ebd69 | ||
|  | 648d5070e2 | ||
|  | 801cf28d1e | ||
|  | fae2ba9c54 | ||
|  | 10d20a8786 | ||
|  | c9bc1e237e | ||
|  | 48a76a46b3 | ||
|  | 4dcb796e1b | ||
|  | 5952103bcd | ||
|  | fd05ee5cd5 | ||
|  | 7e7d238c7a | ||
|  | 755a4170f2 | ||
|  | 263ba415f5 | ||
|  | 317260098a | ||
|  | 5304e94b4e | ||
|  | aab2b311cf | ||
|  | baa7ea40e6 | ||
|  | 481a11b610 | ||
|  | c09fe296b1 | ||
|  | f023aae753 | ||
|  | 5cd9544b55 | ||
|  | 97c23ba65c | ||
|  | 0ffda40ac8 | ||
|  | cb639907ee | ||
|  | 0166842b78 | ||
|  | 277a35b5df | ||
|  | 42124e08b2 | ||
|  | 13169574f0 | ||
|  | 6fb07f0d13 | ||
|  | 8121114ccb | ||
|  | 81881af1c1 | ||
|  | bbb4117e9d | ||
|  | d7ec8179d8 | ||
|  | cef60afee3 | ||
|  | ccf116acde | ||
|  | f609984c90 | ||
|  | 663e654c80 | ||
|  | 31050dbf66 | ||
|  | 8918c418f4 | ||
|  | 56266e0da8 | ||
|  | a3f5b5153a | ||
|  | 6b4c7a16e0 | ||
|  | 7c7756713b | ||
|  | 15d4233a82 | ||
|  | a1411a7559 | ||
|  | 853a4c4f32 | ||
|  | b05b0a14b0 | ||
|  | ff680cb295 | ||
|  | 31e1a3124c | ||
|  | 70278ef0b6 | ||
|  | 0a33fb32e7 | ||
|  | 26f52a5122 | ||
|  | 8f8de4295a | ||
|  | 660b342f2e | ||
|  | 6306a99740 | ||
|  | 5ca6f45809 | ||
|  | 3ea3e1dd01 | ||
|  | 2fddfbe24a | ||
|  | 25df58ba82 | ||
|  | 249bccdc7d | ||
|  | ec0e2db0e9 | ||
|  | ef9b68e0da | ||
|  | 31789738fc | ||
|  | 5fa349a80e | ||
|  | 5dfd0f6f44 | ||
|  | bfa6301570 | ||
|  | 3ea52c4faf | ||
|  | 391e62bfee | ||
|  | 4d1da58f74 | ||
|  | 6dec9ae93b | ||
|  | b466a6de99 | ||
|  | 186681ef44 | ||
|  | 1e2273b7c4 | ||
|  | 8d17f59a58 | ||
|  | d8fcd15aeb | ||
|  | 8cc0eee254 | ||
|  | e4b69023d9 | ||
|  | 7d40890636 | ||
|  | 3ecd2c62ae | ||
|  | 16cabab0d0 | ||
|  | 3e1c9e147f | ||
|  | 5e7a0f5634 | ||
|  | 61b02206c0 | ||
|  | e19a32fc6b | ||
|  | 1712fea1d3 | ||
|  | 1ac4808a64 | ||
|  | 84fd5367ce | ||
|  | 337cfdc15b | ||
|  | b979bd4f13 | ||
|  | 3cab902752 | ||
|  | 1d0dd2a69f | ||
|  | 17f439210a | ||
|  | 9970fd3f89 | ||
|  | 8b7bcceb7b | ||
|  | 93ebdabf6f | ||
|  | 0b278c989b | ||
|  | 518e771afe | 
							
								
								
									
										32
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @@ -5,19 +5,19 @@ | ||||
|  | ||||
| version: 2 | ||||
| updates: | ||||
|   # Maintain dependencies for npm | ||||
|   - package-ecosystem: "npm" # See documentation for possible values | ||||
|     versioning-strategy: increase | ||||
|     directory: "/" # Location of package manifests | ||||
|     schedule: | ||||
|       interval: "daily" | ||||
|   # Maintain dependencies for GitHub Actions | ||||
|   - package-ecosystem: "github-actions" | ||||
|     directory: "/" | ||||
|     schedule: | ||||
|       interval: "daily" | ||||
|   # Maintain dependencies for Docker | ||||
|   - package-ecosystem: "docker" | ||||
|     directory: "/" | ||||
|     schedule: | ||||
|       interval: "daily" | ||||
| - package-ecosystem: npm | ||||
|   versioning-strategy: increase | ||||
|   directory: "/" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|   commit-message: | ||||
|     prefix: "build" | ||||
|     include: "scope" | ||||
|   open-pull-requests-limit: 10 | ||||
| - package-ecosystem: github-actions | ||||
|   directory: "/" | ||||
|   schedule: | ||||
|     interval: weekly | ||||
|   commit-message: | ||||
|     prefix: "build" | ||||
|     include: "scope" | ||||
							
								
								
									
										2
									
								
								.github/workflows/bun-dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/bun-dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @@ -14,7 +14,7 @@ jobs: | ||||
|     if: github.actor == 'dependabot[bot]' | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: oven-sh/setup-bun@v1 | ||||
|       - uses: oven-sh/setup-bun@v2 | ||||
|       - uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/docker-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/docker-publish.yml
									
									
									
									
										vendored
									
									
								
							| @@ -58,7 +58,7 @@ jobs: | ||||
|       # https://github.com/docker/build-push-action | ||||
|       - name: Build and push Docker image | ||||
|         id: build-and-push | ||||
|         uses: docker/build-push-action@v5 | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           context: . | ||||
|           push: ${{ github.event_name != 'pull_request' }} | ||||
|   | ||||
							
								
								
									
										5
									
								
								.github/workflows/release-please.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/release-please.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,7 +18,8 @@ jobs: | ||||
|           # this assumes that you have created a personal access token | ||||
|           # (PAT) and configured it as a GitHub action secret named | ||||
|           # `MY_RELEASE_PLEASE_TOKEN` (this secret name is not important). | ||||
|           token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }} | ||||
|           # token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }} | ||||
|           token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           # this is a built-in strategy in release-please, see "Action Inputs" | ||||
|           # for more options | ||||
|           release-type: node | ||||
|           release-type: node | ||||
|   | ||||
							
								
								
									
										21
									
								
								.github/workflows/remove-docker-tag.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/remove-docker-tag.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| name: Remove Docker Tag | ||||
|  | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|  | ||||
| jobs: | ||||
|   remove-docker-tag: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. | ||||
|     # (required) | ||||
|     permissions: | ||||
|       contents: read | ||||
|       packages: write | ||||
|  | ||||
|     steps: | ||||
|     - name: Remove Docker Tag | ||||
|       uses: ArchieAtkinson/remove-dockertag-action@v0.0 | ||||
|       with: | ||||
|         tag_name: master | ||||
|         github_token: ${{ secrets.GITHUB_TOKEN }} | ||||
							
								
								
									
										31
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,36 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## [0.3.0](https://github.com/C4illin/ConvertX/compare/v0.2.0...v0.3.0) (2024-06-27) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add version number to log ([4dcb796](https://github.com/C4illin/ConvertX/commit/4dcb796e1bd27badc078d0638076cd9f1e81c4a4)), closes [#44](https://github.com/C4illin/ConvertX/issues/44) | ||||
| * change to xelatex ([fae2ba9](https://github.com/C4illin/ConvertX/commit/fae2ba9c54461dccdccd1bfb5e76398540d11d0b)) | ||||
| * print version of installed converters to log ([801cf28](https://github.com/C4illin/ConvertX/commit/801cf28d1e5edac9353b0b16be75a4fb48470b8a)) | ||||
|  | ||||
| ## [0.2.0](https://github.com/C4illin/ConvertX/compare/v0.1.2...v0.2.0) (2024-06-20) | ||||
|  | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| * add libjxl for jpegxl conversion ([ff680cb](https://github.com/C4illin/ConvertX/commit/ff680cb29534a25c3148a90fd064bb86c71fb482)) | ||||
| * change from debian to alpine ([1316957](https://github.com/C4illin/ConvertX/commit/13169574f0134ae236f8d41287bb73930b575e82)), closes [#34](https://github.com/C4illin/ConvertX/issues/34) | ||||
|  | ||||
| ## [0.1.2](https://github.com/C4illin/ConvertX/compare/v0.1.1...v0.1.2) (2024-06-10) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * fix incorrect redirect ([25df58b](https://github.com/C4illin/ConvertX/commit/25df58ba82321aaa6617811a6995cb96c2a00a40)), closes [#23](https://github.com/C4illin/ConvertX/issues/23) | ||||
|  | ||||
| ## [0.1.1](https://github.com/C4illin/ConvertX/compare/v0.1.0...v0.1.1) (2024-05-30) | ||||
|  | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| * :bug: make sure all redirects are 302 ([9970fd3](https://github.com/C4illin/ConvertX/commit/9970fd3f89190af96f8762edc3817d1e03082b3a)), closes [#12](https://github.com/C4illin/ConvertX/issues/12) | ||||
|  | ||||
| ## 0.1.0 (2024-05-30) | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										63
									
								
								Debian.Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								Debian.Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| FROM oven/bun:1-debian as base | ||||
| WORKDIR /app | ||||
|  | ||||
| # install dependencies into temp directory | ||||
| # this will cache them and speed up future builds | ||||
| FROM base AS install | ||||
| RUN mkdir -p /temp/dev | ||||
| COPY package.json bun.lockb /temp/dev/ | ||||
| RUN cd /temp/dev && bun install --frozen-lockfile | ||||
|  | ||||
| # install with --production (exclude devDependencies) | ||||
| RUN mkdir -p /temp/prod | ||||
| COPY package.json bun.lockb /temp/prod/ | ||||
| RUN cd /temp/prod && bun install --frozen-lockfile --production | ||||
|  | ||||
| # FROM base AS install-libjxl-tools | ||||
| # download | ||||
|  | ||||
|  | ||||
|  | ||||
| # copy node_modules from temp directory | ||||
| # then copy all (non-ignored) project files into the image | ||||
| # FROM base AS prerelease | ||||
| # COPY --from=install /temp/dev/node_modules node_modules | ||||
| # COPY . . | ||||
|  | ||||
| # # [optional] tests & build | ||||
| # ENV NODE_ENV=production | ||||
| # RUN bun test | ||||
| # RUN bun run build | ||||
|  | ||||
| # copy production dependencies and source code into final image | ||||
| FROM base AS release | ||||
| LABEL maintainer="Emrik Östling (C4illin)" | ||||
| LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats." | ||||
| LABEL repo="https://github.com/C4illin/ConvertX" | ||||
|  | ||||
| # install additional dependencies | ||||
| RUN rm -rf /var/lib/apt/lists/partial && apt-get update -o Acquire::CompressionTypes::Order::=gz \ | ||||
|   && apt-get install -y \ | ||||
|   pandoc \ | ||||
|   texlive-latex-recommended \ | ||||
|   texlive-fonts-recommended \ | ||||
|   texlive-latex-extra \ | ||||
|   ffmpeg \ | ||||
|   graphicsmagick \ | ||||
|   ghostscript \ | ||||
|   libvips-tools | ||||
|  | ||||
| # # libjxl is not available in the official debian repositories | ||||
| # RUN wget https://github.com/libjxl/libjxl/releases/download/v0.10.2/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -O /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz \ | ||||
| #   && mkdir -p /tmp/libjxl \ | ||||
| #   && tar -xvf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -C /tmp/libjxl \ | ||||
| #   && dpkg -i /tmp/libjxl/libjxl_0.10.2_amd64.deb /tmp/libjxl/jxl_0.10.2_amd64.deb \ | ||||
| #   && rm -rf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz /tmp/libjxl | ||||
|  | ||||
| COPY --from=install /temp/prod/node_modules node_modules | ||||
| # COPY --from=prerelease /app/src/index.tsx /app/src/ | ||||
| # COPY --from=prerelease /app/package.json . | ||||
| COPY . . | ||||
|  | ||||
| EXPOSE 3000/tcp | ||||
| ENTRYPOINT [ "bun", "run", "./src/index.tsx" ] | ||||
							
								
								
									
										18
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| FROM oven/bun:1-debian as base | ||||
| FROM oven/bun:1-alpine as base | ||||
| WORKDIR /app | ||||
|  | ||||
| # install dependencies into temp directory | ||||
| @@ -31,16 +31,19 @@ LABEL description="ConvertX: self-hosted online file converter supporting 700+ f | ||||
| LABEL repo="https://github.com/C4illin/ConvertX" | ||||
|  | ||||
| # install additional dependencies | ||||
| RUN rm -rf /var/lib/apt/lists/partial && apt-get update -o Acquire::CompressionTypes::Order::=gz \ | ||||
|   && apt-get install -y \ | ||||
| RUN apk --no-cache add  \ | ||||
|   pandoc \ | ||||
|   texlive-latex-recommended \ | ||||
|   texlive-fonts-recommended \ | ||||
|   texlive-latex-extra \ | ||||
|   texlive \ | ||||
|   texlive-xetex \ | ||||
|   texmf-dist-latexextra \ | ||||
|   ffmpeg \ | ||||
|   graphicsmagick \ | ||||
|   ghostscript \ | ||||
|   libvips-tools | ||||
|   vips-tools \ | ||||
|   libjxl-tools | ||||
|  | ||||
| # this might be needed for some latex use cases, will add it if needed. | ||||
| #   texmf-dist-fontsextra \ | ||||
|  | ||||
| COPY --from=install /temp/prod/node_modules node_modules | ||||
| # COPY --from=prerelease /app/src/index.tsx /app/src/ | ||||
| @@ -48,4 +51,5 @@ COPY --from=install /temp/prod/node_modules node_modules | ||||
| COPY . . | ||||
|  | ||||
| EXPOSE 3000/tcp | ||||
| ENV NODE_ENV=production | ||||
| ENTRYPOINT [ "bun", "run", "./src/index.tsx" ] | ||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,8 +1,13 @@ | ||||
|  | ||||
| # 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 831 different formats. Written with Typescript, Bun and Elysia. | ||||
| A self-hosted online file converter. Supports 831 different formats. Written with TypeScript, Bun and Elysia. | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| @@ -12,13 +17,14 @@ A self-hosted online file converter. Supports 831 different formats. Written wit | ||||
|  | ||||
| ## Converters supported | ||||
|  | ||||
| | Converter      | Use case      | Converts from | Converts to | | ||||
| |----------------|---------------|---------------|-------------| | ||||
| | Vips           | Images (fast) | 45            | 23          | | ||||
| | PDFLaTeX       | Documents     | 1             | 1           | | ||||
| | Pandoc         | Documents     | 43            | 65          | | ||||
| | GraphicsMagick | Images        | 166           | 133         | | ||||
| | FFmpeg         | Video         | ~473          | ~280        | | ||||
| | Converter                                                                    | Use case      | Converts from | Converts to | | ||||
| |------------------------------------------------------------------------------|---------------|---------------|-------------| | ||||
| | [libjxl](https://github.com/libjxl/libjxl)                                   | JPEG XL       | 11            | 11          | | ||||
| | [Vips](https://github.com/libvips/libvips)                                   | Images        | 45            | 23          | | ||||
| | [XeLaTeX](https://tug.org/xetex/)                                            | Documents     | 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 --> | ||||
|  | ||||
| @@ -28,7 +34,7 @@ A self-hosted online file converter. Supports 831 different formats. Written wit | ||||
| # docker-compose.yml | ||||
| services: | ||||
|   convertx:  | ||||
|     image: ghcr.io/c4illin/convertx:main | ||||
|     image: ghcr.io/c4illin/convertx | ||||
|     ports: | ||||
|       - "3000:3000" | ||||
|     environment: # Defaults are listed below. All are optional. | ||||
| @@ -60,6 +66,7 @@ Tutorial in french: https://belginux.com/installer-convertx-avec-docker/ | ||||
| - [ ] Divide index.tsx into smaller components | ||||
| - [ ] Add tests | ||||
| - [ ] Add searchable list of formats | ||||
| - [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible. | ||||
|  | ||||
| ## Contributors | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ services: | ||||
|   convertx: | ||||
|     build: | ||||
|       context: . | ||||
|       # dockerfile: Debian.Dockerfile | ||||
|     volumes: | ||||
|       - ./data:/app/data | ||||
|     environment: | ||||
|   | ||||
							
								
								
									
										25
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "convertx-frontend", | ||||
|   "version": "0.1.0", | ||||
|   "version": "0.3.0", | ||||
|   "scripts": { | ||||
|     "dev": "bun run --watch src/index.tsx", | ||||
|     "hot": "bun run --hot src/index.tsx", | ||||
| @@ -12,28 +12,31 @@ | ||||
|     "@elysiajs/html": "^1.0.2", | ||||
|     "@elysiajs/jwt": "^1.0.2", | ||||
|     "@elysiajs/static": "^1.0.3", | ||||
|     "elysia": "^1.0.22" | ||||
|     "elysia": "^1.0.25" | ||||
|   }, | ||||
|   "module": "src/index.tsx", | ||||
|   "bun-create": { | ||||
|     "start": "bun run src/index.tsx" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@biomejs/biome": "1.7.3", | ||||
|     "@ianvs/prettier-plugin-sort-imports": "^4.2.1", | ||||
|     "@biomejs/biome": "1.8.2", | ||||
|     "@ianvs/prettier-plugin-sort-imports": "^4.3.0", | ||||
|     "@kitajs/ts-html-plugin": "^4.0.1", | ||||
|     "@picocss/pico": "^2.0.6", | ||||
|     "@total-typescript/ts-reset": "^0.5.1", | ||||
|     "@types/bun": "^1.1.3", | ||||
|     "@types/bun": "^1.1.6", | ||||
|     "@types/eslint": "^8.56.10", | ||||
|     "@types/node": "^20.12.13", | ||||
|     "@types/node": "^20.14.9", | ||||
|     "@types/ws": "^8.5.10", | ||||
|     "@typescript-eslint/eslint-plugin": "^7.11.0", | ||||
|     "@typescript-eslint/parser": "^7.11.0", | ||||
|     "@typescript-eslint/eslint-plugin": "^7.14.1", | ||||
|     "@typescript-eslint/parser": "^7.14.1", | ||||
|     "cpy-cli": "^5.0.0", | ||||
|     "eslint-config-prettier": "^9.1.0", | ||||
|     "eslint-plugin-prettier": "^5.1.3", | ||||
|     "prettier": "^3.2.5", | ||||
|     "typescript": "^5.4.5" | ||||
|   } | ||||
|     "prettier": "^3.3.2", | ||||
|     "typescript": "^5.5.2" | ||||
|   }, | ||||
|   "trustedDependencies": [ | ||||
|     "@biomejs/biome" | ||||
|   ] | ||||
| } | ||||
|   | ||||
| @@ -50,4 +50,4 @@ export const Header = ({ | ||||
|       </nav> | ||||
|     </header> | ||||
|   ); | ||||
| }; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										71
									
								
								src/converters/libjxl.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/converters/libjxl.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +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"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| @@ -16,9 +16,14 @@ import { | ||||
| } from "./graphicsmagick"; | ||||
|  | ||||
| import { | ||||
|   convert as convertPdflatex, | ||||
|   properties as propertiesPdflatex, | ||||
| } from "./pdflatex"; | ||||
|   convert as convertxelatex, | ||||
|   properties as propertiesxelatex, | ||||
| } from "./xelatex"; | ||||
|  | ||||
| import { | ||||
|   convert as convertLibjxl, | ||||
|   properties as propertiesLibjxl, | ||||
| } from "./libjxl"; | ||||
|  | ||||
| import { normalizeFiletype } from "../helpers/normalizeFiletype"; | ||||
|  | ||||
| @@ -50,13 +55,17 @@ const properties: { | ||||
|     ) => any; | ||||
|   }; | ||||
| } = { | ||||
|   libjxl: { | ||||
|     properties: propertiesLibjxl, | ||||
|     converter: convertLibjxl, | ||||
|   }, | ||||
|   vips: { | ||||
|     properties: propertiesImage, | ||||
|     converter: convertImage, | ||||
|   }, | ||||
|   pdflatex: { | ||||
|     properties: propertiesPdflatex, | ||||
|     converter: convertPdflatex, | ||||
|   xelatex: { | ||||
|     properties: propertiesxelatex, | ||||
|     converter: convertxelatex, | ||||
|   }, | ||||
|   pandoc: { | ||||
|     properties: propertiesPandoc, | ||||
| @@ -102,9 +111,8 @@ export async function mainConverter( | ||||
|  | ||||
|       for (const key in converterObj.properties.from) { | ||||
|         if ( | ||||
|           // HOW?? | ||||
|           converterObj.properties.from[key].includes(fileType) && | ||||
|           converterObj.properties.to[key].includes(convertTo) | ||||
|           converterObj?.properties?.from[key]?.includes(fileType) && | ||||
|           converterObj?.properties?.to[key]?.includes(convertTo) | ||||
|         ) { | ||||
|           converterFunc = converterObj.converter; | ||||
|           break; | ||||
| @@ -208,9 +216,9 @@ for (const converterName in properties) { | ||||
|  | ||||
|   for (const key in converterProperties.to) { | ||||
|     if (allTargets[converterName]) { | ||||
|       allTargets[converterName].push(...converterProperties.to[key]); | ||||
|       allTargets[converterName].push(...(converterProperties.to[key] || [])); | ||||
|     } else { | ||||
|       allTargets[converterName] = converterProperties.to[key]; | ||||
|       allTargets[converterName] = converterProperties.to[key] || []; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -229,9 +237,9 @@ for (const converterName in properties) { | ||||
|  | ||||
|   for (const key in converterProperties.from) { | ||||
|     if (allInputs[converterName]) { | ||||
|       allInputs[converterName].push(...converterProperties.from[key]); | ||||
|       allInputs[converterName].push(...(converterProperties.from[key] || [])); | ||||
|     } else { | ||||
|       allInputs[converterName] = converterProperties.from[key]; | ||||
|       allInputs[converterName] = converterProperties.from[key] || []; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -127,9 +127,15 @@ export function convert( | ||||
|   // 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 "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`, | ||||
|       `pandoc ${option} "${filePath}" -f ${fileType} -t ${convertTo} -o "${targetPath}"`, | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|   | ||||
| @@ -19,9 +19,13 @@ export function convert( | ||||
| ): 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("./", "") | ||||
|     const outputPath = targetPath | ||||
|       .split("/") | ||||
|       .slice(0, -1) | ||||
|       .join("/") | ||||
|       .replace("./", ""); | ||||
|     exec( | ||||
|       `pdflatex -interaction=nonstopmode -output-directory="${outputPath}" "${filePath}"`, | ||||
|       `latexmk -xelatex -interaction=nonstopmode -output-directory="${outputPath}" "${filePath}"`, | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
							
								
								
									
										75
									
								
								src/helpers/printVersions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/helpers/printVersions.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| import { exec } from "node:child_process"; | ||||
| import { version } from "../../package.json"; | ||||
| console.log(`ConvertX v${version}`); | ||||
|  | ||||
| if (process.env.NODE_ENV === "production") { | ||||
|   exec("cat /etc/os-release", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Not running on docker, this is not supported."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split('PRETTY_NAME="')[1]?.split('"')[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("pandoc -v", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Pandoc is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("ffmpeg -version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("FFmpeg is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("vips -v", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Vips is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("gm version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("GraphicsMagick is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("djxl --version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("libjxl-tools is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("xelatex -version", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Tex Live with XeTeX is not installed."); | ||||
|     } | ||||
|  | ||||
|     if (stdout) { | ||||
|       console.log(stdout.split("\n")[0]); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| @@ -19,6 +19,10 @@ import { | ||||
|   normalizeFiletype, | ||||
|   normalizeOutputFiletype, | ||||
| } from "./helpers/normalizeFiletype"; | ||||
| import "./helpers/printVersions"; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| const db = new Database("./data/mydb.sqlite", { create: true }); | ||||
| const uploadsDir = "./data/uploads/"; | ||||
| @@ -121,7 +125,7 @@ const app = new Elysia() | ||||
|   ) | ||||
|   .get("/setup", ({ redirect }) => { | ||||
|     if (!FIRST_RUN) { | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
| @@ -164,7 +168,7 @@ const app = new Elysia() | ||||
|   }) | ||||
|   .get("/register", ({ redirect }) => { | ||||
|     if (!ACCOUNT_REGISTRATION) { | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
| @@ -206,7 +210,7 @@ const app = new Elysia() | ||||
|     "/register", | ||||
|     async ({ body, set, redirect, jwt, cookie: { auth } }) => { | ||||
|       if (!ACCOUNT_REGISTRATION && !FIRST_RUN) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       if (FIRST_RUN) { | ||||
| @@ -253,13 +257,13 @@ const app = new Elysia() | ||||
|         sameSite: "strict", | ||||
|       }); | ||||
|  | ||||
|       return redirect("/"); | ||||
|       return redirect("/", 302); | ||||
|     }, | ||||
|     { body: t.Object({ email: t.String(), password: t.String() }) }, | ||||
|   ) | ||||
|   .get("/login", async ({ jwt, redirect, cookie: { auth } }) => { | ||||
|     if (FIRST_RUN) { | ||||
|       return redirect("/setup"); | ||||
|       return redirect("/setup", 302); | ||||
|     } | ||||
|  | ||||
|     // if already logged in, redirect to home | ||||
| @@ -267,7 +271,7 @@ const app = new Elysia() | ||||
|       const user = await jwt.verify(auth.value); | ||||
|  | ||||
|       if (user) { | ||||
|         return redirect("/"); | ||||
|         return redirect("/", 302); | ||||
|       } | ||||
|  | ||||
|       auth.remove(); | ||||
| @@ -361,7 +365,7 @@ const app = new Elysia() | ||||
|         sameSite: "strict", | ||||
|       }); | ||||
|  | ||||
|       return redirect("/"); | ||||
|       return redirect("/", 302); | ||||
|     }, | ||||
|     { body: t.Object({ email: t.String(), password: t.String() }) }, | ||||
|   ) | ||||
| @@ -370,27 +374,27 @@ const app = new Elysia() | ||||
|       auth.remove(); | ||||
|     } | ||||
|  | ||||
|     return redirect("/login"); | ||||
|     return redirect("/login", 302); | ||||
|   }) | ||||
|   .post("/logoff", ({ redirect, cookie: { auth } }) => { | ||||
|     if (auth?.value) { | ||||
|       auth.remove(); | ||||
|     } | ||||
|  | ||||
|     return redirect("/login"); | ||||
|     return redirect("/login", 302); | ||||
|   }) | ||||
|   .get("/", async ({ jwt, redirect, cookie: { auth, jobId } }) => { | ||||
|     if (FIRST_RUN) { | ||||
|       return redirect("/setup"); | ||||
|       return redirect("/setup", 302); | ||||
|     } | ||||
|  | ||||
|     if (!auth?.value) { | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|     // validate jwt | ||||
|     const user = await jwt.verify(auth.value); | ||||
|     if (!user) { | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|  | ||||
|     // make sure user exists in db | ||||
| @@ -402,7 +406,7 @@ const app = new Elysia() | ||||
|       if (auth?.value) { | ||||
|         auth.remove(); | ||||
|       } | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|  | ||||
|     // create a new job | ||||
| @@ -509,16 +513,16 @@ const app = new Elysia() | ||||
|     "/upload", | ||||
|     async ({ body, redirect, jwt, cookie: { auth, jobId } }) => { | ||||
|       if (!auth?.value) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const user = await jwt.verify(auth.value); | ||||
|       if (!user) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       if (!jobId?.value) { | ||||
|         return redirect("/"); | ||||
|         return redirect("/", 302); | ||||
|       } | ||||
|  | ||||
|       const existingJob = await db | ||||
| @@ -526,7 +530,7 @@ const app = new Elysia() | ||||
|         .get(jobId.value, user.id); | ||||
|  | ||||
|       if (!existingJob) { | ||||
|         return redirect("/"); | ||||
|         return redirect("/", 302); | ||||
|       } | ||||
|  | ||||
|       const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; | ||||
| @@ -557,16 +561,16 @@ const app = new Elysia() | ||||
|     "/delete", | ||||
|     async ({ body, redirect, jwt, cookie: { auth, jobId } }) => { | ||||
|       if (!auth?.value) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const user = await jwt.verify(auth.value); | ||||
|       if (!user) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       if (!jobId?.value) { | ||||
|         return redirect("/"); | ||||
|         return redirect("/", 302); | ||||
|       } | ||||
|  | ||||
|       const existingJob = await db | ||||
| @@ -574,7 +578,7 @@ const app = new Elysia() | ||||
|         .get(jobId.value, user.id); | ||||
|  | ||||
|       if (!existingJob) { | ||||
|         return redirect("/"); | ||||
|         return redirect("/", 302); | ||||
|       } | ||||
|  | ||||
|       const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; | ||||
| @@ -587,16 +591,16 @@ const app = new Elysia() | ||||
|     "/convert", | ||||
|     async ({ body, redirect, jwt, cookie: { auth, jobId } }) => { | ||||
|       if (!auth?.value) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const user = await jwt.verify(auth.value); | ||||
|       if (!user) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       if (!jobId?.value) { | ||||
|         return redirect("/"); | ||||
|         return redirect("/", 302); | ||||
|       } | ||||
|  | ||||
|       const existingJob = (await db | ||||
| @@ -604,7 +608,7 @@ const app = new Elysia() | ||||
|         .get(jobId.value, user.id)) as IJobs; | ||||
|  | ||||
|       if (!existingJob) { | ||||
|         return redirect("/"); | ||||
|         return redirect("/", 302); | ||||
|       } | ||||
|  | ||||
|       const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; | ||||
| @@ -627,7 +631,7 @@ const app = new Elysia() | ||||
|       const fileNames = JSON.parse(body.file_names) as string[]; | ||||
|  | ||||
|       if (!Array.isArray(fileNames) || fileNames.length === 0) { | ||||
|         return redirect("/"); | ||||
|         return redirect("/", 302); | ||||
|       } | ||||
|  | ||||
|       db.run( | ||||
| @@ -677,7 +681,7 @@ const app = new Elysia() | ||||
|         }); | ||||
|  | ||||
|       // Redirect the client immediately | ||||
|       return redirect(`/results/${jobId.value}`); | ||||
|       return redirect(`/results/${jobId.value}`, 302); | ||||
|     }, | ||||
|     { | ||||
|       body: t.Object({ | ||||
| @@ -688,12 +692,12 @@ const app = new Elysia() | ||||
|   ) | ||||
|   .get("/history", async ({ jwt, redirect, cookie: { auth } }) => { | ||||
|     if (!auth?.value) { | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|     const user = await jwt.verify(auth.value); | ||||
|  | ||||
|     if (!user) { | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|  | ||||
|     let userJobs = db | ||||
| @@ -751,7 +755,7 @@ const app = new Elysia() | ||||
|     "/results/:jobId", | ||||
|     async ({ params, jwt, set, redirect, cookie: { auth, job_id } }) => { | ||||
|       if (!auth?.value) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       if (job_id?.value) { | ||||
| @@ -761,7 +765,7 @@ const app = new Elysia() | ||||
|  | ||||
|       const user = await jwt.verify(auth.value); | ||||
|       if (!user) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const job = (await db | ||||
| @@ -846,7 +850,7 @@ const app = new Elysia() | ||||
|     "/progress/:jobId", | ||||
|     async ({ jwt, set, params, redirect, cookie: { auth, job_id } }) => { | ||||
|       if (!auth?.value) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       if (job_id?.value) { | ||||
| @@ -856,7 +860,7 @@ const app = new Elysia() | ||||
|  | ||||
|       const user = await jwt.verify(auth.value); | ||||
|       if (!user) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const job = (await db | ||||
| @@ -934,12 +938,12 @@ const app = new Elysia() | ||||
|     "/download/:userId/:jobId/:fileName", | ||||
|     async ({ params, jwt, redirect, cookie: { auth } }) => { | ||||
|       if (!auth?.value) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const user = await jwt.verify(auth.value); | ||||
|       if (!user) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const job = await db | ||||
| @@ -947,7 +951,7 @@ const app = new Elysia() | ||||
|         .get(user.id, params.jobId); | ||||
|  | ||||
|       if (!job) { | ||||
|         return redirect("/results"); | ||||
|         return redirect("/results", 302); | ||||
|       } | ||||
|       // parse from url encoded string | ||||
|       const userId = decodeURIComponent(params.userId); | ||||
| @@ -960,12 +964,12 @@ const app = new Elysia() | ||||
|   ) | ||||
|   .get("/converters", async ({ jwt, redirect, cookie: { auth } }) => { | ||||
|     if (!auth?.value) { | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|  | ||||
|     const user = await jwt.verify(auth.value); | ||||
|     if (!user) { | ||||
|       return redirect("/login"); | ||||
|       return redirect("/login", 302); | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
| @@ -1022,12 +1026,12 @@ const app = new Elysia() | ||||
|     async ({ params, jwt, redirect, cookie: { auth } }) => { | ||||
|       // TODO: Implement zip download | ||||
|       if (!auth?.value) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const user = await jwt.verify(auth.value); | ||||
|       if (!user) { | ||||
|         return redirect("/login"); | ||||
|         return redirect("/login", 302); | ||||
|       } | ||||
|  | ||||
|       const job = await db | ||||
| @@ -1035,7 +1039,7 @@ const app = new Elysia() | ||||
|         .get(user.id, params.jobId); | ||||
|  | ||||
|       if (!job) { | ||||
|         return redirect("/results"); | ||||
|         return redirect("/results", 302); | ||||
|       } | ||||
|  | ||||
|       const userId = decodeURIComponent(params.userId); | ||||
|   | ||||
| @@ -1,9 +1,3 @@ | ||||
| article { | ||||
|   /* height: 300px; */ | ||||
|   /* width: 300px; */ | ||||
|  | ||||
| } | ||||
|  | ||||
| div.icon { | ||||
|   height: 100px; | ||||
|   width: 100px; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user