84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
import math
|
|
from pathlib import Path
|
|
from urllib.parse import quote
|
|
|
|
from app.features.streaming.library.ffprobe import ffprobe
|
|
from app.features.streaming.types import StreamingError
|
|
|
|
|
|
class M3u8:
|
|
duration: float = 6.000000
|
|
|
|
ok_vcodecs: tuple = ("h264", "x264", "avc")
|
|
ok_acodecs: tuple = ("aac", "m4a", "mp3")
|
|
|
|
def __init__(self, download_path: Path, url: str, segment_duration: float | None = None):
|
|
self.url = url
|
|
self.download_path: Path = download_path
|
|
self.duration = float(segment_duration) if segment_duration is not None else self.duration
|
|
|
|
async def make_stream(self, file: Path) -> str:
|
|
try:
|
|
ff = await ffprobe(file)
|
|
except UnicodeDecodeError:
|
|
pass
|
|
|
|
urlPath: str = str(file.relative_to(self.download_path).as_posix()).strip("/")
|
|
|
|
if "duration" not in ff.metadata:
|
|
error = f"Unable to get '{file}' play duration."
|
|
raise StreamingError(error)
|
|
|
|
duration: float = float(ff.metadata.get("duration"))
|
|
|
|
m3u8: list = []
|
|
|
|
m3u8.append("#EXTM3U")
|
|
m3u8.append("#EXT-X-VERSION:3")
|
|
m3u8.append(f"#EXT-X-TARGETDURATION:{int(self.duration)}")
|
|
m3u8.append("#EXT-X-MEDIA-SEQUENCE:0")
|
|
m3u8.append("#EXT-X-PLAYLIST-TYPE:VOD")
|
|
|
|
segmentSize: str = f"{self.duration:.6f}"
|
|
splits: int = math.ceil(duration / self.duration)
|
|
|
|
segmentParams: dict = {}
|
|
|
|
for stream in ff.streams():
|
|
if stream.is_video() and stream.codec_name not in self.ok_vcodecs:
|
|
segmentParams["vc"] = 1
|
|
if stream.is_audio() and stream.codec_name not in self.ok_acodecs:
|
|
segmentParams["ac"] = 1
|
|
|
|
for i in range(splits):
|
|
if (i + 1) == splits:
|
|
segmentSize = f"{duration - (i * self.duration):.6f}"
|
|
segmentParams.update({"sd": segmentSize})
|
|
|
|
m3u8.append(f"#EXTINF:{segmentSize},")
|
|
|
|
url = f"{self.url}api/player/segments/{i}/{quote(urlPath)}.ts"
|
|
if len(segmentParams) > 0:
|
|
url += "?" + "&".join([f"{key}={value}" for key, value in segmentParams.items()])
|
|
|
|
m3u8.append(url)
|
|
|
|
m3u8.append("#EXT-X-ENDLIST")
|
|
|
|
return "\n".join(m3u8)
|
|
|
|
async def make_subtitle(self, file: Path, duration: float) -> str:
|
|
m3u8 = []
|
|
|
|
urlPath: str = str(file.relative_to(self.download_path).as_posix()).strip("/")
|
|
|
|
m3u8.append("#EXTM3U")
|
|
m3u8.append("#EXT-X-VERSION:3")
|
|
m3u8.append(f"#EXT-X-TARGETDURATION:{int(self.duration)}")
|
|
m3u8.append("#EXT-X-MEDIA-SEQUENCE:0")
|
|
m3u8.append("#EXT-X-PLAYLIST-TYPE:VOD")
|
|
m3u8.append(f"#EXTINF:{duration},")
|
|
m3u8.append(f"{self.url}api/player/subtitle/{quote(urlPath)}.vtt")
|
|
m3u8.append("#EXT-X-ENDLIST")
|
|
|
|
return "\n".join(m3u8)
|