added external js runtime, metadata scraping refactor

This commit is contained in:
marcopiovanello
2026-06-29 12:18:06 +02:00
parent b47e6fc734
commit d4275f219d
20 changed files with 626 additions and 721 deletions
+5 -1
View File
@@ -9,7 +9,11 @@
"type": "go",
"request": "launch",
"mode": "debug",
"program": "main.go"
"program": "main.go",
"args": [
"--conf",
"./config.yml"
]
},
{
"type": "chrome",
+6 -7
View File
@@ -1,4 +1,3 @@
# Node (pnpm) ------------------------------------------------------------------
FROM node:24-slim AS ui
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
@@ -11,9 +10,9 @@ RUN rm -rf node_modules
RUN pnpm install
RUN pnpm build
# -----------------------------------------------------------------------------
# Go --------------------------------------------------------------------------
FROM golang AS build
WORKDIR /usr/src/yt-dlp-webui
@@ -22,14 +21,14 @@ COPY . .
COPY --from=ui /usr/src/yt-dlp-webui/frontend /usr/src/yt-dlp-webui/frontend
RUN CGO_ENABLED=0 GOOS=linux go build -o yt-dlp-webui
# -----------------------------------------------------------------------------
# Runtime ---------------------------------------------------------------------
FROM python:3-alpine3.22
RUN apk update && \
apk add ffmpeg ca-certificates curl wget gnutls deno --no-cache && \
pip install "yt-dlp[default,curl-cffi,mutagen,pycryptodomex,phantomjs,secretstorage]"
apk add ffmpeg ca-certificates curl wget gnutls deno --no-cache && \
pip install "yt-dlp[default,curl-cffi,mutagen,pycryptodomex,phantomjs,secretstorage]"
VOLUME /downloads /config
+4 -4
View File
@@ -27,13 +27,13 @@
},
"devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.1.0",
"@types/node": "^20.14.2",
"@types/node": "^24.0.0",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react-swc": "^3.7.2",
"typescript": "^5.7.2",
"vite": "^6.0.3"
"@vitejs/plugin-react": "^6.0.3",
"typescript": "^6.0.3",
"vite": "^8.1.0"
}
}
+443 -613
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
allowBuilds:
'@swc/core': true
esbuild: true
-37
View File
@@ -1,37 +0,0 @@
// import { PaginatedResponse } from '../types'
export type Subscription = {
id: string
url: string
params: string
cron_expression: string
}
// class SubscriptionService {
// private _baseURL: string = ''
// public set baseURL(v: string) {
// this._baseURL = v
// }
// public async delete(id: string): Promise<void> {
// }
// public async listPaginated(start: number, limit: number = 50): Promise<PaginatedResponse<Subscription[]>> {
// const res = await fetch(`${this._baseURL}/subscriptions?id=${start}&limit=${limit}`)
// const data: PaginatedResponse<Subscription[]> = await res.json()
// return data
// }
// public async submit(sub: Subscription): Promise<void> {
// }
// public async edit(sub: Subscription): Promise<void> {
// }
// }
// export default SubscriptionService
+2 -2
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"target": "ES2018",
"target": "es2020",
"lib": [
"dom",
"dom.iterable",
@@ -15,7 +15,7 @@
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
+1 -1
View File
@@ -1,4 +1,4 @@
import react from '@vitejs/plugin-react-swc'
import react from '@vitejs/plugin-react'
import ViteYaml from '@modyfi/vite-plugin-yaml'
import { defineConfig } from 'vite'
+1 -1
View File
@@ -64,7 +64,7 @@ func main() {
cfg.Server.QueueSize = 2
}
// 6. Frontend FS
// Frontend FS
var appFS fs.FS
if fp := v.GetString("frontend_path"); fp != "" {
appFS = os.DirFS(fp)
+31 -30
View File
@@ -7,58 +7,59 @@ import (
)
type Config struct {
Server ServerConfig `yaml:"server"`
Logging LoggingConfig `yaml:"logging"`
Paths PathsConfig `yaml:"paths"`
Authentication AuthConfig `yaml:"authentication"`
OpenId OpenIdConfig `yaml:"openid"`
Frontend FrontendConfig `yaml:"frontend"`
AutoArchive bool `yaml:"auto_archive"`
Twitch TwitchConfig `yaml:"twitch"`
Server ServerConfig `mapstructure:"server"`
Logging LoggingConfig `mapstructure:"logging"`
Paths PathsConfig `mapstructure:"paths"`
Authentication AuthConfig `mapstructure:"authentication"`
OpenId OpenIdConfig `mapstructure:"openid"`
Frontend FrontendConfig `mapstructure:"frontend"`
AutoArchive bool `mapstructure:"auto_archive"`
Twitch TwitchConfig `mapstructure:"twitch"`
path string
}
type ServerConfig struct {
BaseURL string `yaml:"base_url"`
Host string `yaml:"host"`
Port int `yaml:"port"`
QueueSize int `yaml:"queue_size"`
BaseURL string `mapstructure:"base_url"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
QueueSize int `mapstructure:"queue_size"`
}
type LoggingConfig struct {
LogPath string `yaml:"log_path"`
EnableFileLogging bool `yaml:"enable_file_logging"`
LogPath string `mapstructure:"log_path"`
EnableFileLogging bool `mapstructure:"enable_file_logging"`
}
type PathsConfig struct {
DownloadPath string `yaml:"download_path"`
DownloaderPath string `yaml:"downloader_path"`
LocalDatabasePath string `yaml:"local_database_path"`
DownloadPath string `mapstructure:"download_path"`
DownloaderPath string `mapstructure:"downloader_path"`
LocalDatabasePath string `mapstructure:"local_database_path"`
JSRuntimePath string `mapstructure:"js_runtime_path"`
}
type AuthConfig struct {
RequireAuth bool `yaml:"require_auth"`
Username string `yaml:"username"`
PasswordHash string `yaml:"password"`
RequireAuth bool `mapstructure:"require_auth"`
Username string `mapstructure:"username"`
PasswordHash string `mapstructure:"password_hash"`
}
type OpenIdConfig struct {
UseOpenId bool `yaml:"use_openid"`
ProviderURL string `yaml:"openid_provider_url"`
ClientId string `yaml:"openid_client_id"`
ClientSecret string `yaml:"openid_client_secret"`
RedirectURL string `yaml:"openid_redirect_url"`
EmailWhitelist []string `yaml:"openid_email_whitelist"`
UseOpenId bool `mapstructure:"use_openid"`
ProviderURL string `mapstructure:"provider_url"`
ClientId string `mapstructure:"client_id"`
ClientSecret string `mapstructure:"client_secret"`
RedirectURL string `mapstructure:"redirect_url"`
EmailWhitelist []string `mapstructure:"email_whitelist"`
}
type FrontendConfig struct {
FrontendPath string `yaml:"frontend_path"`
FrontendPath string `mapstructure:"frontend_path"`
}
type TwitchConfig struct {
ClientId string `yaml:"client_id"`
ClientSecret string `yaml:"client_secret"`
CheckInterval time.Duration `yaml:"check_interval"`
ClientId string `mapstructure:"client_id"`
ClientSecret string `mapstructure:"client_secret"`
CheckInterval time.Duration `mapstructure:"check_interval"`
}
var (
+1 -1
View File
@@ -8,7 +8,7 @@ import (
type Downloader interface {
Start() error
Stop() error
Status() *internal.ProcessSnapshot
Status() internal.ProcessSnapshot
SetOutput(output internal.DownloadOutput)
SetProgress(progress internal.DownloadProgress)
+9 -2
View File
@@ -54,6 +54,9 @@ func NewGenericDownload(url string, params []string) Downloader {
// in base
g.Id = uuid.NewString()
g.URL = url
g.Params = params
g.Completed = false
return g
}
@@ -89,6 +92,10 @@ func (g *GenericDownloader) Start() error {
"--progress-template",
templateReplacer.Replace(postprocessTemplate),
"--no-exec",
"--js-runtimes",
config.Instance().Paths.JSRuntimePath,
"--remote-components",
"ejs:github",
}
// if user asked to manually override the output path...
@@ -164,8 +171,8 @@ func (g *GenericDownloader) Stop() error {
return nil
}
func (g *GenericDownloader) Status() *internal.ProcessSnapshot {
return &internal.ProcessSnapshot{
func (g *GenericDownloader) Status() internal.ProcessSnapshot {
return internal.ProcessSnapshot{
Id: g.Id,
Info: g.Metadata,
Progress: g.progress,
+4 -5
View File
@@ -41,6 +41,7 @@ func NewLiveStreamDownloader(url string, pipes []pipes.Pipe) Downloader {
// in base
l.Id = uuid.NewString()
l.URL = url
l.pipes = pipes
return l
}
@@ -87,7 +88,7 @@ func (l *LiveStreamDownloader) Start() error {
cancel()
}()
// --- costruisci pipeline ---
// build pipeline
reader := io.Reader(media)
for _, pipe := range l.pipes {
nr, err := pipe.Connect(reader)
@@ -98,7 +99,6 @@ func (l *LiveStreamDownloader) Start() error {
reader = nr
}
// --- fallback: se nessun FileWriter, scrivi su file ---
if !l.hasFileWriter() {
go func() {
filepath.Join(
@@ -122,7 +122,6 @@ func (l *LiveStreamDownloader) Start() error {
}()
}
// --- logs consumer ---
logs := make(chan []byte)
go produceLogs(stderr, logs)
go consumeLogs(ctx, logs, l.logConsumer, l)
@@ -156,8 +155,8 @@ func (l *LiveStreamDownloader) Stop() error {
return nil
}
func (l *LiveStreamDownloader) Status() *internal.ProcessSnapshot {
return &internal.ProcessSnapshot{
func (l *LiveStreamDownloader) Status() internal.ProcessSnapshot {
return internal.ProcessSnapshot{
Id: l.Id,
Info: l.Metadata,
Progress: l.progress,
+1 -1
View File
@@ -100,7 +100,7 @@ func (m *Store) All() *[]internal.ProcessSnapshot {
m.mu.RLock()
for _, v := range m.table {
running = append(running, *(v.Status()))
running = append(running, v.Status())
}
m.mu.RUnlock()
+10 -10
View File
@@ -50,7 +50,7 @@ func (m *MessageQueue) Publish(d downloaders.Downloader) {
// Workers: download + metadata
func (m *MessageQueue) SetupConsumers() {
// N parallel workers for downloadQueue
for i := 0; i < m.concurrency; i++ {
for i := 1; i < m.concurrency+1; i++ {
go m.downloadWorker(i)
}
@@ -60,6 +60,8 @@ func (m *MessageQueue) SetupConsumers() {
// Worker dei download
func (m *MessageQueue) downloadWorker(workerId int) {
slog.Info("download worker spawned", slog.Int("worker", workerId))
for {
select {
case <-m.ctx.Done():
@@ -72,25 +74,22 @@ func (m *MessageQueue) downloadWorker(workerId int) {
continue
}
slog.Info("download worker started",
slog.Info("download worker starting download",
slog.Int("worker", workerId),
slog.String("id", p.GetId()),
)
p.Start()
m.metadataQueue <- p
slog.Info("queued for metadata", slog.String("id", p.GetId()))
// after the download starts succesfully we pass it to the metadata queue
select {
case m.metadataQueue <- p:
slog.Info("queued for metadata", slog.String("id", p.GetId()))
case <-m.ctx.Done():
return
}
p.Start()
}
}
}
func (m *MessageQueue) metadataWorker() {
slog.Info("metadata worker spawned", slog.Int("worker", 1))
for {
select {
case <-m.ctx.Done():
@@ -111,6 +110,7 @@ func (m *MessageQueue) metadataWorker() {
continue
}
slog.Info("metadata worker started", slog.String("id", p.GetId()))
p.SetMetadata(metadata.DefaultFetcher)
}
}
+2 -2
View File
@@ -10,11 +10,11 @@ import (
/*
Applicable modifiers
full | short | description
full | short | description
---------------------------------------------------------------------------------
--playlist-start NUMBER | -I NUMBER: | discard first N entries
--playlist-end NUMBER | -I :NUMBER | discard last N entries
--playlist-reverse | -I ::-1 | self explanatory
--playlist-reverse | -I ::-1 | self explanatory
--max-downloads NUMBER | | stops after N completed downloads
*/
+1 -1
View File
@@ -172,7 +172,7 @@ func (s *Service) DeleteTemplate(ctx context.Context, id string) error {
func (s *Service) GetVersion(ctx context.Context) (string, string, error) {
//TODO: load from realease properties file, or anything else outside code
const CURRENT_RPC_VERSION = "3.2.6"
const CURRENT_RPC_VERSION = "4.0.0"
result := make(chan string, 1)
+2 -1
View File
@@ -139,12 +139,13 @@ func (s *Service) Kill(args string, killed *string) error {
return errors.New("nil process")
}
s.db.Delete(download.GetId())
if err := download.Stop(); err != nil {
slog.Info("failed killing process", slog.String("id", download.GetId()), slog.Any("err", err))
return err
}
s.db.Delete(download.GetId())
slog.Info("succesfully killed process", slog.String("id", download.GetId()))
return nil
-2
View File
@@ -69,7 +69,6 @@ func Run(ctx context.Context, rc *RunConfig) error {
return err
}
// ---- LOGGING ---------------------------------------------------
logWriters := []io.Writer{
os.Stdout,
observableLogger, // for web-ui
@@ -102,7 +101,6 @@ func Run(ctx context.Context, rc *RunConfig) error {
// make the new logger the default one with all the new writers
slog.SetDefault(logger)
// ----------------------------------------------------------------
mq, err := queue.NewMessageQueue()
if err != nil {
+100
View File
@@ -0,0 +1,100 @@
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="download worker spawned" worker=3
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="metadata worker spawned" worker=1
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="download worker spawned" worker=1
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="download worker spawned" worker=4
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="download worker spawned" worker=2
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="published download" id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="published download" id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="download worker starting download" worker=1 id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="queued for metadata" id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="download worker starting download" worker=3 id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="queued for metadata" id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="requesting download" url="https://www.youtube.com/watch?v=-fZM9wOvbh0" params="[https://www.youtube.com/watch?v=-fZM9wOvbh0 --newline --no-colors --no-playlist --progress-template download:{\"eta\":%(progress.eta)s,\"percentage\":\"%(progress._percent_str)s\",\"speed\":%(progress.speed)s} --progress-template postprocess:{\"filepath\":\"%(info.filepath)s\"} --no-exec --js-runtimes deno:/home/marco/.deno/bin/deno --remote-components ejs:github -o ./downloads/%(title)s.%(ext)s -o ./downloads/%(title)s.%(ext)s]"
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="metadata worker started" id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="metadata worker started" id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="requesting download" url="https://www.youtube.com/watch?v=-fZM9wOvbh0" params="[https://www.youtube.com/watch?v=-fZM9wOvbh0 --newline --no-colors --no-playlist --progress-template download:{\"eta\":%(progress.eta)s,\"percentage\":\"%(progress._percent_str)s\",\"speed\":%(progress.speed)s} --progress-template postprocess:{\"filepath\":\"%(info.filepath)s\"} --no-exec --js-runtimes deno:/home/marco/.deno/bin/deno --remote-components ejs:github -r 50K -o ./downloads/%(title)s.%(ext)s -o ./downloads/%(title)s.%(ext)s -o ./downloads/%(title)s.%(ext)s]"
time=2026-06-29T12:10:42.936+02:00 level=INFO msg="retrieving metadata" url="https://www.youtube.com/watch?v=-fZM9wOvbh0"
time=2026-06-29T12:10:42.945+02:00 level=INFO msg="yt-dlp-webui started" address=0.0.0.0:3033
time=2026-06-29T12:10:43.031+02:00 level=ERROR msg="yt-dlp process error" id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" err="WARNING: Your yt-dlp version (2026.03.17) is older than 90 days!"
time=2026-06-29T12:10:43.031+02:00 level=ERROR msg="yt-dlp process error" id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" err=" It is strongly recommended to always use the latest version."
time=2026-06-29T12:10:43.031+02:00 level=ERROR msg="yt-dlp process error" id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" err=" As yt-dlp has been installed via apt, you should use that to update. If you're on a stable release, also check backports.."
time=2026-06-29T12:10:43.031+02:00 level=ERROR msg="yt-dlp process error" id=cd696a78-21a9-41d4-9a3f-47b75ed0cb38 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" err=" To suppress this warning, add --no-update to your command/config."
time=2026-06-29T12:10:43.033+02:00 level=ERROR msg="yt-dlp process error" id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" err="WARNING: Your yt-dlp version (2026.03.17) is older than 90 days!"
time=2026-06-29T12:10:43.033+02:00 level=ERROR msg="yt-dlp process error" id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" err=" It is strongly recommended to always use the latest version."
time=2026-06-29T12:10:43.033+02:00 level=ERROR msg="yt-dlp process error" id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" err=" As yt-dlp has been installed via apt, you should use that to update. If you're on a stable release, also check backports.."
time=2026-06-29T12:10:43.033+02:00 level=ERROR msg="yt-dlp process error" id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" err=" To suppress this warning, add --no-update to your command/config."
time=2026-06-29T12:10:44.859+02:00 level=INFO msg="metadata worker started" id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2
time=2026-06-29T12:10:44.859+02:00 level=INFO msg="metadata worker started" id=98c62f5e-0fda-4dc3-b59f-f6e8e4d9c7a2
time=2026-06-29T12:10:44.860+02:00 level=INFO msg="retrieving metadata" url="https://www.youtube.com/watch?v=-fZM9wOvbh0"
time=2026-06-29T12:10:45.115+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.115+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.135+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.153+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.172+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.196+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.229+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.234+02:00 level=INFO msg=progress id=98c62f5e url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.263+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.6%"
time=2026-06-29T12:10:45.274+02:00 level=INFO msg=progress id=98c62f5e url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.305+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.6%"
time=2026-06-29T12:10:45.348+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.7%"
time=2026-06-29T12:10:45.354+02:00 level=INFO msg=progress id=98c62f5e url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.394+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.407+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.428+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.428+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.428+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.429+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.429+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.429+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.429+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.0%"
time=2026-06-29T12:10:45.430+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.1%"
time=2026-06-29T12:10:45.433+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.1%"
time=2026-06-29T12:10:45.437+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.1%"
time=2026-06-29T12:10:45.446+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.2%"
time=2026-06-29T12:10:45.475+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.3%"
time=2026-06-29T12:10:45.504+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.5%"
time=2026-06-29T12:10:45.514+02:00 level=INFO msg=progress id=98c62f5e url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.525+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.6%"
time=2026-06-29T12:10:45.549+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.6%"
time=2026-06-29T12:10:45.551+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.6%"
time=2026-06-29T12:10:45.556+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.6%"
time=2026-06-29T12:10:45.565+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.7%"
time=2026-06-29T12:10:45.583+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 95.8%"
time=2026-06-29T12:10:45.619+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.0%"
time=2026-06-29T12:10:45.633+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.1%"
time=2026-06-29T12:10:45.687+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.1%"
time=2026-06-29T12:10:45.687+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.1%"
time=2026-06-29T12:10:45.689+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.1%"
time=2026-06-29T12:10:45.691+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.1%"
time=2026-06-29T12:10:45.696+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.2%"
time=2026-06-29T12:10:45.705+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.2%"
time=2026-06-29T12:10:45.723+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.3%"
time=2026-06-29T12:10:45.759+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.6%"
time=2026-06-29T12:10:45.772+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.6%"
time=2026-06-29T12:10:45.829+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.7%"
time=2026-06-29T12:10:45.831+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.7%"
time=2026-06-29T12:10:45.834+02:00 level=INFO msg=progress id=98c62f5e url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 94.5%"
time=2026-06-29T12:10:45.835+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.7%"
time=2026-06-29T12:10:45.844+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.8%"
time=2026-06-29T12:10:45.864+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 96.9%"
time=2026-06-29T12:10:45.899+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.1%"
time=2026-06-29T12:10:45.915+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.969+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.969+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.969+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.969+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.970+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.970+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.970+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.971+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.974+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.978+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.2%"
time=2026-06-29T12:10:45.987+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.3%"
time=2026-06-29T12:10:46.005+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.4%"
time=2026-06-29T12:10:46.041+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.6%"
time=2026-06-29T12:10:46.055+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.7%"
time=2026-06-29T12:10:46.109+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.7%"
time=2026-06-29T12:10:46.111+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.7%"
time=2026-06-29T12:10:46.114+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.7%"
time=2026-06-29T12:10:46.119+02:00 level=INFO msg=progress id=cd696a78 url="https://www.youtube.com/watch?v=-fZM9wOvbh0" percentage=" 97.8%"