Added HLS player.

This commit is contained in:
ArabCoders 2023-11-27 21:35:53 +03:00
parent 9c4a9892fb
commit 28989c4cba
11 changed files with 433 additions and 88 deletions

View file

@ -9,6 +9,7 @@ aiohttp = "*"
yt-dlp = "*"
caribou = "*"
coloredlogs = "*"
ffprobe-python = "*"
[dev-packages]

170
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "08575f369e4bb22a440dd5635b4032bab5326794ccac7944ea4a39d0290d32c6"
"sha256": "258ec26d54c61c901fb67cb207bfb76d71d60ae0866f2ff3ab9f5e2e97940c84"
},
"pipfile-spec": 6,
"requires": {
@ -18,85 +18,85 @@
"default": {
"aiohttp": {
"hashes": [
"sha256:05857848da443c8c12110d99285d499b4e84d59918a21132e45c3f0804876994",
"sha256:05a183f1978802588711aed0dea31e697d760ce9055292db9dc1604daa9a8ded",
"sha256:09f23292d29135025e19e8ff4f0a68df078fe4ee013bca0105b2e803989de92d",
"sha256:11ca808f9a6b63485059f5f6e164ef7ec826483c1212a44f268b3653c91237d8",
"sha256:1736d87dad8ef46a8ec9cddd349fa9f7bd3a064c47dd6469c0d6763d3d49a4fc",
"sha256:1df43596b826022b14998f0460926ce261544fedefe0d2f653e1b20f49e96454",
"sha256:23170247ef89ffa842a02bbfdc425028574d9e010611659abeb24d890bc53bb8",
"sha256:2779f5e7c70f7b421915fd47db332c81de365678180a9f3ab404088f87ba5ff9",
"sha256:28185e36a78d247c55e9fbea2332d16aefa14c5276a582ce7a896231c6b1c208",
"sha256:2cbc14a13fb6b42d344e4f27746a4b03a2cb0c1c3c5b932b0d6ad8881aa390e3",
"sha256:2d71abc15ff7047412ef26bf812dfc8d0d1020d664617f4913df2df469f26b76",
"sha256:2d820162c8c2bdbe97d328cd4f417c955ca370027dce593345e437b2e9ffdc4d",
"sha256:317719d7f824eba55857fe0729363af58e27c066c731bc62cd97bc9c3d9c7ea4",
"sha256:35a68cd63ca6aaef5707888f17a70c36efe62b099a4e853d33dc2e9872125be8",
"sha256:3607375053df58ed6f23903aa10cf3112b1240e8c799d243bbad0f7be0666986",
"sha256:366bc870d7ac61726f32a489fbe3d1d8876e87506870be66b01aeb84389e967e",
"sha256:3abf0551874fecf95f93b58f25ef4fc9a250669a2257753f38f8f592db85ddea",
"sha256:3d7f6235c7475658acfc1769d968e07ab585c79f6ca438ddfecaa9a08006aee2",
"sha256:3dd8119752dd30dd7bca7d4bc2a92a59be6a003e4e5c2cf7e248b89751b8f4b7",
"sha256:42fe4fd9f0dfcc7be4248c162d8056f1d51a04c60e53366b0098d1267c4c9da8",
"sha256:45820ddbb276113ead8d4907a7802adb77548087ff5465d5c554f9aa3928ae7d",
"sha256:4790e44f46a4aa07b64504089def5744d3b6780468c4ec3a1a36eb7f2cae9814",
"sha256:4afa8f71dba3a5a2e1e1282a51cba7341ae76585345c43d8f0e624882b622218",
"sha256:4b777c9286b6c6a94f50ddb3a6e730deec327e9e2256cb08b5530db0f7d40fd8",
"sha256:4ee1b4152bc3190cc40ddd6a14715e3004944263ea208229ab4c297712aa3075",
"sha256:51a4cd44788ea0b5e6bb8fa704597af3a30be75503a7ed1098bc5b8ffdf6c982",
"sha256:536b01513d67d10baf6f71c72decdf492fb7433c5f2f133e9a9087379d4b6f31",
"sha256:571760ad7736b34d05597a1fd38cbc7d47f7b65deb722cb8e86fd827404d1f6b",
"sha256:5a2eb5311a37fe105aa35f62f75a078537e1a9e4e1d78c86ec9893a3c97d7a30",
"sha256:5ab16c254e2312efeb799bc3c06897f65a133b38b69682bf75d1f1ee1a9c43a9",
"sha256:65b0a70a25456d329a5e1426702dde67be0fb7a4ead718005ba2ca582d023a94",
"sha256:673343fbc0c1ac44d0d2640addc56e97a052504beacd7ade0dc5e76d3a4c16e8",
"sha256:6777a390e41e78e7c45dab43a4a0196c55c3b8c30eebe017b152939372a83253",
"sha256:6896b8416be9ada4d22cd359d7cb98955576ce863eadad5596b7cdfbf3e17c6c",
"sha256:694df243f394629bcae2d8ed94c589a181e8ba8604159e6e45e7b22e58291113",
"sha256:70e851f596c00f40a2f00a46126c95c2e04e146015af05a9da3e4867cfc55911",
"sha256:7276fe0017664414fdc3618fca411630405f1aaf0cc3be69def650eb50441787",
"sha256:76a86a9989ebf82ee61e06e2bab408aec4ea367dc6da35145c3352b60a112d11",
"sha256:7a94bde005a8f926d0fa38b88092a03dea4b4875a61fbcd9ac6f4351df1b57cd",
"sha256:7ae5f99a32c53731c93ac3075abd3e1e5cfbe72fc3eaac4c27c9dd64ba3b19fe",
"sha256:7e8a3b79b6d186a9c99761fd4a5e8dd575a48d96021f220ac5b5fa856e5dd029",
"sha256:816f4db40555026e4cdda604a1088577c1fb957d02f3f1292e0221353403f192",
"sha256:8303531e2c17b1a494ffaeba48f2da655fe932c4e9a2626c8718403c83e5dd2b",
"sha256:8488519aa05e636c5997719fe543c8daf19f538f4fa044f3ce94bee608817cff",
"sha256:87c8b0a6487e8109427ccf638580865b54e2e3db4a6e0e11c02639231b41fc0f",
"sha256:8c9e5f4d7208cda1a2bb600e29069eecf857e6980d0ccc922ccf9d1372c16f4b",
"sha256:94697c7293199c2a2551e3e3e18438b4cba293e79c6bc2319f5fd652fccb7456",
"sha256:9623cfd9e85b76b83ef88519d98326d4731f8d71869867e47a0b979ffec61c73",
"sha256:98d21092bf2637c5fa724a428a69e8f5955f2182bff61f8036827cf6ce1157bf",
"sha256:99ae01fb13a618b9942376df77a1f50c20a281390dad3c56a6ec2942e266220d",
"sha256:9c196b30f1b1aa3363a69dd69079ae9bec96c2965c4707eaa6914ba099fb7d4f",
"sha256:a00ce44c21612d185c5275c5cba4bab8d7c1590f248638b667ed8a782fa8cd6f",
"sha256:a1b66dbb8a7d5f50e9e2ea3804b01e766308331d0cac76eb30c563ac89c95985",
"sha256:a1d7edf74a36de0e5ca50787e83a77cf352f5504eb0ffa3f07000a911ba353fb",
"sha256:a1e3b3c107ccb0e537f309f719994a55621acd2c8fdf6d5ce5152aed788fb940",
"sha256:a486ddf57ab98b6d19ad36458b9f09e6022de0381674fe00228ca7b741aacb2f",
"sha256:ac9669990e2016d644ba8ae4758688534aabde8dbbc81f9af129c3f5f01ca9cd",
"sha256:b1a2ea8252cacc7fd51df5a56d7a2bb1986ed39be9397b51a08015727dfb69bd",
"sha256:c5b7bf8fe4d39886adc34311a233a2e01bc10eb4e842220235ed1de57541a896",
"sha256:c67a51ea415192c2e53e4e048c78bab82d21955b4281d297f517707dc836bf3d",
"sha256:ca4fddf84ac7d8a7d0866664936f93318ff01ee33e32381a115b19fb5a4d1202",
"sha256:d5b9345ab92ebe6003ae11d8092ce822a0242146e6fa270889b9ba965457ca40",
"sha256:d97c3e286d0ac9af6223bc132dc4bad6540b37c8d6c0a15fe1e70fb34f9ec411",
"sha256:db04d1de548f7a62d1dd7e7cdf7c22893ee168e22701895067a28a8ed51b3735",
"sha256:dcf71c55ec853826cd70eadb2b6ac62ec577416442ca1e0a97ad875a1b3a0305",
"sha256:de3cc86f4ea8b4c34a6e43a7306c40c1275e52bfa9748d869c6b7d54aa6dad80",
"sha256:deac0a32aec29608eb25d730f4bc5a261a65b6c48ded1ed861d2a1852577c932",
"sha256:e18d92c3e9e22553a73e33784fcb0ed484c9874e9a3e96c16a8d6a1e74a0217b",
"sha256:eb6dfd52063186ac97b4caa25764cdbcdb4b10d97f5c5f66b0fa95052e744eb7",
"sha256:f09960b5bb1017d16c0f9e9f7fc42160a5a49fa1e87a175fd4a2b1a1833ea0af",
"sha256:f1e4f254e9c35d8965d377e065c4a8a55d396fe87c8e7e8429bcfdeeb229bfb3",
"sha256:f32c86dc967ab8c719fd229ce71917caad13cc1e8356ee997bf02c5b368799bf",
"sha256:f50b4663c3e0262c3a361faf440761fbef60ccdde5fe8545689a4b3a3c149fb4",
"sha256:f8e05f5163528962ce1d1806fce763ab893b1c5b7ace0a3538cd81a90622f844",
"sha256:f929f4c9b9a00f3e6cc0587abb95ab9c05681f8b14e0fe1daecfa83ea90f8318",
"sha256:f9e09a1c83521d770d170b3801eea19b89f41ccaa61d53026ed111cb6f088887"
"sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f",
"sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c",
"sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af",
"sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4",
"sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a",
"sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489",
"sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213",
"sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01",
"sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5",
"sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361",
"sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26",
"sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0",
"sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4",
"sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8",
"sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1",
"sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7",
"sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6",
"sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a",
"sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd",
"sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4",
"sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499",
"sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183",
"sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544",
"sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821",
"sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501",
"sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f",
"sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe",
"sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f",
"sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672",
"sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5",
"sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2",
"sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57",
"sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87",
"sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0",
"sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f",
"sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7",
"sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed",
"sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70",
"sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0",
"sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f",
"sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d",
"sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f",
"sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d",
"sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431",
"sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff",
"sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf",
"sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83",
"sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690",
"sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587",
"sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e",
"sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb",
"sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3",
"sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66",
"sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014",
"sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35",
"sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f",
"sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0",
"sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449",
"sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23",
"sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5",
"sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd",
"sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4",
"sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b",
"sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558",
"sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd",
"sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766",
"sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a",
"sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636",
"sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d",
"sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590",
"sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e",
"sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d",
"sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c",
"sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28",
"sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065",
"sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"
],
"index": "pypi",
"version": "==3.9.0"
"version": "==3.9.1"
},
"aiosignal": {
"hashes": [
@ -345,6 +345,14 @@
"index": "pypi",
"version": "==15.0.1"
},
"ffprobe-python": {
"hashes": [
"sha256:b02576228f6da0b0bb973e15e5dadb132013a4ded202dabb89ac77779e9634d6",
"sha256:b1d3e69e65e0814e28575f63a47b836a0b7cc8f1ffedd62d27152d9e4488214a"
],
"index": "pypi",
"version": "==1.0.3"
},
"frozenlist": {
"hashes": [
"sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6",
@ -430,11 +438,11 @@
},
"idna": {
"hashes": [
"sha256:27009fe2735bf8723353582d48575b23c533cc2c2de7b5a68908d91b5eb18d08",
"sha256:79b8f0ac92d2351be5f6122356c9a592c96d81c9a79e4b488bf2a6a15f88057a"
"sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca",
"sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"
],
"markers": "python_version >= '3.5'",
"version": "==3.5"
"version": "==3.6"
},
"multidict": {
"hashes": [

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3
import asyncio
import os
from src.Config import Config
from src.Notifier import Notifier
@ -7,6 +8,8 @@ from src.DownloadQueue import DownloadQueue
from src.Utils import ObjectSerializer
from aiohttp import web
from aiohttp.web import Request, Response
from src.M3u8 import M3u8
from src.Segments import Segments
import socketio
import logging
import caribou
@ -178,6 +181,56 @@ class Main:
return web.Response(text=self.serializer.encode(history))
@self.routes.get(self.config.url_prefix + 'm3u8/{file:.*}')
async def m3u8(request: Request) -> Response:
file: str = request.match_info.get('file')
if not file:
raise web.HTTPBadRequest(reason='file is required')
return web.Response(
text=M3u8(self.config).make_stream(file),
headers={
'Content-Type': 'application/x-mpegURL',
'Cache-Control': 'no-cache',
'Access-Control-Max-Age': "300",
}
)
@self.routes.get(self.config.url_prefix + 'segments/{segment:\d+}/{file:.*}')
async def segments(request: Request) -> Response:
file: str = request.match_info.get('file')
segment: int = request.match_info.get('segment')
sd: int = request.query.get('sd')
vc: int = int(request.query.get('vc', 0))
ac: int = int(request.query.get('ac', 0))
if not file:
raise web.HTTPBadRequest(reason='file is required')
if not segment:
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))),
vconvert=True if vc == 1 else False,
aconvert=True if ac == 1 else False
)
return web.Response(
body=await segmenter.stream(file),
headers={
'Content-Type': 'video/mpegts',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
'X-Accel-Buffering': 'no',
'Access-Control-Allow-Origin': '*',
'Access-Control-Max-Age': "300",
}
)
@self.routes.get(self.config.url_prefix)
async def index(_) -> Response:
return web.FileResponse(os.path.join(

76
app/src/M3u8.py Normal file
View file

@ -0,0 +1,76 @@
import json
import logging
import math
import os
from src.Utils import calcDownloadPath
from ffprobe import FFProbe
from src.Config import Config
class M3u8:
ok_vcodecs: tuple = ('h264', 'x264', 'avc',)
ok_acodecs: tuple = ('aac', 'mp3', 'opus',)
segment_duration: float = 10.000000
config: Config = None
download_path: str = None
def __init__(self, config: Config, segment_duration: float = 6.000000):
self.config = config
self.download_path = self.config.download_path
self.segment_duration = float(segment_duration)
def make_stream(self, file: str):
realFile: str = calcDownloadPath(
basePath=self.download_path,
folder=file,
createPath=False
)
if not os.path.exists(realFile):
raise Exception(f"File {realFile} does not exist.")
metadata = FFProbe(realFile)
# video duration.
duration: float = float(metadata.streams[0].duration)
m3u8 = "#EXTM3U\n"
m3u8 += "#EXT-X-VERSION:3\n"
m3u8 += f"#EXT-X-TARGETDURATION:{int(self.segment_duration)}\n"
m3u8 += "#EXT-X-MEDIA-SEQUENCE:0\n"
m3u8 += "#EXT-X-PLAYLIST-TYPE:VOD\n"
segmentSize: float = '{:.6f}'.format(self.segment_duration)
splits: int = math.ceil(duration / self.segment_duration)
segmentParams = {
}
for stream in metadata.streams:
if stream.codec_type == 'video':
if stream.codec_name not in self.ok_vcodecs:
segmentParams['vc'] = 1
if stream.codec_type == 'audio':
if stream.codec_name not in self.ok_acodecs:
segmentParams['ac'] = 1
for i in range(splits):
if (i + 1) == splits:
segmentParams.update({
'sd': '{:.6f}'.format(duration - (i * self.segment_duration))
})
m3u8 += f"#EXTINF:{segmentParams['sd']}, nodesc\n"
else:
m3u8 += f"#EXTINF:{segmentSize}, nodesc\n"
m3u8 += f"{self.config.url_prefix}segments/{i}/{file}"
if len(segmentParams) > 0:
m3u8 += '?'+'&'.join([f"{key}={value}" for key,
value in segmentParams.items()])
m3u8 += "\n"
m3u8 += "#EXT-X-ENDLIST\n"
# m3u8 = f"{json.dumps(metadata,indent=2,ensure_ascii=False,default=lambda o: o.__dict__)}\n{m3u8}"
return m3u8

115
app/src/Segments.py Normal file
View file

@ -0,0 +1,115 @@
import hashlib
import json
import logging
import math
import os
import subprocess
import tempfile
from src.Utils import calcDownloadPath
from src.Config import Config
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
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:
realFile: str = calcDownloadPath(
basePath=self.download_path,
folder=file,
createPath=False
)
if not os.path.exists(realFile):
raise Exception(f"File {realFile} does not exist.")
tmpDir: str = tempfile.gettempdir()
tmpFile = os.path.join(
tmpDir, f'ytptube_stream.{hashlib.md5(realFile.encode("utf-8")).hexdigest()}')
if not os.path.exists(tmpFile):
os.symlink(realFile, tmpFile)
if self.segment_index == 0:
startTime: float = '{:.6f}'.format(0)
else:
startTime: float = '{:.6f}'.format(
(self.segment_duration * self.segment_index
))
ffmpegCmd = []
ffmpegCmd.append('ffmpeg')
ffmpegCmd.append('-xerror')
ffmpegCmd.append('-hide_banner')
ffmpegCmd.append('-loglevel')
ffmpegCmd.append('error')
ffmpegCmd.append('-ss')
ffmpegCmd.append(str(startTime if startTime else '0.00000'))
ffmpegCmd.append('-t')
ffmpegCmd.append(str('{:.6f}'.format(self.segment_duration)))
ffmpegCmd.append('-copyts')
ffmpegCmd.append('-i')
ffmpegCmd.append(f'file:{tmpFile}')
ffmpegCmd.append('-pix_fmt')
ffmpegCmd.append('yuv420p')
ffmpegCmd.append('-g')
ffmpegCmd.append('52')
ffmpegCmd.append('-map')
ffmpegCmd.append('0:v:0')
ffmpegCmd.append('-strict')
ffmpegCmd.append('-2')
ffmpegCmd.append('-codec:v')
ffmpegCmd.append('libx264' if self.vconvert else 'copy')
if self.vconvert:
ffmpegCmd.append('-crf')
ffmpegCmd.append('23')
ffmpegCmd.append('-preset:v')
ffmpegCmd.append('fast')
ffmpegCmd.append('-level')
ffmpegCmd.append('4.1')
ffmpegCmd.append('-profile:v')
ffmpegCmd.append('baseline')
# audio section.
ffmpegCmd.append('-map')
ffmpegCmd.append('0:a:0')
ffmpegCmd.append('-codec:a')
ffmpegCmd.append('aac' if self.aconvert else 'copy')
if self.aconvert:
ffmpegCmd.append('-b:a')
ffmpegCmd.append('192k')
ffmpegCmd.append('-ar')
ffmpegCmd.append('22050')
ffmpegCmd.append('-ac')
ffmpegCmd.append('2')
ffmpegCmd.append('-sn')
ffmpegCmd.append('-muxdelay')
ffmpegCmd.append('0')
ffmpegCmd.append('-f')
ffmpegCmd.append('mpegts')
ffmpegCmd.append('pipe:1')
logging.info(f'{self.aconvert}, {self.vconvert} '+' '.join(ffmpegCmd))
proc = subprocess.run(ffmpegCmd, stdout=subprocess.PIPE)
return proc.stdout

View file

@ -139,7 +139,7 @@ def getAttributes(vclass: str | type) -> dict:
return attrs
def calcDownloadPath(basePath: str, folder: str = None) -> str:
def calcDownloadPath(basePath: str, folder: str = None, createPath: bool = True) -> str:
"""Calculates download path And prevents directory traversal attacks.
Returns:
@ -155,7 +155,7 @@ def calcDownloadPath(basePath: str, folder: str = None) -> str:
raise Exception(
f'Folder "{folder}" must resolve inside the base download directory "{realBasePath}"')
if not os.path.isdir(download_path):
if not os.path.isdir(download_path) and createPath:
os.makedirs(download_path, exist_ok=True)
return download_path

View file

@ -15,6 +15,7 @@
"@vueuse/core": "^10.6.1",
"bulma": "^0.9.4",
"core-js": "^3.8.3",
"hls.js": "^1.4.12",
"moment": "^2.29.4",
"socket.io-client": "^4.7.2",
"vue": "^3.2.13",
@ -6745,6 +6746,11 @@
"node": "*"
}
},
"node_modules/hls.js": {
"version": "1.4.12",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.4.12.tgz",
"integrity": "sha512-1RBpx2VihibzE3WE9kGoVCtrhhDWTzydzElk/kyRbEOLnb1WIE+3ZabM/L8BqKFTCL3pUy4QzhXgD1Q6Igr1JA=="
},
"node_modules/hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",

View file

@ -16,6 +16,7 @@
"@vueuse/core": "^10.6.1",
"bulma": "^0.9.4",
"core-js": "^3.8.3",
"hls.js": "^1.4.12",
"moment": "^2.29.4",
"socket.io-client": "^4.7.2",
"vue": "^3.2.13",

View file

@ -5,10 +5,11 @@
<PageCompleted :config="config" :completed="completed" @deleteItem="deleteItem" @addItem="addItem"
@playItem="playItem" />
<div class="modal" :class="{ 'is-active': video_link }">
<div class="modal is-active" v-if="video_link">
<div class="modal-background"></div>
<div class="modal-content">
<video :src="video_link" controls autoplay loop preload="auto" class="is-fullwidth"></video>
<VideoPlayer type="default" :link="video_link" :isMuted="false" autoplay="true" :isControls="true"
class="is-fullwidth" />
</div>
<button class="modal-close is-large" aria-label="close" @click="video_link = ''"></button>
</div>
@ -22,6 +23,7 @@ import formAdd from './components/Form-Add'
import DownloadingList from './components/Page-Downloading'
import PageCompleted from './components/Page-Completed'
import PageFooter from './components/Page-Footer'
import VideoPlayer from './components/Video-Player'
import { io } from "socket.io-client";
import { useToast } from 'vue-toastification'
import { useEventBus } from '@vueuse/core'
@ -161,7 +163,7 @@ const addItem = (item) => {
};
const playItem = (item) => {
let baseDir = 'download/';
let baseDir = 'm3u8/';
if (item.folder) {
baseDir += item.folder + '/';
@ -169,5 +171,7 @@ const playItem = (item) => {
video_link.value = config.app.url_host + config.app.url_prefix + baseDir + encodeURIComponent(item.filename);
};
</script>

View file

@ -0,0 +1,81 @@
<template>
<video @pause="pause" @ended="pause" @keyup="changeSpeed" ref="video" :poster="previewImageLink" :controls="isControls"
:title="title">
<source :src="link" type="application/x-mpegURL" />
</video>
</template>
<script setup>
import { onMounted, onUpdated, ref, defineProps, defineEmits } from 'vue'
import Hls from 'hls.js'
const props = defineProps({
previewImageLink: {
type: String,
default: ''
},
link: {
type: String,
default: ''
},
progress: {
type: Number,
default: 0
},
title: {
type: String,
default: ''
},
isMuted: {
type: Boolean,
default: false
},
isControls: {
type: Boolean,
default: true
},
})
const emit = defineEmits(['pause', 'test'])
const video = ref(null)
onMounted(() => {
prepareVideoPlayer()
})
onUpdated(() => {
prepareVideoPlayer()
})
const prepareVideoPlayer = () => {
let hls = new Hls({
debug: false,
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 90,
});
hls.loadSource(props.link)
if (video.value) {
hls.attachMedia(video.value)
video.value.muted = props.isMuted
video.value.currentTime = props.progress
}
}
const pause = () => {
const currentTime = video?.value?.currentTime || 0
emit('pause', currentTime)
}
const changeSpeed = (e) => {
if (e.key === 'w' && video.value) {
video.value.playbackRate = video.value.playbackRate + 0.25
} else if (e.key === 's' && video.value) {
video.value.playbackRate = video.value.playbackRate - 0.25
}
}
</script>

View file

@ -22,8 +22,8 @@ library.add(faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleC
const app = createApp(App);
app.config.globalProperties.capitalize = s => s && s[0].toUpperCase() + s.slice(1);
app.config.globalProperties.makeDownload = (config, item) => {
let baseDir = 'download/';
app.config.globalProperties.makeDownload = (config, item, base = 'download') => {
let baseDir = `${base}/`;
if (item.folder) {
baseDir += item.folder + '/';