Compare commits

...

135 commits
v0.6.3 ... main

Author SHA1 Message Date
forgejo-bot
691e294546 chore: update minor dependencies 2025-04-18 23:00:25 +00:00
forgejo-bot
885c802da6 chore: update minor dependencies 2025-04-17 23:00:58 +00:00
forgejo-bot
6d0d7ccecc chore: update minor dependencies 2025-04-16 23:00:47 +00:00
forgejo-bot
ff5cef233d chore: update minor dependencies 2025-04-15 23:00:57 +00:00
forgejo-bot
2b81cb2fa5 chore: update minor dependencies 2025-04-14 23:01:16 +00:00
forgejo-bot
9ffcd5f596 chore: update minor dependencies 2025-04-13 23:18:23 +00:00
forgejo-bot
1bd6d90814 chore: update minor dependencies 2025-04-12 23:01:12 +00:00
forgejo-bot
c46189ff83 chore: update minor dependencies 2025-04-11 23:01:16 +00:00
forgejo-bot
c416918a1a chore: update minor dependencies 2025-04-10 23:01:11 +00:00
forgejo-bot
7a3bf1fb45 chore: update minor dependencies 2025-04-09 23:01:13 +00:00
forgejo-bot
cd8a4c4d69 chore: update minor dependencies 2025-04-08 23:01:09 +00:00
0d0
9c7f7eace1 maybe its better like this
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 23s
2025-04-08 21:34:12 +02:00
0d0
5066c3ba7a better css 2025-04-08 21:31:34 +02:00
0d0
a833c43713 change tmp name 2025-04-08 21:18:48 +02:00
0d0
a18fbb8614 0.9.3-c
All checks were successful
Create Release / create-release (push) Successful in 32s
Bump deps (only minor versions) / ci (push) Successful in 20s
2025-04-08 20:33:08 +02:00
forgejo-bot
bb233f7cb8 chore: update minor dependencies 2025-04-07 23:01:13 +00:00
forgejo-bot
2f502b2530 chore: update minor dependencies 2025-04-05 23:00:17 +00:00
forgejo-bot
c32cc4d3e7 chore: update minor dependencies 2025-04-04 23:00:53 +00:00
forgejo-bot
5fc6b0700a chore: update minor dependencies 2025-04-03 23:00:53 +00:00
forgejo-bot
b94d5ffc19 chore: update minor dependencies 2025-04-02 23:01:04 +00:00
forgejo-bot
267da475a6 chore: update minor dependencies 2025-04-01 23:01:09 +00:00
forgejo-bot
82aac1d3b9 chore: update minor dependencies 2025-03-31 23:01:13 +00:00
odo
56ef54b119 Aggiorna README.md
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 28s
2025-03-31 08:31:50 +00:00
forgejo-bot
d21d4d57a9 chore: update minor dependencies 2025-03-29 23:00:51 +00:00
0d0
214619ec3f 0.9.3-b
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-03-29 17:18:07 +01:00
forgejo-bot
18adf9a6a3 chore: update minor dependencies 2025-03-28 23:00:51 +00:00
forgejo-bot
66196979cc chore: update minor dependencies 2025-03-27 23:00:54 +00:00
forgejo-bot
4d0eaed688 chore: update minor dependencies 2025-03-26 23:00:41 +00:00
forgejo-bot
6003e59aaa chore: update minor dependencies 2025-03-25 23:00:44 +00:00
forgejo-bot
99ae01f895 chore: update minor dependencies 2025-03-24 23:00:45 +00:00
forgejo-bot
9f8b49515e chore: update minor dependencies 2025-03-23 23:00:39 +00:00
forgejo-bot
c7d65ee49a chore: update minor dependencies 2025-03-22 23:00:42 +00:00
forgejo-bot
84feab36dc chore: update minor dependencies 2025-03-21 23:00:46 +00:00
forgejo-bot
728db51c59 chore: update minor dependencies 2025-03-20 23:00:47 +00:00
forgejo-bot
c3072322d1 chore: update minor dependencies 2025-03-18 23:00:43 +00:00
forgejo-bot
565612cf30 chore: update minor dependencies 2025-03-17 23:00:46 +00:00
0d0
77d1d7331d 0.9.3
All checks were successful
Create Release / create-release (push) Successful in 30s
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-03-16 21:35:06 +01:00
0d0
ebf597fea6 Bump deps 2025-03-16 21:33:34 +01:00
forgejo-bot
7dc90c4c27 chore: update minor dependencies 2025-03-15 23:00:23 +00:00
forgejo-bot
96d59113bc chore: update minor dependencies 2025-03-14 23:00:29 +00:00
forgejo-bot
e0fbca1e6d chore: update minor dependencies 2025-03-13 23:01:04 +00:00
0d0
2899def330 0.9.2
All checks were successful
Create Release / create-release (push) Successful in 32s
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-03-12 19:50:21 +01:00
forgejo-bot
e4dffe691c chore: update minor dependencies 2025-03-12 18:35:39 +00:00
forgejo-bot
c55524221a chore: update minor dependencies 2025-03-03 23:00:42 +00:00
forgejo-bot
1f5a6613d0 chore: update minor dependencies 2025-02-28 23:00:48 +00:00
0d0
a4bec2cb38 Put correct mime-type
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-02-28 07:47:53 +01:00
forgejo-bot
f080ccc912 chore: update minor dependencies 2025-02-27 23:00:13 +00:00
forgejo-bot
8e37875442 chore: update minor dependencies 2025-02-26 23:00:16 +00:00
0d0
af9c2084eb 0.9.1
All checks were successful
Create Release / create-release (push) Successful in 54s
Bump deps (only minor versions) / ci (push) Successful in 33s
2025-02-26 16:52:55 +01:00
0d0
13ba9fd1a1 Ops fix wording
Some checks failed
Bump deps (only minor versions) / ci (push) Has been cancelled
2025-02-26 16:52:43 +01:00
0d0
3888d9d481 0.9.0
All checks were successful
Create Release / create-release (push) Successful in 37s
Bump deps (only minor versions) / ci (push) Successful in 38s
2025-02-26 16:19:04 +01:00
0d0
5ff4dc73de Update install script to accept proxy
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 37s
2025-02-26 16:18:52 +01:00
0d0
2f25ccf600 Lint and format
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 34s
2025-02-26 16:11:16 +01:00
0d0
84d4379f16 Add support for proxies 2025-02-26 16:11:16 +01:00
forgejo-bot
9f4a2bc560 chore: update minor dependencies 2025-02-25 23:00:47 +00:00
0d0
e525eb8a41 add .dockerignore
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-25 17:56:11 +01:00
0d0
5b278b75c0 Write only error logs on production
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 21s
2025-02-25 17:10:10 +01:00
0d0
652208aa57 it's cool that we have format
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-25 17:06:41 +01:00
0d0
760812c692 Update docs 2025-02-25 17:06:17 +01:00
0d0
3aaadc2b92 Remove the
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-25 16:56:25 +01:00
0d0
4aaaa4eedc Add better readme
Some checks failed
Bump deps (only minor versions) / ci (push) Has been cancelled
2025-02-25 16:56:07 +01:00
0d0
da528e3a81 Add better coloring
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-25 16:52:11 +01:00
0d0
cf3733530b Remove sse helpres
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-25 16:50:10 +01:00
0d0
1b76d4de4b Add type of request to logs 2025-02-25 16:50:03 +01:00
0d0
8224e620eb Add SSE Buffer Helper 2025-02-25 16:08:02 +01:00
0d0
c16790699c Update loader 2025-02-25 16:07:52 +01:00
0d0
25312ec595 Add sveltekit-sse
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-25 14:29:52 +01:00
0d0
24ff0a0738 Use winston and error management of stream controller
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 20s
2025-02-25 14:26:07 +01:00
0d0
0b58d9251e Add metadata if used 2025-02-25 14:25:40 +01:00
0d0
e4b1bcc677 Configure winston 2025-02-25 14:25:27 +01:00
0d0
f81083dec6 Add winston 2025-02-25 14:25:20 +01:00
0d0
942e6ed59a Add package-lock to deps check also
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-25 13:35:47 +01:00
0d0
a155cdf791 Update supported formats 2025-02-25 13:34:48 +01:00
0d0
aca6510197 wops deps
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
Create Release / create-release (push) Successful in 31s
2025-02-25 01:20:37 +01:00
0d0
af971bec5d 0.8.3
Some checks failed
Create Release / create-release (push) Failing after 20s
Bump deps (only minor versions) / ci (push) Successful in 24s
2025-02-25 01:19:11 +01:00
0d0
217a9b1028 change wording
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 22s
2025-02-25 01:17:35 +01:00
0d0
d1b80f2cc9 Add link to image
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 22s
2025-02-25 01:16:48 +01:00
0d0
89b2e4aaa2 Invert image and title
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 25s
2025-02-25 01:16:18 +01:00
0d0
697a8a73fe Remove ci from prepare check and lint action
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 23s
2025-02-25 01:15:35 +01:00
0d0
575fdc6386 Update readme
Some checks failed
Bump deps (only minor versions) / ci (push) Failing after 15s
2025-02-25 01:14:53 +01:00
0d0
1c893617dd OPS 2025-02-25 01:13:23 +01:00
0d0
90eaa0cbf1 Remove unused logs
Some checks failed
Bump deps (only minor versions) / ci (push) Failing after 14s
2025-02-25 01:06:44 +01:00
0d0
728d8f45cf Update default metadata and wording
Some checks failed
Bump deps (only minor versions) / ci (push) Failing after 13s
2025-02-25 01:02:01 +01:00
0d0
c8648b5cfe Fix font size of title
Some checks failed
Bump deps (only minor versions) / ci (push) Failing after 15s
2025-02-25 00:58:43 +01:00
0d0
3a5b705d5f Add excplict ssr,csr,prerender
Some checks failed
Bump deps (only minor versions) / ci (push) Failing after 14s
2025-02-25 00:57:02 +01:00
forgejo-bot
220244e26f chore: update minor dependencies 2025-02-24 23:00:38 +00:00
0d0
61e08c5bb9 0.8.2
All checks were successful
Create Release / create-release (push) Successful in 31s
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-02-24 20:14:22 +01:00
0d0
3be5e67018 Update manifest and logo
Some checks failed
Bump deps (only minor versions) / ci (push) Has been cancelled
2025-02-24 20:13:53 +01:00
0d0
e3e914882f update name
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-02-24 20:08:43 +01:00
0d0
b0ed4a7cc4 Do not use npm ci on deps check
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 20s
2025-02-24 20:07:51 +01:00
0d0
26a68fc102 Update readme
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 20s
2025-02-24 20:05:43 +01:00
0d0
4a12828571 Update screen
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 20s
2025-02-24 20:05:10 +01:00
0d0
bb548befc5 update README
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 20s
2025-02-24 19:59:55 +01:00
0d0
e9040bbf2b 0.8.1
All checks were successful
Create Release / create-release (push) Successful in 32s
Bump deps (only minor versions) / ci (push) Successful in 20s
2025-02-24 19:45:28 +01:00
0d0
f2673d112b this should do it
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 26s
2025-02-24 19:43:11 +01:00
0d0
fa24bc435d YTDLP_PATH everywhere
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-02-23 07:55:42 +01:00
0d0
da95e2024f 0.8.0
All checks were successful
Create Release / create-release (push) Successful in 29s
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 06:37:43 +01:00
0d0
6f93574e8a Remove useless helper
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 21s
2025-02-23 06:37:13 +01:00
0d0
5d0fa4e951 update systemd unit file
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-02-23 06:18:50 +01:00
0d0
0d3b797ec8 Remove others
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 16s
2025-02-23 06:12:33 +01:00
0d0
284313259a 0.7.2
All checks were successful
Create Release / create-release (push) Successful in 29s
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 06:06:26 +01:00
0d0
b5cbef9de7 use path also on spawn
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 15s
2025-02-23 06:05:57 +01:00
0d0
109a668413 New deploy method
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 16s
2025-02-23 06:05:06 +01:00
0d0
631b9e44f0 update name
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-23 05:38:29 +01:00
0d0
99096f3d24 0.7.1
All checks were successful
Create Release / create-release (push) Successful in 27s
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-02-23 05:26:29 +01:00
0d0
6b8779f527 better code
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 16s
2025-02-23 05:22:37 +01:00
0d0
b16b84e5a3 0.7.0
All checks were successful
Create Release / create-release (push) Successful in 29s
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-23 05:10:22 +01:00
0d0
45b85fed1d Way better UI
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 16s
2025-02-23 05:09:56 +01:00
0d0
d523c4853f Update release pipeline names
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 04:59:09 +01:00
0d0
c7e99846b1 0.6.9
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
Create Release / create-release (push) Successful in 31s
2025-02-23 04:56:21 +01:00
0d0
4b9018ecbb Forgotten package lock
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 16s
2025-02-23 04:56:00 +01:00
0d0
dcb7cfec27 Add loader and new formats
Some checks failed
Bump deps (only minor versions) / ci (push) Failing after 11s
2025-02-23 04:55:09 +01:00
0d0
204bceeee3 Add +x to downloaded yt-dlp 2025-02-23 04:54:48 +01:00
0d0
151b5432cd Update globals 2025-02-23 02:35:00 +01:00
0d0
10d8ce8a15 fix dc
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-23 02:31:18 +01:00
forgejo-bot
79c3c820a4 chore: update minor dependencies 2025-02-23 01:23:58 +00:00
0d0
15cb677910 try
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-02-23 02:21:20 +01:00
0d0
6e86aa7f8c maybe
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-23 02:12:19 +01:00
0d0
7c9b80917f lol
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 02:11:40 +01:00
0d0
f20b9869cc should work
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-23 02:08:08 +01:00
0d0
11b0b9f6a2 test
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 02:04:53 +01:00
0d0
7213189744 test
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-23 02:03:40 +01:00
0d0
a5e3e310b7 test
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 02:03:07 +01:00
0d0
e06d04f387 every minute
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 18s
2025-02-23 02:01:54 +01:00
0d0
4c08c6ee4a 0.6.7
All checks were successful
Create Release / create-release (push) Successful in 30s
Bump deps (only minor versions) / ci (push) Successful in 16s
2025-02-23 01:55:42 +01:00
0d0
6924e8e082 new release pipeline
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 01:55:25 +01:00
0d0
87746bdcac We were using install instead of clean install
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 16s
2025-02-23 01:40:40 +01:00
0d0
b13e27d7e5 0.6.6
All checks were successful
Create Release / create-release (push) Successful in 30s
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 01:37:04 +01:00
0d0
64e3e21621 fix
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 17s
2025-02-23 01:36:52 +01:00
0d0
51b61f53c8 0.6.5
Some checks failed
Bump deps (only minor versions) / ci (push) Successful in 16s
Create Release / create-release (push) Failing after 25s
2025-02-23 01:35:28 +01:00
0d0
96e12b90f0 no need to prepare
All checks were successful
Bump deps (only minor versions) / ci (push) Successful in 19s
2025-02-23 01:34:16 +01:00
0d0
bf92e60d2b update release pipeline
Some checks failed
Bump deps (only minor versions) / ci (push) Has been cancelled
2025-02-23 01:32:53 +01:00
0d0
6a731be767 test
Some checks failed
Bump deps (only minor versions) / ci (push) Has been cancelled
2025-02-23 01:24:57 +01:00
0d0
48bd9a40e0 prepare does not need to be done here
Some checks failed
Bump deps (only minor versions) / ci (push) Failing after 23s
2025-02-23 01:22:27 +01:00
0d0
074320ba0e test ci
Some checks failed
Bump deps (only minor versions) / ci (push) Failing after 20s
2025-02-23 01:20:00 +01:00
24 changed files with 1502 additions and 814 deletions

23
.dockerignore Normal file
View file

@ -0,0 +1,23 @@
Dockerfile
.dockerignore
.git
.gitignore
.gitattributes
README.md
.npmrc
.prettierrc
.eslintrc.cjs
.graphqlrc
.editorconfig
.svelte-kit
.vscode
node_modules
build
package
**/.env
scripts/
downloads
yt-dlp
build.tar.gz

View file

@ -1,3 +1,5 @@
HOST=0.0.0.0
ORIGIN=https://dl.emersa.it
YTDLP_PATH=./yt-dlp YTDLP_PATH=./yt-dlp
ORIGIN=http://example.com PUBLIC_VERSION=0.6.3
HOST=0.0.0.0 HTTPS_PROXY=

View file

@ -17,6 +17,5 @@ jobs:
node-version: '20' node-version: '20'
- name: Prepare, Check, Lint and Format - name: Prepare, Check, Lint and Format
run: | run: |
npm install npm i
npm run prepare npm run prepare
npm run check

View file

@ -2,7 +2,7 @@ name: Bump deps (only minor versions)
on: on:
schedule: schedule:
- cron: '0 0 * * *' # Runs every night at midnight (UTC) - cron: '0 23 * * *' # Runs every night at midnight
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@ -26,7 +26,7 @@ jobs:
npm install npm install
git config --global user.name "forgejo-bot" git config --global user.name "forgejo-bot"
git config --global user.email "bot@pweapon.org" git config --global user.email "bot@pweapon.org"
git add package.json || exit 0 git add package* || exit 0
git commit -m "chore: update minor dependencies" git commit -m "chore: update minor dependencies"
git push origin HEAD:${GITHUB_REF#refs/heads/} git push origin HEAD:${GITHUB_REF#refs/heads/}
env: env:

View file

@ -21,14 +21,24 @@ jobs:
with: with:
node-version: '20' node-version: '20'
- name: Prepare, Check, Lint and Format - name: Install JQ for extracting package.json version
run: | run: |
npm install apt-get update
apt-get install -y jq
- name: Create Release
run: |
npm ci
touch .env
echo "HOST=0.0.0.0" >> .env
echo "ORIGIN=https://dl.emersa.it" >> .env
echo "PUBLIC_VERSION=$(cat package.json | jq .version)" >> .env
npm run build npm run build
cp .env.example build/.env
cp package* build/ cp package* build/
cp .env build/
mkdir releases mkdir releases
tar czvf releases/build.tar.gz build/ cd build/
tar czvf ../releases/build.tar.gz .
- name: Upload release - name: Upload release
uses: actions/forgejo-release@v2 uses: actions/forgejo-release@v2

View file

@ -2,22 +2,23 @@
![version](https://git.pweapon.org/odo/dl.emersa.it/badges/release.svg 'version') ![version](https://git.pweapon.org/odo/dl.emersa.it/badges/release.svg 'version')
![status](https://git.pweapon.org/odo/dl.emersa.it/badges/workflows/ci.yaml/badge.svg 'status') ![status](https://git.pweapon.org/odo/dl.emersa.it/badges/workflows/ci.yaml/badge.svg 'status')
![GNU](https://img.shields.io/badge/license-GPL--3.0-green?logo=gnu)
<img src='./static/screen.webp' alt='screen' height=300> It's a svelte(kit) frontend + backend that uses [`yt-dlp`](https://github.com/yt-dlp/yt-dlp) to let people download audio/video files from the web.
It's a svelte(kit) frontend that uses `yt-dlp` to let the user download things from the web. [`... we don't have the concept of users, when we refer to people, we call them people.`](https://ar.al/2020/08/07/what-is-the-small-web/)
## How to Deploy <a href='https://dl.emersa.it'><img src='./static/screen.webp' alt='screen' height=300></a>
## Self-Hosting
### Node ### Node
0. Install nodejs - Install NodeJS
1. Download the latest release from the [releases](https://git.pweapon.org/odo/dl.emersa.it/releases) page. - Create a new directory (`"${PROJECT_ROOT}"`)
2. Unpack and decompress (`tar xvf build.tar.gz`) the release - run `scripts/install.sh "${PROJECT_ROOT}"`
3. Then `cd` into it and run: `npm ci` and `npm run build`
4. Copy `.env.example` to `.env`, and add needed variables Edit `scripts/deploy_example.sh` if you need to deploy `scaricatore` to some server using `SSH`.
5. Run it: `node .`
6. You can also try to configure the systemd unit file inside the `configurations` folder.
### Docker ### Docker
@ -25,8 +26,8 @@ It's a svelte(kit) frontend that uses `yt-dlp` to let the user download things f
## Development: getting started ## Development: getting started
- Clone the repo - run `git clone git@git.pweapon.org:odo/dl.emersa.it.git`
- Run `npm install` (you have to have node installed) - Run `cd dl.emersa.it; npm install` (you have to have node installed)
- Run `npm run download-yt-dlp` - Run `npm run download-yt-dlp`
- Copy `.env.example` to `.env` - Copy `.env.example` to `.env`
- Change `.env` to set `YTDLP_PATH` to the yt-dlp binary previously downloaded - Change `.env` to set `YTDLP_PATH` to the yt-dlp binary previously downloaded
@ -35,7 +36,6 @@ It's a svelte(kit) frontend that uses `yt-dlp` to let the user download things f
## To do: ## To do:
- Proper logs (I don't like them, not useful for production build)
- Containerfile for container build - Containerfile for container build
- Source spotify (spotdl) - Source spotify (spotdl)
- Parse URL info without `youtube-dl-exec` - Parse URL info without `youtube-dl-exec`

View file

@ -5,9 +5,9 @@ After=network.target
[Service] [Service]
User=user User=user
Group=user Group=user
WorkingDirectory=<PROJECT_ROOT> WorkingDirectory=/home/user/downloader
ExecStart=/usr/bin/node <PROJECT_ROOT> EnvironmentFile=/home/user/downloader/.env
Environment=ORIGIN=http://example.com ExecStart=/usr/bin/node /home/user/downloader
Restart=always Restart=always
RestartSec=10 RestartSec=10
StandardOutput=syslog StandardOutput=syslog
@ -16,3 +16,4 @@ SyslogIdentifier=downloader
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

1637
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,12 @@
{ {
"name": "dl.emersa.it", "name": "dl.emersa.it",
"private": true, "private": true,
"version": "0.6.2", "version": "0.9.3-c",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"download-yt-dlp": "wget https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux -O yt-dlp", "download-yt-dlp": "wget https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux -O yt-dlp ; chmod +x yt-dlp",
"preview": "vite preview", "preview": "vite preview",
"prepare": "svelte-kit sync || echo ''", "prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@ -15,32 +15,34 @@
"lint": "prettier --check . && eslint ." "lint": "prettier --check . && eslint ."
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.5", "@eslint/compat": "^1.2.8",
"@eslint/js": "^9.18.0", "@eslint/js": "^9.25.0",
"@sveltejs/adapter-node": "^5.2.11", "@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/kit": "^2.16.0", "@sveltejs/kit": "^2.20.7",
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.1.4",
"eslint": "^9.18.0", "eslint": "^9.25.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.1.2",
"eslint-plugin-svelte": "^2.46.1", "eslint-plugin-svelte": "^3.5.1",
"globals": "^15.14.0", "globals": "^16.0.0",
"mdsvex": "^0.12.3", "mdsvex": "^0.12.3",
"prettier": "^3.4.2", "prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11", "prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.0.0", "svelte": "^5.28.1",
"svelte-check": "^4.0.0", "svelte-check": "^4.1.6",
"tailwindcss": "^4.0.0", "sveltekit-sse": "^0.13.18",
"typescript": "^5.0.0", "tailwindcss": "^4.1.4",
"typescript-eslint": "^8.20.0", "typescript": "^5.8.3",
"vite": "^6.0.0" "typescript-eslint": "^8.30.1",
"vite": "^6.3.2"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.0.8", "@tailwindcss/postcss": "^4.1.4",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@types/node": "^22.13.5", "@types/node": "^22.14.1",
"youtube-dl-exec": "^3.0.15" "winston": "^3.17.0",
"youtube-dl-exec": "^3.0.20"
} }
} }

View file

@ -1,11 +1,8 @@
#!/bin/bash #!/bin/bash
#
# An example hook script to verify what is about to be committed. ## Prepares the current environment:
# Called by "git commit" with no arguments. The hook should ## - Installing dependencies
# exit with non-zero status after issuing an appropriate message if ## - Configuring git hooks
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
set -e set -e

View file

@ -1,32 +1,23 @@
#!/usr/bin/bash #!/usr/bin/bash
# This is a script for running `install.sh`
# in a remote server using SSH.
# Configure SSH_SERVER and PROJECT_ROOT variables
# for using this script.
###### ATTENTION ######
# The install.sh script has a confirmation prompt
# because it deletes the content of the PROJECT_ROOT folder,
# just put "yes | install.sh" to automatically confirm.
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NO_DELETE=false
SSH_SERVER="example" SSH_SERVER="example"
PROJECT_ROOT=example_directory PROJECT_ROOT=example_directory
SSH_REMOTE_DIR="${SSH_SERVER}:${PROJECT_ROOT}" SSH_REMOTE_DIR="${SSH_SERVER}:${PROJECT_ROOT}"
for arg in "$@" ssh "${SSH_SERVER}" "mkdir -p ${PROJECT_ROOT}"
do rsync "${__dir}/install.sh" "${SSH_SERVER}:${PROJECT_ROOT}/install.sh"
if [ "$arg" == "--no-delete" ]; then ssh "${SSH_SERVER}" "${PROJECT_ROOT}/install.sh ${PROJECT_ROOT}"
NO_DELETE=true ssh "${SSH_SERVER}" "systemctl restart downloader"
fi
done
if [ "$NO_DELETE" = false ]; then
echo "Deleting node_modules..."
rm -rf node_modules/
else
echo "Skipping deletion of node_modules."
fi
npm ci
npm run build
rsync -r --delete --progress build/ "${SSH_REMOTE_DIR}"
rsync package.json "${SSH_REMOTE_DIR}"
rsync package-lock.json "${SSH_REMOTE_DIR}"
ssh "${SSH_SERVER}" "cd ${PROJECT_ROOT}; npm ci"
ssh "${SSH_SERVER}" "systemctl restart downloader"

33
scripts/install.sh Executable file
View file

@ -0,0 +1,33 @@
#!/usr/bin/bash
### This is a script for installing the latest release
### of `scaricatore`.
PROJECT_ROOT=$1
PROXY=$2
if [ -z ${PROJECT_ROOT+x} ]; then
echo "ERROR: No PROJECT_ROOT set (first argument to ${0})"
exit 1
fi
if [ -z ${PROXY+x} ]; then
echo "INFO: no proxy given"
fi
mkdir -p "${PROJECT_ROOT}"
cd "${PROJECT_ROOT}" || exit 1
read -p "We're about to run rm -rf ${PROJECT_ROOT}/*. Are you sure?" -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]
then
rm -rf ./*
fi
wget https://git.pweapon.org/odo/dl.emersa.it/releases/download/latest/build.tar.gz
tar -xvf build.tar.gz -C .
rm build.tar.gz
npm ci
npm run download-yt-dlp
echo "YTDLP_PATH=$(readlink -f yt-dlp)" >>.env
echo "HTTPS_PROXY=${PROXY}" >>.env

7
src/hooks.server.ts Normal file
View file

@ -0,0 +1,7 @@
import { logger } from '$lib/server/helpers';
export async function handle({ event, resolve }) {
logger.info(`Received ${event.request.method} request: ${event.url}`);
return await resolve(event);
}

View file

@ -0,0 +1,6 @@
{
"mp3": "audio/mpeg",
"opus": "audio/ogg",
"wav": "audio/wav",
"mp4": "video/mp4"
}

View file

@ -1,6 +1,28 @@
<div <div class="w-[150px]">
class="bg-opacity-50 absolute inset-0 z-10 flex items-center justify-center bg-white" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"
id="spinner" ><linearGradient id="a3"
> ><stop offset="0" stop-color="#FF156D" stop-opacity="0"></stop><stop
<div class="h-20 w-20 animate-spin rounded-full border-t-2 border-b-2 border-gray-900"></div> offset="1"
stop-color="#FF156D"
></stop></linearGradient
><circle
fill="none"
stroke="url(#a3)"
stroke-width="15"
stroke-linecap="round"
stroke-dasharray="0 44 0 44 0 44 0 44 0 360"
cx="100"
cy="100"
r="70"
transform-origin="center"
><animateTransform
type="rotate"
attributeName="transform"
calcMode="discrete"
dur="2"
values="360;324;288;252;216;180;144;108;72;36"
repeatCount="indefinite"
></animateTransform></circle
></svg
>
</div> </div>

20
src/lib/server/helpers.ts Normal file
View file

@ -0,0 +1,20 @@
import formats from '$lib/common/supportedFormats.json';
import winston from 'winston';
import { dev } from '$app/environment';
export const logger = winston.createLogger({
level: dev ? 'debug' : 'error',
format: winston.format.json(),
transports: [new winston.transports.Console()]
});
const formatMime = new Map(Object.entries(formats));
export const isURLValid = (url: string) => {
try {
new URL(url);
} catch {
return false;
}
return true;
};
export const mimeTypeMap = formatMime;

View file

@ -1,7 +1,11 @@
import { create } from 'youtube-dl-exec'; import { create } from 'youtube-dl-exec';
import { env } from '$env/dynamic/private'; import { env } from '$env/dynamic/private';
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
import supportedFormats from '$lib/common/supportedFormats.json';
import { logger, mimeTypeMap } from '$lib/server/helpers';
const YTDLP_PATH: string = env.YTDLP_PATH as string; const YTDLP_PATH: string = env.YTDLP_PATH as string;
const HTTPS_PROXY: string = env.v as string;
export const ytdl = create(YTDLP_PATH); export const ytdl = create(YTDLP_PATH);
@ -13,7 +17,8 @@ export async function getYouTubeMetadata(link: string) {
dumpSingleJson: true, dumpSingleJson: true,
noCheckCertificates: true, noCheckCertificates: true,
noWarnings: true, noWarnings: true,
preferFreeFormats: true preferFreeFormats: true,
proxy: HTTPS_PROXY ? HTTPS_PROXY : ''
}); });
} }
@ -21,25 +26,55 @@ export async function getYouTubeMetadata(link: string) {
* Streams the YouTube video/audio using youtube-dl-exec * Streams the YouTube video/audio using youtube-dl-exec
*/ */
export function streamYouTube(link: string, format: string): ReadableStream<Uint8Array> { export function streamYouTube(link: string, format: string): ReadableStream<Uint8Array> {
logger.debug(`Starting to stream: ${link}`);
const mimeType: string | undefined = mimeTypeMap.get(format);
if (!mimeType) {
throw new Error('Unsupported format');
}
logger.debug(`Given format is compatible: ${mimeType}`);
return new ReadableStream({ return new ReadableStream({
start(controller) { start(controller) {
const args = [ const args = ['--no-write-thumbnail', '-o', '-'];
'-o',
'-',
format === 'mp3' ? '--embed-metadata' : '',
'--format',
format === 'mp3' ? 'bestaudio' : 'best',
'--audio-format',
format === 'mp3' ? 'mp3' : '',
'--no-playlist'
].filter(Boolean);
const process = spawn('yt-dlp', [...args, link], { stdio: ['ignore', 'pipe', 'ignore'] }); if (HTTPS_PROXY) {
args.push('--proxy', HTTPS_PROXY);
}
process.stdout.on('data', (chunk) => controller.enqueue(chunk)); if (mimeType?.includes('audio')) {
process.stdout.on('end', () => controller.close()); args.push(
...['--extract-audio', '--embed-metadata', '--embed-thumbnail', '--audio-format', format]
);
} else if (mimeType.includes('video')) {
args.push(...['--embed-metadata', '--embed-thumbnail', '--format', format]);
}
const cmd = `${YTDLP_PATH} ${args.join(' ')} ${link}`;
logger.debug(`Running: ${cmd}`);
const process = spawn(YTDLP_PATH, [...args, link], {
cwd: '/tmp',
stdio: ['ignore', 'pipe', 'pipe']
});
process.stdout.on('data', (chunk) => {
try {
controller.enqueue(chunk);
} catch (ex) {
process.kill();
}
});
process.stderr.on('data', (chunk) => logger.debug(chunk.toString()));
process.stdout.on('end', () => {
try {
controller.close();
} catch (ex) {
logger.error(ex);
}
});
process.stdout.on('error', (err) => { process.stdout.on('error', (err) => {
console.error('Stream error:', err); logger.error('Stream error:', err);
controller.error(err); controller.error(err);
}); });
} }

View file

@ -17,4 +17,11 @@
transform: rotate(180deg); transform: rotate(180deg);
display: inline-block; display: inline-block;
} }
@media screen and (max-height: 600px) {
/* Your CSS rules here */
footer {
display: none;
}
}
</style> </style>

View file

@ -0,0 +1,3 @@
export const prerender = true;
export const ssr = true;
export const csr = true;

View file

@ -1,34 +1,88 @@
<script lang="ts"> <script lang="ts">
import { PUBLIC_VERSION } from '$env/static/public';
import supportedFormats from '$lib/common/supportedFormats.json';
import Loader from '$lib/components/Loader.svelte';
import { onMount } from 'svelte';
let source = $state('youtube'); let source = $state('youtube');
let link = $state(''); let link = $state('');
let format = $state('mp3'); let format = $state('mp3');
let showModal = $state(false); let showModal = $state(false);
let href = $state(''); let href = $state('');
// let formats = ['ogg', 'mp3', 'mp4'] let disabled = $state(true);
const formats = [{ value: 'mp3', label: 'MP3' }]; let metadata = $state(false);
let downloading = $state(false);
let logs = $state('');
let logId = undefined;
const formats = Object.keys(supportedFormats).map((f) => {
return { value: f, label: f.toUpperCase() };
});
const sources = [
{ value: 'youtube', label: 'YouTube' },
{ value: 'youtube', label: 'Any Other Website' }
];
const toggleModal = () => { const toggleModal = () => {
showModal = !showModal; showModal = !showModal;
}; };
const handleSubmit = async (e: SubmitEvent) => { onMount(() => {
e.preventDefault(); document.cookie = 'downloading=0';
});
console.log({ const readLogs = () => {
source, logId = setInterval(() => {
link, logs += "We're downloading <br>";
format }, 2000);
}); };
const onClick = () => {
let checkIterations = 0;
link = '';
downloading = true;
document.cookie = 'downloading=1';
readLogs();
const id = setInterval(() => {
if (document.cookie.includes('downloading=0') || checkIterations > 3) {
downloading = false && clearInterval(id) && clearInterval(logId);
}
checkIterations++;
}, 1000);
};
const createAnchor = () => {
if (!(source && link && format)) {
disabled = true;
return;
}
try {
new URL(link);
disabled = false;
} catch (err) {
/*
if (err.code === 'ERR_INVALID_URL') {
}
*/
disabled = true;
return;
}
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
searchParams.append('source', source); searchParams.append('source', source);
searchParams.append('link', link); searchParams.append('link', link);
searchParams.append('format', format); searchParams.append('format', format);
if (metadata) searchParams.append('metadata', '1');
href = `/download?${searchParams.toString()}`; href = `/download?${searchParams.toString()}`;
}; };
$effect(() => { $effect(() => {
createAnchor();
// Auto selected the radio button based on url regex // Auto selected the radio button based on url regex
if (link.includes('spotify')) { if (link.includes('spotify')) {
source = 'spotify'; source = 'spotify';
@ -40,12 +94,23 @@
<div <div
id="wrapper" id="wrapper"
class="relative mx-auto rounded-lg bg-black p-6 text-green-500 shadow-lg sm:max-w-sm sm:border-4 sm:border-green-500 md:mt-10 md:max-w-md lg:max-w-lg 2xl:max-w-2xl" class="relative mx-auto rounded-lg bg-black p-6 text-[#00ff7f] shadow-lg sm:mt-10 sm:max-w-sm sm:border-4 sm:border-[#00ff7f] lg:max-w-lg 2xl:max-w-2xl"
> >
<div
id="loader"
class={[
'absolute inset-0 z-10 flex items-center justify-center bg-black/20 backdrop-blur-xs',
{ downloading }
]}
>
<Loader />
{@html logs}
</div>
<!-- Info Icon --> <!-- Info Icon -->
<button <button
onclick={toggleModal} onclick={toggleModal}
class="absolute top-3 right-3 text-pink-500 transition hover:text-pink-300" class="absolute top-3 right-3 text-[#ff007f] transition hover:text-[#ff3399]"
aria-label="Open Info Modal" aria-label="Open Info Modal"
> >
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="h-6 w-6" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="h-6 w-6" viewBox="0 0 24 24">
@ -55,46 +120,42 @@
</svg> </svg>
</button> </button>
<h1 id="title" class="mb-6 text-center text-xl">🐙 Scaricatore 🐙</h1> <p id="title" class="mb-6 text-center text-[#00e5ff]">🐙 Scaricatore 🐙</p>
<form class="space-y-6" onsubmit={handleSubmit}> <form class="space-y-6">
<!-- Source Selection --> <!-- Source selection -->
<fieldset class="space-y-4"> <div class="flex justify-between">
<legend class="text-green-400">Choose Source:</legend> <div class="w-2/6 lg:w-3/6">
<label for="source" class="mb-2 block text-[#00e5ff]"> Choose Source: </label>
<label class="flex items-center space-x-3"> <select
<input type="radio" name="source" value="youtube" bind:group={source} class="retro-radio" /> id="source"
<span>YouTube</span>
</label>
<label class="flex items-center space-x-3">
<input
disabled
type="radio"
name="source" name="source"
value="spotify" bind:value={source}
bind:group={source} class="w-full rounded-lg border-4 border-[#00ff7f] bg-[#001a00] px-4 py-3 text-[#00ff7f] focus:border-[#ff3399] focus:outline-none"
class="retro-radio" >
/> {#each sources as source}
<span class="not-available">Spotify</span> <option value={source.value}>{source.label}</option>
</label> {/each}
</select>
<label class="flex items-center space-x-3"> </div>
<input type="radio" name="source" value="other" bind:group={source} class="retro-radio" />
<span>
Other (<a
href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md"
target="_blank"
rel="noopener noreferrer"
class="text-pink-500 hover:underline"
>supported sites
</a>)
</span>
</label>
</fieldset>
<!-- Format Selection -->
<div class="w-2/6">
<label for="format" class="mb-2 block text-[#00e5ff]"> Choose Format: </label>
<select
id="format"
name="format"
bind:value={format}
class="w-full rounded-lg border-4 border-[#00ff7f] bg-[#001a00] px-4 py-3 text-[#00ff7f] focus:border-[#ff3399] focus:outline-none"
>
{#each formats as format}
<option value={format.value}>{format.label}</option>
{/each}
</select>
</div>
</div>
<!-- Link Input --> <!-- Link Input -->
<div> <div>
<label for="link" class="mb-2 block text-green-400"> Enter Playlist or Video Link: </label> <label for="link" class="mb-2 block text-[#00e5ff]"> Enter Video Link: </label>
<input <input
name="link" name="link"
type="url" type="url"
@ -102,36 +163,35 @@
bind:value={link} bind:value={link}
required required
placeholder="Paste your link here" placeholder="Paste your link here"
class="w-full rounded-lg border-4 border-green-500 bg-green-200 px-4 py-3 text-black focus:border-pink-500 focus:outline-none" class="w-full rounded-lg border-4 border-[#00ff7f] bg-[#001a00] px-4 py-3 text-[#00ff7f] focus:border-[#ff3399] focus:outline-none"
/> />
</div> </div>
<!-- Format Selection --> <!-- Metadata -->
<div> <div>
<label for="format" class="mb-2 block text-green-400"> Choose Format: </label> <label for="metadata" class="mb-2 block text-[#00e5ff]"
<select >Set filename (<span class="text-red-700">SLOW</span>)</label
id="format"
name="format"
bind:value={format}
class="w-full rounded-lg border-4 border-green-500 bg-green-200 px-4 py-3 text-black focus:border-pink-500 focus:outline-none"
> >
{#each formats as format} <input
<option value={format.value}>{format.label}</option> type="checkbox"
{/each} id="metadata"
</select> name="metadata"
bind:checked={metadata}
class="rounded-lg border-4 border-[#00ff7f] bg-[#001a00] px-4 py-3 text-[#00ff7f] focus:border-[#ff3399] focus:outline-none"
/>
</div> </div>
<!-- Submit Button --> <a
<button id="btn-download"
type="submit" {href}
class="w-full rounded-lg border-4 border-pink-700 bg-pink-500 px-4 py-3 text-black transition hover:bg-pink-600 active:border-yellow-500" rel="external"
onclick={onClick}
class="{disabled
? 'disabled'
: ''} block w-full rounded-lg border-4 border-[#ff3399] bg-[#ff007f] px-4 py-3 text-center text-4xl text-black transition hover:bg-[#ff3399] active:border-yellow-500"
> >
Create download link DOWNLOAD
</button> </a>
{#if href}
<a class="download-link" {href}> Download </a>
{/if}
</form> </form>
</div> </div>
@ -139,21 +199,21 @@
{#if showModal} {#if showModal}
<div class="bg-opacity-80 fixed inset-0 z-50 flex items-center justify-center bg-black"> <div class="bg-opacity-80 fixed inset-0 z-50 flex items-center justify-center bg-black">
<div <div
class="w-4/5 max-w-lg rounded-lg border-4 border-green-500 bg-green-900 p-6 text-center text-green-100" class="w-4/5 max-w-lg rounded-lg border-4 border-[#00ff7f] bg-[#002b00] p-6 text-center text-[#00ff7f]"
> >
<h2 class="mb-4 text-lg">🐙 Scaricatore 🐙</h2> <h2 class="mb-4 text-lg">🐙 Scaricatore v{PUBLIC_VERSION} 🐙</h2>
<p> <p>
This app allows you to download Spotify playlists and YouTube videos directly. Choose your This app allows you to download Spotify playlists and YouTube videos directly. Choose your
source, paste the link, and select a format to start downloading! source, paste the link, and select a format to start downloading!
</p> </p>
<span class="mt-10 block"> <span class="mt-10 block">
<a class="underline" href="https://git.pweapon.org/odo/dl.emersa.it" <a class="text-[#00e5ff] underline" href="https://git.pweapon.org/odo/dl.emersa.it"
>Click here for the source code</a >Click here for the source code</a
> >
</span> </span>
<button <button
onclick={toggleModal} onclick={toggleModal}
class="mt-6 rounded-lg border-4 border-pink-700 bg-pink-500 px-4 py-2 text-black hover:bg-pink-600" class="mt-6 rounded-lg border-4 border-[#ff3399] bg-[#ff007f] px-4 py-2 text-black hover:bg-[#ff3399]"
> >
Close Close
</button> </button>
@ -163,35 +223,29 @@
<style> <style>
* { * {
font-size: 12px; font-family: 'Press Start 2P', cursive;
} }
.download-link { @media (max-height: 1000px) {
margin: 0 auto; * {
padding: 5px; font-size: 10px;
display: block; }
text-decoration: underline;
text-align: center;
}
.not-available {
text-decoration-line: line-through;
text-decoration-color: red;
} }
.retro-radio { @media (min-width: 1400px) {
appearance: none; * {
background-color: #000; font-size: 12px;
border: 2px solid #39ff14; }
width: 20px;
height: 20px;
cursor: pointer;
} }
.retro-radio:checked { #loader {
background-color: #39ff14; display: none;
box-shadow: }
0 0 4px #39ff14,
0 0 10px #39ff14; #loader.downloading {
display: grid;
justify-items: center;
align-items: center;
} }
input[type='url'], input[type='url'],
@ -200,6 +254,16 @@
} }
#title { #title {
font-size: 22px; font-size: 18px;
}
#btn-download {
font-size: 26px;
transition: opacity 0.3s ease;
}
a.disabled {
pointer-events: none;
cursor: default;
opacity: 0.5;
} }
</style> </style>

View file

@ -1,12 +1,14 @@
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { getYouTubeMetadata, streamYouTube } from '$lib/server/ytdlp'; import { getYouTubeMetadata, streamYouTube, ytdl } from '$lib/server/ytdlp';
import { isURLValid, logger, mimeTypeMap } from '$lib/server/helpers';
export const GET: RequestHandler = async ({ url }) => { const validateRequest = (url: URL) => {
// Get query params // Get query params
const link = url.searchParams.get('link'); const link = url.searchParams.get('link');
const format = url.searchParams.get('format'); // mp3, mp4 const format = url.searchParams.get('format'); // mp3, mp4
const source = url.searchParams.get('source'); // youtube or spotify const source = url.searchParams.get('source'); // youtube or spotify
const metadata = url.searchParams.has('metadata');
// Validate input // Validate input
if (!link || !format || !source) { if (!link || !format || !source) {
@ -17,23 +19,54 @@ export const GET: RequestHandler = async ({ url }) => {
throw error(400, 'Currently, only YouTube is supported'); throw error(400, 'Currently, only YouTube is supported');
} }
try { if (!isURLValid(link)) {
// Fetch metadata for filename throw error(400, 'URL not valid');
const metadata = await getYouTubeMetadata(link); }
const { title, uploader } = metadata;
const safeTitle = `${uploader} - ${title}`;
const filename = `${safeTitle}.${format}`;
console.log(filename); if (!mimeTypeMap.get(format)) {
throw error(400, 'format not valid');
}
logger.debug(`Request is valid`);
return {
link,
format,
source,
metadata
};
};
export const GET: RequestHandler = async ({ url }) => {
const { format, source, metadata, link } = validateRequest(url);
let filename = `noname.${format}`;
if (!!metadata) {
try {
logger.debug(`Fetching video data to set filename`);
// Fetch metadata for filename
const ytMetadata = await getYouTubeMetadata(link);
const { title, uploader } = ytMetadata;
const safeTitle = `${uploader} - ${title}`;
filename = `${safeTitle}.${format}`;
} catch (err) {
logger.error(err);
logger.error('Error fetching metadata:');
throw error(500, 'Failed to fetch video metadata');
}
}
try {
// Stream video/audio // Stream video/audio
return new Response(streamYouTube(link, format), { return new Response(streamYouTube(link, format), {
headers: { headers: {
'Content-Type': format === 'mp3' ? 'audio/mpeg' : 'video/mp4', 'Content-Type': `${mimeTypeMap.get(format)}`,
'Content-Disposition': `attachment; filename="${filename}"` 'Content-Disposition': `attachment; filename="${filename}"`,
'Set-Cookie': 'downloading=0'
} }
}); });
} catch (err) { } catch (err) {
console.error('Error fetching metadata:', err); logger.error(err);
throw error(500, 'Failed to fetch video metadata'); logger.error('Filed to stream file');
throw error(500, 'Failed to stream file');
} }
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

@ -1,16 +1,16 @@
{ {
"name": "EmersaDownloader", "name": "scaricatore",
"start_url": "https://dl.emersa.it", "start_url": "https://dl.emersa.it",
"theme_color": "rgb(34,197,94)", "theme_color": "rgb(34,197,94)",
"background": "black", "background": "black",
"orientation": "portrait", "orientation": "portrait",
"display": "fullscreen", "display": "fullscreen",
"short_name": "e-downloader", "short_name": "scaricatore",
"icons": [ "icons": [
{ {
"src": "favicon.png", "src": "favicon.png",
"type": "image/png", "type": "image/png",
"sizes": "128x128" "sizes": "512x512"
} }
] ]
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After