>"),
)
- @route("POST", "api/workers")
+ @route("POST", "api/workers", "pool_start")
async def pool_restart(self, _) -> Response:
"""
Restart the workers pool.
@@ -1264,7 +1315,7 @@ class HttpAPI(Common):
return web.json_response({"message": "Workers pool being restarted."}, status=web.HTTPOk.status_code)
- @route("PATCH", "api/workers/{id}")
+ @route("PATCH", "api/workers/{id}", "worker_restart")
async def worker_restart(self, request: Request) -> Response:
"""
Restart a worker.
@@ -1287,7 +1338,7 @@ class HttpAPI(Common):
return web.json_response({"status": "restarted" if status else "in_error_state"}, status=web.HTTPOk.status_code)
- @route("DELETE", "api/workers/{id}")
+ @route("DELETE", "api/workers/{id}", "worker_stop")
async def worker_stop(self, request: Request) -> Response:
"""
Stop a worker.
@@ -1310,7 +1361,7 @@ class HttpAPI(Common):
return web.json_response({"status": "stopped" if status else "in_error_state"}, status=web.HTTPOk.status_code)
- @route("GET", "api/player/playlist/{file:.*}.m3u8")
+ @route("GET", "api/player/playlist/{file:.*}.m3u8", "playlist")
async def playlist(self, request: Request) -> Response:
"""
Get the playlist.
@@ -1327,13 +1378,19 @@ class HttpAPI(Common):
if not file:
return web.json_response(data={"error": "file is required"}, status=web.HTTPBadRequest.status_code)
+ base_path: str = self.config.base_path.rstrip("/")
+
try:
realFile, status = get_file(download_path=self.config.download_path, file=file)
if web.HTTPFound.status_code == status:
return Response(
status=web.HTTPFound.status_code,
headers={
- "Location": f"/api/player/playlist/{quote(str(realFile).replace(self.config.download_path, '').strip('/'))}.m3u8"
+ "Location": str(
+ self.app.router["playlist"].url_for(
+ file=str(realFile).replace(self.config.download_path, "").strip("/")
+ )
+ ),
},
)
@@ -1341,7 +1398,7 @@ class HttpAPI(Common):
return web.json_response(data={"error": f"File '{file}' does not exist."}, status=status)
return web.Response(
- text=await Playlist(download_path=self.config.download_path, url="/").make(file=realFile),
+ text=await Playlist(download_path=self.config.download_path, url=f"{base_path}/").make(file=realFile),
headers={
"Content-Type": "application/x-mpegURL",
"Cache-Control": "no-cache",
@@ -1352,7 +1409,7 @@ class HttpAPI(Common):
except StreamingError as e:
return web.json_response(data={"error": str(e)}, status=web.HTTPNotFound.status_code)
- @route("GET", "api/player/m3u8/{mode}/{file:.*}.m3u8")
+ @route("GET", "api/player/m3u8/{mode}/{file:.*}.m3u8", "m3u8")
async def m3u8(self, request: Request) -> Response:
"""
Get the m3u8 file.
@@ -1383,15 +1440,22 @@ class HttpAPI(Common):
duration = float(duration)
+ base_path: str = self.config.base_path.rstrip("/")
+
try:
- cls = M3u8(download_path=self.config.download_path, url="/")
+ cls = M3u8(download_path=self.config.download_path, url=f"{base_path}/")
realFile, status = get_file(download_path=self.config.download_path, file=file)
if web.HTTPFound.status_code == status:
return Response(
status=status,
headers={
- "Location": f"/api/player/m3u8/{mode}/{quote(str(realFile).replace(self.config.download_path, '').strip('/'))}.m3u8"
+ "Location": str(
+ self.app.router["m3u8"].url_for(
+ mode=mode,
+ file=str(realFile).replace(self.config.download_path, "").strip("/"),
+ )
+ ),
},
)
@@ -1416,7 +1480,7 @@ class HttpAPI(Common):
status=web.HTTPOk.status_code,
)
- @route("GET", r"api/player/segments/{segment:\d+}/{file:.*}.ts")
+ @route("GET", r"api/player/segments/{segment:\d+}/{file:.*}.ts", "segments")
async def segments(self, request: Request) -> Response:
"""
Get the segments.
@@ -1445,7 +1509,12 @@ class HttpAPI(Common):
return Response(
status=status,
headers={
- "Location": f"/api/player/segments/{segment}/{quote(str(realFile).replace(self.config.download_path, '').strip('/'))}.ts"
+ "Location": str(
+ self.app.router["segments"].url_for(
+ segment=segment,
+ file=str(realFile).replace(self.config.download_path, "").strip("/"),
+ )
+ ),
},
)
@@ -1487,7 +1556,7 @@ class HttpAPI(Common):
return resp
- @route("GET", "api/player/subtitle/{file:.*}.vtt")
+ @route("GET", "api/player/subtitle/{file:.*}.vtt", "subtitles")
async def subtitles(self, request: Request) -> Response:
"""
Get the subtitles.
@@ -1509,7 +1578,11 @@ class HttpAPI(Common):
return Response(
status=status,
headers={
- "Location": f"/api/player/subtitle/{quote(str(realFile).replace(self.config.download_path, '').strip('/'))}.vtt"
+ "Location": str(
+ self.app.router["subtitles"].url_for(
+ file=str(realFile).replace(self.config.download_path, "").strip("/")
+ )
+ ),
},
)
@@ -1540,7 +1613,7 @@ class HttpAPI(Common):
status=web.HTTPOk.status_code,
)
- @route("GET", "/")
+ @route("GET", "/", "index")
async def index(self, _: Request) -> Response:
"""
Get the index file.
@@ -1566,7 +1639,7 @@ class HttpAPI(Common):
status=web.HTTPOk.status_code,
)
- @route("GET", "api/thumbnail")
+ @route("GET", "api/thumbnail", "get_thumbnail")
async def get_thumbnail(self, request: Request) -> Response:
"""
Get the thumbnail.
@@ -1620,7 +1693,7 @@ class HttpAPI(Common):
data={"error": "failed to retrieve the thumbnail."}, status=web.HTTPInternalServerError.status_code
)
- @route("GET", "api/random/background")
+ @route("GET", "api/random/background", "get_background")
async def get_background(self, request: Request) -> Response:
"""
Get random background.
@@ -1694,7 +1767,7 @@ class HttpAPI(Common):
status=web.HTTPInternalServerError.status_code,
)
- @route("GET", "api/file/ffprobe/{file:.*}")
+ @route("GET", "api/file/ffprobe/{file:.*}", "ffprobe")
async def get_ffprobe(self, request: Request) -> Response:
"""
Get the ffprobe data.
@@ -1716,7 +1789,11 @@ class HttpAPI(Common):
return Response(
status=web.HTTPFound.status_code,
headers={
- "Location": f"/api/file/ffprobe/{quote(str(realFile).replace(self.config.download_path, '').strip('/'))}"
+ "Location": str(
+ self.app.router["ffprobe"].url_for(
+ file=str(realFile).replace(self.config.download_path, "").strip("/")
+ )
+ ),
},
)
@@ -1729,7 +1806,7 @@ class HttpAPI(Common):
except Exception as e:
return web.json_response(data={"error": str(e)}, status=web.HTTPInternalServerError.status_code)
- @route("GET", "api/file/info/{file:.*}")
+ @route("GET", "api/file/info/{file:.*}", "file_info")
async def get_file_info(self, request: Request) -> Response:
"""
Get file info
@@ -1751,7 +1828,11 @@ class HttpAPI(Common):
return Response(
status=web.HTTPFound.status_code,
headers={
- "Location": f"/api/file/info/{quote(str(realFile).replace(self.config.download_path, '').strip('/'))}"
+ "Location": str(
+ self.app.router["file_info"].url_for(
+ file=str(realFile).replace(self.config.download_path, "").strip("/")
+ )
+ ),
},
)
@@ -1780,7 +1861,7 @@ class HttpAPI(Common):
LOG.exception(e)
return web.json_response(data={"error": str(e)}, status=web.HTTPInternalServerError.status_code)
- @route("GET", "api/notifications")
+ @route("GET", "api/notifications", "notifications")
async def notifications(self, _: Request) -> Response:
"""
Get the notification targets.
@@ -1801,7 +1882,7 @@ class HttpAPI(Common):
dumps=self.encoder.encode,
)
- @route("PUT", "api/notifications")
+ @route("PUT", "api/notifications", "notification_add")
async def notification_add(self, request: Request) -> Response:
"""
Add notification targets.
@@ -1857,7 +1938,7 @@ class HttpAPI(Common):
return web.json_response(data=data, status=web.HTTPOk.status_code, dumps=self.encoder.encode)
- @route("POST", "api/notifications/test")
+ @route("POST", "api/notifications/test", "notification_test")
async def notification_test(self, _: Request) -> Response:
"""
Test the notification.
@@ -1875,7 +1956,7 @@ class HttpAPI(Common):
return web.json_response(data=data, status=web.HTTPOk.status_code, dumps=self.encoder.encode)
- @route("GET", "api/file/browser/{path:.*}")
+ @route("GET", "api/file/browser/{path:.*}", "file_browser")
async def file_browser(self, request: Request) -> Response:
"""
Get the file browser.
diff --git a/app/library/HttpSocket.py b/app/library/HttpSocket.py
index 77ea1c56..87496e56 100644
--- a/app/library/HttpSocket.py
+++ b/app/library/HttpSocket.py
@@ -50,6 +50,7 @@ class HttpSocket(Common):
self.queue = queue or DownloadQueue.get_instance()
self._notify = EventBus.get_instance()
+ #logger=True, engineio_logger=True,
self.sio = sio or socketio.AsyncServer(cors_allowed_origins="*")
encoder = encoder or Encoder()
@@ -77,7 +78,7 @@ class HttpSocket(Common):
LOG.debug("Shutting down socket server.")
def attach(self, app: web.Application):
- self.sio.attach(app, socketio_path=self.config.url_socketio)
+ self.sio.attach(app, socketio_path=f"{self.config.base_path.rstrip('/')}/socket.io")
for attr_name in dir(self):
method = getattr(self, attr_name)
diff --git a/app/library/Utils.py b/app/library/Utils.py
index b3e61ec1..d1d29296 100644
--- a/app/library/Utils.py
+++ b/app/library/Utils.py
@@ -969,24 +969,28 @@ async def tail_log(file: str, emitter: callable, sleep_time: float = 0.5):
if not pathlib.Path(file).exists():
return
- async with await open_file(file, "rb") as f:
- await f.seek(0, os.SEEK_END)
- while True:
- line = await f.readline()
- if not line:
- await asyncio_sleep(sleep_time)
- continue
+ try:
+ async with await open_file(file, "rb") as f:
+ await f.seek(0, os.SEEK_END)
+ while True:
+ line = await f.readline()
+ if not line:
+ await asyncio_sleep(sleep_time)
+ continue
- msg = line.decode(errors="replace")
- dt_match = DATETIME_PATTERN.match(msg)
+ msg = line.decode(errors="replace")
+ dt_match = DATETIME_PATTERN.match(msg)
- await emitter(
- {
- "id": sha256(line if isinstance(line, bytes) else line.encode()).hexdigest(),
- "line": msg[dt_match.end() :] if dt_match else msg,
- "datetime": dt_match.group(1) if dt_match else None,
- }
- )
+ await emitter(
+ {
+ "id": sha256(line if isinstance(line, bytes) else line.encode()).hexdigest(),
+ "line": msg[dt_match.end() :] if dt_match else msg,
+ "datetime": dt_match.group(1) if dt_match else None,
+ }
+ )
+ except Exception as e:
+ LOG.error(f"Error while tailing log file '{file!s}': {e!s}")
+ return
def load_cookies(file: str) -> tuple[bool, MozillaCookieJar]:
diff --git a/app/library/config.py b/app/library/config.py
index 805dc11a..195b7b0d 100644
--- a/app/library/config.py
+++ b/app/library/config.py
@@ -28,9 +28,6 @@ class Config:
temp_keep: bool = False
"""Keep temporary files after the download is complete."""
- url_socketio: str = "/socket.io"
- """The URL to use for the socket.io server."""
-
output_template: str = "%(title)s.%(ext)s"
"""The output template to use for the downloaded files."""
@@ -55,11 +52,15 @@ class Config:
log_level_file: str = "info"
"""The log level to use for the file logging."""
+ base_path: str = "/"
+ """The base path to use for the application."""
+
max_workers: int = 1
"""The maximum number of workers to use for downloading."""
streamer_vcodec: str = "libx264"
"""The video codec to use for streaming."""
+
streamer_acodec: str = "aac"
"""The audio codec to use for streaming."""
@@ -221,6 +222,7 @@ class Config:
"browser_enabled",
"ytdlp_cli",
"file_logging",
+ "base_path",
)
"The variables that are relevant to the frontend."
diff --git a/app/main.py b/app/main.py
index 5d1ff35b..2880aa36 100644
--- a/app/main.py
+++ b/app/main.py
@@ -23,6 +23,7 @@ from library.Tasks import Tasks
LOG = logging.getLogger("app")
MIME = magic.Magic(mime=True)
+
class Main:
def __init__(self):
self._config = Config.get_instance()
@@ -99,14 +100,16 @@ class Main:
def started(_):
LOG.info("=" * 40)
- LOG.info(f"YTPTube v{self._config.version} - started on http://{self._config.host}:{self._config.port}")
+ LOG.info(
+ f"YTPTube v{self._config.version} - started on http://{self._config.host}:{self._config.port}{self._config.base_path}"
+ )
LOG.info("=" * 40)
HTTP_LOGGER = None
if self._config.access_log:
from library.HttpAPI import LOG as HTTP_LOGGER
- HTTP_LOGGER.addFilter(lambda record: "GET /api/ping" not in record.getMessage())
+ HTTP_LOGGER.addFilter(lambda record: f"GET {self._app.router['ping'].url_for()}" not in record.getMessage())
web.run_app(
self._app,
diff --git a/ui/components/History.vue b/ui/components/History.vue
index 3047414b..71d74be3 100644
--- a/ui/components/History.vue
+++ b/ui/components/History.vue
@@ -107,8 +107,8 @@
-
-
+
{{ item.title }}
@@ -198,53 +198,52 @@
|