mirror of
https://github.com/C4illin/ConvertX.git
synced 2025-11-05 14:35:27 +00:00
Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
663b1d4171 | ||
|
|
c3067ca12d | ||
|
|
4561ca3760 | ||
|
|
698cce58ce | ||
|
|
339b79f786 | ||
|
|
4f98f778f0 | ||
|
|
8479b33a47 | ||
|
|
78844d7bd5 | ||
|
|
64e4a271e1 | ||
|
|
5fb8c3575b | ||
|
|
a6b8bcecae | ||
|
|
bc9c820820 | ||
|
|
ee9207a7f4 | ||
|
|
a34e215202 | ||
|
|
b4e53dbb8e | ||
|
|
b5e8d82bfa | ||
|
|
5d9000bb33 | ||
|
|
ccb065ef0f | ||
|
|
883fad806b | ||
|
|
feacd1b816 | ||
|
|
094e7a0d1c | ||
|
|
72636c5059 | ||
|
|
291cfc80c6 | ||
|
|
ae1dfafc9d | ||
|
|
6caa583c35 | ||
|
|
2057167576 | ||
|
|
1c9e67fc32 | ||
|
|
d3af9688c6 | ||
|
|
7d0cbb9844 | ||
|
|
88173891ba | ||
|
|
2b4b8f9551 | ||
|
|
63a4328d4a | ||
|
|
413f5dc7b4 | ||
|
|
ebccdf9169 | ||
|
|
47139a550b | ||
|
|
fa5446c446 | ||
|
|
8772e582b0 | ||
|
|
45922ed3a3 | ||
|
|
4c747e8908 | ||
|
|
e573997aa9 | ||
|
|
c57b69991c | ||
|
|
eee983a56a | ||
|
|
22f823c535 | ||
|
|
ed59cd7aa4 | ||
|
|
b28977ffe2 | ||
|
|
a47bb682a5 | ||
|
|
a17eca0a09 | ||
|
|
ea9250543e | ||
|
|
317c932c2a | ||
|
|
5b1703db68 | ||
|
|
60ba7c93fb | ||
|
|
22227130dd | ||
|
|
5daf66f5d0 | ||
|
|
aee1962607 | ||
|
|
0d42762b36 | ||
|
|
b97b12b449 | ||
|
|
bdf651df82 | ||
|
|
267ef14789 | ||
|
|
905adc5e1c | ||
|
|
52ed7274e9 | ||
|
|
a29238c265 | ||
|
|
48c6fb79fc | ||
|
|
8358396656 | ||
|
|
b30e5800c3 | ||
|
|
21a1b50ed8 | ||
|
|
e6a94fb21d | ||
|
|
bef1710e33 | ||
|
|
16b322d4e6 | ||
|
|
9bf64e42d5 | ||
|
|
5988fe8212 | ||
|
|
5df9c0b751 | ||
|
|
136a8b2d74 | ||
|
|
ccfb574d5d | ||
|
|
ad6eedea69 | ||
|
|
c3082db8f7 | ||
|
|
a1f8cbae66 | ||
|
|
bb34bdee87 | ||
|
|
11fcbc3f96 | ||
|
|
f7344e4c65 | ||
|
|
781310f3dc | ||
|
|
3f063644f2 | ||
|
|
081634b610 | ||
|
|
cf3da08c73 | ||
|
|
5f7234d6c1 | ||
|
|
6597c1d7ca | ||
|
|
ecb2c75008 | ||
|
|
d5eeef9f68 | ||
|
|
7456174022 | ||
|
|
bc4ad49285 | ||
|
|
f0d0e43929 | ||
|
|
8ca4f1587d | ||
|
|
1535377bfe | ||
|
|
83bf78fd57 | ||
|
|
4d9c4d64aa | ||
|
|
53fff594fc | ||
|
|
fe4aeaff03 | ||
|
|
2078cb0ee0 | ||
|
|
86a61d35d7 | ||
|
|
96fa7e2f55 | ||
|
|
7d2af46b0b | ||
|
|
57e2999866 | ||
|
|
6fb8ca4d82 | ||
|
|
c295e546bd | ||
|
|
f7abb9389c | ||
|
|
d7de154eda | ||
|
|
20bd111765 | ||
|
|
eadd0da291 | ||
|
|
52294465fb | ||
|
|
049e9163ce | ||
|
|
d466d2dbbc | ||
|
|
3f79ccaa2a | ||
|
|
1e9bde18c7 | ||
|
|
9af23346bf | ||
|
|
d310341fca | ||
|
|
d88a755c13 | ||
|
|
7c6085c685 | ||
|
|
7ed1ad21f2 | ||
|
|
8a2237fbd9 | ||
|
|
0e363f0731 | ||
|
|
4074647b67 | ||
|
|
c84968be50 | ||
|
|
0e53a99d43 | ||
|
|
bdd0cf556f | ||
|
|
2483274388 | ||
|
|
4c5129910a | ||
|
|
fe13a1b736 | ||
|
|
f1ac71b397 | ||
|
|
1b1067a03f |
@@ -1,46 +0,0 @@
|
|||||||
/** @type {import("eslint").Linter.Config} */
|
|
||||||
const config = {
|
|
||||||
root: true,
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
plugins: ["isaacscript", "import"],
|
|
||||||
extends: [
|
|
||||||
"plugin:@typescript-eslint/recommended-type-checked",
|
|
||||||
"plugin:@typescript-eslint/stylistic-type-checked",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
project: ["./tsconfig.json"],
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
// Template files don't have reliable type information
|
|
||||||
{
|
|
||||||
extends: ["plugin:@typescript-eslint/disable-type-checked"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
// These off/not-configured-the-way-we-want lint rules we like & opt into
|
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{ argsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_" },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
|
||||||
"error",
|
|
||||||
{ prefer: "type-imports", fixStyle: "inline-type-imports" },
|
|
||||||
],
|
|
||||||
"import/consistent-type-specifier-style": ["error", "prefer-inline"],
|
|
||||||
|
|
||||||
// For educational purposes we format our comments/jsdoc nicely
|
|
||||||
"isaacscript/complete-sentences-jsdoc": "warn",
|
|
||||||
"isaacscript/format-jsdoc-comments": "warn",
|
|
||||||
|
|
||||||
// These lint rules don't make sense for us but are enabled in the preset configs
|
|
||||||
"@typescript-eslint/no-confusing-void-expression": "off",
|
|
||||||
"@typescript-eslint/restrict-template-expressions": "off",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
23
.github/dependabot.yml
vendored
23
.github/dependabot.yml
vendored
@@ -1,23 +0,0 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: npm
|
|
||||||
versioning-strategy: increase
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
commit-message:
|
|
||||||
prefix: "build"
|
|
||||||
include: "scope"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
commit-message:
|
|
||||||
prefix: "build"
|
|
||||||
include: "scope"
|
|
||||||
28
.github/workflows/bun-dependabot.yml
vendored
28
.github/workflows/bun-dependabot.yml
vendored
@@ -1,28 +0,0 @@
|
|||||||
name: 'Dependabot: Update bun.lockb'
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- "package.json"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-bun-lockb:
|
|
||||||
name: "Update bun.lockb"
|
|
||||||
if: github.actor == 'dependabot[bot]'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: oven-sh/setup-bun@v2
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
|
||||||
- run: |
|
|
||||||
bun install
|
|
||||||
git add bun.lockb
|
|
||||||
git config --global user.name 'dependabot[bot]'
|
|
||||||
git config --global user.email 'dependabot[bot]@users.noreply.github.com'
|
|
||||||
git commit --amend --no-edit
|
|
||||||
git push --force
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -48,3 +48,4 @@ package-lock.json
|
|||||||
/data
|
/data
|
||||||
/Bruno
|
/Bruno
|
||||||
/tsconfig.tsbuildinfo
|
/tsconfig.tsbuildinfo
|
||||||
|
/src/public/generated.css
|
||||||
83
CHANGELOG.md
83
CHANGELOG.md
@@ -1,5 +1,88 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.8.1](https://github.com/C4illin/ConvertX/compare/v0.8.0...v0.8.1) (2024-10-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* disable convert button when input is empty ([78844d7](https://github.com/C4illin/ConvertX/commit/78844d7bd55990789ed07c81e49043e688cbe656)), closes [#151](https://github.com/C4illin/ConvertX/issues/151)
|
||||||
|
* resize to fit for ico ([b4e53db](https://github.com/C4illin/ConvertX/commit/b4e53dbb8e70b3a95b44e5b756759d16117a87e1)), closes [#157](https://github.com/C4illin/ConvertX/issues/157)
|
||||||
|
* treat jfif as jpeg ([339b79f](https://github.com/C4illin/ConvertX/commit/339b79f786131deb93f0d5683e03178fdcab1ef5)), closes [#163](https://github.com/C4illin/ConvertX/issues/163)
|
||||||
|
|
||||||
|
## [0.8.0](https://github.com/C4illin/ConvertX/compare/v0.7.0...v0.8.0) (2024-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add light theme, fixes [#156](https://github.com/C4illin/ConvertX/issues/156) ([72636c5](https://github.com/C4illin/ConvertX/commit/72636c5059ebf09c8fece2e268293650b2f8ccf6))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add support for usd for assimp, [#144](https://github.com/C4illin/ConvertX/issues/144) ([2057167](https://github.com/C4illin/ConvertX/commit/20571675766209ad1251f07e687d29a6791afc8b))
|
||||||
|
* cleanup formats and add opus, fixes [#159](https://github.com/C4illin/ConvertX/issues/159) ([ae1dfaf](https://github.com/C4illin/ConvertX/commit/ae1dfafc9d9116a57b08c2f7fc326990e00824b0))
|
||||||
|
* support .awb and clean up, fixes [#153](https://github.com/C4illin/ConvertX/issues/153), [#92](https://github.com/C4illin/ConvertX/issues/92) ([1c9e67f](https://github.com/C4illin/ConvertX/commit/1c9e67fc3201e0e5dee91e8981adf34daaabf33a))
|
||||||
|
|
||||||
|
## [0.7.0](https://github.com/C4illin/ConvertX/compare/v0.6.0...v0.7.0) (2024-09-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add support for 3d assets through assimp converter ([63a4328](https://github.com/C4illin/ConvertX/commit/63a4328d4a1e01df3e0ec4a877bad8c8ffe71129))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* wrong layout on search with few options ([8817389](https://github.com/C4illin/ConvertX/commit/88173891ba2d69da46eda46f3f598a9b54f26f96))
|
||||||
|
|
||||||
|
## [0.6.0](https://github.com/C4illin/ConvertX/compare/v0.5.0...v0.6.0) (2024-09-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ui remake with tailwind ([22f823c](https://github.com/C4illin/ConvertX/commit/22f823c535b20382981f86a13616b830a1f3392f))
|
||||||
|
|
||||||
|
|
||||||
|
### 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))
|
||||||
|
|
||||||
|
## [0.5.0](https://github.com/C4illin/ConvertX/compare/v0.4.1...v0.5.0) (2024-09-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add option to customize how often files are automatically deleted ([317c932](https://github.com/C4illin/ConvertX/commit/317c932c2a26280bf37ed3d3bf9b879413590f5a))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* improve file name replacement logic ([60ba7c9](https://github.com/C4illin/ConvertX/commit/60ba7c93fbdc961f3569882fade7cc13dee7a7a5))
|
||||||
|
|
||||||
|
## [0.4.1](https://github.com/C4illin/ConvertX/compare/v0.4.0...v0.4.1) (2024-09-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow non lowercase true and false values, fixes [#122](https://github.com/C4illin/ConvertX/issues/122) ([bef1710](https://github.com/C4illin/ConvertX/commit/bef1710e3376baa7e25c107ded20a40d18b8c6b0))
|
||||||
|
|
||||||
|
## [0.4.0](https://github.com/C4illin/ConvertX/compare/v0.3.3...v0.4.0) (2024-08-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add option for unauthenticated file conversions [#114](https://github.com/C4illin/ConvertX/issues/114) ([f0d0e43](https://github.com/C4illin/ConvertX/commit/f0d0e4392983c3e4c530304ea88e023fda9bcac0))
|
||||||
|
* add resvg converter ([d5eeef9](https://github.com/C4illin/ConvertX/commit/d5eeef9f6884b2bb878508bed97ea9ceaa662995))
|
||||||
|
* add robots.txt ([6597c1d](https://github.com/C4illin/ConvertX/commit/6597c1d7caeb4dfb6bc47b442e4dfc9840ad12b7))
|
||||||
|
* Add search bar for formats ([53fff59](https://github.com/C4illin/ConvertX/commit/53fff594fc4d69306abcb2a5cad890fcd0953a58))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* keep unauthenticated user logged in if allowed [#114](https://github.com/C4illin/ConvertX/issues/114) ([bc4ad49](https://github.com/C4illin/ConvertX/commit/bc4ad492852fad8cb832a0c03485cccdd7f7b117))
|
||||||
|
* pdf support in vips ([8ca4f15](https://github.com/C4illin/ConvertX/commit/8ca4f1587df7f358893941c656d78d75f04dac93))
|
||||||
|
* Slow click on conversion popup does not work ([4d9c4d6](https://github.com/C4illin/ConvertX/commit/4d9c4d64aa0266f3928935ada68d91ac81f638aa))
|
||||||
|
|
||||||
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
FROM oven/bun:1-debian as base
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# install dependencies into temp directory
|
|
||||||
# this will cache them and speed up future builds
|
|
||||||
FROM base AS install
|
|
||||||
RUN mkdir -p /temp/dev
|
|
||||||
COPY package.json bun.lockb /temp/dev/
|
|
||||||
RUN cd /temp/dev && bun install --frozen-lockfile
|
|
||||||
|
|
||||||
# install with --production (exclude devDependencies)
|
|
||||||
RUN mkdir -p /temp/prod
|
|
||||||
COPY package.json bun.lockb /temp/prod/
|
|
||||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
|
||||||
|
|
||||||
# FROM base AS install-libjxl-tools
|
|
||||||
# download
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# copy node_modules from temp directory
|
|
||||||
# then copy all (non-ignored) project files into the image
|
|
||||||
# FROM base AS prerelease
|
|
||||||
# COPY --from=install /temp/dev/node_modules node_modules
|
|
||||||
# COPY . .
|
|
||||||
|
|
||||||
# # [optional] tests & build
|
|
||||||
# ENV NODE_ENV=production
|
|
||||||
# RUN bun test
|
|
||||||
# RUN bun run build
|
|
||||||
|
|
||||||
# copy production dependencies and source code into final image
|
|
||||||
FROM base AS release
|
|
||||||
LABEL maintainer="Emrik Östling (C4illin)"
|
|
||||||
LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats."
|
|
||||||
LABEL repo="https://github.com/C4illin/ConvertX"
|
|
||||||
|
|
||||||
# install additional dependencies
|
|
||||||
RUN rm -rf /var/lib/apt/lists/partial && apt-get update -o Acquire::CompressionTypes::Order::=gz \
|
|
||||||
&& apt-get install -y \
|
|
||||||
pandoc \
|
|
||||||
texlive-latex-recommended \
|
|
||||||
texlive-fonts-recommended \
|
|
||||||
texlive-latex-extra \
|
|
||||||
ffmpeg \
|
|
||||||
graphicsmagick \
|
|
||||||
ghostscript \
|
|
||||||
libvips-tools
|
|
||||||
|
|
||||||
# # libjxl is not available in the official debian repositories
|
|
||||||
# RUN wget https://github.com/libjxl/libjxl/releases/download/v0.10.2/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -O /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz \
|
|
||||||
# && mkdir -p /tmp/libjxl \
|
|
||||||
# && tar -xvf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -C /tmp/libjxl \
|
|
||||||
# && dpkg -i /tmp/libjxl/libjxl_0.10.2_amd64.deb /tmp/libjxl/jxl_0.10.2_amd64.deb \
|
|
||||||
# && rm -rf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz /tmp/libjxl
|
|
||||||
|
|
||||||
COPY --from=install /temp/prod/node_modules node_modules
|
|
||||||
# COPY --from=prerelease /app/src/index.tsx /app/src/
|
|
||||||
# COPY --from=prerelease /app/package.json .
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
EXPOSE 3000/tcp
|
|
||||||
ENTRYPOINT [ "bun", "run", "./src/index.tsx" ]
|
|
||||||
26
Dockerfile
26
Dockerfile
@@ -1,4 +1,5 @@
|
|||||||
FROM oven/bun:1.1.21-alpine as base
|
FROM oven/bun:1.1.29-alpine AS base
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# install dependencies into temp directory
|
# install dependencies into temp directory
|
||||||
@@ -13,16 +14,22 @@ RUN mkdir -p /temp/prod
|
|||||||
COPY package.json bun.lockb /temp/prod/
|
COPY package.json bun.lockb /temp/prod/
|
||||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
RUN apk --no-cache add curl gcc
|
||||||
|
ENV PATH=/root/.cargo/bin:$PATH
|
||||||
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
RUN cargo install resvg
|
||||||
|
|
||||||
# copy node_modules from temp directory
|
# copy node_modules from temp directory
|
||||||
# then copy all (non-ignored) project files into the image
|
# then copy all (non-ignored) project files into the image
|
||||||
# FROM base AS prerelease
|
FROM base AS prerelease
|
||||||
# COPY --from=install /temp/dev/node_modules node_modules
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
# COPY . .
|
COPY . .
|
||||||
|
|
||||||
# # [optional] tests & build
|
# # [optional] tests & build
|
||||||
# ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
# RUN bun test
|
# RUN bun test
|
||||||
# RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
# copy production dependencies and source code into final image
|
# copy production dependencies and source code into final image
|
||||||
FROM base AS release
|
FROM base AS release
|
||||||
@@ -40,12 +47,17 @@ RUN apk --no-cache add \
|
|||||||
graphicsmagick \
|
graphicsmagick \
|
||||||
ghostscript \
|
ghostscript \
|
||||||
vips-tools \
|
vips-tools \
|
||||||
libjxl-tools
|
vips-poppler \
|
||||||
|
vips-jxl \
|
||||||
|
libjxl-tools \
|
||||||
|
assimp
|
||||||
|
|
||||||
# this might be needed for some latex use cases, will add it if needed.
|
# this might be needed for some latex use cases, will add it if needed.
|
||||||
# texmf-dist-fontsextra \
|
# texmf-dist-fontsextra \
|
||||||
|
|
||||||
COPY --from=install /temp/prod/node_modules node_modules
|
COPY --from=install /temp/prod/node_modules node_modules
|
||||||
|
COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg
|
||||||
|
COPY --from=prerelease /app/src/public/generated.css /app/src/public/
|
||||||
# COPY --from=prerelease /app/src/index.tsx /app/src/
|
# COPY --from=prerelease /app/src/index.tsx /app/src/
|
||||||
# COPY --from=prerelease /app/package.json .
|
# COPY --from=prerelease /app/package.json .
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
38
README.md
38
README.md
@@ -7,11 +7,12 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
A self-hosted online file converter. Supports 831 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
|
||||||
- Password protection
|
- Password protection
|
||||||
- Multiple accounts
|
- Multiple accounts
|
||||||
|
|
||||||
@@ -20,8 +21,10 @@ A self-hosted online file converter. Supports 831 different formats. Written wit
|
|||||||
| 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 |
|
||||||
| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 |
|
| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 |
|
||||||
| [XeLaTeX](https://tug.org/xetex/) | Documents | 1 | 1 |
|
| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 70 | 24 |
|
||||||
|
| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 |
|
||||||
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 |
|
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 |
|
||||||
| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 166 | 133 |
|
| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 166 | 133 |
|
||||||
| [FFmpeg](https://ffmpeg.org/) | Video | ~473 | ~280 |
|
| [FFmpeg](https://ffmpeg.org/) | Video | ~473 | ~280 |
|
||||||
@@ -37,21 +40,25 @@ Any missing converter? Open an issue or pull request!
|
|||||||
services:
|
services:
|
||||||
convertx:
|
convertx:
|
||||||
image: ghcr.io/c4illin/convertx
|
image: ghcr.io/c4illin/convertx
|
||||||
|
container_name: convertx
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
environment: # Defaults are listed below. All are optional.
|
environment: # Defaults are listed below. All are optional.
|
||||||
- ACCOUNT_REGISTRATION=false # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account)
|
- ACCOUNT_REGISTRATION=false # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account)
|
||||||
- JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default
|
- 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
|
||||||
|
- AUTO_DELETE_EVERY_N_HOURS=24 # checks every n hours for files older then n hours and deletes them, set to 0 to disable
|
||||||
volumes:
|
volumes:
|
||||||
- convertx:/app/data
|
- convertx:/app/data
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- or
|
or
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run ghcr.io/c4illin/convertx:master -p 3000:3000 -e ACCOUNT_REGISTRATION=false -v /path/you/want:/app/data
|
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.
|
||||||
|
|
||||||
@@ -61,14 +68,31 @@ If you get unable to open database file run `chown -R $USER:$USER path` on the p
|
|||||||
|
|
||||||
Tutorial in french: https://belginux.com/installer-convertx-avec-docker/
|
Tutorial in french: https://belginux.com/installer-convertx-avec-docker/
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
0. Install [Bun](https://bun.sh/) and Git
|
||||||
|
1. Clone the repository
|
||||||
|
2. `bun install`
|
||||||
|
3. `bun run dev`
|
||||||
|
|
||||||
|
Pull requests are welcome! See below and open issues for the list of todos.
|
||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
- [x] Add messages for errors in converters
|
- [x] Add messages for errors in converters
|
||||||
|
- [x] Add searchable list of formats
|
||||||
- [ ] Add options for converters
|
- [ ] Add options for converters
|
||||||
- [ ] Add more converters
|
|
||||||
- [ ] Divide index.tsx into smaller components
|
- [ ] Divide index.tsx into smaller components
|
||||||
- [ ] Add tests
|
- [ ] Add tests
|
||||||
- [ ] Add searchable list of formats
|
|
||||||
- [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible.
|
- [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible.
|
||||||
|
- [ ] Make errors logs visible from the web ui
|
||||||
|
- [ ] Add more converters:
|
||||||
|
- [ ] [deark](https://github.com/jsummers/deark)
|
||||||
|
- [ ] LibreOffice
|
||||||
|
- [ ] [dvisvgm](https://github.com/mgieseki/dvisvgm)
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
|
|||||||
21
biome.json
21
biome.json
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
|
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"formatWithErrors": true,
|
"formatWithErrors": true,
|
||||||
@@ -9,7 +9,15 @@
|
|||||||
"lineWidth": 80,
|
"lineWidth": 80,
|
||||||
"attributePosition": "auto"
|
"attributePosition": "auto"
|
||||||
},
|
},
|
||||||
"organizeImports": { "enabled": true },
|
"files": {
|
||||||
|
"ignore": [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/pico.lime.min.css"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
@@ -22,7 +30,11 @@
|
|||||||
"useLiteralKeys": "error",
|
"useLiteralKeys": "error",
|
||||||
"useOptionalChain": "error"
|
"useOptionalChain": "error"
|
||||||
},
|
},
|
||||||
"correctness": { "noPrecisionLoss": "error", "noUnusedVariables": "off" },
|
"correctness": {
|
||||||
|
"noPrecisionLoss": "error",
|
||||||
|
"noUnusedVariables": "off",
|
||||||
|
"useJsxKeyInIterable": "off"
|
||||||
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"noInferrableTypes": "error",
|
"noInferrableTypes": "error",
|
||||||
"noNamespace": "error",
|
"noNamespace": "error",
|
||||||
@@ -42,6 +54,9 @@
|
|||||||
"noUnsafeDeclarationMerging": "error",
|
"noUnsafeDeclarationMerging": "error",
|
||||||
"useAwait": "error",
|
"useAwait": "error",
|
||||||
"useNamespaceKeyword": "error"
|
"useNamespaceKeyword": "error"
|
||||||
|
},
|
||||||
|
"nursery": {
|
||||||
|
"useSortedClasses": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ services:
|
|||||||
# dockerfile: Debian.Dockerfile
|
# dockerfile: Debian.Dockerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
environment:
|
environment: # Defaults are listed below. All are optional.
|
||||||
- ACCOUNT_REGISTRATION=true
|
- 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
|
- JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default
|
||||||
|
- HTTP_ALLOWED=true # setting this to true is unsafe, only set this to true locally
|
||||||
|
- ALLOW_UNAUTHENTICATED=true # allows anyone to use the service without logging in, only set this to true locally
|
||||||
|
- AUTO_DELETE_EVERY_N_HOURS=1 # checks every n hours for files older then n hours and deletes them, set to 0 to disable
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
|
|||||||
59
eslint.config.js
Normal file
59
eslint.config.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { fixupPluginRules } from "@eslint/compat";
|
||||||
|
import eslint from "@eslint/js";
|
||||||
|
import deprecationPlugin from "eslint-plugin-deprecation";
|
||||||
|
import eslintPluginReadableTailwind from "eslint-plugin-readable-tailwind";
|
||||||
|
import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
|
||||||
|
import tailwind from "eslint-plugin-tailwindcss";
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...tailwind.configs["flat/recommended"],
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
deprecation: fixupPluginRules(deprecationPlugin),
|
||||||
|
"simple-import-sort": simpleImportSortPlugin,
|
||||||
|
"readable-tailwind": eslintPluginReadableTailwind,
|
||||||
|
},
|
||||||
|
ignores: ["**/node_modules/**"],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
projectService: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
project: ["./tsconfig.json"],
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
files: ["**/*.{js,mjs,cjs,tsx,ts}"],
|
||||||
|
rules: {
|
||||||
|
...eslintPluginReadableTailwind.configs.warning.rules,
|
||||||
|
"tailwindcss/classnames-order": "off",
|
||||||
|
"readable-tailwind/multiline": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
group: "newLine",
|
||||||
|
printWidth: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"tailwindcss/no-custom-classname": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
whitelist: [
|
||||||
|
"select_container",
|
||||||
|
"convert_to_popup",
|
||||||
|
"convert_to_group",
|
||||||
|
"target",
|
||||||
|
"convert_to_target",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
BIN
images/preview.png
Normal file
BIN
images/preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
63
package.json
63
package.json
@@ -1,21 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "convertx-frontend",
|
"name": "convertx-frontend",
|
||||||
"version": "0.3.3",
|
"version": "0.8.1",
|
||||||
"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": "biome format --write ./src",
|
"format": "eslint --fix .",
|
||||||
"css": "cpy 'node_modules/@picocss/pico/css/pico.lime.min.css' 'src/public/' --flat",
|
"build": "postcss ./src/main.css -o ./src/public/generated.css",
|
||||||
"lint": "run-p 'lint:*'",
|
"lint": "run-p 'lint:*'",
|
||||||
"lint:tsc": "tsc --noEmit",
|
"lint:tsc": "tsc --noEmit",
|
||||||
"lint:knip": "knip"
|
"lint:knip": "knip",
|
||||||
|
"lint:eslint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elysiajs/cookie": "^0.8.0",
|
"@elysiajs/cookie": "^0.8.0",
|
||||||
"@elysiajs/html": "1.0.2",
|
"@elysiajs/html": "1.0.2",
|
||||||
"@elysiajs/jwt": "^1.1.0",
|
"@elysiajs/jwt": "^1.1.1",
|
||||||
"@elysiajs/static": "1.0.3",
|
"@elysiajs/static": "1.0.3",
|
||||||
"elysia": "^1.1.4"
|
"elysia": "^1.1.17"
|
||||||
},
|
},
|
||||||
"module": "src/index.tsx",
|
"module": "src/index.tsx",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -23,28 +24,38 @@
|
|||||||
"start": "bun run src/index.tsx"
|
"start": "bun run src/index.tsx"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.8.3",
|
"@eslint/compat": "^1.1.1",
|
||||||
|
"@eslint/js": "^9.12.0",
|
||||||
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
|
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
|
||||||
"@kitajs/ts-html-plugin": "^4.0.2",
|
"@kitajs/ts-html-plugin": "^4.1.0",
|
||||||
"@picocss/pico": "^2.0.6",
|
"@total-typescript/ts-reset": "^0.6.1",
|
||||||
"@total-typescript/ts-reset": "^0.5.1",
|
"@types/bun": "^1.1.10",
|
||||||
"@types/bun": "^1.1.6",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/eslint": "^9.6.0",
|
"@types/eslint-plugin-tailwindcss": "^3.17.0",
|
||||||
"@types/node": "^22.0.0",
|
"@types/eslint__js": "^8.42.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@types/node": "^22.7.4",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||||
"cpy-cli": "^5.0.0",
|
"@typescript-eslint/parser": "^8.7.0",
|
||||||
"eslint": "^9.8.0",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"cssnano": "^7.0.6",
|
||||||
|
"eslint": "^9.12.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-deprecation": "^3.0.0",
|
||||||
"eslint-plugin-isaacscript": "^3.12.2",
|
"eslint-plugin-isaacscript": "^4.0.0",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"knip": "^5.27.0",
|
"eslint-plugin-readable-tailwind": "^1.8.1",
|
||||||
"npm-run-all": "^4.1.5",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
|
"eslint-plugin-tailwindcss": "^3.17.4",
|
||||||
|
"globals": "^15.9.0",
|
||||||
|
"knip": "^5.30.6",
|
||||||
|
"npm-run-all2": "^6.2.3",
|
||||||
|
"postcss": "^8.4.47",
|
||||||
|
"postcss-cli": "^11.0.0",
|
||||||
|
"postcss-lightningcss": "^1.0.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "^5.5.4"
|
"tailwind-scrollbar": "^3.1.0",
|
||||||
},
|
"tailwindcss": "^3.4.13",
|
||||||
"trustedDependencies": [
|
"typescript": "^5.6.2",
|
||||||
"@biomejs/biome"
|
"typescript-eslint": "^8.8.0"
|
||||||
]
|
}
|
||||||
}
|
}
|
||||||
9
postcss.config.cjs
Normal file
9
postcss.config.cjs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
|
||||||
|
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
7
renovate.json
Normal file
7
renovate.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended",
|
||||||
|
":disableDependencyDashboard"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -7,8 +7,7 @@ export const BaseHtml = ({
|
|||||||
<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" />
|
||||||
<title safe>{title}</title>
|
<title safe>{title}</title>
|
||||||
<link rel="stylesheet" href="/pico.lime.min.css" />
|
<link rel="stylesheet" href="/generated.css" />
|
||||||
<link rel="stylesheet" href="/style.css" />
|
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-icon"
|
rel="apple-touch-icon"
|
||||||
sizes="180x180"
|
sizes="180x180"
|
||||||
@@ -28,6 +27,6 @@ export const BaseHtml = ({
|
|||||||
/>
|
/>
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
</head>
|
</head>
|
||||||
<body>{children}</body>
|
<body class="w-full bg-neutral-900 text-neutral-200">{children}</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,44 +5,65 @@ export const Header = ({
|
|||||||
let rightNav: JSX.Element;
|
let rightNav: JSX.Element;
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
rightNav = (
|
rightNav = (
|
||||||
<ul>
|
<ul class="flex gap-4">
|
||||||
<li>
|
<li>
|
||||||
<a href="/history">History</a>
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-600 transition-all
|
||||||
|
hover:text-accent-500 hover:underline
|
||||||
|
`}
|
||||||
|
href="/history">
|
||||||
|
History
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/logoff">Logout</a>
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-600 transition-all
|
||||||
|
hover:text-accent-500 hover:underline
|
||||||
|
`}
|
||||||
|
href="/logoff">
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
rightNav = (
|
rightNav = (
|
||||||
<ul>
|
<ul class="flex gap-4">
|
||||||
<li>
|
<li>
|
||||||
<a href="/login">Login</a>
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-600 transition-all
|
||||||
|
hover:text-accent-500 hover:underline
|
||||||
|
`}
|
||||||
|
href="/login">
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{accountRegistration && (
|
{accountRegistration ? (
|
||||||
<li>
|
<li>
|
||||||
<a href="/register">Register</a>
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-600 transition-all
|
||||||
|
hover:text-accent-500 hover:underline
|
||||||
|
`}
|
||||||
|
href="/register">
|
||||||
|
Register
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)}
|
) : null}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header class="container">
|
<header class="w-full p-4">
|
||||||
<nav>
|
<nav class="mx-auto flex max-w-4xl justify-between rounded bg-neutral-900 p-4">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<strong>
|
<strong>
|
||||||
<a
|
<a href="/">ConvertX</a>
|
||||||
href="/"
|
|
||||||
style={{
|
|
||||||
textDecoration: "none",
|
|
||||||
color: "inherit",
|
|
||||||
}}>
|
|
||||||
ConvertX
|
|
||||||
</a>
|
|
||||||
</strong>
|
</strong>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
143
src/converters/assimp.ts
Normal file
143
src/converters/assimp.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { exec } from "node:child_process";
|
||||||
|
|
||||||
|
// This could be done dynamically by running `ffmpeg -formats` and parsing the output
|
||||||
|
export const properties = {
|
||||||
|
from: {
|
||||||
|
muxer: [
|
||||||
|
"3d",
|
||||||
|
"3ds",
|
||||||
|
"3mf",
|
||||||
|
"ac",
|
||||||
|
"ac3d",
|
||||||
|
"acc",
|
||||||
|
"amf",
|
||||||
|
"ase",
|
||||||
|
"ask",
|
||||||
|
"assbin",
|
||||||
|
"b3d",
|
||||||
|
"blend",
|
||||||
|
"bsp",
|
||||||
|
"bvh",
|
||||||
|
"cob",
|
||||||
|
"csm",
|
||||||
|
"dae",
|
||||||
|
"dxf",
|
||||||
|
"enff",
|
||||||
|
"fbx",
|
||||||
|
"glb",
|
||||||
|
"gltf",
|
||||||
|
"hmp",
|
||||||
|
"ifc",
|
||||||
|
"ifczip",
|
||||||
|
"iqm",
|
||||||
|
"irr",
|
||||||
|
"irrmesh",
|
||||||
|
"lwo",
|
||||||
|
"lws",
|
||||||
|
"lxo",
|
||||||
|
"md2",
|
||||||
|
"md3",
|
||||||
|
"md5anim",
|
||||||
|
"md5camera",
|
||||||
|
"md5mesh",
|
||||||
|
"mdc",
|
||||||
|
"mdl",
|
||||||
|
"mesh.xml",
|
||||||
|
"mesh",
|
||||||
|
"mot",
|
||||||
|
"ms3d",
|
||||||
|
"ndo",
|
||||||
|
"nff",
|
||||||
|
"obj",
|
||||||
|
"off",
|
||||||
|
"ogex",
|
||||||
|
"pk3",
|
||||||
|
"ply",
|
||||||
|
"pmx",
|
||||||
|
"prj",
|
||||||
|
"q3o",
|
||||||
|
"q3s",
|
||||||
|
"raw",
|
||||||
|
"scn",
|
||||||
|
"sib",
|
||||||
|
"smd",
|
||||||
|
"step",
|
||||||
|
"stl",
|
||||||
|
"stp",
|
||||||
|
"ter",
|
||||||
|
"uc",
|
||||||
|
"usd",
|
||||||
|
"usda",
|
||||||
|
"usdc",
|
||||||
|
"usdz",
|
||||||
|
"vta",
|
||||||
|
"x",
|
||||||
|
"x3d",
|
||||||
|
"x3db",
|
||||||
|
"xgl",
|
||||||
|
"xml",
|
||||||
|
"zae",
|
||||||
|
"zgl",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
muxer: [
|
||||||
|
"3ds",
|
||||||
|
"3mf",
|
||||||
|
"assbin",
|
||||||
|
"assjson",
|
||||||
|
"assxml",
|
||||||
|
"collada",
|
||||||
|
"dae",
|
||||||
|
"fbx",
|
||||||
|
"fbxa",
|
||||||
|
"glb",
|
||||||
|
"glb2",
|
||||||
|
"gltf",
|
||||||
|
"gltf2",
|
||||||
|
"m3d",
|
||||||
|
"m3da",
|
||||||
|
"obj",
|
||||||
|
"objnomtl",
|
||||||
|
"pbrt",
|
||||||
|
"ply",
|
||||||
|
"plyb",
|
||||||
|
"stl",
|
||||||
|
"stlb",
|
||||||
|
"stp",
|
||||||
|
"x",
|
||||||
|
"x3d",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function convert(
|
||||||
|
filePath: string,
|
||||||
|
fileType: string,
|
||||||
|
convertTo: string,
|
||||||
|
targetPath: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
options?: unknown,
|
||||||
|
): Promise<string> {
|
||||||
|
// let command = "ffmpeg";
|
||||||
|
|
||||||
|
const command = `assimp export "${filePath}" "${targetPath}"`;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(command, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(`error: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdout) {
|
||||||
|
console.log(`stdout: ${stdout}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stderr) {
|
||||||
|
console.error(`stderr: ${stderr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve("Done");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ export const properties = {
|
|||||||
muxer: [
|
muxer: [
|
||||||
"264",
|
"264",
|
||||||
"265",
|
"265",
|
||||||
|
"266",
|
||||||
"302",
|
"302",
|
||||||
"3dostr",
|
"3dostr",
|
||||||
"3g2",
|
"3g2",
|
||||||
@@ -18,6 +19,7 @@ export const properties = {
|
|||||||
"aac",
|
"aac",
|
||||||
"aax",
|
"aax",
|
||||||
"ac3",
|
"ac3",
|
||||||
|
"ac4",
|
||||||
"ace",
|
"ace",
|
||||||
"acm",
|
"acm",
|
||||||
"act",
|
"act",
|
||||||
@@ -48,7 +50,6 @@ export const properties = {
|
|||||||
"apng",
|
"apng",
|
||||||
"aptx",
|
"aptx",
|
||||||
"aptxhd",
|
"aptxhd",
|
||||||
"aptx_hd",
|
|
||||||
"aqt",
|
"aqt",
|
||||||
"aqtitle",
|
"aqtitle",
|
||||||
"argo_asf",
|
"argo_asf",
|
||||||
@@ -63,10 +64,12 @@ export const properties = {
|
|||||||
"av1",
|
"av1",
|
||||||
"avc",
|
"avc",
|
||||||
"avi",
|
"avi",
|
||||||
|
"avif",
|
||||||
"avr",
|
"avr",
|
||||||
"avs",
|
"avs",
|
||||||
"avs2",
|
"avs2",
|
||||||
"avs3",
|
"avs3",
|
||||||
|
"awb",
|
||||||
"bcstm",
|
"bcstm",
|
||||||
"bethsoftvid",
|
"bethsoftvid",
|
||||||
"bfi",
|
"bfi",
|
||||||
@@ -75,8 +78,10 @@ export const properties = {
|
|||||||
"bink",
|
"bink",
|
||||||
"binka",
|
"binka",
|
||||||
"bit",
|
"bit",
|
||||||
"bmp_pipe",
|
"bitpacked",
|
||||||
"bmv",
|
"bmv",
|
||||||
|
"bmp",
|
||||||
|
"bonk",
|
||||||
"boa",
|
"boa",
|
||||||
"brender_pix",
|
"brender_pix",
|
||||||
"brstm",
|
"brstm",
|
||||||
@@ -93,7 +98,7 @@ export const properties = {
|
|||||||
"codec2",
|
"codec2",
|
||||||
"codec2raw",
|
"codec2raw",
|
||||||
"concat",
|
"concat",
|
||||||
"cri_pipe",
|
"cri",
|
||||||
"dash",
|
"dash",
|
||||||
"dat",
|
"dat",
|
||||||
"data",
|
"data",
|
||||||
@@ -101,8 +106,9 @@ export const properties = {
|
|||||||
"dav",
|
"dav",
|
||||||
"dbm",
|
"dbm",
|
||||||
"dcstr",
|
"dcstr",
|
||||||
"dds_pipe",
|
"dds",
|
||||||
"derf",
|
"derf",
|
||||||
|
"dfpwm",
|
||||||
"dfa",
|
"dfa",
|
||||||
"dhav",
|
"dhav",
|
||||||
"dif",
|
"dif",
|
||||||
@@ -131,6 +137,8 @@ export const properties = {
|
|||||||
"exr_pipe",
|
"exr_pipe",
|
||||||
"f32be",
|
"f32be",
|
||||||
"f32le",
|
"f32le",
|
||||||
|
"ec3",
|
||||||
|
"evc",
|
||||||
"f4v",
|
"f4v",
|
||||||
"f64be",
|
"f64be",
|
||||||
"f64le",
|
"f64le",
|
||||||
@@ -157,13 +165,13 @@ export const properties = {
|
|||||||
"gdv",
|
"gdv",
|
||||||
"genh",
|
"genh",
|
||||||
"gif",
|
"gif",
|
||||||
"gif_pipe",
|
|
||||||
"gsm",
|
"gsm",
|
||||||
"gxf",
|
"gxf",
|
||||||
"h261",
|
"h261",
|
||||||
"h263",
|
"h263",
|
||||||
"h264",
|
"h264",
|
||||||
"h265",
|
"h265",
|
||||||
|
"h266",
|
||||||
"h26l",
|
"h26l",
|
||||||
"hca",
|
"hca",
|
||||||
"hcom",
|
"hcom",
|
||||||
@@ -180,7 +188,6 @@ export const properties = {
|
|||||||
"ifv",
|
"ifv",
|
||||||
"ilbc",
|
"ilbc",
|
||||||
"image2",
|
"image2",
|
||||||
"image2pipe",
|
|
||||||
"imf",
|
"imf",
|
||||||
"imx",
|
"imx",
|
||||||
"ingenient",
|
"ingenient",
|
||||||
@@ -197,21 +204,17 @@ export const properties = {
|
|||||||
"ivr",
|
"ivr",
|
||||||
"j2b",
|
"j2b",
|
||||||
"j2k",
|
"j2k",
|
||||||
"j2k_pipe",
|
|
||||||
"jack",
|
"jack",
|
||||||
"jacosub",
|
"jacosub",
|
||||||
"jpegls_pipe",
|
|
||||||
"jpeg_pipe",
|
|
||||||
"jv",
|
"jv",
|
||||||
|
"jpegls",
|
||||||
|
"jpeg",
|
||||||
|
"jxl",
|
||||||
"kmsgrab",
|
"kmsgrab",
|
||||||
"kux",
|
"kux",
|
||||||
"kvag",
|
"kvag",
|
||||||
"lavfi",
|
"lavfi",
|
||||||
"libcdio",
|
"laf",
|
||||||
"libdc1394",
|
|
||||||
"libgme",
|
|
||||||
"libopenmpt",
|
|
||||||
"live_flv",
|
|
||||||
"lmlm4",
|
"lmlm4",
|
||||||
"loas",
|
"loas",
|
||||||
"lrc",
|
"lrc",
|
||||||
@@ -224,16 +227,13 @@ export const properties = {
|
|||||||
"m4b",
|
"m4b",
|
||||||
"m4v",
|
"m4v",
|
||||||
"mac",
|
"mac",
|
||||||
"matroska",
|
|
||||||
"mca",
|
"mca",
|
||||||
"mcc",
|
"mcc",
|
||||||
"mdl",
|
"mdl",
|
||||||
"med",
|
"med",
|
||||||
"mgsts",
|
|
||||||
"microdvd",
|
"microdvd",
|
||||||
"mj2",
|
"mj2",
|
||||||
"mjpeg",
|
"mjpeg",
|
||||||
"mjpeg_2000",
|
|
||||||
"mjpg",
|
"mjpg",
|
||||||
"mk3d",
|
"mk3d",
|
||||||
"mka",
|
"mka",
|
||||||
@@ -257,9 +257,6 @@ export const properties = {
|
|||||||
"mpc",
|
"mpc",
|
||||||
"mpc8",
|
"mpc8",
|
||||||
"mpeg",
|
"mpeg",
|
||||||
"mpegts",
|
|
||||||
"mpegtsraw",
|
|
||||||
"mpegvideo",
|
|
||||||
"mpg",
|
"mpg",
|
||||||
"mpjpeg",
|
"mpjpeg",
|
||||||
"mpl2",
|
"mpl2",
|
||||||
@@ -294,25 +291,27 @@ export const properties = {
|
|||||||
"okt",
|
"okt",
|
||||||
"oma",
|
"oma",
|
||||||
"omg",
|
"omg",
|
||||||
|
"opus",
|
||||||
"openal",
|
"openal",
|
||||||
"oss",
|
"oss",
|
||||||
|
"osq",
|
||||||
"paf",
|
"paf",
|
||||||
"pam_pipe",
|
"pdv",
|
||||||
"pbm_pipe",
|
"pam",
|
||||||
"pcx_pipe",
|
"pbm",
|
||||||
"pgmyuv_pipe",
|
"pcx",
|
||||||
"pgm_pipe",
|
"pgmyuv",
|
||||||
"pgx_pipe",
|
"pgm",
|
||||||
"photocd_pipe",
|
"pgx",
|
||||||
"pictor_pipe",
|
"photocd",
|
||||||
|
"pictor",
|
||||||
"pjs",
|
"pjs",
|
||||||
"plm",
|
"plm",
|
||||||
"pmp",
|
"pmp",
|
||||||
"png_pipe",
|
"png",
|
||||||
"ppm",
|
"ppm",
|
||||||
"ppm_pipe",
|
"pp",
|
||||||
"pp_bnk",
|
"psd",
|
||||||
"psd_pipe",
|
|
||||||
"psm",
|
"psm",
|
||||||
"psp",
|
"psp",
|
||||||
"psxstr",
|
"psxstr",
|
||||||
@@ -323,7 +322,7 @@ export const properties = {
|
|||||||
"pvf",
|
"pvf",
|
||||||
"qcif",
|
"qcif",
|
||||||
"qcp",
|
"qcp",
|
||||||
"qdraw_pipe",
|
"qdraw",
|
||||||
"r3d",
|
"r3d",
|
||||||
"rawvideo",
|
"rawvideo",
|
||||||
"rco",
|
"rco",
|
||||||
@@ -335,6 +334,7 @@ export const properties = {
|
|||||||
"rm",
|
"rm",
|
||||||
"roq",
|
"roq",
|
||||||
"rpl",
|
"rpl",
|
||||||
|
"rka",
|
||||||
"rsd",
|
"rsd",
|
||||||
"rso",
|
"rso",
|
||||||
"rt",
|
"rt",
|
||||||
@@ -355,6 +355,7 @@ export const properties = {
|
|||||||
"sbc",
|
"sbc",
|
||||||
"sbg",
|
"sbg",
|
||||||
"scc",
|
"scc",
|
||||||
|
"sdns",
|
||||||
"sdp",
|
"sdp",
|
||||||
"sdr2",
|
"sdr2",
|
||||||
"sds",
|
"sds",
|
||||||
@@ -364,10 +365,9 @@ export const properties = {
|
|||||||
"sfx",
|
"sfx",
|
||||||
"sfx2",
|
"sfx2",
|
||||||
"sga",
|
"sga",
|
||||||
"sgi_pipe",
|
"sgi",
|
||||||
"shn",
|
"shn",
|
||||||
"siff",
|
"siff",
|
||||||
"simbiosis_imx",
|
|
||||||
"sln",
|
"sln",
|
||||||
"smi",
|
"smi",
|
||||||
"smjpeg",
|
"smjpeg",
|
||||||
@@ -389,12 +389,9 @@ export const properties = {
|
|||||||
"stp",
|
"stp",
|
||||||
"str",
|
"str",
|
||||||
"sub",
|
"sub",
|
||||||
"subviewer",
|
|
||||||
"subviewer1",
|
|
||||||
"sunrast_pipe",
|
|
||||||
"sup",
|
"sup",
|
||||||
"svag",
|
"svag",
|
||||||
"svg_pipe",
|
"svg",
|
||||||
"svs",
|
"svs",
|
||||||
"sw",
|
"sw",
|
||||||
"swf",
|
"swf",
|
||||||
@@ -404,7 +401,8 @@ export const properties = {
|
|||||||
"thd",
|
"thd",
|
||||||
"thp",
|
"thp",
|
||||||
"tiertexseq",
|
"tiertexseq",
|
||||||
"tiff_pipe",
|
"tif",
|
||||||
|
"tiff",
|
||||||
"tmv",
|
"tmv",
|
||||||
"truehd",
|
"truehd",
|
||||||
"tta",
|
"tta",
|
||||||
@@ -424,6 +422,7 @@ export const properties = {
|
|||||||
"ul",
|
"ul",
|
||||||
"ult",
|
"ult",
|
||||||
"umx",
|
"umx",
|
||||||
|
"usm",
|
||||||
"uw",
|
"uw",
|
||||||
"v",
|
"v",
|
||||||
"v210",
|
"v210",
|
||||||
@@ -447,12 +446,14 @@ export const properties = {
|
|||||||
"vql",
|
"vql",
|
||||||
"vt",
|
"vt",
|
||||||
"vtt",
|
"vtt",
|
||||||
|
"vvc",
|
||||||
"w64",
|
"w64",
|
||||||
|
"wa",
|
||||||
"wav",
|
"wav",
|
||||||
|
"way",
|
||||||
"wc3movie",
|
"wc3movie",
|
||||||
"webm",
|
"webm",
|
||||||
"webm_dash_manifest",
|
"webp",
|
||||||
"webp_pipe",
|
|
||||||
"webvtt",
|
"webvtt",
|
||||||
"wow",
|
"wow",
|
||||||
"wsaud",
|
"wsaud",
|
||||||
@@ -464,32 +465,31 @@ export const properties = {
|
|||||||
"x11grab",
|
"x11grab",
|
||||||
"xa",
|
"xa",
|
||||||
"xbin",
|
"xbin",
|
||||||
"xbm_pipe",
|
|
||||||
"xl",
|
"xl",
|
||||||
"xm",
|
"xm",
|
||||||
|
"xmd",
|
||||||
"xmv",
|
"xmv",
|
||||||
"xpk",
|
"xpk",
|
||||||
"xpm_pipe",
|
|
||||||
"xvag",
|
"xvag",
|
||||||
"xwd_pipe",
|
|
||||||
"xwma",
|
"xwma",
|
||||||
"y4m",
|
"y4m",
|
||||||
"yop",
|
"yop",
|
||||||
"yuv",
|
"yuv",
|
||||||
"yuv10",
|
"yuv10",
|
||||||
"yuv4mpegpipe",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
muxer: [
|
muxer: [
|
||||||
"264",
|
"264",
|
||||||
"265",
|
"265",
|
||||||
|
"266",
|
||||||
"302",
|
"302",
|
||||||
"3g2",
|
"3g2",
|
||||||
"3gp",
|
"3gp",
|
||||||
"a64",
|
"a64",
|
||||||
"aac",
|
"aac",
|
||||||
"ac3",
|
"ac3",
|
||||||
|
"ac4",
|
||||||
"adts",
|
"adts",
|
||||||
"adx",
|
"adx",
|
||||||
"afc",
|
"afc",
|
||||||
@@ -497,43 +497,32 @@ export const properties = {
|
|||||||
"aifc",
|
"aifc",
|
||||||
"aiff",
|
"aiff",
|
||||||
"al",
|
"al",
|
||||||
"alaw",
|
|
||||||
"alp",
|
|
||||||
"alsa",
|
|
||||||
"amr",
|
"amr",
|
||||||
"amv",
|
"amv",
|
||||||
"apm",
|
"apm",
|
||||||
"apng",
|
"apng",
|
||||||
"aptx",
|
"aptx",
|
||||||
"aptxhd",
|
"aptxhd",
|
||||||
"aptx_hd",
|
|
||||||
"argo_asf",
|
|
||||||
"asf",
|
"asf",
|
||||||
"asf_stream",
|
|
||||||
"ass",
|
"ass",
|
||||||
"ast",
|
"ast",
|
||||||
"au",
|
"au",
|
||||||
|
"aud",
|
||||||
|
"av1",
|
||||||
"avi",
|
"avi",
|
||||||
"avm2",
|
"avif",
|
||||||
"avs",
|
"avs",
|
||||||
"avs2",
|
"avs2",
|
||||||
|
"avs3",
|
||||||
"bit",
|
"bit",
|
||||||
"bmp",
|
"bmp",
|
||||||
"c2",
|
"c2",
|
||||||
"caca",
|
|
||||||
"caf",
|
"caf",
|
||||||
"cavs",
|
"cavs",
|
||||||
"cavsvideo",
|
|
||||||
"chk",
|
"chk",
|
||||||
"chromaprint",
|
|
||||||
"codec2",
|
|
||||||
"codec2raw",
|
|
||||||
"cpk",
|
"cpk",
|
||||||
"crc",
|
"cvg",
|
||||||
"dash",
|
"dfpwm",
|
||||||
"data",
|
|
||||||
"daud",
|
|
||||||
"dirac",
|
|
||||||
"dnxhd",
|
"dnxhd",
|
||||||
"dnxhr",
|
"dnxhr",
|
||||||
"dpx",
|
"dpx",
|
||||||
@@ -542,30 +531,16 @@ export const properties = {
|
|||||||
"dv",
|
"dv",
|
||||||
"dvd",
|
"dvd",
|
||||||
"eac3",
|
"eac3",
|
||||||
|
"ec3",
|
||||||
|
"evc",
|
||||||
"exr",
|
"exr",
|
||||||
"f32be",
|
|
||||||
"f32le",
|
|
||||||
"f4v",
|
"f4v",
|
||||||
"f64be",
|
|
||||||
"f64le",
|
|
||||||
"fbdev",
|
|
||||||
"ffmeta",
|
"ffmeta",
|
||||||
"ffmetadata",
|
|
||||||
"fifo",
|
|
||||||
"fifo_test",
|
|
||||||
"filmstrip",
|
|
||||||
"film_cpk",
|
|
||||||
"fits",
|
"fits",
|
||||||
"flac",
|
"flac",
|
||||||
"flm",
|
"flm",
|
||||||
"flv",
|
"flv",
|
||||||
"framecrc",
|
|
||||||
"framehash",
|
|
||||||
"framemd5",
|
|
||||||
"g722",
|
"g722",
|
||||||
"g723_1",
|
|
||||||
"g726",
|
|
||||||
"g726le",
|
|
||||||
"gif",
|
"gif",
|
||||||
"gsm",
|
"gsm",
|
||||||
"gxf",
|
"gxf",
|
||||||
@@ -573,32 +548,26 @@ export const properties = {
|
|||||||
"h263",
|
"h263",
|
||||||
"h264",
|
"h264",
|
||||||
"h265",
|
"h265",
|
||||||
"hash",
|
"h266",
|
||||||
"hds",
|
"hdr",
|
||||||
"hevc",
|
"hevc",
|
||||||
"hls",
|
|
||||||
"ico",
|
"ico",
|
||||||
"ilbc",
|
|
||||||
"im1",
|
"im1",
|
||||||
"im24",
|
"im24",
|
||||||
"im8",
|
"im8",
|
||||||
"image2",
|
|
||||||
"image2pipe",
|
|
||||||
"ipod",
|
|
||||||
"ircam",
|
"ircam",
|
||||||
"isma",
|
"isma",
|
||||||
"ismv",
|
"ismv",
|
||||||
"ivf",
|
"ivf",
|
||||||
"j2c",
|
"j2c",
|
||||||
"j2k",
|
"j2k",
|
||||||
"jacosub",
|
|
||||||
"jls",
|
"jls",
|
||||||
"jp2",
|
"jp2",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"jpg",
|
"jpg",
|
||||||
"js",
|
"js",
|
||||||
"jss",
|
"jss",
|
||||||
"kvag",
|
"jxl",
|
||||||
"latm",
|
"latm",
|
||||||
"lbc",
|
"lbc",
|
||||||
"ljpg",
|
"ljpg",
|
||||||
@@ -613,13 +582,9 @@ export const properties = {
|
|||||||
"m4a",
|
"m4a",
|
||||||
"m4b",
|
"m4b",
|
||||||
"m4v",
|
"m4v",
|
||||||
"matroska",
|
|
||||||
"md5",
|
|
||||||
"microdvd",
|
|
||||||
"mjpeg",
|
"mjpeg",
|
||||||
"mjpg",
|
"mjpg",
|
||||||
"mkv",
|
"mkv",
|
||||||
"mkvtimestamp_v2",
|
|
||||||
"mlp",
|
"mlp",
|
||||||
"mmf",
|
"mmf",
|
||||||
"mov",
|
"mov",
|
||||||
@@ -629,26 +594,17 @@ export const properties = {
|
|||||||
"mpa",
|
"mpa",
|
||||||
"mpd",
|
"mpd",
|
||||||
"mpeg",
|
"mpeg",
|
||||||
"mpeg1video",
|
|
||||||
"mpeg2video",
|
|
||||||
"mpegts",
|
|
||||||
"mpg",
|
"mpg",
|
||||||
"mpjpeg",
|
|
||||||
"msbc",
|
"msbc",
|
||||||
"mts",
|
"mts",
|
||||||
"mulaw",
|
|
||||||
"mxf",
|
"mxf",
|
||||||
"mxf_d10",
|
|
||||||
"mxf_opatom",
|
|
||||||
"null",
|
|
||||||
"nut",
|
"nut",
|
||||||
|
"obu",
|
||||||
"oga",
|
"oga",
|
||||||
"ogg",
|
"ogg",
|
||||||
"ogv",
|
"ogv",
|
||||||
"oma",
|
"oma",
|
||||||
"opengl",
|
|
||||||
"opus",
|
"opus",
|
||||||
"oss",
|
|
||||||
"pam",
|
"pam",
|
||||||
"pbm",
|
"pbm",
|
||||||
"pcm",
|
"pcm",
|
||||||
@@ -656,14 +612,14 @@ export const properties = {
|
|||||||
"pfm",
|
"pfm",
|
||||||
"pgm",
|
"pgm",
|
||||||
"pgmyuv",
|
"pgmyuv",
|
||||||
|
"phm",
|
||||||
"pix",
|
"pix",
|
||||||
"png",
|
"png",
|
||||||
"ppm",
|
"ppm",
|
||||||
"psp",
|
"psp",
|
||||||
"pulse",
|
"qoi",
|
||||||
"ra",
|
"ra",
|
||||||
"ras",
|
"ras",
|
||||||
"rawvideo",
|
|
||||||
"rco",
|
"rco",
|
||||||
"rcv",
|
"rcv",
|
||||||
"rgb",
|
"rgb",
|
||||||
@@ -671,84 +627,47 @@ export const properties = {
|
|||||||
"roq",
|
"roq",
|
||||||
"rs",
|
"rs",
|
||||||
"rso",
|
"rso",
|
||||||
"rtp",
|
|
||||||
"rtp_mpegts",
|
|
||||||
"rtsp",
|
|
||||||
"s16be",
|
|
||||||
"s16le",
|
|
||||||
"s24be",
|
|
||||||
"s24le",
|
|
||||||
"s32be",
|
|
||||||
"s32le",
|
|
||||||
"s8",
|
|
||||||
"sap",
|
|
||||||
"sb",
|
"sb",
|
||||||
"sbc",
|
"sbc",
|
||||||
"scc",
|
"scc",
|
||||||
"sdl",
|
|
||||||
"sdl2",
|
|
||||||
"segment",
|
|
||||||
"sf",
|
"sf",
|
||||||
"sgi",
|
"sgi",
|
||||||
"singlejpeg",
|
|
||||||
"smjpeg",
|
|
||||||
"smoothstreaming",
|
|
||||||
"sndio",
|
|
||||||
"sox",
|
"sox",
|
||||||
"spdif",
|
"spdif",
|
||||||
"spx",
|
"spx",
|
||||||
"srt",
|
"srt",
|
||||||
"ssa",
|
"ssa",
|
||||||
"ssegment",
|
|
||||||
"streamhash",
|
|
||||||
"stream_segment",
|
|
||||||
"sub",
|
"sub",
|
||||||
"sun",
|
"sun",
|
||||||
"sunras",
|
"sunras",
|
||||||
"sup",
|
"sup",
|
||||||
"svcd",
|
|
||||||
"sw",
|
"sw",
|
||||||
"swf",
|
"swf",
|
||||||
"tco",
|
"tco",
|
||||||
"tee",
|
|
||||||
"tga",
|
"tga",
|
||||||
"thd",
|
"thd",
|
||||||
"tif",
|
"tif",
|
||||||
"tiff",
|
"tiff",
|
||||||
"truehd",
|
|
||||||
"ts",
|
"ts",
|
||||||
"tta",
|
"tta",
|
||||||
"ttml",
|
"ttml",
|
||||||
"tun",
|
"tun",
|
||||||
"u16be",
|
|
||||||
"u16le",
|
|
||||||
"u24be",
|
|
||||||
"u24le",
|
|
||||||
"u32be",
|
|
||||||
"u32le",
|
|
||||||
"u8",
|
|
||||||
"ub",
|
"ub",
|
||||||
"ul",
|
"ul",
|
||||||
"uncodedframecrc",
|
|
||||||
"uw",
|
"uw",
|
||||||
"v4l2",
|
|
||||||
"vag",
|
"vag",
|
||||||
|
"vbn",
|
||||||
"vc1",
|
"vc1",
|
||||||
"vc1test",
|
|
||||||
"vc2",
|
"vc2",
|
||||||
"vcd",
|
|
||||||
"vidc",
|
|
||||||
"video4linux2",
|
|
||||||
"vob",
|
"vob",
|
||||||
"voc",
|
"voc",
|
||||||
"vtt",
|
"vtt",
|
||||||
|
"vvc",
|
||||||
"w64",
|
"w64",
|
||||||
"wav",
|
"wav",
|
||||||
|
"wbmp",
|
||||||
"webm",
|
"webm",
|
||||||
"webm_chunk",
|
|
||||||
"webm_dash_manifest",
|
|
||||||
"webp",
|
"webp",
|
||||||
"webvtt",
|
|
||||||
"wma",
|
"wma",
|
||||||
"wmv",
|
"wmv",
|
||||||
"wtv",
|
"wtv",
|
||||||
@@ -756,12 +675,10 @@ export const properties = {
|
|||||||
"xbm",
|
"xbm",
|
||||||
"xface",
|
"xface",
|
||||||
"xml",
|
"xml",
|
||||||
"xv",
|
|
||||||
"xwd",
|
"xwd",
|
||||||
"y",
|
"y",
|
||||||
"y4m",
|
"y4m",
|
||||||
"yuv",
|
"yuv",
|
||||||
"yuv4mpegpipe",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -771,42 +688,19 @@ export async function convert(
|
|||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
options?: any,
|
options?: unknown,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// let command = "ffmpeg";
|
let extra = "";
|
||||||
|
let message = "Done";
|
||||||
|
|
||||||
// these are containers that can contain multiple formats
|
if (convertTo === "ico") {
|
||||||
// const autoDetect = [
|
// make sure image is 256x256 or smaller
|
||||||
// "mp4",
|
extra = `-filter:v "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease"`;
|
||||||
// "mkv",
|
message = "Done: resized to 256x256";
|
||||||
// "avi",
|
}
|
||||||
// "mov",
|
|
||||||
// "m4a",
|
|
||||||
// "3gp",
|
|
||||||
// "3g2",
|
|
||||||
// "mj2",
|
|
||||||
// "psp",
|
|
||||||
// "m4b",
|
|
||||||
// "ism",
|
|
||||||
// "ismv",
|
|
||||||
// "isma",
|
|
||||||
// "f4v",
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// if (!(fileType in autoDetect)) {
|
const command = `ffmpeg -i "${filePath}" ${extra} "${targetPath}"`;
|
||||||
// command += ` -f "${fileType}"`;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// command += ` -i "${filePath}"`;
|
|
||||||
|
|
||||||
// if (!(convertTo in autoDetect)) {
|
|
||||||
// command += ` -f "${convertTo}"`;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// command += ` "${targetPath}"`;
|
|
||||||
|
|
||||||
const command = `ffmpeg -i "${filePath}" "${targetPath}"`;
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(command, (error, stdout, stderr) => {
|
exec(command, (error, stdout, stderr) => {
|
||||||
@@ -822,7 +716,7 @@ export async function convert(
|
|||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("success");
|
resolve(message);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ export const properties = {
|
|||||||
"svgz",
|
"svgz",
|
||||||
"text",
|
"text",
|
||||||
"tga",
|
"tga",
|
||||||
|
"tif",
|
||||||
"tiff",
|
"tiff",
|
||||||
"tile",
|
"tile",
|
||||||
"tim",
|
"tim",
|
||||||
@@ -227,7 +228,6 @@ export const properties = {
|
|||||||
"jbig",
|
"jbig",
|
||||||
"jng",
|
"jng",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"jpg",
|
|
||||||
"k",
|
"k",
|
||||||
"m",
|
"m",
|
||||||
"m2v",
|
"m2v",
|
||||||
@@ -313,8 +313,8 @@ export function convert(
|
|||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
options?: any,
|
options?: unknown,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(
|
exec(
|
||||||
@@ -332,7 +332,7 @@ export function convert(
|
|||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("success");
|
resolve("Done");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export function convert(
|
|||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
options?: any,
|
options?: unknown,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let tool = "";
|
let tool = "";
|
||||||
if (fileType === "jxl") {
|
if (fileType === "jxl") {
|
||||||
@@ -65,7 +65,7 @@ export function convert(
|
|||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("success");
|
resolve("Done");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,52 @@
|
|||||||
import { convert as convertImage, properties as propertiesImage } from "./vips";
|
|
||||||
|
|
||||||
import {
|
|
||||||
convert as convertPandoc,
|
|
||||||
properties as propertiesPandoc,
|
|
||||||
} from "./pandoc";
|
|
||||||
|
|
||||||
import {
|
|
||||||
convert as convertFFmpeg,
|
|
||||||
properties as propertiesFFmpeg,
|
|
||||||
} from "./ffmpeg";
|
|
||||||
|
|
||||||
import {
|
|
||||||
convert as convertGraphicsmagick,
|
|
||||||
properties as propertiesGraphicsmagick,
|
|
||||||
} from "./graphicsmagick";
|
|
||||||
|
|
||||||
import {
|
|
||||||
convert as convertxelatex,
|
|
||||||
properties as propertiesxelatex,
|
|
||||||
} from "./xelatex";
|
|
||||||
|
|
||||||
import {
|
|
||||||
convert as convertLibjxl,
|
|
||||||
properties as propertiesLibjxl,
|
|
||||||
} from "./libjxl";
|
|
||||||
|
|
||||||
import { normalizeFiletype } from "../helpers/normalizeFiletype";
|
import { normalizeFiletype } from "../helpers/normalizeFiletype";
|
||||||
|
import { convert as convertassimp, properties as propertiesassimp } from "./assimp";
|
||||||
|
import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg";
|
||||||
|
import { convert as convertGraphicsmagick, properties as propertiesGraphicsmagick } from "./graphicsmagick";
|
||||||
|
import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl";
|
||||||
|
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
|
||||||
|
import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
|
||||||
|
import { convert as convertImage, properties as propertiesImage } from "./vips";
|
||||||
|
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
|
||||||
|
|
||||||
|
|
||||||
// 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: {
|
const properties: Record<
|
||||||
[key: string]: {
|
string,
|
||||||
|
{
|
||||||
properties: {
|
properties: {
|
||||||
from: { [key: string]: string[] };
|
from: Record<string, string[]>;
|
||||||
to: { [key: string]: string[] };
|
to: Record<string, string[]>;
|
||||||
options?: {
|
options?: Record<
|
||||||
[key: string]: {
|
string,
|
||||||
[key: string]: {
|
Record<
|
||||||
|
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,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
options?: any,
|
options?: unknown,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
) => unknown;
|
||||||
) => any;
|
}
|
||||||
};
|
> = {
|
||||||
} = {
|
|
||||||
libjxl: {
|
libjxl: {
|
||||||
properties: propertiesLibjxl,
|
properties: propertiesLibjxl,
|
||||||
converter: convertLibjxl,
|
converter: convertLibjxl,
|
||||||
},
|
},
|
||||||
|
resvg: {
|
||||||
|
properties: propertiesresvg,
|
||||||
|
converter: convertresvg,
|
||||||
|
},
|
||||||
vips: {
|
vips: {
|
||||||
properties: propertiesImage,
|
properties: propertiesImage,
|
||||||
converter: convertImage,
|
converter: convertImage,
|
||||||
@@ -75,6 +63,10 @@ const properties: {
|
|||||||
properties: propertiesGraphicsmagick,
|
properties: propertiesGraphicsmagick,
|
||||||
converter: convertGraphicsmagick,
|
converter: convertGraphicsmagick,
|
||||||
},
|
},
|
||||||
|
assimp: {
|
||||||
|
properties: propertiesassimp,
|
||||||
|
converter: convertassimp,
|
||||||
|
},
|
||||||
ffmpeg: {
|
ffmpeg: {
|
||||||
properties: propertiesFFmpeg,
|
properties: propertiesFFmpeg,
|
||||||
converter: convertFFmpeg,
|
converter: convertFFmpeg,
|
||||||
@@ -84,24 +76,19 @@ const properties: {
|
|||||||
export async function mainConverter(
|
export async function mainConverter(
|
||||||
inputFilePath: string,
|
inputFilePath: string,
|
||||||
fileTypeOriginal: string,
|
fileTypeOriginal: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
convertTo: string,
|
||||||
convertTo: any,
|
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
options?: unknown,
|
||||||
options?: any,
|
|
||||||
converterName?: string,
|
converterName?: string,
|
||||||
) {
|
) {
|
||||||
const fileType = normalizeFiletype(fileTypeOriginal);
|
const fileType = normalizeFiletype(fileTypeOriginal);
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
let converterFunc: typeof properties["libjxl"]["converter"] | undefined;
|
||||||
let converterFunc: any;
|
|
||||||
// let converterName = converterName;
|
|
||||||
|
|
||||||
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
|
||||||
// biome-ignore lint/style/noParameterAssign: <explanation>
|
|
||||||
for (converterName in properties) {
|
for (converterName in properties) {
|
||||||
const converterObj = properties[converterName];
|
const converterObj = properties[converterName];
|
||||||
|
|
||||||
@@ -129,7 +116,7 @@ export async function mainConverter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await converterFunc(
|
const result = await converterFunc(
|
||||||
inputFilePath,
|
inputFilePath,
|
||||||
fileType,
|
fileType,
|
||||||
convertTo,
|
convertTo,
|
||||||
@@ -139,7 +126,13 @@ export async function mainConverter(
|
|||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
|
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
|
||||||
|
result,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (typeof result === "string") {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
return "Done";
|
return "Done";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -150,7 +143,7 @@ export async function mainConverter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const possibleTargets: { [key: string]: { [key: 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;
|
||||||
@@ -175,9 +168,7 @@ for (const converterName in properties) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPossibleTargets = (
|
export const getPossibleTargets = (from: string): Record<string, string[]> => {
|
||||||
from: string,
|
|
||||||
): { [key: string]: string[] } => {
|
|
||||||
const fromClean = normalizeFiletype(from);
|
const fromClean = normalizeFiletype(from);
|
||||||
|
|
||||||
return possibleTargets[fromClean] || {};
|
return possibleTargets[fromClean] || {};
|
||||||
@@ -201,11 +192,12 @@ for (const converterName in properties) {
|
|||||||
}
|
}
|
||||||
possibleInputs.sort();
|
possibleInputs.sort();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const getPossibleInputs = () => {
|
const getPossibleInputs = () => {
|
||||||
return possibleInputs;
|
return possibleInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const allTargets: { [key: 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;
|
||||||
@@ -227,7 +219,7 @@ export const getAllTargets = () => {
|
|||||||
return allTargets;
|
return allTargets;
|
||||||
};
|
};
|
||||||
|
|
||||||
const allInputs: { [key: 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;
|
||||||
|
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ export function convert(
|
|||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
options?: any,
|
options?: unknown,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// set xelatex here
|
// set xelatex here
|
||||||
const xelatex = ["pdf", "latex"];
|
const xelatex = ["pdf", "latex"];
|
||||||
@@ -149,7 +149,7 @@ export function convert(
|
|||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("success");
|
resolve("Done");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
37
src/converters/resvg.ts
Normal file
37
src/converters/resvg.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { exec } from "node:child_process";
|
||||||
|
|
||||||
|
export const properties = {
|
||||||
|
from: {
|
||||||
|
images: ["svg"],
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
images: ["png"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function convert(
|
||||||
|
filePath: string,
|
||||||
|
fileType: string,
|
||||||
|
convertTo: string,
|
||||||
|
targetPath: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
options?: unknown,
|
||||||
|
): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(`resvg "${filePath}" "${targetPath}"`, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(`error: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdout) {
|
||||||
|
console.log(`stdout: ${stdout}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stderr) {
|
||||||
|
console.error(`stderr: ${stderr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve("Done");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { exec } from "node:child_process";
|
import { exec } from "node:child_process";
|
||||||
|
|
||||||
|
|
||||||
// declare possible conversions
|
// declare possible conversions
|
||||||
export const properties = {
|
export const properties = {
|
||||||
from: {
|
from: {
|
||||||
@@ -94,8 +95,8 @@ export function convert(
|
|||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
options?: any,
|
options?: unknown,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// if (fileType === "svg") {
|
// if (fileType === "svg") {
|
||||||
// const scale = options.scale || 1;
|
// const scale = options.scale || 1;
|
||||||
@@ -113,9 +114,15 @@ export function convert(
|
|||||||
// .toFormat(convertTo)
|
// .toFormat(convertTo)
|
||||||
// .toFile(targetPath);
|
// .toFile(targetPath);
|
||||||
// }
|
// }
|
||||||
|
let action = "copy";
|
||||||
|
if (fileType === "pdf") {
|
||||||
|
action = "pdfload";
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
exec(`vips copy "${filePath}" "${targetPath}"`, (error, stdout, stderr) => {
|
exec(
|
||||||
|
`vips ${action} "${filePath}" "${targetPath}"`,
|
||||||
|
(error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(`error: ${error}`);
|
reject(`error: ${error}`);
|
||||||
}
|
}
|
||||||
@@ -128,7 +135,8 @@ export function convert(
|
|||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("success");
|
resolve("Done");
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,8 @@ export function convert(
|
|||||||
fileType: string,
|
fileType: string,
|
||||||
convertTo: string,
|
convertTo: string,
|
||||||
targetPath: string,
|
targetPath: string,
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
options?: any,
|
options?: unknown,
|
||||||
): 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", "")
|
||||||
@@ -39,7 +39,7 @@ export function convert(
|
|||||||
console.error(`stderr: ${stderr}`);
|
console.error(`stderr: ${stderr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve("success");
|
resolve("Done");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export const normalizeFiletype = (filetype: string): string => {
|
|||||||
const lowercaseFiletype = filetype.toLowerCase();
|
const lowercaseFiletype = filetype.toLowerCase();
|
||||||
|
|
||||||
switch (lowercaseFiletype) {
|
switch (lowercaseFiletype) {
|
||||||
|
case "jfif":
|
||||||
case "jpg":
|
case "jpg":
|
||||||
return "jpeg";
|
return "jpeg";
|
||||||
case "htm":
|
case "htm":
|
||||||
@@ -23,6 +24,9 @@ export const normalizeOutputFiletype = (filetype: string): string => {
|
|||||||
return "jpg";
|
return "jpg";
|
||||||
case "latex":
|
case "latex":
|
||||||
return "tex";
|
return "tex";
|
||||||
|
case "markdown_phpextra":
|
||||||
|
case "markdown_strict":
|
||||||
|
case "markdown_mmd":
|
||||||
case "markdown":
|
case "markdown":
|
||||||
return "md";
|
return "md";
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -73,6 +73,26 @@ if (process.env.NODE_ENV === "production") {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
exec("resvg -V", (error, stdout) => {
|
||||||
|
if (error) {
|
||||||
|
console.error("resvg is not installed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdout) {
|
||||||
|
console.log(`resvg v${stdout.split("\n")[0]}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exec("assimp version", (error, stdout) => {
|
||||||
|
if (error) {
|
||||||
|
console.error("assimp is not installed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdout) {
|
||||||
|
console.log(`assimp v${stdout.split("\n")[5]}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
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");
|
||||||
|
|||||||
17
src/helpers/tailwind.ts
Normal file
17
src/helpers/tailwind.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import tw from "tailwindcss";
|
||||||
|
import postcss from "postcss";
|
||||||
|
|
||||||
|
export const generateTailwind = async () => {
|
||||||
|
const result = await Bun.file("./src/main.css")
|
||||||
|
.text()
|
||||||
|
.then((sourceText) => {
|
||||||
|
const config = "./tailwind.config.js";
|
||||||
|
|
||||||
|
return postcss([tw(config)]).process(sourceText, {
|
||||||
|
from: "./src/main.css",
|
||||||
|
to: "./public/generated.css",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
567
src/index.tsx
567
src/index.tsx
@@ -1,11 +1,11 @@
|
|||||||
import { Database } from "bun:sqlite";
|
import { randomInt, randomUUID } from "node:crypto";
|
||||||
import { randomUUID } from "node:crypto";
|
|
||||||
import { rmSync } from "node:fs";
|
import { rmSync } from "node:fs";
|
||||||
import { mkdir, unlink } from "node:fs/promises";
|
import { mkdir, unlink } from "node:fs/promises";
|
||||||
import cookie from "@elysiajs/cookie";
|
import cookie from "@elysiajs/cookie";
|
||||||
import { html } from "@elysiajs/html";
|
import { html } from "@elysiajs/html";
|
||||||
import { jwt } from "@elysiajs/jwt";
|
import { jwt, type JWTPayloadSpec } from "@elysiajs/jwt";
|
||||||
import { staticPlugin } from "@elysiajs/static";
|
import { staticPlugin } from "@elysiajs/static";
|
||||||
|
import { Database } from "bun:sqlite";
|
||||||
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";
|
||||||
@@ -27,9 +27,15 @@ const uploadsDir = "./data/uploads/";
|
|||||||
const outputDir = "./data/output/";
|
const outputDir = "./data/output/";
|
||||||
|
|
||||||
const ACCOUNT_REGISTRATION =
|
const ACCOUNT_REGISTRATION =
|
||||||
process.env.ACCOUNT_REGISTRATION === "true" || false;
|
process.env.ACCOUNT_REGISTRATION?.toLowerCase() === "true" || false;
|
||||||
|
|
||||||
const HTTP_ALLOWED = process.env.HTTP_ALLOWED === "true" || false;
|
const HTTP_ALLOWED =
|
||||||
|
process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false;
|
||||||
|
const ALLOW_UNAUTHENTICATED =
|
||||||
|
process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false;
|
||||||
|
const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
|
||||||
|
? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
|
||||||
|
: 24;
|
||||||
|
|
||||||
// fileNames: fileNames,
|
// fileNames: fileNames,
|
||||||
// filesToConvert: fileNames.length,
|
// filesToConvert: fileNames.length,
|
||||||
@@ -115,7 +121,7 @@ const app = new Elysia({
|
|||||||
schema: t.Object({
|
schema: t.Object({
|
||||||
id: t.String(),
|
id: t.String(),
|
||||||
}),
|
}),
|
||||||
secret: process.env.JWT_SECRET || randomUUID(),
|
secret: process.env.JWT_SECRET ?? randomUUID(),
|
||||||
exp: "7d",
|
exp: "7d",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -132,36 +138,51 @@ const app = new Elysia({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseHtml title="ConvertX | Setup">
|
<BaseHtml title="ConvertX | Setup">
|
||||||
<main class="container">
|
<main class="mx-auto w-full max-w-4xl px-4">
|
||||||
<h1>Welcome to ConvertX</h1>
|
<h1 class="my-8 text-3xl">Welcome to ConvertX!</h1>
|
||||||
<article>
|
<article class="article p-0">
|
||||||
<header>Create your account</header>
|
<header class="w-full bg-neutral-800 p-4">
|
||||||
<form method="post" action="/register">
|
Create your account
|
||||||
<fieldset>
|
</header>
|
||||||
<label>
|
<form method="post" action="/register" class="p-4">
|
||||||
Email/Username
|
<fieldset class="mb-4 flex flex-col gap-4">
|
||||||
|
<label class="flex flex-col gap-1">
|
||||||
|
Email
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
|
class="rounded bg-neutral-800 p-3"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
|
autocomplete="email"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label class="flex flex-col gap-1">
|
||||||
Password
|
Password
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
|
class="rounded bg-neutral-800 p-3"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
|
autocomplete="current-password"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<input type="submit" value="Create account" />
|
<input type="submit" value="Create account" class="btn-primary" />
|
||||||
</form>
|
</form>
|
||||||
<footer>
|
<footer class="p-4">
|
||||||
Report any issues on{" "}
|
Report any issues on{" "}
|
||||||
<a href="https://github.com/C4illin/ConvertX">GitHub</a>.
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-500 underline
|
||||||
|
hover:text-accent-400
|
||||||
|
`}
|
||||||
|
href="https://github.com/C4illin/ConvertX"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
@@ -177,32 +198,38 @@ const app = new Elysia({
|
|||||||
<BaseHtml title="ConvertX | Register">
|
<BaseHtml title="ConvertX | Register">
|
||||||
<>
|
<>
|
||||||
<Header accountRegistration={ACCOUNT_REGISTRATION} />
|
<Header accountRegistration={ACCOUNT_REGISTRATION} />
|
||||||
<main class="container">
|
<main class="w-full px-4">
|
||||||
<article>
|
<article class="article">
|
||||||
<form method="post">
|
<form method="post" class="flex flex-col gap-4">
|
||||||
<fieldset>
|
<fieldset class="mb-4 flex flex-col gap-4">
|
||||||
<label>
|
<label class="flex flex-col gap-1">
|
||||||
Email
|
Email
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
|
class="rounded bg-neutral-800 p-3"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label class="flex flex-col gap-1">
|
||||||
Password
|
Password
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
|
class="rounded bg-neutral-800 p-3"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
autocomplete="new-password"
|
autocomplete="current-password"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<input type="submit" value="Register" />
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Register"
|
||||||
|
class="btn-primary w-full"
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
@@ -293,25 +320,27 @@ const app = new Elysia({
|
|||||||
<BaseHtml title="ConvertX | Login">
|
<BaseHtml title="ConvertX | Login">
|
||||||
<>
|
<>
|
||||||
<Header accountRegistration={ACCOUNT_REGISTRATION} />
|
<Header accountRegistration={ACCOUNT_REGISTRATION} />
|
||||||
<main class="container">
|
<main class="w-full px-4">
|
||||||
<article>
|
<article class="article">
|
||||||
<form method="post">
|
<form method="post" class="flex flex-col gap-4">
|
||||||
<fieldset>
|
<fieldset class="mb-4 flex flex-col gap-4">
|
||||||
<label>
|
<label class="flex flex-col gap-1">
|
||||||
Email
|
Email
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
|
class="rounded bg-neutral-800 p-3"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label class="flex flex-col gap-1">
|
||||||
Password
|
Password
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
|
class="rounded bg-neutral-800 p-3"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
required
|
required
|
||||||
@@ -319,12 +348,20 @@ const app = new Elysia({
|
|||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div role="group">
|
<div role="group">
|
||||||
{ACCOUNT_REGISTRATION && (
|
{ACCOUNT_REGISTRATION ? (
|
||||||
<a href="/register" role="button" class="secondary">
|
<a
|
||||||
|
href="/register"
|
||||||
|
role="button"
|
||||||
|
class="btn-primary w-full"
|
||||||
|
>
|
||||||
Register an account
|
Register an account
|
||||||
</a>
|
</a>
|
||||||
)}
|
) : null}
|
||||||
<input type="submit" value="Login" />
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Login"
|
||||||
|
class="btn-primary w-full"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
@@ -336,7 +373,7 @@ const app = new Elysia({
|
|||||||
.post(
|
.post(
|
||||||
"/login",
|
"/login",
|
||||||
async function handler({ body, set, redirect, jwt, cookie: { auth } }) {
|
async function handler({ body, set, redirect, jwt, cookie: { auth } }) {
|
||||||
const existingUser = await db
|
const existingUser = db
|
||||||
.query("SELECT * FROM users WHERE email = ?")
|
.query("SELECT * FROM users WHERE email = ?")
|
||||||
.as(User)
|
.as(User)
|
||||||
.get(body.email);
|
.get(body.email);
|
||||||
@@ -403,17 +440,19 @@ const app = new Elysia({
|
|||||||
return redirect("/setup", 302);
|
return redirect("/setup", 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!auth?.value) {
|
if (!auth?.value && !ALLOW_UNAUTHENTICATED) {
|
||||||
return redirect("/login", 302);
|
|
||||||
}
|
|
||||||
// validate jwt
|
|
||||||
const user = await jwt.verify(auth.value);
|
|
||||||
if (!user) {
|
|
||||||
return redirect("/login", 302);
|
return redirect("/login", 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate jwt
|
||||||
|
let user: ({ id: string } & JWTPayloadSpec) | false = false;
|
||||||
|
if (auth?.value) {
|
||||||
|
user = await jwt.verify(auth.value);
|
||||||
|
|
||||||
|
if (user !== false && user.id) {
|
||||||
|
if (Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED) {
|
||||||
// make sure user exists in db
|
// make sure user exists in db
|
||||||
const existingUser = await db
|
const existingUser = db
|
||||||
.query("SELECT * FROM users WHERE id = ?")
|
.query("SELECT * FROM users WHERE id = ?")
|
||||||
.as(User)
|
.as(User)
|
||||||
.get(user.id);
|
.get(user.id);
|
||||||
@@ -424,6 +463,39 @@ const app = new Elysia({
|
|||||||
}
|
}
|
||||||
return redirect("/login", 302);
|
return redirect("/login", 302);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (ALLOW_UNAUTHENTICATED) {
|
||||||
|
const newUserId = String(
|
||||||
|
randomInt(
|
||||||
|
2 ** 24,
|
||||||
|
Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const accessToken = await jwt.sign({
|
||||||
|
id: newUserId,
|
||||||
|
});
|
||||||
|
|
||||||
|
user = { id: newUserId };
|
||||||
|
if (!auth) {
|
||||||
|
return {
|
||||||
|
message: "No auth cookie, perhaps your browser is blocking cookies.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// set cookie
|
||||||
|
auth.set({
|
||||||
|
value: accessToken,
|
||||||
|
httpOnly: true,
|
||||||
|
secure: !HTTP_ALLOWED,
|
||||||
|
maxAge: 24 * 60 * 60,
|
||||||
|
sameSite: "strict",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return redirect("/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(
|
||||||
@@ -455,37 +527,110 @@ const app = new Elysia({
|
|||||||
<BaseHtml>
|
<BaseHtml>
|
||||||
<>
|
<>
|
||||||
<Header loggedIn />
|
<Header loggedIn />
|
||||||
<main class="container">
|
<main class="w-full px-4">
|
||||||
<article>
|
<article class="article">
|
||||||
<h1>Convert</h1>
|
<h1 class="mb-4 text-xl">Convert</h1>
|
||||||
<div style={{ maxHeight: "50vh", overflowY: "auto" }}>
|
<div class="mb-4 max-h-[50vh] overflow-y-auto scrollbar-thin">
|
||||||
<table id="file-list" class="striped" />
|
<table
|
||||||
|
id="file-list"
|
||||||
|
class={`
|
||||||
|
w-full table-auto rounded bg-neutral-900
|
||||||
|
[&_td]:p-4
|
||||||
|
[&_tr]:rounded [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="dropzone"
|
||||||
|
class={`
|
||||||
|
relative flex h-48 w-full items-center justify-center rounded border border-dashed
|
||||||
|
border-neutral-700 transition-all
|
||||||
|
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||||
|
hover:border-neutral-600
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<b>Choose a file</b> or drag it here
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
name="file"
|
||||||
|
multiple
|
||||||
|
class="absolute inset-0 size-full cursor-pointer opacity-0"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input type="file" name="file" multiple />
|
|
||||||
{/* <label for="convert_from">Convert from</label> */}
|
|
||||||
{/* <select name="convert_from" aria-label="Convert from" required>
|
|
||||||
<option selected disabled value="">
|
|
||||||
Convert from
|
|
||||||
</option>
|
|
||||||
{getPossibleInputs().map((input) => (
|
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<option>{input}</option>
|
|
||||||
))}
|
|
||||||
</select> */}
|
|
||||||
</article>
|
</article>
|
||||||
<form method="post" action="/convert">
|
<form
|
||||||
|
method="post"
|
||||||
|
action="/convert"
|
||||||
|
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>
|
<article class="article w-full">
|
||||||
<select name="convert_to" aria-label="Convert to" required>
|
<input
|
||||||
|
type="search"
|
||||||
|
name="convert_to_search"
|
||||||
|
placeholder="Search for conversions"
|
||||||
|
autocomplete="off"
|
||||||
|
class="w-full rounded bg-neutral-800 p-4"
|
||||||
|
/>
|
||||||
|
<div class="select_container relative">
|
||||||
|
<article
|
||||||
|
class={`
|
||||||
|
convert_to_popup absolute z-[2] m-0 hidden h-[30vh] max-h-[50vh] w-full
|
||||||
|
flex-col overflow-y-auto overflow-x-hidden rounded bg-neutral-800
|
||||||
|
sm:h-[30vh]
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{Object.entries(getAllTargets()).map(
|
||||||
|
([converter, targets]) => (
|
||||||
|
<article
|
||||||
|
class={`
|
||||||
|
convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
|
||||||
|
`}
|
||||||
|
data-converter={converter}
|
||||||
|
>
|
||||||
|
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||||
|
{converter}
|
||||||
|
</header>
|
||||||
|
<ul class="convert_to_target flex flex-row flex-wrap gap-1">
|
||||||
|
{targets.map((target) => (
|
||||||
|
<button
|
||||||
|
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||||
|
tabindex={0}
|
||||||
|
class={`
|
||||||
|
target rounded bg-neutral-700 p-1 text-base
|
||||||
|
hover:bg-neutral-600
|
||||||
|
`}
|
||||||
|
data-value={`${target},${converter}`}
|
||||||
|
data-target={target}
|
||||||
|
data-converter={converter}
|
||||||
|
type="button"
|
||||||
|
safe
|
||||||
|
>
|
||||||
|
{target}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{/* 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
|
||||||
|
>
|
||||||
<option selected disabled value="">
|
<option selected disabled value="">
|
||||||
Convert to
|
Convert to
|
||||||
</option>
|
</option>
|
||||||
{Object.entries(getAllTargets()).map(
|
{Object.entries(getAllTargets()).map(
|
||||||
([converter, targets]) => (
|
([converter, targets]) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<optgroup label={converter}>
|
<optgroup label={converter}>
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<option value={`${target},${converter}`} safe>
|
<option value={`${target},${converter}`} safe>
|
||||||
{target}
|
{target}
|
||||||
</option>
|
</option>
|
||||||
@@ -494,8 +639,17 @@ const app = new Elysia({
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<input type="submit" value="Convert" />
|
<input
|
||||||
|
class={`
|
||||||
|
btn-primary w-full
|
||||||
|
disabled:cursor-not-allowed disabled:opacity-50
|
||||||
|
`}
|
||||||
|
type="submit"
|
||||||
|
value="Convert"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
<script src="script.js" defer />
|
<script src="script.js" defer />
|
||||||
@@ -507,16 +661,55 @@ const app = new Elysia({
|
|||||||
"/conversions",
|
"/conversions",
|
||||||
({ body }) => {
|
({ body }) => {
|
||||||
return (
|
return (
|
||||||
<select name="convert_to" aria-label="Convert to" required>
|
<>
|
||||||
|
<article
|
||||||
|
class={`
|
||||||
|
convert_to_popup absolute z-[2] m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
|
||||||
|
overflow-y-auto overflow-x-hidden rounded bg-neutral-800
|
||||||
|
sm:h-[30vh]
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{Object.entries(getPossibleTargets(body.fileType)).map(
|
||||||
|
([converter, targets]) => (
|
||||||
|
<article
|
||||||
|
class="convert_to_group flex w-full flex-col border-b border-neutral-700 p-4"
|
||||||
|
data-converter={converter}
|
||||||
|
>
|
||||||
|
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||||
|
{converter}
|
||||||
|
</header>
|
||||||
|
<ul class="convert_to_target flex flex-row flex-wrap gap-1">
|
||||||
|
{targets.map((target) => (
|
||||||
|
<button
|
||||||
|
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||||
|
tabindex={0}
|
||||||
|
class={`
|
||||||
|
target rounded bg-neutral-700 p-1 text-base
|
||||||
|
hover:bg-neutral-600
|
||||||
|
`}
|
||||||
|
data-value={`${target},${converter}`}
|
||||||
|
data-target={target}
|
||||||
|
data-converter={converter}
|
||||||
|
type="button"
|
||||||
|
safe
|
||||||
|
>
|
||||||
|
{target}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<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(
|
{Object.entries(getPossibleTargets(body.fileType)).map(
|
||||||
([converter, targets]) => (
|
([converter, targets]) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<optgroup label={converter}>
|
<optgroup label={converter}>
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<option value={`${target},${converter}`} safe>
|
<option value={`${target},${converter}`} safe>
|
||||||
{target}
|
{target}
|
||||||
</option>
|
</option>
|
||||||
@@ -525,6 +718,7 @@ const app = new Elysia({
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{ body: t.Object({ fileType: t.String() }) },
|
{ body: t.Object({ fileType: t.String() }) },
|
||||||
@@ -561,13 +755,7 @@ const app = new Elysia({
|
|||||||
await Bun.write(`${userUploadsDir}${file.name}`, file);
|
await Bun.write(`${userUploadsDir}${file.name}`, file);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await Bun.write(
|
await Bun.write(`${userUploadsDir}${body.file["name"]}`, body.file);
|
||||||
`${userUploadsDir}${
|
|
||||||
// biome-ignore lint/complexity/useLiteralKeys: ts bug
|
|
||||||
body.file["name"]
|
|
||||||
}`,
|
|
||||||
body.file,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,7 +811,7 @@ const app = new Elysia({
|
|||||||
return redirect("/", 302);
|
return redirect("/", 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingJob = await 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);
|
||||||
@@ -645,9 +833,7 @@ const app = new Elysia({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const convertTo = normalizeFiletype(
|
const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? "");
|
||||||
body.convert_to.split(",")[0] as string,
|
|
||||||
);
|
|
||||||
const converterName = body.convert_to.split(",")[1];
|
const converterName = body.convert_to.split(",")[1];
|
||||||
const fileNames = JSON.parse(body.file_names) as string[];
|
const fileNames = JSON.parse(body.file_names) as string[];
|
||||||
|
|
||||||
@@ -667,10 +853,13 @@ const app = new Elysia({
|
|||||||
Promise.all(
|
Promise.all(
|
||||||
fileNames.map(async (fileName) => {
|
fileNames.map(async (fileName) => {
|
||||||
const filePath = `${userUploadsDir}${fileName}`;
|
const filePath = `${userUploadsDir}${fileName}`;
|
||||||
const fileTypeOrig = fileName.split(".").pop() as string;
|
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(fileTypeOrig, newFileExt);
|
const newFileName = fileName.replace(
|
||||||
|
new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
|
||||||
|
newFileExt,
|
||||||
|
);
|
||||||
const targetPath = `${userOutputDir}${newFileName}`;
|
const targetPath = `${userOutputDir}${newFileName}`;
|
||||||
|
|
||||||
const result = await mainConverter(
|
const result = await mainConverter(
|
||||||
@@ -742,29 +931,42 @@ const app = new Elysia({
|
|||||||
<BaseHtml title="ConvertX | Results">
|
<BaseHtml title="ConvertX | Results">
|
||||||
<>
|
<>
|
||||||
<Header loggedIn />
|
<Header loggedIn />
|
||||||
<main class="container">
|
<main class="w-full px-4">
|
||||||
<article>
|
<article class="article">
|
||||||
<h1>Results</h1>
|
<h1 class="mb-4 text-xl">Results</h1>
|
||||||
<table>
|
<table
|
||||||
|
class={`
|
||||||
|
w-full table-auto rounded bg-neutral-900 text-left
|
||||||
|
[&_td]:p-4
|
||||||
|
[&_tr]:rounded [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
|
`}
|
||||||
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Time</th>
|
<th class="px-4 py-2">Time</th>
|
||||||
<th>Files</th>
|
<th class="px-4 py-2">Files</th>
|
||||||
<th>Files Done</th>
|
<th class="px-4 py-2">Files Done</th>
|
||||||
<th>Status</th>
|
<th class="px-4 py-2">Status</th>
|
||||||
<th>View</th>
|
<th class="px-4 py-2">View</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{userJobs.map((job) => (
|
{userJobs.map((job) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td safe>{job.date_created}</td>
|
<td safe>{job.date_created}</td>
|
||||||
<td>{job.num_files}</td>
|
<td>{job.num_files}</td>
|
||||||
<td>{job.finished_files}</td>
|
<td>{job.finished_files}</td>
|
||||||
<td safe>{job.status}</td>
|
<td safe>{job.status}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={`/results/${job.id}`}>View</a>
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-500 underline
|
||||||
|
hover:text-accent-400
|
||||||
|
`}
|
||||||
|
href={`/results/${job.id}`}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@@ -793,7 +995,7 @@ const app = new Elysia({
|
|||||||
return redirect("/login", 302);
|
return redirect("/login", 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const job = await db
|
const job = db
|
||||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||||
.as(Jobs)
|
.as(Jobs)
|
||||||
.get(user.id, params.jobId);
|
.get(user.id, params.jobId);
|
||||||
@@ -816,50 +1018,77 @@ const app = new Elysia({
|
|||||||
<BaseHtml title="ConvertX | Result">
|
<BaseHtml title="ConvertX | Result">
|
||||||
<>
|
<>
|
||||||
<Header loggedIn />
|
<Header loggedIn />
|
||||||
<main class="container">
|
<main class="w-full px-4">
|
||||||
<article>
|
<article class="article">
|
||||||
<div class="grid">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<h1>Results</h1>
|
<h1 class="text-xl">Results</h1>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
style={{ width: "10rem", float: "right" }}
|
class="btn-primary float-right w-40"
|
||||||
onclick="downloadAll()"
|
onclick="downloadAll()"
|
||||||
{...(files.length !== job.num_files
|
{...(files.length !== job.num_files
|
||||||
? { disabled: true, "aria-busy": "true" }
|
? { disabled: true, "aria-busy": "true" }
|
||||||
: "")}>
|
: "")}
|
||||||
|
>
|
||||||
{files.length === job.num_files
|
{files.length === job.num_files
|
||||||
? "Download All"
|
? "Download All"
|
||||||
: "Converting..."}
|
: "Converting..."}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<progress max={job.num_files} value={files.length} />
|
<progress
|
||||||
<table>
|
max={job.num_files}
|
||||||
|
value={files.length}
|
||||||
|
class={`
|
||||||
|
mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full
|
||||||
|
border-0 bg-neutral-700 bg-none text-accent-500 accent-accent-500
|
||||||
|
[&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full
|
||||||
|
[&::-webkit-progress-value]:[background:none]
|
||||||
|
[&[value]::-webkit-progress-value]:bg-accent-500
|
||||||
|
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
<table
|
||||||
|
class={`
|
||||||
|
w-full table-auto rounded bg-neutral-900 text-left
|
||||||
|
[&_td]:p-4
|
||||||
|
[&_tr]:rounded [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
|
`}
|
||||||
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Converted File Name</th>
|
<th class="px-4 py-2">Converted File Name</th>
|
||||||
<th>Status</th>
|
<th class="px-4 py-2">Status</th>
|
||||||
<th>View</th>
|
<th class="px-4 py-2">View</th>
|
||||||
<th>Download</th>
|
<th class="px-4 py-2">Download</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{files.map((file) => (
|
{files.map((file) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td safe>{file.output_file_name}</td>
|
<td safe>{file.output_file_name}</td>
|
||||||
<td safe>{file.status}</td>
|
<td safe>{file.status}</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href={`/download/${outputPath}${file.output_file_name}`}>
|
class={`
|
||||||
|
text-accent-500 underline
|
||||||
|
hover:text-accent-400
|
||||||
|
`}
|
||||||
|
href={`/download/${outputPath}${file.output_file_name}`}
|
||||||
|
>
|
||||||
View
|
View
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-500 underline
|
||||||
|
hover:text-accent-400
|
||||||
|
`}
|
||||||
href={`/download/${outputPath}${file.output_file_name}`}
|
href={`/download/${outputPath}${file.output_file_name}`}
|
||||||
download={file.output_file_name}>
|
download={file.output_file_name}
|
||||||
|
>
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -892,7 +1121,7 @@ const app = new Elysia({
|
|||||||
return redirect("/login", 302);
|
return redirect("/login", 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const job = await db
|
const job = db
|
||||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||||
.as(Jobs)
|
.as(Jobs)
|
||||||
.get(user.id, params.jobId);
|
.get(user.id, params.jobId);
|
||||||
@@ -912,48 +1141,76 @@ const app = new Elysia({
|
|||||||
.all(params.jobId);
|
.all(params.jobId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article>
|
<article class="article">
|
||||||
<div class="grid">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<h1>Results</h1>
|
<h1 class="text-xl">Results</h1>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
style={{ width: "10rem", float: "right" }}
|
class="btn-primary float-right w-40"
|
||||||
onclick="downloadAll()"
|
onclick="downloadAll()"
|
||||||
{...(files.length !== job.num_files
|
{...(files.length !== job.num_files
|
||||||
? { disabled: true, "aria-busy": "true" }
|
? { disabled: true, "aria-busy": "true" }
|
||||||
: "")}>
|
: "")}
|
||||||
|
>
|
||||||
{files.length === job.num_files
|
{files.length === job.num_files
|
||||||
? "Download All"
|
? "Download All"
|
||||||
: "Converting..."}
|
: "Converting..."}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<progress max={job.num_files} value={files.length} />
|
<progress
|
||||||
<table>
|
max={job.num_files}
|
||||||
|
value={files.length}
|
||||||
|
class={`
|
||||||
|
mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0
|
||||||
|
bg-neutral-700 bg-none text-accent-500 accent-accent-500
|
||||||
|
[&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full
|
||||||
|
[&::-webkit-progress-value]:[background:none]
|
||||||
|
[&[value]::-webkit-progress-value]:bg-accent-500
|
||||||
|
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
<table
|
||||||
|
class={`
|
||||||
|
w-full table-auto rounded bg-neutral-900 text-left
|
||||||
|
[&_td]:p-4
|
||||||
|
[&_tr]:rounded [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
|
`}
|
||||||
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Converted File Name</th>
|
<th class="px-4 py-2">Converted File Name</th>
|
||||||
<th>Status</th>
|
<th class="px-4 py-2">Status</th>
|
||||||
<th>View</th>
|
<th class="px-4 py-2">View</th>
|
||||||
<th>Download</th>
|
<th class="px-4 py-2">Download</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{files.map((file) => (
|
{files.map((file) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td safe>{file.output_file_name}</td>
|
<td safe>{file.output_file_name}</td>
|
||||||
<td safe>{file.status}</td>
|
<td safe>{file.status}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={`/download/${outputPath}${file.output_file_name}`}>
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-500 underline
|
||||||
|
hover:text-accent-400
|
||||||
|
`}
|
||||||
|
href={`/download/${outputPath}${file.output_file_name}`}
|
||||||
|
>
|
||||||
View
|
View
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
|
class={`
|
||||||
|
text-accent-500 underline
|
||||||
|
hover:text-accent-400
|
||||||
|
`}
|
||||||
href={`/download/${outputPath}${file.output_file_name}`}
|
href={`/download/${outputPath}${file.output_file_name}`}
|
||||||
download={file.output_file_name}>
|
download={file.output_file_name}
|
||||||
|
>
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@@ -1007,15 +1264,22 @@ const app = new Elysia({
|
|||||||
<BaseHtml title="ConvertX | Converters">
|
<BaseHtml title="ConvertX | Converters">
|
||||||
<>
|
<>
|
||||||
<Header loggedIn />
|
<Header loggedIn />
|
||||||
<main class="container">
|
<main class="w-full px-4">
|
||||||
<article>
|
<article class="article">
|
||||||
<h1>Converters</h1>
|
<h1 class="mb-4 text-xl">Converters</h1>
|
||||||
<table>
|
<table
|
||||||
|
class={`
|
||||||
|
w-full table-auto rounded bg-neutral-900 text-left
|
||||||
|
[&_td]:p-4
|
||||||
|
[&_tr]:rounded [&_tr]:border-b [&_tr]:border-neutral-800
|
||||||
|
[&_ul]:list-inside [&_ul]:list-disc
|
||||||
|
`}
|
||||||
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Converter</th>
|
<th class="mx-4 my-2">Converter</th>
|
||||||
<th>From (Count)</th>
|
<th class="mx-4 my-2">From (Count)</th>
|
||||||
<th>To (Count)</th>
|
<th class="mx-4 my-2">To (Count)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -1023,14 +1287,12 @@ const app = new Elysia({
|
|||||||
([converter, targets]) => {
|
([converter, targets]) => {
|
||||||
const inputs = getAllInputs(converter);
|
const inputs = getAllInputs(converter);
|
||||||
return (
|
return (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<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) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<li safe>{input}</li>
|
<li safe>{input}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -1039,7 +1301,6 @@ const app = new Elysia({
|
|||||||
Count: {targets.length}
|
Count: {targets.length}
|
||||||
<ul>
|
<ul>
|
||||||
{targets.map((target) => (
|
{targets.map((target) => (
|
||||||
// biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
|
|
||||||
<li safe>{target}</li>
|
<li safe>{target}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -1077,30 +1338,44 @@ const app = new Elysia({
|
|||||||
return redirect("/results", 302);
|
return redirect("/results", 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = decodeURIComponent(params.userId);
|
// const userId = decodeURIComponent(params.userId);
|
||||||
const jobId = decodeURIComponent(params.jobId);
|
// const jobId = decodeURIComponent(params.jobId);
|
||||||
const outputPath = `${outputDir}${userId}/${jobId}/`;
|
// const outputPath = `${outputDir}${userId}/${jobId}/`;
|
||||||
|
|
||||||
// return Bun.zip(outputPath);
|
// return Bun.zip(outputPath);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.onError(({ code, error, request }) => {
|
.onError(({ error }) => {
|
||||||
// log.error(` ${request.method} ${request.url}`, code, error);
|
// log.error(` ${request.method} ${request.url}`, code, error);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
})
|
});
|
||||||
.listen(3000);
|
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
await import("./helpers/tailwind").then(async ({ generateTailwind }) => {
|
||||||
|
const result = await generateTailwind();
|
||||||
|
|
||||||
|
app.get("/generated.css", ({ set }) => {
|
||||||
|
set.headers["content-type"] = "text/css";
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.listen(3000);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`,
|
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearJobs = () => {
|
const clearJobs = () => {
|
||||||
// clear all jobs older than 24 hours
|
|
||||||
// get all files older than 24 hours
|
|
||||||
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() - 24 * 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
|
||||||
@@ -1111,7 +1386,9 @@ const clearJobs = () => {
|
|||||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run every 24 hours
|
setTimeout(clearJobs, AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000);
|
||||||
setTimeout(clearJobs, 24 * 60 * 60 * 1000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (AUTO_DELETE_EVERY_N_HOURS > 0) {
|
||||||
clearJobs();
|
clearJobs();
|
||||||
|
}
|
||||||
|
|||||||
45
src/main.css
Normal file
45
src/main.css
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.article {
|
||||||
|
@apply p-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
@apply bg-accent-500 text-contrast rounded p-4 hover:bg-accent-400 cursor-pointer transition-colors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--contrast: 255, 255, 255;
|
||||||
|
--neutral-900: 243, 244, 246;
|
||||||
|
--neutral-800: 229, 231, 235;
|
||||||
|
--neutral-700: 209, 213, 219;
|
||||||
|
--neutral-600: 156, 163, 175;
|
||||||
|
--neutral-500: 180, 180, 180;
|
||||||
|
--neutral-400: 75, 85, 99;
|
||||||
|
--neutral-300: 55, 65, 81;
|
||||||
|
--neutral-200: 31, 41, 55;
|
||||||
|
--neutral-100: 17, 24, 39;
|
||||||
|
--accent-400: 132, 204, 22;
|
||||||
|
--accent-500: 101, 163, 13;
|
||||||
|
--accent-600: 77, 124, 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--contrast: 0, 0, 0;
|
||||||
|
--neutral-900: 17, 24, 39;
|
||||||
|
--neutral-800: 31, 41, 55;
|
||||||
|
--neutral-700: 55, 65, 81;
|
||||||
|
--neutral-600: 75, 85, 99;
|
||||||
|
--neutral-500: 107, 114, 128;
|
||||||
|
--neutral-300: 209, 213, 219;
|
||||||
|
--neutral-400: 156, 163, 175;
|
||||||
|
--neutral-200: 229, 231, 235;
|
||||||
|
--accent-600: 101, 163, 13;
|
||||||
|
--accent-500: 132, 204, 22;
|
||||||
|
--accent-400: 163, 230, 53;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/public/pico.lime.min.css
vendored
4
src/public/pico.lime.min.css
vendored
File diff suppressed because one or more lines are too long
2
src/public/robots.txt
Normal file
2
src/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
@@ -1,9 +1,98 @@
|
|||||||
// Select the file input element
|
// Select the file input element
|
||||||
const fileInput = document.querySelector('input[type="file"]');
|
const fileInput = document.querySelector('input[type="file"]');
|
||||||
|
const dropZone = document.getElementById("dropzone");
|
||||||
const fileNames = [];
|
const fileNames = [];
|
||||||
let fileType;
|
let fileType;
|
||||||
|
|
||||||
const selectContainer = document.querySelector("form > article");
|
dropZone.addEventListener("dragover", () => {
|
||||||
|
dropZone.classList.add("dragover");
|
||||||
|
});
|
||||||
|
|
||||||
|
dropZone.addEventListener("dragleave", () => {
|
||||||
|
dropZone.classList.remove("dragover");
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectContainer = document.querySelector("form .select_container");
|
||||||
|
|
||||||
|
const updateSearchBar = () => {
|
||||||
|
const convertToInput = document.querySelector(
|
||||||
|
"input[name='convert_to_search']",
|
||||||
|
);
|
||||||
|
const convertToPopup = document.querySelector(".convert_to_popup");
|
||||||
|
const convertToGroupElements = document.querySelectorAll(".convert_to_group");
|
||||||
|
const convertToGroups = {};
|
||||||
|
const convertToElement = document.querySelector("select[name='convert_to']");
|
||||||
|
const convertButton = document.querySelector("input[type='submit']");
|
||||||
|
|
||||||
|
const showMatching = (search) => {
|
||||||
|
for (const [targets, groupElement] of Object.values(convertToGroups)) {
|
||||||
|
let matchingTargetsFound = 0;
|
||||||
|
for (const target of targets) {
|
||||||
|
if (target.dataset.target.includes(search)) {
|
||||||
|
matchingTargetsFound++;
|
||||||
|
target.classList.remove("hidden");
|
||||||
|
target.classList.add("flex");
|
||||||
|
} else {
|
||||||
|
target.classList.add("hidden");
|
||||||
|
target.classList.remove("flex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingTargetsFound === 0) {
|
||||||
|
groupElement.classList.add("hidden");
|
||||||
|
groupElement.classList.remove("flex");
|
||||||
|
} else {
|
||||||
|
groupElement.classList.remove("hidden");
|
||||||
|
groupElement.classList.add("flex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const groupElement of convertToGroupElements) {
|
||||||
|
const groupName = groupElement.dataset.converter;
|
||||||
|
|
||||||
|
const targetElements = groupElement.querySelectorAll(".target");
|
||||||
|
const targets = Array.from(targetElements);
|
||||||
|
|
||||||
|
for (const target of targets) {
|
||||||
|
target.onmousedown = () => {
|
||||||
|
convertToElement.value = target.dataset.value;
|
||||||
|
convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`;
|
||||||
|
convertButton.disabled = false;
|
||||||
|
showMatching("");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
convertToGroups[groupName] = [targets, groupElement];
|
||||||
|
}
|
||||||
|
|
||||||
|
convertToInput.addEventListener("input", (e) => {
|
||||||
|
showMatching(e.target.value.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
convertToInput.addEventListener("search", () => {
|
||||||
|
// when the user clears the search bar using the 'x' button
|
||||||
|
convertButton.disabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
convertToInput.addEventListener("blur", (e) => {
|
||||||
|
// Keep the popup open even when clicking on a target button
|
||||||
|
// for a split second to allow the click to go through
|
||||||
|
if (e?.relatedTarget?.classList?.contains("target")) {
|
||||||
|
convertToPopup.classList.add("hidden");
|
||||||
|
convertToPopup.classList.remove("flex");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertToPopup.classList.add("hidden");
|
||||||
|
convertToPopup.classList.remove("flex");
|
||||||
|
});
|
||||||
|
|
||||||
|
convertToInput.addEventListener("focus", () => {
|
||||||
|
convertToPopup.classList.remove("hidden");
|
||||||
|
convertToPopup.classList.add("flex");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// const convertFromSelect = document.querySelector("select[name='convert_from']");
|
// const convertFromSelect = document.querySelector("select[name='convert_from']");
|
||||||
|
|
||||||
@@ -28,6 +117,7 @@ fileInput.addEventListener("change", (e) => {
|
|||||||
|
|
||||||
if (!fileType) {
|
if (!fileType) {
|
||||||
fileType = file.name.split(".").pop();
|
fileType = file.name.split(".").pop();
|
||||||
|
console.log("fileType", fileType);
|
||||||
fileInput.setAttribute("accept", `.${fileType}`);
|
fileInput.setAttribute("accept", `.${fileType}`);
|
||||||
setTitle();
|
setTitle();
|
||||||
|
|
||||||
@@ -49,6 +139,7 @@ fileInput.addEventListener("change", (e) => {
|
|||||||
.then((res) => res.text())
|
.then((res) => res.text())
|
||||||
.then((html) => {
|
.then((html) => {
|
||||||
selectContainer.innerHTML = html;
|
selectContainer.innerHTML = html;
|
||||||
|
updateSearchBar();
|
||||||
})
|
})
|
||||||
.catch((err) => console.log(err));
|
.catch((err) => console.log(err));
|
||||||
}
|
}
|
||||||
@@ -69,6 +160,7 @@ const setTitle = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add a onclick for the delete button
|
// Add a onclick for the delete button
|
||||||
|
// 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;
|
||||||
@@ -119,7 +211,9 @@ const uploadFiles = (files) => {
|
|||||||
|
|
||||||
const formConvert = document.querySelector("form[action='/convert']");
|
const formConvert = document.querySelector("form[action='/convert']");
|
||||||
|
|
||||||
formConvert.addEventListener("submit", (e) => {
|
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();
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
div.icon {
|
|
||||||
height: 100px;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[type="submit"] {
|
|
||||||
width: 50%
|
|
||||||
}
|
|
||||||
|
|
||||||
div.center {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
26
tailwind.config.js
Normal file
26
tailwind.config.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
content: ["./src/**/*.{html,js,tsx,jsx,cjs,mjs}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
contrast: "rgba(var(--contrast))",
|
||||||
|
"neutral-900": "rgba(var(--neutral-900))",
|
||||||
|
"neutral-800": "rgba(var(--neutral-800))",
|
||||||
|
"neutral-700": "rgba(var(--neutral-700))",
|
||||||
|
"neutral-600": "rgba(var(--neutral-600))",
|
||||||
|
"neutral-500": "rgba(var(--neutral-500))",
|
||||||
|
"neutral-400": "rgba(var(--neutral-400))",
|
||||||
|
"neutral-300": "rgba(var(--neutral-300))",
|
||||||
|
"neutral-200": "rgba(var(--neutral-200))",
|
||||||
|
"neutral-100": "rgba(var(--neutral-100))",
|
||||||
|
"accent-600": "rgba(var(--accent-600))",
|
||||||
|
"accent-500": "rgba(var(--accent-500))",
|
||||||
|
"accent-400": "rgba(var(--accent-400))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require("tailwind-scrollbar")],
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext"],
|
"lib": ["ESNext"],
|
||||||
"module": "esnext",
|
"module": "ESNext",
|
||||||
"target": "esnext",
|
"target": "ES2021",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user