mirror of
				https://github.com/C4illin/ConvertX.git
				synced 2025-10-31 03:53:30 +00:00 
			
		
		
		
	Compare commits
	
		
			80 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 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 | 
							
								
								
									
										15
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										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'] | ||||
							
								
								
									
										42
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,47 @@ | ||||
| # 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 ([bed52ce](https://github.com/C4illin/ConvertX/commit/bed52cef17ff68ec5e8770705a1fdf038e02e607)) | ||||
| * 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 ([a5eaaa4](https://github.com/C4illin/ConvertX/commit/a5eaaa422a64506dd16d90d48a240556de33bc93)) | ||||
| * 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) | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										21
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -20,10 +20,7 @@ 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 | ||||
| # will switch to alpine again when it works | ||||
| FROM oven/bun:1.2.2-slim AS prerelease | ||||
| FROM base AS prerelease | ||||
| WORKDIR /app | ||||
| COPY --from=install /temp/dev/node_modules node_modules | ||||
| COPY . . | ||||
| @@ -33,9 +30,8 @@ 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,18 @@ RUN apk --no-cache add  \ | ||||
|   vips-tools \ | ||||
|   vips-poppler \ | ||||
|   vips-jxl \ | ||||
|   vips-heif \ | ||||
|   vips-magick \ | ||||
|   libjxl-tools \ | ||||
|   assimp \ | ||||
|   inkscape \ | ||||
|   poppler-utils \ | ||||
|   gcompat \ | ||||
|   libva-utils \ | ||||
|   py3-numpy | ||||
|   py3-numpy \ | ||||
|   potrace | ||||
|  | ||||
| RUN apk --no-cache add qt6-qtbase-private-dev --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ | ||||
|  | ||||
| RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/ | ||||
| # 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 \ | ||||
| @@ -67,8 +64,6 @@ RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine | ||||
| COPY --from=install /temp/prod/node_modules node_modules | ||||
| COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg | ||||
| COPY --from=prerelease /app/public/generated.css /app/public/ | ||||
| # COPY --from=prerelease /app/src/index.tsx /app/src/ | ||||
| # COPY --from=prerelease /app/package.json . | ||||
| COPY . . | ||||
|  | ||||
| EXPOSE 3000/tcp | ||||
|   | ||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							| @@ -27,6 +27,7 @@ A self-hosted online file converter. Supports over a thousand different formats. | ||||
| | [libjxl](https://github.com/libjxl/libjxl)                                   | JPEG XL       | 11            | 11          | | ||||
| | [resvg](https://github.com/RazrFalcon/resvg)                                 | SVG           | 1             | 1           | | ||||
| | [Vips](https://github.com/libvips/libvips)                                   | Images        | 45            | 23          | | ||||
| | [libheif](https://github.com/strukturag/libheif)                             | HEIF          | 2             | 4           | | ||||
| | [XeLaTeX](https://tug.org/xetex/)                                            | LaTeX         | 1             | 1           | | ||||
| | [Calibre](https://calibre-ebook.com/)                                        | E-books       | 26            | 19          | | ||||
| | [Pandoc](https://pandoc.org/)                                                | Documents     | 43            | 65          | | ||||
| @@ -41,6 +42,9 @@ 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: | ||||
| @@ -79,9 +83,7 @@ All are optional, JWT_SECRET is recommended to be set. | ||||
| | 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` | | ||||
|  | ||||
| > [!WARNING] | ||||
| > If you can't login, make sure you are accessing the service over https or set HTTP_ALLOWED=true | ||||
| | HIDE_HISTORY             | false | Hide the history page | | ||||
|  | ||||
| ### Docker images | ||||
|  | ||||
| @@ -96,10 +98,15 @@ The image is available on [GitHub Container Registry](https://github.com/C4illin | ||||
| | `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/> | ||||
|   | ||||
							
								
								
									
										9
									
								
								SECURITY.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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. | ||||
| @@ -13,5 +13,6 @@ services: | ||||
|       - 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 | ||||
|   | ||||
							
								
								
									
										53
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "convertx-frontend", | ||||
|   "version": "0.11.1", | ||||
|   "version": "0.13.0", | ||||
|   "scripts": { | ||||
|     "dev": "bun run --watch src/index.tsx", | ||||
|     "hot": "bun run --hot src/index.tsx", | ||||
| @@ -12,12 +12,12 @@ | ||||
|     "lint:eslint": "eslint ." | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@elysiajs/cookie": "^0.8.0", | ||||
|     "@elysiajs/html": "^1.2.0", | ||||
|     "@elysiajs/jwt": "^1.2.0", | ||||
|     "@elysiajs/static": "^1.2.0", | ||||
|     "@kitajs/html": "^4.2.7", | ||||
|     "elysia": "^1.2.12" | ||||
|     "@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", | ||||
| @@ -25,31 +25,30 @@ | ||||
|     "start": "bun run src/index.tsx" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@eslint/js": "^9.19.0", | ||||
|     "@eslint/js": "^9.26.0", | ||||
|     "@ianvs/prettier-plugin-sort-imports": "^4.4.1", | ||||
|     "@kitajs/ts-html-plugin": "^4.1.1", | ||||
|     "@tailwindcss/cli": "^4.0.4", | ||||
|     "@tailwindcss/postcss": "^4.0.4", | ||||
|     "@tailwindcss/cli": "^4.1.6", | ||||
|     "@tailwindcss/postcss": "^4.1.6", | ||||
|     "@total-typescript/ts-reset": "^0.6.1", | ||||
|     "@types/bun": "^1.2.2", | ||||
|     "@types/bun": "^1.2.13", | ||||
|     "@types/eslint-plugin-tailwindcss": "^3.17.0", | ||||
|     "@types/eslint__js": "^8.42.3", | ||||
|     "@types/node": "^22.13.1", | ||||
|     "autoprefixer": "^10.4.20", | ||||
|     "cssnano": "^7.0.6", | ||||
|     "eslint": "^9.19.0", | ||||
|     "eslint-plugin-readable-tailwind": "^2.0.0-beta.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": "4.0.0-alpha.0", | ||||
|     "globals": "^15.14.0", | ||||
|     "knip": "^5.43.6", | ||||
|     "npm-run-all2": "^7.0.2", | ||||
|     "postcss": "^8.5.1", | ||||
|     "postcss-cli": "^11.0.0", | ||||
|     "prettier": "^3.4.2", | ||||
|     "tailwind-scrollbar": "^4.0.0", | ||||
|     "tailwindcss": "^4.0.4", | ||||
|     "typescript": "^5.7.3", | ||||
|     "typescript-eslint": "^8.23.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" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										490
									
								
								public/script.js
									
									
									
									
									
								
							
							
						
						
									
										490
									
								
								public/script.js
									
									
									
									
									
								
							| @@ -1,236 +1,254 @@ | ||||
| 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", () => { | ||||
|   dropZone.classList.add("dragover"); | ||||
| }); | ||||
|  | ||||
| dropZone.addEventListener("dragleave", () => { | ||||
|   dropZone.classList.remove("dragover"); | ||||
| }); | ||||
|  | ||||
| dropZone.addEventListener("drop", () => { | ||||
|   dropZone.classList.remove("dragover"); | ||||
| }); | ||||
|  | ||||
| const selectContainer = document.querySelector("form .select_container"); | ||||
|  | ||||
| const updateSearchBar = () => { | ||||
|   const convertToInput = document.querySelector( | ||||
|     "input[name='convert_to_search']", | ||||
|   ); | ||||
|   const convertToPopup = document.querySelector(".convert_to_popup"); | ||||
|   const convertToGroupElements = document.querySelectorAll(".convert_to_group"); | ||||
|   const convertToGroups = {}; | ||||
|   const convertToElement = document.querySelector("select[name='convert_to']"); | ||||
|  | ||||
|   const showMatching = (search) => { | ||||
|     for (const [targets, groupElement] of Object.values(convertToGroups)) { | ||||
|       let matchingTargetsFound = 0; | ||||
|       for (const target of targets) { | ||||
|         if (target.dataset.target.includes(search)) { | ||||
|           matchingTargetsFound++; | ||||
|           target.classList.remove("hidden"); | ||||
|           target.classList.add("flex"); | ||||
|         } else { | ||||
|           target.classList.add("hidden"); | ||||
|           target.classList.remove("flex"); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (matchingTargetsFound === 0) { | ||||
|         groupElement.classList.add("hidden"); | ||||
|         groupElement.classList.remove("flex"); | ||||
|       } else { | ||||
|         groupElement.classList.remove("hidden"); | ||||
|         groupElement.classList.add("flex"); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   for (const groupElement of convertToGroupElements) { | ||||
|     const groupName = groupElement.dataset.converter; | ||||
|  | ||||
|     const targetElements = groupElement.querySelectorAll(".target"); | ||||
|     const targets = Array.from(targetElements); | ||||
|  | ||||
|     for (const target of targets) { | ||||
|       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(""); | ||||
|       }; | ||||
|     } | ||||
|  | ||||
|     convertToGroups[groupName] = [targets, groupElement]; | ||||
|   } | ||||
|  | ||||
|   convertToInput.addEventListener("input", (e) => { | ||||
|     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 | ||||
|     if (e?.relatedTarget?.classList?.contains("target")) { | ||||
|       convertToPopup.classList.add("hidden"); | ||||
|       convertToPopup.classList.remove("flex"); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     convertToPopup.classList.add("hidden"); | ||||
|     convertToPopup.classList.remove("flex"); | ||||
|   }); | ||||
|  | ||||
|   convertToInput.addEventListener("focus", () => { | ||||
|     convertToPopup.classList.remove("hidden"); | ||||
|     convertToPopup.classList.add("flex"); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // Add a 'change' event listener to the file input element | ||||
| fileInput.addEventListener("change", (e) => { | ||||
|   // 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 onclick="deleteRow(this)">Remove</a></td> | ||||
|     `; | ||||
|  | ||||
|     if (!fileType) { | ||||
|       fileType = file.name.split(".").pop(); | ||||
|       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(`${webroot}/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); | ||||
|   } | ||||
|  | ||||
|   uploadFiles(files); | ||||
| }); | ||||
|  | ||||
| const setTitle = () => { | ||||
|   const title = document.querySelector("h1"); | ||||
|   title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`; | ||||
| }; | ||||
|  | ||||
| // 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; | ||||
|   row.remove(); | ||||
|  | ||||
|   // remove from fileNames | ||||
|   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(`${webroot}/delete`, { | ||||
|     method: "POST", | ||||
|     body: JSON.stringify({ filename: filename }), | ||||
|     headers: { | ||||
|       "Content-Type": "application/json", | ||||
|     }, | ||||
|   }) | ||||
|     .catch((err) => console.log(err)); | ||||
| }; | ||||
|  | ||||
| const uploadFiles = (files) => { | ||||
|   convertButton.disabled = true; | ||||
|   convertButton.textContent = "Uploading..."; | ||||
|   pendingFiles += 1; | ||||
|  | ||||
|   const formData = new FormData(); | ||||
|  | ||||
|   for (const file of files) { | ||||
|     formData.append("file", file, file.name); | ||||
|   } | ||||
|  | ||||
|   fetch(`${webroot}/upload`, { | ||||
|     method: "POST", | ||||
|     body: formData, | ||||
|   }) | ||||
|     .then((res) => res.json()) | ||||
|     .then((data) => { | ||||
|       pendingFiles -= 1; | ||||
|       if (pendingFiles === 0) { | ||||
|         if (formatSelected) { | ||||
|           convertButton.disabled = false; | ||||
|         } | ||||
|         convertButton.textContent = "Convert"; | ||||
|       } | ||||
|       console.log(data); | ||||
|     }) | ||||
|     .catch((err) => console.log(err)); | ||||
| }; | ||||
|  | ||||
| const formConvert = document.querySelector(`form[action='${webroot}/convert']`); | ||||
|  | ||||
| formConvert.addEventListener("submit", () => { | ||||
|   const hiddenInput = document.querySelector("input[name='file_names']"); | ||||
|   hiddenInput.value = JSON.stringify(fileNames); | ||||
| }); | ||||
|  | ||||
| updateSearchBar(); | ||||
| 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", () => { | ||||
|   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 = () => { | ||||
|   const convertToInput = document.querySelector( | ||||
|     "input[name='convert_to_search']", | ||||
|   ); | ||||
|   const convertToPopup = document.querySelector(".convert_to_popup"); | ||||
|   const convertToGroupElements = document.querySelectorAll(".convert_to_group"); | ||||
|   const convertToGroups = {}; | ||||
|   const convertToElement = document.querySelector("select[name='convert_to']"); | ||||
|  | ||||
|   const showMatching = (search) => { | ||||
|     for (const [targets, groupElement] of Object.values(convertToGroups)) { | ||||
|       let matchingTargetsFound = 0; | ||||
|       for (const target of targets) { | ||||
|         if (target.dataset.target.includes(search)) { | ||||
|           matchingTargetsFound++; | ||||
|           target.classList.remove("hidden"); | ||||
|           target.classList.add("flex"); | ||||
|         } else { | ||||
|           target.classList.add("hidden"); | ||||
|           target.classList.remove("flex"); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       if (matchingTargetsFound === 0) { | ||||
|         groupElement.classList.add("hidden"); | ||||
|         groupElement.classList.remove("flex"); | ||||
|       } else { | ||||
|         groupElement.classList.remove("hidden"); | ||||
|         groupElement.classList.add("flex"); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   for (const groupElement of convertToGroupElements) { | ||||
|     const groupName = groupElement.dataset.converter; | ||||
|  | ||||
|     const targetElements = groupElement.querySelectorAll(".target"); | ||||
|     const targets = Array.from(targetElements); | ||||
|  | ||||
|     for (const target of targets) { | ||||
|       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(""); | ||||
|       }; | ||||
|     } | ||||
|  | ||||
|     convertToGroups[groupName] = [targets, groupElement]; | ||||
|   } | ||||
|  | ||||
|   convertToInput.addEventListener("input", (e) => { | ||||
|     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 | ||||
|     if (e?.relatedTarget?.classList?.contains("target")) { | ||||
|       convertToPopup.classList.add("hidden"); | ||||
|       convertToPopup.classList.remove("flex"); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     convertToPopup.classList.add("hidden"); | ||||
|     convertToPopup.classList.remove("flex"); | ||||
|   }); | ||||
|  | ||||
|   convertToInput.addEventListener("focus", () => { | ||||
|     convertToPopup.classList.remove("hidden"); | ||||
|     convertToPopup.classList.add("flex"); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| // Add a 'change' event listener to the file input element | ||||
| fileInput.addEventListener("change", (e) => { | ||||
|   const files = e.target.files; | ||||
|   for (const file of files) { | ||||
|     handleFile(file); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| const setTitle = () => { | ||||
|   const title = document.querySelector("h1"); | ||||
|   title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`; | ||||
| }; | ||||
|  | ||||
| // 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; | ||||
|   row.remove(); | ||||
|  | ||||
|   // remove from fileNames | ||||
|   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(`${webroot}/delete`, { | ||||
|     method: "POST", | ||||
|     body: JSON.stringify({ filename: filename }), | ||||
|     headers: { | ||||
|       "Content-Type": "application/json", | ||||
|     }, | ||||
|   }) | ||||
|     .catch((err) => console.log(err)); | ||||
| }; | ||||
|  | ||||
| const uploadFile = (file) => { | ||||
|   convertButton.disabled = true; | ||||
|   convertButton.textContent = "Uploading..."; | ||||
|   pendingFiles += 1; | ||||
|  | ||||
|   const formData = new FormData(); | ||||
|   formData.append("file", file, file.name); | ||||
|  | ||||
|   let xhr = new XMLHttpRequest();  | ||||
|  | ||||
|   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='${webroot}/convert']`); | ||||
|  | ||||
| formConvert.addEventListener("submit", () => { | ||||
|   const hiddenInput = document.querySelector("input[name='file_names']"); | ||||
|   hiddenInput.value = JSON.stringify(fileNames); | ||||
| }); | ||||
|  | ||||
| updateSearchBar(); | ||||
|   | ||||
| @@ -4,28 +4,32 @@ export const Header = ({ | ||||
|   loggedIn, | ||||
|   accountRegistration, | ||||
|   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-accent-600 transition-all | ||||
|               hover:text-accent-500 hover:underline | ||||
|             `} | ||||
|             href={`${webroot}/history`} | ||||
|           > | ||||
|             History | ||||
|           </a> | ||||
|         </li> | ||||
|         {!hideHistory && ( | ||||
|           <li> | ||||
|             <a | ||||
|               class={` | ||||
|                 text-accent-600 transition-all | ||||
|                 hover:text-accent-500 hover:underline | ||||
|               `} | ||||
|               href={`${webroot}/history`} | ||||
|             > | ||||
|               History | ||||
|             </a> | ||||
|           </li> | ||||
|         )} | ||||
|         {!allowUnauthenticated ? ( | ||||
|           <li> | ||||
|             <a | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { exec } from "node:child_process"; | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
| @@ -119,10 +119,8 @@ export async function convert( | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   const command = `assimp export "${filePath}" "${targetPath}"`; | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     exec(command, (error, stdout, stderr) => { | ||||
|     execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { exec } from "node:child_process"; | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
| @@ -64,10 +64,8 @@ export async function convert( | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   const command = `ebook-convert "${filePath}" "${targetPath}"`; | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     exec(command, (error, stdout, stderr) => { | ||||
|     execFile("ebook-convert", [filePath, targetPath], (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { exec } from "node:child_process"; | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| // This could be done dynamically by running `ffmpeg -formats` and parsing the output | ||||
| export const properties = { | ||||
| @@ -691,19 +691,28 @@ export async function convert( | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   let extra = ""; | ||||
|   let extraArgs: string[] = []; | ||||
|   let message = "Done"; | ||||
|  | ||||
|   if (convertTo === "ico") { | ||||
|     // make sure image is 256x256 or smaller | ||||
|     extra = `-filter:v "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease"`; | ||||
|     extraArgs = ['-filter:v', "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease"]; | ||||
|     message = "Done: resized to 256x256"; | ||||
|   } | ||||
|  | ||||
|   const command = `ffmpeg ${process.env.FFMPEG_ARGS || ""} -i "${filePath}" ${extra} "${targetPath}"`; | ||||
|   // Parse FFMPEG_ARGS environment variable into array | ||||
|   const ffmpegArgs = process.env.FFMPEG_ARGS ? process.env.FFMPEG_ARGS.split(/\s+/) : []; | ||||
|    | ||||
|   // Build arguments array | ||||
|   const args = [ | ||||
|     ...ffmpegArgs, | ||||
|     "-i", filePath, | ||||
|     ...extraArgs, | ||||
|     targetPath | ||||
|   ]; | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     exec(command, (error, stdout, stderr) => { | ||||
|     execFile("ffmpeg", args, (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|   | ||||
| @@ -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, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): 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("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     image: [ | ||||
|       "3fr", | ||||
|       "8bim", | ||||
|       "8bimtext", | ||||
|       "8bimwtext", | ||||
|       "app1", | ||||
|       "app1jpeg", | ||||
|       "art", | ||||
|       "arw", | ||||
|       "avs", | ||||
|       "b", | ||||
|       "bie", | ||||
|       "bigtiff", | ||||
|       "bmp", | ||||
|       "c", | ||||
|       "cals", | ||||
|       "caption", | ||||
|       "cin", | ||||
|       "cmyk", | ||||
|       "cmyka", | ||||
|       "cr2", | ||||
|       "crw", | ||||
|       "cur", | ||||
|       "cut", | ||||
|       "dcm", | ||||
|       "dcr", | ||||
|       "dcx", | ||||
|       "dng", | ||||
|       "dpx", | ||||
|       "epdf", | ||||
|       "epi", | ||||
|       "eps", | ||||
|       "epsf", | ||||
|       "epsi", | ||||
|       "ept", | ||||
|       "ept2", | ||||
|       "ept3", | ||||
|       "erf", | ||||
|       "exif", | ||||
|       "fax", | ||||
|       "file", | ||||
|       "fits", | ||||
|       "fractal", | ||||
|       "ftp", | ||||
|       "g", | ||||
|       "gif", | ||||
|       "gif87", | ||||
|       "gradient", | ||||
|       "gray", | ||||
|       "graya", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "hrz", | ||||
|       "http", | ||||
|       "icb", | ||||
|       "icc", | ||||
|       "icm", | ||||
|       "ico", | ||||
|       "icon", | ||||
|       "identity", | ||||
|       "image", | ||||
|       "iptc", | ||||
|       "iptctext", | ||||
|       "iptcwtext", | ||||
|       "jbg", | ||||
|       "jbig", | ||||
|       "jng", | ||||
|       "jnx", | ||||
|       "jpeg", | ||||
|       "jpg", | ||||
|       "k", | ||||
|       "k25", | ||||
|       "kdc", | ||||
|       "label", | ||||
|       "m", | ||||
|       "mac", | ||||
|       "map", | ||||
|       "mat", | ||||
|       "mef", | ||||
|       "miff", | ||||
|       "mng", | ||||
|       "mono", | ||||
|       "mpc", | ||||
|       "mrw", | ||||
|       "msl", | ||||
|       "mtv", | ||||
|       "mvg", | ||||
|       "nef", | ||||
|       "null", | ||||
|       "o", | ||||
|       "orf", | ||||
|       "otb", | ||||
|       "p7", | ||||
|       "pal", | ||||
|       "palm", | ||||
|       "pam", | ||||
|       "pbm", | ||||
|       "pcd", | ||||
|       "pcds", | ||||
|       "pct", | ||||
|       "pcx", | ||||
|       "pdb", | ||||
|       "pdf", | ||||
|       "pef", | ||||
|       "pfa", | ||||
|       "pfb", | ||||
|       "pgm", | ||||
|       "picon", | ||||
|       "pict", | ||||
|       "pix", | ||||
|       "plasma", | ||||
|       "png", | ||||
|       "png00", | ||||
|       "png24", | ||||
|       "png32", | ||||
|       "png48", | ||||
|       "png64", | ||||
|       "png8", | ||||
|       "pnm", | ||||
|       "ppm", | ||||
|       "ps", | ||||
|       "ptif", | ||||
|       "pwp", | ||||
|       "r", | ||||
|       "raf", | ||||
|       "ras", | ||||
|       "rgb", | ||||
|       "rgba", | ||||
|       "rla", | ||||
|       "rle", | ||||
|       "sct", | ||||
|       "sfw", | ||||
|       "sgi", | ||||
|       "sr2", | ||||
|       "srf", | ||||
|       "stegano", | ||||
|       "sun", | ||||
|       "svg", | ||||
|       "svgz", | ||||
|       "text", | ||||
|       "tga", | ||||
|       "tif", | ||||
|       "tiff", | ||||
|       "tile", | ||||
|       "tim", | ||||
|       "topol", | ||||
|       "ttf", | ||||
|       "txt", | ||||
|       "uyvy", | ||||
|       "vda", | ||||
|       "vicar", | ||||
|       "vid", | ||||
|       "viff", | ||||
|       "vst", | ||||
|       "wbmp", | ||||
|       "webp", | ||||
|       "wmf", | ||||
|       "wpg", | ||||
|       "x3f", | ||||
|       "xbm", | ||||
|       "xc", | ||||
|       "xcf", | ||||
|       "xmp", | ||||
|       "xpm", | ||||
|       "xv", | ||||
|       "xwd", | ||||
|       "y", | ||||
|       "yuv", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     image: [ | ||||
|       "8bim", | ||||
|       "8bimtext", | ||||
|       "8bimwtext", | ||||
|       "app1", | ||||
|       "app1jpeg", | ||||
|       "art", | ||||
|       "avs", | ||||
|       "b", | ||||
|       "bie", | ||||
|       "bigtiff", | ||||
|       "bmp", | ||||
|       "bmp2", | ||||
|       "bmp3", | ||||
|       "brf", | ||||
|       "c", | ||||
|       "cals", | ||||
|       "cin", | ||||
|       "cmyk", | ||||
|       "cmyka", | ||||
|       "dcx", | ||||
|       "dpx", | ||||
|       "epdf", | ||||
|       "epi", | ||||
|       "eps", | ||||
|       "eps2", | ||||
|       "eps3", | ||||
|       "epsf", | ||||
|       "epsi", | ||||
|       "ept", | ||||
|       "ept2", | ||||
|       "ept3", | ||||
|       "exif", | ||||
|       "fax", | ||||
|       "fits", | ||||
|       "g", | ||||
|       "gif", | ||||
|       "gif87", | ||||
|       "gray", | ||||
|       "graya", | ||||
|       "histogram", | ||||
|       "html", | ||||
|       "icb", | ||||
|       "icc", | ||||
|       "icm", | ||||
|       "info", | ||||
|       "iptc", | ||||
|       "iptctext", | ||||
|       "iptcwtext", | ||||
|       "isobrl", | ||||
|       "isobrl6", | ||||
|       "jbg", | ||||
|       "jbig", | ||||
|       "jng", | ||||
|       "jpeg", | ||||
|       "k", | ||||
|       "m", | ||||
|       "m2v", | ||||
|       "map", | ||||
|       "mat", | ||||
|       "matte", | ||||
|       "miff", | ||||
|       "mng", | ||||
|       "mono", | ||||
|       "mpc", | ||||
|       "mpeg", | ||||
|       "mpg", | ||||
|       "msl", | ||||
|       "mtv", | ||||
|       "mvg", | ||||
|       "null", | ||||
|       "o", | ||||
|       "otb", | ||||
|       "p7", | ||||
|       "pal", | ||||
|       "pam", | ||||
|       "pbm", | ||||
|       "pcd", | ||||
|       "pcds", | ||||
|       "pcl", | ||||
|       "pct", | ||||
|       "pcx", | ||||
|       "pdb", | ||||
|       "pdf", | ||||
|       "pgm", | ||||
|       "picon", | ||||
|       "pict", | ||||
|       "png", | ||||
|       "png00", | ||||
|       "png24", | ||||
|       "png32", | ||||
|       "png48", | ||||
|       "png64", | ||||
|       "png8", | ||||
|       "pnm", | ||||
|       "ppm", | ||||
|       "preview", | ||||
|       "ps", | ||||
|       "ps2", | ||||
|       "ps3", | ||||
|       "ptif", | ||||
|       "r", | ||||
|       "ras", | ||||
|       "rgb", | ||||
|       "rgba", | ||||
|       "sgi", | ||||
|       "shtml", | ||||
|       "sun", | ||||
|       "text", | ||||
|       "tga", | ||||
|       "tiff", | ||||
|       "txt", | ||||
|       "ubrl", | ||||
|       "ubrl6", | ||||
|       "uil", | ||||
|       "uyvy", | ||||
|       "vda", | ||||
|       "vicar", | ||||
|       "vid", | ||||
|       "viff", | ||||
|       "vst", | ||||
|       "wbmp", | ||||
|       "webp", | ||||
|       "x", | ||||
|       "xbm", | ||||
|       "xmp", | ||||
|       "xpm", | ||||
|       "xv", | ||||
|       "xwd", | ||||
|       "y", | ||||
|       "yuv", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     execFile( | ||||
|       "gm", | ||||
|       ["convert", filePath, targetPath], | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
|  | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
|  | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
|  | ||||
|         resolve("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -1,64 +1,59 @@ | ||||
| import { exec } from "node:child_process"; | ||||
| 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", | ||||
|       ] | ||||
|     }, | ||||
|   }; | ||||
|   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) => { | ||||
|       exec(`inkscape "${filePath}" -o "${targetPath}"`, (error, stdout, stderr) => { | ||||
| 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
									
								
							
							
						
						
									
										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, | ||||
|   // 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) => { | ||||
|     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("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| // declare possible conversions | ||||
| export const properties = { | ||||
|   from: { | ||||
|     jxl: ["jxl"], | ||||
|     images: [ | ||||
|       "apng", | ||||
|       "exr", | ||||
|       "gif", | ||||
|       "jpeg", | ||||
|       "pam", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pgx", | ||||
|       "png", | ||||
|       "ppm", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     jxl: [ | ||||
|       "apng", | ||||
|       "exr", | ||||
|       "gif", | ||||
|       "jpeg", | ||||
|       "pam", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pgx", | ||||
|       "png", | ||||
|       "ppm", | ||||
|     ], | ||||
|     images: ["jxl"], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   let tool = ""; | ||||
|   if (fileType === "jxl") { | ||||
|     tool = "djxl"; | ||||
|   } | ||||
|  | ||||
|   if (convertTo === "jxl") { | ||||
|     tool = "cjxl"; | ||||
|   } | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     execFile(tool, [filePath, targetPath], (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,9 @@ import { convert as convertPandoc, properties as propertiesPandoc } from "./pand | ||||
| 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 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 | ||||
| @@ -53,14 +55,18 @@ const properties: Record< | ||||
|     properties: propertiesImage, | ||||
|     converter: convertImage, | ||||
|   }, | ||||
|   libheif: { | ||||
|     properties: propertiesLibheif, | ||||
|     converter: convertLibheif, | ||||
|   }, | ||||
|   xelatex: { | ||||
|     properties: propertiesxelatex, | ||||
|     converter: convertxelatex, | ||||
|   }, | ||||
|   calibre: { | ||||
|     properties: propertiesCalibre, | ||||
|     converter: convertCalibre, | ||||
|   }, | ||||
|   // calibre: { | ||||
|   //   properties: propertiesCalibre, | ||||
|   //   converter: convertCalibre, | ||||
|   // }, | ||||
|   pandoc: { | ||||
|     properties: propertiesPandoc, | ||||
|     converter: convertPandoc, | ||||
| @@ -81,6 +87,10 @@ const properties: Record< | ||||
|     properties: propertiesFFmpeg, | ||||
|     converter: convertFFmpeg, | ||||
|   }, | ||||
|   potrace: { | ||||
|     properties: propertiespotrace, | ||||
|     converter: convertpotrace, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export async function mainConverter( | ||||
|   | ||||
| @@ -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, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): 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("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     text: [ | ||||
|       "textile", | ||||
|       "tikiwiki", | ||||
|       "tsv", | ||||
|       "twiki", | ||||
|       "typst", | ||||
|       "vimwiki", | ||||
|       "biblatex", | ||||
|       "bibtex", | ||||
|       "bits", | ||||
|       "commonmark", | ||||
|       "commonmark_x", | ||||
|       "creole", | ||||
|       "csljson", | ||||
|       "csv", | ||||
|       "djot", | ||||
|       "docbook", | ||||
|       "docx", | ||||
|       "dokuwiki", | ||||
|       "endnotexml", | ||||
|       "epub", | ||||
|       "fb2", | ||||
|       "gfm", | ||||
|       "haddock", | ||||
|       "html", | ||||
|       "ipynb", | ||||
|       "jats", | ||||
|       "jira", | ||||
|       "json", | ||||
|       "latex", | ||||
|       "man", | ||||
|       "markdown", | ||||
|       "markdown_mmd", | ||||
|       "markdown_phpextra", | ||||
|       "markdown_strict", | ||||
|       "mediawiki", | ||||
|       "muse", | ||||
|       "pandoc native", | ||||
|       "opml", | ||||
|       "org", | ||||
|       "ris", | ||||
|       "rst", | ||||
|       "rtf", | ||||
|       "t2t", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     text: [ | ||||
|       "tei", | ||||
|       "texinfo", | ||||
|       "textile", | ||||
|       "typst", | ||||
|       "xwiki", | ||||
|       "zimwiki", | ||||
|       "asciidoc", | ||||
|       "asciidoc_legacy", | ||||
|       "asciidoctor", | ||||
|       "beamer", | ||||
|       "biblatex", | ||||
|       "bibtex", | ||||
|       "chunkedhtml", | ||||
|       "commonmark", | ||||
|       "commonmark_x", | ||||
|       "context", | ||||
|       "csljson", | ||||
|       "djot", | ||||
|       "docbook", | ||||
|       "docbook4", | ||||
|       "docbook5", | ||||
|       "docx", | ||||
|       "dokuwiki", | ||||
|       "dzslides", | ||||
|       "epub", | ||||
|       "epub2", | ||||
|       "epub3", | ||||
|       "fb2", | ||||
|       "gfm", | ||||
|       "haddock", | ||||
|       "html", | ||||
|       "html4", | ||||
|       "html5", | ||||
|       "icml", | ||||
|       "ipynb", | ||||
|       "jats", | ||||
|       "jats_archiving", | ||||
|       "jats_articleauthoring", | ||||
|       "jats_publishing", | ||||
|       "jira", | ||||
|       "json", | ||||
|       "latex", | ||||
|       "man", | ||||
|       "markdown", | ||||
|       "markdown_mmd", | ||||
|       "markdown_phpextra", | ||||
|       "markdown_strict", | ||||
|       "markua", | ||||
|       "mediawiki", | ||||
|       "ms", | ||||
|       "muse", | ||||
|       "pandoc native", | ||||
|       "odt", | ||||
|       "opendocument", | ||||
|       "opml", | ||||
|       "org", | ||||
|       "pdf", | ||||
|       "plain", | ||||
|       "pptx", | ||||
|       "revealjs", | ||||
|       "rst", | ||||
|       "rtf", | ||||
|       "s5", | ||||
|       "slideous", | ||||
|       "slidy", | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   // set xelatex here | ||||
|   const xelatex = ["pdf", "latex"]; | ||||
|  | ||||
|   // Build arguments array | ||||
|   const args: string[] = []; | ||||
|  | ||||
|   if (xelatex.includes(convertTo)) { | ||||
|     args.push("--pdf-engine=xelatex"); | ||||
|   } | ||||
|  | ||||
|   args.push(filePath); | ||||
|   args.push("-f", fileType); | ||||
|   args.push("-t", convertTo); | ||||
|   args.push("-o", targetPath); | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     execFile("pandoc", args, (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|   | ||||
							
								
								
									
										37
									
								
								src/converters/potrace.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): 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("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: ["svg"], | ||||
|   }, | ||||
|   to: { | ||||
|     images: ["png"], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => { | ||||
|       if (error) { | ||||
|         reject(`error: ${error}`); | ||||
|       } | ||||
|  | ||||
|       if (stdout) { | ||||
|         console.log(`stdout: ${stdout}`); | ||||
|       } | ||||
|  | ||||
|       if (stderr) { | ||||
|         console.error(`stderr: ${stderr}`); | ||||
|       } | ||||
|  | ||||
|       resolve("Done"); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -1,142 +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, | ||||
|   // 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) => { | ||||
|     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("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| // declare possible conversions | ||||
| export const properties = { | ||||
|   from: { | ||||
|     images: [ | ||||
|       "avif", | ||||
|       "bif", | ||||
|       "csv", | ||||
|       "exr", | ||||
|       "fits", | ||||
|       "gif", | ||||
|       "hdr.gz", | ||||
|       "hdr", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "img.gz", | ||||
|       "img", | ||||
|       "j2c", | ||||
|       "j2k", | ||||
|       "jp2", | ||||
|       "jpeg", | ||||
|       "jpx", | ||||
|       "jxl", | ||||
|       "mat", | ||||
|       "mrxs", | ||||
|       "ndpi", | ||||
|       "nia.gz", | ||||
|       "nia", | ||||
|       "nii.gz", | ||||
|       "nii", | ||||
|       "pdf", | ||||
|       "pfm", | ||||
|       "pgm", | ||||
|       "pic", | ||||
|       "png", | ||||
|       "ppm", | ||||
|       "raw", | ||||
|       "scn", | ||||
|       "svg", | ||||
|       "svs", | ||||
|       "svslide", | ||||
|       "szi", | ||||
|       "tif", | ||||
|       "tiff", | ||||
|       "v", | ||||
|       "vips", | ||||
|       "vms", | ||||
|       "vmu", | ||||
|       "webp", | ||||
|       "zip", | ||||
|     ], | ||||
|   }, | ||||
|   to: { | ||||
|     images: [ | ||||
|       "avif", | ||||
|       "dzi", | ||||
|       "fits", | ||||
|       "gif", | ||||
|       "hdr.gz", | ||||
|       "heic", | ||||
|       "heif", | ||||
|       "img.gz", | ||||
|       "j2c", | ||||
|       "j2k", | ||||
|       "jp2", | ||||
|       "jpeg", | ||||
|       "jpx", | ||||
|       "jxl", | ||||
|       "mat", | ||||
|       "nia.gz", | ||||
|       "nia", | ||||
|       "nii.gz", | ||||
|       "nii", | ||||
|       "png", | ||||
|       "tiff", | ||||
|       "vips", | ||||
|       "webp", | ||||
|     ], | ||||
|   }, | ||||
|   options: { | ||||
|     svg: { | ||||
|       scale: { | ||||
|         description: "Scale the image up or down", | ||||
|         type: "number", | ||||
|         default: 1, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   // if (fileType === "svg") { | ||||
|   //   const scale = options.scale || 1; | ||||
|   //   const metadata = await sharp(filePath).metadata(); | ||||
|  | ||||
|   //   if (!metadata || !metadata.width || !metadata.height) { | ||||
|   //     throw new Error("Could not get metadata from image"); | ||||
|   //   } | ||||
|  | ||||
|   //   const newWidth = Math.round(metadata.width * scale); | ||||
|   //   const newHeight = Math.round(metadata.height * scale); | ||||
|  | ||||
|   //   return await sharp(filePath) | ||||
|   //     .resize(newWidth, newHeight) | ||||
|   //     .toFormat(convertTo) | ||||
|   //     .toFile(targetPath); | ||||
|   // } | ||||
|   let action = "copy"; | ||||
|   if (fileType === "pdf") { | ||||
|     action = "pdfload"; | ||||
|   } | ||||
|  | ||||
|   return new Promise((resolve, reject) => { | ||||
|     execFile( | ||||
|       "vips", | ||||
|       [action, filePath, targetPath], | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
|  | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
|  | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
|  | ||||
|         resolve("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|   // 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("./", ""); | ||||
|     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("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
| import { execFile } from "node:child_process"; | ||||
|  | ||||
| export const properties = { | ||||
|   from: { | ||||
|     text: ["tex", "latex"], | ||||
|   }, | ||||
|   to: { | ||||
|     text: ["pdf"], | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export function convert( | ||||
|   filePath: string, | ||||
|   fileType: string, | ||||
|   convertTo: string, | ||||
|   targetPath: string, | ||||
|   // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||||
|   options?: unknown, | ||||
| ): Promise<string> { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "") | ||||
|     const outputPath = targetPath | ||||
|       .split("/") | ||||
|       .slice(0, -1) | ||||
|       .join("/") | ||||
|       .replace("./", ""); | ||||
|  | ||||
|     execFile( | ||||
|       "latexmk", | ||||
|       [ | ||||
|         "-xelatex", | ||||
|         "-interaction=nonstopmode", | ||||
|         `-output-directory=${outputPath}`, | ||||
|         filePath, | ||||
|       ], | ||||
|       (error, stdout, stderr) => { | ||||
|         if (error) { | ||||
|           reject(`error: ${error}`); | ||||
|         } | ||||
|  | ||||
|         if (stdout) { | ||||
|           console.log(`stdout: ${stdout}`); | ||||
|         } | ||||
|  | ||||
|         if (stderr) { | ||||
|           console.error(`stderr: ${stderr}`); | ||||
|         } | ||||
|  | ||||
|         resolve("Done"); | ||||
|       }, | ||||
|     ); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -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") { | ||||
| @@ -113,6 +114,26 @@ if (process.env.NODE_ENV === "production") { | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   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]); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   exec("bun -v", (error, stdout) => { | ||||
|     if (error) { | ||||
|       console.error("Bun is not installed. wait what"); | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| import { randomInt, randomUUID } from "node:crypto"; | ||||
| import { rmSync } from "node:fs"; | ||||
| import { mkdir, unlink } from "node:fs/promises"; | ||||
| import cookie from "@elysiajs/cookie"; | ||||
| import { html, Html } from "@elysiajs/html"; | ||||
| import { jwt, type JWTPayloadSpec } from "@elysiajs/jwt"; | ||||
| import { staticPlugin } from "@elysiajs/static"; | ||||
| import { Database } from "bun:sqlite"; | ||||
| import { Elysia, t } from "elysia"; | ||||
| import sanitize from "sanitize-filename"; | ||||
| import { BaseHtml } from "./components/base"; | ||||
| import { Header } from "./components/header"; | ||||
| import { | ||||
| @@ -36,6 +36,8 @@ const ALLOW_UNAUTHENTICATED = | ||||
| const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS | ||||
|   ? Number(process.env.AUTO_DELETE_EVERY_N_HOURS) | ||||
|   : 24; | ||||
| const HIDE_HISTORY = | ||||
|   process.env.HIDE_HISTORY?.toLowerCase() === "true" || false; | ||||
|  | ||||
| const WEBROOT = process.env.WEBROOT ?? ""; | ||||
|  | ||||
| @@ -116,7 +118,6 @@ const app = new Elysia({ | ||||
|   }, | ||||
|   prefix: WEBROOT, | ||||
| }) | ||||
|   .use(cookie()) | ||||
|   .use(html()) | ||||
|   .use( | ||||
|     jwt({ | ||||
| @@ -352,6 +353,7 @@ const app = new Elysia({ | ||||
|             webroot={WEBROOT} | ||||
|             accountRegistration={ACCOUNT_REGISTRATION} | ||||
|             allowUnauthenticated={ALLOW_UNAUTHENTICATED} | ||||
|             hideHistory={HIDE_HISTORY} | ||||
|           /> | ||||
|           <main | ||||
|             class={` | ||||
| @@ -579,7 +581,7 @@ const app = new Elysia({ | ||||
|           > | ||||
|             <article class="article"> | ||||
|               <h1 class="mb-4 text-xl">Convert</h1> | ||||
|               <div class="mb-4 max-h-[50vh] overflow-y-auto scrollbar-thin"> | ||||
|               <div class="scrollbar-thin mb-4 max-h-[50vh] overflow-y-auto"> | ||||
|                 <table | ||||
|                   id="file-list" | ||||
|                   class={` | ||||
| @@ -594,8 +596,8 @@ const app = new Elysia({ | ||||
|                 class={` | ||||
|                   relative flex h-48 w-full items-center justify-center rounded border border-dashed | ||||
|                   border-neutral-700 transition-all | ||||
|                   [&.dragover]:border-4 [&.dragover]:border-neutral-500 | ||||
|                   hover:border-neutral-600 | ||||
|                   [&.dragover]:border-4 [&.dragover]:border-neutral-500 | ||||
|                 `} | ||||
|               > | ||||
|                 <span> | ||||
| @@ -692,7 +694,7 @@ const app = new Elysia({ | ||||
|               </article> | ||||
|               <input | ||||
|                 class={` | ||||
|                   btn-primary w-full | ||||
|                   btn-primary w-full opacity-100 | ||||
|                   disabled:cursor-not-allowed disabled:opacity-50 | ||||
|                 `} | ||||
|                 type="submit" | ||||
| @@ -886,6 +888,10 @@ const app = new Elysia({ | ||||
|       const converterName = body.convert_to.split(",")[1]; | ||||
|       const fileNames = JSON.parse(body.file_names) as string[]; | ||||
|  | ||||
|       for (let i = 0; i < fileNames.length; i++) { | ||||
|         fileNames[i] = sanitize(fileNames[i] || ""); | ||||
|       } | ||||
|  | ||||
|       if (!Array.isArray(fileNames) || fileNames.length === 0) { | ||||
|         return redirect(`${WEBROOT}/`, 302); | ||||
|       } | ||||
| @@ -950,6 +956,10 @@ const app = new Elysia({ | ||||
|     }, | ||||
|   ) | ||||
|   .get("/history", async ({ jwt, redirect, cookie: { auth } }) => { | ||||
|     if (HIDE_HISTORY) { | ||||
|       return redirect(`${WEBROOT}/`, 302); | ||||
|     } | ||||
|  | ||||
|     if (!auth?.value) { | ||||
|       return redirect(`${WEBROOT}/login`, 302); | ||||
|     } | ||||
| @@ -983,6 +993,7 @@ const app = new Elysia({ | ||||
|           <Header | ||||
|             webroot={WEBROOT} | ||||
|             allowUnauthenticated={ALLOW_UNAUTHENTICATED} | ||||
|             hideHistory={HIDE_HISTORY} | ||||
|             loggedIn | ||||
|           /> | ||||
|           <main | ||||
| @@ -1049,8 +1060,7 @@ const app = new Elysia({ | ||||
|                   {userJobs.map((job) => ( | ||||
|                     <tr> | ||||
|                       <td safe> | ||||
|                         {job.date_created.split("T")[1]?.split(".")[0] ?? | ||||
|                           job.date_created} | ||||
|                         {new Date(job.date_created).toLocaleTimeString()} | ||||
|                       </td> | ||||
|                       <td>{job.num_files}</td> | ||||
|                       <td class="max-sm:hidden">{job.finished_files}</td> | ||||
| @@ -1148,12 +1158,12 @@ const app = new Elysia({ | ||||
|                   max={job.num_files} | ||||
|                   value={files.length} | ||||
|                   class={` | ||||
|                     mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full | ||||
|                     border-0 bg-neutral-700 bg-none text-accent-500 accent-accent-500 | ||||
|                     [&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full | ||||
|                     [&::-webkit-progress-value]:[background:none] | ||||
|                     text-accent-500 accent-accent-500 mb-4 inline-block h-2 w-full appearance-none | ||||
|                     overflow-hidden rounded-full border-0 bg-neutral-700 bg-none | ||||
|                     [&[value]::-webkit-progress-value]:bg-accent-500 | ||||
|                     [&[value]::-webkit-progress-value]:transition-[inline-size] | ||||
|                     [&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full | ||||
|                     [&::-webkit-progress-value]:[background:none] | ||||
|                   `} | ||||
|                 /> | ||||
|                 <table | ||||
| @@ -1300,12 +1310,12 @@ const app = new Elysia({ | ||||
|             max={job.num_files} | ||||
|             value={files.length} | ||||
|             class={` | ||||
|               mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0 | ||||
|               bg-neutral-700 bg-none text-accent-500 accent-accent-500 | ||||
|               [&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full | ||||
|               [&::-webkit-progress-value]:[background:none] | ||||
|               text-accent-500 accent-accent-500 mb-4 inline-block h-2 w-full appearance-none | ||||
|               overflow-hidden rounded-full border-0 bg-neutral-700 bg-none | ||||
|               [&[value]::-webkit-progress-value]:bg-accent-500 | ||||
|               [&[value]::-webkit-progress-value]:transition-[inline-size] | ||||
|               [&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full | ||||
|               [&::-webkit-progress-value]:[background:none] | ||||
|             `} | ||||
|           /> | ||||
|           <table | ||||
| @@ -1411,7 +1421,7 @@ const app = new Elysia({ | ||||
|       // parse from url encoded string | ||||
|       const userId = decodeURIComponent(params.userId); | ||||
|       const jobId = decodeURIComponent(params.jobId); | ||||
|       const fileName = decodeURIComponent(params.fileName); | ||||
|       const fileName = sanitize(decodeURIComponent(params.fileName)); | ||||
|  | ||||
|       const filePath = `${outputDir}${userId}/${jobId}/${fileName}`; | ||||
|       return Bun.file(filePath); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user