Merge pull request #222 from arabcoders/dev

Minor README & EventBus update
This commit is contained in:
Abdulmohsen 2025-03-23 00:55:16 +03:00 committed by GitHub
commit 28d902b2a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 41 additions and 19 deletions

View file

@ -4,31 +4,30 @@
Web GUI for [yt-dlp](https://github.com/yt-dlp/yt-dlp) with playlist & channel support. Web GUI for [yt-dlp](https://github.com/yt-dlp/yt-dlp) with playlist & channel support.
YTPTube started as a fork of [meTube](https://github.com/alexta69/metube), Since then it went under heavy changes, and it supports many new features. [![Short screenshot](https://raw.githubusercontent.com/ArabCoders/ytptube/master/sc_short.png)](https://raw.githubusercontent.com/ArabCoders/ytptube/master/sc_full.png)
# YTPTube Features. # YTPTube Features.
* Multi-downloads support. * Multi-downloads support.
* Handle live streams. * Can Handle live streams.
* Schedule channels or playlists to be downloaded automatically at a specified time. * Scheduler to queue channels or playlists to be downloaded automatically at a specified time.
* Send notification to targets based on selected events. * Send notification to targets based on selected events.
* Support per link `yt-dlp JSON config or cli options`, `cookies` & `output format`. * Support per link `yt-dlp JSON config or cli options`, `cookies` & `output format`.
* Queue multiple URLs separated by comma. * Queue multiple URLs separated by comma.
* A built in video player that can play any video file regardless of the format. **With support for sidecar external subtitles**. * A built in video player that can play any video file regardless of the format. **With support for sidecar external subtitles**.
* New `POST /api/history` endpoint that allow one or multiple links to be sent at the same time. * New `POST /api/history` endpoint that allow one or multiple links to be sent at the same time.
* New `GET /api/history/add?url=http://..` endpoint that allow to add single item via GET request. * New `GET /api/history/add?url=http://..` endpoint that allow to add single item via GET request.
* Completely redesigned the frontend UI. * Modern frontend UI.
* Switched out of binary file storage in favor of SQLite. * SQLite as database backend.
* Basic Authentication support. * Basic Authentication support.
* Support for curl_cffi, see [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#impersonation) * Support for curl_cffi, see [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#impersonation)
* Support for both advanced and basic mode for WebUI. * Support for both advanced and basic mode for WebUI.
* Bundled tools in container: curl-cffi, ffmpeg, ffprobe, aria2, rtmpdump, mkvtoolsnix, mp4box. * Bundled tools in container: curl-cffi, ffmpeg, ffprobe, aria2, rtmpdump, mkvtoolsnix, mp4box.
[![Short screenshot](https://raw.githubusercontent.com/ArabCoders/ytptube/master/sc_short.png)](https://raw.githubusercontent.com/ArabCoders/ytptube/master/sc_full.png)
# Recommended basic `ytdlp.json` file settings # Recommended basic `ytdlp.json` file settings
Your `ytdlp.json` config should include the following basic options for optimal working conditions. Your `/config/ytdlp.json` config should include the following basic options for optimal working conditions.
```json ```json
{ {
@ -291,6 +290,20 @@ Disables the `Information` button.
For API endpoints, please refer to the [API documentation](API.md). it's somewhat outdated, but it's a good starting point. For API endpoints, please refer to the [API documentation](API.md). it's somewhat outdated, but it's a good starting point.
# How to autoload yt-dlp plugins?
Loading yt-dlp plugin in YTPTube is is quite simple, we already have everything setup for you. simply, create a folder
inside the `/config` directory named `yt-dlp` so, the path will be `/config/yt-dlp`. then follow
[yt-dlp plugins docs](https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#plugins) to know how to install the plugins.
Once you have installed the plugins, restart the container and the plugins will be auto-loaded on demand.
# The origin of the project.
The project first started as a fork [meTube](https://github.com/alexta69/metube), since then it has been completely
rewritten and redesigned. The original project was a great starting point, but it didn't align with my vision for the
project and what i wanted to achieve with it.
# Social contact # Social contact
If you have short or quick questions, you are free to join my [discord server](https://discord.gg/G3GpVR8xpb) and ask If you have short or quick questions, you are free to join my [discord server](https://discord.gg/G3GpVR8xpb) and ask

View file

@ -160,7 +160,7 @@ class Event:
id: str = field(default_factory=lambda: str(uuid.uuid4()), init=False) id: str = field(default_factory=lambda: str(uuid.uuid4()), init=False)
"""The id of the event.""" """The id of the event."""
created_at: str = field(default_factory=lambda: str(datetime.datetime.now(tz=datetime.timezone.utc).isoformat())) created_at: str = field(default_factory=lambda: str(datetime.datetime.now(tz=datetime.UTC).isoformat()))
"""The time the event was created.""" """The time the event was created."""
event: str event: str
@ -209,8 +209,8 @@ class EventListener:
async def handle(self, event: Event, **kwargs): async def handle(self, event: Event, **kwargs):
if self.is_coroutine: if self.is_coroutine:
return self.call_back(event, self.name, **kwargs) return self.call_back(event, self.name, **kwargs)
else:
return asyncio.create_task(self.call_back(event, self.name, **kwargs)) return asyncio.create_task(self.call_back(event, self.name, **kwargs))
class EventBus(metaclass=Singleton): class EventBus(metaclass=Singleton):

View file

@ -2,8 +2,9 @@ import asyncio
import json import json
import logging import logging
import os import os
from collections.abc import Awaitable
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import UTC, datetime from datetime import datetime
from typing import Any from typing import Any
import httpx import httpx
@ -11,7 +12,7 @@ from aiohttp import web
from .config import Config from .config import Config
from .encoder import Encoder from .encoder import Encoder
from .Events import EventBus, Event, Events from .Events import Event, EventBus, Events
from .ItemDTO import ItemDTO from .ItemDTO import ItemDTO
from .Singleton import Singleton from .Singleton import Singleton
from .Utils import ag, validate_uuid from .Utils import ag, validate_uuid
@ -323,7 +324,7 @@ class Notification(metaclass=Singleton):
return True return True
async def send(self, ev: Event) -> list[dict]: async def send(self, ev: Event, wait: bool = True) -> list[dict] | Awaitable[list[dict]]:
if len(self._targets) < 1: if len(self._targets) < 1:
return [] return []
@ -339,7 +340,10 @@ class Notification(metaclass=Singleton):
tasks.append(self._send(target, ev)) tasks.append(self._send(target, ev))
return await asyncio.gather(*tasks) if wait:
return await asyncio.gather(*tasks)
return tasks
async def _send(self, target: Target, ev: Event) -> dict: async def _send(self, target: Target, ev: Event) -> dict:
try: try:
@ -367,6 +371,7 @@ class Notification(metaclass=Singleton):
if "form" == target.request.type.lower(): if "form" == target.request.type.lower():
reqBody["data"]["data"] = self._encoder.encode(reqBody["data"]["data"]) reqBody["data"]["data"] = self._encoder.encode(reqBody["data"]["data"])
logging.getLogger("httpx").setLevel(logging.WARNING)
response = await self._client.request(**reqBody) response = await self._client.request(**reqBody)
respData = {"url": target.request.url, "status": response.status_code, "text": response.text} respData = {"url": target.request.url, "status": response.status_code, "text": response.text}
@ -384,11 +389,8 @@ class Notification(metaclass=Singleton):
return {"url": target.request.url, "status": 500, "text": str(ev)} return {"url": target.request.url, "status": 500, "text": str(ev)}
def emit(self, e: Event, _, **kwargs): # noqa: ARG002 def emit(self, e: Event, _, **kwargs): # noqa: ARG002
if len(self._targets) < 1: if len(self._targets) < 1 or not NotificationEvents.is_valid(e.event):
return [] return asyncio.sleep(0)
if not NotificationEvents.is_valid(e.event):
return []
return self.send(e) return self.send(e)

View file

@ -345,6 +345,13 @@ const checkInfo = async () => {
} }
} }
// trim all fields in copy only if they are strings
for (const key in copy) {
if (typeof copy[key] === 'string') {
copy[key] = copy[key].trim()
}
}
emitter('submit', { reference: toRaw(props.reference), preset: toRaw(copy) }); emitter('submit', { reference: toRaw(props.reference), preset: toRaw(copy) });
} }