style: usun em-dashe z calego repo (zamiana na zwykly mylnik)
All checks were successful
deploy / deploy (push) Successful in 1m3s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 11:47:17 +02:00
parent de5832821c
commit ab2cd510a6
10 changed files with 33 additions and 33 deletions

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Sanity docker dostępny
- name: Sanity - docker dostępny
run: docker version && docker compose version
- name: Build obrazów

View File

@@ -1,4 +1,4 @@
# Wdrożenie videodownloader.naurecki.pl
# Wdrożenie - videodownloader.naurecki.pl
## Pipeline
Push do `main` uruchamia `.gitea/workflows/deploy.yml` na runnerze Gitea
@@ -6,7 +6,7 @@ Push do `main` uruchamia `.gitea/workflows/deploy.yml` na runnerze Gitea
i robi `up -d` na demonie Dockera serwera. Serwis `web` (nginx) słucha na
`127.0.0.1:8086`, `api` (NestJS) wewnętrznie + diagnostycznie na `127.0.0.1:3105`.
## Reverse proxy (Apache) jednorazowo na serwerze
## Reverse proxy (Apache) - jednorazowo na serwerze
Plik `/etc/apache2/sites-available/videodownloader.naurecki.pl.conf`:
```apache
@@ -31,5 +31,5 @@ sudo certbot --apache -d videodownloader.naurecki.pl --redirect -n \
--agree-tos -m dariusz@naurecki.pl
```
DNS: `*.naurecki.pl` (wildcard) już wskazuje serwer bez dodatkowej konfiguracji.
DNS: `*.naurecki.pl` (wildcard) już wskazuje serwer - bez dodatkowej konfiguracji.

View File

@@ -7,33 +7,33 @@ Dokument zawiera (1) gotowy **prompt** do wygenerowania aplikacji od zera oraz
## 1. Master-prompt (do odtworzenia aplikacji)
> Zbuduj webową aplikację SPA „Video & Sound Downloader Pro” webowy odpowiednik
> Zbuduj webową aplikację SPA „Video & Sound Downloader Pro” - webowy odpowiednik
> desktopowego narzędzia łączącego `yt-dlp` i `FFmpeg`. **Stack: backend NestJS
> (TypeScript), frontend React + Vite (TypeScript).** Monorepo `apps/api`, `apps/web`.
>
> **Backend (NestJS):**
> - `POST /api/probe` przyjmuje `{ url }`, uruchamia `yt-dlp -J` i zwraca tytuł,
> - `POST /api/probe` - przyjmuje `{ url }`, uruchamia `yt-dlp -J` i zwraca tytuł,
> miniaturę, czas trwania, dostępne jakości oraz listę języków napisów.
> - `POST /api/jobs` `{ url, mode, format, subtitles }`, gdzie `mode ∈
> - `POST /api/jobs` - `{ url, mode, format, subtitles }`, gdzie `mode ∈
> {mp3, mp4, playlist, facebook}`. Tworzy job w katalogu tymczasowym, uruchamia
> `yt-dlp` (+ `ffmpeg`) z argumentami zależnymi od trybu i zwraca `jobId`.
> - `GET /api/jobs/:id/events` strumień **SSE** z liniami logu i procentem postępu
> - `GET /api/jobs/:id/events` - strumień **SSE** z liniami logu i procentem postępu
> (parsowanie `--newline --progress`); zdarzenia `log`, `progress`, `done`, `error`.
> - `GET /api/jobs/:id/file` strumień gotowego pliku do przeglądarki, po wysłaniu
> - `GET /api/jobs/:id/file` - strumień gotowego pliku do przeglądarki, po wysłaniu
> sprzątnięcie katalogu joba (TTL-cleanup dla porzuconych).
> - `DELETE /api/jobs/:id` STOP: zabija proces yt-dlp/ffmpeg joba.
> - Joby w pamięci (EventEmitter) bez bazy, lekko i „portable”.
> - `DELETE /api/jobs/:id` - STOP: zabija proces yt-dlp/ffmpeg joba.
> - Joby w pamięci (EventEmitter) - bez bazy, lekko i „portable”.
> - Mapowanie trybów: `mp3` → `-x --audio-format mp3 --audio-quality <kbps>`;
> `mp4`/`facebook` → `-f "bv*[height<=H]+ba/b" --merge-output-format mp4`;
> `playlist` → `--yes-playlist`, wynik pakowany do ZIP. Napisy:
> `--write-subs --sub-langs all --embed-subs` (bez auto-napisów: `--no-write-auto-subs`).
>
> **Frontend (React) odwzoruj 1:1 zrzut ekranu desktopu:**
> **Frontend (React) - odwzoruj 1:1 zrzut ekranu desktopu:**
> ciemny motyw, akcenty niebieskie. Nagłówek z ikoną i wersją; pole **Link** z
> przyciskiem wklejenia ze schowka; sekcja **„Wybierz opcję”** z 4 kafelkami
> (MP3 / MP4 / PLAYLISTA / FACEBOOK, ikony i kolory jak na zrzucie, aktywny kafel
> podświetlony); **„Jakość / Format”** jako dropdown zależny od trybu; w miejscu
> „Zapisz do” informacja o pobraniu przez przeglądarkę; duży przycisk
> „Zapisz do” - informacja o pobraniu przez przeglądarkę; duży przycisk
> **„POBIERZ …”** + **STOP**; **banner trybu testowego**; panel **Log** na żywo
> (z SSE); stopka **Wsparcie** / **O mnie**.
>
@@ -56,7 +56,7 @@ Dokument zawiera (1) gotowy **prompt** do wygenerowania aplikacji od zera oraz
| 6 | **CI/CD** | `.gitea/workflows/deploy.yml`: push `main` → build + `up -d` (docker.sock). |
| 7 | **Reverse proxy** | vhost Apache `videodownloader.naurecki.pl``127.0.0.1:8086`. |
| 8 | **SSL** | `certbot --apache -d videodownloader.naurecki.pl`. |
| 9 | **DNS** | Gotowe `*.naurecki.pl` (wildcard) już wskazuje `212.109.167.206`. |
| 9 | **DNS** | Gotowe - `*.naurecki.pl` (wildcard) już wskazuje `212.109.167.206`. |
| 10 | **Weryfikacja** | `https://videodownloader.naurecki.pl` + test pobrania. |
**Ustalenia infrastrukturalne** (zweryfikowane na serwerze):

View File

@@ -1,8 +1,8 @@
# Video & Sound Downloader Pro wersja web (SPA)
# Video & Sound Downloader Pro - wersja web (SPA)
Webowy odpowiednik desktopowego narzędzia **Video & Sound Downloader Pro v6.5.6**.
Łączy `yt-dlp` + `FFmpeg` w jednym panelu: pobieranie audio (MP3), wideo (MP4),
całych playlist oraz materiałów z Facebooka bez wpisywania komend i bez ręcznej
całych playlist oraz materiałów z Facebooka - bez wpisywania komend i bez ręcznej
konwersji. Backend: **NestJS**, frontend: **React (Vite)**. Obraz portowany jest do
Dockera i wdrażany na `https://videodownloader.naurecki.pl`.
@@ -17,10 +17,10 @@ Dockera i wdrażany na `https://videodownloader.naurecki.pl`.
```
videodownloader/
├── apps/
│ ├── api/ # NestJS joby pobierania, yt-dlp+ffmpeg, postęp SSE
│ ├── api/ # NestJS - joby pobierania, yt-dlp+ffmpeg, postęp SSE
│ │ ├── src/
│ │ └── Dockerfile # node + python3 + yt-dlp + ffmpeg
│ └── web/ # React + Vite (TS) UI odwzorowany ze zrzutu
│ └── web/ # React + Vite (TS) - UI odwzorowany ze zrzutu
│ ├── src/
│ ├── nginx.conf # serwuje statyki + proxy /api (+ SSE bez buforowania)
│ └── Dockerfile
@@ -36,15 +36,15 @@ Ruch publiczny: `Apache (vhost videodownloader.naurecki.pl, SSL)` → `127.0.0.1
- **Pole Link** z przyciskiem „wklej ze schowka”.
- **4 tryby**: `MP3` (pobierz dźwięk), `MP4` (pobierz wideo), `PLAYLISTA`
(pobierz całą listę → ZIP), `FACEBOOK` (pobierz wideo).
- **Jakość / Format** lista zależna od trybu (MP3 320/256/192/128 kbps;
- **Jakość / Format** - lista zależna od trybu (MP3 320/256/192/128 kbps;
MP4 2160/1440/1080/720/480p).
- **Napisy** opcja pobrania dostępnych napisów (`--write-subs --embed-subs`);
- **Napisy** - opcja pobrania dostępnych napisów (`--write-subs --embed-subs`);
zgodnie z oryginałem **nie** dotyczy automatycznie generowanych napisów YouTube.
- Duży przycisk **POBIERZ** + **STOP** (przerwanie joba).
- **Log na żywo** (SSE) komunikaty postępu, konwersji i scalania audio/wideo.
- **Log na żywo** (SSE) - komunikaty postępu, konwersji i scalania audio/wideo.
- Przyciski **Wsparcie** / **O mnie** oraz informacja o wersji **yt-dlp** z
przyciskiem „Sprawdź aktualizacje”.
- **Auto-aktualizacja yt-dlp** API sprawdza i aktualizuje yt-dlp (`yt-dlp -U`)
- **Auto-aktualizacja yt-dlp** - API sprawdza i aktualizuje yt-dlp (`yt-dlp -U`)
przy starcie oraz cyklicznie (domyślnie co 24 h; `YTDLP_AUTO_UPDATE`,
`YTDLP_UPDATE_INTERVAL_H`). Ręczne wymuszenie: `POST /api/ytdlp/update`.
@@ -67,4 +67,4 @@ Wymaga lokalnie `yt-dlp` i `ffmpeg` w PATH (w produkcji są w obrazie).
Push do `main` → runner Gitea (`.gitea/workflows/deploy.yml`) buduje obrazy z
`docker-compose.prod.yml` i robi `up -d` na demonie Dockera serwera. Konfiguracja
reverse proxy + SSL patrz [DEPLOY.md](DEPLOY.md).
reverse proxy + SSL - patrz [DEPLOY.md](DEPLOY.md).

View File

@@ -1,7 +1,7 @@
{
"name": "vsd-api",
"version": "6.5.6",
"description": "Video & Sound Downloader Pro API (NestJS)",
"description": "Video & Sound Downloader Pro - API (NestJS)",
"license": "UNLICENSED",
"scripts": {
"build": "nest build",

View File

@@ -30,7 +30,7 @@ export class JobsController {
@Sse(':id/events')
events(@Param('id') id: string): Observable<MessageEvent> {
// Bez pola `type` inaczej Nest emituje nazwane zdarzenia (event: <kind>),
// Bez pola `type` - inaczej Nest emituje nazwane zdarzenia (event: <kind>),
// a przeglądarkowy EventSource.onmessage reaguje tylko na domyślne „message”.
return this.jobs.events(id).pipe(
map((ev) => ({ data: JSON.stringify(ev) }) as MessageEvent),

View File

@@ -129,7 +129,7 @@ export class JobsService {
if (entries.length === 1) {
const p = path.join(job.workDir, entries[0]);
const st = await fs.stat(p);
this.emit(job, { kind: 'log', line: `OK: gotowy plik ${entries[0]} (${fmtSize(st.size)})` });
this.emit(job, { kind: 'log', line: `OK: gotowy plik - ${entries[0]} (${fmtSize(st.size)})` });
return { path: p, name: entries[0], size: st.size };
}
@@ -147,7 +147,7 @@ export class JobsService {
void archive.finalize();
});
const st = await fs.stat(zipPath);
this.emit(job, { kind: 'log', line: `OK: gotowe archiwum ${zipName} (${fmtSize(st.size)})` });
this.emit(job, { kind: 'log', line: `OK: gotowe archiwum - ${zipName} (${fmtSize(st.size)})` });
return { path: zipPath, name: zipName, size: st.size };
}

View File

@@ -4,7 +4,7 @@ server {
root /usr/share/nginx/html;
index index.html;
# API + SSE (strumień logów) bez buforowania, długie timeouty na pobieranie
# API + SSE (strumień logów) - bez buforowania, długie timeouty na pobieranie
location /api/ {
proxy_pass http://api:3000;
proxy_http_version 1.1;

View File

@@ -151,7 +151,7 @@ export default function App() {
}
setBusy(true);
setProgress(0);
setLog([`Start: ${def.title} ${url.trim()}`]);
setLog([`Start: ${def.title} - ${url.trim()}`]);
try {
const res = await fetch('/api/jobs', {
method: 'POST',
@@ -303,7 +303,7 @@ export default function App() {
</div>
</div>
<p className="hint">
Napisy dotyczą tylko faktycznie dostępnych napisów nie obejmują
Napisy dotyczą tylko faktycznie dostępnych napisów - nie obejmują
automatycznie generowanych napisów YouTube. Gotowy plik pobiera się przez
przeglądarkę (folder Pobrane).
</p>
@@ -358,7 +358,7 @@ export default function App() {
<button
className="ghost"
onClick={() =>
alert('Video & Sound Downloader Pro webowa wersja oparta o yt-dlp + FFmpeg.')
alert('Video & Sound Downloader Pro - webowa wersja oparta o yt-dlp + FFmpeg.')
}
>
O mnie

View File

@@ -16,9 +16,9 @@ services:
YTDLP_AUTO_UPDATE: ${YTDLP_AUTO_UPDATE:-1}
YTDLP_UPDATE_INTERVAL_H: ${YTDLP_UPDATE_INTERVAL_H:-24}
tmpfs:
# katalog roboczy pobierań w pamięci/efemeryczny sprzątany po jobie
# katalog roboczy pobierań w pamięci/efemeryczny - sprzątany po jobie
- /app/work
# tylko loopback hosta diagnostyka; publiczny ruch idzie przez web (nginx)
# tylko loopback hosta - diagnostyka; publiczny ruch idzie przez web (nginx)
ports:
- "127.0.0.1:3105:3000"