Added HLS player.
This commit is contained in:
parent
9c4a9892fb
commit
28989c4cba
11 changed files with 433 additions and 88 deletions
1
Pipfile
1
Pipfile
|
|
@ -9,6 +9,7 @@ aiohttp = "*"
|
|||
yt-dlp = "*"
|
||||
caribou = "*"
|
||||
coloredlogs = "*"
|
||||
ffprobe-python = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
|||
170
Pipfile.lock
generated
170
Pipfile.lock
generated
|
|
@ -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": [
|
||||
|
|
|
|||
53
app/main.py
53
app/main.py
|
|
@ -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
76
app/src/M3u8.py
Normal 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
115
app/src/Segments.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
6
frontend/package-lock.json
generated
6
frontend/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
81
frontend/src/components/Video-Player.vue
Normal file
81
frontend/src/components/Video-Player.vue
Normal 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>
|
||||
|
|
@ -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 + '/';
|
||||
|
|
|
|||
Loading…
Reference in a new issue