diff --git a/app/src/Config.py b/app/Config.py similarity index 91% rename from app/src/Config.py rename to app/Config.py index 8a115360..fb721409 100644 --- a/app/src/Config.py +++ b/app/Config.py @@ -4,6 +4,7 @@ import os import re import sys import coloredlogs +from version import APP_VERSION class Config: @@ -33,7 +34,10 @@ class Config: logging_level: str = 'info' - _boolean_vars: tuple = ('keep_archive', 'ytdl_debug') + version: str = APP_VERSION + + _boolean_vars: tuple = ('keep_archive', 'ytdl_debug',) + _immutable: tuple = ('version',) def __init__(self): baseDefualtPath: str = os.path.dirname(os.path.dirname(__file__)) @@ -46,6 +50,11 @@ class Config: if k.startswith('_'): continue + # If the variable is not updateable, set it to the default value. + if k in self._immutable: + setattr(self, k, v) + continue + lookUpKey: str = f'YTP_{k}'.upper() setattr( self, k, @@ -53,7 +62,7 @@ class Config: ) for k, v in self.__dict__.items(): - if k.startswith('_'): + if k.startswith('_') or k in self._immutable: continue if isinstance(v, str) and '{' in v and '}' in v: diff --git a/app/src/DataStore.py b/app/DataStore.py similarity index 97% rename from app/src/DataStore.py rename to app/DataStore.py index 84be145c..05ce99ce 100644 --- a/app/src/DataStore.py +++ b/app/DataStore.py @@ -4,10 +4,10 @@ from datetime import datetime, timezone from email.utils import formatdate import json import sqlite3 -from src.Utils import calcDownloadPath -from src.Config import Config -from src.Download import Download -from src.DTO.ItemDTO import ItemDTO +from Utils import calcDownloadPath +from Config import Config +from Download import Download +from ItemDTO import ItemDTO class DataStore: diff --git a/app/src/Download.py b/app/Download.py similarity index 98% rename from app/src/Download.py rename to app/Download.py index bde97203..261b37d9 100644 --- a/app/src/Download.py +++ b/app/Download.py @@ -7,9 +7,8 @@ import os import re import shutil import yt_dlp -from src.Utils import get_format, get_opts, jsonCookie, mergeConfig -from src.DTO.ItemDTO import ItemDTO -from src.Notifier import Notifier +from Utils import Notifier, get_format, get_opts, jsonCookie, mergeConfig +from ItemDTO import ItemDTO log = logging.getLogger('download') diff --git a/app/src/DownloadQueue.py b/app/DownloadQueue.py similarity index 97% rename from app/src/DownloadQueue.py rename to app/DownloadQueue.py index 4ce8cf00..3f68d3f3 100644 --- a/app/src/DownloadQueue.py +++ b/app/DownloadQueue.py @@ -5,12 +5,11 @@ import os import time import yt_dlp from sqlite3 import Connection -from src.Config import Config -from src.Notifier import Notifier -from src.Download import Download -from src.DTO.ItemDTO import ItemDTO -from src.DataStore import DataStore -from src.Utils import ObjectSerializer, calcDownloadPath, ExtractInfo, mergeConfig +from Config import Config +from Download import Download +from ItemDTO import ItemDTO +from DataStore import DataStore +from Utils import Notifier, ObjectSerializer, calcDownloadPath, ExtractInfo, mergeConfig from datetime import datetime, timezone log = logging.getLogger('DownloadQueue') diff --git a/app/src/DTO/ItemDTO.py b/app/ItemDTO.py similarity index 100% rename from app/src/DTO/ItemDTO.py rename to app/ItemDTO.py diff --git a/app/src/Utils.py b/app/Utils.py similarity index 91% rename from app/src/Utils.py rename to app/Utils.py index fdb969e7..7584079b 100644 --- a/app/src/Utils.py +++ b/app/Utils.py @@ -5,6 +5,7 @@ import logging import os from typing import Any import yt_dlp +from socketio import AsyncServer log = logging.getLogger('Utils') @@ -274,3 +275,31 @@ class ObjectSerializer(json.JSONEncoder): def default(self, obj): return obj.__dict__ if isinstance(obj, object) else json.JSONEncoder.default(self, obj) + + +class Notifier: + ''' + This class is used to send events to the frontend. + ''' + + def __init__(self, sio: AsyncServer, serializer: ObjectSerializer): + self.sio = sio + self.serializer = serializer + + async def added(self, dl): + await self.emit('added', dl) + + async def updated(self, dl): + await self.emit('updated', dl) + + async def completed(self, dl): + await self.emit('completed', dl) + + async def canceled(self, id): + await self.emit('canceled', id) + + async def cleared(self, id): + await self.emit('cleared', id) + + async def emit(self, event: str, data): + await self.sio.emit(event, self.serializer.encode(data)) diff --git a/app/main.py b/app/main.py index c07cd1de..62f8941c 100644 --- a/app/main.py +++ b/app/main.py @@ -5,24 +5,19 @@ from datetime import datetime import json import os import random -from src.Config import Config -from version import APP_VERSION -from src.Notifier import Notifier -from src.DownloadQueue import DownloadQueue -from src.Utils import ObjectSerializer +from Config import Config +from DownloadQueue import DownloadQueue +from Utils import ObjectSerializer, Notifier from aiohttp import web from aiohttp.web import Request, Response -from src.M3u8 import M3u8 -from src.Segments import Segments +from player.M3u8 import M3u8 +from player.Segments import Segments import socketio import logging import caribou import sqlite3 from aiocron import crontab -log: logging.Logger = None - - class Main: config: Config = None serializer: ObjectSerializer = None @@ -36,7 +31,6 @@ class Main: def __init__(self): self.config = Config() - self.config.version = APP_VERSION self.logger = logging.getLogger('main') try: @@ -113,6 +107,7 @@ class Main: ) async def connect(self, sid, _): + self.logger.info(f'Config [{self.config.__dict__}].') data: dict = { **self.dqueue.get(), "config": self.config, @@ -285,7 +280,10 @@ class Main: raise web.HTTPBadRequest(reason='file is required') return web.Response( - text=M3u8(self.config).make_stream(file), + text=M3u8(url=f"{self.config.url_host}{self.config.url_prefix}").make_stream( + download_path=self.config.download_path, + file=file + ), headers={ 'Content-Type': 'application/x-mpegURL', 'Cache-Control': 'no-cache', @@ -308,7 +306,6 @@ class Main: raise web.HTTPBadRequest(reason='segment is required') segmenter = Segments( - config=self.config, segment_index=int(segment), segment_duration=float('{:.6f}'.format( float(sd if sd else M3u8.segment_duration))), @@ -317,7 +314,7 @@ class Main: ) return web.Response( - body=await segmenter.stream(file), + body=await segmenter.stream(download_path=self.config.download_path, file=file), headers={ 'Content-Type': 'video/mpegts', 'Connection': 'keep-alive', diff --git a/app/src/M3u8.py b/app/player/M3u8.py similarity index 83% rename from app/src/M3u8.py rename to app/player/M3u8.py index fcb83609..28ef74fa 100644 --- a/app/src/M3u8.py +++ b/app/player/M3u8.py @@ -1,9 +1,8 @@ import math import os from urllib.parse import quote -from src.Utils import calcDownloadPath -from src.ffprobe import FFProbe -from src.Config import Config +from Utils import calcDownloadPath +from .ffprobe import FFProbe class M3u8: @@ -11,17 +10,15 @@ class M3u8: ok_acodecs: tuple = ('aac', 'mp3',) segment_duration: float = 10.000000 - config: Config = None - download_path: str = None + url: str = None - def __init__(self, config: Config, segment_duration: float = 6.000000): - self.config = config - self.download_path = self.config.download_path + def __init__(self, url: str, segment_duration: float = 6.000000): + self.url = url self.segment_duration = float(segment_duration) - def make_stream(self, file: str): + def make_stream(self, download_path: str, file: str): realFile: str = calcDownloadPath( - basePath=self.download_path, + basePath=download_path, folder=file, createPath=False ) @@ -68,7 +65,7 @@ class M3u8: else: m3u8 += f"#EXTINF:{segmentSize}, nodesc\n" - m3u8 += f"{self.config.url_host}{self.config.url_prefix}segments/{i}/{quote(file)}" + m3u8 += f"{self.url}segments/{i}/{quote(file)}" if len(segmentParams) > 0: m3u8 += '?'+'&'.join([f"{key}={value}" for key, value in segmentParams.items()]) diff --git a/app/src/Segments.py b/app/player/Segments.py similarity index 88% rename from app/src/Segments.py rename to app/player/Segments.py index 10571164..9fa4de46 100644 --- a/app/src/Segments.py +++ b/app/player/Segments.py @@ -3,30 +3,27 @@ import logging import os import subprocess import tempfile -from src.Utils import calcDownloadPath -from src.Config import Config +from Utils import calcDownloadPath +from Config import Config log = logging.getLogger('segments') + class Segments: - config: Config = None - download_path: str = None segment_duration: int segment_index: int vconvert: bool aconvert: bool - def __init__(self, config: Config, segment_index: int, segment_duration: float, vconvert: bool, aconvert: bool): - self.config = config - self.download_path = self.config.download_path + def __init__(self, segment_index: int, segment_duration: float, vconvert: bool, aconvert: bool): self.segment_duration = float(segment_duration) self.segment_index = int(segment_index) self.vconvert = bool(vconvert) self.aconvert = bool(aconvert) - async def stream(self, file: str) -> bytes: + async def stream(self, download_path: str, file: str) -> bytes: realFile: str = calcDownloadPath( - basePath=self.download_path, + basePath=download_path, folder=file, createPath=False ) diff --git a/app/src/ffprobe.py b/app/player/ffprobe.py similarity index 99% rename from app/src/ffprobe.py rename to app/player/ffprobe.py index 0d81b76e..d7ed76c4 100644 --- a/app/src/ffprobe.py +++ b/app/player/ffprobe.py @@ -6,7 +6,6 @@ import json import operator import os import pipes -import re import subprocess diff --git a/app/src/Notifier.py b/app/src/Notifier.py deleted file mode 100644 index 53362b90..00000000 --- a/app/src/Notifier.py +++ /dev/null @@ -1,26 +0,0 @@ -from socketio import AsyncServer -from src.Utils import ObjectSerializer - - -class Notifier: - def __init__(self, sio: AsyncServer, serializer: ObjectSerializer): - self.sio = sio - self.serializer = serializer - - async def added(self, dl): - await self.emit('added', dl) - - async def updated(self, dl): - await self.emit('updated', dl) - - async def completed(self, dl): - await self.emit('completed', dl) - - async def canceled(self, id): - await self.emit('canceled', id) - - async def cleared(self, id): - await self.emit('cleared', id) - - async def emit(self, event: str, data): - await self.sio.emit(event, self.serializer.encode(data))