mirror of
https://github.com/C4illin/ConvertX.git
synced 2025-10-23 16:14:08 +00:00
chore: fix lint
This commit is contained in:
@@ -1,21 +1,13 @@
|
|||||||
# Development Dockerfile for ConvertX
|
|
||||||
FROM debian:trixie-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX"
|
|
||||||
LABEL description="Development environment for ConvertX with all tools and dependencies"
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install system dependencies and development tools
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
# Basic tools
|
|
||||||
curl \
|
curl \
|
||||||
unzip \
|
unzip \
|
||||||
git \
|
git \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
# Build tools
|
|
||||||
build-essential \
|
build-essential \
|
||||||
# ConvertX runtime dependencies
|
|
||||||
assimp-utils \
|
assimp-utils \
|
||||||
calibre \
|
calibre \
|
||||||
dasel \
|
dasel \
|
||||||
@@ -48,7 +40,6 @@ RUN apt-get update && apt-get install -y \
|
|||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install Bun (JavaScript runtime and package manager)
|
|
||||||
RUN ARCH=$(uname -m) && \
|
RUN ARCH=$(uname -m) && \
|
||||||
if [ "$ARCH" = "aarch64" ]; then \
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
curl -fsSL -o bun-linux-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.2/bun-linux-aarch64.zip; \
|
curl -fsSL -o bun-linux-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.2/bun-linux-aarch64.zip; \
|
||||||
@@ -59,7 +50,6 @@ RUN ARCH=$(uname -m) && \
|
|||||||
rm bun-linux-*.zip && \
|
rm bun-linux-*.zip && \
|
||||||
chmod +x /usr/local/bin/bun
|
chmod +x /usr/local/bin/bun
|
||||||
|
|
||||||
# Install VTracer binary for vector tracing
|
|
||||||
RUN ARCH=$(uname -m) && \
|
RUN ARCH=$(uname -m) && \
|
||||||
if [ "$ARCH" = "aarch64" ]; then \
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \
|
VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \
|
||||||
@@ -72,15 +62,8 @@ RUN ARCH=$(uname -m) && \
|
|||||||
chmod +x /usr/local/bin/vtracer && \
|
chmod +x /usr/local/bin/vtracer && \
|
||||||
rm /tmp/vtracer.tar.gz
|
rm /tmp/vtracer.tar.gz
|
||||||
|
|
||||||
# Create data directory for development
|
|
||||||
RUN mkdir -p data
|
RUN mkdir -p data
|
||||||
|
|
||||||
# Set environment variables for development
|
|
||||||
ENV NODE_ENV=development
|
ENV NODE_ENV=development
|
||||||
ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox"
|
ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox"
|
||||||
|
|
||||||
# Expose the development port
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Default command for development
|
|
||||||
CMD ["bun", "run", "dev"]
|
CMD ["bun", "run", "dev"]
|
@@ -1,57 +1,49 @@
|
|||||||
{
|
{
|
||||||
"name": "ConvertX Development Environment",
|
"name": "ConvertX Development Environment",
|
||||||
"build": {
|
"build": {
|
||||||
"dockerfile": "Dockerfile",
|
"dockerfile": "Dockerfile"
|
||||||
"context": ".."
|
},
|
||||||
},
|
"features": {
|
||||||
"features": {
|
"ghcr.io/devcontainers/features/git:1": {},
|
||||||
"ghcr.io/devcontainers/features/git:1": {},
|
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||||
"ghcr.io/devcontainers/features/github-cli:1": {}
|
},
|
||||||
},
|
"customizations": {
|
||||||
"customizations": {
|
"vscode": {
|
||||||
"vscode": {
|
"extensions": [
|
||||||
"extensions": [
|
"ms-vscode.vscode-typescript-next",
|
||||||
"ms-vscode.vscode-typescript-next",
|
"bradlc.vscode-tailwindcss",
|
||||||
"bradlc.vscode-tailwindcss",
|
"esbenp.prettier-vscode",
|
||||||
"esbenp.prettier-vscode",
|
"dbaeumer.vscode-eslint",
|
||||||
"dbaeumer.vscode-eslint",
|
"ms-vscode.vscode-json",
|
||||||
"ms-vscode.vscode-json",
|
"ms-vscode.vscode-docker"
|
||||||
"ms-vscode.vscode-docker"
|
],
|
||||||
],
|
"settings": {
|
||||||
"settings": {
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.formatOnSave": true,
|
||||||
"editor.formatOnSave": true,
|
"editor.codeActionsOnSave": {
|
||||||
"editor.codeActionsOnSave": {
|
"source.fixAll.eslint": "explicit"
|
||||||
"source.fixAll.eslint": "explicit"
|
},
|
||||||
},
|
"typescript.preferences.importModuleSpecifier": "relative",
|
||||||
"typescript.preferences.importModuleSpecifier": "relative",
|
"typescript.suggest.autoImports": true,
|
||||||
"typescript.suggest.autoImports": true,
|
"tailwindCSS.includeLanguages": {
|
||||||
"tailwindCSS.includeLanguages": {
|
"typescript": "javascript",
|
||||||
"typescript": "javascript",
|
"typescriptreact": "javascript"
|
||||||
"typescriptreact": "javascript"
|
},
|
||||||
},
|
"files.associations": {
|
||||||
"files.associations": {
|
"*.css": "tailwindcss"
|
||||||
"*.css": "tailwindcss"
|
},
|
||||||
},
|
"terminal.integrated.defaultProfile.linux": "bash"
|
||||||
"terminal.integrated.defaultProfile.linux": "bash"
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
"forwardPorts": [3000],
|
||||||
"forwardPorts": [3000],
|
"portsAttributes": {
|
||||||
"portsAttributes": {
|
"3000": {
|
||||||
"3000": {
|
"label": "ConvertX Application",
|
||||||
"label": "ConvertX Application",
|
"onAutoForward": "notify"
|
||||||
"onAutoForward": "notify"
|
}
|
||||||
}
|
},
|
||||||
},
|
"postCreateCommand": "bun install",
|
||||||
// "postCreateCommand": "bun install && bun run dev",
|
"remoteUser": "root",
|
||||||
"remoteUser": "root",
|
"mounts": ["source=${localWorkspaceFolder}/data,target=/app/data,type=bind"]
|
||||||
"mounts": [
|
}
|
||||||
"source=${localWorkspaceFolder}/data,target=/app/data,type=bind",
|
|
||||||
"source=${localWorkspaceFolder}/src,target=/app/src,type=bind"
|
|
||||||
],
|
|
||||||
"containerEnv": {
|
|
||||||
"NODE_ENV": "development",
|
|
||||||
"QTWEBENGINE_CHROMIUM_FLAGS": "--no-sandbox"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
30
.github/FUNDING.yml
vendored
30
.github/FUNDING.yml
vendored
@@ -1,15 +1,15 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [C4illin] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: [C4illin] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
patreon: # Replace with a single Patreon username
|
patreon: # Replace with a single Patreon username
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
liberapay: # Replace with a single Liberapay username
|
liberapay: # Replace with a single Liberapay username
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
polar: # Replace with a single Polar username
|
polar: # Replace with a single Polar username
|
||||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||||
thanks_dev: # Replace with a single thanks.dev username
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
|
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,22 +1,22 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ""
|
title: ""
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: ""
|
assignees: ""
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
**Checklist:**
|
**Checklist:**
|
||||||
|
|
||||||
- [ ] I am accessing ConvertX over HTTPS or have `HTTP_ALLOWED=true`
|
- [ ] I am accessing ConvertX over HTTPS or have `HTTP_ALLOWED=true`
|
||||||
|
52
.github/ISSUE_TEMPLATE/converter_request.md
vendored
52
.github/ISSUE_TEMPLATE/converter_request.md
vendored
@@ -1,26 +1,26 @@
|
|||||||
---
|
---
|
||||||
name: Converter request
|
name: Converter request
|
||||||
about: Suggest a converter for this project
|
about: Suggest a converter for this project
|
||||||
title: "[Converter Request]"
|
title: "[Converter Request]"
|
||||||
labels: "converter request"
|
labels: "converter request"
|
||||||
assignees: ""
|
assignees: ""
|
||||||
---
|
---
|
||||||
|
|
||||||
**What file formats are missing?**
|
**What file formats are missing?**
|
||||||
|
|
||||||
<!-- Provide an example of what you would like to convert -->
|
<!-- Provide an example of what you would like to convert -->
|
||||||
|
|
||||||
**What converter should be added**
|
**What converter should be added**
|
||||||
|
|
||||||
<!-- It has to be free and preferably open source -->
|
<!-- It has to be free and preferably open source -->
|
||||||
|
|
||||||
**Are you willing to add it?**
|
**Are you willing to add it?**
|
||||||
|
|
||||||
<!-- Adding a converter is very easy just copy one of the existing and modify it -->
|
<!-- Adding a converter is very easy just copy one of the existing and modify it -->
|
||||||
|
|
||||||
- [ ] Yes
|
- [ ] Yes
|
||||||
- [ ] No
|
- [ ] No
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
|
||||||
<!-- Add any other context or screenshots about the feature request here. -->
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
|
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: "[Feature Request]"
|
title: "[Feature Request]"
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: ""
|
assignees: ""
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
**Describe the solution you'd like**
|
||||||
A clear and concise description of what you want to happen.
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context or screenshots about the feature request here.
|
Add any other context or screenshots about the feature request here.
|
||||||
|
62
.github/workflows/check-lint.yml
vendored
62
.github/workflows/check-lint.yml
vendored
@@ -1,31 +1,31 @@
|
|||||||
name: Check Lint
|
name: Check Lint
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
name: Run linting checks
|
name: Run linting checks
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Bun
|
- name: Set up Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.2
|
bun-version: 1.2.2
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run lint
|
- name: Run lint
|
||||||
run: bun run lint
|
run: bun run lint
|
||||||
|
346
.github/workflows/docker-publish.yml
vendored
346
.github/workflows/docker-publish.yml
vendored
@@ -1,173 +1,173 @@
|
|||||||
name: Docker
|
name: Docker
|
||||||
|
|
||||||
# thanks to https://github.com/sredevopsorg/multi-arch-docker-github-workflow
|
# thanks to https://github.com/sredevopsorg/multi-arch-docker-github-workflow
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
tags: ["v*.*.*"]
|
tags: ["v*.*.*"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
env:
|
env:
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
DOCKERHUB_USERNAME: c4illin
|
DOCKERHUB_USERNAME: c4illin
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# The build job builds the Docker image for each platform specified in the matrix.
|
# The build job builds the Docker image for each platform specified in the matrix.
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- linux/amd64
|
- linux/amd64
|
||||||
- linux/arm64
|
- linux/arm64
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
attestations: write
|
attestations: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-24.04' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
|
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-24.04' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }}
|
||||||
|
|
||||||
name: Build Docker image for ${{ matrix.platform }}
|
name: Build Docker image for ${{ matrix.platform }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare environment for current platform
|
- name: Prepare environment for current platform
|
||||||
# This step sets up the environment for the current platform being built.
|
# 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.
|
# 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 '/'.
|
# This is useful for naming artifacts and other resources that cannot contain '/'.
|
||||||
# The environment variable PLATFORMS_PAIR will be used later in the workflow.
|
# The environment variable PLATFORMS_PAIR will be used later in the workflow.
|
||||||
id: prepare
|
id: prepare
|
||||||
run: |
|
run: |
|
||||||
platform=${{ matrix.platform }}
|
platform=${{ matrix.platform }}
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: downcase REPO
|
- name: downcase REPO
|
||||||
run: |
|
run: |
|
||||||
echo "REPO=${GITHUB_REPOSITORY@L}" >> "${GITHUB_ENV}"
|
echo "REPO=${GITHUB_REPOSITORY@L}" >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
- name: Docker meta default
|
- name: Docker meta default
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ env.REPO }}
|
images: ghcr.io/${{ env.REPO }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
# here we only login to ghcr.io since the this only pushes internal images
|
# here we only login to ghcr.io since the this only pushes internal images
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push by digest
|
- name: Build and push by digest
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
env:
|
env:
|
||||||
DOCKER_BUILDKIT: 1
|
DOCKER_BUILDKIT: 1
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
annotations: ${{ steps.meta.outputs.annotations }}
|
||||||
outputs: type=image,name=ghcr.io/${{ env.REPO }},push-by-digest=true,name-canonical=true,oci-mediatypes=true
|
outputs: type=image,name=ghcr.io/${{ env.REPO }},push-by-digest=true,name-canonical=true,oci-mediatypes=true
|
||||||
push: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
push: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
cache-from: type=gha,scope=${{ matrix.platform }}
|
cache-from: type=gha,scope=${{ matrix.platform }}
|
||||||
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
|
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/digests
|
mkdir -p /tmp/digests
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
touch "/tmp/digests/${digest#sha256:}"
|
touch "/tmp/digests/${digest#sha256:}"
|
||||||
|
|
||||||
- name: Upload digest
|
- name: Upload digest
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: digests-${{ env.PLATFORM_PAIR }}
|
name: digests-${{ env.PLATFORM_PAIR }}
|
||||||
path: /tmp/digests/*
|
path: /tmp/digests/*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
merge:
|
merge:
|
||||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||||
name: Merge Docker manifests
|
name: Merge Docker manifests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
attestations: write
|
attestations: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
needs:
|
needs:
|
||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: downcase REPO
|
- name: downcase REPO
|
||||||
run: |
|
run: |
|
||||||
echo "REPO=${GITHUB_REPOSITORY@L}" >> "${GITHUB_ENV}"
|
echo "REPO=${GITHUB_REPOSITORY@L}" >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
ghcr.io/${{ env.REPO }}
|
ghcr.io/${{ env.REPO }}
|
||||||
${{ env.IMAGE_NAME }}
|
${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Get execution timestamp with RFC3339 format
|
- name: Get execution timestamp with RFC3339 format
|
||||||
id: timestamp
|
id: timestamp
|
||||||
run: |
|
run: |
|
||||||
echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT
|
echo "timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create manifest list and push
|
- name: Create manifest list and push
|
||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
$(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.description=${{ github.event.repository.description }}' \
|
||||||
--annotation='index:org.opencontainers.image.created=${{ steps.timestamp.outputs.timestamp }}' \
|
--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.url=${{ github.event.repository.url }}' \
|
||||||
--annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \
|
--annotation='index:org.opencontainers.image.source=${{ github.event.repository.url }}' \
|
||||||
$(printf 'ghcr.io/${{ env.REPO }}@sha256:%s ' *)
|
$(printf 'ghcr.io/${{ env.REPO }}@sha256:%s ' *)
|
||||||
|
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect 'ghcr.io/${{ env.REPO }}:${{ steps.meta.outputs.version }}'
|
docker buildx imagetools inspect 'ghcr.io/${{ env.REPO }}:${{ steps.meta.outputs.version }}'
|
||||||
|
54
.github/workflows/dockerhub-description.yml
vendored
54
.github/workflows/dockerhub-description.yml
vendored
@@ -1,27 +1,27 @@
|
|||||||
name: Update Docker Hub Description
|
name: Update Docker Hub Description
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
DOCKERHUB_USERNAME: c4illin
|
DOCKERHUB_USERNAME: c4illin
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- README.md
|
- README.md
|
||||||
- .github/workflows/dockerhub-description.yml
|
- .github/workflows/dockerhub-description.yml
|
||||||
jobs:
|
jobs:
|
||||||
dockerHubDescription:
|
dockerHubDescription:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Docker Hub Description
|
- name: Docker Hub Description
|
||||||
uses: peter-evans/dockerhub-description@v5
|
uses: peter-evans/dockerhub-description@v5
|
||||||
with:
|
with:
|
||||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
repository: ${{ env.IMAGE_NAME }}
|
repository: ${{ env.IMAGE_NAME }}
|
||||||
short-description: ${{ github.event.repository.description }}
|
short-description: ${{ github.event.repository.description }}
|
||||||
enable-url-completion: true
|
enable-url-completion: true
|
||||||
|
50
.github/workflows/release-please.yml
vendored
50
.github/workflows/release-please.yml
vendored
@@ -1,25 +1,25 @@
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
name: release-please
|
name: release-please
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-please:
|
release-please:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: googleapis/release-please-action@v4
|
- uses: googleapis/release-please-action@v4
|
||||||
with:
|
with:
|
||||||
# this assumes that you have created a personal access token
|
# this assumes that you have created a personal access token
|
||||||
# (PAT) and configured it as a GitHub action secret named
|
# (PAT) and configured it as a GitHub action secret named
|
||||||
# `MY_RELEASE_PLEASE_TOKEN` (this secret name is not important).
|
# `MY_RELEASE_PLEASE_TOKEN` (this secret name is not important).
|
||||||
token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
|
token: ${{ secrets.MY_RELEASE_PLEASE_TOKEN }}
|
||||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# this is a built-in strategy in release-please, see "Action Inputs"
|
# this is a built-in strategy in release-please, see "Action Inputs"
|
||||||
# for more options
|
# for more options
|
||||||
release-type: node
|
release-type: node
|
||||||
|
42
.github/workflows/remove-docker-tag.yml
vendored
42
.github/workflows/remove-docker-tag.yml
vendored
@@ -1,21 +1,21 @@
|
|||||||
name: Remove Docker Tag
|
name: Remove Docker Tag
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
remove-docker-tag:
|
remove-docker-tag:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
|
||||||
# (required)
|
# (required)
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Remove Docker Tag
|
- name: Remove Docker Tag
|
||||||
uses: ArchieAtkinson/remove-dockertag-action@v0.0
|
uses: ArchieAtkinson/remove-dockertag-action@v0.0
|
||||||
with:
|
with:
|
||||||
tag_name: master
|
tag_name: master
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
62
.github/workflows/run-bun-test.yml
vendored
62
.github/workflows/run-bun-test.yml
vendored
@@ -1,31 +1,31 @@
|
|||||||
name: Check Tests
|
name: Check Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Bun
|
- name: Set up Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: 1.2.2
|
bun-version: 1.2.2
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: bun test
|
run: bun test
|
||||||
|
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||||
}
|
}
|
||||||
|
466
CHANGELOG.md
466
CHANGELOG.md
@@ -1,233 +1,233 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.14.1](https://github.com/C4illin/ConvertX/compare/v0.14.0...v0.14.1) (2025-06-04)
|
## [0.14.1](https://github.com/C4illin/ConvertX/compare/v0.14.0...v0.14.1) (2025-06-04)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- change to baseline build ([6ea3058](https://github.com/C4illin/ConvertX/commit/6ea3058e66262f7a14633bddcecd5573948f524a)), closes [#311](https://github.com/C4illin/ConvertX/issues/311)
|
- change to baseline build ([6ea3058](https://github.com/C4illin/ConvertX/commit/6ea3058e66262f7a14633bddcecd5573948f524a)), closes [#311](https://github.com/C4illin/ConvertX/issues/311)
|
||||||
|
|
||||||
## [0.14.0](https://github.com/C4illin/ConvertX/compare/v0.13.0...v0.14.0) (2025-06-03)
|
## [0.14.0](https://github.com/C4illin/ConvertX/compare/v0.13.0...v0.14.0) (2025-06-03)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add dvisvgm ([625e1a5](https://github.com/C4illin/ConvertX/commit/625e1a51f620fe9da79d0127eb6c95f468d9ea2b))
|
- add dvisvgm ([625e1a5](https://github.com/C4illin/ConvertX/commit/625e1a51f620fe9da79d0127eb6c95f468d9ea2b))
|
||||||
- add ImageMagick ([b47e575](https://github.com/C4illin/ConvertX/commit/b47e5755f677056e8acecad54c0c2e28a5e137f3)), closes [#295](https://github.com/C4illin/ConvertX/issues/295), closes [#269](https://github.com/C4illin/ConvertX/issues/269)
|
- add ImageMagick ([b47e575](https://github.com/C4illin/ConvertX/commit/b47e5755f677056e8acecad54c0c2e28a5e137f3)), closes [#295](https://github.com/C4illin/ConvertX/issues/295), closes [#269](https://github.com/C4illin/ConvertX/issues/269)
|
||||||
- enhance job details display with file information ([50725ed](https://github.com/C4illin/ConvertX/commit/50725edd021bb9a7f58c85b79c1eab355ad22ced)), closes [#251](https://github.com/C4illin/ConvertX/issues/251)
|
- enhance job details display with file information ([50725ed](https://github.com/C4illin/ConvertX/commit/50725edd021bb9a7f58c85b79c1eab355ad22ced)), closes [#251](https://github.com/C4illin/ConvertX/issues/251)
|
||||||
- improve job details interaction and accessibility ([29ba229](https://github.com/C4illin/ConvertX/commit/29ba229bc23d2019d2ee9829da7852f884ffa611))
|
- improve job details interaction and accessibility ([29ba229](https://github.com/C4illin/ConvertX/commit/29ba229bc23d2019d2ee9829da7852f884ffa611))
|
||||||
- show version in footer ([9a49ded](https://github.com/C4illin/ConvertX/commit/9a49dedacac7e67a432b6da0daf1967038d97d26))
|
- show version in footer ([9a49ded](https://github.com/C4illin/ConvertX/commit/9a49dedacac7e67a432b6da0daf1967038d97d26))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- add av1 and h26X with containers ([af5c768](https://github.com/C4illin/ConvertX/commit/af5c768dc74b3124fd7ef4b29e27c83a5d19ad49)), closes [#287](https://github.com/C4illin/ConvertX/issues/287), closes [#293](https://github.com/C4illin/ConvertX/issues/293)
|
- add av1 and h26X with containers ([af5c768](https://github.com/C4illin/ConvertX/commit/af5c768dc74b3124fd7ef4b29e27c83a5d19ad49)), closes [#287](https://github.com/C4illin/ConvertX/issues/287), closes [#293](https://github.com/C4illin/ConvertX/issues/293)
|
||||||
- progress bars on firefox ([ff2c005](https://github.com/C4illin/ConvertX/commit/ff2c0057e890b9ecb552df30914333349ea20eb7))
|
- progress bars on firefox ([ff2c005](https://github.com/C4illin/ConvertX/commit/ff2c0057e890b9ecb552df30914333349ea20eb7))
|
||||||
- register button style ([b9bbf77](https://github.com/C4illin/ConvertX/commit/b9bbf7792f01fcaa77e3520925de107e856926f1))
|
- register button style ([b9bbf77](https://github.com/C4illin/ConvertX/commit/b9bbf7792f01fcaa77e3520925de107e856926f1))
|
||||||
- switch from alpine to debian trixie ([4e4c029](https://github.com/C4illin/ConvertX/commit/4e4c029cb800df86affb99c3a82dda9e6708bdde)), closes [#234](https://github.com/C4illin/ConvertX/issues/234), closes [#199](https://github.com/C4illin/ConvertX/issues/199)
|
- switch from alpine to debian trixie ([4e4c029](https://github.com/C4illin/ConvertX/commit/4e4c029cb800df86affb99c3a82dda9e6708bdde)), closes [#234](https://github.com/C4illin/ConvertX/issues/234), closes [#199](https://github.com/C4illin/ConvertX/issues/199)
|
||||||
|
|
||||||
## [0.13.0](https://github.com/C4illin/ConvertX/compare/v0.12.1...v0.13.0) (2025-05-14)
|
## [0.13.0](https://github.com/C4illin/ConvertX/compare/v0.12.1...v0.13.0) (2025-05-14)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add HIDE_HISTORY option to control visibility of history page ([9d1c931](https://github.com/C4illin/ConvertX/commit/9d1c93155cc33ed6c83f9e5122afff8f28d0e4bf))
|
- 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 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 .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 support for drag/drop of images ([ff2ef74](https://github.com/C4illin/ConvertX/commit/ff2ef7413542cf10ba7a6e246763bcecd6829ec1))
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.12.1](https://github.com/C4illin/ConvertX/compare/v0.12.0...v0.12.1) (2025-03-20)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.12.0](https://github.com/C4illin/ConvertX/compare/v0.11.1...v0.12.0) (2025-03-06)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- added progress bar for file upload ([db60f35](https://github.com/C4illin/ConvertX/commit/db60f355b2973f43f8e5990e6fe4e351b959b659))
|
- 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))
|
- 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))
|
- replace exec with execFile ([9263d17](https://github.com/C4illin/ConvertX/commit/9263d17609dc4b2b367eb7fee67b3182e283b3a3))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- add libheif ([6b92540](https://github.com/C4illin/ConvertX/commit/6b9254047c0598963aee1d99e20ba1650a0368bf))
|
- 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)
|
- 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))
|
- 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))
|
- 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)
|
- 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)
|
## [0.11.1](https://github.com/C4illin/ConvertX/compare/v0.11.0...v0.11.1) (2025-02-07)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.11.0](https://github.com/C4illin/ConvertX/compare/v0.10.1...v0.11.0) (2025-02-05)
|
||||||
|
|
||||||
### Features
|
### 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
|
### Bug Fixes
|
||||||
|
|
||||||
- don't crash if file is not found ([16f27c1](https://github.com/C4illin/ConvertX/commit/16f27c13bbc1c0e5fa2316f3db11d0918524053b))
|
- 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))
|
- 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)
|
## [0.10.1](https://github.com/C4illin/ConvertX/compare/v0.10.0...v0.10.1) (2025-01-21)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.10.0](https://github.com/C4illin/ConvertX/compare/v0.9.0...v0.10.0) (2025-01-18)
|
||||||
|
|
||||||
### Features
|
### 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
|
### 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 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))
|
- 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))
|
- 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)
|
## [0.9.0](https://github.com/C4illin/ConvertX/compare/v0.8.1...v0.9.0) (2024-11-21)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add inkscape for vector images ([f3740e9](https://github.com/C4illin/ConvertX/commit/f3740e9ded100b8500f3613517960248bbd3c210))
|
- 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)
|
- 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)
|
- disable convert when uploading ([58e220e](https://github.com/C4illin/ConvertX/commit/58e220e82d7f9c163d6ea4dc31092c08a3e254f4)), closes [#177](https://github.com/C4illin/ConvertX/issues/177)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- treat unknown as m4a ([1a442d6](https://github.com/C4illin/ConvertX/commit/1a442d6e69606afef63b1e7df36aa83d111fa23d)), closes [#178](https://github.com/C4illin/ConvertX/issues/178)
|
- 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)
|
- 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)
|
## [0.8.1](https://github.com/C4illin/ConvertX/compare/v0.8.0...v0.8.1) (2024-10-05)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
- 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)
|
- 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)
|
- 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)
|
## [0.8.0](https://github.com/C4illin/ConvertX/compare/v0.7.0...v0.8.0) (2024-09-30)
|
||||||
|
|
||||||
### Features
|
### 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
|
### Bug Fixes
|
||||||
|
|
||||||
- add support for usd for assimp, [#144](https://github.com/C4illin/ConvertX/issues/144) ([2057167](https://github.com/C4illin/ConvertX/commit/20571675766209ad1251f07e687d29a6791afc8b))
|
- 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))
|
- 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))
|
- 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)
|
## [0.7.0](https://github.com/C4illin/ConvertX/compare/v0.6.0...v0.7.0) (2024-09-26)
|
||||||
|
|
||||||
### Features
|
### 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
|
### 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)
|
## [0.6.0](https://github.com/C4illin/ConvertX/compare/v0.5.0...v0.6.0) (2024-09-25)
|
||||||
|
|
||||||
### Features
|
### 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
|
### 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)
|
## [0.5.0](https://github.com/C4illin/ConvertX/compare/v0.4.1...v0.5.0) (2024-09-20)
|
||||||
|
|
||||||
### Features
|
### 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
|
### 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)
|
## [0.4.1](https://github.com/C4illin/ConvertX/compare/v0.4.0...v0.4.1) (2024-09-15)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.4.0](https://github.com/C4illin/ConvertX/compare/v0.3.3...v0.4.0) (2024-08-26)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add option for unauthenticated file conversions [#114](https://github.com/C4illin/ConvertX/issues/114) ([f0d0e43](https://github.com/C4illin/ConvertX/commit/f0d0e4392983c3e4c530304ea88e023fda9bcac0))
|
- 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 resvg converter ([d5eeef9](https://github.com/C4illin/ConvertX/commit/d5eeef9f6884b2bb878508bed97ea9ceaa662995))
|
||||||
- add robots.txt ([6597c1d](https://github.com/C4illin/ConvertX/commit/6597c1d7caeb4dfb6bc47b442e4dfc9840ad12b7))
|
- 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 search bar for formats ([53fff59](https://github.com/C4illin/ConvertX/commit/53fff594fc4d69306abcb2a5cad890fcd0953a58))
|
||||||
|
|
||||||
### Bug Fixes
|
### 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))
|
- 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))
|
- 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))
|
- 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)
|
## [0.3.3](https://github.com/C4illin/ConvertX/compare/v0.3.2...v0.3.3) (2024-07-30)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.3.2](https://github.com/C4illin/ConvertX/compare/v0.3.1...v0.3.2) (2024-07-09)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.3.1](https://github.com/C4illin/ConvertX/compare/v0.3.0...v0.3.1) (2024-06-27)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.3.0](https://github.com/C4illin/ConvertX/compare/v0.2.0...v0.3.0) (2024-06-27)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add version number to log ([4dcb796](https://github.com/C4illin/ConvertX/commit/4dcb796e1bd27badc078d0638076cd9f1e81c4a4)), closes [#44](https://github.com/C4illin/ConvertX/issues/44)
|
- 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))
|
- 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))
|
- 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)
|
## [0.2.0](https://github.com/C4illin/ConvertX/compare/v0.1.2...v0.2.0) (2024-06-20)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- add libjxl for jpegxl conversion ([ff680cb](https://github.com/C4illin/ConvertX/commit/ff680cb29534a25c3148a90fd064bb86c71fb482))
|
- 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)
|
- 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)
|
## [0.1.2](https://github.com/C4illin/ConvertX/compare/v0.1.1...v0.1.2) (2024-06-10)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## [0.1.1](https://github.com/C4illin/ConvertX/compare/v0.1.0...v0.1.1) (2024-05-30)
|
||||||
|
|
||||||
### Bug Fixes
|
### 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)
|
## 0.1.0 (2024-05-30)
|
||||||
|
|
||||||
### Features
|
### 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
|
### 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))
|
||||||
|
310
README.md
310
README.md
@@ -1,155 +1,155 @@
|
|||||||

|

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

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/13818" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13818" alt="C4illin%2FConvertX | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/13818" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13818" alt="C4illin%2FConvertX | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
<!--  -->
|
<!--  -->
|
||||||
|
|
||||||
A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia.
|
A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Convert files to different formats
|
- Convert files to different formats
|
||||||
- Process multiple files at once
|
- Process multiple files at once
|
||||||
- Password protection
|
- Password protection
|
||||||
- Multiple accounts
|
- Multiple accounts
|
||||||
|
|
||||||
## Converters supported
|
## Converters supported
|
||||||
|
|
||||||
| Converter | Use case | Converts from | Converts to |
|
| Converter | Use case | Converts from | Converts to |
|
||||||
| -------------------------------------------------- | ---------------- | ------------- | ----------- |
|
| -------------------------------------------------- | ---------------- | ------------- | ----------- |
|
||||||
| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 |
|
| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 |
|
||||||
| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 |
|
| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 |
|
||||||
| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 |
|
| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 |
|
||||||
| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 |
|
| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 |
|
||||||
| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 |
|
| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 |
|
||||||
| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 |
|
| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 |
|
||||||
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 |
|
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 |
|
||||||
| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 |
|
| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 |
|
||||||
| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 |
|
| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 |
|
||||||
| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 |
|
| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 |
|
||||||
| [Inkscape](https://inkscape.org/) | Vector images | 7 | 17 |
|
| [Inkscape](https://inkscape.org/) | Vector images | 7 | 17 |
|
||||||
| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 77 | 23 |
|
| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 77 | 23 |
|
||||||
| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 |
|
| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 |
|
||||||
| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 |
|
| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 |
|
||||||
| [VTracer](https://github.com/visioncortex/vtracer) | Raster to vector | 8 | 1 |
|
| [VTracer](https://github.com/visioncortex/vtracer) | Raster to vector | 8 | 1 |
|
||||||
| [Dasel](https://github.com/TomWright/dasel) | Data Files | 5 | 4 |
|
| [Dasel](https://github.com/TomWright/dasel) | Data Files | 5 | 4 |
|
||||||
|
|
||||||
<!-- many ffmpeg fileformats are duplicates -->
|
<!-- many ffmpeg fileformats are duplicates -->
|
||||||
|
|
||||||
Any missing converter? Open an issue or pull request!
|
Any missing converter? Open an issue or pull request!
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true
|
> If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
# docker-compose.yml
|
# docker-compose.yml
|
||||||
services:
|
services:
|
||||||
convertx:
|
convertx:
|
||||||
image: ghcr.io/c4illin/convertx
|
image: ghcr.io/c4illin/convertx
|
||||||
container_name: convertx
|
container_name: convertx
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
environment:
|
environment:
|
||||||
- JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset
|
- JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() if unset
|
||||||
# - HTTP_ALLOWED=true # uncomment this if accessing it over a non-https connection
|
# - HTTP_ALLOWED=true # uncomment this if accessing it over a non-https connection
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -p 3000:3000 -v ./data:/app/data ghcr.io/c4illin/convertx
|
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.
|
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.
|
If you get unable to open database file run `chown -R $USER:$USER path` on the path you choose.
|
||||||
|
|
||||||
### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
All are optional, JWT_SECRET is recommended to be set.
|
All are optional, JWT_SECRET is recommended to be set.
|
||||||
|
|
||||||
| Name | Default | Description |
|
| 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 |
|
| 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 |
|
| ACCOUNT_REGISTRATION | false | Allow users to register accounts |
|
||||||
| HTTP_ALLOWED | false | Allow HTTP connections, only set this to true locally |
|
| 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 |
|
| 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 |
|
| 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/" |
|
| 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` |
|
| FFMPEG_ARGS | | Arguments to pass to ffmpeg, e.g. `-preset veryfast` |
|
||||||
| HIDE_HISTORY | false | Hide the history page |
|
| HIDE_HISTORY | false | Hide the history page |
|
||||||
| LANGUAGE | en | Language to format date strings in, specified as a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) |
|
| LANGUAGE | en | Language to format date strings in, specified as a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) |
|
||||||
| UNAUTHENTICATED_USER_SHARING | false | Shares conversion history between all unauthenticated users |
|
| UNAUTHENTICATED_USER_SHARING | false | Shares conversion history between all unauthenticated users |
|
||||||
|
|
||||||
### Docker images
|
### 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.
|
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).
|
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 | What it is |
|
||||||
| -------------------------------------- | -------------------------------- |
|
| -------------------------------------- | -------------------------------- |
|
||||||
| `image: ghcr.io/c4illin/convertx` | The latest release on ghcr |
|
| `image: ghcr.io/c4illin/convertx` | The latest release on ghcr |
|
||||||
| `image: ghcr.io/c4illin/convertx:main` | The latest commit 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` | The latest release on docker hub |
|
||||||
| `image: c4illin/convertx:main` | The latest commit on docker hub |
|
| `image: c4illin/convertx:main` | The latest commit on docker hub |
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
<!-- Dockerhub was introduced in 0.9.0 and older releases -->
|
<!-- Dockerhub was introduced in 0.9.0 and older releases -->
|
||||||
|
|
||||||
### Tutorial
|
### Tutorial
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> These are written by other people, and may be outdated, incorrect or wrong.
|
> These are written by other people, and may be outdated, incorrect or wrong.
|
||||||
|
|
||||||
Tutorial in french: <https://belginux.com/installer-convertx-avec-docker/>
|
Tutorial in french: <https://belginux.com/installer-convertx-avec-docker/>
|
||||||
|
|
||||||
Tutorial in chinese: <https://xzllll.com/24092901/>
|
Tutorial in chinese: <https://xzllll.com/24092901/>
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
0. Install [Bun](https://bun.sh/) and Git
|
0. Install [Bun](https://bun.sh/) and Git
|
||||||
1. Clone the repository
|
1. Clone the repository
|
||||||
2. `bun install`
|
2. `bun install`
|
||||||
3. `bun run dev`
|
3. `bun run dev`
|
||||||
|
|
||||||
Pull requests are welcome! See open issues for the list of todos. The ones tagged with "converter request" are quite easy. Help with docs and cleaning up in issues are also very welcome!
|
Pull requests are welcome! See open issues for the list of todos. The ones tagged with "converter request" are quite easy. Help with docs and cleaning up in issues are also very welcome!
|
||||||
|
|
||||||
Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for commit messages.
|
Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) for commit messages.
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
<a href="https://github.com/C4illin/ConvertX/graphs/contributors">
|
<a href="https://github.com/C4illin/ConvertX/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=C4illin/ConvertX" alt="Image with all contributors"/>
|
<img src="https://contrib.rocks/image?repo=C4illin/ConvertX" alt="Image with all contributors"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
<a href="https://github.com/C4illin/ConvertX/stargazers">
|
<a href="https://github.com/C4illin/ConvertX/stargazers">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date&theme=dark" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date&theme=dark" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" />
|
||||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" />
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=C4illin/ConvertX&type=Date" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
18
SECURITY.md
18
SECURITY.md
@@ -1,9 +1,9 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
Only the latest release is supported
|
Only the latest release is supported
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/C4illin/ConvertX/security/advisories/new) tab.
|
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/C4illin/ConvertX/security/advisories/new) tab.
|
||||||
|
144
biome.json
144
biome.json
@@ -1,72 +1,72 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"formatWithErrors": true,
|
"formatWithErrors": true,
|
||||||
"indentStyle": "space",
|
"indentStyle": "space",
|
||||||
"indentWidth": 2,
|
"indentWidth": 2,
|
||||||
"lineEnding": "lf",
|
"lineEnding": "lf",
|
||||||
"lineWidth": 80,
|
"lineWidth": 80,
|
||||||
"attributePosition": "auto"
|
"attributePosition": "auto"
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignore": ["**/node_modules/**", "**/pico.lime.min.css"]
|
"ignore": ["**/node_modules/**", "**/pico.lime.min.css"]
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true,
|
"recommended": true,
|
||||||
"complexity": {
|
"complexity": {
|
||||||
"noBannedTypes": "error",
|
"noBannedTypes": "error",
|
||||||
"noUselessThisAlias": "error",
|
"noUselessThisAlias": "error",
|
||||||
"noUselessTypeConstraint": "error",
|
"noUselessTypeConstraint": "error",
|
||||||
"useArrowFunction": "off",
|
"useArrowFunction": "off",
|
||||||
"useLiteralKeys": "error",
|
"useLiteralKeys": "error",
|
||||||
"useOptionalChain": "error"
|
"useOptionalChain": "error"
|
||||||
},
|
},
|
||||||
"correctness": {
|
"correctness": {
|
||||||
"noPrecisionLoss": "error",
|
"noPrecisionLoss": "error",
|
||||||
"noUnusedVariables": "off",
|
"noUnusedVariables": "off",
|
||||||
"useJsxKeyInIterable": "off"
|
"useJsxKeyInIterable": "off"
|
||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"noInferrableTypes": "error",
|
"noInferrableTypes": "error",
|
||||||
"noNamespace": "error",
|
"noNamespace": "error",
|
||||||
"useAsConstAssertion": "error",
|
"useAsConstAssertion": "error",
|
||||||
"useBlockStatements": "off",
|
"useBlockStatements": "off",
|
||||||
"useConsistentArrayType": "error",
|
"useConsistentArrayType": "error",
|
||||||
"useForOf": "error",
|
"useForOf": "error",
|
||||||
"useImportType": "error",
|
"useImportType": "error",
|
||||||
"useShorthandFunctionType": "error"
|
"useShorthandFunctionType": "error"
|
||||||
},
|
},
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
"noEmptyBlockStatements": "error",
|
"noEmptyBlockStatements": "error",
|
||||||
"noEmptyInterface": "error",
|
"noEmptyInterface": "error",
|
||||||
"noExplicitAny": "warn",
|
"noExplicitAny": "warn",
|
||||||
"noExtraNonNullAssertion": "error",
|
"noExtraNonNullAssertion": "error",
|
||||||
"noMisleadingInstantiator": "error",
|
"noMisleadingInstantiator": "error",
|
||||||
"noUnsafeDeclarationMerging": "error",
|
"noUnsafeDeclarationMerging": "error",
|
||||||
"useAwait": "error",
|
"useAwait": "error",
|
||||||
"useNamespaceKeyword": "error"
|
"useNamespaceKeyword": "error"
|
||||||
},
|
},
|
||||||
"nursery": {
|
"nursery": {
|
||||||
"useSortedClasses": "error"
|
"useSortedClasses": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"jsxQuoteStyle": "double",
|
"jsxQuoteStyle": "double",
|
||||||
"quoteProperties": "asNeeded",
|
"quoteProperties": "asNeeded",
|
||||||
"semicolons": "always",
|
"semicolons": "always",
|
||||||
"arrowParentheses": "always",
|
"arrowParentheses": "always",
|
||||||
"bracketSpacing": true,
|
"bracketSpacing": true,
|
||||||
"bracketSameLine": true,
|
"bracketSameLine": true,
|
||||||
"quoteStyle": "double",
|
"quoteStyle": "double",
|
||||||
"attributePosition": "auto"
|
"attributePosition": "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
bun.lock
15
bun.lock
@@ -21,7 +21,6 @@
|
|||||||
"@total-typescript/ts-reset": "^0.6.1",
|
"@total-typescript/ts-reset": "^0.6.1",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/node": "^24.6.2",
|
"@types/node": "^24.6.2",
|
||||||
"@types/tar": "^6.1.13",
|
|
||||||
"@typescript-eslint/parser": "^8.45.0",
|
"@typescript-eslint/parser": "^8.45.0",
|
||||||
"eslint": "^9.37.0",
|
"eslint": "^9.37.0",
|
||||||
"eslint-plugin-better-tailwindcss": "^3.7.9",
|
"eslint-plugin-better-tailwindcss": "^3.7.9",
|
||||||
@@ -240,8 +239,6 @@
|
|||||||
|
|
||||||
"@types/react": ["@types/react@19.1.15", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-+kLxJpaJzXybyDyFXYADyP1cznTO8HSuBpenGlnKOAkH4hyNINiywvXS/tGJhsrGGP/gM185RA3xpjY0Yg4erA=="],
|
"@types/react": ["@types/react@19.1.15", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-+kLxJpaJzXybyDyFXYADyP1cznTO8HSuBpenGlnKOAkH4hyNINiywvXS/tGJhsrGGP/gM185RA3xpjY0Yg4erA=="],
|
||||||
|
|
||||||
"@types/tar": ["@types/tar@6.1.13", "", { "dependencies": { "@types/node": "*", "minipass": "^4.0.0" } }, "sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.45.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/type-utils": "8.45.0", "@typescript-eslint/utils": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.45.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/type-utils": "8.45.0", "@typescript-eslint/utils": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.45.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.45.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ=="],
|
||||||
@@ -470,7 +467,7 @@
|
|||||||
|
|
||||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||||
|
|
||||||
"minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
|
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||||
|
|
||||||
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
|
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
|
||||||
|
|
||||||
@@ -636,8 +633,6 @@
|
|||||||
|
|
||||||
"@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
"@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||||
|
|
||||||
"@isaacs/fs-minipass/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
"@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||||
@@ -658,8 +653,6 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"@types/tar/@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
@@ -674,10 +667,6 @@
|
|||||||
|
|
||||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"minizlib/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
|
||||||
|
|
||||||
"tar/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
|
||||||
|
|
||||||
"tsconfig-paths-webpack-plugin/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"tsconfig-paths-webpack-plugin/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
"@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||||
@@ -704,8 +693,6 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"@types/tar/@types/node/undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
|
||||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
40
compose.yaml
40
compose.yaml
@@ -1,20 +1,20 @@
|
|||||||
services:
|
services:
|
||||||
convertx:
|
convertx:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
# dockerfile: Debian.Dockerfile
|
# dockerfile: Debian.Dockerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
environment: # Defaults are listed below. All are optional.
|
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)
|
- 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
|
- JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default
|
||||||
- HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally
|
- 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
|
- 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
|
- 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
|
# - FFMPEG_ARGS=-hwaccel vulkan # additional arguments to pass to ffmpeg
|
||||||
# - WEBROOT=/convertx # the root path of the web interface, leave empty to disable
|
# - 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
|
# - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
|
||||||
- TZ=Europe/Stockholm # set your timezone, defaults to UTC
|
- TZ=Europe/Stockholm # set your timezone, defaults to UTC
|
||||||
# - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
|
# - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
|
144
eslint.config.ts
144
eslint.config.ts
@@ -1,72 +1,72 @@
|
|||||||
import js from "@eslint/js";
|
import js from "@eslint/js";
|
||||||
import eslintParserTypeScript from "@typescript-eslint/parser";
|
import eslintParserTypeScript from "@typescript-eslint/parser";
|
||||||
import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
|
import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
|
||||||
import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
|
import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
|
||||||
import globals from "globals";
|
import globals from "globals";
|
||||||
import tseslint from "typescript-eslint";
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
tseslint.configs.recommended,
|
tseslint.configs.recommended,
|
||||||
{
|
{
|
||||||
plugins: {
|
plugins: {
|
||||||
"simple-import-sort": simpleImportSortPlugin,
|
"simple-import-sort": simpleImportSortPlugin,
|
||||||
"better-tailwindcss": eslintPluginBetterTailwindcss,
|
"better-tailwindcss": eslintPluginBetterTailwindcss,
|
||||||
},
|
},
|
||||||
ignores: ["**/node_modules/**", "eslint.config.ts"],
|
ignores: ["**/node_modules/**", "eslint.config.ts"],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parser: eslintParserTypeScript,
|
parser: eslintParserTypeScript,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: true,
|
project: true,
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
jsx: true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
...globals.node,
|
...globals.node,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
files: ["**/*.{tsx,ts}"],
|
files: ["**/*.{tsx,ts}"],
|
||||||
settings: {
|
settings: {
|
||||||
"better-tailwindcss": {
|
"better-tailwindcss": {
|
||||||
entryPoint: "src/main.css",
|
entryPoint: "src/main.css",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...(eslintPluginBetterTailwindcss.configs["recommended-warn"] ?? {}).rules,
|
...(eslintPluginBetterTailwindcss.configs["recommended-warn"] ?? {}).rules,
|
||||||
...(eslintPluginBetterTailwindcss.configs["stylistic-warn"] ?? {}).rules,
|
...(eslintPluginBetterTailwindcss.configs["stylistic-warn"] ?? {}).rules,
|
||||||
// "tailwindcss/classnames-order": "off",
|
// "tailwindcss/classnames-order": "off",
|
||||||
"better-tailwindcss/enforce-consistent-line-wrapping": [
|
"better-tailwindcss/enforce-consistent-line-wrapping": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
group: "newLine",
|
group: "newLine",
|
||||||
printWidth: 100,
|
printWidth: 100,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"better-tailwindcss/no-unregistered-classes": [
|
"better-tailwindcss/no-unregistered-classes": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
ignore: [
|
ignore: [
|
||||||
"^group(?:\\/(\\S*))?$",
|
"^group(?:\\/(\\S*))?$",
|
||||||
"^peer(?:\\/(\\S*))?$",
|
"^peer(?:\\/(\\S*))?$",
|
||||||
"select_container",
|
"select_container",
|
||||||
"convert_to_popup",
|
"convert_to_popup",
|
||||||
"convert_to_group",
|
"convert_to_group",
|
||||||
"target",
|
"target",
|
||||||
"convert_to_target",
|
"convert_to_target",
|
||||||
"job-details-toggle",
|
"job-details-toggle",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ["**/*.{js,cjs,mjs,jsx}"],
|
files: ["**/*.{js,cjs,mjs,jsx}"],
|
||||||
extends: [tseslint.configs.disableTypeChecked],
|
extends: [tseslint.configs.disableTypeChecked],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.browser,
|
...globals.browser,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
18
knip.json
18
knip.json
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||||
"entry": ["tests/**/*.test.ts"],
|
"entry": ["tests/**/*.test.ts"],
|
||||||
"project": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"],
|
"project": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"],
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"entry": ["src/main.css"]
|
"entry": ["src/main.css"]
|
||||||
},
|
},
|
||||||
"ignoreDependencies": ["tailwind-scrollbar"]
|
"ignoreDependencies": ["tailwind-scrollbar"]
|
||||||
}
|
}
|
||||||
|
@@ -4,12 +4,12 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --watch src/index.tsx",
|
"dev": "bun run --watch src/index.tsx",
|
||||||
"hot": "bun run --hot src/index.tsx",
|
"hot": "bun run --hot src/index.tsx",
|
||||||
"format": "run-p 'format:*'",
|
"format": "npm-run-all 'format:*'",
|
||||||
"format:eslint": "eslint --fix .",
|
"format:eslint": "eslint --fix .",
|
||||||
"format:prettier": "prettier --write .",
|
"format:prettier": "prettier --write .",
|
||||||
"build:js": "tsc",
|
"build:js": "tsc",
|
||||||
"build": "bun x @tailwindcss/cli -i ./src/main.css -o ./public/generated.css && bun run build:js",
|
"build": "bun x @tailwindcss/cli -i ./src/main.css -o ./public/generated.css && bun run build:js",
|
||||||
"lint": "run-p 'lint:*'",
|
"lint": "npm-run-all 'lint:*'",
|
||||||
"lint:tsc": "tsc --noEmit",
|
"lint:tsc": "tsc --noEmit",
|
||||||
"lint:knip": "knip",
|
"lint:knip": "knip",
|
||||||
"lint:eslint": "eslint .",
|
"lint:eslint": "eslint .",
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
"@total-typescript/ts-reset": "^0.6.1",
|
"@total-typescript/ts-reset": "^0.6.1",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/node": "^24.6.2",
|
"@types/node": "^24.6.2",
|
||||||
"@types/tar": "^6.1.13",
|
|
||||||
"@typescript-eslint/parser": "^8.45.0",
|
"@typescript-eslint/parser": "^8.45.0",
|
||||||
"eslint": "^9.37.0",
|
"eslint": "^9.37.0",
|
||||||
"eslint-plugin-better-tailwindcss": "^3.7.9",
|
"eslint-plugin-better-tailwindcss": "^3.7.9",
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
"@tailwindcss/postcss": {},
|
"@tailwindcss/postcss": {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* @type {import('prettier').Config & import("@ianvs/prettier-plugin-sort-imports").PluginConfig}
|
* @type {import('prettier').Config & import("@ianvs/prettier-plugin-sort-imports").PluginConfig}
|
||||||
*/
|
*/
|
||||||
const config = {
|
const config = {
|
||||||
arrowParens: "always",
|
arrowParens: "always",
|
||||||
printWidth: 100,
|
printWidth: 100,
|
||||||
singleQuote: false,
|
singleQuote: false,
|
||||||
semi: true,
|
semi: true,
|
||||||
tabWidth: 2,
|
tabWidth: 2,
|
||||||
plugins: ["@ianvs/prettier-plugin-sort-imports"],
|
plugins: ["@ianvs/prettier-plugin-sort-imports"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@@ -1,24 +1,24 @@
|
|||||||
const webroot = document.querySelector("meta[name='webroot']").content;
|
const webroot = document.querySelector("meta[name='webroot']").content;
|
||||||
const jobId = window.location.pathname.split("/").pop();
|
const jobId = window.location.pathname.split("/").pop();
|
||||||
const main = document.querySelector("main");
|
const main = document.querySelector("main");
|
||||||
let progressElem = document.querySelector("progress");
|
let progressElem = document.querySelector("progress");
|
||||||
|
|
||||||
const refreshData = () => {
|
const refreshData = () => {
|
||||||
// console.log("Refreshing data...", progressElem.value, progressElem.max);
|
// console.log("Refreshing data...", progressElem.value, progressElem.max);
|
||||||
if (progressElem.value !== progressElem.max) {
|
if (progressElem.value !== progressElem.max) {
|
||||||
fetch(`${webroot}/progress/${jobId}`, {
|
fetch(`${webroot}/progress/${jobId}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
})
|
})
|
||||||
.then((res) => res.text())
|
.then((res) => res.text())
|
||||||
.then((html) => {
|
.then((html) => {
|
||||||
main.innerHTML = html;
|
main.innerHTML = html;
|
||||||
})
|
})
|
||||||
.catch((err) => console.log(err));
|
.catch((err) => console.log(err));
|
||||||
|
|
||||||
setTimeout(refreshData, 1000);
|
setTimeout(refreshData, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
progressElem = document.querySelector("progress");
|
progressElem = document.querySelector("progress");
|
||||||
};
|
};
|
||||||
|
|
||||||
refreshData();
|
refreshData();
|
||||||
|
502
public/script.js
502
public/script.js
@@ -1,251 +1,251 @@
|
|||||||
const webroot = document.querySelector("meta[name='webroot']").content;
|
const webroot = document.querySelector("meta[name='webroot']").content;
|
||||||
const fileInput = document.querySelector('input[type="file"]');
|
const fileInput = document.querySelector('input[type="file"]');
|
||||||
const dropZone = document.getElementById("dropzone");
|
const dropZone = document.getElementById("dropzone");
|
||||||
const convertButton = document.querySelector("input[type='submit']");
|
const convertButton = document.querySelector("input[type='submit']");
|
||||||
const fileNames = [];
|
const fileNames = [];
|
||||||
let fileType;
|
let fileType;
|
||||||
let pendingFiles = 0;
|
let pendingFiles = 0;
|
||||||
let formatSelected = false;
|
let formatSelected = false;
|
||||||
|
|
||||||
dropZone.addEventListener("dragover", (e) => {
|
dropZone.addEventListener("dragover", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dropZone.classList.add("dragover");
|
dropZone.classList.add("dragover");
|
||||||
});
|
});
|
||||||
|
|
||||||
dropZone.addEventListener("dragleave", () => {
|
dropZone.addEventListener("dragleave", () => {
|
||||||
dropZone.classList.remove("dragover");
|
dropZone.classList.remove("dragover");
|
||||||
});
|
});
|
||||||
|
|
||||||
dropZone.addEventListener("drop", (e) => {
|
dropZone.addEventListener("drop", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dropZone.classList.remove("dragover");
|
dropZone.classList.remove("dragover");
|
||||||
|
|
||||||
const files = e.dataTransfer.files;
|
const files = e.dataTransfer.files;
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
console.warn("No files dropped — likely a URL or unsupported source.");
|
console.warn("No files dropped — likely a URL or unsupported source.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
console.log("Handling dropped file:", file.name);
|
console.log("Handling dropped file:", file.name);
|
||||||
handleFile(file);
|
handleFile(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extracted handleFile function for reusability in drag-and-drop and file input
|
// Extracted handleFile function for reusability in drag-and-drop and file input
|
||||||
function handleFile(file) {
|
function handleFile(file) {
|
||||||
const fileList = document.querySelector("#file-list");
|
const fileList = document.querySelector("#file-list");
|
||||||
|
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td>${file.name}</td>
|
<td>${file.name}</td>
|
||||||
<td><progress max="100" class="inline-block h-2 appearance-none overflow-hidden rounded-full border-0 bg-neutral-700 bg-none text-accent-500 accent-accent-500 [&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full [&::-webkit-progress-value]:[background:none] [&[value]::-webkit-progress-value]:bg-accent-500 [&[value]::-webkit-progress-value]:transition-[inline-size]"></progress></td>
|
<td><progress max="100" class="inline-block h-2 appearance-none overflow-hidden rounded-full border-0 bg-neutral-700 bg-none text-accent-500 accent-accent-500 [&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full [&::-webkit-progress-value]:[background:none] [&[value]::-webkit-progress-value]:bg-accent-500 [&[value]::-webkit-progress-value]:transition-[inline-size]"></progress></td>
|
||||||
<td>${(file.size / 1024).toFixed(2)} kB</td>
|
<td>${(file.size / 1024).toFixed(2)} kB</td>
|
||||||
<td><a onclick="deleteRow(this)">Remove</a></td>
|
<td><a onclick="deleteRow(this)">Remove</a></td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (!fileType) {
|
if (!fileType) {
|
||||||
fileType = file.name.split(".").pop();
|
fileType = file.name.split(".").pop();
|
||||||
fileInput.setAttribute("accept", `.${fileType}`);
|
fileInput.setAttribute("accept", `.${fileType}`);
|
||||||
setTitle();
|
setTitle();
|
||||||
|
|
||||||
fetch(`${webroot}/conversions`, {
|
fetch(`${webroot}/conversions`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ fileType }),
|
body: JSON.stringify({ fileType }),
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
})
|
})
|
||||||
.then((res) => res.text())
|
.then((res) => res.text())
|
||||||
.then((html) => {
|
.then((html) => {
|
||||||
selectContainer.innerHTML = html;
|
selectContainer.innerHTML = html;
|
||||||
updateSearchBar();
|
updateSearchBar();
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileList.appendChild(row);
|
fileList.appendChild(row);
|
||||||
file.htmlRow = row;
|
file.htmlRow = row;
|
||||||
fileNames.push(file.name);
|
fileNames.push(file.name);
|
||||||
uploadFile(file);
|
uploadFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectContainer = document.querySelector("form .select_container");
|
const selectContainer = document.querySelector("form .select_container");
|
||||||
|
|
||||||
const updateSearchBar = () => {
|
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 convertToPopup = document.querySelector(".convert_to_popup");
|
||||||
const convertToGroupElements = document.querySelectorAll(".convert_to_group");
|
const convertToGroupElements = document.querySelectorAll(".convert_to_group");
|
||||||
const convertToGroups = {};
|
const convertToGroups = {};
|
||||||
const convertToElement = document.querySelector("select[name='convert_to']");
|
const convertToElement = document.querySelector("select[name='convert_to']");
|
||||||
|
|
||||||
const showMatching = (search) => {
|
const showMatching = (search) => {
|
||||||
for (const [targets, groupElement] of Object.values(convertToGroups)) {
|
for (const [targets, groupElement] of Object.values(convertToGroups)) {
|
||||||
let matchingTargetsFound = 0;
|
let matchingTargetsFound = 0;
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
if (target.dataset.target.includes(search)) {
|
if (target.dataset.target.includes(search)) {
|
||||||
matchingTargetsFound++;
|
matchingTargetsFound++;
|
||||||
target.classList.remove("hidden");
|
target.classList.remove("hidden");
|
||||||
target.classList.add("flex");
|
target.classList.add("flex");
|
||||||
} else {
|
} else {
|
||||||
target.classList.add("hidden");
|
target.classList.add("hidden");
|
||||||
target.classList.remove("flex");
|
target.classList.remove("flex");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingTargetsFound === 0) {
|
if (matchingTargetsFound === 0) {
|
||||||
groupElement.classList.add("hidden");
|
groupElement.classList.add("hidden");
|
||||||
groupElement.classList.remove("flex");
|
groupElement.classList.remove("flex");
|
||||||
} else {
|
} else {
|
||||||
groupElement.classList.remove("hidden");
|
groupElement.classList.remove("hidden");
|
||||||
groupElement.classList.add("flex");
|
groupElement.classList.add("flex");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const groupElement of convertToGroupElements) {
|
for (const groupElement of convertToGroupElements) {
|
||||||
const groupName = groupElement.dataset.converter;
|
const groupName = groupElement.dataset.converter;
|
||||||
|
|
||||||
const targetElements = groupElement.querySelectorAll(".target");
|
const targetElements = groupElement.querySelectorAll(".target");
|
||||||
const targets = Array.from(targetElements);
|
const targets = Array.from(targetElements);
|
||||||
|
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
target.onmousedown = () => {
|
target.onmousedown = () => {
|
||||||
convertToElement.value = target.dataset.value;
|
convertToElement.value = target.dataset.value;
|
||||||
convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`;
|
convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`;
|
||||||
formatSelected = true;
|
formatSelected = true;
|
||||||
if (pendingFiles === 0 && fileNames.length > 0) {
|
if (pendingFiles === 0 && fileNames.length > 0) {
|
||||||
convertButton.disabled = false;
|
convertButton.disabled = false;
|
||||||
}
|
}
|
||||||
showMatching("");
|
showMatching("");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
convertToGroups[groupName] = [targets, groupElement];
|
convertToGroups[groupName] = [targets, groupElement];
|
||||||
}
|
}
|
||||||
|
|
||||||
convertToInput.addEventListener("input", (e) => {
|
convertToInput.addEventListener("input", (e) => {
|
||||||
showMatching(e.target.value.toLowerCase());
|
showMatching(e.target.value.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
convertToInput.addEventListener("search", () => {
|
convertToInput.addEventListener("search", () => {
|
||||||
// when the user clears the search bar using the 'x' button
|
// when the user clears the search bar using the 'x' button
|
||||||
convertButton.disabled = true;
|
convertButton.disabled = true;
|
||||||
formatSelected = false;
|
formatSelected = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
convertToInput.addEventListener("blur", (e) => {
|
convertToInput.addEventListener("blur", (e) => {
|
||||||
// Keep the popup open even when clicking on a target button
|
// Keep the popup open even when clicking on a target button
|
||||||
// for a split second to allow the click to go through
|
// for a split second to allow the click to go through
|
||||||
if (e?.relatedTarget?.classList?.contains("target")) {
|
if (e?.relatedTarget?.classList?.contains("target")) {
|
||||||
convertToPopup.classList.add("hidden");
|
convertToPopup.classList.add("hidden");
|
||||||
convertToPopup.classList.remove("flex");
|
convertToPopup.classList.remove("flex");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
convertToPopup.classList.add("hidden");
|
convertToPopup.classList.add("hidden");
|
||||||
convertToPopup.classList.remove("flex");
|
convertToPopup.classList.remove("flex");
|
||||||
});
|
});
|
||||||
|
|
||||||
convertToInput.addEventListener("focus", () => {
|
convertToInput.addEventListener("focus", () => {
|
||||||
convertToPopup.classList.remove("hidden");
|
convertToPopup.classList.remove("hidden");
|
||||||
convertToPopup.classList.add("flex");
|
convertToPopup.classList.add("flex");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add a 'change' event listener to the file input element
|
// Add a 'change' event listener to the file input element
|
||||||
fileInput.addEventListener("change", (e) => {
|
fileInput.addEventListener("change", (e) => {
|
||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
handleFile(file);
|
handleFile(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const setTitle = () => {
|
const setTitle = () => {
|
||||||
const title = document.querySelector("h1");
|
const title = document.querySelector("h1");
|
||||||
title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`;
|
title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add a onclick for the delete button
|
// Add a onclick for the delete button
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const deleteRow = (target) => {
|
const deleteRow = (target) => {
|
||||||
const filename = target.parentElement.parentElement.children[0].textContent;
|
const filename = target.parentElement.parentElement.children[0].textContent;
|
||||||
const row = target.parentElement.parentElement;
|
const row = target.parentElement.parentElement;
|
||||||
row.remove();
|
row.remove();
|
||||||
|
|
||||||
// remove from fileNames
|
// remove from fileNames
|
||||||
const index = fileNames.indexOf(filename);
|
const index = fileNames.indexOf(filename);
|
||||||
fileNames.splice(index, 1);
|
fileNames.splice(index, 1);
|
||||||
|
|
||||||
// reset fileInput
|
// reset fileInput
|
||||||
fileInput.value = "";
|
fileInput.value = "";
|
||||||
|
|
||||||
// if fileNames is empty, reset fileType
|
// if fileNames is empty, reset fileType
|
||||||
if (fileNames.length === 0) {
|
if (fileNames.length === 0) {
|
||||||
fileType = null;
|
fileType = null;
|
||||||
fileInput.removeAttribute("accept");
|
fileInput.removeAttribute("accept");
|
||||||
convertButton.disabled = true;
|
convertButton.disabled = true;
|
||||||
setTitle();
|
setTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`${webroot}/delete`, {
|
fetch(`${webroot}/delete`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ filename: filename }),
|
body: JSON.stringify({ filename: filename }),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}).catch((err) => console.log(err));
|
}).catch((err) => console.log(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFile = (file) => {
|
const uploadFile = (file) => {
|
||||||
convertButton.disabled = true;
|
convertButton.disabled = true;
|
||||||
convertButton.textContent = "Uploading...";
|
convertButton.textContent = "Uploading...";
|
||||||
pendingFiles += 1;
|
pendingFiles += 1;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file, file.name);
|
formData.append("file", file, file.name);
|
||||||
|
|
||||||
let xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
xhr.open("POST", `${webroot}/upload`, true);
|
xhr.open("POST", `${webroot}/upload`, true);
|
||||||
|
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
let data = JSON.parse(xhr.responseText);
|
let data = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
pendingFiles -= 1;
|
pendingFiles -= 1;
|
||||||
if (pendingFiles === 0) {
|
if (pendingFiles === 0) {
|
||||||
if (formatSelected) {
|
if (formatSelected) {
|
||||||
convertButton.disabled = false;
|
convertButton.disabled = false;
|
||||||
}
|
}
|
||||||
convertButton.textContent = "Convert";
|
convertButton.textContent = "Convert";
|
||||||
}
|
}
|
||||||
|
|
||||||
//Remove the progress bar when upload is done
|
//Remove the progress bar when upload is done
|
||||||
let progressbar = file.htmlRow.getElementsByTagName("progress");
|
let progressbar = file.htmlRow.getElementsByTagName("progress");
|
||||||
progressbar[0].parentElement.remove();
|
progressbar[0].parentElement.remove();
|
||||||
console.log(data);
|
console.log(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.upload.onprogress = (e) => {
|
xhr.upload.onprogress = (e) => {
|
||||||
let sent = e.loaded;
|
let sent = e.loaded;
|
||||||
let total = e.total;
|
let total = e.total;
|
||||||
console.log(`upload progress (${file.name}):`, (100 * sent) / total);
|
console.log(`upload progress (${file.name}):`, (100 * sent) / total);
|
||||||
|
|
||||||
let progressbar = file.htmlRow.getElementsByTagName("progress");
|
let progressbar = file.htmlRow.getElementsByTagName("progress");
|
||||||
progressbar[0].value = (100 * sent) / total;
|
progressbar[0].value = (100 * sent) / total;
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = (e) => {
|
xhr.onerror = (e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formConvert = document.querySelector(`form[action='${webroot}/convert']`);
|
const formConvert = document.querySelector(`form[action='${webroot}/convert']`);
|
||||||
|
|
||||||
formConvert.addEventListener("submit", () => {
|
formConvert.addEventListener("submit", () => {
|
||||||
const hiddenInput = document.querySelector("input[name='file_names']");
|
const hiddenInput = document.querySelector("input[name='file_names']");
|
||||||
hiddenInput.value = JSON.stringify(fileNames);
|
hiddenInput.value = JSON.stringify(fileNames);
|
||||||
});
|
});
|
||||||
|
|
||||||
updateSearchBar();
|
updateSearchBar();
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "ConvertX | Self Hosted File Converter",
|
"name": "ConvertX | Self Hosted File Converter",
|
||||||
"short_name": "ConvertX",
|
"short_name": "ConvertX",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/android-chrome-192x192.png",
|
"src": "/android-chrome-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/android-chrome-512x512.png",
|
"src": "/android-chrome-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme_color": "#a5d601",
|
"theme_color": "#a5d601",
|
||||||
"background_color": "#13171f",
|
"background_color": "#13171f",
|
||||||
"display": "standalone"
|
"display": "standalone"
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["config:recommended", ":disableDependencyDashboard"],
|
"extends": ["config:recommended", ":disableDependencyDashboard"],
|
||||||
"lockFileMaintenance": {
|
"lockFileMaintenance": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"automerge": true
|
"automerge": true
|
||||||
},
|
},
|
||||||
"ignoreDeps": ["bun-types", "@types/bun"]
|
"ignoreDeps": ["bun-types", "@types/bun"]
|
||||||
}
|
}
|
||||||
|
2
reset.d.ts
vendored
2
reset.d.ts
vendored
@@ -1 +1 @@
|
|||||||
import "@total-typescript/ts-reset";
|
import "@total-typescript/ts-reset";
|
||||||
|
@@ -1,44 +1,44 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import { Html } from "@elysiajs/html";
|
||||||
import { version } from "../../package.json";
|
import { version } from "../../package.json";
|
||||||
|
|
||||||
export const BaseHtml = ({
|
export const BaseHtml = ({
|
||||||
children,
|
children,
|
||||||
title = "ConvertX",
|
title = "ConvertX",
|
||||||
webroot = "",
|
webroot = "",
|
||||||
}: {
|
}: {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
title?: string;
|
title?: string;
|
||||||
webroot?: string;
|
webroot?: string;
|
||||||
}) => (
|
}) => (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="webroot" content={webroot} />
|
<meta name="webroot" content={webroot} />
|
||||||
<title safe>{title}</title>
|
<title safe>{title}</title>
|
||||||
<link rel="stylesheet" href={`${webroot}/generated.css`} />
|
<link rel="stylesheet" href={`${webroot}/generated.css`} />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href={`${webroot}/apple-touch-icon.png`} />
|
<link rel="apple-touch-icon" sizes="180x180" href={`${webroot}/apple-touch-icon.png`} />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href={`${webroot}/favicon-32x32.png`} />
|
<link rel="icon" type="image/png" sizes="32x32" href={`${webroot}/favicon-32x32.png`} />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href={`${webroot}/favicon-16x16.png`} />
|
<link rel="icon" type="image/png" sizes="16x16" href={`${webroot}/favicon-16x16.png`} />
|
||||||
<link rel="manifest" href={`${webroot}/site.webmanifest`} />
|
<link rel="manifest" href={`${webroot}/site.webmanifest`} />
|
||||||
</head>
|
</head>
|
||||||
<body class={`flex min-h-screen w-full flex-col bg-neutral-900 text-neutral-200`}>
|
<body class={`flex min-h-screen w-full flex-col bg-neutral-900 text-neutral-200`}>
|
||||||
{children}
|
{children}
|
||||||
<footer class="w-full">
|
<footer class="w-full">
|
||||||
<div class="p-4 text-center text-sm text-neutral-500">
|
<div class="p-4 text-center text-sm text-neutral-500">
|
||||||
<span>Powered by </span>
|
<span>Powered by </span>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/C4illin/ConvertX"
|
href="https://github.com/C4illin/ConvertX"
|
||||||
class={`
|
class={`
|
||||||
text-neutral-400
|
text-neutral-400
|
||||||
hover:text-accent-500
|
hover:text-accent-500
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
ConvertX{" "}
|
ConvertX{" "}
|
||||||
</a>
|
</a>
|
||||||
<span safe>v{version || ""}</span>
|
<span safe>v{version || ""}</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
@@ -1,106 +1,106 @@
|
|||||||
import { Html } from "@kitajs/html";
|
import { Html } from "@kitajs/html";
|
||||||
|
|
||||||
export const Header = ({
|
export const Header = ({
|
||||||
loggedIn,
|
loggedIn,
|
||||||
accountRegistration,
|
accountRegistration,
|
||||||
allowUnauthenticated,
|
allowUnauthenticated,
|
||||||
hideHistory,
|
hideHistory,
|
||||||
webroot = "",
|
webroot = "",
|
||||||
}: {
|
}: {
|
||||||
loggedIn?: boolean;
|
loggedIn?: boolean;
|
||||||
accountRegistration?: boolean;
|
accountRegistration?: boolean;
|
||||||
allowUnauthenticated?: boolean;
|
allowUnauthenticated?: boolean;
|
||||||
hideHistory?: boolean;
|
hideHistory?: boolean;
|
||||||
webroot?: string;
|
webroot?: string;
|
||||||
}) => {
|
}) => {
|
||||||
let rightNav: JSX.Element;
|
let rightNav: JSX.Element;
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
rightNav = (
|
rightNav = (
|
||||||
<ul class="flex gap-4">
|
<ul class="flex gap-4">
|
||||||
{!hideHistory && (
|
{!hideHistory && (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-600 transition-all
|
text-accent-600 transition-all
|
||||||
hover:text-accent-500 hover:underline
|
hover:text-accent-500 hover:underline
|
||||||
`}
|
`}
|
||||||
href={`${webroot}/history`}
|
href={`${webroot}/history`}
|
||||||
>
|
>
|
||||||
History
|
History
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
{!allowUnauthenticated ? (
|
{!allowUnauthenticated ? (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-600 transition-all
|
text-accent-600 transition-all
|
||||||
hover:text-accent-500 hover:underline
|
hover:text-accent-500 hover:underline
|
||||||
`}
|
`}
|
||||||
href={`${webroot}/account`}
|
href={`${webroot}/account`}
|
||||||
>
|
>
|
||||||
Account
|
Account
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
{!allowUnauthenticated ? (
|
{!allowUnauthenticated ? (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-600 transition-all
|
text-accent-600 transition-all
|
||||||
hover:text-accent-500 hover:underline
|
hover:text-accent-500 hover:underline
|
||||||
`}
|
`}
|
||||||
href={`${webroot}/logoff`}
|
href={`${webroot}/logoff`}
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
rightNav = (
|
rightNav = (
|
||||||
<ul class="flex gap-4">
|
<ul class="flex gap-4">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-600 transition-all
|
text-accent-600 transition-all
|
||||||
hover:text-accent-500 hover:underline
|
hover:text-accent-500 hover:underline
|
||||||
`}
|
`}
|
||||||
href={`${webroot}/login`}
|
href={`${webroot}/login`}
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{accountRegistration ? (
|
{accountRegistration ? (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-600 transition-all
|
text-accent-600 transition-all
|
||||||
hover:text-accent-500 hover:underline
|
hover:text-accent-500 hover:underline
|
||||||
`}
|
`}
|
||||||
href={`${webroot}/register`}
|
href={`${webroot}/register`}
|
||||||
>
|
>
|
||||||
Register
|
Register
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header class="w-full p-4">
|
<header class="w-full p-4">
|
||||||
<nav class={`mx-auto flex max-w-4xl justify-between rounded-sm bg-neutral-900 p-4`}>
|
<nav class={`mx-auto flex max-w-4xl justify-between rounded-sm bg-neutral-900 p-4`}>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<strong>
|
<strong>
|
||||||
<a href={`${webroot}/`}>ConvertX</a>
|
<a href={`${webroot}/`}>ConvertX</a>
|
||||||
</strong>
|
</strong>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{rightNav}
|
{rightNav}
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,140 +1,140 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
object: [
|
object: [
|
||||||
"3d",
|
"3d",
|
||||||
"3ds",
|
"3ds",
|
||||||
"3mf",
|
"3mf",
|
||||||
"ac",
|
"ac",
|
||||||
"ac3d",
|
"ac3d",
|
||||||
"acc",
|
"acc",
|
||||||
"amf",
|
"amf",
|
||||||
"amj",
|
"amj",
|
||||||
"ase",
|
"ase",
|
||||||
"ask",
|
"ask",
|
||||||
"assbin",
|
"assbin",
|
||||||
"b3d",
|
"b3d",
|
||||||
"blend",
|
"blend",
|
||||||
"bsp",
|
"bsp",
|
||||||
"bvh",
|
"bvh",
|
||||||
"cob",
|
"cob",
|
||||||
"csm",
|
"csm",
|
||||||
"dae",
|
"dae",
|
||||||
"dxf",
|
"dxf",
|
||||||
"enff",
|
"enff",
|
||||||
"fbx",
|
"fbx",
|
||||||
"glb",
|
"glb",
|
||||||
"gltf",
|
"gltf",
|
||||||
"hmb",
|
"hmb",
|
||||||
"hmp",
|
"hmp",
|
||||||
"ifc",
|
"ifc",
|
||||||
"ifczip",
|
"ifczip",
|
||||||
"iqm",
|
"iqm",
|
||||||
"irr",
|
"irr",
|
||||||
"irrmesh",
|
"irrmesh",
|
||||||
"lwo",
|
"lwo",
|
||||||
"lws",
|
"lws",
|
||||||
"lxo",
|
"lxo",
|
||||||
"m3d",
|
"m3d",
|
||||||
"md2",
|
"md2",
|
||||||
"md3",
|
"md3",
|
||||||
"md5anim",
|
"md5anim",
|
||||||
"md5camera",
|
"md5camera",
|
||||||
"md5mesh",
|
"md5mesh",
|
||||||
"mdc",
|
"mdc",
|
||||||
"mdl",
|
"mdl",
|
||||||
"mesh.xml",
|
"mesh.xml",
|
||||||
"mesh",
|
"mesh",
|
||||||
"mot",
|
"mot",
|
||||||
"ms3d",
|
"ms3d",
|
||||||
"ndo",
|
"ndo",
|
||||||
"nff",
|
"nff",
|
||||||
"obj",
|
"obj",
|
||||||
"off",
|
"off",
|
||||||
"ogex",
|
"ogex",
|
||||||
"pk3",
|
"pk3",
|
||||||
"ply",
|
"ply",
|
||||||
"pmx",
|
"pmx",
|
||||||
"prj",
|
"prj",
|
||||||
"q3o",
|
"q3o",
|
||||||
"q3s",
|
"q3s",
|
||||||
"raw",
|
"raw",
|
||||||
"scn",
|
"scn",
|
||||||
"sib",
|
"sib",
|
||||||
"smd",
|
"smd",
|
||||||
"step",
|
"step",
|
||||||
"stl",
|
"stl",
|
||||||
"stp",
|
"stp",
|
||||||
"ter",
|
"ter",
|
||||||
"uc",
|
"uc",
|
||||||
"usd",
|
"usd",
|
||||||
"usda",
|
"usda",
|
||||||
"usdc",
|
"usdc",
|
||||||
"usdz",
|
"usdz",
|
||||||
"vta",
|
"vta",
|
||||||
"x",
|
"x",
|
||||||
"x3d",
|
"x3d",
|
||||||
"x3db",
|
"x3db",
|
||||||
"xgl",
|
"xgl",
|
||||||
"xml",
|
"xml",
|
||||||
"zae",
|
"zae",
|
||||||
"zgl",
|
"zgl",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
object: [
|
object: [
|
||||||
"3ds",
|
"3ds",
|
||||||
"3mf",
|
"3mf",
|
||||||
"assbin",
|
"assbin",
|
||||||
"assjson",
|
"assjson",
|
||||||
"assxml",
|
"assxml",
|
||||||
"collada",
|
"collada",
|
||||||
"dae",
|
"dae",
|
||||||
"fbx",
|
"fbx",
|
||||||
"fbxa",
|
"fbxa",
|
||||||
"glb",
|
"glb",
|
||||||
"glb2",
|
"glb2",
|
||||||
"gltf",
|
"gltf",
|
||||||
"gltf2",
|
"gltf2",
|
||||||
"json",
|
"json",
|
||||||
"obj",
|
"obj",
|
||||||
"objnomtl",
|
"objnomtl",
|
||||||
"pbrt",
|
"pbrt",
|
||||||
"ply",
|
"ply",
|
||||||
"plyb",
|
"plyb",
|
||||||
"stl",
|
"stl",
|
||||||
"stlb",
|
"stlb",
|
||||||
"stp",
|
"stp",
|
||||||
"x",
|
"x",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function convert(
|
export async function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => {
|
execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,86 +1,86 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
document: [
|
document: [
|
||||||
"azw4",
|
"azw4",
|
||||||
"chm",
|
"chm",
|
||||||
"cbr",
|
"cbr",
|
||||||
"cbz",
|
"cbz",
|
||||||
"cbt",
|
"cbt",
|
||||||
"cba",
|
"cba",
|
||||||
"cb7",
|
"cb7",
|
||||||
"djvu",
|
"djvu",
|
||||||
"docx",
|
"docx",
|
||||||
"epub",
|
"epub",
|
||||||
"fb2",
|
"fb2",
|
||||||
"htlz",
|
"htlz",
|
||||||
"html",
|
"html",
|
||||||
"lit",
|
"lit",
|
||||||
"lrf",
|
"lrf",
|
||||||
"mobi",
|
"mobi",
|
||||||
"odt",
|
"odt",
|
||||||
"pdb",
|
"pdb",
|
||||||
"pdf",
|
"pdf",
|
||||||
"pml",
|
"pml",
|
||||||
"rb",
|
"rb",
|
||||||
"rtf",
|
"rtf",
|
||||||
"recipe",
|
"recipe",
|
||||||
"snb",
|
"snb",
|
||||||
"tcr",
|
"tcr",
|
||||||
"txt",
|
"txt",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
document: [
|
document: [
|
||||||
"azw3",
|
"azw3",
|
||||||
"docx",
|
"docx",
|
||||||
"epub",
|
"epub",
|
||||||
"fb2",
|
"fb2",
|
||||||
"html",
|
"html",
|
||||||
"htmlz",
|
"htmlz",
|
||||||
"kepub.epub",
|
"kepub.epub",
|
||||||
"lit",
|
"lit",
|
||||||
"lrf",
|
"lrf",
|
||||||
"mobi",
|
"mobi",
|
||||||
"oeb",
|
"oeb",
|
||||||
"pdb",
|
"pdb",
|
||||||
"pdf",
|
"pdf",
|
||||||
"pml",
|
"pml",
|
||||||
"rb",
|
"rb",
|
||||||
"rtf",
|
"rtf",
|
||||||
"snb",
|
"snb",
|
||||||
"tcr",
|
"tcr",
|
||||||
"txt",
|
"txt",
|
||||||
"txtz",
|
"txtz",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function convert(
|
export async function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("ebook-convert", [filePath, targetPath], (error, stdout, stderr) => {
|
execFile("ebook-convert", [filePath, targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,48 +1,48 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
document: ["yaml", "toml", "json", "xml", "csv"],
|
document: ["yaml", "toml", "json", "xml", "csv"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
document: ["yaml", "toml", "json", "csv"],
|
document: ["yaml", "toml", "json", "csv"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function convert(
|
export async function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
|
|
||||||
args.push("--file", filePath);
|
args.push("--file", filePath);
|
||||||
args.push("--read", fileType);
|
args.push("--read", fileType);
|
||||||
args.push("--write", convertTo);
|
args.push("--write", convertTo);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("dasel", args, (error, stdout, stderr) => {
|
execFile("dasel", args, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFile(targetPath, stdout, (err: NodeJS.ErrnoException | null) => {
|
fs.writeFile(targetPath, stdout, (err: NodeJS.ErrnoException | null) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(`Failed to write output: ${err}`);
|
reject(`Failed to write output: ${err}`);
|
||||||
} else {
|
} else {
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,49 +1,49 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
images: ["dvi", "xdv", "pdf", "eps"],
|
images: ["dvi", "xdv", "pdf", "eps"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
images: ["svg", "svgz"],
|
images: ["svg", "svgz"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const inputArgs: string[] = [];
|
const inputArgs: string[] = [];
|
||||||
if (fileType === "eps") {
|
if (fileType === "eps") {
|
||||||
inputArgs.push("--eps");
|
inputArgs.push("--eps");
|
||||||
}
|
}
|
||||||
if (fileType === "pdf") {
|
if (fileType === "pdf") {
|
||||||
inputArgs.push("--pdf");
|
inputArgs.push("--pdf");
|
||||||
}
|
}
|
||||||
if (convertTo === "svgz") {
|
if (convertTo === "svgz") {
|
||||||
inputArgs.push("-z");
|
inputArgs.push("-z");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("dvisvgm", [...inputArgs, filePath, "-o", targetPath], (error, stdout, stderr) => {
|
execFile("dvisvgm", [...inputArgs, filePath, "-o", targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,337 +1,337 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
image: [
|
image: [
|
||||||
"3fr",
|
"3fr",
|
||||||
"8bim",
|
"8bim",
|
||||||
"8bimtext",
|
"8bimtext",
|
||||||
"8bimwtext",
|
"8bimwtext",
|
||||||
"app1",
|
"app1",
|
||||||
"app1jpeg",
|
"app1jpeg",
|
||||||
"art",
|
"art",
|
||||||
"arw",
|
"arw",
|
||||||
"avs",
|
"avs",
|
||||||
"b",
|
"b",
|
||||||
"bie",
|
"bie",
|
||||||
"bigtiff",
|
"bigtiff",
|
||||||
"bmp",
|
"bmp",
|
||||||
"c",
|
"c",
|
||||||
"cals",
|
"cals",
|
||||||
"caption",
|
"caption",
|
||||||
"cin",
|
"cin",
|
||||||
"cmyk",
|
"cmyk",
|
||||||
"cmyka",
|
"cmyka",
|
||||||
"cr2",
|
"cr2",
|
||||||
"crw",
|
"crw",
|
||||||
"cur",
|
"cur",
|
||||||
"cut",
|
"cut",
|
||||||
"dcm",
|
"dcm",
|
||||||
"dcr",
|
"dcr",
|
||||||
"dcx",
|
"dcx",
|
||||||
"dng",
|
"dng",
|
||||||
"dpx",
|
"dpx",
|
||||||
"epdf",
|
"epdf",
|
||||||
"epi",
|
"epi",
|
||||||
"eps",
|
"eps",
|
||||||
"epsf",
|
"epsf",
|
||||||
"epsi",
|
"epsi",
|
||||||
"ept",
|
"ept",
|
||||||
"ept2",
|
"ept2",
|
||||||
"ept3",
|
"ept3",
|
||||||
"erf",
|
"erf",
|
||||||
"exif",
|
"exif",
|
||||||
"fax",
|
"fax",
|
||||||
"file",
|
"file",
|
||||||
"fits",
|
"fits",
|
||||||
"fractal",
|
"fractal",
|
||||||
"ftp",
|
"ftp",
|
||||||
"g",
|
"g",
|
||||||
"gif",
|
"gif",
|
||||||
"gif87",
|
"gif87",
|
||||||
"gradient",
|
"gradient",
|
||||||
"gray",
|
"gray",
|
||||||
"graya",
|
"graya",
|
||||||
"heic",
|
"heic",
|
||||||
"heif",
|
"heif",
|
||||||
"hrz",
|
"hrz",
|
||||||
"http",
|
"http",
|
||||||
"icb",
|
"icb",
|
||||||
"icc",
|
"icc",
|
||||||
"icm",
|
"icm",
|
||||||
"ico",
|
"ico",
|
||||||
"icon",
|
"icon",
|
||||||
"identity",
|
"identity",
|
||||||
"image",
|
"image",
|
||||||
"iptc",
|
"iptc",
|
||||||
"iptctext",
|
"iptctext",
|
||||||
"iptcwtext",
|
"iptcwtext",
|
||||||
"jbg",
|
"jbg",
|
||||||
"jbig",
|
"jbig",
|
||||||
"jng",
|
"jng",
|
||||||
"jnx",
|
"jnx",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"jpg",
|
"jpg",
|
||||||
"k",
|
"k",
|
||||||
"k25",
|
"k25",
|
||||||
"kdc",
|
"kdc",
|
||||||
"label",
|
"label",
|
||||||
"m",
|
"m",
|
||||||
"mac",
|
"mac",
|
||||||
"map",
|
"map",
|
||||||
"mat",
|
"mat",
|
||||||
"mef",
|
"mef",
|
||||||
"miff",
|
"miff",
|
||||||
"mng",
|
"mng",
|
||||||
"mono",
|
"mono",
|
||||||
"mpc",
|
"mpc",
|
||||||
"mrw",
|
"mrw",
|
||||||
"msl",
|
"msl",
|
||||||
"mtv",
|
"mtv",
|
||||||
"mvg",
|
"mvg",
|
||||||
"nef",
|
"nef",
|
||||||
"null",
|
"null",
|
||||||
"o",
|
"o",
|
||||||
"orf",
|
"orf",
|
||||||
"otb",
|
"otb",
|
||||||
"p7",
|
"p7",
|
||||||
"pal",
|
"pal",
|
||||||
"palm",
|
"palm",
|
||||||
"pam",
|
"pam",
|
||||||
"pbm",
|
"pbm",
|
||||||
"pcd",
|
"pcd",
|
||||||
"pcds",
|
"pcds",
|
||||||
"pct",
|
"pct",
|
||||||
"pcx",
|
"pcx",
|
||||||
"pdb",
|
"pdb",
|
||||||
"pdf",
|
"pdf",
|
||||||
"pef",
|
"pef",
|
||||||
"pfa",
|
"pfa",
|
||||||
"pfb",
|
"pfb",
|
||||||
"pgm",
|
"pgm",
|
||||||
"picon",
|
"picon",
|
||||||
"pict",
|
"pict",
|
||||||
"pix",
|
"pix",
|
||||||
"plasma",
|
"plasma",
|
||||||
"png",
|
"png",
|
||||||
"png00",
|
"png00",
|
||||||
"png24",
|
"png24",
|
||||||
"png32",
|
"png32",
|
||||||
"png48",
|
"png48",
|
||||||
"png64",
|
"png64",
|
||||||
"png8",
|
"png8",
|
||||||
"pnm",
|
"pnm",
|
||||||
"ppm",
|
"ppm",
|
||||||
"ps",
|
"ps",
|
||||||
"ptif",
|
"ptif",
|
||||||
"pwp",
|
"pwp",
|
||||||
"r",
|
"r",
|
||||||
"raf",
|
"raf",
|
||||||
"ras",
|
"ras",
|
||||||
"rgb",
|
"rgb",
|
||||||
"rgba",
|
"rgba",
|
||||||
"rla",
|
"rla",
|
||||||
"rle",
|
"rle",
|
||||||
"sct",
|
"sct",
|
||||||
"sfw",
|
"sfw",
|
||||||
"sgi",
|
"sgi",
|
||||||
"sr2",
|
"sr2",
|
||||||
"srf",
|
"srf",
|
||||||
"stegano",
|
"stegano",
|
||||||
"sun",
|
"sun",
|
||||||
"svg",
|
"svg",
|
||||||
"svgz",
|
"svgz",
|
||||||
"text",
|
"text",
|
||||||
"tga",
|
"tga",
|
||||||
"tif",
|
"tif",
|
||||||
"tiff",
|
"tiff",
|
||||||
"tile",
|
"tile",
|
||||||
"tim",
|
"tim",
|
||||||
"topol",
|
"topol",
|
||||||
"ttf",
|
"ttf",
|
||||||
"txt",
|
"txt",
|
||||||
"uyvy",
|
"uyvy",
|
||||||
"vda",
|
"vda",
|
||||||
"vicar",
|
"vicar",
|
||||||
"vid",
|
"vid",
|
||||||
"viff",
|
"viff",
|
||||||
"vst",
|
"vst",
|
||||||
"wbmp",
|
"wbmp",
|
||||||
"webp",
|
"webp",
|
||||||
"wmf",
|
"wmf",
|
||||||
"wpg",
|
"wpg",
|
||||||
"x3f",
|
"x3f",
|
||||||
"xbm",
|
"xbm",
|
||||||
"xc",
|
"xc",
|
||||||
"xcf",
|
"xcf",
|
||||||
"xmp",
|
"xmp",
|
||||||
"xpm",
|
"xpm",
|
||||||
"xv",
|
"xv",
|
||||||
"xwd",
|
"xwd",
|
||||||
"y",
|
"y",
|
||||||
"yuv",
|
"yuv",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
image: [
|
image: [
|
||||||
"8bim",
|
"8bim",
|
||||||
"8bimtext",
|
"8bimtext",
|
||||||
"8bimwtext",
|
"8bimwtext",
|
||||||
"app1",
|
"app1",
|
||||||
"app1jpeg",
|
"app1jpeg",
|
||||||
"art",
|
"art",
|
||||||
"avs",
|
"avs",
|
||||||
"b",
|
"b",
|
||||||
"bie",
|
"bie",
|
||||||
"bigtiff",
|
"bigtiff",
|
||||||
"bmp",
|
"bmp",
|
||||||
"bmp2",
|
"bmp2",
|
||||||
"bmp3",
|
"bmp3",
|
||||||
"brf",
|
"brf",
|
||||||
"c",
|
"c",
|
||||||
"cals",
|
"cals",
|
||||||
"cin",
|
"cin",
|
||||||
"cmyk",
|
"cmyk",
|
||||||
"cmyka",
|
"cmyka",
|
||||||
"dcx",
|
"dcx",
|
||||||
"dpx",
|
"dpx",
|
||||||
"epdf",
|
"epdf",
|
||||||
"epi",
|
"epi",
|
||||||
"eps",
|
"eps",
|
||||||
"eps2",
|
"eps2",
|
||||||
"eps3",
|
"eps3",
|
||||||
"epsf",
|
"epsf",
|
||||||
"epsi",
|
"epsi",
|
||||||
"ept",
|
"ept",
|
||||||
"ept2",
|
"ept2",
|
||||||
"ept3",
|
"ept3",
|
||||||
"exif",
|
"exif",
|
||||||
"fax",
|
"fax",
|
||||||
"fits",
|
"fits",
|
||||||
"g",
|
"g",
|
||||||
"gif",
|
"gif",
|
||||||
"gif87",
|
"gif87",
|
||||||
"gray",
|
"gray",
|
||||||
"graya",
|
"graya",
|
||||||
"histogram",
|
"histogram",
|
||||||
"html",
|
"html",
|
||||||
"icb",
|
"icb",
|
||||||
"icc",
|
"icc",
|
||||||
"icm",
|
"icm",
|
||||||
"info",
|
"info",
|
||||||
"iptc",
|
"iptc",
|
||||||
"iptctext",
|
"iptctext",
|
||||||
"iptcwtext",
|
"iptcwtext",
|
||||||
"isobrl",
|
"isobrl",
|
||||||
"isobrl6",
|
"isobrl6",
|
||||||
"jbg",
|
"jbg",
|
||||||
"jbig",
|
"jbig",
|
||||||
"jng",
|
"jng",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"k",
|
"k",
|
||||||
"m",
|
"m",
|
||||||
"m2v",
|
"m2v",
|
||||||
"map",
|
"map",
|
||||||
"mat",
|
"mat",
|
||||||
"matte",
|
"matte",
|
||||||
"miff",
|
"miff",
|
||||||
"mng",
|
"mng",
|
||||||
"mono",
|
"mono",
|
||||||
"mpc",
|
"mpc",
|
||||||
"mpeg",
|
"mpeg",
|
||||||
"mpg",
|
"mpg",
|
||||||
"msl",
|
"msl",
|
||||||
"mtv",
|
"mtv",
|
||||||
"mvg",
|
"mvg",
|
||||||
"null",
|
"null",
|
||||||
"o",
|
"o",
|
||||||
"otb",
|
"otb",
|
||||||
"p7",
|
"p7",
|
||||||
"pal",
|
"pal",
|
||||||
"pam",
|
"pam",
|
||||||
"pbm",
|
"pbm",
|
||||||
"pcd",
|
"pcd",
|
||||||
"pcds",
|
"pcds",
|
||||||
"pcl",
|
"pcl",
|
||||||
"pct",
|
"pct",
|
||||||
"pcx",
|
"pcx",
|
||||||
"pdb",
|
"pdb",
|
||||||
"pdf",
|
"pdf",
|
||||||
"pgm",
|
"pgm",
|
||||||
"picon",
|
"picon",
|
||||||
"pict",
|
"pict",
|
||||||
"png",
|
"png",
|
||||||
"png00",
|
"png00",
|
||||||
"png24",
|
"png24",
|
||||||
"png32",
|
"png32",
|
||||||
"png48",
|
"png48",
|
||||||
"png64",
|
"png64",
|
||||||
"png8",
|
"png8",
|
||||||
"pnm",
|
"pnm",
|
||||||
"ppm",
|
"ppm",
|
||||||
"preview",
|
"preview",
|
||||||
"ps",
|
"ps",
|
||||||
"ps2",
|
"ps2",
|
||||||
"ps3",
|
"ps3",
|
||||||
"ptif",
|
"ptif",
|
||||||
"r",
|
"r",
|
||||||
"ras",
|
"ras",
|
||||||
"rgb",
|
"rgb",
|
||||||
"rgba",
|
"rgba",
|
||||||
"sgi",
|
"sgi",
|
||||||
"shtml",
|
"shtml",
|
||||||
"sun",
|
"sun",
|
||||||
"text",
|
"text",
|
||||||
"tga",
|
"tga",
|
||||||
"tiff",
|
"tiff",
|
||||||
"txt",
|
"txt",
|
||||||
"ubrl",
|
"ubrl",
|
||||||
"ubrl6",
|
"ubrl6",
|
||||||
"uil",
|
"uil",
|
||||||
"uyvy",
|
"uyvy",
|
||||||
"vda",
|
"vda",
|
||||||
"vicar",
|
"vicar",
|
||||||
"vid",
|
"vid",
|
||||||
"viff",
|
"viff",
|
||||||
"vst",
|
"vst",
|
||||||
"wbmp",
|
"wbmp",
|
||||||
"webp",
|
"webp",
|
||||||
"x",
|
"x",
|
||||||
"xbm",
|
"xbm",
|
||||||
"xmp",
|
"xmp",
|
||||||
"xpm",
|
"xpm",
|
||||||
"xv",
|
"xv",
|
||||||
"xwd",
|
"xwd",
|
||||||
"y",
|
"y",
|
||||||
"yuv",
|
"yuv",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("gm", ["convert", filePath, targetPath], (error, stdout, stderr) => {
|
execFile("gm", ["convert", filePath, targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,492 +1,492 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
// declare possible conversions
|
// declare possible conversions
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
images: [
|
images: [
|
||||||
"3fr",
|
"3fr",
|
||||||
"3g2",
|
"3g2",
|
||||||
"3gp",
|
"3gp",
|
||||||
"aai",
|
"aai",
|
||||||
"ai",
|
"ai",
|
||||||
"apng",
|
"apng",
|
||||||
"art",
|
"art",
|
||||||
"arw",
|
"arw",
|
||||||
"avci",
|
"avci",
|
||||||
"avi",
|
"avi",
|
||||||
"avif",
|
"avif",
|
||||||
"avs",
|
"avs",
|
||||||
"bayer",
|
"bayer",
|
||||||
"bayera",
|
"bayera",
|
||||||
"bgr",
|
"bgr",
|
||||||
"bgra",
|
"bgra",
|
||||||
"bgro",
|
"bgro",
|
||||||
"bmp",
|
"bmp",
|
||||||
"bmp2",
|
"bmp2",
|
||||||
"bmp3",
|
"bmp3",
|
||||||
"cal",
|
"cal",
|
||||||
"cals",
|
"cals",
|
||||||
"canvas",
|
"canvas",
|
||||||
"caption",
|
"caption",
|
||||||
"cin",
|
"cin",
|
||||||
"clip",
|
"clip",
|
||||||
"clipboard",
|
"clipboard",
|
||||||
"cmyk",
|
"cmyk",
|
||||||
"cmyka",
|
"cmyka",
|
||||||
"cr2",
|
"cr2",
|
||||||
"cr3",
|
"cr3",
|
||||||
"crw",
|
"crw",
|
||||||
"cube",
|
"cube",
|
||||||
"cur",
|
"cur",
|
||||||
"cut",
|
"cut",
|
||||||
"data",
|
"data",
|
||||||
"dcm",
|
"dcm",
|
||||||
"dcr",
|
"dcr",
|
||||||
"dcraw",
|
"dcraw",
|
||||||
"dcx",
|
"dcx",
|
||||||
"dds",
|
"dds",
|
||||||
"dfont",
|
"dfont",
|
||||||
"dng",
|
"dng",
|
||||||
"dpx",
|
"dpx",
|
||||||
"dxt1",
|
"dxt1",
|
||||||
"dxt5",
|
"dxt5",
|
||||||
"emf",
|
"emf",
|
||||||
"epdf",
|
"epdf",
|
||||||
"epi",
|
"epi",
|
||||||
"eps",
|
"eps",
|
||||||
"epsf",
|
"epsf",
|
||||||
"epsi",
|
"epsi",
|
||||||
"ept",
|
"ept",
|
||||||
"ept2",
|
"ept2",
|
||||||
"ept3",
|
"ept3",
|
||||||
"erf",
|
"erf",
|
||||||
"exr",
|
"exr",
|
||||||
"farbfeld",
|
"farbfeld",
|
||||||
"fax",
|
"fax",
|
||||||
"ff",
|
"ff",
|
||||||
"fff",
|
"fff",
|
||||||
"file",
|
"file",
|
||||||
"fits",
|
"fits",
|
||||||
"fl32",
|
"fl32",
|
||||||
"flif",
|
"flif",
|
||||||
"flv",
|
"flv",
|
||||||
"fractal",
|
"fractal",
|
||||||
"ftp",
|
"ftp",
|
||||||
"fts",
|
"fts",
|
||||||
"ftxt",
|
"ftxt",
|
||||||
"g3",
|
"g3",
|
||||||
"g4",
|
"g4",
|
||||||
"gif",
|
"gif",
|
||||||
"gif87",
|
"gif87",
|
||||||
"gradient",
|
"gradient",
|
||||||
"gray",
|
"gray",
|
||||||
"graya",
|
"graya",
|
||||||
"group4",
|
"group4",
|
||||||
"hald",
|
"hald",
|
||||||
"hdr",
|
"hdr",
|
||||||
"heic",
|
"heic",
|
||||||
"heif",
|
"heif",
|
||||||
"hrz",
|
"hrz",
|
||||||
"http",
|
"http",
|
||||||
"https",
|
"https",
|
||||||
"icb",
|
"icb",
|
||||||
"ico",
|
"ico",
|
||||||
"icon",
|
"icon",
|
||||||
"iiq",
|
"iiq",
|
||||||
"inline",
|
"inline",
|
||||||
"ipl",
|
"ipl",
|
||||||
"j2c",
|
"j2c",
|
||||||
"j2k",
|
"j2k",
|
||||||
"jng",
|
"jng",
|
||||||
"jnx",
|
"jnx",
|
||||||
"jp2",
|
"jp2",
|
||||||
"jpc",
|
"jpc",
|
||||||
"jpe",
|
"jpe",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"jpg",
|
"jpg",
|
||||||
"jpm",
|
"jpm",
|
||||||
"jps",
|
"jps",
|
||||||
"jpt",
|
"jpt",
|
||||||
"jxl",
|
"jxl",
|
||||||
"k25",
|
"k25",
|
||||||
"kdc",
|
"kdc",
|
||||||
"label",
|
"label",
|
||||||
"m2v",
|
"m2v",
|
||||||
"m4v",
|
"m4v",
|
||||||
"mac",
|
"mac",
|
||||||
"map",
|
"map",
|
||||||
"mask",
|
"mask",
|
||||||
"mat",
|
"mat",
|
||||||
"mdc",
|
"mdc",
|
||||||
"mef",
|
"mef",
|
||||||
"miff",
|
"miff",
|
||||||
"mkv",
|
"mkv",
|
||||||
"mng",
|
"mng",
|
||||||
"mono",
|
"mono",
|
||||||
"mos",
|
"mos",
|
||||||
"mov",
|
"mov",
|
||||||
"mp4",
|
"mp4",
|
||||||
"mpc",
|
"mpc",
|
||||||
"mpeg",
|
"mpeg",
|
||||||
"mpg",
|
"mpg",
|
||||||
"mpo",
|
"mpo",
|
||||||
"mrw",
|
"mrw",
|
||||||
"msl",
|
"msl",
|
||||||
"msvg",
|
"msvg",
|
||||||
"mtv",
|
"mtv",
|
||||||
"mvg",
|
"mvg",
|
||||||
"nef",
|
"nef",
|
||||||
"nrw",
|
"nrw",
|
||||||
"null",
|
"null",
|
||||||
"ora",
|
"ora",
|
||||||
"orf",
|
"orf",
|
||||||
"otb",
|
"otb",
|
||||||
"otf",
|
"otf",
|
||||||
"pal",
|
"pal",
|
||||||
"palm",
|
"palm",
|
||||||
"pam",
|
"pam",
|
||||||
"pango",
|
"pango",
|
||||||
"pattern",
|
"pattern",
|
||||||
"pbm",
|
"pbm",
|
||||||
"pcd",
|
"pcd",
|
||||||
"pcds",
|
"pcds",
|
||||||
"pcl",
|
"pcl",
|
||||||
"pct",
|
"pct",
|
||||||
"pcx",
|
"pcx",
|
||||||
"pdb",
|
"pdb",
|
||||||
"pdf",
|
"pdf",
|
||||||
"pdfa",
|
"pdfa",
|
||||||
"pef",
|
"pef",
|
||||||
"pes",
|
"pes",
|
||||||
"pfa",
|
"pfa",
|
||||||
"pfb",
|
"pfb",
|
||||||
"pfm",
|
"pfm",
|
||||||
"pgm",
|
"pgm",
|
||||||
"pgx",
|
"pgx",
|
||||||
"phm",
|
"phm",
|
||||||
"picon",
|
"picon",
|
||||||
"pict",
|
"pict",
|
||||||
"pix",
|
"pix",
|
||||||
"pjpeg",
|
"pjpeg",
|
||||||
"plasma",
|
"plasma",
|
||||||
"png",
|
"png",
|
||||||
"png00",
|
"png00",
|
||||||
"png24",
|
"png24",
|
||||||
"png32",
|
"png32",
|
||||||
"png48",
|
"png48",
|
||||||
"png64",
|
"png64",
|
||||||
"png8",
|
"png8",
|
||||||
"pnm",
|
"pnm",
|
||||||
"pocketmod",
|
"pocketmod",
|
||||||
"ppm",
|
"ppm",
|
||||||
"ps",
|
"ps",
|
||||||
"psb",
|
"psb",
|
||||||
"psd",
|
"psd",
|
||||||
"ptif",
|
"ptif",
|
||||||
"pwp",
|
"pwp",
|
||||||
"qoi",
|
"qoi",
|
||||||
"radial",
|
"radial",
|
||||||
"raf",
|
"raf",
|
||||||
"ras",
|
"ras",
|
||||||
"raw",
|
"raw",
|
||||||
"rgb",
|
"rgb",
|
||||||
"rgb565",
|
"rgb565",
|
||||||
"rgba",
|
"rgba",
|
||||||
"rgbo",
|
"rgbo",
|
||||||
"rgf",
|
"rgf",
|
||||||
"rla",
|
"rla",
|
||||||
"rle",
|
"rle",
|
||||||
"rmf",
|
"rmf",
|
||||||
"rsvg",
|
"rsvg",
|
||||||
"rw2",
|
"rw2",
|
||||||
"rwl",
|
"rwl",
|
||||||
"scr",
|
"scr",
|
||||||
"screenshot",
|
"screenshot",
|
||||||
"sct",
|
"sct",
|
||||||
"sfw",
|
"sfw",
|
||||||
"sgi",
|
"sgi",
|
||||||
"six",
|
"six",
|
||||||
"sixel",
|
"sixel",
|
||||||
"sr2",
|
"sr2",
|
||||||
"srf",
|
"srf",
|
||||||
"srw",
|
"srw",
|
||||||
"stegano",
|
"stegano",
|
||||||
"sti",
|
"sti",
|
||||||
"strimg",
|
"strimg",
|
||||||
"sun",
|
"sun",
|
||||||
"svg",
|
"svg",
|
||||||
"svgz",
|
"svgz",
|
||||||
"text",
|
"text",
|
||||||
"tga",
|
"tga",
|
||||||
"tiff",
|
"tiff",
|
||||||
"tiff64",
|
"tiff64",
|
||||||
"tile",
|
"tile",
|
||||||
"tim",
|
"tim",
|
||||||
"tm2",
|
"tm2",
|
||||||
"ttc",
|
"ttc",
|
||||||
"ttf",
|
"ttf",
|
||||||
"txt",
|
"txt",
|
||||||
"uyvy",
|
"uyvy",
|
||||||
"vda",
|
"vda",
|
||||||
"vicar",
|
"vicar",
|
||||||
"vid",
|
"vid",
|
||||||
"viff",
|
"viff",
|
||||||
"vips",
|
"vips",
|
||||||
"vst",
|
"vst",
|
||||||
"wbmp",
|
"wbmp",
|
||||||
"webm",
|
"webm",
|
||||||
"webp",
|
"webp",
|
||||||
"wmf",
|
"wmf",
|
||||||
"wmv",
|
"wmv",
|
||||||
"wpg",
|
"wpg",
|
||||||
"x3f",
|
"x3f",
|
||||||
"xbm",
|
"xbm",
|
||||||
"xc",
|
"xc",
|
||||||
"xcf",
|
"xcf",
|
||||||
"xpm",
|
"xpm",
|
||||||
"xps",
|
"xps",
|
||||||
"xv",
|
"xv",
|
||||||
"ycbcr",
|
"ycbcr",
|
||||||
"ycbcra",
|
"ycbcra",
|
||||||
"yuv",
|
"yuv",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
images: [
|
images: [
|
||||||
"aai",
|
"aai",
|
||||||
"ai",
|
"ai",
|
||||||
"apng",
|
"apng",
|
||||||
"art",
|
"art",
|
||||||
"ashlar",
|
"ashlar",
|
||||||
"avif",
|
"avif",
|
||||||
"avs",
|
"avs",
|
||||||
"bayer",
|
"bayer",
|
||||||
"bayera",
|
"bayera",
|
||||||
"bgr",
|
"bgr",
|
||||||
"bgra",
|
"bgra",
|
||||||
"bgro",
|
"bgro",
|
||||||
"bmp",
|
"bmp",
|
||||||
"bmp2",
|
"bmp2",
|
||||||
"bmp3",
|
"bmp3",
|
||||||
"brf",
|
"brf",
|
||||||
"cal",
|
"cal",
|
||||||
"cals",
|
"cals",
|
||||||
"cin",
|
"cin",
|
||||||
"cip",
|
"cip",
|
||||||
"clip",
|
"clip",
|
||||||
"clipboard",
|
"clipboard",
|
||||||
"cmyk",
|
"cmyk",
|
||||||
"cmyka",
|
"cmyka",
|
||||||
"cur",
|
"cur",
|
||||||
"data",
|
"data",
|
||||||
"dcx",
|
"dcx",
|
||||||
"dds",
|
"dds",
|
||||||
"dpx",
|
"dpx",
|
||||||
"dxt1",
|
"dxt1",
|
||||||
"dxt5",
|
"dxt5",
|
||||||
"epdf",
|
"epdf",
|
||||||
"epi",
|
"epi",
|
||||||
"eps",
|
"eps",
|
||||||
"eps2",
|
"eps2",
|
||||||
"eps3",
|
"eps3",
|
||||||
"epsf",
|
"epsf",
|
||||||
"epsi",
|
"epsi",
|
||||||
"ept",
|
"ept",
|
||||||
"ept2",
|
"ept2",
|
||||||
"ept3",
|
"ept3",
|
||||||
"exr",
|
"exr",
|
||||||
"farbfeld",
|
"farbfeld",
|
||||||
"fax",
|
"fax",
|
||||||
"ff",
|
"ff",
|
||||||
"fits",
|
"fits",
|
||||||
"fl32",
|
"fl32",
|
||||||
"flif",
|
"flif",
|
||||||
"flv",
|
"flv",
|
||||||
"fts",
|
"fts",
|
||||||
"ftxt",
|
"ftxt",
|
||||||
"g3",
|
"g3",
|
||||||
"g4",
|
"g4",
|
||||||
"gif",
|
"gif",
|
||||||
"gif87",
|
"gif87",
|
||||||
"gray",
|
"gray",
|
||||||
"graya",
|
"graya",
|
||||||
"group4",
|
"group4",
|
||||||
"hdr",
|
"hdr",
|
||||||
"histogram",
|
"histogram",
|
||||||
"hrz",
|
"hrz",
|
||||||
"htm",
|
"htm",
|
||||||
"html",
|
"html",
|
||||||
"icb",
|
"icb",
|
||||||
"ico",
|
"ico",
|
||||||
"icon",
|
"icon",
|
||||||
"info",
|
"info",
|
||||||
"inline",
|
"inline",
|
||||||
"ipl",
|
"ipl",
|
||||||
"isobrl",
|
"isobrl",
|
||||||
"isobrl6",
|
"isobrl6",
|
||||||
"j2c",
|
"j2c",
|
||||||
"j2k",
|
"j2k",
|
||||||
"jng",
|
"jng",
|
||||||
"jp2",
|
"jp2",
|
||||||
"jpc",
|
"jpc",
|
||||||
"jpe",
|
"jpe",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"jpg",
|
"jpg",
|
||||||
"jpm",
|
"jpm",
|
||||||
"jps",
|
"jps",
|
||||||
"jpt",
|
"jpt",
|
||||||
"json",
|
"json",
|
||||||
"jxl",
|
"jxl",
|
||||||
"m2v",
|
"m2v",
|
||||||
"m4v",
|
"m4v",
|
||||||
"map",
|
"map",
|
||||||
"mask",
|
"mask",
|
||||||
"mat",
|
"mat",
|
||||||
"matte",
|
"matte",
|
||||||
"miff",
|
"miff",
|
||||||
"mkv",
|
"mkv",
|
||||||
"mng",
|
"mng",
|
||||||
"mono",
|
"mono",
|
||||||
"mov",
|
"mov",
|
||||||
"mp4",
|
"mp4",
|
||||||
"mpc",
|
"mpc",
|
||||||
"mpeg",
|
"mpeg",
|
||||||
"mpg",
|
"mpg",
|
||||||
"msl",
|
"msl",
|
||||||
"msvg",
|
"msvg",
|
||||||
"mtv",
|
"mtv",
|
||||||
"mvg",
|
"mvg",
|
||||||
"null",
|
"null",
|
||||||
"otb",
|
"otb",
|
||||||
"pal",
|
"pal",
|
||||||
"palm",
|
"palm",
|
||||||
"pam",
|
"pam",
|
||||||
"pbm",
|
"pbm",
|
||||||
"pcd",
|
"pcd",
|
||||||
"pcds",
|
"pcds",
|
||||||
"pcl",
|
"pcl",
|
||||||
"pct",
|
"pct",
|
||||||
"pcx",
|
"pcx",
|
||||||
"pdb",
|
"pdb",
|
||||||
"pdf",
|
"pdf",
|
||||||
"pdfa",
|
"pdfa",
|
||||||
"pfm",
|
"pfm",
|
||||||
"pgm",
|
"pgm",
|
||||||
"pgx",
|
"pgx",
|
||||||
"phm",
|
"phm",
|
||||||
"picon",
|
"picon",
|
||||||
"pict",
|
"pict",
|
||||||
"pjpeg",
|
"pjpeg",
|
||||||
"png",
|
"png",
|
||||||
"png00",
|
"png00",
|
||||||
"png24",
|
"png24",
|
||||||
"png32",
|
"png32",
|
||||||
"png48",
|
"png48",
|
||||||
"png64",
|
"png64",
|
||||||
"png8",
|
"png8",
|
||||||
"pnm",
|
"pnm",
|
||||||
"pocketmod",
|
"pocketmod",
|
||||||
"ppm",
|
"ppm",
|
||||||
"ps",
|
"ps",
|
||||||
"ps2",
|
"ps2",
|
||||||
"ps3",
|
"ps3",
|
||||||
"psb",
|
"psb",
|
||||||
"psd",
|
"psd",
|
||||||
"ptif",
|
"ptif",
|
||||||
"qoi",
|
"qoi",
|
||||||
"ras",
|
"ras",
|
||||||
"rgb",
|
"rgb",
|
||||||
"rgba",
|
"rgba",
|
||||||
"rgbo",
|
"rgbo",
|
||||||
"rgf",
|
"rgf",
|
||||||
"rsvg",
|
"rsvg",
|
||||||
"sgi",
|
"sgi",
|
||||||
"shtml",
|
"shtml",
|
||||||
"six",
|
"six",
|
||||||
"sixel",
|
"sixel",
|
||||||
"sparse",
|
"sparse",
|
||||||
"strimg",
|
"strimg",
|
||||||
"sun",
|
"sun",
|
||||||
"svg",
|
"svg",
|
||||||
"svgz",
|
"svgz",
|
||||||
"tga",
|
"tga",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"tiff",
|
"tiff",
|
||||||
"tiff64",
|
"tiff64",
|
||||||
"txt",
|
"txt",
|
||||||
"ubrl",
|
"ubrl",
|
||||||
"ubrl6",
|
"ubrl6",
|
||||||
"uil",
|
"uil",
|
||||||
"uyvy",
|
"uyvy",
|
||||||
"vda",
|
"vda",
|
||||||
"vicar",
|
"vicar",
|
||||||
"vid",
|
"vid",
|
||||||
"viff",
|
"viff",
|
||||||
"vips",
|
"vips",
|
||||||
"vst",
|
"vst",
|
||||||
"wbmp",
|
"wbmp",
|
||||||
"webm",
|
"webm",
|
||||||
"webp",
|
"webp",
|
||||||
"wmv",
|
"wmv",
|
||||||
"wpg",
|
"wpg",
|
||||||
"xbm",
|
"xbm",
|
||||||
"xpm",
|
"xpm",
|
||||||
"xv",
|
"xv",
|
||||||
"yaml",
|
"yaml",
|
||||||
"ycbcr",
|
"ycbcr",
|
||||||
"ycbcra",
|
"ycbcra",
|
||||||
"yuv",
|
"yuv",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let outputArgs: string[] = [];
|
let outputArgs: string[] = [];
|
||||||
let inputArgs: string[] = [];
|
let inputArgs: string[] = [];
|
||||||
|
|
||||||
if (convertTo === "ico") {
|
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") {
|
if (fileType === "svg") {
|
||||||
// this might be a bit too much, but it works
|
// this might be a bit too much, but it works
|
||||||
inputArgs = ["-background", "none", "-density", "512"];
|
inputArgs = ["-background", "none", "-density", "512"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle EMF files specifically to avoid LibreOffice delegate issues
|
// Handle EMF files specifically to avoid LibreOffice delegate issues
|
||||||
if (fileType === "emf") {
|
if (fileType === "emf") {
|
||||||
// Use direct conversion without delegates for EMF files
|
// Use direct conversion without delegates for EMF files
|
||||||
inputArgs.push("-define", "emf:delegate=false", "-density", "300");
|
inputArgs.push("-define", "emf:delegate=false", "-density", "300");
|
||||||
outputArgs.push("-background", "white", "-alpha", "remove");
|
outputArgs.push("-background", "white", "-alpha", "remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile(
|
execFile(
|
||||||
"magick",
|
"magick",
|
||||||
[...inputArgs, filePath, ...outputArgs, targetPath],
|
[...inputArgs, filePath, ...outputArgs, targetPath],
|
||||||
(error, stdout, stderr) => {
|
(error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,56 +1,56 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
images: ["svg", "pdf", "eps", "ps", "wmf", "emf", "png"],
|
images: ["svg", "pdf", "eps", "ps", "wmf", "emf", "png"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
images: [
|
images: [
|
||||||
"dxf",
|
"dxf",
|
||||||
"emf",
|
"emf",
|
||||||
"eps",
|
"eps",
|
||||||
"fxg",
|
"fxg",
|
||||||
"gpl",
|
"gpl",
|
||||||
"hpgl",
|
"hpgl",
|
||||||
"html",
|
"html",
|
||||||
"odg",
|
"odg",
|
||||||
"pdf",
|
"pdf",
|
||||||
"png",
|
"png",
|
||||||
"pov",
|
"pov",
|
||||||
"ps",
|
"ps",
|
||||||
"sif",
|
"sif",
|
||||||
"svg",
|
"svg",
|
||||||
"svgz",
|
"svgz",
|
||||||
"tex",
|
"tex",
|
||||||
"wmf",
|
"wmf",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("inkscape", [filePath, "-o", targetPath], (error, stdout, stderr) => {
|
execFile("inkscape", [filePath, "-o", targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,38 +1,38 @@
|
|||||||
import { execFile as execFileOriginal } from "child_process";
|
import { execFile as execFileOriginal } from "child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
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: {
|
to: {
|
||||||
images: ["jpeg", "png", "y4m"],
|
images: ["jpeg", "png", "y4m"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("heif-convert", [filePath, targetPath], (error, stdout, stderr) => {
|
execFile("heif-convert", [filePath, targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,50 +1,50 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
// declare possible conversions
|
// declare possible conversions
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
jxl: ["jxl"],
|
jxl: ["jxl"],
|
||||||
images: ["apng", "exr", "gif", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"],
|
images: ["apng", "exr", "gif", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
jxl: ["apng", "exr", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"],
|
jxl: ["apng", "exr", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"],
|
||||||
images: ["jxl"],
|
images: ["jxl"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let tool = "";
|
let tool = "";
|
||||||
if (fileType === "jxl") {
|
if (fileType === "jxl") {
|
||||||
tool = "djxl";
|
tool = "djxl";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convertTo === "jxl") {
|
if (convertTo === "jxl") {
|
||||||
tool = "cjxl";
|
tool = "cjxl";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile(tool, [filePath, targetPath], (error, stdout, stderr) => {
|
execFile(tool, [filePath, targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,177 +1,177 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
text: [
|
text: [
|
||||||
"602",
|
"602",
|
||||||
"abw",
|
"abw",
|
||||||
"csv",
|
"csv",
|
||||||
"cwk",
|
"cwk",
|
||||||
"doc",
|
"doc",
|
||||||
"docm",
|
"docm",
|
||||||
"docx",
|
"docx",
|
||||||
"dot",
|
"dot",
|
||||||
"dotx",
|
"dotx",
|
||||||
"dotm",
|
"dotm",
|
||||||
"epub",
|
"epub",
|
||||||
"fb2",
|
"fb2",
|
||||||
"fodt",
|
"fodt",
|
||||||
"htm",
|
"htm",
|
||||||
"html",
|
"html",
|
||||||
"hwp",
|
"hwp",
|
||||||
"mcw",
|
"mcw",
|
||||||
"mw",
|
"mw",
|
||||||
"mwd",
|
"mwd",
|
||||||
"lwp",
|
"lwp",
|
||||||
"lrf",
|
"lrf",
|
||||||
"odt",
|
"odt",
|
||||||
"ott",
|
"ott",
|
||||||
"pages",
|
"pages",
|
||||||
"pdf",
|
"pdf",
|
||||||
"psw",
|
"psw",
|
||||||
"rtf",
|
"rtf",
|
||||||
"sdw",
|
"sdw",
|
||||||
"stw",
|
"stw",
|
||||||
"sxw",
|
"sxw",
|
||||||
"tab",
|
"tab",
|
||||||
"tsv",
|
"tsv",
|
||||||
"txt",
|
"txt",
|
||||||
"wn",
|
"wn",
|
||||||
"wpd",
|
"wpd",
|
||||||
"wps",
|
"wps",
|
||||||
"wpt",
|
"wpt",
|
||||||
"wri",
|
"wri",
|
||||||
"xhtml",
|
"xhtml",
|
||||||
"xml",
|
"xml",
|
||||||
"zabw",
|
"zabw",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
text: [
|
text: [
|
||||||
"csv",
|
"csv",
|
||||||
"doc",
|
"doc",
|
||||||
"docm",
|
"docm",
|
||||||
"docx",
|
"docx",
|
||||||
"dot",
|
"dot",
|
||||||
"dotx",
|
"dotx",
|
||||||
"dotm",
|
"dotm",
|
||||||
"epub",
|
"epub",
|
||||||
"fodt",
|
"fodt",
|
||||||
"htm",
|
"htm",
|
||||||
"html",
|
"html",
|
||||||
"odt",
|
"odt",
|
||||||
"ott",
|
"ott",
|
||||||
"pdf",
|
"pdf",
|
||||||
"rtf",
|
"rtf",
|
||||||
"tab",
|
"tab",
|
||||||
"tsv",
|
"tsv",
|
||||||
"txt",
|
"txt",
|
||||||
"wps",
|
"wps",
|
||||||
"wpt",
|
"wpt",
|
||||||
"xhtml",
|
"xhtml",
|
||||||
"xml",
|
"xml",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type FileCategories = "text" | "calc";
|
type FileCategories = "text" | "calc";
|
||||||
|
|
||||||
const filters: Record<FileCategories, Record<string, string>> = {
|
const filters: Record<FileCategories, Record<string, string>> = {
|
||||||
text: {
|
text: {
|
||||||
"602": "T602Document",
|
"602": "T602Document",
|
||||||
abw: "AbiWord",
|
abw: "AbiWord",
|
||||||
csv: "Text",
|
csv: "Text",
|
||||||
doc: "MS Word 97",
|
doc: "MS Word 97",
|
||||||
docm: "MS Word 2007 XML VBA",
|
docm: "MS Word 2007 XML VBA",
|
||||||
docx: "MS Word 2007 XML",
|
docx: "MS Word 2007 XML",
|
||||||
dot: "MS Word 97 Vorlage",
|
dot: "MS Word 97 Vorlage",
|
||||||
dotx: "MS Word 2007 XML Template",
|
dotx: "MS Word 2007 XML Template",
|
||||||
dotm: "MS Word 2007 XML Template",
|
dotm: "MS Word 2007 XML Template",
|
||||||
epub: "EPUB",
|
epub: "EPUB",
|
||||||
fb2: "Fictionbook 2",
|
fb2: "Fictionbook 2",
|
||||||
fodt: "OpenDocument Text Flat XML",
|
fodt: "OpenDocument Text Flat XML",
|
||||||
htm: "HTML (StarWriter)",
|
htm: "HTML (StarWriter)",
|
||||||
html: "HTML (StarWriter)",
|
html: "HTML (StarWriter)",
|
||||||
hwp: "writer_MIZI_Hwp_97",
|
hwp: "writer_MIZI_Hwp_97",
|
||||||
mcw: "MacWrite",
|
mcw: "MacWrite",
|
||||||
mw: "MacWrite",
|
mw: "MacWrite",
|
||||||
mwd: "Mariner_Write",
|
mwd: "Mariner_Write",
|
||||||
lwp: "LotusWordPro",
|
lwp: "LotusWordPro",
|
||||||
lrf: "BroadBand eBook",
|
lrf: "BroadBand eBook",
|
||||||
odt: "writer8",
|
odt: "writer8",
|
||||||
ott: "writer8_template",
|
ott: "writer8_template",
|
||||||
pages: "Apple Pages",
|
pages: "Apple Pages",
|
||||||
// pdf: "writer_pdf_import",
|
// pdf: "writer_pdf_import",
|
||||||
psw: "PocketWord File",
|
psw: "PocketWord File",
|
||||||
rtf: "Rich Text Format",
|
rtf: "Rich Text Format",
|
||||||
sdw: "StarOffice_Writer",
|
sdw: "StarOffice_Writer",
|
||||||
stw: "writer_StarOffice_XML_Writer_Template",
|
stw: "writer_StarOffice_XML_Writer_Template",
|
||||||
sxw: "StarOffice XML (Writer)",
|
sxw: "StarOffice XML (Writer)",
|
||||||
tab: "Text",
|
tab: "Text",
|
||||||
tsv: "Text",
|
tsv: "Text",
|
||||||
txt: "Text",
|
txt: "Text",
|
||||||
wn: "WriteNow",
|
wn: "WriteNow",
|
||||||
wpd: "WordPerfect",
|
wpd: "WordPerfect",
|
||||||
wps: "MS Word 97",
|
wps: "MS Word 97",
|
||||||
wpt: "MS Word 97 Vorlage",
|
wpt: "MS Word 97 Vorlage",
|
||||||
wri: "MS_Write",
|
wri: "MS_Write",
|
||||||
xhtml: "HTML (StarWriter)",
|
xhtml: "HTML (StarWriter)",
|
||||||
xml: "OpenDocument Text Flat XML",
|
xml: "OpenDocument Text Flat XML",
|
||||||
zabw: "AbiWord",
|
zabw: "AbiWord",
|
||||||
},
|
},
|
||||||
calc: {},
|
calc: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFilters = (fileType: string, converto: string) => {
|
const getFilters = (fileType: string, converto: string) => {
|
||||||
if (fileType in filters.text && converto in filters.text) {
|
if (fileType in filters.text && converto in filters.text) {
|
||||||
return [filters.text[fileType], filters.text[converto]];
|
return [filters.text[fileType], filters.text[converto]];
|
||||||
} else if (fileType in filters.calc && converto in filters.calc) {
|
} else if (fileType in filters.calc && converto in filters.calc) {
|
||||||
return [filters.calc[fileType], filters.calc[converto]];
|
return [filters.calc[fileType], filters.calc[converto]];
|
||||||
}
|
}
|
||||||
return [null, null];
|
return [null, null];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal,
|
execFile: ExecFileFn = execFileOriginal,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "") ?? targetPath;
|
const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "") ?? targetPath;
|
||||||
|
|
||||||
// Build arguments array
|
// Build arguments array
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
args.push("--headless");
|
args.push("--headless");
|
||||||
const [inFilter, outFilter] = getFilters(fileType, convertTo);
|
const [inFilter, outFilter] = getFilters(fileType, convertTo);
|
||||||
|
|
||||||
if (inFilter) {
|
if (inFilter) {
|
||||||
args.push(`--infilter="${inFilter}"`);
|
args.push(`--infilter="${inFilter}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outFilter) {
|
if (outFilter) {
|
||||||
args.push("--convert-to", `${convertTo}:${outFilter}`, "--outdir", outputPath, filePath);
|
args.push("--convert-to", `${convertTo}:${outFilter}`, "--outdir", outputPath, filePath);
|
||||||
} else {
|
} else {
|
||||||
args.push("--convert-to", convertTo, "--outdir", outputPath, filePath);
|
args.push("--convert-to", convertTo, "--outdir", outputPath, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("soffice", args, (error, stdout, stderr) => {
|
execFile("soffice", args, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,364 +1,364 @@
|
|||||||
import { Cookie } from "elysia";
|
import { Cookie } from "elysia";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { MAX_CONVERT_PROCESS } from "../helpers/env";
|
import { MAX_CONVERT_PROCESS } from "../helpers/env";
|
||||||
import { normalizeFiletype, normalizeOutputFiletype } from "../helpers/normalizeFiletype";
|
import { normalizeFiletype, normalizeOutputFiletype } from "../helpers/normalizeFiletype";
|
||||||
import { convert as convertassimp, properties as propertiesassimp } from "./assimp";
|
import { convert as convertassimp, properties as propertiesassimp } from "./assimp";
|
||||||
import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
|
import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
|
||||||
import { convert as convertDasel, properties as propertiesDasel } from "./dasel";
|
import { convert as convertDasel, properties as propertiesDasel } from "./dasel";
|
||||||
import { convert as convertDvisvgm, properties as propertiesDvisvgm } from "./dvisvgm";
|
import { convert as convertDvisvgm, properties as propertiesDvisvgm } from "./dvisvgm";
|
||||||
import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg";
|
import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg";
|
||||||
import {
|
import {
|
||||||
convert as convertGraphicsmagick,
|
convert as convertGraphicsmagick,
|
||||||
properties as propertiesGraphicsmagick,
|
properties as propertiesGraphicsmagick,
|
||||||
} from "./graphicsmagick";
|
} from "./graphicsmagick";
|
||||||
import { convert as convertImagemagick, properties as propertiesImagemagick } from "./imagemagick";
|
import { convert as convertImagemagick, properties as propertiesImagemagick } from "./imagemagick";
|
||||||
import { convert as convertInkscape, properties as propertiesInkscape } from "./inkscape";
|
import { convert as convertInkscape, properties as propertiesInkscape } from "./inkscape";
|
||||||
import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif";
|
import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif";
|
||||||
import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl";
|
import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl";
|
||||||
import { convert as convertLibreOffice, properties as propertiesLibreOffice } from "./libreoffice";
|
import { convert as convertLibreOffice, properties as propertiesLibreOffice } from "./libreoffice";
|
||||||
import { convert as convertMsgconvert, properties as propertiesMsgconvert } from "./msgconvert";
|
import { convert as convertMsgconvert, properties as propertiesMsgconvert } from "./msgconvert";
|
||||||
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
|
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
|
||||||
import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace";
|
import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace";
|
||||||
import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
|
import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
|
||||||
import { convert as convertImage, properties as propertiesImage } from "./vips";
|
import { convert as convertImage, properties as propertiesImage } from "./vips";
|
||||||
import { convert as convertVtracer, properties as propertiesVtracer } from "./vtracer";
|
import { convert as convertVtracer, properties as propertiesVtracer } from "./vtracer";
|
||||||
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
|
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
|
||||||
|
|
||||||
// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
|
// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
|
||||||
|
|
||||||
const properties: Record<
|
const properties: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
properties: {
|
properties: {
|
||||||
from: Record<string, string[]>;
|
from: Record<string, string[]>;
|
||||||
to: Record<string, string[]>;
|
to: Record<string, string[]>;
|
||||||
options?: Record<
|
options?: Record<
|
||||||
string,
|
string,
|
||||||
Record<
|
Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
description: string;
|
description: string;
|
||||||
type: string;
|
type: string;
|
||||||
default: number;
|
default: number;
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
converter: (
|
converter: (
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
|
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
) => unknown;
|
) => unknown;
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
// Prioritize Inkscape for EMF files as it handles them better than ImageMagick
|
// Prioritize Inkscape for EMF files as it handles them better than ImageMagick
|
||||||
inkscape: {
|
inkscape: {
|
||||||
properties: propertiesInkscape,
|
properties: propertiesInkscape,
|
||||||
converter: convertInkscape,
|
converter: convertInkscape,
|
||||||
},
|
},
|
||||||
libjxl: {
|
libjxl: {
|
||||||
properties: propertiesLibjxl,
|
properties: propertiesLibjxl,
|
||||||
converter: convertLibjxl,
|
converter: convertLibjxl,
|
||||||
},
|
},
|
||||||
resvg: {
|
resvg: {
|
||||||
properties: propertiesresvg,
|
properties: propertiesresvg,
|
||||||
converter: convertresvg,
|
converter: convertresvg,
|
||||||
},
|
},
|
||||||
vips: {
|
vips: {
|
||||||
properties: propertiesImage,
|
properties: propertiesImage,
|
||||||
converter: convertImage,
|
converter: convertImage,
|
||||||
},
|
},
|
||||||
libheif: {
|
libheif: {
|
||||||
properties: propertiesLibheif,
|
properties: propertiesLibheif,
|
||||||
converter: convertLibheif,
|
converter: convertLibheif,
|
||||||
},
|
},
|
||||||
xelatex: {
|
xelatex: {
|
||||||
properties: propertiesxelatex,
|
properties: propertiesxelatex,
|
||||||
converter: convertxelatex,
|
converter: convertxelatex,
|
||||||
},
|
},
|
||||||
calibre: {
|
calibre: {
|
||||||
properties: propertiesCalibre,
|
properties: propertiesCalibre,
|
||||||
converter: convertCalibre,
|
converter: convertCalibre,
|
||||||
},
|
},
|
||||||
dasel: {
|
dasel: {
|
||||||
properties: propertiesDasel,
|
properties: propertiesDasel,
|
||||||
converter: convertDasel,
|
converter: convertDasel,
|
||||||
},
|
},
|
||||||
libreoffice: {
|
libreoffice: {
|
||||||
properties: propertiesLibreOffice,
|
properties: propertiesLibreOffice,
|
||||||
converter: convertLibreOffice,
|
converter: convertLibreOffice,
|
||||||
},
|
},
|
||||||
pandoc: {
|
pandoc: {
|
||||||
properties: propertiesPandoc,
|
properties: propertiesPandoc,
|
||||||
converter: convertPandoc,
|
converter: convertPandoc,
|
||||||
},
|
},
|
||||||
msgconvert: {
|
msgconvert: {
|
||||||
properties: propertiesMsgconvert,
|
properties: propertiesMsgconvert,
|
||||||
converter: convertMsgconvert,
|
converter: convertMsgconvert,
|
||||||
},
|
},
|
||||||
dvisvgm: {
|
dvisvgm: {
|
||||||
properties: propertiesDvisvgm,
|
properties: propertiesDvisvgm,
|
||||||
converter: convertDvisvgm,
|
converter: convertDvisvgm,
|
||||||
},
|
},
|
||||||
imagemagick: {
|
imagemagick: {
|
||||||
properties: propertiesImagemagick,
|
properties: propertiesImagemagick,
|
||||||
converter: convertImagemagick,
|
converter: convertImagemagick,
|
||||||
},
|
},
|
||||||
graphicsmagick: {
|
graphicsmagick: {
|
||||||
properties: propertiesGraphicsmagick,
|
properties: propertiesGraphicsmagick,
|
||||||
converter: convertGraphicsmagick,
|
converter: convertGraphicsmagick,
|
||||||
},
|
},
|
||||||
assimp: {
|
assimp: {
|
||||||
properties: propertiesassimp,
|
properties: propertiesassimp,
|
||||||
converter: convertassimp,
|
converter: convertassimp,
|
||||||
},
|
},
|
||||||
ffmpeg: {
|
ffmpeg: {
|
||||||
properties: propertiesFFmpeg,
|
properties: propertiesFFmpeg,
|
||||||
converter: convertFFmpeg,
|
converter: convertFFmpeg,
|
||||||
},
|
},
|
||||||
potrace: {
|
potrace: {
|
||||||
properties: propertiesPotrace,
|
properties: propertiesPotrace,
|
||||||
converter: convertPotrace,
|
converter: convertPotrace,
|
||||||
},
|
},
|
||||||
vtracer: {
|
vtracer: {
|
||||||
properties: propertiesVtracer,
|
properties: propertiesVtracer,
|
||||||
converter: convertVtracer,
|
converter: convertVtracer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function chunks<T>(arr: T[], size: number): T[][] {
|
function chunks<T>(arr: T[], size: number): T[][] {
|
||||||
if (size <= 0) {
|
if (size <= 0) {
|
||||||
return [arr];
|
return [arr];
|
||||||
}
|
}
|
||||||
return Array.from({ length: Math.ceil(arr.length / size) }, (_: T, i: number) =>
|
return Array.from({ length: Math.ceil(arr.length / size) }, (_: T, i: number) =>
|
||||||
arr.slice(i * size, i * size + size),
|
arr.slice(i * size, i * size + size),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleConvert(
|
export async function handleConvert(
|
||||||
fileNames: string[],
|
fileNames: string[],
|
||||||
userUploadsDir: string,
|
userUploadsDir: string,
|
||||||
userOutputDir: string,
|
userOutputDir: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
converterName: string,
|
converterName: string,
|
||||||
jobId: Cookie<string | undefined>,
|
jobId: Cookie<string | undefined>,
|
||||||
) {
|
) {
|
||||||
const query = db.query(
|
const query = db.query(
|
||||||
"INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)",
|
"INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)",
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const chunk of chunks(fileNames, MAX_CONVERT_PROCESS)) {
|
for (const chunk of chunks(fileNames, MAX_CONVERT_PROCESS)) {
|
||||||
const toProcess: Promise<string>[] = [];
|
const toProcess: Promise<string>[] = [];
|
||||||
for (const fileName of chunk) {
|
for (const fileName of chunk) {
|
||||||
const filePath = `${userUploadsDir}${fileName}`;
|
const filePath = `${userUploadsDir}${fileName}`;
|
||||||
const fileTypeOrig = fileName.split(".").pop() ?? "";
|
const fileTypeOrig = fileName.split(".").pop() ?? "";
|
||||||
const fileType = normalizeFiletype(fileTypeOrig);
|
const fileType = normalizeFiletype(fileTypeOrig);
|
||||||
const newFileExt = normalizeOutputFiletype(convertTo);
|
const newFileExt = normalizeOutputFiletype(convertTo);
|
||||||
const newFileName = fileName.replace(
|
const newFileName = fileName.replace(
|
||||||
new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
|
new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
|
||||||
newFileExt,
|
newFileExt,
|
||||||
);
|
);
|
||||||
const targetPath = `${userOutputDir}${newFileName}`;
|
const targetPath = `${userOutputDir}${newFileName}`;
|
||||||
toProcess.push(
|
toProcess.push(
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
mainConverter(filePath, fileType, convertTo, targetPath, {}, converterName)
|
mainConverter(filePath, fileType, convertTo, targetPath, {}, converterName)
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (jobId.value) {
|
if (jobId.value) {
|
||||||
query.run(jobId.value, fileName, newFileName, r);
|
query.run(jobId.value, fileName, newFileName, r);
|
||||||
}
|
}
|
||||||
resolve(r);
|
resolve(r);
|
||||||
})
|
})
|
||||||
.catch((c) => reject(c));
|
.catch((c) => reject(c));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await Promise.all(toProcess);
|
await Promise.all(toProcess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mainConverter(
|
async function mainConverter(
|
||||||
inputFilePath: string,
|
inputFilePath: string,
|
||||||
fileTypeOriginal: string,
|
fileTypeOriginal: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
converterName?: string,
|
converterName?: string,
|
||||||
) {
|
) {
|
||||||
const fileType = normalizeFiletype(fileTypeOriginal);
|
const fileType = normalizeFiletype(fileTypeOriginal);
|
||||||
|
|
||||||
let converterFunc: (typeof properties)["libjxl"]["converter"] | undefined;
|
let converterFunc: (typeof properties)["libjxl"]["converter"] | undefined;
|
||||||
|
|
||||||
if (converterName) {
|
if (converterName) {
|
||||||
converterFunc = properties[converterName]?.converter;
|
converterFunc = properties[converterName]?.converter;
|
||||||
} else {
|
} else {
|
||||||
// Iterate over each converter in properties
|
// Iterate over each converter in properties
|
||||||
for (converterName in properties) {
|
for (converterName in properties) {
|
||||||
const converterObj = properties[converterName];
|
const converterObj = properties[converterName];
|
||||||
|
|
||||||
if (!converterObj) {
|
if (!converterObj) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in converterObj.properties.from) {
|
for (const key in converterObj.properties.from) {
|
||||||
if (
|
if (
|
||||||
converterObj?.properties?.from[key]?.includes(fileType) &&
|
converterObj?.properties?.from[key]?.includes(fileType) &&
|
||||||
converterObj?.properties?.to[key]?.includes(convertTo)
|
converterObj?.properties?.to[key]?.includes(convertTo)
|
||||||
) {
|
) {
|
||||||
converterFunc = converterObj.converter;
|
converterFunc = converterObj.converter;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!converterFunc) {
|
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";
|
return "File type not supported";
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await converterFunc(inputFilePath, fileType, convertTo, targetPath, options);
|
const result = await converterFunc(inputFilePath, fileType, convertTo, targetPath, options);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
|
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (typeof result === "string") {
|
if (typeof result === "string") {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Done";
|
return "Done";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`Failed to convert ${inputFilePath} from ${fileType} to ${convertTo} using ${converterName}.`,
|
`Failed to convert ${inputFilePath} from ${fileType} to ${convertTo} using ${converterName}.`,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
return "Failed, check logs";
|
return "Failed, check logs";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTargets: Record<string, Record<string, string[]>> = {};
|
const possibleTargets: Record<string, Record<string, string[]>> = {};
|
||||||
|
|
||||||
for (const converterName in properties) {
|
for (const converterName in properties) {
|
||||||
const converterProperties = properties[converterName]?.properties;
|
const converterProperties = properties[converterName]?.properties;
|
||||||
|
|
||||||
if (!converterProperties) {
|
if (!converterProperties) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in converterProperties.from) {
|
for (const key in converterProperties.from) {
|
||||||
if (converterProperties.from[key] === undefined) {
|
if (converterProperties.from[key] === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const extension of converterProperties.from[key] ?? []) {
|
for (const extension of converterProperties.from[key] ?? []) {
|
||||||
if (!possibleTargets[extension]) {
|
if (!possibleTargets[extension]) {
|
||||||
possibleTargets[extension] = {};
|
possibleTargets[extension] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
possibleTargets[extension][converterName] = converterProperties.to[key] || [];
|
possibleTargets[extension][converterName] = converterProperties.to[key] || [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPossibleTargets = (from: string): Record<string, string[]> => {
|
export const getPossibleTargets = (from: string): Record<string, string[]> => {
|
||||||
const fromClean = normalizeFiletype(from);
|
const fromClean = normalizeFiletype(from);
|
||||||
|
|
||||||
return possibleTargets[fromClean] || {};
|
return possibleTargets[fromClean] || {};
|
||||||
};
|
};
|
||||||
|
|
||||||
const possibleInputs: string[] = [];
|
const possibleInputs: string[] = [];
|
||||||
for (const converterName in properties) {
|
for (const converterName in properties) {
|
||||||
const converterProperties = properties[converterName]?.properties;
|
const converterProperties = properties[converterName]?.properties;
|
||||||
|
|
||||||
if (!converterProperties) {
|
if (!converterProperties) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in converterProperties.from) {
|
for (const key in converterProperties.from) {
|
||||||
for (const extension of converterProperties.from[key] ?? []) {
|
for (const extension of converterProperties.from[key] ?? []) {
|
||||||
if (!possibleInputs.includes(extension)) {
|
if (!possibleInputs.includes(extension)) {
|
||||||
possibleInputs.push(extension);
|
possibleInputs.push(extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
possibleInputs.sort();
|
possibleInputs.sort();
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const getPossibleInputs = () => {
|
const getPossibleInputs = () => {
|
||||||
return possibleInputs;
|
return possibleInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const allTargets: Record<string, string[]> = {};
|
const allTargets: Record<string, string[]> = {};
|
||||||
|
|
||||||
for (const converterName in properties) {
|
for (const converterName in properties) {
|
||||||
const converterProperties = properties[converterName]?.properties;
|
const converterProperties = properties[converterName]?.properties;
|
||||||
|
|
||||||
if (!converterProperties) {
|
if (!converterProperties) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in converterProperties.to) {
|
for (const key in converterProperties.to) {
|
||||||
if (allTargets[converterName]) {
|
if (allTargets[converterName]) {
|
||||||
allTargets[converterName].push(...(converterProperties.to[key] || []));
|
allTargets[converterName].push(...(converterProperties.to[key] || []));
|
||||||
} else {
|
} else {
|
||||||
allTargets[converterName] = converterProperties.to[key] || [];
|
allTargets[converterName] = converterProperties.to[key] || [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAllTargets = () => {
|
export const getAllTargets = () => {
|
||||||
return allTargets;
|
return allTargets;
|
||||||
};
|
};
|
||||||
|
|
||||||
const allInputs: Record<string, string[]> = {};
|
const allInputs: Record<string, string[]> = {};
|
||||||
for (const converterName in properties) {
|
for (const converterName in properties) {
|
||||||
const converterProperties = properties[converterName]?.properties;
|
const converterProperties = properties[converterName]?.properties;
|
||||||
|
|
||||||
if (!converterProperties) {
|
if (!converterProperties) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in converterProperties.from) {
|
for (const key in converterProperties.from) {
|
||||||
if (allInputs[converterName]) {
|
if (allInputs[converterName]) {
|
||||||
allInputs[converterName].push(...(converterProperties.from[key] || []));
|
allInputs[converterName].push(...(converterProperties.from[key] || []));
|
||||||
} else {
|
} else {
|
||||||
allInputs[converterName] = converterProperties.from[key] || [];
|
allInputs[converterName] = converterProperties.from[key] || [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAllInputs = (converter: string) => {
|
export const getAllInputs = (converter: string) => {
|
||||||
return allInputs[converter] || [];
|
return allInputs[converter] || [];
|
||||||
};
|
};
|
||||||
|
|
||||||
// // count the number of unique formats
|
// // count the number of unique formats
|
||||||
// const uniqueFormats = new Set();
|
// const uniqueFormats = new Set();
|
||||||
|
|
||||||
// for (const converterName in properties) {
|
// for (const converterName in properties) {
|
||||||
// const converterProperties = properties[converterName]?.properties;
|
// const converterProperties = properties[converterName]?.properties;
|
||||||
|
|
||||||
// if (!converterProperties) {
|
// if (!converterProperties) {
|
||||||
// continue;
|
// continue;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// for (const key in converterProperties.from) {
|
// for (const key in converterProperties.from) {
|
||||||
// for (const extension of converterProperties.from[key] ?? []) {
|
// for (const extension of converterProperties.from[key] ?? []) {
|
||||||
// uniqueFormats.add(extension);
|
// uniqueFormats.add(extension);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// for (const key in converterProperties.to) {
|
// for (const key in converterProperties.to) {
|
||||||
// for (const extension of converterProperties.to[key] ?? []) {
|
// for (const extension of converterProperties.to[key] ?? []) {
|
||||||
// uniqueFormats.add(extension);
|
// uniqueFormats.add(extension);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // print the number of unique Inputs and Outputs
|
// // print the number of unique Inputs and Outputs
|
||||||
// console.log(`Unique Formats: ${uniqueFormats.size}`);
|
// console.log(`Unique Formats: ${uniqueFormats.size}`);
|
||||||
|
@@ -1,52 +1,52 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
email: ["msg"],
|
email: ["msg"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
email: ["eml"],
|
email: ["eml"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal,
|
execFile: ExecFileFn = execFileOriginal,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (fileType === "msg" && convertTo === "eml") {
|
if (fileType === "msg" && convertTo === "eml") {
|
||||||
// Convert MSG to EML using msgconvert
|
// Convert MSG to EML using msgconvert
|
||||||
// msgconvert will output to the same directory as the input file with .eml extension
|
// msgconvert will output to the same directory as the input file with .eml extension
|
||||||
// We need to use --outfile to specify the target path
|
// We need to use --outfile to specify the target path
|
||||||
const args = ["--outfile", targetPath, filePath];
|
const args = ["--outfile", targetPath, filePath];
|
||||||
|
|
||||||
execFile("msgconvert", args, (error, stdout, stderr) => {
|
execFile("msgconvert", args, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(new Error(`msgconvert failed: ${error.message}`));
|
reject(new Error(`msgconvert failed: ${error.message}`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
// Log sanitized stderr to avoid exposing sensitive paths
|
// Log sanitized stderr to avoid exposing sensitive paths
|
||||||
const sanitizedStderr = stderr.replace(/(\/[^\s]+)/g, "[REDACTED_PATH]");
|
const sanitizedStderr = stderr.replace(/(\/[^\s]+)/g, "[REDACTED_PATH]");
|
||||||
console.warn(
|
console.warn(
|
||||||
`msgconvert stderr: ${sanitizedStderr.length > 200 ? sanitizedStderr.slice(0, 200) + "..." : sanitizedStderr}`,
|
`msgconvert stderr: ${sanitizedStderr.length > 200 ? sanitizedStderr.slice(0, 200) + "..." : sanitizedStderr}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(targetPath);
|
resolve(targetPath);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
reject(
|
reject(
|
||||||
new Error(
|
new Error(
|
||||||
`Unsupported conversion from ${fileType} to ${convertTo}. Only MSG to EML conversion is currently supported.`,
|
`Unsupported conversion from ${fileType} to ${convertTo}. Only MSG to EML conversion is currently supported.`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,163 +1,163 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
text: [
|
text: [
|
||||||
"textile",
|
"textile",
|
||||||
"tikiwiki",
|
"tikiwiki",
|
||||||
"tsv",
|
"tsv",
|
||||||
"twiki",
|
"twiki",
|
||||||
"typst",
|
"typst",
|
||||||
"vimwiki",
|
"vimwiki",
|
||||||
"biblatex",
|
"biblatex",
|
||||||
"bibtex",
|
"bibtex",
|
||||||
"bits",
|
"bits",
|
||||||
"commonmark",
|
"commonmark",
|
||||||
"commonmark_x",
|
"commonmark_x",
|
||||||
"creole",
|
"creole",
|
||||||
"csljson",
|
"csljson",
|
||||||
"csv",
|
"csv",
|
||||||
"djot",
|
"djot",
|
||||||
"docbook",
|
"docbook",
|
||||||
"docx",
|
"docx",
|
||||||
"dokuwiki",
|
"dokuwiki",
|
||||||
"endnotexml",
|
"endnotexml",
|
||||||
"epub",
|
"epub",
|
||||||
"fb2",
|
"fb2",
|
||||||
"gfm",
|
"gfm",
|
||||||
"haddock",
|
"haddock",
|
||||||
"html",
|
"html",
|
||||||
"ipynb",
|
"ipynb",
|
||||||
"jats",
|
"jats",
|
||||||
"jira",
|
"jira",
|
||||||
"json",
|
"json",
|
||||||
"latex",
|
"latex",
|
||||||
"man",
|
"man",
|
||||||
"markdown",
|
"markdown",
|
||||||
"markdown_mmd",
|
"markdown_mmd",
|
||||||
"markdown_phpextra",
|
"markdown_phpextra",
|
||||||
"markdown_strict",
|
"markdown_strict",
|
||||||
"mediawiki",
|
"mediawiki",
|
||||||
"muse",
|
"muse",
|
||||||
"pandoc native",
|
"pandoc native",
|
||||||
"opml",
|
"opml",
|
||||||
"org",
|
"org",
|
||||||
"ris",
|
"ris",
|
||||||
"rst",
|
"rst",
|
||||||
"rtf",
|
"rtf",
|
||||||
"t2t",
|
"t2t",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
text: [
|
text: [
|
||||||
"tei",
|
"tei",
|
||||||
"texinfo",
|
"texinfo",
|
||||||
"textile",
|
"textile",
|
||||||
"typst",
|
"typst",
|
||||||
"xwiki",
|
"xwiki",
|
||||||
"zimwiki",
|
"zimwiki",
|
||||||
"asciidoc",
|
"asciidoc",
|
||||||
"asciidoc_legacy",
|
"asciidoc_legacy",
|
||||||
"asciidoctor",
|
"asciidoctor",
|
||||||
"beamer",
|
"beamer",
|
||||||
"biblatex",
|
"biblatex",
|
||||||
"bibtex",
|
"bibtex",
|
||||||
"chunkedhtml",
|
"chunkedhtml",
|
||||||
"commonmark",
|
"commonmark",
|
||||||
"commonmark_x",
|
"commonmark_x",
|
||||||
"context",
|
"context",
|
||||||
"csljson",
|
"csljson",
|
||||||
"djot",
|
"djot",
|
||||||
"docbook",
|
"docbook",
|
||||||
"docbook4",
|
"docbook4",
|
||||||
"docbook5",
|
"docbook5",
|
||||||
"docx",
|
"docx",
|
||||||
"dokuwiki",
|
"dokuwiki",
|
||||||
"dzslides",
|
"dzslides",
|
||||||
"epub",
|
"epub",
|
||||||
"epub2",
|
"epub2",
|
||||||
"epub3",
|
"epub3",
|
||||||
"fb2",
|
"fb2",
|
||||||
"gfm",
|
"gfm",
|
||||||
"haddock",
|
"haddock",
|
||||||
"html",
|
"html",
|
||||||
"html4",
|
"html4",
|
||||||
"html5",
|
"html5",
|
||||||
"icml",
|
"icml",
|
||||||
"ipynb",
|
"ipynb",
|
||||||
"jats",
|
"jats",
|
||||||
"jats_archiving",
|
"jats_archiving",
|
||||||
"jats_articleauthoring",
|
"jats_articleauthoring",
|
||||||
"jats_publishing",
|
"jats_publishing",
|
||||||
"jira",
|
"jira",
|
||||||
"json",
|
"json",
|
||||||
"latex",
|
"latex",
|
||||||
"man",
|
"man",
|
||||||
"markdown",
|
"markdown",
|
||||||
"markdown_mmd",
|
"markdown_mmd",
|
||||||
"markdown_phpextra",
|
"markdown_phpextra",
|
||||||
"markdown_strict",
|
"markdown_strict",
|
||||||
"markua",
|
"markua",
|
||||||
"mediawiki",
|
"mediawiki",
|
||||||
"ms",
|
"ms",
|
||||||
"muse",
|
"muse",
|
||||||
"pandoc native",
|
"pandoc native",
|
||||||
"odt",
|
"odt",
|
||||||
"opendocument",
|
"opendocument",
|
||||||
"opml",
|
"opml",
|
||||||
"org",
|
"org",
|
||||||
"pdf",
|
"pdf",
|
||||||
"plain",
|
"plain",
|
||||||
"pptx",
|
"pptx",
|
||||||
"revealjs",
|
"revealjs",
|
||||||
"rst",
|
"rst",
|
||||||
"rtf",
|
"rtf",
|
||||||
"s5",
|
"s5",
|
||||||
"slideous",
|
"slideous",
|
||||||
"slidy",
|
"slidy",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal,
|
execFile: ExecFileFn = execFileOriginal,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// set xelatex here
|
// set xelatex here
|
||||||
const xelatex = ["pdf", "latex"];
|
const xelatex = ["pdf", "latex"];
|
||||||
|
|
||||||
// Build arguments array
|
// Build arguments array
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
|
|
||||||
if (xelatex.includes(convertTo)) {
|
if (xelatex.includes(convertTo)) {
|
||||||
args.push("--pdf-engine=xelatex");
|
args.push("--pdf-engine=xelatex");
|
||||||
}
|
}
|
||||||
|
|
||||||
args.push(filePath);
|
args.push(filePath);
|
||||||
args.push("-f", fileType);
|
args.push("-f", fileType);
|
||||||
args.push("-t", convertTo);
|
args.push("-t", convertTo);
|
||||||
args.push("-o", targetPath);
|
args.push("-o", targetPath);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("pandoc", args, (error, stdout, stderr) => {
|
execFile("pandoc", args, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,50 +1,50 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
images: ["pnm", "pbm", "pgm", "bmp"],
|
images: ["pnm", "pbm", "pgm", "bmp"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
images: [
|
images: [
|
||||||
"svg",
|
"svg",
|
||||||
"pdf",
|
"pdf",
|
||||||
"pdfpage",
|
"pdfpage",
|
||||||
"eps",
|
"eps",
|
||||||
"postscript",
|
"postscript",
|
||||||
"ps",
|
"ps",
|
||||||
"dxf",
|
"dxf",
|
||||||
"geojson",
|
"geojson",
|
||||||
"pgm",
|
"pgm",
|
||||||
"gimppath",
|
"gimppath",
|
||||||
"xfig",
|
"xfig",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => {
|
execFile("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,38 +1,38 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
images: ["svg"],
|
images: ["svg"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
images: ["png"],
|
images: ["png"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => {
|
execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
import { ExecFileOptions } from "child_process";
|
import { ExecFileOptions } from "child_process";
|
||||||
|
|
||||||
export type ExecFileFn = (
|
export type ExecFileFn = (
|
||||||
cmd: string,
|
cmd: string,
|
||||||
args: string[],
|
args: string[],
|
||||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||||
options?: ExecFileOptions,
|
options?: ExecFileOptions,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export type ConvertFnWithExecFile = (
|
export type ConvertFnWithExecFile = (
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options: unknown,
|
options: unknown,
|
||||||
execFileOverride?: ExecFileFn,
|
execFileOverride?: ExecFileFn,
|
||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
|
@@ -1,139 +1,139 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
// declare possible conversions
|
// declare possible conversions
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
images: [
|
images: [
|
||||||
"avif",
|
"avif",
|
||||||
"bif",
|
"bif",
|
||||||
"csv",
|
"csv",
|
||||||
"exr",
|
"exr",
|
||||||
"fits",
|
"fits",
|
||||||
"gif",
|
"gif",
|
||||||
"hdr.gz",
|
"hdr.gz",
|
||||||
"hdr",
|
"hdr",
|
||||||
"heic",
|
"heic",
|
||||||
"heif",
|
"heif",
|
||||||
"img.gz",
|
"img.gz",
|
||||||
"img",
|
"img",
|
||||||
"j2c",
|
"j2c",
|
||||||
"j2k",
|
"j2k",
|
||||||
"jp2",
|
"jp2",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"jpx",
|
"jpx",
|
||||||
"jxl",
|
"jxl",
|
||||||
"mat",
|
"mat",
|
||||||
"mrxs",
|
"mrxs",
|
||||||
"ndpi",
|
"ndpi",
|
||||||
"nia.gz",
|
"nia.gz",
|
||||||
"nia",
|
"nia",
|
||||||
"nii.gz",
|
"nii.gz",
|
||||||
"nii",
|
"nii",
|
||||||
"pdf",
|
"pdf",
|
||||||
"pfm",
|
"pfm",
|
||||||
"pgm",
|
"pgm",
|
||||||
"pic",
|
"pic",
|
||||||
"png",
|
"png",
|
||||||
"ppm",
|
"ppm",
|
||||||
"raw",
|
"raw",
|
||||||
"scn",
|
"scn",
|
||||||
"svg",
|
"svg",
|
||||||
"svs",
|
"svs",
|
||||||
"svslide",
|
"svslide",
|
||||||
"szi",
|
"szi",
|
||||||
"tif",
|
"tif",
|
||||||
"tiff",
|
"tiff",
|
||||||
"v",
|
"v",
|
||||||
"vips",
|
"vips",
|
||||||
"vms",
|
"vms",
|
||||||
"vmu",
|
"vmu",
|
||||||
"webp",
|
"webp",
|
||||||
"zip",
|
"zip",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
images: [
|
images: [
|
||||||
"avif",
|
"avif",
|
||||||
"dzi",
|
"dzi",
|
||||||
"fits",
|
"fits",
|
||||||
"gif",
|
"gif",
|
||||||
"hdr.gz",
|
"hdr.gz",
|
||||||
"heic",
|
"heic",
|
||||||
"heif",
|
"heif",
|
||||||
"img.gz",
|
"img.gz",
|
||||||
"j2c",
|
"j2c",
|
||||||
"j2k",
|
"j2k",
|
||||||
"jp2",
|
"jp2",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"jpx",
|
"jpx",
|
||||||
"jxl",
|
"jxl",
|
||||||
"mat",
|
"mat",
|
||||||
"nia.gz",
|
"nia.gz",
|
||||||
"nia",
|
"nia",
|
||||||
"nii.gz",
|
"nii.gz",
|
||||||
"nii",
|
"nii",
|
||||||
"png",
|
"png",
|
||||||
"tiff",
|
"tiff",
|
||||||
"vips",
|
"vips",
|
||||||
"webp",
|
"webp",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
svg: {
|
svg: {
|
||||||
scale: {
|
scale: {
|
||||||
description: "Scale the image up or down",
|
description: "Scale the image up or down",
|
||||||
type: "number",
|
type: "number",
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal,
|
execFile: ExecFileFn = execFileOriginal,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// if (fileType === "svg") {
|
// if (fileType === "svg") {
|
||||||
// const scale = options.scale || 1;
|
// const scale = options.scale || 1;
|
||||||
// const metadata = await sharp(filePath).metadata();
|
// const metadata = await sharp(filePath).metadata();
|
||||||
|
|
||||||
// if (!metadata || !metadata.width || !metadata.height) {
|
// if (!metadata || !metadata.width || !metadata.height) {
|
||||||
// throw new Error("Could not get metadata from image");
|
// throw new Error("Could not get metadata from image");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// const newWidth = Math.round(metadata.width * scale);
|
// const newWidth = Math.round(metadata.width * scale);
|
||||||
// const newHeight = Math.round(metadata.height * scale);
|
// const newHeight = Math.round(metadata.height * scale);
|
||||||
|
|
||||||
// return await sharp(filePath)
|
// return await sharp(filePath)
|
||||||
// .resize(newWidth, newHeight)
|
// .resize(newWidth, newHeight)
|
||||||
// .toFormat(convertTo)
|
// .toFormat(convertTo)
|
||||||
// .toFile(targetPath);
|
// .toFile(targetPath);
|
||||||
// }
|
// }
|
||||||
let action = "copy";
|
let action = "copy";
|
||||||
if (fileType === "pdf") {
|
if (fileType === "pdf") {
|
||||||
action = "pdfload";
|
action = "pdfload";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
execFile("vips", [action, filePath, targetPath], (error, stdout, stderr) => {
|
execFile("vips", [action, filePath, targetPath], (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,80 +1,80 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
images: ["jpg", "jpeg", "png", "bmp", "gif", "tiff", "tif", "webp"],
|
images: ["jpg", "jpeg", "png", "bmp", "gif", "tiff", "tif", "webp"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
images: ["svg"],
|
images: ["svg"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
interface VTracerOptions {
|
interface VTracerOptions {
|
||||||
colormode?: string;
|
colormode?: string;
|
||||||
hierarchical?: string;
|
hierarchical?: string;
|
||||||
mode?: string;
|
mode?: string;
|
||||||
filter_speckle?: string | number;
|
filter_speckle?: string | number;
|
||||||
color_precision?: string | number;
|
color_precision?: string | number;
|
||||||
layer_difference?: string | number;
|
layer_difference?: string | number;
|
||||||
corner_threshold?: string | number;
|
corner_threshold?: string | number;
|
||||||
length_threshold?: string | number;
|
length_threshold?: string | number;
|
||||||
max_iterations?: string | number;
|
max_iterations?: string | number;
|
||||||
splice_threshold?: string | number;
|
splice_threshold?: string | number;
|
||||||
path_precision?: string | number;
|
path_precision?: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Build vtracer arguments
|
// Build vtracer arguments
|
||||||
const args = ["--input", filePath, "--output", targetPath];
|
const args = ["--input", filePath, "--output", targetPath];
|
||||||
|
|
||||||
// Add optional parameter if provided
|
// Add optional parameter if provided
|
||||||
if (options && typeof options === "object") {
|
if (options && typeof options === "object") {
|
||||||
const opts = options as VTracerOptions;
|
const opts = options as VTracerOptions;
|
||||||
const validOptions: Array<keyof VTracerOptions> = [
|
const validOptions: Array<keyof VTracerOptions> = [
|
||||||
"colormode",
|
"colormode",
|
||||||
"hierarchical",
|
"hierarchical",
|
||||||
"mode",
|
"mode",
|
||||||
"filter_speckle",
|
"filter_speckle",
|
||||||
"color_precision",
|
"color_precision",
|
||||||
"layer_difference",
|
"layer_difference",
|
||||||
"corner_threshold",
|
"corner_threshold",
|
||||||
"length_threshold",
|
"length_threshold",
|
||||||
"max_iterations",
|
"max_iterations",
|
||||||
"splice_threshold",
|
"splice_threshold",
|
||||||
"path_precision",
|
"path_precision",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const option of validOptions) {
|
for (const option of validOptions) {
|
||||||
if (opts[option] !== undefined) {
|
if (opts[option] !== undefined) {
|
||||||
args.push(`--${option}`, String(opts[option]));
|
args.push(`--${option}`, String(opts[option]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
execFile("vtracer", args, (error, stdout, stderr) => {
|
execFile("vtracer", args, (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ""}`);
|
reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ""}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.log(`stderr: ${stderr}`);
|
console.log(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,45 +1,45 @@
|
|||||||
import { execFile as execFileOriginal } from "node:child_process";
|
import { execFile as execFileOriginal } from "node:child_process";
|
||||||
import { ExecFileFn } from "./types";
|
import { ExecFileFn } from "./types";
|
||||||
|
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
text: ["tex", "latex"],
|
text: ["tex", "latex"],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
text: ["pdf"],
|
text: ["pdf"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function convert(
|
export function convert(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
options?: unknown,
|
options?: unknown,
|
||||||
execFile: ExecFileFn = execFileOriginal,
|
execFile: ExecFileFn = execFileOriginal,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
|
// const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
|
||||||
const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "");
|
const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "");
|
||||||
|
|
||||||
execFile(
|
execFile(
|
||||||
"latexmk",
|
"latexmk",
|
||||||
["-xelatex", "-interaction=nonstopmode", `-output-directory=${outputPath}`, filePath],
|
["-xelatex", "-interaction=nonstopmode", `-output-directory=${outputPath}`, filePath],
|
||||||
(error, stdout, stderr) => {
|
(error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`stdout: ${stdout}`);
|
console.log(`stdout: ${stdout}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("Done");
|
resolve("Done");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
82
src/db/db.ts
82
src/db/db.ts
@@ -1,41 +1,41 @@
|
|||||||
import { Database } from "bun:sqlite";
|
import { Database } from "bun:sqlite";
|
||||||
|
|
||||||
const db = new Database("./data/mydb.sqlite", { create: true });
|
const db = new Database("./data/mydb.sqlite", { create: true });
|
||||||
|
|
||||||
if (!db.query("SELECT * FROM sqlite_master WHERE type='table'").get()) {
|
if (!db.query("SELECT * FROM sqlite_master WHERE type='table'").get()) {
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
email TEXT NOT NULL,
|
email TEXT NOT NULL,
|
||||||
password TEXT NOT NULL
|
password TEXT NOT NULL
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS file_names (
|
CREATE TABLE IF NOT EXISTS file_names (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
job_id INTEGER NOT NULL,
|
job_id INTEGER NOT NULL,
|
||||||
file_name TEXT NOT NULL,
|
file_name TEXT NOT NULL,
|
||||||
output_file_name TEXT NOT NULL,
|
output_file_name TEXT NOT NULL,
|
||||||
status TEXT DEFAULT 'not started',
|
status TEXT DEFAULT 'not started',
|
||||||
FOREIGN KEY (job_id) REFERENCES jobs(id)
|
FOREIGN KEY (job_id) REFERENCES jobs(id)
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS jobs (
|
CREATE TABLE IF NOT EXISTS jobs (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
date_created TEXT NOT NULL,
|
date_created TEXT NOT NULL,
|
||||||
status TEXT DEFAULT 'not started',
|
status TEXT DEFAULT 'not started',
|
||||||
num_files INTEGER DEFAULT 0,
|
num_files INTEGER DEFAULT 0,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
);
|
);
|
||||||
PRAGMA user_version = 1;`);
|
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) {
|
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;");
|
db.exec("PRAGMA user_version = 1;");
|
||||||
console.log("Updated database to version 1.");
|
console.log("Updated database to version 1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable WAL mode
|
// enable WAL mode
|
||||||
db.exec("PRAGMA journal_mode = WAL;");
|
db.exec("PRAGMA journal_mode = WAL;");
|
||||||
|
|
||||||
export default db;
|
export default db;
|
||||||
|
@@ -1,23 +1,23 @@
|
|||||||
export class Filename {
|
export class Filename {
|
||||||
id!: number;
|
id!: number;
|
||||||
job_id!: number;
|
job_id!: number;
|
||||||
file_name!: string;
|
file_name!: string;
|
||||||
output_file_name!: string;
|
output_file_name!: string;
|
||||||
status!: string;
|
status!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Jobs {
|
export class Jobs {
|
||||||
finished_files!: number;
|
finished_files!: number;
|
||||||
id!: number;
|
id!: number;
|
||||||
user_id!: number;
|
user_id!: number;
|
||||||
date_created!: string;
|
date_created!: string;
|
||||||
status!: string;
|
status!: string;
|
||||||
num_files!: number;
|
num_files!: number;
|
||||||
files_detailed!: Filename[];
|
files_detailed!: Filename[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class User {
|
export class User {
|
||||||
id!: number;
|
id!: number;
|
||||||
email!: string;
|
email!: string;
|
||||||
password!: string;
|
password!: string;
|
||||||
}
|
}
|
||||||
|
@@ -1,25 +1,25 @@
|
|||||||
export const ACCOUNT_REGISTRATION =
|
export const ACCOUNT_REGISTRATION =
|
||||||
process.env.ACCOUNT_REGISTRATION?.toLowerCase() === "true" || false;
|
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 =
|
export const ALLOW_UNAUTHENTICATED =
|
||||||
process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false;
|
process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false;
|
||||||
|
|
||||||
export const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
|
export const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
|
||||||
? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
|
? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
|
||||||
: 24;
|
: 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 ?? "";
|
export const WEBROOT = process.env.WEBROOT ?? "";
|
||||||
|
|
||||||
export const LANGUAGE = process.env.LANGUAGE?.toLowerCase() || "en";
|
export const LANGUAGE = process.env.LANGUAGE?.toLowerCase() || "en";
|
||||||
|
|
||||||
export const MAX_CONVERT_PROCESS =
|
export const MAX_CONVERT_PROCESS =
|
||||||
process.env.MAX_CONVERT_PROCESS && Number(process.env.MAX_CONVERT_PROCESS) > 0
|
process.env.MAX_CONVERT_PROCESS && Number(process.env.MAX_CONVERT_PROCESS) > 0
|
||||||
? Number(process.env.MAX_CONVERT_PROCESS)
|
? Number(process.env.MAX_CONVERT_PROCESS)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
export const UNAUTHENTICATED_USER_SHARING =
|
export const UNAUTHENTICATED_USER_SHARING =
|
||||||
process.env.UNAUTHENTICATED_USER_SHARING?.toLowerCase() === "true" || false;
|
process.env.UNAUTHENTICATED_USER_SHARING?.toLowerCase() === "true" || false;
|
||||||
|
@@ -1,37 +1,37 @@
|
|||||||
export const normalizeFiletype = (filetype: string): string => {
|
export const normalizeFiletype = (filetype: string): string => {
|
||||||
const lowercaseFiletype = filetype.toLowerCase();
|
const lowercaseFiletype = filetype.toLowerCase();
|
||||||
|
|
||||||
switch (lowercaseFiletype) {
|
switch (lowercaseFiletype) {
|
||||||
case "jfif":
|
case "jfif":
|
||||||
case "jpg":
|
case "jpg":
|
||||||
return "jpeg";
|
return "jpeg";
|
||||||
case "htm":
|
case "htm":
|
||||||
return "html";
|
return "html";
|
||||||
case "tex":
|
case "tex":
|
||||||
return "latex";
|
return "latex";
|
||||||
case "md":
|
case "md":
|
||||||
return "markdown";
|
return "markdown";
|
||||||
case "unknown":
|
case "unknown":
|
||||||
return "m4a";
|
return "m4a";
|
||||||
default:
|
default:
|
||||||
return lowercaseFiletype;
|
return lowercaseFiletype;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const normalizeOutputFiletype = (filetype: string): string => {
|
export const normalizeOutputFiletype = (filetype: string): string => {
|
||||||
const lowercaseFiletype = filetype.toLowerCase();
|
const lowercaseFiletype = filetype.toLowerCase();
|
||||||
|
|
||||||
switch (lowercaseFiletype) {
|
switch (lowercaseFiletype) {
|
||||||
case "jpeg":
|
case "jpeg":
|
||||||
return "jpg";
|
return "jpg";
|
||||||
case "latex":
|
case "latex":
|
||||||
return "tex";
|
return "tex";
|
||||||
case "markdown_phpextra":
|
case "markdown_phpextra":
|
||||||
case "markdown_strict":
|
case "markdown_strict":
|
||||||
case "markdown_mmd":
|
case "markdown_mmd":
|
||||||
case "markdown":
|
case "markdown":
|
||||||
return "md";
|
return "md";
|
||||||
default:
|
default:
|
||||||
return lowercaseFiletype;
|
return lowercaseFiletype;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,186 +1,186 @@
|
|||||||
import { exec } from "node:child_process";
|
import { exec } from "node:child_process";
|
||||||
import { version } from "../../package.json";
|
import { version } from "../../package.json";
|
||||||
|
|
||||||
console.log(`ConvertX v${version}`);
|
console.log(`ConvertX v${version}`);
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production") {
|
||||||
exec("cat /etc/os-release", (error, stdout) => {
|
exec("cat /etc/os-release", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Not running on docker, this is not supported.");
|
console.error("Not running on docker, this is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split('PRETTY_NAME="')[1]?.split('"')[0]);
|
console.log(stdout.split('PRETTY_NAME="')[1]?.split('"')[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("pandoc -v", (error, stdout) => {
|
exec("pandoc -v", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Pandoc is not installed.");
|
console.error("Pandoc is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("ffmpeg -version", (error, stdout) => {
|
exec("ffmpeg -version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("FFmpeg is not installed.");
|
console.error("FFmpeg is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("vips -v", (error, stdout) => {
|
exec("vips -v", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Vips is not installed.");
|
console.error("Vips is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("magick --version", (error, stdout) => {
|
exec("magick --version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("ImageMagick is not installed.");
|
console.error("ImageMagick is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]?.replace("Version: ", ""));
|
console.log(stdout.split("\n")[0]?.replace("Version: ", ""));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("gm version", (error, stdout) => {
|
exec("gm version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("GraphicsMagick is not installed.");
|
console.error("GraphicsMagick is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("inkscape --version", (error, stdout) => {
|
exec("inkscape --version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Inkscape is not installed.");
|
console.error("Inkscape is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("djxl --version", (error, stdout) => {
|
exec("djxl --version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("libjxl-tools is not installed.");
|
console.error("libjxl-tools is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("dasel --version", (error, stdout) => {
|
exec("dasel --version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("dasel is not installed.");
|
console.error("dasel is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("xelatex -version", (error, stdout) => {
|
exec("xelatex -version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Tex Live with XeTeX is not installed.");
|
console.error("Tex Live with XeTeX is not installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("resvg -V", (error, stdout) => {
|
exec("resvg -V", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("resvg is not installed");
|
console.error("resvg is not installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`resvg v${stdout.split("\n")[0]}`);
|
console.log(`resvg v${stdout.split("\n")[0]}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("assimp version", (error, stdout) => {
|
exec("assimp version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("assimp is not installed");
|
console.error("assimp is not installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`assimp ${stdout.split("\n")[5]}`);
|
console.log(`assimp ${stdout.split("\n")[5]}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("ebook-convert --version", (error, stdout) => {
|
exec("ebook-convert --version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("ebook-convert (calibre) is not installed");
|
console.error("ebook-convert (calibre) is not installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("heif-info -v", (error, stdout) => {
|
exec("heif-info -v", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("libheif is not installed");
|
console.error("libheif is not installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`libheif v${stdout.split("\n")[0]}`);
|
console.log(`libheif v${stdout.split("\n")[0]}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("potrace -v", (error, stdout) => {
|
exec("potrace -v", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("potrace is not installed");
|
console.error("potrace is not installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("soffice --version", (error, stdout) => {
|
exec("soffice --version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("libreoffice is not installed");
|
console.error("libreoffice is not installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("msgconvert --version", (error, stdout) => {
|
exec("msgconvert --version", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("msgconvert (libemail-outlook-message-perl) is not installed");
|
console.error("msgconvert (libemail-outlook-message-perl) is not installed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(stdout.split("\n")[0]);
|
console.log(stdout.split("\n")[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
exec("bun -v", (error, stdout) => {
|
exec("bun -v", (error, stdout) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Bun is not installed. wait what");
|
console.error("Bun is not installed. wait what");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
console.log(`Bun v${stdout.split("\n")[0]}`);
|
console.log(`Bun v${stdout.split("\n")[0]}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
import tailwind from "@tailwindcss/postcss";
|
import tailwind from "@tailwindcss/postcss";
|
||||||
import postcss from "postcss";
|
import postcss from "postcss";
|
||||||
|
|
||||||
export const generateTailwind = async () => {
|
export const generateTailwind = async () => {
|
||||||
const result = await Bun.file("./src/main.css")
|
const result = await Bun.file("./src/main.css")
|
||||||
.text()
|
.text()
|
||||||
.then((sourceText) => {
|
.then((sourceText) => {
|
||||||
return postcss([tailwind]).process(sourceText, {
|
return postcss([tailwind]).process(sourceText, {
|
||||||
from: "./src/main.css",
|
from: "./src/main.css",
|
||||||
to: "./public/generated.css",
|
to: "./public/generated.css",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
188
src/index.tsx
188
src/index.tsx
@@ -1,94 +1,94 @@
|
|||||||
import { rmSync } from "node:fs";
|
import { rmSync } from "node:fs";
|
||||||
import { mkdir } from "node:fs/promises";
|
import { mkdir } from "node:fs/promises";
|
||||||
import { html } from "@elysiajs/html";
|
import { html } from "@elysiajs/html";
|
||||||
import { staticPlugin } from "@elysiajs/static";
|
import { staticPlugin } from "@elysiajs/static";
|
||||||
import { Elysia } from "elysia";
|
import { Elysia } from "elysia";
|
||||||
import "./helpers/printVersions";
|
import "./helpers/printVersions";
|
||||||
import db from "./db/db";
|
import db from "./db/db";
|
||||||
import { Jobs } from "./db/types";
|
import { Jobs } from "./db/types";
|
||||||
import { AUTO_DELETE_EVERY_N_HOURS, WEBROOT } from "./helpers/env";
|
import { AUTO_DELETE_EVERY_N_HOURS, WEBROOT } from "./helpers/env";
|
||||||
import { chooseConverter } from "./pages/chooseConverter";
|
import { chooseConverter } from "./pages/chooseConverter";
|
||||||
import { convert } from "./pages/convert";
|
import { convert } from "./pages/convert";
|
||||||
import { deleteFile } from "./pages/deleteFile";
|
import { deleteFile } from "./pages/deleteFile";
|
||||||
import { download } from "./pages/download";
|
import { download } from "./pages/download";
|
||||||
import { history } from "./pages/history";
|
import { history } from "./pages/history";
|
||||||
import { listConverters } from "./pages/listConverters";
|
import { listConverters } from "./pages/listConverters";
|
||||||
import { results } from "./pages/results";
|
import { results } from "./pages/results";
|
||||||
import { root } from "./pages/root";
|
import { root } from "./pages/root";
|
||||||
import { upload } from "./pages/upload";
|
import { upload } from "./pages/upload";
|
||||||
import { user } from "./pages/user";
|
import { user } from "./pages/user";
|
||||||
|
|
||||||
mkdir("./data", { recursive: true }).catch(console.error);
|
mkdir("./data", { recursive: true }).catch(console.error);
|
||||||
|
|
||||||
export const uploadsDir = "./data/uploads/";
|
export const uploadsDir = "./data/uploads/";
|
||||||
export const outputDir = "./data/output/";
|
export const outputDir = "./data/output/";
|
||||||
|
|
||||||
const app = new Elysia({
|
const app = new Elysia({
|
||||||
serve: {
|
serve: {
|
||||||
maxRequestBodySize: Number.MAX_SAFE_INTEGER,
|
maxRequestBodySize: Number.MAX_SAFE_INTEGER,
|
||||||
},
|
},
|
||||||
prefix: WEBROOT,
|
prefix: WEBROOT,
|
||||||
})
|
})
|
||||||
.use(html())
|
.use(html())
|
||||||
.use(
|
.use(
|
||||||
staticPlugin({
|
staticPlugin({
|
||||||
assets: "public",
|
assets: "public",
|
||||||
prefix: "",
|
prefix: "",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.use(user)
|
.use(user)
|
||||||
.use(root)
|
.use(root)
|
||||||
.use(upload)
|
.use(upload)
|
||||||
.use(history)
|
.use(history)
|
||||||
.use(convert)
|
.use(convert)
|
||||||
.use(download)
|
.use(download)
|
||||||
.use(results)
|
.use(results)
|
||||||
.use(deleteFile)
|
.use(deleteFile)
|
||||||
.use(listConverters)
|
.use(listConverters)
|
||||||
.use(chooseConverter)
|
.use(chooseConverter)
|
||||||
.onError(({ error }) => {
|
.onError(({ error }) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
await import("./helpers/tailwind").then(async ({ generateTailwind }) => {
|
await import("./helpers/tailwind").then(async ({ generateTailwind }) => {
|
||||||
const result = await generateTailwind();
|
const result = await generateTailwind();
|
||||||
|
|
||||||
app.get("/generated.css", ({ set }) => {
|
app.get("/generated.css", ({ set }) => {
|
||||||
set.headers["content-type"] = "text/css";
|
set.headers["content-type"] = "text/css";
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.listen(3000);
|
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 clearJobs = () => {
|
||||||
const jobs = db
|
const jobs = db
|
||||||
.query("SELECT * FROM jobs WHERE date_created < ?")
|
.query("SELECT * FROM jobs WHERE date_created < ?")
|
||||||
.as(Jobs)
|
.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) {
|
for (const job of jobs) {
|
||||||
// delete the directories
|
// delete the directories
|
||||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// delete the job
|
// delete the job
|
||||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(clearJobs, AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000);
|
setTimeout(clearJobs, AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (AUTO_DELETE_EVERY_N_HOURS > 0) {
|
if (AUTO_DELETE_EVERY_N_HOURS > 0) {
|
||||||
clearJobs();
|
clearJobs();
|
||||||
}
|
}
|
||||||
|
64
src/main.css
64
src/main.css
@@ -1,32 +1,32 @@
|
|||||||
@import "./theme/theme.css";
|
@import "./theme/theme.css";
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@plugin "tailwind-scrollbar";
|
@plugin "tailwind-scrollbar";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-contrast: var(--contrast);
|
--color-contrast: var(--contrast);
|
||||||
--color-neutral-900: var(--neutral-900);
|
--color-neutral-900: var(--neutral-900);
|
||||||
--color-neutral-800: var(--neutral-800);
|
--color-neutral-800: var(--neutral-800);
|
||||||
--color-neutral-700: var(--neutral-700);
|
--color-neutral-700: var(--neutral-700);
|
||||||
--color-neutral-600: var(--neutral-600);
|
--color-neutral-600: var(--neutral-600);
|
||||||
--color-neutral-500: var(--neutral-500);
|
--color-neutral-500: var(--neutral-500);
|
||||||
--color-neutral-400: var(--neutral-400);
|
--color-neutral-400: var(--neutral-400);
|
||||||
--color-neutral-300: var(--neutral-300);
|
--color-neutral-300: var(--neutral-300);
|
||||||
--color-neutral-200: var(--neutral-200);
|
--color-neutral-200: var(--neutral-200);
|
||||||
--color-neutral-100: var(--neutral-100);
|
--color-neutral-100: var(--neutral-100);
|
||||||
--color-accent-600: var(--accent-600);
|
--color-accent-600: var(--accent-600);
|
||||||
--color-accent-500: var(--accent-500);
|
--color-accent-500: var(--accent-500);
|
||||||
--color-accent-400: var(--accent-400);
|
--color-accent-400: var(--accent-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility article {
|
@utility article {
|
||||||
@apply px-2 sm:px-4 py-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded-sm;
|
@apply px-2 sm:px-4 py-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility btn-primary {
|
@utility btn-primary {
|
||||||
@apply bg-accent-500 text-contrast rounded-sm p-2 sm:p-4 hover:bg-accent-400 cursor-pointer transition-colors;
|
@apply bg-accent-500 text-contrast rounded-sm p-2 sm:p-4 hover:bg-accent-400 cursor-pointer transition-colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility btn-secondary {
|
@utility btn-secondary {
|
||||||
@apply bg-neutral-400 text-contrast rounded-sm p-2 sm:p-4 hover:bg-neutral-300 cursor-pointer transition-colors;
|
@apply bg-neutral-400 text-contrast rounded-sm p-2 sm:p-4 hover:bg-neutral-300 cursor-pointer transition-colors;
|
||||||
}
|
}
|
||||||
|
@@ -1,67 +1,67 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import { Html } from "@elysiajs/html";
|
||||||
import Elysia, { t } from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import { getPossibleTargets } from "../converters/main";
|
import { getPossibleTargets } from "../converters/main";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
export const chooseConverter = new Elysia().use(userService).post(
|
export const chooseConverter = new Elysia().use(userService).post(
|
||||||
"/conversions",
|
"/conversions",
|
||||||
({ body }) => {
|
({ body }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<article
|
<article
|
||||||
class={`
|
class={`
|
||||||
convert_to_popup absolute z-2 m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
|
convert_to_popup absolute z-2 m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
|
||||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||||
sm:h-[30vh]
|
sm:h-[30vh]
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
|
{Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
|
||||||
<article
|
<article
|
||||||
class={`convert_to_group flex w-full flex-col border-b border-neutral-700 p-4`}
|
class={`convert_to_group flex w-full flex-col border-b border-neutral-700 p-4`}
|
||||||
data-converter={converter}
|
data-converter={converter}
|
||||||
>
|
>
|
||||||
<header class="mb-2 w-full text-xl font-bold" safe>
|
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||||
{converter}
|
{converter}
|
||||||
</header>
|
</header>
|
||||||
<ul class="convert_to_target flex flex-row flex-wrap gap-1">
|
<ul class="convert_to_target flex flex-row flex-wrap gap-1">
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
<button
|
<button
|
||||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||||
tabindex={0}
|
tabindex={0}
|
||||||
class={`
|
class={`
|
||||||
target rounded bg-neutral-700 p-1 text-base
|
target rounded bg-neutral-700 p-1 text-base
|
||||||
hover:bg-neutral-600
|
hover:bg-neutral-600
|
||||||
`}
|
`}
|
||||||
data-value={`${target},${converter}`}
|
data-value={`${target},${converter}`}
|
||||||
data-target={target}
|
data-target={target}
|
||||||
data-converter={converter}
|
data-converter={converter}
|
||||||
type="button"
|
type="button"
|
||||||
safe
|
safe
|
||||||
>
|
>
|
||||||
{target}
|
{target}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<select name="convert_to" aria-label="Convert to" required hidden>
|
<select name="convert_to" aria-label="Convert to" required hidden>
|
||||||
<option selected disabled value="">
|
<option selected disabled value="">
|
||||||
Convert to
|
Convert to
|
||||||
</option>
|
</option>
|
||||||
{Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
|
{Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
|
||||||
<optgroup label={converter}>
|
<optgroup label={converter}>
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
<option value={`${target},${converter}`} safe>
|
<option value={`${target},${converter}`} safe>
|
||||||
{target}
|
{target}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{ body: t.Object({ fileType: t.String() }) },
|
{ body: t.Object({ fileType: t.String() }) },
|
||||||
);
|
);
|
||||||
|
@@ -1,94 +1,94 @@
|
|||||||
import { mkdir } from "node:fs/promises";
|
import { mkdir } from "node:fs/promises";
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import sanitize from "sanitize-filename";
|
import sanitize from "sanitize-filename";
|
||||||
import { outputDir, uploadsDir } from "..";
|
import { outputDir, uploadsDir } from "..";
|
||||||
import { handleConvert } from "../converters/main";
|
import { handleConvert } from "../converters/main";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { Jobs } from "../db/types";
|
import { Jobs } from "../db/types";
|
||||||
import { WEBROOT } from "../helpers/env";
|
import { WEBROOT } from "../helpers/env";
|
||||||
import { normalizeFiletype } from "../helpers/normalizeFiletype";
|
import { normalizeFiletype } from "../helpers/normalizeFiletype";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
export const convert = new Elysia().use(userService).post(
|
export const convert = new Elysia().use(userService).post(
|
||||||
"/convert",
|
"/convert",
|
||||||
async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
|
async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
|
||||||
if (!auth?.value) {
|
if (!auth?.value) {
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await jwt.verify(auth.value);
|
const user = await jwt.verify(auth.value);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!jobId?.value) {
|
if (!jobId?.value) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingJob = db
|
const existingJob = db
|
||||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||||
.as(Jobs)
|
.as(Jobs)
|
||||||
.get(jobId.value, user.id);
|
.get(jobId.value, user.id);
|
||||||
|
|
||||||
if (!existingJob) {
|
if (!existingJob) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||||
const userOutputDir = `${outputDir}${user.id}/${jobId.value}/`;
|
const userOutputDir = `${outputDir}${user.id}/${jobId.value}/`;
|
||||||
|
|
||||||
// create the output directory
|
// create the output directory
|
||||||
try {
|
try {
|
||||||
await mkdir(userOutputDir, { recursive: true });
|
await mkdir(userOutputDir, { recursive: true });
|
||||||
} catch (error) {
|
} 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] ?? "");
|
const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? "");
|
||||||
const converterName = body.convert_to.split(",")[1];
|
const converterName = body.convert_to.split(",")[1];
|
||||||
|
|
||||||
if (!converterName) {
|
if (!converterName) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileNames = JSON.parse(body.file_names) as string[];
|
const fileNames = JSON.parse(body.file_names) as string[];
|
||||||
|
|
||||||
for (let i = 0; i < fileNames.length; i++) {
|
for (let i = 0; i < fileNames.length; i++) {
|
||||||
fileNames[i] = sanitize(fileNames[i] || "");
|
fileNames[i] = sanitize(fileNames[i] || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(fileNames) || fileNames.length === 0) {
|
if (!Array.isArray(fileNames) || fileNames.length === 0) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
db.query("UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2").run(
|
db.query("UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2").run(
|
||||||
fileNames.length,
|
fileNames.length,
|
||||||
jobId.value,
|
jobId.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start the conversion process in the background
|
// Start the conversion process in the background
|
||||||
handleConvert(fileNames, userUploadsDir, userOutputDir, convertTo, converterName, jobId)
|
handleConvert(fileNames, userUploadsDir, userOutputDir, convertTo, converterName, jobId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// All conversions are done, update the job status to 'completed'
|
// All conversions are done, update the job status to 'completed'
|
||||||
if (jobId.value) {
|
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
|
// Delete all uploaded files in userUploadsDir
|
||||||
// rmSync(userUploadsDir, { recursive: true, force: true });
|
// rmSync(userUploadsDir, { recursive: true, force: true });
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error in conversion process:", error);
|
console.error("Error in conversion process:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Redirect the client immediately
|
// Redirect the client immediately
|
||||||
return redirect(`${WEBROOT}/results/${jobId.value}`, 302);
|
return redirect(`${WEBROOT}/results/${jobId.value}`, 302);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
convert_to: t.String(),
|
convert_to: t.String(),
|
||||||
file_names: t.String(),
|
file_names: t.String(),
|
||||||
}),
|
}),
|
||||||
auth: true,
|
auth: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@@ -1,32 +1,32 @@
|
|||||||
import { unlink } from "node:fs/promises";
|
import { unlink } from "node:fs/promises";
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { uploadsDir } from "..";
|
import { uploadsDir } from "..";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { WEBROOT } from "../helpers/env";
|
import { WEBROOT } from "../helpers/env";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
export const deleteFile = new Elysia().use(userService).post(
|
export const deleteFile = new Elysia().use(userService).post(
|
||||||
"/delete",
|
"/delete",
|
||||||
async ({ body, redirect, cookie: { jobId }, user }) => {
|
async ({ body, redirect, cookie: { jobId }, user }) => {
|
||||||
if (!jobId?.value) {
|
if (!jobId?.value) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingJob = await db
|
const existingJob = await db
|
||||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||||
.get(jobId.value, user.id);
|
.get(jobId.value, user.id);
|
||||||
|
|
||||||
if (!existingJob) {
|
if (!existingJob) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||||
|
|
||||||
await unlink(`${userUploadsDir}${body.filename}`);
|
await unlink(`${userUploadsDir}${body.filename}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "File deleted successfully.",
|
message: "File deleted successfully.",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ body: t.Object({ filename: t.String() }), auth: true },
|
{ body: t.Object({ filename: t.String() }), auth: true },
|
||||||
);
|
);
|
||||||
|
@@ -1,61 +1,65 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { Elysia, t } from 'elysia'
|
import { Elysia } from "elysia";
|
||||||
import sanitize from "sanitize-filename";
|
import sanitize from "sanitize-filename";
|
||||||
import * as tar from "tar";
|
import * as tar from "tar";
|
||||||
import { outputDir } from "..";
|
import { outputDir } from "..";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { WEBROOT } from "../helpers/env";
|
import { WEBROOT } from "../helpers/env";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
export const download = new Elysia()
|
export const download = new Elysia()
|
||||||
.use(userService)
|
.use(userService)
|
||||||
.get(
|
.get(
|
||||||
"/download/:userId/:jobId/:fileName",
|
"/download/:userId/:jobId/:fileName",
|
||||||
async ({ params, redirect, user }) => {
|
async ({ params, redirect, user }) => {
|
||||||
const job = await db
|
const job = await db
|
||||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||||
.get(user.id, params.jobId);
|
.get(user.id, params.jobId);
|
||||||
|
|
||||||
if (!job) {
|
if (!job) {
|
||||||
return redirect(`${WEBROOT}/results`, 302);
|
return redirect(`${WEBROOT}/results`, 302);
|
||||||
}
|
}
|
||||||
// parse from URL encoded string
|
// parse from URL encoded string
|
||||||
const userId = decodeURIComponent(params.userId);
|
const userId = decodeURIComponent(params.userId);
|
||||||
const jobId = decodeURIComponent(params.jobId);
|
const jobId = decodeURIComponent(params.jobId);
|
||||||
const fileName = sanitize(decodeURIComponent(params.fileName));
|
const fileName = sanitize(decodeURIComponent(params.fileName));
|
||||||
|
|
||||||
const filePath = `${outputDir}${userId}/${jobId}/${fileName}`;
|
const filePath = `${outputDir}${userId}/${jobId}/${fileName}`;
|
||||||
return Bun.file(filePath);
|
return Bun.file(filePath);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
auth: true,
|
auth: true,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.get("/archive/:userId/:jobId", async ({ params, redirect, user }) => {
|
.get(
|
||||||
const job = await db
|
"/archive/:userId/:jobId",
|
||||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
async ({ params, redirect, user }) => {
|
||||||
.get(user.id, params.jobId);
|
const job = await db
|
||||||
|
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||||
if (!job) {
|
.get(user.id, params.jobId);
|
||||||
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 outputTar = path.join(outputPath, `converted_files_${jobId}.tar`);
|
const jobId = decodeURIComponent(params.jobId);
|
||||||
|
const outputPath = `${outputDir}${userId}/${jobId}`;
|
||||||
await tar.create(
|
const outputTar = path.join(outputPath, `converted_files_${jobId}.tar`);
|
||||||
{
|
|
||||||
file: outputTar,
|
await tar.create(
|
||||||
cwd: outputPath,
|
{
|
||||||
filter: (path) => {
|
file: outputTar,
|
||||||
return !path.match(".*\\.tar");
|
cwd: outputPath,
|
||||||
},
|
filter: (path) => {
|
||||||
},
|
return !path.match(".*\\.tar");
|
||||||
["."],
|
},
|
||||||
);
|
},
|
||||||
return Bun.file(outputTar);
|
["."],
|
||||||
}, {
|
);
|
||||||
auth: true,
|
return Bun.file(outputTar);
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
auth: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@@ -1,213 +1,215 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import { Html } from "@elysiajs/html";
|
||||||
import { Elysia } from "elysia";
|
import { Elysia } from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
import { Header } from "../components/header";
|
import { Header } from "../components/header";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { Filename, Jobs } from "../db/types";
|
import { Filename, Jobs } from "../db/types";
|
||||||
import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, LANGUAGE, WEBROOT } from "../helpers/env";
|
import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, LANGUAGE, WEBROOT } from "../helpers/env";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
export const history = new Elysia()
|
export const history = new Elysia().use(userService).get(
|
||||||
.use(userService)
|
"/history",
|
||||||
.get("/history", async ({ jwt, redirect, user }) => {
|
async ({ redirect, user }) => {
|
||||||
if (HIDE_HISTORY) {
|
if (HIDE_HISTORY) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
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) {
|
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.finished_files = files.length;
|
||||||
job.files_detailed = files;
|
job.files_detailed = files;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out jobs with no files
|
// Filter out jobs with no files
|
||||||
userJobs = userJobs.filter((job) => job.num_files > 0);
|
userJobs = userJobs.filter((job) => job.num_files > 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Results">
|
<BaseHtml webroot={WEBROOT} title="ConvertX | Results">
|
||||||
<>
|
<>
|
||||||
<Header
|
<Header
|
||||||
webroot={WEBROOT}
|
webroot={WEBROOT}
|
||||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||||
hideHistory={HIDE_HISTORY}
|
hideHistory={HIDE_HISTORY}
|
||||||
loggedIn
|
loggedIn
|
||||||
/>
|
/>
|
||||||
<main
|
<main
|
||||||
class={`
|
class={`
|
||||||
w-full flex-1 px-2
|
w-full flex-1 px-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<article class="article">
|
<article class="article">
|
||||||
<h1 class="mb-4 text-xl">Results</h1>
|
<h1 class="mb-4 text-xl">Results</h1>
|
||||||
<table
|
<table
|
||||||
class={`
|
class={`
|
||||||
w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
|
w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
|
||||||
[&_td]:p-4
|
[&_td]:p-4
|
||||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<span class="sr-only">Expand details</span>
|
<span class="sr-only">Expand details</span>
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Time
|
Time
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Files
|
Files
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
max-sm:hidden
|
max-sm:hidden
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Files Done
|
Files Done
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{userJobs.map((job) => (
|
{userJobs.map((job) => (
|
||||||
<>
|
<>
|
||||||
<tr id={`job-row-${job.id}`}>
|
<tr id={`job-row-${job.id}`}>
|
||||||
<td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
|
<td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
|
||||||
<svg
|
<svg
|
||||||
id={`arrow-${job.id}`}
|
id={`arrow-${job.id}`}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
class="inline-block h-4 w-4"
|
class="inline-block h-4 w-4"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</td>
|
</td>
|
||||||
<td safe>{new Date(job.date_created).toLocaleTimeString(LANGUAGE)}</td>
|
<td safe>{new Date(job.date_created).toLocaleTimeString(LANGUAGE)}</td>
|
||||||
<td>{job.num_files}</td>
|
<td>{job.num_files}</td>
|
||||||
<td class="max-sm:hidden">{job.finished_files}</td>
|
<td class="max-sm:hidden">{job.finished_files}</td>
|
||||||
<td safe>{job.status}</td>
|
<td safe>{job.status}</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-500 underline
|
text-accent-500 underline
|
||||||
hover:text-accent-400
|
hover:text-accent-400
|
||||||
`}
|
`}
|
||||||
href={`${WEBROOT}/results/${job.id}`}
|
href={`${WEBROOT}/results/${job.id}`}
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr id={`details-${job.id}`} class="hidden">
|
<tr id={`details-${job.id}`} class="hidden">
|
||||||
<td colspan="6">
|
<td colspan="6">
|
||||||
<div class="p-2 text-sm text-neutral-500">
|
<div class="p-2 text-sm text-neutral-500">
|
||||||
<div class="mb-1 font-semibold">Detailed File Information:</div>
|
<div class="mb-1 font-semibold">Detailed File Information:</div>
|
||||||
{job.files_detailed.map((file: Filename) => (
|
{job.files_detailed.map((file: Filename) => (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="w-5/12 truncate" title={file.file_name} safe>
|
<span class="w-5/12 truncate" title={file.file_name} safe>
|
||||||
{file.file_name}
|
{file.file_name}
|
||||||
</span>
|
</span>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
class={`mx-2 inline-block h-4 w-4 text-neutral-500`}
|
class={`mx-2 inline-block h-4 w-4 text-neutral-500`}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="w-5/12 truncate" title={file.output_file_name} safe>
|
<span class="w-5/12 truncate" title={file.output_file_name} safe>
|
||||||
{file.output_file_name}
|
{file.output_file_name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
{`
|
{`
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const toggles = document.querySelectorAll('.job-details-toggle');
|
const toggles = document.querySelectorAll('.job-details-toggle');
|
||||||
toggles.forEach(toggle => {
|
toggles.forEach(toggle => {
|
||||||
toggle.addEventListener('click', function() {
|
toggle.addEventListener('click', function() {
|
||||||
const jobId = this.dataset.jobId;
|
const jobId = this.dataset.jobId;
|
||||||
const detailsRow = document.getElementById(\`details-\${jobId}\`);
|
const detailsRow = document.getElementById(\`details-\${jobId}\`);
|
||||||
// The arrow SVG itself has the ID arrow-\${jobId}
|
// The arrow SVG itself has the ID arrow-\${jobId}
|
||||||
const arrow = document.getElementById(\`arrow-\${jobId}\`);
|
const arrow = document.getElementById(\`arrow-\${jobId}\`);
|
||||||
|
|
||||||
if (detailsRow && arrow) {
|
if (detailsRow && arrow) {
|
||||||
detailsRow.classList.toggle("hidden");
|
detailsRow.classList.toggle("hidden");
|
||||||
if (detailsRow.classList.contains("hidden")) {
|
if (detailsRow.classList.contains("hidden")) {
|
||||||
// Right-facing arrow (collapsed)
|
// Right-facing arrow (collapsed)
|
||||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />';
|
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />';
|
||||||
} else {
|
} else {
|
||||||
// Down-facing arrow (expanded)
|
// Down-facing arrow (expanded)
|
||||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />';
|
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
`}
|
`}
|
||||||
</script>
|
</script>
|
||||||
</>
|
</>
|
||||||
</BaseHtml>
|
</BaseHtml>
|
||||||
);
|
);
|
||||||
}, {
|
},
|
||||||
auth: true
|
{
|
||||||
});
|
auth: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@@ -1,73 +1,75 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import { Html } from "@elysiajs/html";
|
||||||
import Elysia from "elysia";
|
import Elysia from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
import { Header } from "../components/header";
|
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 { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
export const listConverters = new Elysia()
|
export const listConverters = new Elysia().use(userService).get(
|
||||||
.use(userService)
|
"/converters",
|
||||||
.get("/converters", async () => {
|
async () => {
|
||||||
return (
|
return (
|
||||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Converters">
|
<BaseHtml webroot={WEBROOT} title="ConvertX | Converters">
|
||||||
<>
|
<>
|
||||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
||||||
<main
|
<main
|
||||||
class={`
|
class={`
|
||||||
w-full flex-1 px-2
|
w-full flex-1 px-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<article class="article">
|
<article class="article">
|
||||||
<h1 class="mb-4 text-xl">Converters</h1>
|
<h1 class="mb-4 text-xl">Converters</h1>
|
||||||
<table
|
<table
|
||||||
class={`
|
class={`
|
||||||
w-full table-auto rounded bg-neutral-900 text-left
|
w-full table-auto rounded bg-neutral-900 text-left
|
||||||
[&_td]:p-4
|
[&_td]:p-4
|
||||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
[&_ul]:list-inside [&_ul]:list-disc
|
[&_ul]:list-inside [&_ul]:list-disc
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="mx-4 my-2">Converter</th>
|
<th class="mx-4 my-2">Converter</th>
|
||||||
<th class="mx-4 my-2">From (Count)</th>
|
<th class="mx-4 my-2">From (Count)</th>
|
||||||
<th class="mx-4 my-2">To (Count)</th>
|
<th class="mx-4 my-2">To (Count)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Object.entries(getAllTargets()).map(([converter, targets]) => {
|
{Object.entries(getAllTargets()).map(([converter, targets]) => {
|
||||||
const inputs = getAllInputs(converter);
|
const inputs = getAllInputs(converter);
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td safe>{converter}</td>
|
<td safe>{converter}</td>
|
||||||
<td>
|
<td>
|
||||||
Count: {inputs.length}
|
Count: {inputs.length}
|
||||||
<ul>
|
<ul>
|
||||||
{inputs.map((input) => (
|
{inputs.map((input) => (
|
||||||
<li safe>{input}</li>
|
<li safe>{input}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
Count: {targets.length}
|
Count: {targets.length}
|
||||||
<ul>
|
<ul>
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
<li safe>{target}</li>
|
<li safe>{target}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
</BaseHtml>
|
</BaseHtml>
|
||||||
);
|
);
|
||||||
}, {
|
},
|
||||||
auth: true
|
{
|
||||||
});
|
auth: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@@ -1,207 +1,215 @@
|
|||||||
import { Html } from "@elysiajs/html";
|
import { Html } from "@elysiajs/html";
|
||||||
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
||||||
import { Elysia } from "elysia";
|
import { Elysia } from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
import { Header } from "../components/header";
|
import { Header } from "../components/header";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { Filename, Jobs } from "../db/types";
|
import { Filename, Jobs } from "../db/types";
|
||||||
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
function ResultsArticle({
|
function ResultsArticle({
|
||||||
user,
|
user,
|
||||||
job,
|
job,
|
||||||
files,
|
files,
|
||||||
outputPath,
|
outputPath,
|
||||||
}: {
|
}: {
|
||||||
user: {
|
user: {
|
||||||
id: string;
|
id: string;
|
||||||
} & JWTPayloadSpec;
|
} & JWTPayloadSpec;
|
||||||
job: Jobs;
|
job: Jobs;
|
||||||
files: Filename[];
|
files: Filename[];
|
||||||
outputPath: string;
|
outputPath: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<article class="article">
|
<article class="article">
|
||||||
<div class="mb-4 flex items-center justify-between">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<h1 class="text-xl">Results</h1>
|
<h1 class="text-xl">Results</h1>
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||||
href={`${WEBROOT}/archive/${user.id}/${job.id}`}
|
href={`${WEBROOT}/archive/${user.id}/${job.id}`}
|
||||||
download={`converted_files_${job.id}.tar`}
|
download={`converted_files_${job.id}.tar`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="float-right w-40 btn-primary"
|
class="float-right w-40 btn-primary"
|
||||||
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
|
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
|
||||||
>
|
>
|
||||||
{files.length === job.num_files ? "Download All" : "Converting..."}
|
{files.length === job.num_files ? "Download All" : "Converting..."}
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<progress
|
<progress
|
||||||
max={job.num_files}
|
max={job.num_files}
|
||||||
value={files.length}
|
value={files.length}
|
||||||
class={`
|
class={`
|
||||||
mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0
|
mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0
|
||||||
bg-neutral-700 bg-none text-accent-500 accent-accent-500
|
bg-neutral-700 bg-none text-accent-500 accent-accent-500
|
||||||
[&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full
|
[&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full
|
||||||
[&::-webkit-progress-value]:[background:none]
|
[&::-webkit-progress-value]:[background:none]
|
||||||
[&[value]::-webkit-progress-value]:bg-accent-500
|
[&[value]::-webkit-progress-value]:bg-accent-500
|
||||||
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
<table
|
<table
|
||||||
class={`
|
class={`
|
||||||
w-full table-auto rounded bg-neutral-900 text-left
|
w-full table-auto rounded bg-neutral-900 text-left
|
||||||
[&_td]:p-4
|
[&_td]:p-4
|
||||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Converted File Name
|
Converted File Name
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
class={`
|
class={`
|
||||||
px-2 py-2
|
px-2 py-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
Download
|
Download
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{files.map((file) => (
|
{files.map((file) => (
|
||||||
<tr>
|
<tr>
|
||||||
<td safe class="max-w-[20vw] truncate">
|
<td safe class="max-w-[20vw] truncate">
|
||||||
{file.output_file_name}
|
{file.output_file_name}
|
||||||
</td>
|
</td>
|
||||||
<td safe>{file.status}</td>
|
<td safe>{file.status}</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-500 underline
|
text-accent-500 underline
|
||||||
hover:text-accent-400
|
hover:text-accent-400
|
||||||
`}
|
`}
|
||||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-500 underline
|
text-accent-500 underline
|
||||||
hover:text-accent-400
|
hover:text-accent-400
|
||||||
`}
|
`}
|
||||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||||
download={file.output_file_name}
|
download={file.output_file_name}
|
||||||
>
|
>
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const results = new Elysia()
|
export const results = new Elysia()
|
||||||
.use(userService)
|
.use(userService)
|
||||||
.get("/results/:jobId", async ({ params, jwt, set, redirect, cookie: { job_id }, user }) => {
|
.get(
|
||||||
if (job_id?.value) {
|
"/results/:jobId",
|
||||||
// Clear the job_id cookie since we are viewing the results
|
async ({ params, set, cookie: { job_id }, user }) => {
|
||||||
job_id.remove();
|
if (job_id?.value) {
|
||||||
}
|
// Clear the job_id cookie since we are viewing the results
|
||||||
|
job_id.remove();
|
||||||
const job = db
|
}
|
||||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
|
||||||
.as(Jobs)
|
const job = db
|
||||||
.get(user.id, params.jobId);
|
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||||
|
.as(Jobs)
|
||||||
if (!job) {
|
.get(user.id, params.jobId);
|
||||||
set.status = 404;
|
|
||||||
return {
|
if (!job) {
|
||||||
message: "Job not found.",
|
set.status = 404;
|
||||||
};
|
return {
|
||||||
}
|
message: "Job not found.",
|
||||||
|
};
|
||||||
const outputPath = `${user.id}/${params.jobId}/`;
|
}
|
||||||
|
|
||||||
const files = db
|
const outputPath = `${user.id}/${params.jobId}/`;
|
||||||
.query("SELECT * FROM file_names WHERE job_id = ?")
|
|
||||||
.as(Filename)
|
const files = db
|
||||||
.all(params.jobId);
|
.query("SELECT * FROM file_names WHERE job_id = ?")
|
||||||
|
.as(Filename)
|
||||||
return (
|
.all(params.jobId);
|
||||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Result">
|
|
||||||
<>
|
return (
|
||||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
<BaseHtml webroot={WEBROOT} title="ConvertX | Result">
|
||||||
<main
|
<>
|
||||||
class={`
|
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
||||||
w-full flex-1 px-2
|
<main
|
||||||
sm:px-4
|
class={`
|
||||||
`}
|
w-full flex-1 px-2
|
||||||
>
|
sm:px-4
|
||||||
<ResultsArticle user={user} job={job} files={files} outputPath={outputPath} />
|
`}
|
||||||
</main>
|
>
|
||||||
<script src={`${WEBROOT}/results.js`} defer />
|
<ResultsArticle user={user} job={job} files={files} outputPath={outputPath} />
|
||||||
</>
|
</main>
|
||||||
</BaseHtml>
|
<script src={`${WEBROOT}/results.js`} defer />
|
||||||
);
|
</>
|
||||||
}, { auth: true })
|
</BaseHtml>
|
||||||
.post("/progress/:jobId", async ({ jwt, set, params, cookie: { job_id }, user }) => {
|
);
|
||||||
if (job_id?.value) {
|
},
|
||||||
// Clear the job_id cookie since we are viewing the results
|
{ auth: true },
|
||||||
job_id.remove();
|
)
|
||||||
}
|
.post(
|
||||||
|
"/progress/:jobId",
|
||||||
const job = db
|
async ({ set, params, cookie: { job_id }, user }) => {
|
||||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
if (job_id?.value) {
|
||||||
.as(Jobs)
|
// Clear the job_id cookie since we are viewing the results
|
||||||
.get(user.id, params.jobId);
|
job_id.remove();
|
||||||
|
}
|
||||||
if (!job) {
|
|
||||||
set.status = 404;
|
const job = db
|
||||||
return {
|
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||||
message: "Job not found.",
|
.as(Jobs)
|
||||||
};
|
.get(user.id, params.jobId);
|
||||||
}
|
|
||||||
|
if (!job) {
|
||||||
const outputPath = `${user.id}/${params.jobId}/`;
|
set.status = 404;
|
||||||
|
return {
|
||||||
const files = db
|
message: "Job not found.",
|
||||||
.query("SELECT * FROM file_names WHERE job_id = ?")
|
};
|
||||||
.as(Filename)
|
}
|
||||||
.all(params.jobId);
|
|
||||||
|
const outputPath = `${user.id}/${params.jobId}/`;
|
||||||
return <ResultsArticle user={user} job={job} files={files} outputPath={outputPath} />;
|
|
||||||
}, { auth: true });
|
const files = db
|
||||||
|
.query("SELECT * FROM file_names WHERE job_id = ?")
|
||||||
|
.as(Filename)
|
||||||
|
.all(params.jobId);
|
||||||
|
|
||||||
|
return <ResultsArticle user={user} job={job} files={files} outputPath={outputPath} />;
|
||||||
|
},
|
||||||
|
{ auth: true },
|
||||||
|
);
|
||||||
|
@@ -1,248 +1,250 @@
|
|||||||
import { randomInt } from "node:crypto";
|
import { randomInt } from "node:crypto";
|
||||||
import { Html } from "@elysiajs/html";
|
import { Html } from "@elysiajs/html";
|
||||||
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
import { Header } from "../components/header";
|
import { Header } from "../components/header";
|
||||||
import { getAllTargets } from "../converters/main";
|
import { getAllTargets } from "../converters/main";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { User } from "../db/types";
|
import { User } from "../db/types";
|
||||||
import {
|
import {
|
||||||
ACCOUNT_REGISTRATION,
|
ACCOUNT_REGISTRATION,
|
||||||
ALLOW_UNAUTHENTICATED,
|
ALLOW_UNAUTHENTICATED,
|
||||||
HIDE_HISTORY,
|
HIDE_HISTORY,
|
||||||
HTTP_ALLOWED,
|
HTTP_ALLOWED,
|
||||||
UNAUTHENTICATED_USER_SHARING,
|
UNAUTHENTICATED_USER_SHARING,
|
||||||
WEBROOT,
|
WEBROOT,
|
||||||
} from "../helpers/env";
|
} from "../helpers/env";
|
||||||
import { FIRST_RUN, userService } from "./user";
|
import { FIRST_RUN, userService } from "./user";
|
||||||
|
|
||||||
export const root = new Elysia()
|
export const root = new Elysia().use(userService).get(
|
||||||
.use(userService)
|
"/",
|
||||||
.get("/", async ({ jwt, redirect, cookie: { auth, jobId } }) => {
|
async ({ jwt, redirect, cookie: { auth, jobId } }) => {
|
||||||
if (!ALLOW_UNAUTHENTICATED) {
|
if (!ALLOW_UNAUTHENTICATED) {
|
||||||
if (FIRST_RUN) {
|
if (FIRST_RUN) {
|
||||||
return redirect(`${WEBROOT}/setup`, 302);
|
return redirect(`${WEBROOT}/setup`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!auth?.value) {
|
if (!auth?.value) {
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate jwt
|
// validate jwt
|
||||||
let user: ({ id: string } & JWTPayloadSpec) | false = false;
|
let user: ({ id: string } & JWTPayloadSpec) | false = false;
|
||||||
if (ALLOW_UNAUTHENTICATED) {
|
if (ALLOW_UNAUTHENTICATED) {
|
||||||
const newUserId = String(
|
const newUserId = String(
|
||||||
UNAUTHENTICATED_USER_SHARING
|
UNAUTHENTICATED_USER_SHARING
|
||||||
? 0
|
? 0
|
||||||
: randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
|
: randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
|
||||||
);
|
);
|
||||||
const accessToken = await jwt.sign({
|
const accessToken = await jwt.sign({
|
||||||
id: newUserId,
|
id: newUserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
user = { id: newUserId };
|
user = { id: newUserId };
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
return {
|
return {
|
||||||
message: "No auth cookie, perhaps your browser is blocking cookies.",
|
message: "No auth cookie, perhaps your browser is blocking cookies.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// set cookie
|
// set cookie
|
||||||
auth.set({
|
auth.set({
|
||||||
value: accessToken,
|
value: accessToken,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: !HTTP_ALLOWED,
|
secure: !HTTP_ALLOWED,
|
||||||
maxAge: 24 * 60 * 60,
|
maxAge: 24 * 60 * 60,
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
});
|
});
|
||||||
} else if (auth?.value) {
|
} else if (auth?.value) {
|
||||||
user = await jwt.verify(auth.value);
|
user = await jwt.verify(auth.value);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
user !== false &&
|
user !== false &&
|
||||||
user.id &&
|
user.id &&
|
||||||
(Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED)
|
(Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED)
|
||||||
) {
|
) {
|
||||||
// Make sure user exists in db
|
// Make sure user exists in db
|
||||||
const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
|
const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
if (auth?.value) {
|
if (auth?.value) {
|
||||||
auth.remove();
|
auth.remove();
|
||||||
}
|
}
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return redirect(`${WEBROOT}/login`, 302);
|
return redirect(`${WEBROOT}/login`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new job
|
// create a new job
|
||||||
db.query("INSERT INTO jobs (user_id, date_created) VALUES (?, ?)").run(
|
db.query("INSERT INTO jobs (user_id, date_created) VALUES (?, ?)").run(
|
||||||
user.id,
|
user.id,
|
||||||
new Date().toISOString(),
|
new Date().toISOString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { id } = db
|
const { id } = db
|
||||||
.query("SELECT id FROM jobs WHERE user_id = ? ORDER BY id DESC")
|
.query("SELECT id FROM jobs WHERE user_id = ? ORDER BY id DESC")
|
||||||
.get(user.id) as { id: number };
|
.get(user.id) as { id: number };
|
||||||
|
|
||||||
if (!jobId) {
|
if (!jobId) {
|
||||||
return { message: "Cookies should be enabled to use this app." };
|
return { message: "Cookies should be enabled to use this app." };
|
||||||
}
|
}
|
||||||
|
|
||||||
jobId.set({
|
jobId.set({
|
||||||
value: id,
|
value: id,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: !HTTP_ALLOWED,
|
secure: !HTTP_ALLOWED,
|
||||||
maxAge: 24 * 60 * 60,
|
maxAge: 24 * 60 * 60,
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("jobId set to:", id);
|
console.log("jobId set to:", id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseHtml webroot={WEBROOT}>
|
<BaseHtml webroot={WEBROOT}>
|
||||||
<>
|
<>
|
||||||
<Header
|
<Header
|
||||||
webroot={WEBROOT}
|
webroot={WEBROOT}
|
||||||
accountRegistration={ACCOUNT_REGISTRATION}
|
accountRegistration={ACCOUNT_REGISTRATION}
|
||||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||||
hideHistory={HIDE_HISTORY}
|
hideHistory={HIDE_HISTORY}
|
||||||
loggedIn
|
loggedIn
|
||||||
/>
|
/>
|
||||||
<main
|
<main
|
||||||
class={`
|
class={`
|
||||||
w-full flex-1 px-2
|
w-full flex-1 px-2
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<article class="article">
|
<article class="article">
|
||||||
<h1 class="mb-4 text-xl">Convert</h1>
|
<h1 class="mb-4 text-xl">Convert</h1>
|
||||||
<div class="mb-4 scrollbar-thin max-h-[50vh] overflow-y-auto">
|
<div class="mb-4 scrollbar-thin max-h-[50vh] overflow-y-auto">
|
||||||
<table
|
<table
|
||||||
id="file-list"
|
id="file-list"
|
||||||
class={`
|
class={`
|
||||||
w-full table-auto rounded bg-neutral-900
|
w-full table-auto rounded bg-neutral-900
|
||||||
[&_td]:p-4 [&_td]:first:max-w-[30vw] [&_td]:first:truncate
|
[&_td]:p-4 [&_td]:first:max-w-[30vw] [&_td]:first:truncate
|
||||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="dropzone"
|
id="dropzone"
|
||||||
class={`
|
class={`
|
||||||
relative flex h-48 w-full items-center justify-center rounded border border-dashed
|
relative flex h-48 w-full items-center justify-center rounded border border-dashed
|
||||||
border-neutral-700 transition-all
|
border-neutral-700 transition-all
|
||||||
hover:border-neutral-600
|
hover:border-neutral-600
|
||||||
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<b>Choose a file</b> or drag it here
|
<b>Choose a file</b> or drag it here
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
name="file"
|
name="file"
|
||||||
multiple
|
multiple
|
||||||
class="absolute inset-0 size-full cursor-pointer opacity-0"
|
class="absolute inset-0 size-full cursor-pointer opacity-0"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<form
|
<form
|
||||||
method="post"
|
method="post"
|
||||||
action={`${WEBROOT}/convert`}
|
action={`${WEBROOT}/convert`}
|
||||||
class="relative mx-auto mb-[35vh] w-full max-w-4xl"
|
class="relative mx-auto mb-[35vh] w-full max-w-4xl"
|
||||||
>
|
>
|
||||||
<input type="hidden" name="file_names" id="file_names" />
|
<input type="hidden" name="file_names" id="file_names" />
|
||||||
<article class="article w-full">
|
<article class="article w-full">
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
name="convert_to_search"
|
name="convert_to_search"
|
||||||
placeholder="Search for conversions"
|
placeholder="Search for conversions"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="w-full rounded-sm bg-neutral-800 p-4"
|
class="w-full rounded-sm bg-neutral-800 p-4"
|
||||||
/>
|
/>
|
||||||
<div class="select_container relative">
|
<div class="select_container relative">
|
||||||
<article
|
<article
|
||||||
class={`
|
class={`
|
||||||
convert_to_popup absolute z-2 m-0 hidden h-[30vh] max-h-[50vh] w-full flex-col
|
convert_to_popup absolute z-2 m-0 hidden h-[30vh] max-h-[50vh] w-full flex-col
|
||||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||||
sm:h-[30vh]
|
sm:h-[30vh]
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||||
<article
|
<article
|
||||||
class={`
|
class={`
|
||||||
convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
|
convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
|
||||||
`}
|
`}
|
||||||
data-converter={converter}
|
data-converter={converter}
|
||||||
>
|
>
|
||||||
<header class="mb-2 w-full text-xl font-bold" safe>
|
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||||
{converter}
|
{converter}
|
||||||
</header>
|
</header>
|
||||||
<ul class={`convert_to_target flex flex-row flex-wrap gap-1`}>
|
<ul class={`convert_to_target flex flex-row flex-wrap gap-1`}>
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
<button
|
<button
|
||||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||||
tabindex={0}
|
tabindex={0}
|
||||||
class={`
|
class={`
|
||||||
target rounded bg-neutral-700 p-1 text-base
|
target rounded bg-neutral-700 p-1 text-base
|
||||||
hover:bg-neutral-600
|
hover:bg-neutral-600
|
||||||
`}
|
`}
|
||||||
data-value={`${target},${converter}`}
|
data-value={`${target},${converter}`}
|
||||||
data-target={target}
|
data-target={target}
|
||||||
data-converter={converter}
|
data-converter={converter}
|
||||||
type="button"
|
type="button"
|
||||||
safe
|
safe
|
||||||
>
|
>
|
||||||
{target}
|
{target}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{/* Hidden element which determines the format to convert the file too and the converter to use */}
|
{/* Hidden element which determines the format to convert the file too and the converter to use */}
|
||||||
<select name="convert_to" aria-label="Convert to" required hidden>
|
<select name="convert_to" aria-label="Convert to" required hidden>
|
||||||
<option selected disabled value="">
|
<option selected disabled value="">
|
||||||
Convert to
|
Convert to
|
||||||
</option>
|
</option>
|
||||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||||
<optgroup label={converter}>
|
<optgroup label={converter}>
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
<option value={`${target},${converter}`} safe>
|
<option value={`${target},${converter}`} safe>
|
||||||
{target}
|
{target}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<input
|
<input
|
||||||
class={`
|
class={`
|
||||||
w-full btn-primary opacity-100
|
w-full btn-primary opacity-100
|
||||||
disabled:cursor-not-allowed disabled:opacity-50
|
disabled:cursor-not-allowed disabled:opacity-50
|
||||||
`}
|
`}
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Convert"
|
value="Convert"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
<script src="script.js" defer />
|
<script src="script.js" defer />
|
||||||
</>
|
</>
|
||||||
</BaseHtml>
|
</BaseHtml>
|
||||||
);
|
);
|
||||||
}, {
|
},
|
||||||
cookie: t.Cookie({
|
{
|
||||||
auth: t.Optional(t.String()),
|
cookie: t.Cookie({
|
||||||
jobId: t.Optional(t.String()),
|
auth: t.Optional(t.String()),
|
||||||
})
|
jobId: t.Optional(t.String()),
|
||||||
});
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
@@ -1,39 +1,39 @@
|
|||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { WEBROOT } from "../helpers/env";
|
import { WEBROOT } from "../helpers/env";
|
||||||
import { uploadsDir } from "../index";
|
import { uploadsDir } from "../index";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
export const upload = new Elysia().use(userService).post(
|
export const upload = new Elysia().use(userService).post(
|
||||||
"/upload",
|
"/upload",
|
||||||
async ({ body, redirect, user, cookie: { jobId } }) => {
|
async ({ body, redirect, user, cookie: { jobId } }) => {
|
||||||
if (!jobId?.value) {
|
if (!jobId?.value) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingJob = await db
|
const existingJob = await db
|
||||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||||
.get(jobId.value, user.id);
|
.get(jobId.value, user.id);
|
||||||
|
|
||||||
if (!existingJob) {
|
if (!existingJob) {
|
||||||
return redirect(`${WEBROOT}/`, 302);
|
return redirect(`${WEBROOT}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||||
|
|
||||||
if (body?.file) {
|
if (body?.file) {
|
||||||
if (Array.isArray(body.file)) {
|
if (Array.isArray(body.file)) {
|
||||||
for (const file of body.file) {
|
for (const file of body.file) {
|
||||||
await Bun.write(`${userUploadsDir}${file.name}`, file);
|
await Bun.write(`${userUploadsDir}${file.name}`, file);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await Bun.write(`${userUploadsDir}${body.file["name"]}`, body.file);
|
await Bun.write(`${userUploadsDir}${body.file["name"]}`, body.file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: "Files uploaded successfully.",
|
message: "Files uploaded successfully.",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ body: t.Object({ file: t.Files() }), auth: true },
|
{ body: t.Object({ file: t.Files() }), auth: true },
|
||||||
);
|
);
|
||||||
|
1041
src/pages/user.tsx
1041
src/pages/user.tsx
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,47 @@
|
|||||||
:root {
|
:root {
|
||||||
/* Light mode */
|
/* Light mode */
|
||||||
--contrast: oklch(100% 0 0);
|
--contrast: oklch(100% 0 0);
|
||||||
/* Neutral colors - Gray */
|
/* Neutral colors - Gray */
|
||||||
--neutral-950: oklch(98.5% 0.002 247.839);
|
--neutral-950: oklch(98.5% 0.002 247.839);
|
||||||
--neutral-900: oklch(96.7% 0.003 264.542);
|
--neutral-900: oklch(96.7% 0.003 264.542);
|
||||||
--neutral-800: oklch(92.8% 0.006 264.531);
|
--neutral-800: oklch(92.8% 0.006 264.531);
|
||||||
--neutral-700: oklch(87.2% 0.01 258.338);
|
--neutral-700: oklch(87.2% 0.01 258.338);
|
||||||
--neutral-600: oklch(70.7% 0.022 261.325);
|
--neutral-600: oklch(70.7% 0.022 261.325);
|
||||||
--neutral-500: oklch(55.1% 0.027 264.364);
|
--neutral-500: oklch(55.1% 0.027 264.364);
|
||||||
--neutral-400: oklch(44.6% 0.03 256.802);
|
--neutral-400: oklch(44.6% 0.03 256.802);
|
||||||
--neutral-300: oklch(37.3% 0.034 259.733);
|
--neutral-300: oklch(37.3% 0.034 259.733);
|
||||||
--neutral-200: oklch(26.9% 0 0);
|
--neutral-200: oklch(26.9% 0 0);
|
||||||
--neutral-100: oklch(21% 0.034 264.665);
|
--neutral-100: oklch(21% 0.034 264.665);
|
||||||
--neutral-50: oklch(13% 0.028 261.692);
|
--neutral-50: oklch(13% 0.028 261.692);
|
||||||
/* lime-700 */
|
/* lime-700 */
|
||||||
--accent-600: oklch(53.2% 0.157 131.589);
|
--accent-600: oklch(53.2% 0.157 131.589);
|
||||||
/* lime-600 */
|
/* lime-600 */
|
||||||
--accent-500: oklch(64.8% 0.2 131.684);
|
--accent-500: oklch(64.8% 0.2 131.684);
|
||||||
/* lime-500 */
|
/* lime-500 */
|
||||||
--accent-400: oklch(76.8% 0.233 130.85);
|
--accent-400: oklch(76.8% 0.233 130.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
/* Dark mode */
|
/* Dark mode */
|
||||||
:root {
|
:root {
|
||||||
--contrast: oklch(0% 0 0);
|
--contrast: oklch(0% 0 0);
|
||||||
/* Neutral colors - Gray */
|
/* Neutral colors - Gray */
|
||||||
--neutral-950: oklch(13% 0.028 261.692);
|
--neutral-950: oklch(13% 0.028 261.692);
|
||||||
--neutral-900: oklch(21% 0.034 264.665);
|
--neutral-900: oklch(21% 0.034 264.665);
|
||||||
--neutral-800: oklch(27.8% 0.033 256.848);
|
--neutral-800: oklch(27.8% 0.033 256.848);
|
||||||
--neutral-700: oklch(37.3% 0.034 259.733);
|
--neutral-700: oklch(37.3% 0.034 259.733);
|
||||||
--neutral-600: oklch(44.6% 0.03 256.802);
|
--neutral-600: oklch(44.6% 0.03 256.802);
|
||||||
--neutral-500: oklch(55.1% 0.027 264.364);
|
--neutral-500: oklch(55.1% 0.027 264.364);
|
||||||
--neutral-400: oklch(70.7% 0.022 261.325);
|
--neutral-400: oklch(70.7% 0.022 261.325);
|
||||||
--neutral-300: oklch(87.2% 0.01 258.338);
|
--neutral-300: oklch(87.2% 0.01 258.338);
|
||||||
--neutral-200: oklch(92.8% 0.006 264.531);
|
--neutral-200: oklch(92.8% 0.006 264.531);
|
||||||
--neutral-100: oklch(96.7% 0.003 264.542);
|
--neutral-100: oklch(96.7% 0.003 264.542);
|
||||||
--neutral-50: oklch(98.5% 0.002 247.839);
|
--neutral-50: oklch(98.5% 0.002 247.839);
|
||||||
/* lime-600 */
|
/* lime-600 */
|
||||||
--accent-600: oklch(64.8% 0.2 131.684);
|
--accent-600: oklch(64.8% 0.2 131.684);
|
||||||
/* lime-500 */
|
/* lime-500 */
|
||||||
--accent-500: oklch(76.8% 0.233 130.85);
|
--accent-500: oklch(76.8% 0.233 130.85);
|
||||||
/* lime-400 */
|
/* lime-400 */
|
||||||
--accent-400: oklch(84.1% 0.238 128.85);
|
--accent-400: oklch(84.1% 0.238 128.85);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/assimp";
|
import { convert } from "../../src/converters/assimp";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test.skip("dummy - required to trigger test detection", () => {});
|
test.skip("dummy - required to trigger test detection", () => {});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/calibre";
|
import { convert } from "../../src/converters/calibre";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test.skip("dummy - required to trigger test detection", () => {});
|
test.skip("dummy - required to trigger test detection", () => {});
|
||||||
|
@@ -1,91 +1,91 @@
|
|||||||
import type { ExecFileException } from "node:child_process";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
import { beforeEach, expect, test } from "bun:test";
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { convert } from "../../src/converters/dvisvgm";
|
import { convert } from "../../src/converters/dvisvgm";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
let calls: string[][] = [];
|
let calls: string[][] = [];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
calls = [];
|
calls = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test("convert respects eps filetype", async () => {
|
test("convert respects eps filetype", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.eps", "eps", "stl", "output.stl", undefined, mockExecFile);
|
const result = await convert("input.eps", "eps", "stl", "output.stl", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["--eps", "input.eps", "output.stl"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["--eps", "input.eps", "output.stl"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert respects pdf filetype", async () => {
|
test("convert respects pdf filetype", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.pdf", "pdf", "stl", "output.stl", undefined, mockExecFile);
|
const result = await convert("input.pdf", "pdf", "stl", "output.stl", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["--pdf", "input.pdf", "output.stl"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["--pdf", "input.pdf", "output.stl"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert respects svgz conversion target type", async () => {
|
test("convert respects svgz conversion target type", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.obj", "eps", "svgz", "output.svgz", undefined, mockExecFile);
|
const result = await convert("input.obj", "eps", "svgz", "output.svgz", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["-z", "input.obj", "output.svgz"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["-z", "input.obj", "output.svgz"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
@@ -1,181 +1,181 @@
|
|||||||
import { beforeEach, expect, test } from "bun:test";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/ffmpeg";
|
import { convert } from "../../src/converters/ffmpeg";
|
||||||
|
|
||||||
let calls: string[][] = [];
|
let calls: string[][] = [];
|
||||||
|
|
||||||
function mockExecFile(
|
function mockExecFile(
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
args: string[],
|
args: string[],
|
||||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||||
) {
|
) {
|
||||||
calls.push(args);
|
calls.push(args);
|
||||||
if (args.includes("fail.mov")) {
|
if (args.includes("fail.mov")) {
|
||||||
callback(new Error("mock failure"), "", "Fake stderr: fail");
|
callback(new Error("mock failure"), "", "Fake stderr: fail");
|
||||||
} else {
|
} else {
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
calls = [];
|
calls = [];
|
||||||
delete process.env.FFMPEG_ARGS;
|
delete process.env.FFMPEG_ARGS;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("converts a normal file", async () => {
|
test("converts a normal file", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("in.mp4", "mp4", "avi", "out.avi", undefined, mockExecFile);
|
const result = await convert("in.mp4", "mp4", "avi", "out.avi", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["-i", "in.mp4", "out.avi"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["-i", "in.mp4", "out.avi"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("adds resize for ico output", async () => {
|
test("adds resize for ico output", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("in.png", "png", "ico", "out.ico", undefined, mockExecFile);
|
const result = await convert("in.png", "png", "ico", "out.ico", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done: resized to 256x256");
|
expect(result).toBe("Done: resized to 256x256");
|
||||||
expect(calls[0]).toEqual(
|
expect(calls[0]).toEqual(
|
||||||
expect.arrayContaining(["-filter:v", expect.stringContaining("scale=")]),
|
expect.arrayContaining(["-filter:v", expect.stringContaining("scale=")]),
|
||||||
);
|
);
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses libaom-av1 for av1.mp4", async () => {
|
test("uses libaom-av1 for av1.mp4", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
await convert("in.mkv", "mkv", "av1.mp4", "out.mp4", undefined, mockExecFile);
|
await convert("in.mkv", "mkv", "av1.mp4", "out.mp4", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libaom-av1"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libaom-av1"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses libx264 for h264.mp4", async () => {
|
test("uses libx264 for h264.mp4", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile);
|
await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx264"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx264"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses libx265 for h265.mp4", async () => {
|
test("uses libx265 for h265.mp4", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
await convert("in.mkv", "mkv", "h265.mp4", "out.mp4", undefined, mockExecFile);
|
await convert("in.mkv", "mkv", "h265.mp4", "out.mp4", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx265"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx265"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses libx266 for h266.mp4", async () => {
|
test("uses libx266 for h266.mp4", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
await convert("in.mkv", "mkv", "h266.mp4", "out.mp4", undefined, mockExecFile);
|
await convert("in.mkv", "mkv", "h266.mp4", "out.mp4", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx266"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["-c:v", "libx266"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("respects FFMPEG_ARGS", async () => {
|
test("respects FFMPEG_ARGS", async () => {
|
||||||
process.env.FFMPEG_ARGS = "-hide_banner -y";
|
process.env.FFMPEG_ARGS = "-hide_banner -y";
|
||||||
|
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
await convert("input.mov", "mov", "mp4", "output.mp4", undefined, mockExecFile);
|
await convert("input.mov", "mov", "mp4", "output.mp4", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(calls[0]?.slice(0, 2)).toEqual(["-hide_banner", "-y"]);
|
expect(calls[0]?.slice(0, 2)).toEqual(["-hide_banner", "-y"]);
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("fails on exec error", async () => {
|
test("fails on exec error", async () => {
|
||||||
const originalConsoleError = console.error;
|
const originalConsoleError = console.error;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.error = (msg) => {
|
console.error = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(convert("fail.mov", "mov", "mp4", "output.mp4", undefined, mockExecFile)).rejects.toThrow(
|
expect(convert("fail.mov", "mov", "mp4", "output.mp4", undefined, mockExecFile)).rejects.toThrow(
|
||||||
"mock failure",
|
"mock failure",
|
||||||
);
|
);
|
||||||
|
|
||||||
console.error = originalConsoleError;
|
console.error = originalConsoleError;
|
||||||
|
|
||||||
expect(loggedMessage).toBe("stderr: Fake stderr: fail");
|
expect(loggedMessage).toBe("stderr: Fake stderr: fail");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("logs stderr when execFile returns only stderr and no error", async () => {
|
test("logs stderr when execFile returns only stderr and no error", async () => {
|
||||||
const originalConsoleError = console.error;
|
const originalConsoleError = console.error;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.error = (msg) => {
|
console.error = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock execFile to call back with no error, no stdout, but with stderr
|
// Mock execFile to call back with no error, no stdout, but with stderr
|
||||||
const mockExecFileStderrOnly = (
|
const mockExecFileStderrOnly = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback(null, "", "Only stderr output");
|
callback(null, "", "Only stderr output");
|
||||||
};
|
};
|
||||||
|
|
||||||
await convert("input.mov", "mov", "mp4", "output.mp4", undefined, mockExecFileStderrOnly);
|
await convert("input.mov", "mov", "mp4", "output.mp4", undefined, mockExecFileStderrOnly);
|
||||||
|
|
||||||
console.error = originalConsoleError;
|
console.error = originalConsoleError;
|
||||||
|
|
||||||
expect(loggedMessage).toBe("stderr: Only stderr output");
|
expect(loggedMessage).toBe("stderr: Only stderr output");
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/graphicsmagick";
|
import { convert } from "../../src/converters/graphicsmagick";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test.skip("dummy - required to trigger test detection", () => {});
|
test.skip("dummy - required to trigger test detection", () => {});
|
||||||
|
@@ -1,26 +1,26 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { ConvertFnWithExecFile } from "../../../src/converters/types";
|
import { ConvertFnWithExecFile } from "../../../src/converters/types";
|
||||||
import {
|
import {
|
||||||
runConvertFailTest,
|
runConvertFailTest,
|
||||||
runConvertLogsStderror,
|
runConvertLogsStderror,
|
||||||
runConvertLogsStderrorAndStdout,
|
runConvertLogsStderrorAndStdout,
|
||||||
runConvertSuccessTest,
|
runConvertSuccessTest,
|
||||||
} from "./converters";
|
} from "./converters";
|
||||||
|
|
||||||
export function runCommonTests(convert: ConvertFnWithExecFile) {
|
export function runCommonTests(convert: ConvertFnWithExecFile) {
|
||||||
test("convert resolves when execFile succeeds", async () => {
|
test("convert resolves when execFile succeeds", async () => {
|
||||||
await runConvertSuccessTest(convert);
|
await runConvertSuccessTest(convert);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert rejects when execFile fails", async () => {
|
test("convert rejects when execFile fails", async () => {
|
||||||
await runConvertFailTest(convert);
|
await runConvertFailTest(convert);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert logs stderr when present", async () => {
|
test("convert logs stderr when present", async () => {
|
||||||
await runConvertLogsStderror(convert);
|
await runConvertLogsStderror(convert);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert logs both stderr and stdout when present", async () => {
|
test("convert logs both stderr and stdout when present", async () => {
|
||||||
await runConvertLogsStderrorAndStdout(convert);
|
await runConvertLogsStderrorAndStdout(convert);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,121 +1,121 @@
|
|||||||
import type { ExecFileException } from "node:child_process";
|
import { expect } from "bun:test";
|
||||||
import { expect } from "bun:test";
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { ConvertFnWithExecFile, ExecFileFn } from "../../../src/converters/types";
|
import { ConvertFnWithExecFile, ExecFileFn } from "../../../src/converters/types";
|
||||||
|
|
||||||
export async function runConvertSuccessTest(convertFn: ConvertFnWithExecFile) {
|
export async function runConvertSuccessTest(convertFn: ConvertFnWithExecFile) {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile);
|
const result = await convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runConvertFailTest(convertFn: ConvertFnWithExecFile) {
|
export async function runConvertFailTest(convertFn: ConvertFnWithExecFile) {
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback(new Error("Test error"), "", "");
|
callback(new Error("Test error"), "", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile),
|
convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile),
|
||||||
).rejects.toMatch(/error: Error: Test error/);
|
).rejects.toMatch(/error: Error: Test error/);
|
||||||
|
|
||||||
// Test with error object lacking 'message' property
|
// Test with error object lacking 'message' property
|
||||||
const mockExecFileNoMessage: ExecFileFn = (
|
const mockExecFileNoMessage: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
// Simulate a non-standard error object
|
// Simulate a non-standard error object
|
||||||
callback({ notMessage: true } as unknown as ExecFileException, "", "");
|
callback({ notMessage: true } as unknown as ExecFileException, "", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFileNoMessage),
|
convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFileNoMessage),
|
||||||
).rejects.toMatch(/error:/i);
|
).rejects.toMatch(/error:/i);
|
||||||
|
|
||||||
// Test with a non-object error (e.g., a string)
|
// Test with a non-object error (e.g., a string)
|
||||||
const mockExecFileStringError: ExecFileFn = (
|
const mockExecFileStringError: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback("string error" as unknown as ExecFileException, "", "");
|
callback("string error" as unknown as ExecFileException, "", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFileStringError),
|
convertFn("input.obj", "obj", "stl", "output.stl", undefined, mockExecFileStringError),
|
||||||
).rejects.toMatch(/error:/i);
|
).rejects.toMatch(/error:/i);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runConvertLogsStderror(convertFn: ConvertFnWithExecFile) {
|
export async function runConvertLogsStderror(convertFn: ConvertFnWithExecFile) {
|
||||||
const originalConsoleError = console.error;
|
const originalConsoleError = console.error;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.error = (msg) => {
|
console.error = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile = (
|
const mockExecFile = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback(null, "", "Fake stderr");
|
callback(null, "", "Fake stderr");
|
||||||
};
|
};
|
||||||
|
|
||||||
await convertFn("file.obj", "obj", "stl", "out.stl", undefined, mockExecFile);
|
await convertFn("file.obj", "obj", "stl", "out.stl", undefined, mockExecFile);
|
||||||
|
|
||||||
console.error = originalConsoleError;
|
console.error = originalConsoleError;
|
||||||
|
|
||||||
expect(loggedMessage).toBe("stderr: Fake stderr");
|
expect(loggedMessage).toBe("stderr: Fake stderr");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runConvertLogsStderrorAndStdout(convertFn: ConvertFnWithExecFile) {
|
export async function runConvertLogsStderrorAndStdout(convertFn: ConvertFnWithExecFile) {
|
||||||
const originalConsoleError = console.error;
|
const originalConsoleError = console.error;
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedError = "";
|
let loggedError = "";
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.error = (msg) => {
|
console.error = (msg) => {
|
||||||
loggedError = msg;
|
loggedError = msg;
|
||||||
};
|
};
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile = (
|
const mockExecFile = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback(null, "Fake stdout", "Fake stderr");
|
callback(null, "Fake stdout", "Fake stderr");
|
||||||
};
|
};
|
||||||
|
|
||||||
await convertFn("file.obj", "obj", "stl", "out.stl", undefined, mockExecFile);
|
await convertFn("file.obj", "obj", "stl", "out.stl", undefined, mockExecFile);
|
||||||
|
|
||||||
console.error = originalConsoleError;
|
console.error = originalConsoleError;
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(loggedError).toBe("stderr: Fake stderr");
|
expect(loggedError).toBe("stderr: Fake stderr");
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
}
|
}
|
||||||
|
@@ -1,165 +1,165 @@
|
|||||||
import type { ExecFileException } from "node:child_process";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
import { beforeEach, expect, test } from "bun:test";
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { convert } from "../../src/converters/imagemagick";
|
import { convert } from "../../src/converters/imagemagick";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
let calls: string[][] = [];
|
let calls: string[][] = [];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
calls = [];
|
calls = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test("convert respects ico conversion target type", async () => {
|
test("convert respects ico conversion target type", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.obj", "eps", "ico", "output.ico", undefined, mockExecFile);
|
const result = await convert("input.obj", "eps", "ico", "output.ico", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(
|
expect(calls[0]).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
"-define",
|
"-define",
|
||||||
"icon:auto-resize=256,128,64,48,32,16",
|
"icon:auto-resize=256,128,64,48,32,16",
|
||||||
"-background",
|
"-background",
|
||||||
"none",
|
"none",
|
||||||
"input.obj",
|
"input.obj",
|
||||||
"output.ico",
|
"output.ico",
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert respects ico conversion target type with svg as input filetype", async () => {
|
test("convert respects ico conversion target type with svg as input filetype", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.svg", "svg", "ico", "output.ico", undefined, mockExecFile);
|
const result = await convert("input.svg", "svg", "ico", "output.ico", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(
|
expect(calls[0]).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
"-define",
|
"-define",
|
||||||
"icon:auto-resize=256,128,64,48,32,16",
|
"icon:auto-resize=256,128,64,48,32,16",
|
||||||
"-background",
|
"-background",
|
||||||
"none",
|
"none",
|
||||||
"-density",
|
"-density",
|
||||||
"512",
|
"512",
|
||||||
"input.svg",
|
"input.svg",
|
||||||
"output.ico",
|
"output.ico",
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert respects ico conversion target type with emf as input filetype", async () => {
|
test("convert respects ico conversion target type with emf as input filetype", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.emf", "emf", "ico", "output.ico", undefined, mockExecFile);
|
const result = await convert("input.emf", "emf", "ico", "output.ico", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(
|
expect(calls[0]).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
"-define",
|
"-define",
|
||||||
"icon:auto-resize=256,128,64,48,32,16",
|
"icon:auto-resize=256,128,64,48,32,16",
|
||||||
"-background",
|
"-background",
|
||||||
"none",
|
"none",
|
||||||
"emf:delegate=false",
|
"emf:delegate=false",
|
||||||
"-density",
|
"-density",
|
||||||
"300",
|
"300",
|
||||||
"white",
|
"white",
|
||||||
"-alpha",
|
"-alpha",
|
||||||
"remove",
|
"remove",
|
||||||
"input.emf",
|
"input.emf",
|
||||||
"output.ico",
|
"output.ico",
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert respects emf as input filetype", async () => {
|
test("convert respects emf as input filetype", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.emf", "emf", "obj", "output.obj", undefined, mockExecFile);
|
const result = await convert("input.emf", "emf", "obj", "output.obj", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(
|
expect(calls[0]).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
"-define",
|
"-define",
|
||||||
"emf:delegate=false",
|
"emf:delegate=false",
|
||||||
"-density",
|
"-density",
|
||||||
"300",
|
"300",
|
||||||
"-background",
|
"-background",
|
||||||
"white",
|
"white",
|
||||||
"-alpha",
|
"-alpha",
|
||||||
"remove",
|
"remove",
|
||||||
"input.emf",
|
"input.emf",
|
||||||
"output.obj",
|
"output.obj",
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/inkscape";
|
import { convert } from "../../src/converters/inkscape";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test.skip("dummy - required to trigger test detection", () => {});
|
test.skip("dummy - required to trigger test detection", () => {});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/libheif";
|
import { convert } from "../../src/converters/libheif";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test.skip("dummy - required to trigger test detection", () => {});
|
test.skip("dummy - required to trigger test detection", () => {});
|
||||||
|
@@ -1,91 +1,91 @@
|
|||||||
import type { ExecFileException } from "node:child_process";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
import { beforeEach, expect, test } from "bun:test";
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { convert } from "../../src/converters/libjxl";
|
import { convert } from "../../src/converters/libjxl";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
let command: string = "";
|
let command: string = "";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
command = "";
|
command = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test("convert uses djxl with input filetype being jxl", async () => {
|
test("convert uses djxl with input filetype being jxl", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
command = _cmd;
|
command = _cmd;
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.jxl", "jxl", "png", "output.png", undefined, mockExecFile);
|
const result = await convert("input.jxl", "jxl", "png", "output.png", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(command).toEqual("djxl");
|
expect(command).toEqual("djxl");
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert uses cjxl with output filetype being jxl", async () => {
|
test("convert uses cjxl with output filetype being jxl", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
command = _cmd;
|
command = _cmd;
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.png", "png", "jxl", "output.jxl", undefined, mockExecFile);
|
const result = await convert("input.png", "png", "jxl", "output.jxl", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(command).toEqual("cjxl");
|
expect(command).toEqual("cjxl");
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert uses empty string as command with neither input nor output filetype being jxl", async () => {
|
test("convert uses empty string as command with neither input nor output filetype being jxl", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
command = _cmd;
|
command = _cmd;
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.png", "png", "jpg", "output.jpg", undefined, mockExecFile);
|
const result = await convert("input.png", "png", "jpg", "output.jpg", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(command).toEqual("");
|
expect(command).toEqual("");
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
@@ -1,61 +1,61 @@
|
|||||||
import type { ExecFileException } from "node:child_process";
|
import { expect, test } from "bun:test";
|
||||||
import { expect, test } from "bun:test";
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { convert } from "../../src/converters/msgconvert";
|
import { convert } from "../../src/converters/msgconvert";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
|
|
||||||
test("convert rejects conversion if input filetype is not msg and output type is not eml", async () => {
|
test("convert rejects conversion if input filetype is not msg and output type is not eml", async () => {
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedError = new Error(
|
const expectedError = new Error(
|
||||||
"Unsupported conversion from obj to stl. Only MSG to EML conversion is currently supported.",
|
"Unsupported conversion from obj to stl. Only MSG to EML conversion is currently supported.",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(convert("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile)).rejects.toEqual(
|
expect(convert("input.obj", "obj", "stl", "output.stl", undefined, mockExecFile)).rejects.toEqual(
|
||||||
expectedError,
|
expectedError,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert rejects conversion on error", async () => {
|
test("convert rejects conversion on error", async () => {
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback(new Error("Test error"), "", "");
|
callback(new Error("Test error"), "", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedError = new Error("msgconvert failed: Test error");
|
const expectedError = new Error("msgconvert failed: Test error");
|
||||||
|
|
||||||
expect(convert("input.msg", "msg", "eml", "output.eml", undefined, mockExecFile)).rejects.toEqual(
|
expect(convert("input.msg", "msg", "eml", "output.eml", undefined, mockExecFile)).rejects.toEqual(
|
||||||
expectedError,
|
expectedError,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert logs stderr as warning", async () => {
|
test("convert logs stderr as warning", async () => {
|
||||||
const originalConsoleWarn = console.warn;
|
const originalConsoleWarn = console.warn;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.warn = (msg) => {
|
console.warn = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile = (
|
const mockExecFile = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
callback(null, "", "Fake stderr");
|
callback(null, "", "Fake stderr");
|
||||||
};
|
};
|
||||||
|
|
||||||
await convert("file.msg", "msg", "eml", "out.eml", undefined, mockExecFile);
|
await convert("file.msg", "msg", "eml", "out.eml", undefined, mockExecFile);
|
||||||
|
|
||||||
console.error = originalConsoleWarn;
|
console.error = originalConsoleWarn;
|
||||||
|
|
||||||
expect(loggedMessage).toBe("msgconvert stderr: Fake stderr");
|
expect(loggedMessage).toBe("msgconvert stderr: Fake stderr");
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/potrace";
|
import { convert } from "../../src/converters/potrace";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test.skip("dummy - required to trigger test detection", () => {});
|
test.skip("dummy - required to trigger test detection", () => {});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/resvg";
|
import { convert } from "../../src/converters/resvg";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test.skip("dummy - required to trigger test detection", () => {});
|
test.skip("dummy - required to trigger test detection", () => {});
|
||||||
|
@@ -1,65 +1,65 @@
|
|||||||
import type { ExecFileException } from "node:child_process";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
import { beforeEach, expect, test } from "bun:test";
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
import { convert } from "../../src/converters/vips";
|
import { convert } from "../../src/converters/vips";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
let calls: string[][] = [];
|
let calls: string[][] = [];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
calls = [];
|
calls = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test("convert uses action pdfload with filetype being pdf", async () => {
|
test("convert uses action pdfload with filetype being pdf", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.pdf", "pdf", "obj", "output.obj", undefined, mockExecFile);
|
const result = await convert("input.pdf", "pdf", "obj", "output.obj", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["pdfload"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["pdfload"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("convert uses action copy with filetype being anything but pdf", async () => {
|
test("convert uses action copy with filetype being anything but pdf", async () => {
|
||||||
const originalConsoleLog = console.log;
|
const originalConsoleLog = console.log;
|
||||||
|
|
||||||
let loggedMessage = "";
|
let loggedMessage = "";
|
||||||
console.log = (msg) => {
|
console.log = (msg) => {
|
||||||
loggedMessage = msg;
|
loggedMessage = msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockExecFile: ExecFileFn = (
|
const mockExecFile: ExecFileFn = (
|
||||||
_cmd: string,
|
_cmd: string,
|
||||||
_args: string[],
|
_args: string[],
|
||||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||||
) => {
|
) => {
|
||||||
calls.push(_args);
|
calls.push(_args);
|
||||||
callback(null, "Fake stdout", "");
|
callback(null, "Fake stdout", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await convert("input.jpg", "jpg", "obj", "output.obj", undefined, mockExecFile);
|
const result = await convert("input.jpg", "jpg", "obj", "output.obj", undefined, mockExecFile);
|
||||||
|
|
||||||
console.log = originalConsoleLog;
|
console.log = originalConsoleLog;
|
||||||
|
|
||||||
expect(result).toBe("Done");
|
expect(result).toBe("Done");
|
||||||
expect(calls[0]).toEqual(expect.arrayContaining(["copy"]));
|
expect(calls[0]).toEqual(expect.arrayContaining(["copy"]));
|
||||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
import { convert } from "../../src/converters/xelatex";
|
import { convert } from "../../src/converters/xelatex";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
||||||
runCommonTests(convert);
|
runCommonTests(convert);
|
||||||
|
|
||||||
test.skip("dummy - required to trigger test detection", () => {});
|
test.skip("dummy - required to trigger test detection", () => {});
|
||||||
|
@@ -1,34 +1,34 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext"],
|
"lib": ["ESNext"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ES2021",
|
"target": "ES2021",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
// "allowImportingTsExtensions": true,
|
// "allowImportingTsExtensions": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"jsxFactory": "Html.createElement",
|
"jsxFactory": "Html.createElement",
|
||||||
"jsxFragmentFactory": "Html.Fragment",
|
"jsxFragmentFactory": "Html.Fragment",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
// non bun init
|
// non bun init
|
||||||
"plugins": [{ "name": "@kitajs/ts-html-plugin" }],
|
"plugins": [{ "name": "@kitajs/ts-html-plugin" }],
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
// "noUnusedLocals": true,
|
// "noUnusedLocals": true,
|
||||||
// "noUnusedParameters": true,
|
// "noUnusedParameters": true,
|
||||||
"exactOptionalPropertyTypes": true,
|
"exactOptionalPropertyTypes": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
// "noImplicitReturns": true
|
// "noImplicitReturns": true
|
||||||
},
|
},
|
||||||
"include": ["src", "tests", "package.json", "reset.d.ts"]
|
"include": ["src", "tests", "package.json", "reset.d.ts"]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user