Merge pull request #89 from arabcoders/dev
Video Streaming imporvements.
This commit is contained in:
commit
d639762001
7 changed files with 34 additions and 28 deletions
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
|
@ -33,7 +33,7 @@
|
||||||
"YTP_DOWNLOAD_PATH": "${workspaceFolder}/var/downloads",
|
"YTP_DOWNLOAD_PATH": "${workspaceFolder}/var/downloads",
|
||||||
"YTP_TEMP_PATH": "${workspaceFolder}/var/tmp",
|
"YTP_TEMP_PATH": "${workspaceFolder}/var/tmp",
|
||||||
"YTP_URL_HOST": "http://localhost:8081",
|
"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_DOWNLOAD_PATH": "${workspaceFolder}/var/downloads",
|
||||||
"YTP_TEMP_PATH": "${workspaceFolder}/var/tmp",
|
"YTP_TEMP_PATH": "${workspaceFolder}/var/tmp",
|
||||||
"YTP_URL_HOST": "http://localhost:8081",
|
"YTP_URL_HOST": "http://localhost:8081",
|
||||||
"YTP_LOG_LEVEL": "DEBUG"
|
"YTP_LOG_LEVEL": "DEBUG",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -28,6 +28,7 @@
|
||||||
"preferredcodec",
|
"preferredcodec",
|
||||||
"preferredquality",
|
"preferredquality",
|
||||||
"tmpfilename",
|
"tmpfilename",
|
||||||
|
"vcodec",
|
||||||
"vconvert",
|
"vconvert",
|
||||||
"writedescription",
|
"writedescription",
|
||||||
"xerror"
|
"xerror"
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,8 @@ ENV YTP_PORT=8081
|
||||||
ENV XDG_CONFIG_HOME=/config
|
ENV XDG_CONFIG_HOME=/config
|
||||||
ENV XDG_CACHE_HOME=/tmp
|
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 && \
|
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 && \
|
useradd -u ${USER_ID:-1000} -U -d /app -s /bin/bash app && \
|
||||||
rm -rf /var/cache/apk/*
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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_PORT__: Which port to bind to. Defaults to `8081`.
|
||||||
* __YTP_LOG_LEVEL__: Log level. Defaults to `info`.
|
* __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_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
|
## Running behind a reverse proxy
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@ class Config:
|
||||||
|
|
||||||
started: int = 0
|
started: int = 0
|
||||||
|
|
||||||
|
streamer_vcodec = 'libx264'
|
||||||
|
streamer_acodec = 'aac'
|
||||||
|
|
||||||
ytdlp_version: str = YTDLP_VERSION
|
ytdlp_version: str = YTDLP_VERSION
|
||||||
|
|
||||||
_int_vars: tuple = ('port', 'max_workers', 'socket_timeout', 'extract_info_timeout',)
|
_int_vars: tuple = ('port', 'max_workers', 'socket_timeout', 'extract_info_timeout',)
|
||||||
|
|
|
||||||
16
app/main.py
16
app/main.py
|
|
@ -4,7 +4,9 @@ import asyncio
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
from Config import Config
|
from Config import Config
|
||||||
from DownloadQueue import DownloadQueue
|
from DownloadQueue import DownloadQueue
|
||||||
from Utils import ObjectSerializer, Notifier
|
from Utils import ObjectSerializer, Notifier
|
||||||
|
|
@ -18,6 +20,7 @@ import logging
|
||||||
import caribou
|
import caribou
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from aiocron import crontab
|
from aiocron import crontab
|
||||||
|
from aiohttp.helpers import parse_http_date
|
||||||
import magic
|
import magic
|
||||||
|
|
||||||
LOG = logging.getLogger('app')
|
LOG = logging.getLogger('app')
|
||||||
|
|
@ -440,6 +443,11 @@ class Main:
|
||||||
vc: int = int(request.query.get('vc', 0))
|
vc: int = int(request.query.get('vc', 0))
|
||||||
ac: int = int(request.query.get('ac', 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:
|
if not file:
|
||||||
raise web.HTTPBadRequest(reason='file is required')
|
raise web.HTTPBadRequest(reason='file is required')
|
||||||
|
|
||||||
|
|
@ -457,10 +465,14 @@ class Main:
|
||||||
body=await segmenter.stream(path=self.config.download_path, file=file),
|
body=await segmenter.stream(path=self.config.download_path, file=file),
|
||||||
headers={
|
headers={
|
||||||
'Content-Type': 'video/mpegts',
|
'Content-Type': 'video/mpegts',
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'X-Accel-Buffering': 'no',
|
'X-Accel-Buffering': 'no',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'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()),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from Utils import calcDownloadPath
|
from Utils import calcDownloadPath
|
||||||
|
from Config import Config
|
||||||
|
|
||||||
LOG = logging.getLogger('segments')
|
LOG = logging.getLogger('segments')
|
||||||
|
|
||||||
|
|
@ -13,12 +14,18 @@ class Segments:
|
||||||
index: int
|
index: int
|
||||||
vconvert: bool
|
vconvert: bool
|
||||||
aconvert: bool
|
aconvert: bool
|
||||||
|
vcodec: str
|
||||||
|
acodec: str
|
||||||
|
|
||||||
def __init__(self, index: int, duration: float, vconvert: bool, aconvert: bool):
|
def __init__(self, index: int, duration: float, vconvert: bool, aconvert: bool):
|
||||||
|
config = Config.get_instance()
|
||||||
self.index = int(index)
|
self.index = int(index)
|
||||||
self.duration = float(duration)
|
self.duration = float(duration)
|
||||||
self.vconvert = bool(vconvert)
|
self.vconvert = bool(vconvert)
|
||||||
self.aconvert = bool(aconvert)
|
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:
|
async def stream(self, path: str, file: str) -> bytes:
|
||||||
realFile: str = calcDownloadPath(basePath=path, folder=file, createPath=False)
|
realFile: str = calcDownloadPath(basePath=path, folder=file, createPath=False)
|
||||||
|
|
@ -66,29 +73,13 @@ class Segments:
|
||||||
fargs.append('-2')
|
fargs.append('-2')
|
||||||
|
|
||||||
fargs.append('-codec:v')
|
fargs.append('-codec:v')
|
||||||
fargs.append('libx264' if self.vconvert else 'copy')
|
fargs.append(self.vcodec 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')
|
|
||||||
|
|
||||||
# audio section.
|
# audio section.
|
||||||
fargs.append('-map')
|
fargs.append('-map')
|
||||||
fargs.append('0:a:0')
|
fargs.append('0:a:0')
|
||||||
fargs.append('-codec:a')
|
fargs.append('-codec:a')
|
||||||
fargs.append('aac' if self.aconvert else 'copy')
|
fargs.append(self.acodec 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('-sn')
|
fargs.append('-sn')
|
||||||
|
|
||||||
|
|
@ -110,7 +101,7 @@ class Segments:
|
||||||
data, err = await proc.communicate()
|
data, err = await proc.communicate()
|
||||||
|
|
||||||
if 0 != proc.returncode:
|
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}.')
|
raise Exception(f'Failed to stream {realFile} segment {self.index}.')
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue