diff --git a/.vscode/launch.json b/.vscode/launch.json index 4667fe81..90854880 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -33,7 +33,7 @@ "YTP_DOWNLOAD_PATH": "${workspaceFolder}/var/downloads", "YTP_TEMP_PATH": "${workspaceFolder}/var/tmp", "YTP_URL_HOST": "http://localhost:8081", - "YTP_LOG_LEVEL": "DEBUG" + "YTP_LOG_LEVEL": "DEBUG", } }, { @@ -48,7 +48,7 @@ "YTP_DOWNLOAD_PATH": "${workspaceFolder}/var/downloads", "YTP_TEMP_PATH": "${workspaceFolder}/var/tmp", "YTP_URL_HOST": "http://localhost:8081", - "YTP_LOG_LEVEL": "DEBUG" + "YTP_LOG_LEVEL": "DEBUG", } }, { diff --git a/.vscode/settings.json b/.vscode/settings.json index 4f87eae2..ea210f0f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,6 +28,7 @@ "preferredcodec", "preferredquality", "tmpfilename", + "vcodec", "vconvert", "writedescription", "xerror" diff --git a/Dockerfile b/Dockerfile index 6a14a6db..f8e76872 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,12 +34,8 @@ ENV YTP_PORT=8081 ENV XDG_CONFIG_HOME=/config ENV XDG_CACHE_HOME=/tmp -# removed ffmpeg as 6.1.0 is broken with DASH protocal downloads -COPY --from=mwader/static-ffmpeg:6.1.1 /ffmpeg /usr/bin/ -COPY --from=mwader/static-ffmpeg:6.1.1 /ffprobe /usr/bin/ - RUN mkdir /config /downloads && ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && \ - apk add --update --no-cache bash mkvtoolnix patch aria2 coreutils curl shadow sqlite tzdata libmagic && \ + apk add --update --no-cache bash mkvtoolnix patch aria2 coreutils curl shadow sqlite tzdata libmagic ffmpeg && \ useradd -u ${USER_ID:-1000} -U -d /app -s /bin/bash app && \ rm -rf /var/cache/apk/* diff --git a/README.md b/README.md index ef022942..4e347451 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,9 @@ Certain values can be set via environment variables, using the `-e` parameter on * __YTP_PORT__: Which port to bind to. Defaults to `8081`. * __YTP_LOG_LEVEL__: Log level. Defaults to `info`. * __YTP_MAX_WORKERS__: How many works to use for downloads. Defaults to `1`. +* __YTP_MAX_WORKERS__: How many works to use for downloads. Defaults to `1`. +* __YTP_STREAMER_VCODEC__: The video codec to use for in-browser streaming. Defaults to `libx264`. +* __YTP_STREAMER_ACODEC__: The audio codec to use for in-browser streaming. Defaults to `aac`. ## Running behind a reverse proxy diff --git a/app/Config.py b/app/Config.py index d25fdb82..591d0e80 100644 --- a/app/Config.py +++ b/app/Config.py @@ -53,6 +53,9 @@ class Config: started: int = 0 + streamer_vcodec = 'libx264' + streamer_acodec = 'aac' + ytdlp_version: str = YTDLP_VERSION _int_vars: tuple = ('port', 'max_workers', 'socket_timeout', 'extract_info_timeout',) diff --git a/app/main.py b/app/main.py index 5e5a4edc..e6fcc3e5 100644 --- a/app/main.py +++ b/app/main.py @@ -4,7 +4,9 @@ import asyncio from datetime import datetime import json import os +import pathlib import random +import time from Config import Config from DownloadQueue import DownloadQueue from Utils import ObjectSerializer, Notifier @@ -18,6 +20,7 @@ import logging import caribou import sqlite3 from aiocron import crontab +from aiohttp.helpers import parse_http_date import magic LOG = logging.getLogger('app') @@ -440,6 +443,11 @@ class Main: vc: int = int(request.query.get('vc', 0)) ac: int = int(request.query.get('ac', 0)) + if request.if_modified_since: + file_path = os.path.join(self.config.download_path, file) + if os.path.exists(file_path) and request.if_modified_since.timestamp() == os.path.getmtime(file_path): + return web.Response(status=304) + if not file: raise web.HTTPBadRequest(reason='file is required') @@ -457,10 +465,14 @@ class Main: body=await segmenter.stream(path=self.config.download_path, file=file), headers={ 'Content-Type': 'video/mpegts', - 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no', 'Access-Control-Allow-Origin': '*', - 'Access-Control-Max-Age': "300", + 'Pragma': 'public', + 'Cache-Control': f'public, max-age={time.time() + 31536000}', + 'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp( + os.path.getmtime(os.path.join(self.config.download_path, file))).timetuple() + ), + 'Expires': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(time.time() + 31536000).timetuple()), } ) diff --git a/app/player/Segments.py b/app/player/Segments.py index b89f781d..3e0ba9cd 100644 --- a/app/player/Segments.py +++ b/app/player/Segments.py @@ -4,6 +4,7 @@ import logging import os import tempfile from Utils import calcDownloadPath +from Config import Config LOG = logging.getLogger('segments') @@ -13,12 +14,18 @@ class Segments: index: int vconvert: bool aconvert: bool + vcodec: str + acodec: str def __init__(self, index: int, duration: float, vconvert: bool, aconvert: bool): + config = Config.get_instance() self.index = int(index) self.duration = float(duration) self.vconvert = bool(vconvert) self.aconvert = bool(aconvert) + self.vcodec = config.streamer_vcodec + self.acodec = config.streamer_acodec + self.vconvert = True async def stream(self, path: str, file: str) -> bytes: realFile: str = calcDownloadPath(basePath=path, folder=file, createPath=False) @@ -66,29 +73,13 @@ class Segments: fargs.append('-2') fargs.append('-codec:v') - fargs.append('libx264' if self.vconvert else 'copy') - if self.vconvert: - fargs.append('-crf') - fargs.append('23') - fargs.append('-preset:v') - fargs.append('fast') - fargs.append('-level') - fargs.append('4.1') - fargs.append('-profile:v') - fargs.append('baseline') + fargs.append(self.vcodec if self.vconvert else 'copy') # audio section. fargs.append('-map') fargs.append('0:a:0') fargs.append('-codec:a') - fargs.append('aac' if self.aconvert else 'copy') - if self.aconvert: - fargs.append('-b:a') - fargs.append('192k') - fargs.append('-ar') - fargs.append('22050') - fargs.append('-ac') - fargs.append('2') + fargs.append(self.acodec if self.aconvert else 'copy') fargs.append('-sn') @@ -110,7 +101,7 @@ class Segments: data, err = await proc.communicate() if 0 != proc.returncode: - LOG.error(f'Failed to stream {realFile} segment {self.index}. {err.decode("utf-8")}') + LOG.error(f'Failed to stream {realFile} segment {self.index}. {err.decode("utf-8")}.') raise Exception(f'Failed to stream {realFile} segment {self.index}.') return data