diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 880958a..ece66dd 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,164 +1,164 @@ -name: Docker - -# thanks to https://github.com/sredevopsorg/multi-arch-docker-github-workflow - -on: - push: - branches: ["main"] - tags: ["v*.*.*"] - pull_request: - branches: ["main"] - workflow_dispatch: -env: - GHCR_IMAGE: ghcr.io/c4illin/convertx - IMAGE_NAME: ${{ github.repository }} - DOCKERHUB_USERNAME: c4illin - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - # The build job builds the Docker image for each platform specified in the matrix. - build: - strategy: - fail-fast: false - matrix: - platform: - - linux/amd64 - - linux/arm64 - - permissions: - contents: write - packages: write - attestations: write - checks: write - actions: read - - runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-24.04' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }} - - name: Build Docker image for ${{ matrix.platform }} - - steps: - - name: Prepare environment for current platform - # This step sets up the environment for the current platform being built. - # It replaces the '/' character in the platform name with '-' and sets it as an environment variable. - # This is useful for naming artifacts and other resources that cannot contain '/'. - # The environment variable PLATFORMS_PAIR will be used later in the workflow. - id: prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Docker meta default - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.GHCR_IMAGE }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.10.0 - with: - platforms: ${{ matrix.platform }} - - - name: Login to GitHub Container Registry - # here we only login to ghcr.io since the this only pushes internal images - uses: docker/login-action@v3.4.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push by digest - id: build - uses: docker/build-push-action@v6.18.0 - env: - DOCKER_BUILDKIT: 1 - with: - context: . - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - annotations: ${{ steps.meta.outputs.annotations }} - outputs: type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true - cache-from: type=gha,scope=${{ matrix.platform }} - cache-to: type=gha,mode=max,scope=${{ matrix.platform }} - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - merge: - name: Merge Docker manifests - runs-on: ubuntu-latest - - permissions: - attestations: write - contents: read - packages: write - - needs: - - build - steps: - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: /tmp/digests - pattern: digests-* - merge-multiple: true - - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ${{ env.GHCR_IMAGE }} - ${{ env.IMAGE_NAME }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Get execution timestamp with RFC3339 format - id: timestamp - run: | - echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create \ - $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - --annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \ - --annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \ - --annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \ - --annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \ - $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect '${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.version }}' +name: Docker + +# thanks to https://github.com/sredevopsorg/multi-arch-docker-github-workflow + +on: + push: + branches: ["main"] + tags: ["v*.*.*"] + pull_request: + branches: ["main"] + workflow_dispatch: +env: + GHCR_IMAGE: ghcr.io/c4illin/convertx + IMAGE_NAME: ${{ github.repository }} + DOCKERHUB_USERNAME: c4illin + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # The build job builds the Docker image for each platform specified in the matrix. + build: + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + + permissions: + contents: write + packages: write + attestations: write + checks: write + actions: read + + runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-24.04' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }} + + name: Build Docker image for ${{ matrix.platform }} + + steps: + - name: Prepare environment for current platform + # This step sets up the environment for the current platform being built. + # It replaces the '/' character in the platform name with '-' and sets it as an environment variable. + # This is useful for naming artifacts and other resources that cannot contain '/'. + # The environment variable PLATFORMS_PAIR will be used later in the workflow. + id: prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Docker meta default + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.GHCR_IMAGE }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.10.0 + with: + platforms: ${{ matrix.platform }} + + - name: Login to GitHub Container Registry + # here we only login to ghcr.io since the this only pushes internal images + uses: docker/login-action@v3.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6.18.0 + env: + DOCKER_BUILDKIT: 1 + with: + context: . + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + outputs: type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true + cache-from: type=gha,scope=${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=${{ matrix.platform }} + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: Merge Docker manifests + runs-on: ubuntu-latest + + permissions: + attestations: write + contents: read + packages: write + + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.GHCR_IMAGE }} + ${{ env.IMAGE_NAME }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get execution timestamp with RFC3339 format + id: timestamp + run: | + echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + --annotation='index:org.opencontainers.image.description=${{ github.event.repository.description }}' \ + --annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \ + --annotation='index:org.opencontainers.image.url=${{ github.event.repository.url }}' \ + --annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \ + $(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect '${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.version }}' diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml index cf84217..cb860a3 100644 --- a/.github/workflows/dockerhub-description.yml +++ b/.github/workflows/dockerhub-description.yml @@ -15,13 +15,13 @@ jobs: dockerHubDescription: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v4 - with: - username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - repository: ${{ env.IMAGE_NAME }} - short-description: ${{ github.event.repository.description }} - enable-url-completion: true + - name: Docker Hub Description + uses: peter-evans/dockerhub-description@v4 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: ${{ env.IMAGE_NAME }} + short-description: ${{ github.event.repository.description }} + enable-url-completion: true diff --git a/.github/workflows/remove-docker-tag.yml b/.github/workflows/remove-docker-tag.yml index cc81527..ea8f3a9 100644 --- a/.github/workflows/remove-docker-tag.yml +++ b/.github/workflows/remove-docker-tag.yml @@ -14,8 +14,8 @@ jobs: packages: write steps: - - name: Remove Docker Tag - uses: ArchieAtkinson/remove-dockertag-action@v0.0 - with: - tag_name: master - github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + - name: Remove Docker Tag + uses: ArchieAtkinson/remove-dockertag-action@v0.0 + with: + tag_name: master + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.vscode/settings.json b/.vscode/settings.json index d067910..fae8e3d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { "typescript.tsdk": "node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3256468..f3f57f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,243 +2,209 @@ ## [0.13.0](https://github.com/C4illin/ConvertX/compare/v0.12.1...v0.13.0) (2025-05-14) - ### Features -* add HIDE_HISTORY option to control visibility of history page ([9d1c931](https://github.com/C4illin/ConvertX/commit/9d1c93155cc33ed6c83f9e5122afff8f28d0e4bf)) -* add potrace converter ([bdbd4a1](https://github.com/C4illin/ConvertX/commit/bdbd4a122c09559b089b985ea12c5f3e085107da)) -* Add support for .HIF files ([70705c1](https://github.com/C4illin/ConvertX/commit/70705c1850d470296df85958c02a01fb5bc3a25f)) -* add support for drag/drop of images ([ff2ef74](https://github.com/C4illin/ConvertX/commit/ff2ef7413542cf10ba7a6e246763bcecd6829ec1)) - +- add HIDE_HISTORY option to control visibility of history page ([9d1c931](https://github.com/C4illin/ConvertX/commit/9d1c93155cc33ed6c83f9e5122afff8f28d0e4bf)) +- add potrace converter ([bdbd4a1](https://github.com/C4illin/ConvertX/commit/bdbd4a122c09559b089b985ea12c5f3e085107da)) +- Add support for .HIF files ([70705c1](https://github.com/C4illin/ConvertX/commit/70705c1850d470296df85958c02a01fb5bc3a25f)) +- add support for drag/drop of images ([ff2ef74](https://github.com/C4illin/ConvertX/commit/ff2ef7413542cf10ba7a6e246763bcecd6829ec1)) ### Bug Fixes -* add timezone support ([4b5c732](https://github.com/C4illin/ConvertX/commit/4b5c732380bc844dccf340ea1eb4f8bfe3bb44a5)), closes [#258](https://github.com/C4illin/ConvertX/issues/258) +- add timezone support ([4b5c732](https://github.com/C4illin/ConvertX/commit/4b5c732380bc844dccf340ea1eb4f8bfe3bb44a5)), closes [#258](https://github.com/C4illin/ConvertX/issues/258) ## [0.12.1](https://github.com/C4illin/ConvertX/compare/v0.12.0...v0.12.1) (2025-03-20) - ### Bug Fixes -* rollback to bun 1.2.2 ([cdae798](https://github.com/C4illin/ConvertX/commit/cdae798fcf5879e4adea87386a38748b9a1e1ddc)) +- rollback to bun 1.2.2 ([cdae798](https://github.com/C4illin/ConvertX/commit/cdae798fcf5879e4adea87386a38748b9a1e1ddc)) ## [0.12.0](https://github.com/C4illin/ConvertX/compare/v0.11.1...v0.12.0) (2025-03-06) - ### Features -* added progress bar for file upload ([db60f35](https://github.com/C4illin/ConvertX/commit/db60f355b2973f43f8e5990e6fe4e351b959b659)) -* made every upload file independent ([cc54bdc](https://github.com/C4illin/ConvertX/commit/cc54bdcbe764c41cc3273485d072fd3178ad2dca)) -* replace exec with execFile ([9263d17](https://github.com/C4illin/ConvertX/commit/9263d17609dc4b2b367eb7fee67b3182e283b3a3)) - +- added progress bar for file upload ([db60f35](https://github.com/C4illin/ConvertX/commit/db60f355b2973f43f8e5990e6fe4e351b959b659)) +- made every upload file independent ([cc54bdc](https://github.com/C4illin/ConvertX/commit/cc54bdcbe764c41cc3273485d072fd3178ad2dca)) +- replace exec with execFile ([9263d17](https://github.com/C4illin/ConvertX/commit/9263d17609dc4b2b367eb7fee67b3182e283b3a3)) ### Bug Fixes -* add libheif ([6b92540](https://github.com/C4illin/ConvertX/commit/6b9254047c0598963aee1d99e20ba1650a0368bf)) -* add libheif ([decfea5](https://github.com/C4illin/ConvertX/commit/decfea5dc9627b216bb276a9e1578c32cfa1deb6)), closes [#202](https://github.com/C4illin/ConvertX/issues/202) -* added onerror log ([ae4bbc8](https://github.com/C4illin/ConvertX/commit/ae4bbc8baacbaf67763c62ea44140bb21cc17230)) -* refactored uploadFile to only accept a single file instead of multiple ([dc82a43](https://github.com/C4illin/ConvertX/commit/dc82a438d4104b79ff423d502a6779a43928968a)) -* update libheif to 1.19.5 ([fba5e21](https://github.com/C4illin/ConvertX/commit/fba5e212e8d0eaba8971e239e35aeb521f3cd813)), closes [#202](https://github.com/C4illin/ConvertX/issues/202) +- add libheif ([6b92540](https://github.com/C4illin/ConvertX/commit/6b9254047c0598963aee1d99e20ba1650a0368bf)) +- add libheif ([decfea5](https://github.com/C4illin/ConvertX/commit/decfea5dc9627b216bb276a9e1578c32cfa1deb6)), closes [#202](https://github.com/C4illin/ConvertX/issues/202) +- added onerror log ([ae4bbc8](https://github.com/C4illin/ConvertX/commit/ae4bbc8baacbaf67763c62ea44140bb21cc17230)) +- refactored uploadFile to only accept a single file instead of multiple ([dc82a43](https://github.com/C4illin/ConvertX/commit/dc82a438d4104b79ff423d502a6779a43928968a)) +- update libheif to 1.19.5 ([fba5e21](https://github.com/C4illin/ConvertX/commit/fba5e212e8d0eaba8971e239e35aeb521f3cd813)), closes [#202](https://github.com/C4illin/ConvertX/issues/202) ## [0.11.1](https://github.com/C4illin/ConvertX/compare/v0.11.0...v0.11.1) (2025-02-07) - ### Bug Fixes -* mobile view overflow ([bec58ac](https://github.com/C4illin/ConvertX/commit/bec58ac59f9600e35385b9e21d174f3ab1b42b1d)) +- mobile view overflow ([bec58ac](https://github.com/C4illin/ConvertX/commit/bec58ac59f9600e35385b9e21d174f3ab1b42b1d)) ## [0.11.0](https://github.com/C4illin/ConvertX/compare/v0.10.1...v0.11.0) (2025-02-05) - ### Features -* add deps for vaapi ([2bbbd03](https://github.com/C4illin/ConvertX/commit/2bbbd03554d384a4488143f29e5fc863cfdf333b)), closes [#192](https://github.com/C4illin/ConvertX/issues/192) - +- add deps for vaapi ([2bbbd03](https://github.com/C4illin/ConvertX/commit/2bbbd03554d384a4488143f29e5fc863cfdf333b)), closes [#192](https://github.com/C4illin/ConvertX/issues/192) ### Bug Fixes -* don't crash if file is not found ([16f27c1](https://github.com/C4illin/ConvertX/commit/16f27c13bbc1c0e5fa2316f3db11d0918524053b)) -* install numpy for inkscape ([0e61051](https://github.com/C4illin/ConvertX/commit/0e61051fc6be188164c3865b4fb579c140859fdc)) +- don't crash if file is not found ([16f27c1](https://github.com/C4illin/ConvertX/commit/16f27c13bbc1c0e5fa2316f3db11d0918524053b)) +- install numpy for inkscape ([0e61051](https://github.com/C4illin/ConvertX/commit/0e61051fc6be188164c3865b4fb579c140859fdc)) ## [0.10.1](https://github.com/C4illin/ConvertX/compare/v0.10.0...v0.10.1) (2025-01-21) - ### Bug Fixes -* ffmpeg works without ffmpeg_args ([3b7ea88](https://github.com/C4illin/ConvertX/commit/3b7ea88b7382f7c21b120bdc9bda5bb10547f55d)), closes [#212](https://github.com/C4illin/ConvertX/issues/212) +- ffmpeg works without ffmpeg_args ([3b7ea88](https://github.com/C4illin/ConvertX/commit/3b7ea88b7382f7c21b120bdc9bda5bb10547f55d)), closes [#212](https://github.com/C4illin/ConvertX/issues/212) ## [0.10.0](https://github.com/C4illin/ConvertX/compare/v0.9.0...v0.10.0) (2025-01-18) - ### Features -* add calibre ([03d3edf](https://github.com/C4illin/ConvertX/commit/03d3edfff65c252dd4b8922fc98257c089c1ff74)), closes [#191](https://github.com/C4illin/ConvertX/issues/191) - +- add calibre ([03d3edf](https://github.com/C4illin/ConvertX/commit/03d3edfff65c252dd4b8922fc98257c089c1ff74)), closes [#191](https://github.com/C4illin/ConvertX/issues/191) ### Bug Fixes -* add FFMPEG_ARGS env variable ([f537c81](https://github.com/C4illin/ConvertX/commit/f537c81db7815df8017f834e3162291197e1c40f)), closes [#190](https://github.com/C4illin/ConvertX/issues/190) -* add qt6-qtbase-private-dev from community repo ([95dbc9f](https://github.com/C4illin/ConvertX/commit/95dbc9f678bec7e6e2c03587e1473fb8ff708ea3)) -* skip account setup when ALLOW_UNAUTHENTICATED is true ([538c5b6](https://github.com/C4illin/ConvertX/commit/538c5b60c9e27a8184740305475245da79bae143)) +- add FFMPEG_ARGS env variable ([f537c81](https://github.com/C4illin/ConvertX/commit/f537c81db7815df8017f834e3162291197e1c40f)), closes [#190](https://github.com/C4illin/ConvertX/issues/190) +- add qt6-qtbase-private-dev from community repo ([95dbc9f](https://github.com/C4illin/ConvertX/commit/95dbc9f678bec7e6e2c03587e1473fb8ff708ea3)) +- skip account setup when ALLOW_UNAUTHENTICATED is true ([538c5b6](https://github.com/C4illin/ConvertX/commit/538c5b60c9e27a8184740305475245da79bae143)) ## [0.9.0](https://github.com/C4illin/ConvertX/compare/v0.8.1...v0.9.0) (2024-11-21) - ### Features -* add inkscape for vector images ([f3740e9](https://github.com/C4illin/ConvertX/commit/f3740e9ded100b8500f3613517960248bbd3c210)) -* Allow to chose webroot ([36cb6cc](https://github.com/C4illin/ConvertX/commit/36cb6cc589d80d0a87fa8dbe605db71a9a2570f9)), closes [#180](https://github.com/C4illin/ConvertX/issues/180) -* disable convert when uploading ([58e220e](https://github.com/C4illin/ConvertX/commit/58e220e82d7f9c163d6ea4dc31092c08a3e254f4)), closes [#177](https://github.com/C4illin/ConvertX/issues/177) - +- add inkscape for vector images ([f3740e9](https://github.com/C4illin/ConvertX/commit/f3740e9ded100b8500f3613517960248bbd3c210)) +- Allow to chose webroot ([36cb6cc](https://github.com/C4illin/ConvertX/commit/36cb6cc589d80d0a87fa8dbe605db71a9a2570f9)), closes [#180](https://github.com/C4illin/ConvertX/issues/180) +- disable convert when uploading ([58e220e](https://github.com/C4illin/ConvertX/commit/58e220e82d7f9c163d6ea4dc31092c08a3e254f4)), closes [#177](https://github.com/C4illin/ConvertX/issues/177) ### Bug Fixes -* treat unknown as m4a ([1a442d6](https://github.com/C4illin/ConvertX/commit/1a442d6e69606afef63b1e7df36aa83d111fa23d)), closes [#178](https://github.com/C4illin/ConvertX/issues/178) -* wait for both upload and selection ([4c05fd7](https://github.com/C4illin/ConvertX/commit/4c05fd72bbbf91ee02327f6fcbf749b78272376b)), closes [#177](https://github.com/C4illin/ConvertX/issues/177) +- treat unknown as m4a ([1a442d6](https://github.com/C4illin/ConvertX/commit/1a442d6e69606afef63b1e7df36aa83d111fa23d)), closes [#178](https://github.com/C4illin/ConvertX/issues/178) +- wait for both upload and selection ([4c05fd7](https://github.com/C4illin/ConvertX/commit/4c05fd72bbbf91ee02327f6fcbf749b78272376b)), closes [#177](https://github.com/C4illin/ConvertX/issues/177) ## [0.8.1](https://github.com/C4illin/ConvertX/compare/v0.8.0...v0.8.1) (2024-10-05) - ### Bug Fixes -* disable convert button when input is empty ([78844d7](https://github.com/C4illin/ConvertX/commit/78844d7bd55990789ed07c81e49043e688cbe656)), closes [#151](https://github.com/C4illin/ConvertX/issues/151) -* resize to fit for ico ([b4e53db](https://github.com/C4illin/ConvertX/commit/b4e53dbb8e70b3a95b44e5b756759d16117a87e1)), closes [#157](https://github.com/C4illin/ConvertX/issues/157) -* treat jfif as jpeg ([339b79f](https://github.com/C4illin/ConvertX/commit/339b79f786131deb93f0d5683e03178fdcab1ef5)), closes [#163](https://github.com/C4illin/ConvertX/issues/163) +- disable convert button when input is empty ([78844d7](https://github.com/C4illin/ConvertX/commit/78844d7bd55990789ed07c81e49043e688cbe656)), closes [#151](https://github.com/C4illin/ConvertX/issues/151) +- resize to fit for ico ([b4e53db](https://github.com/C4illin/ConvertX/commit/b4e53dbb8e70b3a95b44e5b756759d16117a87e1)), closes [#157](https://github.com/C4illin/ConvertX/issues/157) +- treat jfif as jpeg ([339b79f](https://github.com/C4illin/ConvertX/commit/339b79f786131deb93f0d5683e03178fdcab1ef5)), closes [#163](https://github.com/C4illin/ConvertX/issues/163) ## [0.8.0](https://github.com/C4illin/ConvertX/compare/v0.7.0...v0.8.0) (2024-09-30) - ### Features -* add light theme, fixes [#156](https://github.com/C4illin/ConvertX/issues/156) ([72636c5](https://github.com/C4illin/ConvertX/commit/72636c5059ebf09c8fece2e268293650b2f8ccf6)) - +- add light theme, fixes [#156](https://github.com/C4illin/ConvertX/issues/156) ([72636c5](https://github.com/C4illin/ConvertX/commit/72636c5059ebf09c8fece2e268293650b2f8ccf6)) ### Bug Fixes -* add support for usd for assimp, [#144](https://github.com/C4illin/ConvertX/issues/144) ([2057167](https://github.com/C4illin/ConvertX/commit/20571675766209ad1251f07e687d29a6791afc8b)) -* cleanup formats and add opus, fixes [#159](https://github.com/C4illin/ConvertX/issues/159) ([ae1dfaf](https://github.com/C4illin/ConvertX/commit/ae1dfafc9d9116a57b08c2f7fc326990e00824b0)) -* support .awb and clean up, fixes [#153](https://github.com/C4illin/ConvertX/issues/153), [#92](https://github.com/C4illin/ConvertX/issues/92) ([1c9e67f](https://github.com/C4illin/ConvertX/commit/1c9e67fc3201e0e5dee91e8981adf34daaabf33a)) +- add support for usd for assimp, [#144](https://github.com/C4illin/ConvertX/issues/144) ([2057167](https://github.com/C4illin/ConvertX/commit/20571675766209ad1251f07e687d29a6791afc8b)) +- cleanup formats and add opus, fixes [#159](https://github.com/C4illin/ConvertX/issues/159) ([ae1dfaf](https://github.com/C4illin/ConvertX/commit/ae1dfafc9d9116a57b08c2f7fc326990e00824b0)) +- support .awb and clean up, fixes [#153](https://github.com/C4illin/ConvertX/issues/153), [#92](https://github.com/C4illin/ConvertX/issues/92) ([1c9e67f](https://github.com/C4illin/ConvertX/commit/1c9e67fc3201e0e5dee91e8981adf34daaabf33a)) ## [0.7.0](https://github.com/C4illin/ConvertX/compare/v0.6.0...v0.7.0) (2024-09-26) - ### Features -* Add support for 3d assets through assimp converter ([63a4328](https://github.com/C4illin/ConvertX/commit/63a4328d4a1e01df3e0ec4a877bad8c8ffe71129)) - +- Add support for 3d assets through assimp converter ([63a4328](https://github.com/C4illin/ConvertX/commit/63a4328d4a1e01df3e0ec4a877bad8c8ffe71129)) ### Bug Fixes -* wrong layout on search with few options ([8817389](https://github.com/C4illin/ConvertX/commit/88173891ba2d69da46eda46f3f598a9b54f26f96)) +- wrong layout on search with few options ([8817389](https://github.com/C4illin/ConvertX/commit/88173891ba2d69da46eda46f3f598a9b54f26f96)) ## [0.6.0](https://github.com/C4illin/ConvertX/compare/v0.5.0...v0.6.0) (2024-09-25) - ### Features -* ui remake with tailwind ([22f823c](https://github.com/C4illin/ConvertX/commit/22f823c535b20382981f86a13616b830a1f3392f)) - +- ui remake with tailwind ([22f823c](https://github.com/C4illin/ConvertX/commit/22f823c535b20382981f86a13616b830a1f3392f)) ### Bug Fixes -* rename css file to force update cache, fixes [#141](https://github.com/C4illin/ConvertX/issues/141) ([47139a5](https://github.com/C4illin/ConvertX/commit/47139a550bd3d847da288c61bf8f88953b79c673)) +- rename css file to force update cache, fixes [#141](https://github.com/C4illin/ConvertX/issues/141) ([47139a5](https://github.com/C4illin/ConvertX/commit/47139a550bd3d847da288c61bf8f88953b79c673)) ## [0.5.0](https://github.com/C4illin/ConvertX/compare/v0.4.1...v0.5.0) (2024-09-20) - ### Features -* add option to customize how often files are automatically deleted ([317c932](https://github.com/C4illin/ConvertX/commit/317c932c2a26280bf37ed3d3bf9b879413590f5a)) - +- add option to customize how often files are automatically deleted ([317c932](https://github.com/C4illin/ConvertX/commit/317c932c2a26280bf37ed3d3bf9b879413590f5a)) ### Bug Fixes -* improve file name replacement logic ([60ba7c9](https://github.com/C4illin/ConvertX/commit/60ba7c93fbdc961f3569882fade7cc13dee7a7a5)) +- improve file name replacement logic ([60ba7c9](https://github.com/C4illin/ConvertX/commit/60ba7c93fbdc961f3569882fade7cc13dee7a7a5)) ## [0.4.1](https://github.com/C4illin/ConvertX/compare/v0.4.0...v0.4.1) (2024-09-15) - ### Bug Fixes -* allow non lowercase true and false values, fixes [#122](https://github.com/C4illin/ConvertX/issues/122) ([bef1710](https://github.com/C4illin/ConvertX/commit/bef1710e3376baa7e25c107ded20a40d18b8c6b0)) +- allow non lowercase true and false values, fixes [#122](https://github.com/C4illin/ConvertX/issues/122) ([bef1710](https://github.com/C4illin/ConvertX/commit/bef1710e3376baa7e25c107ded20a40d18b8c6b0)) ## [0.4.0](https://github.com/C4illin/ConvertX/compare/v0.3.3...v0.4.0) (2024-08-26) - ### Features -* add option for unauthenticated file conversions [#114](https://github.com/C4illin/ConvertX/issues/114) ([f0d0e43](https://github.com/C4illin/ConvertX/commit/f0d0e4392983c3e4c530304ea88e023fda9bcac0)) -* add resvg converter ([d5eeef9](https://github.com/C4illin/ConvertX/commit/d5eeef9f6884b2bb878508bed97ea9ceaa662995)) -* add robots.txt ([6597c1d](https://github.com/C4illin/ConvertX/commit/6597c1d7caeb4dfb6bc47b442e4dfc9840ad12b7)) -* Add search bar for formats ([53fff59](https://github.com/C4illin/ConvertX/commit/53fff594fc4d69306abcb2a5cad890fcd0953a58)) - +- add option for unauthenticated file conversions [#114](https://github.com/C4illin/ConvertX/issues/114) ([f0d0e43](https://github.com/C4illin/ConvertX/commit/f0d0e4392983c3e4c530304ea88e023fda9bcac0)) +- add resvg converter ([d5eeef9](https://github.com/C4illin/ConvertX/commit/d5eeef9f6884b2bb878508bed97ea9ceaa662995)) +- add robots.txt ([6597c1d](https://github.com/C4illin/ConvertX/commit/6597c1d7caeb4dfb6bc47b442e4dfc9840ad12b7)) +- Add search bar for formats ([53fff59](https://github.com/C4illin/ConvertX/commit/53fff594fc4d69306abcb2a5cad890fcd0953a58)) ### Bug Fixes -* keep unauthenticated user logged in if allowed [#114](https://github.com/C4illin/ConvertX/issues/114) ([bc4ad49](https://github.com/C4illin/ConvertX/commit/bc4ad492852fad8cb832a0c03485cccdd7f7b117)) -* pdf support in vips ([8ca4f15](https://github.com/C4illin/ConvertX/commit/8ca4f1587df7f358893941c656d78d75f04dac93)) -* Slow click on conversion popup does not work ([4d9c4d6](https://github.com/C4illin/ConvertX/commit/4d9c4d64aa0266f3928935ada68d91ac81f638aa)) +- keep unauthenticated user logged in if allowed [#114](https://github.com/C4illin/ConvertX/issues/114) ([bc4ad49](https://github.com/C4illin/ConvertX/commit/bc4ad492852fad8cb832a0c03485cccdd7f7b117)) +- pdf support in vips ([8ca4f15](https://github.com/C4illin/ConvertX/commit/8ca4f1587df7f358893941c656d78d75f04dac93)) +- Slow click on conversion popup does not work ([4d9c4d6](https://github.com/C4illin/ConvertX/commit/4d9c4d64aa0266f3928935ada68d91ac81f638aa)) ## [0.3.3](https://github.com/C4illin/ConvertX/compare/v0.3.2...v0.3.3) (2024-07-30) - ### Bug Fixes -* downgrade @elysiajs/html dependency to version 1.0.2 ([c714ade](https://github.com/C4illin/ConvertX/commit/c714ade3e23865ba6cfaf76c9e7259df1cda222c)) +- downgrade @elysiajs/html dependency to version 1.0.2 ([c714ade](https://github.com/C4illin/ConvertX/commit/c714ade3e23865ba6cfaf76c9e7259df1cda222c)) ## [0.3.2](https://github.com/C4illin/ConvertX/compare/v0.3.1...v0.3.2) (2024-07-09) - ### Bug Fixes -* increase max request body to support large uploads ([3ae2db5](https://github.com/C4illin/ConvertX/commit/3ae2db5d9b36fe3dcd4372ddcd32aa573ea59aa6)), closes [#64](https://github.com/C4illin/ConvertX/issues/64) +- increase max request body to support large uploads ([3ae2db5](https://github.com/C4illin/ConvertX/commit/3ae2db5d9b36fe3dcd4372ddcd32aa573ea59aa6)), closes [#64](https://github.com/C4illin/ConvertX/issues/64) ## [0.3.1](https://github.com/C4illin/ConvertX/compare/v0.3.0...v0.3.1) (2024-06-27) - ### Bug Fixes -* release releases ([4d4c13a](https://github.com/C4illin/ConvertX/commit/4d4c13a8d85ec7c9209ad41cdbea7d4380b0edbf)) +- release releases ([4d4c13a](https://github.com/C4illin/ConvertX/commit/4d4c13a8d85ec7c9209ad41cdbea7d4380b0edbf)) ## [0.3.0](https://github.com/C4illin/ConvertX/compare/v0.2.0...v0.3.0) (2024-06-27) - ### Features -* add version number to log ([4dcb796](https://github.com/C4illin/ConvertX/commit/4dcb796e1bd27badc078d0638076cd9f1e81c4a4)), closes [#44](https://github.com/C4illin/ConvertX/issues/44) -* change to xelatex ([fae2ba9](https://github.com/C4illin/ConvertX/commit/fae2ba9c54461dccdccd1bfb5e76398540d11d0b)) -* print version of installed converters to log ([801cf28](https://github.com/C4illin/ConvertX/commit/801cf28d1e5edac9353b0b16be75a4fb48470b8a)) +- add version number to log ([4dcb796](https://github.com/C4illin/ConvertX/commit/4dcb796e1bd27badc078d0638076cd9f1e81c4a4)), closes [#44](https://github.com/C4illin/ConvertX/issues/44) +- change to xelatex ([fae2ba9](https://github.com/C4illin/ConvertX/commit/fae2ba9c54461dccdccd1bfb5e76398540d11d0b)) +- print version of installed converters to log ([801cf28](https://github.com/C4illin/ConvertX/commit/801cf28d1e5edac9353b0b16be75a4fb48470b8a)) ## [0.2.0](https://github.com/C4illin/ConvertX/compare/v0.1.2...v0.2.0) (2024-06-20) - ### Features -* add libjxl for jpegxl conversion ([ff680cb](https://github.com/C4illin/ConvertX/commit/ff680cb29534a25c3148a90fd064bb86c71fb482)) -* change from debian to alpine ([1316957](https://github.com/C4illin/ConvertX/commit/13169574f0134ae236f8d41287bb73930b575e82)), closes [#34](https://github.com/C4illin/ConvertX/issues/34) +- add libjxl for jpegxl conversion ([ff680cb](https://github.com/C4illin/ConvertX/commit/ff680cb29534a25c3148a90fd064bb86c71fb482)) +- change from debian to alpine ([1316957](https://github.com/C4illin/ConvertX/commit/13169574f0134ae236f8d41287bb73930b575e82)), closes [#34](https://github.com/C4illin/ConvertX/issues/34) ## [0.1.2](https://github.com/C4illin/ConvertX/compare/v0.1.1...v0.1.2) (2024-06-10) - ### Bug Fixes -* fix incorrect redirect ([25df58b](https://github.com/C4illin/ConvertX/commit/25df58ba82321aaa6617811a6995cb96c2a00a40)), closes [#23](https://github.com/C4illin/ConvertX/issues/23) +- fix incorrect redirect ([25df58b](https://github.com/C4illin/ConvertX/commit/25df58ba82321aaa6617811a6995cb96c2a00a40)), closes [#23](https://github.com/C4illin/ConvertX/issues/23) ## [0.1.1](https://github.com/C4illin/ConvertX/compare/v0.1.0...v0.1.1) (2024-05-30) - ### Bug Fixes -* :bug: make sure all redirects are 302 ([9970fd3](https://github.com/C4illin/ConvertX/commit/9970fd3f89190af96f8762edc3817d1e03082b3a)), closes [#12](https://github.com/C4illin/ConvertX/issues/12) +- :bug: make sure all redirects are 302 ([9970fd3](https://github.com/C4illin/ConvertX/commit/9970fd3f89190af96f8762edc3817d1e03082b3a)), closes [#12](https://github.com/C4illin/ConvertX/issues/12) ## 0.1.0 (2024-05-30) - ### Features -* remove file from file list in index.html ([787ff97](https://github.com/C4illin/ConvertX/commit/787ff9741ecbbf4fb4c02b43bd22a214a173fd7b)) - +- remove file from file list in index.html ([787ff97](https://github.com/C4illin/ConvertX/commit/787ff9741ecbbf4fb4c02b43bd22a214a173fd7b)) ### Miscellaneous Chores -* release 0.1.0 ([54d9aec](https://github.com/C4illin/ConvertX/commit/54d9aecbf949689b12aa7e5e8e9be7b9032f4431)) +- release 0.1.0 ([54d9aec](https://github.com/C4illin/ConvertX/commit/54d9aecbf949689b12aa7e5e8e9be7b9032f4431)) diff --git a/README.md b/README.md index 8920cea..ffba8c2 100644 --- a/README.md +++ b/README.md @@ -1,158 +1,159 @@ -![ConvertX](images/logo.png) - -# ConvertX - -[![Docker](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml/badge.svg?branch=main)](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml) -[![ghcr.io Pulls](https://img.shields.io/badge/dynamic/json?logo=github&url=https%3A%2F%2Fipitio.github.io%2Fbackage%2FC4illin%2FConvertX%2Fconvertx.json&query=%24.downloads&label=ghcr.io%20pulls&cacheSeconds=14400)](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX) -[![Docker Pulls](https://img.shields.io/docker/pulls/c4illin/convertx?style=flat&logo=docker&label=dockerhub%20pulls&link=https%3A%2F%2Fhub.docker.com%2Frepository%2Fdocker%2Fc4illin%2Fconvertx%2Fgeneral)](https://hub.docker.com/r/c4illin/convertx) -[![GitHub Release](https://img.shields.io/github/v/release/C4illin/ConvertX)](https://github.com/C4illin/ConvertX/pkgs/container/convertx) -![GitHub commits since latest release](https://img.shields.io/github/commits-since/C4illin/ConvertX/latest) -![GitHub repo size](https://img.shields.io/github/repo-size/C4illin/ConvertX) -![Docker container size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=latest&label=image+size&trim=) - -C4illin%2FConvertX | Trendshift - - -A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia. - -## Features - -- Convert files to different formats -- Process multiple files at once -- Password protection -- Multiple accounts - -## Converters supported - -| Converter | Use case | Converts from | Converts to | -|------------------------------------------------------------------------------|---------------|---------------|-------------| -| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 | -| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 | -| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 | -| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 | -| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 | -| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 | -| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 | -| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 | -| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 | -| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 | -| [Inkscape](https://inkscape.org/) | Vector images | 7 | 17 | -| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 77 | 23 | -| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 | -| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 | - - - - -Any missing converter? Open an issue or pull request! - -## Deployment - -> [!WARNING] -> If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true - -```yml -# docker-compose.yml -services: - convertx: - image: ghcr.io/c4illin/convertx - container_name: convertx - restart: unless-stopped - ports: - - "3000:3000" - environment: - - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset - volumes: - - ./data:/app/data -``` - -or - -```bash -docker run -p 3000:3000 -v ./data:/app/data ghcr.io/c4illin/convertx -``` - -Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account. - -If you get unable to open database file run `chown -R $USER:$USER path` on the path you choose. - -### Environment variables - -All are optional, JWT_SECRET is recommended to be set. - -| Name | Default | Description | -|---------------------------|---------|-------------| -| JWT_SECRET | when unset it will use the value from randomUUID() | A long and secret string used to sign the JSON Web Token | -| ACCOUNT_REGISTRATION | false | Allow users to register accounts | -| HTTP_ALLOWED | false | Allow HTTP connections, only set this to true locally | -| ALLOW_UNAUTHENTICATED | false | Allow unauthenticated users to use the service, only set this to true locally | -| AUTO_DELETE_EVERY_N_HOURS | 24 | Checks every n hours for files older then n hours and deletes them, set to 0 to disable | -| WEBROOT | | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" | -| FFMPEG_ARGS | | Arguments to pass to ffmpeg, e.g. `-preset veryfast` | -| HIDE_HISTORY | false | Hide the history page | - -### Docker images - -There is a `:latest` tag that is updated with every release and a `:main` tag that is updated with every push to the main branch. `:latest` is recommended for normal use. - -The image is available on [GitHub Container Registry](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX) and [Docker Hub](https://hub.docker.com/r/c4illin/convertx). - -| Image | What it is | -|-------|------------| -| `image: ghcr.io/c4illin/convertx` | The latest release on ghcr | -| `image: ghcr.io/c4illin/convertx:main` | The latest commit on ghcr | -| `image: c4illin/convertx` | The latest release on docker hub | -| `image: c4illin/convertx:main` | The latest commit on docker hub | - -![Release image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=latest&label=release+image&trim=) -![Dev image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=main&label=dev+image&trim=) - - -### Tutorial - -> [!NOTE] -> These are written by other people, and may be outdated, incorrect or wrong. - -Tutorial in french: - -Tutorial in chinese: - -## Screenshots - -![ConvertX Preview](images/preview.png) - -## Development - -0. Install [Bun](https://bun.sh/) and Git -1. Clone the repository -2. `bun install` -3. `bun run dev` - -Pull requests are welcome! See below and open issues for the list of todos. - -Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for commit messages. - -## Todo - -- [ ] Add options for converters -- [ ] Add tests -- [ ] Make errors logs visible from the web ui -- [ ] Add more converters: - - [ ] [deark](https://github.com/jsummers/deark) - - [ ] LibreOffice - -## Contributors - - - Image with all contributors - - -## Star History - - - - - - Star History Chart - - +![ConvertX](images/logo.png) + +# ConvertX + +[![Docker](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml/badge.svg?branch=main)](https://github.com/C4illin/ConvertX/actions/workflows/docker-publish.yml) +[![ghcr.io Pulls](https://img.shields.io/badge/dynamic/json?logo=github&url=https%3A%2F%2Fipitio.github.io%2Fbackage%2FC4illin%2FConvertX%2Fconvertx.json&query=%24.downloads&label=ghcr.io%20pulls&cacheSeconds=14400)](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX) +[![Docker Pulls](https://img.shields.io/docker/pulls/c4illin/convertx?style=flat&logo=docker&label=dockerhub%20pulls&link=https%3A%2F%2Fhub.docker.com%2Frepository%2Fdocker%2Fc4illin%2Fconvertx%2Fgeneral)](https://hub.docker.com/r/c4illin/convertx) +[![GitHub Release](https://img.shields.io/github/v/release/C4illin/ConvertX)](https://github.com/C4illin/ConvertX/pkgs/container/convertx) +![GitHub commits since latest release](https://img.shields.io/github/commits-since/C4illin/ConvertX/latest) +![GitHub repo size](https://img.shields.io/github/repo-size/C4illin/ConvertX) +![Docker container size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=latest&label=image+size&trim=) + +C4illin%2FConvertX | Trendshift + + + +A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia. + +## Features + +- Convert files to different formats +- Process multiple files at once +- Password protection +- Multiple accounts + +## Converters supported + +| Converter | Use case | Converts from | Converts to | +| ------------------------------------------------ | ---------------- | ------------- | ----------- | +| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 | +| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 | +| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 | +| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 | +| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 | +| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 | +| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 | +| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 | +| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 | +| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 | +| [Inkscape](https://inkscape.org/) | Vector images | 7 | 17 | +| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 77 | 23 | +| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 | +| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 | + + + +Any missing converter? Open an issue or pull request! + +## Deployment + +> [!WARNING] +> If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true + +```yml +# docker-compose.yml +services: + convertx: + image: ghcr.io/c4illin/convertx + container_name: convertx + restart: unless-stopped + ports: + - "3000:3000" + environment: + - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset + volumes: + - ./data:/app/data +``` + +or + +```bash +docker run -p 3000:3000 -v ./data:/app/data ghcr.io/c4illin/convertx +``` + +Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account. + +If you get unable to open database file run `chown -R $USER:$USER path` on the path you choose. + +### Environment variables + +All are optional, JWT_SECRET is recommended to be set. + +| Name | Default | Description | +| ------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| JWT_SECRET | when unset it will use the value from randomUUID() | A long and secret string used to sign the JSON Web Token | +| ACCOUNT_REGISTRATION | false | Allow users to register accounts | +| HTTP_ALLOWED | false | Allow HTTP connections, only set this to true locally | +| ALLOW_UNAUTHENTICATED | false | Allow unauthenticated users to use the service, only set this to true locally | +| AUTO_DELETE_EVERY_N_HOURS | 24 | Checks every n hours for files older then n hours and deletes them, set to 0 to disable | +| WEBROOT | | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" | +| FFMPEG_ARGS | | Arguments to pass to ffmpeg, e.g. `-preset veryfast` | +| HIDE_HISTORY | false | Hide the history page | + +### Docker images + +There is a `:latest` tag that is updated with every release and a `:main` tag that is updated with every push to the main branch. `:latest` is recommended for normal use. + +The image is available on [GitHub Container Registry](https://github.com/C4illin/ConvertX/pkgs/container/ConvertX) and [Docker Hub](https://hub.docker.com/r/c4illin/convertx). + +| Image | What it is | +| -------------------------------------- | -------------------------------- | +| `image: ghcr.io/c4illin/convertx` | The latest release on ghcr | +| `image: ghcr.io/c4illin/convertx:main` | The latest commit on ghcr | +| `image: c4illin/convertx` | The latest release on docker hub | +| `image: c4illin/convertx:main` | The latest commit on docker hub | + +![Release image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=latest&label=release+image&trim=) +![Dev image size](https://ghcr-badge.egpl.dev/c4illin/convertx/size?color=%230375b6&tag=main&label=dev+image&trim=) + + + +### Tutorial + +> [!NOTE] +> These are written by other people, and may be outdated, incorrect or wrong. + +Tutorial in french: + +Tutorial in chinese: + +## Screenshots + +![ConvertX Preview](images/preview.png) + +## Development + +0. Install [Bun](https://bun.sh/) and Git +1. Clone the repository +2. `bun install` +3. `bun run dev` + +Pull requests are welcome! See below and open issues for the list of todos. + +Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for commit messages. + +## Todo + +- [ ] Add options for converters +- [ ] Add tests +- [ ] Make errors logs visible from the web ui +- [ ] Add more converters: + - [ ] [deark](https://github.com/jsummers/deark) + - [ ] LibreOffice + +## Contributors + + + Image with all contributors + + +## Star History + + + + + + Star History Chart + + diff --git a/biome.json b/biome.json index e14b242..b49e76e 100644 --- a/biome.json +++ b/biome.json @@ -10,10 +10,7 @@ "attributePosition": "auto" }, "files": { - "ignore": [ - "**/node_modules/**", - "**/pico.lime.min.css" - ] + "ignore": ["**/node_modules/**", "**/pico.lime.min.css"] }, "organizeImports": { "enabled": true @@ -72,4 +69,4 @@ "attributePosition": "auto" } } -} \ No newline at end of file +} diff --git a/compose.yaml b/compose.yaml index 183fd6b..e129451 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,19 +1,19 @@ -services: - convertx: - build: - context: . - # dockerfile: Debian.Dockerfile - volumes: - - ./data:/app/data - environment: # Defaults are listed below. All are optional. - - ACCOUNT_REGISTRATION=true # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account) - - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default - - HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally - - ALLOW_UNAUTHENTICATED=false # allows anyone to use the service without logging in, only set this to true locally - - AUTO_DELETE_EVERY_N_HOURS=1 # checks every n hours for files older then n hours and deletes them, set to 0 to disable - # - FFMPEG_ARGS=-hwaccel vulkan # additional arguments to pass to ffmpeg - # - WEBROOT=/convertx # the root path of the web interface, leave empty to disable - # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false - - TZ=Europe/Stockholm # set your timezone, defaults to UTC - ports: - - 3000:3000 +services: + convertx: + build: + context: . + # dockerfile: Debian.Dockerfile + volumes: + - ./data:/app/data + environment: # Defaults are listed below. All are optional. + - ACCOUNT_REGISTRATION=true # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account) + - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default + - HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally + - ALLOW_UNAUTHENTICATED=false # allows anyone to use the service without logging in, only set this to true locally + - AUTO_DELETE_EVERY_N_HOURS=1 # checks every n hours for files older then n hours and deletes them, set to 0 to disable + # - FFMPEG_ARGS=-hwaccel vulkan # additional arguments to pass to ffmpeg + # - WEBROOT=/convertx # the root path of the web interface, leave empty to disable + # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false + - TZ=Europe/Stockholm # set your timezone, defaults to UTC + ports: + - 3000:3000 diff --git a/eslint.config.ts b/eslint.config.ts index f237f31..6bf8f24 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,66 +1,66 @@ -import js from "@eslint/js"; -import eslintParserTypeScript from "@typescript-eslint/parser"; -import type { Linter } from "eslint"; -import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss"; -import simpleImportSortPlugin from "eslint-plugin-simple-import-sort"; -import globals from "globals"; -import tseslint from "typescript-eslint"; - -export default [ - js.configs.recommended, - ...tseslint.configs.recommended, - // ...tailwind.configs["flat/recommended"], - { - plugins: { - "simple-import-sort": simpleImportSortPlugin, - "better-tailwindcss": eslintPluginBetterTailwindcss, - }, - ignores: ["**/node_modules/**"], - languageOptions: { - parser: eslintParserTypeScript, - parserOptions: { - project: true, - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - ...globals.node, - ...globals.browser, - }, - }, - files: ["**/*.{js,mjs,cjs,jsx,tsx,ts}"], - settings: { - "better-tailwindcss": { - entryPoint: "src/main.css", - }, - }, - rules: { - ...eslintPluginBetterTailwindcss.configs["recommended-warn"]?.rules, - ...eslintPluginBetterTailwindcss.configs["stylistic-warn"]?.rules, - // "tailwindcss/classnames-order": "off", - "better-tailwindcss/multiline": [ - "warn", - { - group: "newLine", - printWidth: 100, - }, - ], - "better-tailwindcss/no-unregistered-classes": [ - "warn", - { - ignore: [ - "^group(?:\\/(\\S*))?$", - "^peer(?:\\/(\\S*))?$", - "select_container", - "convert_to_popup", - "convert_to_group", - "target", - "convert_to_target", - "job-details-toggle", - ], - }, - ], - }, - }, -] as Linter.Config[]; +import js from "@eslint/js"; +import eslintParserTypeScript from "@typescript-eslint/parser"; +import type { Linter } from "eslint"; +import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss"; +import simpleImportSortPlugin from "eslint-plugin-simple-import-sort"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default [ + js.configs.recommended, + ...tseslint.configs.recommended, + // ...tailwind.configs["flat/recommended"], + { + plugins: { + "simple-import-sort": simpleImportSortPlugin, + "better-tailwindcss": eslintPluginBetterTailwindcss, + }, + ignores: ["**/node_modules/**"], + languageOptions: { + parser: eslintParserTypeScript, + parserOptions: { + project: true, + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.node, + ...globals.browser, + }, + }, + files: ["**/*.{js,mjs,cjs,jsx,tsx,ts}"], + settings: { + "better-tailwindcss": { + entryPoint: "src/main.css", + }, + }, + rules: { + ...(eslintPluginBetterTailwindcss.configs["recommended-warn"] ?? {}).rules, + ...(eslintPluginBetterTailwindcss.configs["stylistic-warn"] ?? {}).rules, + // "tailwindcss/classnames-order": "off", + "better-tailwindcss/multiline": [ + "warn", + { + group: "newLine", + printWidth: 100, + }, + ], + "better-tailwindcss/no-unregistered-classes": [ + "warn", + { + ignore: [ + "^group(?:\\/(\\S*))?$", + "^peer(?:\\/(\\S*))?$", + "select_container", + "convert_to_popup", + "convert_to_group", + "target", + "convert_to_target", + "job-details-toggle", + ], + }, + ], + }, + }, +] as Linter.Config[]; diff --git a/package.json b/package.json index 6ecfac1..5f6ba95 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,15 @@ "scripts": { "dev": "bun run --watch src/index.tsx", "hot": "bun run --hot src/index.tsx", - "format": "eslint --fix .", + "format": "run-p 'format:*'", + "format:eslint": "eslint --fix .", + "format:prettier": "prettier --write .", "build": "bunx @tailwindcss/cli -i ./src/main.css -o ./public/generated.css", "lint": "run-p 'lint:*'", "lint:tsc": "tsc --noEmit", "lint:knip": "knip", - "lint:eslint": "eslint ." + "lint:eslint": "eslint .", + "lint:prettier": "prettier --check ." }, "dependencies": { "@elysiajs/html": "^1.3.0", diff --git a/postcss.config.js b/postcss.config.js index 5ba0f7a..c2ddf74 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,5 +1,5 @@ -export default { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; \ No newline at end of file +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/prettier.config.js b/prettier.config.js index 561cb0c..df16566 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -3,7 +3,7 @@ */ const config = { arrowParens: "always", - printWidth: 80, + printWidth: 100, singleQuote: false, semi: true, tabWidth: 2, diff --git a/public/script.js b/public/script.js index 81caee2..8b89194 100644 --- a/public/script.js +++ b/public/script.js @@ -72,9 +72,7 @@ function handleFile(file) { const selectContainer = document.querySelector("form .select_container"); const updateSearchBar = () => { - const convertToInput = document.querySelector( - "input[name='convert_to_search']", - ); + const convertToInput = document.querySelector("input[name='convert_to_search']"); const convertToPopup = document.querySelector(".convert_to_popup"); const convertToGroupElements = document.querySelectorAll(".convert_to_group"); const convertToGroups = {}; @@ -195,8 +193,7 @@ const deleteRow = (target) => { headers: { "Content-Type": "application/json", }, - }) - .catch((err) => console.log(err)); + }).catch((err) => console.log(err)); }; const uploadFile = (file) => { @@ -234,7 +231,7 @@ const uploadFile = (file) => { console.log(`upload progress (${file.name}):`, (100 * sent) / total); let progressbar = file.htmlRow.getElementsByTagName("progress"); - progressbar[0].value = ((100 * sent) / total); + progressbar[0].value = (100 * sent) / total; }; xhr.onerror = (e) => { diff --git a/renovate.json b/renovate.json index 2d3f60e..3418a44 100644 --- a/renovate.json +++ b/renovate.json @@ -1,11 +1,8 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - ":disableDependencyDashboard" - ], + "extends": ["config:recommended", ":disableDependencyDashboard"], "lockFileMaintenance": { "enabled": true, "automerge": true } -} \ No newline at end of file +} diff --git a/reset.d.ts b/reset.d.ts index e186b1f..a3d4a03 100644 --- a/reset.d.ts +++ b/reset.d.ts @@ -1 +1 @@ -import "@total-typescript/ts-reset"; \ No newline at end of file +import "@total-typescript/ts-reset"; diff --git a/src/components/base.tsx b/src/components/base.tsx index 3c84b75..06b6c7b 100644 --- a/src/components/base.tsx +++ b/src/components/base.tsx @@ -17,23 +17,9 @@ export const BaseHtml = ({ {title} - - - + + + diff --git a/src/converters/assimp.ts b/src/converters/assimp.ts index 76085e6..cac4f9f 100644 --- a/src/converters/assimp.ts +++ b/src/converters/assimp.ts @@ -1,139 +1,139 @@ -import { execFile } from "node:child_process"; - -export const properties = { - from: { - object: [ - "3d", - "3ds", - "3mf", - "ac", - "ac3d", - "acc", - "amf", - "amj", - "ase", - "ask", - "assbin", - "b3d", - "blend", - "bsp", - "bvh", - "cob", - "csm", - "dae", - "dxf", - "enff", - "fbx", - "glb", - "gltf", - "hmb", - "hmp", - "ifc", - "ifczip", - "iqm", - "irr", - "irrmesh", - "lwo", - "lws", - "lxo", - "m3d", - "md2", - "md3", - "md5anim", - "md5camera", - "md5mesh", - "mdc", - "mdl", - "mesh.xml", - "mesh", - "mot", - "ms3d", - "ndo", - "nff", - "obj", - "off", - "ogex", - "pk3", - "ply", - "pmx", - "prj", - "q3o", - "q3s", - "raw", - "scn", - "sib", - "smd", - "step", - "stl", - "stp", - "ter", - "uc", - "usd", - "usda", - "usdc", - "usdz", - "vta", - "x", - "x3d", - "x3db", - "xgl", - "xml", - "zae", - "zgl", - ], - }, - to: { - object: [ - "3ds", - "3mf", - "assbin", - "assjson", - "assxml", - "collada", - "dae", - "fbx", - "fbxa", - "glb", - "glb2", - "gltf", - "gltf2", - "json", - "obj", - "objnomtl", - "pbrt", - "ply", - "plyb", - "stl", - "stlb", - "stp", - "x", - ], - }, -}; - -export async function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, -): Promise { - return new Promise((resolve, reject) => { - execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve("Done"); - }); - }); -} +import { execFile } from "node:child_process"; + +export const properties = { + from: { + object: [ + "3d", + "3ds", + "3mf", + "ac", + "ac3d", + "acc", + "amf", + "amj", + "ase", + "ask", + "assbin", + "b3d", + "blend", + "bsp", + "bvh", + "cob", + "csm", + "dae", + "dxf", + "enff", + "fbx", + "glb", + "gltf", + "hmb", + "hmp", + "ifc", + "ifczip", + "iqm", + "irr", + "irrmesh", + "lwo", + "lws", + "lxo", + "m3d", + "md2", + "md3", + "md5anim", + "md5camera", + "md5mesh", + "mdc", + "mdl", + "mesh.xml", + "mesh", + "mot", + "ms3d", + "ndo", + "nff", + "obj", + "off", + "ogex", + "pk3", + "ply", + "pmx", + "prj", + "q3o", + "q3s", + "raw", + "scn", + "sib", + "smd", + "step", + "stl", + "stp", + "ter", + "uc", + "usd", + "usda", + "usdc", + "usdz", + "vta", + "x", + "x3d", + "x3db", + "xgl", + "xml", + "zae", + "zgl", + ], + }, + to: { + object: [ + "3ds", + "3mf", + "assbin", + "assjson", + "assxml", + "collada", + "dae", + "fbx", + "fbxa", + "glb", + "glb2", + "gltf", + "gltf2", + "json", + "obj", + "objnomtl", + "pbrt", + "ply", + "plyb", + "stl", + "stlb", + "stp", + "x", + ], + }, +}; + +export async function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + return new Promise((resolve, reject) => { + execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => { + if (error) { + reject(`error: ${error}`); + } + + if (stdout) { + console.log(`stdout: ${stdout}`); + } + + if (stderr) { + console.error(`stderr: ${stderr}`); + } + + resolve("Done"); + }); + }); +} diff --git a/src/converters/dvisvgm.ts b/src/converters/dvisvgm.ts index ef58c81..a9ce593 100644 --- a/src/converters/dvisvgm.ts +++ b/src/converters/dvisvgm.ts @@ -29,24 +29,20 @@ export function convert( } return new Promise((resolve, reject) => { - execFile( - "dvisvgm", - [...inputArgs, filePath, "-o", targetPath], - (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } + execFile("dvisvgm", [...inputArgs, filePath, "-o", targetPath], (error, stdout, stderr) => { + if (error) { + reject(`error: ${error}`); + } - if (stdout) { - console.log(`stdout: ${stdout}`); - } + if (stdout) { + console.log(`stdout: ${stdout}`); + } - if (stderr) { - console.error(`stderr: ${stderr}`); - } + if (stderr) { + console.error(`stderr: ${stderr}`); + } - resolve("Done"); - }, - ); + resolve("Done"); + }); }); } diff --git a/src/converters/ffmpeg.ts b/src/converters/ffmpeg.ts index feb6a66..b8bc796 100644 --- a/src/converters/ffmpeg.ts +++ b/src/converters/ffmpeg.ts @@ -1,756 +1,754 @@ -import { execFile } from "node:child_process"; - -// This could be done dynamically by running `ffmpeg -formats` and parsing the output -export const properties = { - from: { - muxer: [ - "264", - "265", - "266", - "302", - "3dostr", - "3g2", - "3gp", - "4xm", - "669", - "722", - "aa", - "aa3", - "aac", - "aax", - "ac3", - "ac4", - "ace", - "acm", - "act", - "adf", - "adp", - "ads", - "adx", - "aea", - "afc", - "aiff", - "aix", - "al", - "alaw", - "alias_pix", - "alp", - "alsa", - "amf", - "amr", - "amrnb", - "amrwb", - "ams", - "anm", - "ans", - "apc", - "ape", - "apl", - "apm", - "apng", - "aptx", - "aptxhd", - "aqt", - "aqtitle", - "argo_asf", - "argo_brp", - "art", - "asc", - "asf", - "asf_o", - "ass", - "ast", - "au", - "av1", - "avc", - "avi", - "avif", - "avr", - "avs", - "avs2", - "avs3", - "awb", - "bcstm", - "bethsoftvid", - "bfi", - "bfstm", - "bin", - "bink", - "binka", - "bit", - "bitpacked", - "bmv", - "bmp", - "bonk", - "boa", - "brender_pix", - "brstm", - "c2", - "c93", - "caf", - "cavsvideo", - "cdata", - "cdg", - "cdxl", - "cgi", - "cif", - "cine", - "codec2", - "codec2raw", - "concat", - "cri", - "dash", - "dat", - "data", - "daud", - "dav", - "dbm", - "dcstr", - "dds", - "derf", - "dfpwm", - "dfa", - "dhav", - "dif", - "digi", - "dirac", - "diz", - "dmf", - "dnxhd", - "dpx_pipe", - "dsf", - "dsicin", - "dsm", - "dss", - "dtk", - "dtm", - "dts", - "dtshd", - "dv", - "dvbsub", - "dvbtxt", - "dxa", - "ea", - "eac3", - "ea_cdata", - "epaf", - "exr_pipe", - "f32be", - "f32le", - "ec3", - "evc", - "f4v", - "f64be", - "f64le", - "fap", - "far", - "fbdev", - "ffmetadata", - "filmstrip", - "film_cpk", - "fits", - "flac", - "flic", - "flm", - "flv", - "frm", - "fsb", - "fwse", - "g722", - "g723_1", - "g726", - "g726le", - "g729", - "gdm", - "gdv", - "genh", - "gif", - "gsm", - "gxf", - "h261", - "h263", - "h264", - "h265", - "h266", - "h26l", - "hca", - "hcom", - "hevc", - "hls", - "hnm", - "ice", - "ico", - "idcin", - "idf", - "idx", - "iec61883", - "iff", - "ifv", - "ilbc", - "image2", - "imf", - "imx", - "ingenient", - "ipmovie", - "ipu", - "ircam", - "ism", - "isma", - "ismv", - "iss", - "it", - "iv8", - "ivf", - "ivr", - "j2b", - "j2k", - "jack", - "jacosub", - "jv", - "jpegls", - "jpeg", - "jxl", - "kmsgrab", - "kux", - "kvag", - "lavfi", - "laf", - "lmlm4", - "loas", - "lrc", - "luodat", - "lvf", - "lxf", - "m15", - "m2a", - "m4a", - "m4b", - "m4v", - "mac", - "mca", - "mcc", - "mdl", - "med", - "microdvd", - "mj2", - "mjpeg", - "mjpg", - "mk3d", - "mka", - "mks", - "mkv", - "mlp", - "mlv", - "mm", - "mmcmp", - "mmf", - "mms", - "mo3", - "mod", - "mods", - "moflex", - "mov", - "mp2", - "mp3", - "mp4", - "mpa", - "mpc", - "mpc8", - "mpeg", - "mpg", - "mpjpeg", - "mpl2", - "mpo", - "mpsub", - "mptm", - "msbc", - "msf", - "msnwctcp", - "msp", - "mt2", - "mtaf", - "mtm", - "mtv", - "mulaw", - "musx", - "mv", - "mvi", - "mxf", - "mxg", - "nc", - "nfo", - "nist", - "nistsphere", - "nsp", - "nst", - "nsv", - "nut", - "nuv", - "obu", - "ogg", - "okt", - "oma", - "omg", - "opus", - "openal", - "oss", - "osq", - "paf", - "pdv", - "pam", - "pbm", - "pcx", - "pgmyuv", - "pgm", - "pgx", - "photocd", - "pictor", - "pjs", - "plm", - "pmp", - "png", - "ppm", - "pp", - "psd", - "psm", - "psp", - "psxstr", - "pt36", - "ptm", - "pulse", - "pva", - "pvf", - "qcif", - "qcp", - "qdraw", - "r3d", - "rawvideo", - "rco", - "rcv", - "realtext", - "redspark", - "rgb", - "rl2", - "rm", - "roq", - "rpl", - "rka", - "rsd", - "rso", - "rt", - "rtp", - "rtsp", - "s16be", - "s16le", - "s24be", - "s24le", - "s32be", - "s32le", - "s337m", - "s3m", - "s8", - "sami", - "sap", - "sb", - "sbc", - "sbg", - "scc", - "sdns", - "sdp", - "sdr2", - "sds", - "sdx", - "ser", - "sf", - "sfx", - "sfx2", - "sga", - "sgi", - "shn", - "siff", - "sln", - "smi", - "smjpeg", - "smk", - "smush", - "sndio", - "sol", - "son", - "sox", - "spdif", - "sph", - "srt", - "ss2", - "ssa", - "st26", - "stk", - "stl", - "stm", - "stp", - "str", - "sub", - "sup", - "svag", - "svg", - "svs", - "sw", - "swf", - "tak", - "tco", - "tedcaptions", - "thd", - "thp", - "tiertexseq", - "tif", - "tiff", - "tmv", - "truehd", - "tta", - "tty", - "txd", - "txt", - "ty", - "ty+", - "u16be", - "u16le", - "u24be", - "u24le", - "u32be", - "u32le", - "u8", - "ub", - "ul", - "ult", - "umx", - "usm", - "uw", - "v", - "v210", - "v210x", - "vag", - "vb", - "vc1", - "vc1test", - "vidc", - "video4linux2", - "viv", - "vividas", - "vivo", - "vmd", - "vobsub", - "voc", - "vpk", - "vplayer", - "vqe", - "vqf", - "vql", - "vt", - "vtt", - "vvc", - "w64", - "wa", - "wav", - "way", - "wc3movie", - "webm", - "webp", - "webvtt", - "wow", - "wsaud", - "wsd", - "wsvqa", - "wtv", - "wv", - "wve", - "x11grab", - "xa", - "xbin", - "xl", - "xm", - "xmd", - "xmv", - "xpk", - "xvag", - "xwma", - "y4m", - "yop", - "yuv", - "yuv10", - ], - }, - to: { - muxer: [ - "264", - "265", - "266", - "302", - "3g2", - "3gp", - "a64", - "aac", - "ac3", - "ac4", - "adts", - "adx", - "afc", - "aif", - "aifc", - "aiff", - "al", - "amr", - "amv", - "apm", - "apng", - "aptx", - "aptxhd", - "asf", - "ass", - "ast", - "au", - "aud", - "av1.mkv", - "av1.mp4", - "avi", - "avif", - "avs", - "avs2", - "avs3", - "bit", - "bmp", - "c2", - "caf", - "cavs", - "chk", - "cpk", - "cvg", - "dfpwm", - "dnxhd", - "dnxhr", - "dpx", - "drc", - "dts", - "dv", - "dvd", - "eac3", - "ec3", - "evc", - "exr", - "f4v", - "ffmeta", - "fits", - "flac", - "flm", - "flv", - "g722", - "gif", - "gsm", - "gxf", - "h261", - "h263", - "h264.mkv", - "h264.mp4", - "h265.mkv", - "h265.mp4", - "h266.mkv", - "hdr", - "hevc", - "ico", - "im1", - "im24", - "im8", - "ircam", - "isma", - "ismv", - "ivf", - "j2c", - "j2k", - "jls", - "jp2", - "jpeg", - "jpg", - "js", - "jss", - "jxl", - "latm", - "lbc", - "ljpg", - "loas", - "lrc", - "m1v", - "m2a", - "m2t", - "m2ts", - "m2v", - "m3u8", - "m4a", - "m4b", - "m4v", - "mjpeg", - "mjpg", - "mkv", - "mlp", - "mmf", - "mov", - "mp2", - "mp3", - "mp4", - "mpa", - "mpd", - "mpeg", - "mpg", - "msbc", - "mts", - "mxf", - "nut", - "obu", - "oga", - "ogg", - "ogv", - "oma", - "opus", - "pam", - "pbm", - "pcm", - "pcx", - "pfm", - "pgm", - "pgmyuv", - "phm", - "pix", - "png", - "ppm", - "psp", - "qoi", - "ra", - "ras", - "rco", - "rcv", - "rgb", - "rm", - "roq", - "rs", - "rso", - "sb", - "sbc", - "scc", - "sf", - "sgi", - "sox", - "spdif", - "spx", - "srt", - "ssa", - "sub", - "sun", - "sunras", - "sup", - "sw", - "swf", - "tco", - "tga", - "thd", - "tif", - "tiff", - "ts", - "tta", - "ttml", - "tun", - "ub", - "ul", - "uw", - "vag", - "vbn", - "vc1", - "vc2", - "vob", - "voc", - "vtt", - "vvc", - "w64", - "wav", - "wbmp", - "webm", - "webp", - "wma", - "wmv", - "wtv", - "wv", - "xbm", - "xface", - "xml", - "xwd", - "y", - "y4m", - "yuv", - ], - }, -}; - -export async function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, -): Promise { - let extraArgs: string[] = []; - let message = "Done"; - - if (convertTo === "ico") { - // make sure image is 256x256 or smaller - extraArgs = [ - "-filter:v", - "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease", - ]; - message = "Done: resized to 256x256"; - } - - if (convertTo.split(".").length > 1) { - // support av1.mkv and av1.mp4 and h265.mp4 etc. - const split = convertTo.split("."); - const codec_short = split[0]; - - switch (codec_short) { - case "av1": - extraArgs.push("-c:v", "libaom-av1"); - break; - case "h264": - extraArgs.push("-c:v", "libx264"); - break; - case "h265": - extraArgs.push("-c:v", "libx265"); - break; - case "h266": - extraArgs.push("-c:v", "libx266"); - break; - } - } - - // Parse FFMPEG_ARGS environment variable into array - const ffmpegArgs = process.env.FFMPEG_ARGS - ? process.env.FFMPEG_ARGS.split(/\s+/) - : []; - - return new Promise((resolve, reject) => { - execFile( - "ffmpeg", - [...ffmpegArgs, "-i", filePath, ...extraArgs, targetPath], - (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve(message); - }, - ); - }); -} +import { execFile } from "node:child_process"; + +// This could be done dynamically by running `ffmpeg -formats` and parsing the output +export const properties = { + from: { + muxer: [ + "264", + "265", + "266", + "302", + "3dostr", + "3g2", + "3gp", + "4xm", + "669", + "722", + "aa", + "aa3", + "aac", + "aax", + "ac3", + "ac4", + "ace", + "acm", + "act", + "adf", + "adp", + "ads", + "adx", + "aea", + "afc", + "aiff", + "aix", + "al", + "alaw", + "alias_pix", + "alp", + "alsa", + "amf", + "amr", + "amrnb", + "amrwb", + "ams", + "anm", + "ans", + "apc", + "ape", + "apl", + "apm", + "apng", + "aptx", + "aptxhd", + "aqt", + "aqtitle", + "argo_asf", + "argo_brp", + "art", + "asc", + "asf", + "asf_o", + "ass", + "ast", + "au", + "av1", + "avc", + "avi", + "avif", + "avr", + "avs", + "avs2", + "avs3", + "awb", + "bcstm", + "bethsoftvid", + "bfi", + "bfstm", + "bin", + "bink", + "binka", + "bit", + "bitpacked", + "bmv", + "bmp", + "bonk", + "boa", + "brender_pix", + "brstm", + "c2", + "c93", + "caf", + "cavsvideo", + "cdata", + "cdg", + "cdxl", + "cgi", + "cif", + "cine", + "codec2", + "codec2raw", + "concat", + "cri", + "dash", + "dat", + "data", + "daud", + "dav", + "dbm", + "dcstr", + "dds", + "derf", + "dfpwm", + "dfa", + "dhav", + "dif", + "digi", + "dirac", + "diz", + "dmf", + "dnxhd", + "dpx_pipe", + "dsf", + "dsicin", + "dsm", + "dss", + "dtk", + "dtm", + "dts", + "dtshd", + "dv", + "dvbsub", + "dvbtxt", + "dxa", + "ea", + "eac3", + "ea_cdata", + "epaf", + "exr_pipe", + "f32be", + "f32le", + "ec3", + "evc", + "f4v", + "f64be", + "f64le", + "fap", + "far", + "fbdev", + "ffmetadata", + "filmstrip", + "film_cpk", + "fits", + "flac", + "flic", + "flm", + "flv", + "frm", + "fsb", + "fwse", + "g722", + "g723_1", + "g726", + "g726le", + "g729", + "gdm", + "gdv", + "genh", + "gif", + "gsm", + "gxf", + "h261", + "h263", + "h264", + "h265", + "h266", + "h26l", + "hca", + "hcom", + "hevc", + "hls", + "hnm", + "ice", + "ico", + "idcin", + "idf", + "idx", + "iec61883", + "iff", + "ifv", + "ilbc", + "image2", + "imf", + "imx", + "ingenient", + "ipmovie", + "ipu", + "ircam", + "ism", + "isma", + "ismv", + "iss", + "it", + "iv8", + "ivf", + "ivr", + "j2b", + "j2k", + "jack", + "jacosub", + "jv", + "jpegls", + "jpeg", + "jxl", + "kmsgrab", + "kux", + "kvag", + "lavfi", + "laf", + "lmlm4", + "loas", + "lrc", + "luodat", + "lvf", + "lxf", + "m15", + "m2a", + "m4a", + "m4b", + "m4v", + "mac", + "mca", + "mcc", + "mdl", + "med", + "microdvd", + "mj2", + "mjpeg", + "mjpg", + "mk3d", + "mka", + "mks", + "mkv", + "mlp", + "mlv", + "mm", + "mmcmp", + "mmf", + "mms", + "mo3", + "mod", + "mods", + "moflex", + "mov", + "mp2", + "mp3", + "mp4", + "mpa", + "mpc", + "mpc8", + "mpeg", + "mpg", + "mpjpeg", + "mpl2", + "mpo", + "mpsub", + "mptm", + "msbc", + "msf", + "msnwctcp", + "msp", + "mt2", + "mtaf", + "mtm", + "mtv", + "mulaw", + "musx", + "mv", + "mvi", + "mxf", + "mxg", + "nc", + "nfo", + "nist", + "nistsphere", + "nsp", + "nst", + "nsv", + "nut", + "nuv", + "obu", + "ogg", + "okt", + "oma", + "omg", + "opus", + "openal", + "oss", + "osq", + "paf", + "pdv", + "pam", + "pbm", + "pcx", + "pgmyuv", + "pgm", + "pgx", + "photocd", + "pictor", + "pjs", + "plm", + "pmp", + "png", + "ppm", + "pp", + "psd", + "psm", + "psp", + "psxstr", + "pt36", + "ptm", + "pulse", + "pva", + "pvf", + "qcif", + "qcp", + "qdraw", + "r3d", + "rawvideo", + "rco", + "rcv", + "realtext", + "redspark", + "rgb", + "rl2", + "rm", + "roq", + "rpl", + "rka", + "rsd", + "rso", + "rt", + "rtp", + "rtsp", + "s16be", + "s16le", + "s24be", + "s24le", + "s32be", + "s32le", + "s337m", + "s3m", + "s8", + "sami", + "sap", + "sb", + "sbc", + "sbg", + "scc", + "sdns", + "sdp", + "sdr2", + "sds", + "sdx", + "ser", + "sf", + "sfx", + "sfx2", + "sga", + "sgi", + "shn", + "siff", + "sln", + "smi", + "smjpeg", + "smk", + "smush", + "sndio", + "sol", + "son", + "sox", + "spdif", + "sph", + "srt", + "ss2", + "ssa", + "st26", + "stk", + "stl", + "stm", + "stp", + "str", + "sub", + "sup", + "svag", + "svg", + "svs", + "sw", + "swf", + "tak", + "tco", + "tedcaptions", + "thd", + "thp", + "tiertexseq", + "tif", + "tiff", + "tmv", + "truehd", + "tta", + "tty", + "txd", + "txt", + "ty", + "ty+", + "u16be", + "u16le", + "u24be", + "u24le", + "u32be", + "u32le", + "u8", + "ub", + "ul", + "ult", + "umx", + "usm", + "uw", + "v", + "v210", + "v210x", + "vag", + "vb", + "vc1", + "vc1test", + "vidc", + "video4linux2", + "viv", + "vividas", + "vivo", + "vmd", + "vobsub", + "voc", + "vpk", + "vplayer", + "vqe", + "vqf", + "vql", + "vt", + "vtt", + "vvc", + "w64", + "wa", + "wav", + "way", + "wc3movie", + "webm", + "webp", + "webvtt", + "wow", + "wsaud", + "wsd", + "wsvqa", + "wtv", + "wv", + "wve", + "x11grab", + "xa", + "xbin", + "xl", + "xm", + "xmd", + "xmv", + "xpk", + "xvag", + "xwma", + "y4m", + "yop", + "yuv", + "yuv10", + ], + }, + to: { + muxer: [ + "264", + "265", + "266", + "302", + "3g2", + "3gp", + "a64", + "aac", + "ac3", + "ac4", + "adts", + "adx", + "afc", + "aif", + "aifc", + "aiff", + "al", + "amr", + "amv", + "apm", + "apng", + "aptx", + "aptxhd", + "asf", + "ass", + "ast", + "au", + "aud", + "av1.mkv", + "av1.mp4", + "avi", + "avif", + "avs", + "avs2", + "avs3", + "bit", + "bmp", + "c2", + "caf", + "cavs", + "chk", + "cpk", + "cvg", + "dfpwm", + "dnxhd", + "dnxhr", + "dpx", + "drc", + "dts", + "dv", + "dvd", + "eac3", + "ec3", + "evc", + "exr", + "f4v", + "ffmeta", + "fits", + "flac", + "flm", + "flv", + "g722", + "gif", + "gsm", + "gxf", + "h261", + "h263", + "h264.mkv", + "h264.mp4", + "h265.mkv", + "h265.mp4", + "h266.mkv", + "hdr", + "hevc", + "ico", + "im1", + "im24", + "im8", + "ircam", + "isma", + "ismv", + "ivf", + "j2c", + "j2k", + "jls", + "jp2", + "jpeg", + "jpg", + "js", + "jss", + "jxl", + "latm", + "lbc", + "ljpg", + "loas", + "lrc", + "m1v", + "m2a", + "m2t", + "m2ts", + "m2v", + "m3u8", + "m4a", + "m4b", + "m4v", + "mjpeg", + "mjpg", + "mkv", + "mlp", + "mmf", + "mov", + "mp2", + "mp3", + "mp4", + "mpa", + "mpd", + "mpeg", + "mpg", + "msbc", + "mts", + "mxf", + "nut", + "obu", + "oga", + "ogg", + "ogv", + "oma", + "opus", + "pam", + "pbm", + "pcm", + "pcx", + "pfm", + "pgm", + "pgmyuv", + "phm", + "pix", + "png", + "ppm", + "psp", + "qoi", + "ra", + "ras", + "rco", + "rcv", + "rgb", + "rm", + "roq", + "rs", + "rso", + "sb", + "sbc", + "scc", + "sf", + "sgi", + "sox", + "spdif", + "spx", + "srt", + "ssa", + "sub", + "sun", + "sunras", + "sup", + "sw", + "swf", + "tco", + "tga", + "thd", + "tif", + "tiff", + "ts", + "tta", + "ttml", + "tun", + "ub", + "ul", + "uw", + "vag", + "vbn", + "vc1", + "vc2", + "vob", + "voc", + "vtt", + "vvc", + "w64", + "wav", + "wbmp", + "webm", + "webp", + "wma", + "wmv", + "wtv", + "wv", + "xbm", + "xface", + "xml", + "xwd", + "y", + "y4m", + "yuv", + ], + }, +}; + +export async function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + let extraArgs: string[] = []; + let message = "Done"; + + if (convertTo === "ico") { + // make sure image is 256x256 or smaller + extraArgs = [ + "-filter:v", + "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease", + ]; + message = "Done: resized to 256x256"; + } + + if (convertTo.split(".").length > 1) { + // support av1.mkv and av1.mp4 and h265.mp4 etc. + const split = convertTo.split("."); + const codec_short = split[0]; + + switch (codec_short) { + case "av1": + extraArgs.push("-c:v", "libaom-av1"); + break; + case "h264": + extraArgs.push("-c:v", "libx264"); + break; + case "h265": + extraArgs.push("-c:v", "libx265"); + break; + case "h266": + extraArgs.push("-c:v", "libx266"); + break; + } + } + + // Parse FFMPEG_ARGS environment variable into array + const ffmpegArgs = process.env.FFMPEG_ARGS ? process.env.FFMPEG_ARGS.split(/\s+/) : []; + + return new Promise((resolve, reject) => { + execFile( + "ffmpeg", + [...ffmpegArgs, "-i", filePath, ...extraArgs, targetPath], + (error, stdout, stderr) => { + if (error) { + reject(`error: ${error}`); + } + + if (stdout) { + console.log(`stdout: ${stdout}`); + } + + if (stderr) { + console.error(`stderr: ${stderr}`); + } + + resolve(message); + }, + ); + }); +} diff --git a/src/converters/graphicsmagick.ts b/src/converters/graphicsmagick.ts index 3186668..dbd8996 100644 --- a/src/converters/graphicsmagick.ts +++ b/src/converters/graphicsmagick.ts @@ -1,340 +1,336 @@ -import { execFile } from "node:child_process"; - -export const properties = { - from: { - image: [ - "3fr", - "8bim", - "8bimtext", - "8bimwtext", - "app1", - "app1jpeg", - "art", - "arw", - "avs", - "b", - "bie", - "bigtiff", - "bmp", - "c", - "cals", - "caption", - "cin", - "cmyk", - "cmyka", - "cr2", - "crw", - "cur", - "cut", - "dcm", - "dcr", - "dcx", - "dng", - "dpx", - "epdf", - "epi", - "eps", - "epsf", - "epsi", - "ept", - "ept2", - "ept3", - "erf", - "exif", - "fax", - "file", - "fits", - "fractal", - "ftp", - "g", - "gif", - "gif87", - "gradient", - "gray", - "graya", - "heic", - "heif", - "hrz", - "http", - "icb", - "icc", - "icm", - "ico", - "icon", - "identity", - "image", - "iptc", - "iptctext", - "iptcwtext", - "jbg", - "jbig", - "jng", - "jnx", - "jpeg", - "jpg", - "k", - "k25", - "kdc", - "label", - "m", - "mac", - "map", - "mat", - "mef", - "miff", - "mng", - "mono", - "mpc", - "mrw", - "msl", - "mtv", - "mvg", - "nef", - "null", - "o", - "orf", - "otb", - "p7", - "pal", - "palm", - "pam", - "pbm", - "pcd", - "pcds", - "pct", - "pcx", - "pdb", - "pdf", - "pef", - "pfa", - "pfb", - "pgm", - "picon", - "pict", - "pix", - "plasma", - "png", - "png00", - "png24", - "png32", - "png48", - "png64", - "png8", - "pnm", - "ppm", - "ps", - "ptif", - "pwp", - "r", - "raf", - "ras", - "rgb", - "rgba", - "rla", - "rle", - "sct", - "sfw", - "sgi", - "sr2", - "srf", - "stegano", - "sun", - "svg", - "svgz", - "text", - "tga", - "tif", - "tiff", - "tile", - "tim", - "topol", - "ttf", - "txt", - "uyvy", - "vda", - "vicar", - "vid", - "viff", - "vst", - "wbmp", - "webp", - "wmf", - "wpg", - "x3f", - "xbm", - "xc", - "xcf", - "xmp", - "xpm", - "xv", - "xwd", - "y", - "yuv", - ], - }, - to: { - image: [ - "8bim", - "8bimtext", - "8bimwtext", - "app1", - "app1jpeg", - "art", - "avs", - "b", - "bie", - "bigtiff", - "bmp", - "bmp2", - "bmp3", - "brf", - "c", - "cals", - "cin", - "cmyk", - "cmyka", - "dcx", - "dpx", - "epdf", - "epi", - "eps", - "eps2", - "eps3", - "epsf", - "epsi", - "ept", - "ept2", - "ept3", - "exif", - "fax", - "fits", - "g", - "gif", - "gif87", - "gray", - "graya", - "histogram", - "html", - "icb", - "icc", - "icm", - "info", - "iptc", - "iptctext", - "iptcwtext", - "isobrl", - "isobrl6", - "jbg", - "jbig", - "jng", - "jpeg", - "k", - "m", - "m2v", - "map", - "mat", - "matte", - "miff", - "mng", - "mono", - "mpc", - "mpeg", - "mpg", - "msl", - "mtv", - "mvg", - "null", - "o", - "otb", - "p7", - "pal", - "pam", - "pbm", - "pcd", - "pcds", - "pcl", - "pct", - "pcx", - "pdb", - "pdf", - "pgm", - "picon", - "pict", - "png", - "png00", - "png24", - "png32", - "png48", - "png64", - "png8", - "pnm", - "ppm", - "preview", - "ps", - "ps2", - "ps3", - "ptif", - "r", - "ras", - "rgb", - "rgba", - "sgi", - "shtml", - "sun", - "text", - "tga", - "tiff", - "txt", - "ubrl", - "ubrl6", - "uil", - "uyvy", - "vda", - "vicar", - "vid", - "viff", - "vst", - "wbmp", - "webp", - "x", - "xbm", - "xmp", - "xpm", - "xv", - "xwd", - "y", - "yuv", - ], - }, -}; - -export function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, -): Promise { - return new Promise((resolve, reject) => { - execFile( - "gm", - ["convert", filePath, targetPath], - (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve("Done"); - }, - ); - }); -} +import { execFile } from "node:child_process"; + +export const properties = { + from: { + image: [ + "3fr", + "8bim", + "8bimtext", + "8bimwtext", + "app1", + "app1jpeg", + "art", + "arw", + "avs", + "b", + "bie", + "bigtiff", + "bmp", + "c", + "cals", + "caption", + "cin", + "cmyk", + "cmyka", + "cr2", + "crw", + "cur", + "cut", + "dcm", + "dcr", + "dcx", + "dng", + "dpx", + "epdf", + "epi", + "eps", + "epsf", + "epsi", + "ept", + "ept2", + "ept3", + "erf", + "exif", + "fax", + "file", + "fits", + "fractal", + "ftp", + "g", + "gif", + "gif87", + "gradient", + "gray", + "graya", + "heic", + "heif", + "hrz", + "http", + "icb", + "icc", + "icm", + "ico", + "icon", + "identity", + "image", + "iptc", + "iptctext", + "iptcwtext", + "jbg", + "jbig", + "jng", + "jnx", + "jpeg", + "jpg", + "k", + "k25", + "kdc", + "label", + "m", + "mac", + "map", + "mat", + "mef", + "miff", + "mng", + "mono", + "mpc", + "mrw", + "msl", + "mtv", + "mvg", + "nef", + "null", + "o", + "orf", + "otb", + "p7", + "pal", + "palm", + "pam", + "pbm", + "pcd", + "pcds", + "pct", + "pcx", + "pdb", + "pdf", + "pef", + "pfa", + "pfb", + "pgm", + "picon", + "pict", + "pix", + "plasma", + "png", + "png00", + "png24", + "png32", + "png48", + "png64", + "png8", + "pnm", + "ppm", + "ps", + "ptif", + "pwp", + "r", + "raf", + "ras", + "rgb", + "rgba", + "rla", + "rle", + "sct", + "sfw", + "sgi", + "sr2", + "srf", + "stegano", + "sun", + "svg", + "svgz", + "text", + "tga", + "tif", + "tiff", + "tile", + "tim", + "topol", + "ttf", + "txt", + "uyvy", + "vda", + "vicar", + "vid", + "viff", + "vst", + "wbmp", + "webp", + "wmf", + "wpg", + "x3f", + "xbm", + "xc", + "xcf", + "xmp", + "xpm", + "xv", + "xwd", + "y", + "yuv", + ], + }, + to: { + image: [ + "8bim", + "8bimtext", + "8bimwtext", + "app1", + "app1jpeg", + "art", + "avs", + "b", + "bie", + "bigtiff", + "bmp", + "bmp2", + "bmp3", + "brf", + "c", + "cals", + "cin", + "cmyk", + "cmyka", + "dcx", + "dpx", + "epdf", + "epi", + "eps", + "eps2", + "eps3", + "epsf", + "epsi", + "ept", + "ept2", + "ept3", + "exif", + "fax", + "fits", + "g", + "gif", + "gif87", + "gray", + "graya", + "histogram", + "html", + "icb", + "icc", + "icm", + "info", + "iptc", + "iptctext", + "iptcwtext", + "isobrl", + "isobrl6", + "jbg", + "jbig", + "jng", + "jpeg", + "k", + "m", + "m2v", + "map", + "mat", + "matte", + "miff", + "mng", + "mono", + "mpc", + "mpeg", + "mpg", + "msl", + "mtv", + "mvg", + "null", + "o", + "otb", + "p7", + "pal", + "pam", + "pbm", + "pcd", + "pcds", + "pcl", + "pct", + "pcx", + "pdb", + "pdf", + "pgm", + "picon", + "pict", + "png", + "png00", + "png24", + "png32", + "png48", + "png64", + "png8", + "pnm", + "ppm", + "preview", + "ps", + "ps2", + "ps3", + "ptif", + "r", + "ras", + "rgb", + "rgba", + "sgi", + "shtml", + "sun", + "text", + "tga", + "tiff", + "txt", + "ubrl", + "ubrl6", + "uil", + "uyvy", + "vda", + "vicar", + "vid", + "viff", + "vst", + "wbmp", + "webp", + "x", + "xbm", + "xmp", + "xpm", + "xv", + "xwd", + "y", + "yuv", + ], + }, +}; + +export function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + 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"); + }); + }); +} diff --git a/src/converters/imagemagick.ts b/src/converters/imagemagick.ts index f4fc234..4122a24 100644 --- a/src/converters/imagemagick.ts +++ b/src/converters/imagemagick.ts @@ -452,12 +452,7 @@ export function convert( let inputArgs: string[] = []; if (convertTo === "ico") { - outputArgs = [ - "-define", - "icon:auto-resize=256,128,64,48,32,16", - "-background", - "none", - ]; + outputArgs = ["-define", "icon:auto-resize=256,128,64,48,32,16", "-background", "none"]; if (fileType === "svg") { // this might be a bit too much, but it works diff --git a/src/converters/inkscape.ts b/src/converters/inkscape.ts index 7af7163..9e13fb0 100644 --- a/src/converters/inkscape.ts +++ b/src/converters/inkscape.ts @@ -36,24 +36,20 @@ export function convert( options?: unknown, ): Promise { return new Promise((resolve, reject) => { - execFile( - "inkscape", - [filePath, "-o", targetPath], - (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } + execFile("inkscape", [filePath, "-o", targetPath], (error, stdout, stderr) => { + if (error) { + reject(`error: ${error}`); + } - if (stdout) { - console.log(`stdout: ${stdout}`); - } + if (stdout) { + console.log(`stdout: ${stdout}`); + } - if (stderr) { - console.error(`stderr: ${stderr}`); - } + if (stderr) { + console.error(`stderr: ${stderr}`); + } - resolve("Done"); - }, - ); + resolve("Done"); + }); }); } diff --git a/src/converters/libheif.ts b/src/converters/libheif.ts index 4935683..38ac949 100644 --- a/src/converters/libheif.ts +++ b/src/converters/libheif.ts @@ -2,19 +2,7 @@ import { execFile } from "child_process"; export const properties = { from: { - images: [ - "avci", - "avcs", - "avif", - "h264", - "heic", - "heics", - "heif", - "heifs", - "hif", - "mkv", - "mp4", - ], + images: ["avci", "avcs", "avif", "h264", "heic", "heics", "heif", "heifs", "hif", "mkv", "mp4"], }, to: { images: ["jpeg", "png", "y4m"], @@ -30,24 +18,20 @@ export function convert( options?: unknown, ): Promise { return new Promise((resolve, reject) => { - execFile( - "heif-convert", - [filePath, targetPath], - (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } + execFile("heif-convert", [filePath, targetPath], (error, stdout, stderr) => { + if (error) { + reject(`error: ${error}`); + } - if (stdout) { - console.log(`stdout: ${stdout}`); - } + if (stdout) { + console.log(`stdout: ${stdout}`); + } - if (stderr) { - console.error(`stderr: ${stderr}`); - } + if (stderr) { + console.error(`stderr: ${stderr}`); + } - resolve("Done"); - }, - ); + resolve("Done"); + }); }); } diff --git a/src/converters/libjxl.ts b/src/converters/libjxl.ts index d08d6fd..0ef11a8 100644 --- a/src/converters/libjxl.ts +++ b/src/converters/libjxl.ts @@ -1,71 +1,49 @@ -import { execFile } from "node:child_process"; - -// declare possible conversions -export const properties = { - from: { - jxl: ["jxl"], - images: [ - "apng", - "exr", - "gif", - "jpeg", - "pam", - "pfm", - "pgm", - "pgx", - "png", - "ppm", - ], - }, - to: { - jxl: [ - "apng", - "exr", - "gif", - "jpeg", - "pam", - "pfm", - "pgm", - "pgx", - "png", - "ppm", - ], - images: ["jxl"], - }, -}; - -export function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, -): Promise { - let tool = ""; - if (fileType === "jxl") { - tool = "djxl"; - } - - if (convertTo === "jxl") { - tool = "cjxl"; - } - - return new Promise((resolve, reject) => { - execFile(tool, [filePath, targetPath], (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve("Done"); - }); - }); -} +import { execFile } from "node:child_process"; + +// declare possible conversions +export const properties = { + from: { + jxl: ["jxl"], + images: ["apng", "exr", "gif", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"], + }, + to: { + jxl: ["apng", "exr", "gif", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"], + images: ["jxl"], + }, +}; + +export function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + 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"); + }); + }); +} diff --git a/src/converters/main.ts b/src/converters/main.ts index 3d89e6a..a9f2e42 100644 --- a/src/converters/main.ts +++ b/src/converters/main.ts @@ -1,19 +1,21 @@ import { normalizeFiletype } from "../helpers/normalizeFiletype"; import { convert as convertassimp, properties as propertiesassimp } from "./assimp"; +import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre"; +import { convert as convertDvisvgm, properties as propertiesDvisvgm } from "./dvisvgm"; import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg"; -import { convert as convertGraphicsmagick, properties as propertiesGraphicsmagick } from "./graphicsmagick"; +import { + convert as convertGraphicsmagick, + properties as propertiesGraphicsmagick, +} from "./graphicsmagick"; +import { convert as convertImagemagick, properties as propertiesImagemagick } from "./imagemagick"; import { convert as convertInkscape, properties as propertiesInkscape } from "./inkscape"; +import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif"; import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl"; import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc"; +import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace"; import { convert as convertresvg, properties as propertiesresvg } from "./resvg"; import { convert as convertImage, properties as propertiesImage } from "./vips"; import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex"; -import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre"; -import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif"; -import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace"; -import { convert as convertImagemagick, properties as propertiesImagemagick } from "./imagemagick"; -import { convert as convertDvisvgm, properties as propertiesDvisvgm } from "./dvisvgm"; - // This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular @@ -113,7 +115,7 @@ export async function mainConverter( ) { const fileType = normalizeFiletype(fileTypeOriginal); - let converterFunc: typeof properties["libjxl"]["converter"] | undefined; + let converterFunc: (typeof properties)["libjxl"]["converter"] | undefined; if (converterName) { converterFunc = properties[converterName]?.converter; @@ -139,20 +141,12 @@ export async function mainConverter( } if (!converterFunc) { - console.log( - `No available converter supports converting from ${fileType} to ${convertTo}.`, - ); + console.log(`No available converter supports converting from ${fileType} to ${convertTo}.`); return "File type not supported"; } try { - const result = await converterFunc( - inputFilePath, - fileType, - convertTo, - targetPath, - options, - ); + const result = await converterFunc(inputFilePath, fileType, convertTo, targetPath, options); console.log( `Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`, @@ -192,8 +186,7 @@ for (const converterName in properties) { possibleTargets[extension] = {}; } - possibleTargets[extension][converterName] = - converterProperties.to[key] || []; + possibleTargets[extension][converterName] = converterProperties.to[key] || []; } } } @@ -294,4 +287,4 @@ export const getAllInputs = (converter: string) => { // } // // print the number of unique Inputs and Outputs -// console.log(`Unique Formats: ${uniqueFormats.size}`); \ No newline at end of file +// console.log(`Unique Formats: ${uniqueFormats.size}`); diff --git a/src/converters/pandoc.ts b/src/converters/pandoc.ts index a3995fa..6aab79d 100644 --- a/src/converters/pandoc.ts +++ b/src/converters/pandoc.ts @@ -1,162 +1,162 @@ -import { execFile } from "node:child_process"; - -export const properties = { - from: { - text: [ - "textile", - "tikiwiki", - "tsv", - "twiki", - "typst", - "vimwiki", - "biblatex", - "bibtex", - "bits", - "commonmark", - "commonmark_x", - "creole", - "csljson", - "csv", - "djot", - "docbook", - "docx", - "dokuwiki", - "endnotexml", - "epub", - "fb2", - "gfm", - "haddock", - "html", - "ipynb", - "jats", - "jira", - "json", - "latex", - "man", - "markdown", - "markdown_mmd", - "markdown_phpextra", - "markdown_strict", - "mediawiki", - "muse", - "pandoc native", - "opml", - "org", - "ris", - "rst", - "rtf", - "t2t", - ], - }, - to: { - text: [ - "tei", - "texinfo", - "textile", - "typst", - "xwiki", - "zimwiki", - "asciidoc", - "asciidoc_legacy", - "asciidoctor", - "beamer", - "biblatex", - "bibtex", - "chunkedhtml", - "commonmark", - "commonmark_x", - "context", - "csljson", - "djot", - "docbook", - "docbook4", - "docbook5", - "docx", - "dokuwiki", - "dzslides", - "epub", - "epub2", - "epub3", - "fb2", - "gfm", - "haddock", - "html", - "html4", - "html5", - "icml", - "ipynb", - "jats", - "jats_archiving", - "jats_articleauthoring", - "jats_publishing", - "jira", - "json", - "latex", - "man", - "markdown", - "markdown_mmd", - "markdown_phpextra", - "markdown_strict", - "markua", - "mediawiki", - "ms", - "muse", - "pandoc native", - "odt", - "opendocument", - "opml", - "org", - "pdf", - "plain", - "pptx", - "revealjs", - "rst", - "rtf", - "s5", - "slideous", - "slidy", - ], - }, -}; - -export function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, -): Promise { - // set xelatex here - const xelatex = ["pdf", "latex"]; - - // Build arguments array - const args: string[] = []; - - if (xelatex.includes(convertTo)) { - args.push("--pdf-engine=xelatex"); - } - - args.push(filePath); - args.push("-f", fileType); - args.push("-t", convertTo); - args.push("-o", targetPath); - - return new Promise((resolve, reject) => { - execFile("pandoc", args, (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve("Done"); - }); - }); -} +import { execFile } from "node:child_process"; + +export const properties = { + from: { + text: [ + "textile", + "tikiwiki", + "tsv", + "twiki", + "typst", + "vimwiki", + "biblatex", + "bibtex", + "bits", + "commonmark", + "commonmark_x", + "creole", + "csljson", + "csv", + "djot", + "docbook", + "docx", + "dokuwiki", + "endnotexml", + "epub", + "fb2", + "gfm", + "haddock", + "html", + "ipynb", + "jats", + "jira", + "json", + "latex", + "man", + "markdown", + "markdown_mmd", + "markdown_phpextra", + "markdown_strict", + "mediawiki", + "muse", + "pandoc native", + "opml", + "org", + "ris", + "rst", + "rtf", + "t2t", + ], + }, + to: { + text: [ + "tei", + "texinfo", + "textile", + "typst", + "xwiki", + "zimwiki", + "asciidoc", + "asciidoc_legacy", + "asciidoctor", + "beamer", + "biblatex", + "bibtex", + "chunkedhtml", + "commonmark", + "commonmark_x", + "context", + "csljson", + "djot", + "docbook", + "docbook4", + "docbook5", + "docx", + "dokuwiki", + "dzslides", + "epub", + "epub2", + "epub3", + "fb2", + "gfm", + "haddock", + "html", + "html4", + "html5", + "icml", + "ipynb", + "jats", + "jats_archiving", + "jats_articleauthoring", + "jats_publishing", + "jira", + "json", + "latex", + "man", + "markdown", + "markdown_mmd", + "markdown_phpextra", + "markdown_strict", + "markua", + "mediawiki", + "ms", + "muse", + "pandoc native", + "odt", + "opendocument", + "opml", + "org", + "pdf", + "plain", + "pptx", + "revealjs", + "rst", + "rtf", + "s5", + "slideous", + "slidy", + ], + }, +}; + +export function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + // 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"); + }); + }); +} diff --git a/src/converters/potrace.ts b/src/converters/potrace.ts index 0c145a7..cdf09cc 100644 --- a/src/converters/potrace.ts +++ b/src/converters/potrace.ts @@ -5,33 +5,45 @@ export const properties = { images: ["pnm", "pbm", "pgm", "bmp"], }, to: { - images: ["svg", "pdf", "pdfpage", "eps", "postscript", "ps", "dxf", "geojson", "pgm", "gimppath", "xfig"], + images: [ + "svg", + "pdf", + "pdfpage", + "eps", + "postscript", + "ps", + "dxf", + "geojson", + "pgm", + "gimppath", + "xfig", + ], }, }; export function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, - ): Promise { - return new Promise((resolve, reject) => { - execFile("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve("Done"); - }); + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + 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"); }); - } \ No newline at end of file + }); +} diff --git a/src/converters/resvg.ts b/src/converters/resvg.ts index 9ea3745..1e409cb 100644 --- a/src/converters/resvg.ts +++ b/src/converters/resvg.ts @@ -1,37 +1,37 @@ -import { execFile } from "node:child_process"; - -export const properties = { - from: { - images: ["svg"], - }, - to: { - images: ["png"], - }, -}; - -export function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, -): Promise { - return new Promise((resolve, reject) => { - execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve("Done"); - }); - }); -} +import { execFile } from "node:child_process"; + +export const properties = { + from: { + images: ["svg"], + }, + to: { + images: ["png"], + }, +}; + +export function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + 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"); + }); + }); +} diff --git a/src/converters/vips.ts b/src/converters/vips.ts index 6781359..6451cda 100644 --- a/src/converters/vips.ts +++ b/src/converters/vips.ts @@ -1,142 +1,138 @@ -import { execFile } from "node:child_process"; - -// declare possible conversions -export const properties = { - from: { - images: [ - "avif", - "bif", - "csv", - "exr", - "fits", - "gif", - "hdr.gz", - "hdr", - "heic", - "heif", - "img.gz", - "img", - "j2c", - "j2k", - "jp2", - "jpeg", - "jpx", - "jxl", - "mat", - "mrxs", - "ndpi", - "nia.gz", - "nia", - "nii.gz", - "nii", - "pdf", - "pfm", - "pgm", - "pic", - "png", - "ppm", - "raw", - "scn", - "svg", - "svs", - "svslide", - "szi", - "tif", - "tiff", - "v", - "vips", - "vms", - "vmu", - "webp", - "zip", - ], - }, - to: { - images: [ - "avif", - "dzi", - "fits", - "gif", - "hdr.gz", - "heic", - "heif", - "img.gz", - "j2c", - "j2k", - "jp2", - "jpeg", - "jpx", - "jxl", - "mat", - "nia.gz", - "nia", - "nii.gz", - "nii", - "png", - "tiff", - "vips", - "webp", - ], - }, - options: { - svg: { - scale: { - description: "Scale the image up or down", - type: "number", - default: 1, - }, - }, - }, -}; - -export function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, -): Promise { - // if (fileType === "svg") { - // const scale = options.scale || 1; - // const metadata = await sharp(filePath).metadata(); - - // if (!metadata || !metadata.width || !metadata.height) { - // throw new Error("Could not get metadata from image"); - // } - - // const newWidth = Math.round(metadata.width * scale); - // const newHeight = Math.round(metadata.height * scale); - - // return await sharp(filePath) - // .resize(newWidth, newHeight) - // .toFormat(convertTo) - // .toFile(targetPath); - // } - let action = "copy"; - if (fileType === "pdf") { - action = "pdfload"; - } - - return new Promise((resolve, reject) => { - execFile( - "vips", - [action, filePath, targetPath], - (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve("Done"); - }, - ); - }); -} +import { execFile } from "node:child_process"; + +// declare possible conversions +export const properties = { + from: { + images: [ + "avif", + "bif", + "csv", + "exr", + "fits", + "gif", + "hdr.gz", + "hdr", + "heic", + "heif", + "img.gz", + "img", + "j2c", + "j2k", + "jp2", + "jpeg", + "jpx", + "jxl", + "mat", + "mrxs", + "ndpi", + "nia.gz", + "nia", + "nii.gz", + "nii", + "pdf", + "pfm", + "pgm", + "pic", + "png", + "ppm", + "raw", + "scn", + "svg", + "svs", + "svslide", + "szi", + "tif", + "tiff", + "v", + "vips", + "vms", + "vmu", + "webp", + "zip", + ], + }, + to: { + images: [ + "avif", + "dzi", + "fits", + "gif", + "hdr.gz", + "heic", + "heif", + "img.gz", + "j2c", + "j2k", + "jp2", + "jpeg", + "jpx", + "jxl", + "mat", + "nia.gz", + "nia", + "nii.gz", + "nii", + "png", + "tiff", + "vips", + "webp", + ], + }, + options: { + svg: { + scale: { + description: "Scale the image up or down", + type: "number", + default: 1, + }, + }, + }, +}; + +export function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + // 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"); + }); + }); +} diff --git a/src/converters/xelatex.ts b/src/converters/xelatex.ts index 905202c..5b29356 100644 --- a/src/converters/xelatex.ts +++ b/src/converters/xelatex.ts @@ -1,53 +1,44 @@ -import { execFile } from "node:child_process"; - -export const properties = { - from: { - text: ["tex", "latex"], - }, - to: { - text: ["pdf"], - }, -}; - -export function convert( - filePath: string, - fileType: string, - convertTo: string, - targetPath: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - options?: unknown, -): Promise { - return new Promise((resolve, reject) => { - // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "") - const outputPath = targetPath - .split("/") - .slice(0, -1) - .join("/") - .replace("./", ""); - - execFile( - "latexmk", - [ - "-xelatex", - "-interaction=nonstopmode", - `-output-directory=${outputPath}`, - filePath, - ], - (error, stdout, stderr) => { - if (error) { - reject(`error: ${error}`); - } - - if (stdout) { - console.log(`stdout: ${stdout}`); - } - - if (stderr) { - console.error(`stderr: ${stderr}`); - } - - resolve("Done"); - }, - ); - }); -} +import { execFile } from "node:child_process"; + +export const properties = { + from: { + text: ["tex", "latex"], + }, + to: { + text: ["pdf"], + }, +}; + +export function convert( + filePath: string, + fileType: string, + convertTo: string, + targetPath: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options?: unknown, +): Promise { + 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"); + }, + ); + }); +} diff --git a/src/db/db.ts b/src/db/db.ts index 641a3f7..1193d5d 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -1,4 +1,5 @@ import { Database } from "bun:sqlite"; + const db = new Database("./data/mydb.sqlite", { create: true }); if (!db.query("SELECT * FROM sqlite_master WHERE type='table'").get()) { @@ -27,13 +28,9 @@ CREATE TABLE IF NOT EXISTS jobs ( PRAGMA user_version = 1;`); } -const dbVersion = ( - db.query("PRAGMA user_version").get() as { user_version?: number } -).user_version; +const dbVersion = (db.query("PRAGMA user_version").get() as { user_version?: number }).user_version; if (dbVersion === 0) { - db.exec( - "ALTER TABLE file_names ADD COLUMN status TEXT DEFAULT 'not started';", - ); + db.exec("ALTER TABLE file_names ADD COLUMN status TEXT DEFAULT 'not started';"); db.exec("PRAGMA user_version = 1;"); console.log("Updated database to version 1."); } @@ -41,4 +38,4 @@ if (dbVersion === 0) { // enable WAL mode db.exec("PRAGMA journal_mode = WAL;"); -export default db; \ No newline at end of file +export default db; diff --git a/src/db/types.ts b/src/db/types.ts index 146a1b1..4825711 100644 --- a/src/db/types.ts +++ b/src/db/types.ts @@ -20,4 +20,4 @@ export class User { id!: number; email!: string; password!: string; -} \ No newline at end of file +} diff --git a/src/helpers/env.ts b/src/helpers/env.ts index 68b1c34..d00843f 100644 --- a/src/helpers/env.ts +++ b/src/helpers/env.ts @@ -1,8 +1,7 @@ export const ACCOUNT_REGISTRATION = process.env.ACCOUNT_REGISTRATION?.toLowerCase() === "true" || false; -export const HTTP_ALLOWED = - process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false; +export const HTTP_ALLOWED = process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false; export const ALLOW_UNAUTHENTICATED = process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false; @@ -11,7 +10,6 @@ export const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS ? Number(process.env.AUTO_DELETE_EVERY_N_HOURS) : 24; -export const HIDE_HISTORY = - process.env.HIDE_HISTORY?.toLowerCase() === "true" || false; +export const HIDE_HISTORY = process.env.HIDE_HISTORY?.toLowerCase() === "true" || false; export const WEBROOT = process.env.WEBROOT ?? ""; diff --git a/src/helpers/normalizeFiletype.ts b/src/helpers/normalizeFiletype.ts index f6aba73..cf9389a 100644 --- a/src/helpers/normalizeFiletype.ts +++ b/src/helpers/normalizeFiletype.ts @@ -1,37 +1,37 @@ -export const normalizeFiletype = (filetype: string): string => { - const lowercaseFiletype = filetype.toLowerCase(); - - switch (lowercaseFiletype) { - case "jfif": - case "jpg": - return "jpeg"; - case "htm": - return "html"; - case "tex": - return "latex"; - case "md": - return "markdown"; - case "unknown": - return "m4a"; - default: - return lowercaseFiletype; - } -}; - -export const normalizeOutputFiletype = (filetype: string): string => { - const lowercaseFiletype = filetype.toLowerCase(); - - switch (lowercaseFiletype) { - case "jpeg": - return "jpg"; - case "latex": - return "tex"; - case "markdown_phpextra": - case "markdown_strict": - case "markdown_mmd": - case "markdown": - return "md"; - default: - return lowercaseFiletype; - } -}; +export const normalizeFiletype = (filetype: string): string => { + const lowercaseFiletype = filetype.toLowerCase(); + + switch (lowercaseFiletype) { + case "jfif": + case "jpg": + return "jpeg"; + case "htm": + return "html"; + case "tex": + return "latex"; + case "md": + return "markdown"; + case "unknown": + return "m4a"; + default: + return lowercaseFiletype; + } +}; + +export const normalizeOutputFiletype = (filetype: string): string => { + const lowercaseFiletype = filetype.toLowerCase(); + + switch (lowercaseFiletype) { + case "jpeg": + return "jpg"; + case "latex": + return "tex"; + case "markdown_phpextra": + case "markdown_strict": + case "markdown_mmd": + case "markdown": + return "md"; + default: + return lowercaseFiletype; + } +}; diff --git a/src/index.tsx b/src/index.tsx index a801b84..b95b4b8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,22 +2,21 @@ import { rmSync } from "node:fs"; import { mkdir } from "node:fs/promises"; import { html } from "@elysiajs/html"; import { staticPlugin } from "@elysiajs/static"; - import { Elysia } from "elysia"; import "./helpers/printVersions"; -import { AUTO_DELETE_EVERY_N_HOURS, WEBROOT } from "./helpers/env"; -import { user } from "./pages/user"; -import { root } from "./pages/root" -import { upload } from "./pages/upload" -import { history } from "./pages/history"; -import { convert } from "./pages/convert" -import { download } from "./pages/download" -import { results } from "./pages/results"; -import { deleteFile } from "./pages/deleteFile"; -import { listConverters } from "./pages/listConverters"; -import { chooseConverter } from "./pages/chooseConverter"; import db from "./db/db"; import { Jobs } from "./db/types"; +import { AUTO_DELETE_EVERY_N_HOURS, WEBROOT } from "./helpers/env"; +import { chooseConverter } from "./pages/chooseConverter"; +import { convert } from "./pages/convert"; +import { deleteFile } from "./pages/deleteFile"; +import { download } from "./pages/download"; +import { history } from "./pages/history"; +import { listConverters } from "./pages/listConverters"; +import { results } from "./pages/results"; +import { root } from "./pages/root"; +import { upload } from "./pages/upload"; +import { user } from "./pages/user"; mkdir("./data", { recursive: true }).catch(console.error); @@ -64,19 +63,13 @@ if (process.env.NODE_ENV !== "production") { app.listen(3000); -console.log( - `🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`, -); +console.log(`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`); const clearJobs = () => { const jobs = db .query("SELECT * FROM jobs WHERE date_created < ?") .as(Jobs) - .all( - new Date( - Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000, - ).toISOString(), - ); + .all(new Date(Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000).toISOString()); for (const job of jobs) { // delete the directories diff --git a/src/pages/chooseConverter.tsx b/src/pages/chooseConverter.tsx index bbe067c..9557fb9 100644 --- a/src/pages/chooseConverter.tsx +++ b/src/pages/chooseConverter.tsx @@ -1,75 +1,67 @@ -import Elysia, { t } from "elysia"; -import { userService } from "./user"; import { Html } from "@elysiajs/html"; -import { - getPossibleTargets, -} from "../converters/main"; +import Elysia, { t } from "elysia"; +import { getPossibleTargets } from "../converters/main"; +import { userService } from "./user"; -export const chooseConverter = new Elysia() - .use(userService) - .post( - "/conversions", - ({ body }) => { - return ( - <> - +export const chooseConverter = new Elysia().use(userService).post( + "/conversions", + ({ body }) => { + return ( + <> + - - - ); - }, - { body: t.Object({ fileType: t.String() }) }, - ) \ No newline at end of file + + + ); + }, + { body: t.Object({ fileType: t.String() }) }, +); diff --git a/src/pages/convert.tsx b/src/pages/convert.tsx index 9f8835e..a95302c 100644 --- a/src/pages/convert.tsx +++ b/src/pages/convert.tsx @@ -3,14 +3,11 @@ import { Elysia, t } from "elysia"; import sanitize from "sanitize-filename"; import { outputDir, uploadsDir } from ".."; import { mainConverter } from "../converters/main"; -import { WEBROOT } from "../helpers/env"; import db from "../db/db"; -import { - normalizeFiletype, - normalizeOutputFiletype, -} from "../helpers/normalizeFiletype"; -import { userService } from "./user"; import { Jobs } from "../db/types"; +import { WEBROOT } from "../helpers/env"; +import { normalizeFiletype, normalizeOutputFiletype } from "../helpers/normalizeFiletype"; +import { userService } from "./user"; export const convert = new Elysia().use(userService).post( "/convert", @@ -44,10 +41,7 @@ export const convert = new Elysia().use(userService).post( try { await mkdir(userOutputDir, { recursive: true }); } catch (error) { - console.error( - `Failed to create the output directory: ${userOutputDir}.`, - error, - ); + console.error(`Failed to create the output directory: ${userOutputDir}.`, error); } const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? ""); @@ -62,9 +56,10 @@ export const convert = new Elysia().use(userService).post( return redirect(`${WEBROOT}/`, 302); } - db.query( - "UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2", - ).run(fileNames.length, jobId.value); + db.query("UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2").run( + fileNames.length, + jobId.value, + ); const query = db.query( "INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)", @@ -99,9 +94,7 @@ export const convert = new Elysia().use(userService).post( .then(() => { // All conversions are done, update the job status to 'completed' if (jobId.value) { - db.query("UPDATE jobs SET status = 'completed' WHERE id = ?1").run( - jobId.value, - ); + db.query("UPDATE jobs SET status = 'completed' WHERE id = ?1").run(jobId.value); } // delete all uploaded files in userUploadsDir diff --git a/src/pages/deleteFile.tsx b/src/pages/deleteFile.tsx index ef8b719..665d802 100644 --- a/src/pages/deleteFile.tsx +++ b/src/pages/deleteFile.tsx @@ -1,39 +1,41 @@ -import { Elysia, t } from "elysia"; -import { userService } from "./user"; import { unlink } from "node:fs/promises"; -import { WEBROOT } from "../helpers/env"; +import { Elysia, t } from "elysia"; import { uploadsDir } from ".."; import db from "../db/db"; +import { WEBROOT } from "../helpers/env"; +import { userService } from "./user"; -export const deleteFile = new Elysia() - .use(userService) - .post( - "/delete", - async ({ body, redirect, jwt, cookie: { auth, jobId } }) => { - if (!auth?.value) { - return redirect(`${WEBROOT}/login`, 302); - } +export const deleteFile = new Elysia().use(userService).post( + "/delete", + async ({ body, redirect, jwt, cookie: { auth, jobId } }) => { + if (!auth?.value) { + return redirect(`${WEBROOT}/login`, 302); + } - const user = await jwt.verify(auth.value); - if (!user) { - return redirect(`${WEBROOT}/login`, 302); - } + const user = await jwt.verify(auth.value); + if (!user) { + return redirect(`${WEBROOT}/login`, 302); + } - if (!jobId?.value) { - return redirect(`${WEBROOT}/`, 302); - } + if (!jobId?.value) { + return redirect(`${WEBROOT}/`, 302); + } - const existingJob = await db - .query("SELECT * FROM jobs WHERE id = ? AND user_id = ?") - .get(jobId.value, user.id); + const existingJob = await db + .query("SELECT * FROM jobs WHERE id = ? AND user_id = ?") + .get(jobId.value, user.id); - if (!existingJob) { - return redirect(`${WEBROOT}/`, 302); - } + if (!existingJob) { + return redirect(`${WEBROOT}/`, 302); + } - const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; + const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`; - await unlink(`${userUploadsDir}${body.filename}`); - }, - { body: t.Object({ filename: t.String() }) }, - ) \ No newline at end of file + await unlink(`${userUploadsDir}${body.filename}`); + + return { + message: "File deleted successfully.", + }; + }, + { body: t.Object({ filename: t.String() }) }, +); diff --git a/src/pages/download.tsx b/src/pages/download.tsx index f5517e8..917ee39 100644 --- a/src/pages/download.tsx +++ b/src/pages/download.tsx @@ -1,10 +1,9 @@ - import { Elysia } from "elysia"; -import { userService } from "./user"; -import { WEBROOT } from "../helpers/env"; import sanitize from "sanitize-filename"; import { outputDir } from ".."; import db from "../db/db"; +import { WEBROOT } from "../helpers/env"; +import { userService } from "./user"; export const download = new Elysia() .use(userService) @@ -36,31 +35,28 @@ export const download = new Elysia() return Bun.file(filePath); }, ) - .get( - "/zip/:userId/:jobId", - async ({ params, jwt, redirect, cookie: { auth } }) => { - // TODO: Implement zip download - if (!auth?.value) { - return redirect(`${WEBROOT}/login`, 302); - } + .get("/zip/:userId/:jobId", async ({ params, jwt, redirect, cookie: { auth } }) => { + // TODO: Implement zip download + if (!auth?.value) { + return redirect(`${WEBROOT}/login`, 302); + } - const user = await jwt.verify(auth.value); - if (!user) { - return redirect(`${WEBROOT}/login`, 302); - } + const user = await jwt.verify(auth.value); + if (!user) { + return redirect(`${WEBROOT}/login`, 302); + } - const job = await db - .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?") - .get(user.id, params.jobId); + const job = await db + .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?") + .get(user.id, params.jobId); - if (!job) { - return redirect(`${WEBROOT}/results`, 302); - } + if (!job) { + return redirect(`${WEBROOT}/results`, 302); + } - // const userId = decodeURIComponent(params.userId); - // const jobId = decodeURIComponent(params.jobId); - // const outputPath = `${outputDir}${userId}/`{jobId}/); + // const userId = decodeURIComponent(params.userId); + // const jobId = decodeURIComponent(params.jobId); + // const outputPath = `${outputDir}${userId}/`{jobId}/); - // return Bun.zip(outputPath); - }, - ) \ No newline at end of file + // return Bun.zip(outputPath); + }); diff --git a/src/pages/history.tsx b/src/pages/history.tsx index 648efcb..52e27bf 100644 --- a/src/pages/history.tsx +++ b/src/pages/history.tsx @@ -1,11 +1,11 @@ +import { Html } from "@elysiajs/html"; import { Elysia } from "elysia"; import { BaseHtml } from "../components/base"; -import { Html } from "@elysiajs/html"; import { Header } from "../components/header"; -import { userService } from "./user"; -import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, WEBROOT } from "../helpers/env"; -import { Filename, Jobs } from "../db/types"; import db from "../db/db"; +import { Filename, Jobs } from "../db/types"; +import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, WEBROOT } from "../helpers/env"; +import { userService } from "./user"; export const history = new Elysia() .use(userService) @@ -23,17 +23,10 @@ export const history = new Elysia() return redirect(`${WEBROOT}/login`, 302); } - let userJobs = db - .query("SELECT * FROM jobs WHERE user_id = ?") - .as(Jobs) - .all(user.id) - .reverse(); + let userJobs = db.query("SELECT * FROM jobs WHERE user_id = ?").as(Jobs).all(user.id).reverse(); for (const job of userJobs) { - const files = db - .query("SELECT * FROM file_names WHERE job_id = ?") - .as(Filename) - .all(job.id); + const files = db.query("SELECT * FROM file_names WHERE job_id = ?").as(Filename).all(job.id); job.finished_files = files.length; job.files_detailed = files; @@ -123,10 +116,7 @@ export const history = new Elysia() {userJobs.map((job) => ( <> - + - - {new Date(job.date_created).toLocaleTimeString()} - + {new Date(job.date_created).toLocaleTimeString()} {job.num_files} {job.finished_files} {job.status} @@ -164,43 +152,29 @@ export const history = new Elysia()
-
- Detailed File Information: -
- {job.files_detailed.map( - (file: Filename) => ( -
Detailed File Information:
+ {job.files_detailed.map((file: Filename) => ( +
+ + {file.file_name} + + - - {file.file_name} - - - - - - {file.output_file_name} - -
- ), - )} + + + + {file.output_file_name} + +
+ ))} @@ -239,4 +213,4 @@ export const history = new Elysia() ); - }) \ No newline at end of file + }); diff --git a/src/pages/listConverters.tsx b/src/pages/listConverters.tsx index adf9f57..a2b99be 100644 --- a/src/pages/listConverters.tsx +++ b/src/pages/listConverters.tsx @@ -1,13 +1,10 @@ -import Elysia from "elysia"; -import { userService } from "./user"; import { Html } from "@elysiajs/html"; -import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env"; +import Elysia from "elysia"; import { BaseHtml } from "../components/base"; import { Header } from "../components/header"; -import { - getAllInputs, - getAllTargets, -} from "../converters/main"; +import { getAllInputs, getAllTargets } from "../converters/main"; +import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env"; +import { userService } from "./user"; export const listConverters = new Elysia() .use(userService) @@ -24,11 +21,7 @@ export const listConverters = new Elysia() return ( <> -
+
- {Object.entries(getAllTargets()).map( - ([converter, targets]) => { - const inputs = getAllInputs(converter); - return ( - - {converter} - - Count: {inputs.length} -
    - {inputs.map((input) => ( -
  • {input}
  • - ))} -
- - - Count: {targets.length} -
    - {targets.map((target) => ( -
  • {target}
  • - ))} -
- - - ); - }, - )} + {Object.entries(getAllTargets()).map(([converter, targets]) => { + const inputs = getAllInputs(converter); + return ( + + {converter} + + Count: {inputs.length} +
    + {inputs.map((input) => ( +
  • {input}
  • + ))} +
+ + + Count: {targets.length} +
    + {targets.map((target) => ( +
  • {target}
  • + ))} +
+ + + ); + })} @@ -86,4 +77,4 @@ export const listConverters = new Elysia() ); - }) \ No newline at end of file + }); diff --git a/src/pages/results.tsx b/src/pages/results.tsx index a1570db..81e6128 100644 --- a/src/pages/results.tsx +++ b/src/pages/results.tsx @@ -1,214 +1,215 @@ -import { Elysia } from "elysia"; -import { userService } from "./user"; import { Html } from "@elysiajs/html"; -import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env"; -import { Filename, Jobs } from "../db/types"; -import { Header } from "../components/header"; +import { Elysia } from "elysia"; import { BaseHtml } from "../components/base"; +import { Header } from "../components/header"; import db from "../db/db"; +import { Filename, Jobs } from "../db/types"; +import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env"; +import { userService } from "./user"; -function ResultsArticle({ job, files, outputPath }: { job: Jobs, files: Filename[], outputPath: string }) { - return (
-
-

Results

-
- +function ResultsArticle({ + job, + files, + outputPath, +}: { + job: Jobs; + files: Filename[]; + outputPath: string; +}) { + return ( +
+
+

Results

+
+ +
-
- - - - - - - - - - - - {files.map((file) => ( + +
- Converted File Name - - Status - - View - - Download -
+ - - - - + + + + - ))} - -
- {file.output_file_name} - {file.status} - - View - - - - Download - - + Converted File Name + + Status + + View + + Download +
-
- ) + + + {files.map((file) => ( + + + {file.output_file_name} + + {file.status} + + + View + + + + + Download + + + + ))} + + + + ); } export const results = new Elysia() .use(userService) - .get( - "/results/:jobId", - async ({ params, jwt, set, redirect, cookie: { auth, job_id } }) => { - if (!auth?.value) { - return redirect(`${WEBROOT}/login`, 302); - } + .get("/results/:jobId", async ({ params, jwt, set, redirect, cookie: { auth, job_id } }) => { + if (!auth?.value) { + return redirect(`${WEBROOT}/login`, 302); + } - if (job_id?.value) { - // clear the job_id cookie since we are viewing the results - job_id.remove(); - } + if (job_id?.value) { + // clear the job_id cookie since we are viewing the results + job_id.remove(); + } - const user = await jwt.verify(auth.value); - if (!user) { - return redirect(`${WEBROOT}/login`, 302); - } + const user = await jwt.verify(auth.value); + if (!user) { + return redirect(`${WEBROOT}/login`, 302); + } - const job = db - .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?") - .as(Jobs) - .get(user.id, params.jobId); + const job = db + .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?") + .as(Jobs) + .get(user.id, params.jobId); - if (!job) { - set.status = 404; - return { - message: "Job not found.", - }; - } + if (!job) { + set.status = 404; + return { + message: "Job not found.", + }; + } - const outputPath = `${user.id}/${params.jobId}/`; + const outputPath = `${user.id}/${params.jobId}/`; - const files = db - .query("SELECT * FROM file_names WHERE job_id = ?") - .as(Filename) - .all(params.jobId); + const files = db + .query("SELECT * FROM file_names WHERE job_id = ?") + .as(Filename) + .all(params.jobId); - return ( - - <> -
-
- -
-