Merge pull request #246 from arabcoders/dev

Simplify the codebase and removed all backwards compatibility flags
This commit is contained in:
Abdulmohsen 2025-04-02 00:49:10 +03:00 committed by GitHub
commit 18fe5409ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1306 additions and 1610 deletions

View file

@ -22,6 +22,7 @@
"consoletitle",
"cookiesfrombrowser",
"copyts",
"cronsim",
"daterange",
"dotenv",
"finaldir",

554
Pipfile.lock generated
View file

@ -35,91 +35,91 @@
},
"aiohttp": {
"hashes": [
"sha256:04eb541ce1e03edc1e3be1917a0f45ac703e913c21a940111df73a2c2db11d73",
"sha256:05582cb2d156ac7506e68b5eac83179faedad74522ed88f88e5861b78740dc0e",
"sha256:0a29be28e60e5610d2437b5b2fed61d6f3dcde898b57fb048aa5079271e7f6f3",
"sha256:0b2501f1b981e70932b4a552fc9b3c942991c7ae429ea117e8fba57718cdeed0",
"sha256:0df3788187559c262922846087e36228b75987f3ae31dd0a1e5ee1034090d42f",
"sha256:12c5869e7ddf6b4b1f2109702b3cd7515667b437da90a5a4a50ba1354fe41881",
"sha256:14fc03508359334edc76d35b2821832f092c8f092e4b356e74e38419dfe7b6de",
"sha256:1a7169ded15505f55a87f8f0812c94c9412623c744227b9e51083a72a48b68a5",
"sha256:1c68e41c4d576cd6aa6c6d2eddfb32b2acfb07ebfbb4f9da991da26633a3db1a",
"sha256:20412c7cc3720e47a47e63c0005f78c0c2370020f9f4770d7fc0075f397a9fb0",
"sha256:22a8107896877212130c58f74e64b77f7007cb03cea8698be317272643602d45",
"sha256:28a3d083819741592685762d51d789e6155411277050d08066537c5edc4066e6",
"sha256:2b86efe23684b58a88e530c4ab5b20145f102916bbb2d82942cafec7bd36a647",
"sha256:2d0b46abee5b5737cb479cc9139b29f010a37b1875ee56d142aefc10686a390b",
"sha256:321238a42ed463848f06e291c4bbfb3d15ba5a79221a82c502da3e23d7525d06",
"sha256:3a8a0d127c10b8d89e69bbd3430da0f73946d839e65fec00ae48ca7916a31948",
"sha256:3a8b0321e40a833e381d127be993b7349d1564b756910b28b5f6588a159afef3",
"sha256:3b420d076a46f41ea48e5fcccb996f517af0d406267e31e6716f480a3d50d65c",
"sha256:3b512f1de1c688f88dbe1b8bb1283f7fbeb7a2b2b26e743bb2193cbadfa6f307",
"sha256:413fe39fd929329f697f41ad67936f379cba06fcd4c462b62e5b0f8061ee4a77",
"sha256:41cf0cefd9e7b5c646c2ef529c8335e7eafd326f444cc1cdb0c47b6bc836f9be",
"sha256:4848ae31ad44330b30f16c71e4f586cd5402a846b11264c412de99fa768f00f3",
"sha256:4b0a200e85da5c966277a402736a96457b882360aa15416bf104ca81e6f5807b",
"sha256:4e2e8ef37d4bc110917d038807ee3af82700a93ab2ba5687afae5271b8bc50ff",
"sha256:4edcbe34e6dba0136e4cabf7568f5a434d89cc9de5d5155371acda275353d228",
"sha256:51ba80d473eb780a329d73ac8afa44aa71dfb521693ccea1dea8b9b5c4df45ce",
"sha256:5409a59d5057f2386bb8b8f8bbcfb6e15505cedd8b2445db510563b5d7ea1186",
"sha256:572def4aad0a4775af66d5a2b5923c7de0820ecaeeb7987dcbccda2a735a993f",
"sha256:599b66582f7276ebefbaa38adf37585e636b6a7a73382eb412f7bc0fc55fb73d",
"sha256:59a05cdc636431f7ce843c7c2f04772437dd816a5289f16440b19441be6511f1",
"sha256:602d4db80daf4497de93cb1ce00b8fc79969c0a7cf5b67bec96fa939268d806a",
"sha256:65c75b14ee74e8eeff2886321e76188cbe938d18c85cff349d948430179ad02c",
"sha256:69bb252bfdca385ccabfd55f4cd740d421dd8c8ad438ded9637d81c228d0da49",
"sha256:6d3986112e34eaa36e280dc8286b9dd4cc1a5bcf328a7f147453e188f6fe148f",
"sha256:6dd9766da617855f7e85f27d2bf9a565ace04ba7c387323cd3e651ac4329db91",
"sha256:70ab0f61c1a73d3e0342cedd9a7321425c27a7067bebeeacd509f96695b875fc",
"sha256:749f1eb10e51dbbcdba9df2ef457ec060554842eea4d23874a3e26495f9e87b1",
"sha256:781c8bd423dcc4641298c8c5a2a125c8b1c31e11f828e8d35c1d3a722af4c15a",
"sha256:7e7abe865504f41b10777ac162c727af14e9f4db9262e3ed8254179053f63e6d",
"sha256:7f2dadece8b85596ac3ab1ec04b00694bdd62abc31e5618f524648d18d9dd7fa",
"sha256:86135c32d06927339c8c5e64f96e4eee8825d928374b9b71a3c42379d7437058",
"sha256:8778620396e554b758b59773ab29c03b55047841d8894c5e335f12bfc45ebd28",
"sha256:87f0e003fb4dd5810c7fbf47a1239eaa34cd929ef160e0a54c570883125c4831",
"sha256:8aa5c68e1e68fff7cd3142288101deb4316b51f03d50c92de6ea5ce646e6c71f",
"sha256:8d14e274828561db91e4178f0057a915f3af1757b94c2ca283cb34cbb6e00b50",
"sha256:8d1dd75aa4d855c7debaf1ef830ff2dfcc33f893c7db0af2423ee761ebffd22b",
"sha256:92007c89a8cb7be35befa2732b0b32bf3a394c1b22ef2dff0ef12537d98a7bda",
"sha256:92868f6512714efd4a6d6cb2bfc4903b997b36b97baea85f744229f18d12755e",
"sha256:948abc8952aff63de7b2c83bfe3f211c727da3a33c3a5866a0e2cf1ee1aa950f",
"sha256:95d7787f2bcbf7cb46823036a8d64ccfbc2ffc7d52016b4044d901abceeba3db",
"sha256:997b57e38aa7dc6caab843c5e042ab557bc83a2f91b7bd302e3c3aebbb9042a1",
"sha256:99b8bbfc8111826aa8363442c0fc1f5751456b008737ff053570f06a151650b3",
"sha256:9e73fa341d8b308bb799cf0ab6f55fc0461d27a9fa3e4582755a3d81a6af8c09",
"sha256:a0d2c04a623ab83963576548ce098baf711a18e2c32c542b62322a0b4584b990",
"sha256:a40087b82f83bd671cbeb5f582c233d196e9653220404a798798bfc0ee189fff",
"sha256:ad1f2fb9fe9b585ea4b436d6e998e71b50d2b087b694ab277b30e060c434e5db",
"sha256:b05774864c87210c531b48dfeb2f7659407c2dda8643104fb4ae5e2c311d12d9",
"sha256:b41693b7388324b80f9acfabd479bd1c84f0bc7e8f17bab4ecd9675e9ff9c734",
"sha256:b42dbd097abb44b3f1156b4bf978ec5853840802d6eee2784857be11ee82c6a0",
"sha256:b4e7c7ec4146a94a307ca4f112802a8e26d969018fabed526efc340d21d3e7d0",
"sha256:b59d096b5537ec7c85954cb97d821aae35cfccce3357a2cafe85660cc6295628",
"sha256:b9c60d1de973ca94af02053d9b5111c4fbf97158e139b14f1be68337be267be6",
"sha256:bccd2cb7aa5a3bfada72681bdb91637094d81639e116eac368f8b3874620a654",
"sha256:c32593ead1a8c6aabd58f9d7ee706e48beac796bb0cb71d6b60f2c1056f0a65f",
"sha256:c7571f99525c76a6280f5fe8e194eeb8cb4da55586c3c61c59c33a33f10cfce7",
"sha256:c8b2df9feac55043759aa89f722a967d977d80f8b5865a4153fc41c93b957efc",
"sha256:ca9f835cdfedcb3f5947304e85b8ca3ace31eef6346d8027a97f4de5fb687534",
"sha256:cc9253069158d57e27d47a8453d8a2c5a370dc461374111b5184cf2f147a3cc3",
"sha256:ced66c5c6ad5bcaf9be54560398654779ec1c3695f1a9cf0ae5e3606694a000a",
"sha256:d173c0ac508a2175f7c9a115a50db5fd3e35190d96fdd1a17f9cb10a6ab09aa1",
"sha256:d6edc538c7480fa0a3b2bdd705f8010062d74700198da55d16498e1b49549b9c",
"sha256:daf20d9c3b12ae0fdf15ed92235e190f8284945563c4b8ad95b2d7a31f331cd3",
"sha256:dc311634f6f28661a76cbc1c28ecf3b3a70a8edd67b69288ab7ca91058eb5a33",
"sha256:e2bc827c01f75803de77b134afdbf74fa74b62970eafdf190f3244931d7a5c0d",
"sha256:e365034c5cf6cf74f57420b57682ea79e19eb29033399dd3f40de4d0171998fa",
"sha256:e906da0f2bcbf9b26cc2b144929e88cb3bf943dd1942b4e5af066056875c7618",
"sha256:e9faafa74dbb906b2b6f3eb9942352e9e9db8d583ffed4be618a89bd71a4e914",
"sha256:ec6cd1954ca2bbf0970f531a628da1b1338f594bf5da7e361e19ba163ecc4f3b",
"sha256:f296d637a50bb15fb6a229fbb0eb053080e703b53dbfe55b1e4bb1c5ed25d325",
"sha256:f30fc72daf85486cdcdfc3f5e0aea9255493ef499e31582b34abadbfaafb0965",
"sha256:fe846f0a98aa9913c2852b630cd39b4098f296e0907dd05f6c7b30d911afa4c3"
"sha256:03ce9d2f01aef26cd6aaba2f330273c2364237db2f499b93c3e9f2e249f83cd2",
"sha256:05eea49d598c4ece6f285e00464de206f838b48ff21938d5aa9c394e115945b9",
"sha256:078b1ce274f967951b42a65d5b7ec115b7886343a5271f2eed330458ea87bb48",
"sha256:0987dcf32e4c47f22634d32e4b0ffbc368bbcf2b33b408cd1a3d2dc0a6a5cd34",
"sha256:0c7eba0f90e27ec4af64db051f35387fa17128e6eeb58ee0f2318f2627168cc2",
"sha256:0e97c1e55f6931f07ecaf53aef8352def8386adfd0cd3caa6429cc40e886d6a9",
"sha256:0ec98c22030ea2a430cb11afddda7d4737b7e5c236c704f0d7d15504978598f7",
"sha256:18733fa6bbe44698ff96138c1f1d682bbdf0846250a42c25c108eed328fef0d4",
"sha256:1bee0f9e2d4088b57243d63afcb06256bd2d9ff683080f51e74fa790c8cfedfd",
"sha256:204f6695b47a7d130ddf6680158920825d0d32202a870e0bc56a2ec637935b1a",
"sha256:20dda85988a4f506bf5a457b416b238e32ab944a2deb878ddf0af92df9254a9b",
"sha256:2241d862dc6a3c0c2662a6934e79078d3a1e51a76c0dca5d65b30f3debee6c9b",
"sha256:23397670f3739b6f3c4019da8226190f6cce5ab2008b664ed96a6d1f0fe7f069",
"sha256:23857adc919b64bba8a4db0eccb24e53fcaf85633e690ef1581c5562ed58cae7",
"sha256:269d145c593a65f78fb9a64dece90341561ddb2e91a96d42681132b2f706c42a",
"sha256:291f324f49ecede693dfb4820a412d1388cb10a2214ab60028252b505e105d6f",
"sha256:29cce2a7e009494e675018c0b1819a133befbab8629c797276c5d793bbdf1138",
"sha256:357355c9d51c8b12bbc7de43b27ce4b51f14cce050e00b5a87d0d5527d779395",
"sha256:36a490f1ebe0b982366314c77f02258f87bd5d9bd362839dc6a24188447f37eb",
"sha256:379882ab6a40e6e0879ad8e84dca74ddbadff94af4f314b59b7da89c8463a669",
"sha256:38368a32530dcdeccfa47544cf66724118d9cc8a889c057e116723ab62994380",
"sha256:3cfd9f4aeaec4a75a0b4986a9977ac0a09b3d87ae83415e4b461e86715c80897",
"sha256:41f82df6f0f895f0f843bc86762bea45b4d0fe876de49239ffc89d2365426399",
"sha256:430f9707f0c1239a92bff7769b0db185ef400278dc63c89f88ed1bd5153aab7a",
"sha256:433e7388b3063bba462b3362641988270b087a9ccae22390364f86b37a480c17",
"sha256:43625253e3dc018d34867b70909149f15f29eac0382802afe027f2fbf17bcb9c",
"sha256:437eee9e057a7907b11e4af2b18df56b6c795b28e0a3ac250691936cf6bf40eb",
"sha256:45d8c22c91bdb7417bd4f5119242dbd2e2140c0e9de47342c765f127f897eb57",
"sha256:48d790d05c76b6cb93a1259b44d3f0019b61d507f8ebf0d49da3ef5ac858b05d",
"sha256:4b03093d4140d926965d23497a059ec59c8c07e602d2489ce5fb990f3a897db4",
"sha256:4b85486e8914d4e778343f5322834aada678eaf4c5315e50d41d9b74817ff97b",
"sha256:558de86eef9a886e43c6ae5b75cecdce81203da5832d19d11da8de417967d478",
"sha256:576b56a34d26ea8a8f0e1a30b8a069ba4ab121fb8eb796d1b89fedda7c74c553",
"sha256:5a61df20fa77792e83307e266f76790f7cb67980dd476948948de212ee7ec64c",
"sha256:5b5edd482ff0a8585b3f4e8b3681819447324166a43a5588800a5bca340dbf27",
"sha256:5bd37d615cd26d09321bd0168305f8508778712cf38aeffeed550274fb48a2ee",
"sha256:63f8d6106566f98fcfde7a643c9da52d90679b8592dea76c4adfc3cd5d06d22c",
"sha256:65eb40e74e3126cba185da7a78815cf3a30140932193831b3bfd73c79965c723",
"sha256:6fd82d0b3f73c59c80dade0ca8e0342de1ee261e147140ade65a465be670e83c",
"sha256:71ed71d9431e770550aab27a77ef9d30d33ce6f558ab7818be7205ae6aca635d",
"sha256:73a7f6283634dd30f93b9a67c414c00517869478b50361c503535e075fa07eaf",
"sha256:74afb637cd06760afe0aa55a3ce82178ef4c950be65935add8f3809f701f36ca",
"sha256:79a08d675167c50f41d106d67bbcbc9e86e1b43d305c4b9f982d5c656a94a9ba",
"sha256:7ac49c32901489343b4dab064ab520f6b879a03fb4f9667c84620b66f07bed69",
"sha256:7cf4b2b5a0f7a738ecd759eaeaef800fc7c57683b7be9d8a43fcb86ca62701b4",
"sha256:7e20bd6aa51a5209c9131395e20ce126e8e317c0cf78a8180f026b4d73f018f6",
"sha256:825ec92391e3e4ddda74de79ed0f8b443e9b412a0c9c82618ca2163abd875df5",
"sha256:82ddf7f642b9c0b08063f3cf4e2818b22901bce8ebad05c232d9e295e77436a0",
"sha256:84370ff70c1677ee0c4db40fe2baee6ffc72e9d32def3ff18739c1390c62329f",
"sha256:85d73479b79172e7d667b466c170ca6097a334c09ecd83c95c210546031251b5",
"sha256:8b4d8d78fbd5290204dcf43957a2184acd5cee358f203f24a3a97f7d7984eeb7",
"sha256:8c6e39f0bb2418f839841f92b3cd64077ff5166d724c984ab450ca08d5e51d92",
"sha256:94c552a4864ed292dadf1d341213374284a3af72e49bea02e70ce6f07cb37004",
"sha256:9b97fae7f75a0b666ce4281627856d1b1a85477f26c2e8b266292e770c17df36",
"sha256:a0361cafb50b185356a5f346c169aea1d14783df99e6da714d626b104586e0b5",
"sha256:a3739daa52c0cff42f1c40f63b2fe818fcf41019d84c80a7add3224207a7060f",
"sha256:a3d706afcc808f6add4208dfa13f911fd93c2a3dab6be484fee4fd0602a0867e",
"sha256:a449c48f5b02c0c14f5310881558ca861bff8e00b1f79be4cf6a53f638464c30",
"sha256:a50b86eb9cf74fa5b6f1386e08e1520dcbe83d7dfd4c8bf6f2ca72b03d42e79f",
"sha256:a6d6d86443580f846ec9cf60f899b7cace34411f2ff5c95db5970047195e5bfa",
"sha256:ae3a5d9f2fbe736fec7d24be25c57aa78c2d78d96540439ea68a8abbed9906fc",
"sha256:b9b9a1e592ac8fcc4584baea240e41f77415e0de98932fdf19565aa3b6a02d0b",
"sha256:be11989cbc0728f81c0d022cef140ef8adb20d3012ad8f0ac61853bef571eb52",
"sha256:c28cbae1ce76dc48d0fcccb045ac21d00dcc1b306bb745910cf35585ce89404e",
"sha256:c2de66177e087999568638c02639cf0248011b5c7fca69b006947153c534fcab",
"sha256:c37aa3eb8eb92f3793f0c1e73f212a76cbc8d363e9990df54b5b7099f75ce740",
"sha256:c930064b79cc0eb63678e376b819d546b0e2360264cd7544c32119496f646f35",
"sha256:c982b2cbd7b8f4b31e9faf2de09e22b060a6cd0a693f20892dda41a8fb0f46ef",
"sha256:d8370d31e6d8ecccd97cd791c466d0acb56527df51b0c105d7ea54c7fcc0f75b",
"sha256:d99981304065f4ea407dd7495f74f8b8c10f0e26733f8a47dc174ece73744d14",
"sha256:d9c8417a24063f35b526f8bf14f6f4bdea6f3f49850457337b6ea928901adbbc",
"sha256:e13767424425bb607931a0b9e703b95d2693650011ef8f0166b4cd80066b66b9",
"sha256:e792131352418dde7b0c598e217e89ecf6a26889f46f35f910e5544ffdebf3ae",
"sha256:eba325409a0f990f9b43ed18916cbf5b9779bc4c979b8887c444e7be9c38ccca",
"sha256:ec3dd04138bd30e6a3403dbd3ab5a5ccfb501597c5a95196cd816936ed55b777",
"sha256:edcddb97574402ff7481bc6f70819ba863e77b0be58a840ed5f59d52d2f20a71",
"sha256:f00e7540a60460fbeaffff4a7dcf72fe8b710f8280a542a4d798273787c64e72",
"sha256:f1b6c639750bf2a228957e25fcab7a7ff11987c543d70bf73223369f0d7bdb27",
"sha256:f1e0369f0dc8c895e718ce37147f56d46142d37596be183ab7a34192c5e6e4c5",
"sha256:f30e6980ec5d6ad815a233e19e39fe27ea94b1081c31c8aa1df1b629da3737b8",
"sha256:fc270fe480e7e425c45054543f58510fe649f70b77f514171909bbfe585105c0"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==3.11.14"
"version": "==3.11.15"
},
"aiosignal": {
"hashes": [
@ -625,101 +625,101 @@
},
"multidict": {
"hashes": [
"sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8",
"sha256:042028348dc5a1f2be6c666437042a98a5d24cee50380f4c0902215e5ec41844",
"sha256:05fefbc3cddc4e36da209a5e49f1094bbece9a581faa7f3589201fd95df40e5d",
"sha256:063be88bd684782a0715641de853e1e58a2f25b76388538bd62d974777ce9bc2",
"sha256:07bfa8bc649783e703263f783f73e27fef8cd37baaad4389816cf6a133141331",
"sha256:08549895e6a799bd551cf276f6e59820aa084f0f90665c0f03dd3a50db5d3c48",
"sha256:095a2eabe8c43041d3e6c2cb8287a257b5f1801c2d6ebd1dd877424f1e89cf29",
"sha256:0b183a959fb88ad1be201de2c4bdf52fa8e46e6c185d76201286a97b6f5ee65c",
"sha256:0c383d28857f66f5aebe3e91d6cf498da73af75fbd51cedbe1adfb85e90c0460",
"sha256:0d57a01a2a9fa00234aace434d8c131f0ac6e0ac6ef131eda5962d7e79edfb5b",
"sha256:0dc25a3293c50744796e87048de5e68996104d86d940bb24bc3ec31df281b191",
"sha256:0e5a644e50ef9fb87878d4d57907f03a12410d2aa3b93b3acdf90a741df52c49",
"sha256:0f249badb360b0b4d694307ad40f811f83df4da8cef7b68e429e4eea939e49dd",
"sha256:0f74f2fc51555f4b037ef278efc29a870d327053aba5cb7d86ae572426c7cccc",
"sha256:125dd82b40f8c06d08d87b3510beaccb88afac94e9ed4a6f6c71362dc7dbb04b",
"sha256:13551d0e2d7201f0959725a6a769b6f7b9019a168ed96006479c9ac33fe4096b",
"sha256:14ed9ed1bfedd72a877807c71113deac292bf485159a29025dfdc524c326f3e1",
"sha256:163f4604e76639f728d127293d24c3e208b445b463168af3d031b92b0998bb90",
"sha256:19e2819b0b468174de25c0ceed766606a07cedeab132383f1e83b9a4e96ccb4f",
"sha256:1e2a2193d3aa5cbf5758f6d5680a52aa848e0cf611da324f71e5e48a9695cc86",
"sha256:1f3c099d3899b14e1ce52262eb82a5f5cb92157bb5106bf627b618c090a0eadc",
"sha256:214207dcc7a6221d9942f23797fe89144128a71c03632bf713d918db99bd36de",
"sha256:2325105e16d434749e1be8022f942876a936f9bece4ec41ae244e3d7fae42aaf",
"sha256:2529ddbdaa424b2c6c2eb668ea684dd6b75b839d0ad4b21aad60c168269478d7",
"sha256:256d431fe4583c5f1e0f2e9c4d9c22f3a04ae96009b8cfa096da3a8723db0a16",
"sha256:25bb96338512e2f46f615a2bb7c6012fe92a4a5ebd353e5020836a7e33120349",
"sha256:2e87f1926e91855ae61769ba3e3f7315120788c099677e0842e697b0bfb659f2",
"sha256:2fc6af8e39f7496047c7876314f4317736eac82bf85b54c7c76cf1a6f8e35d98",
"sha256:3157126b028c074951839233647bd0e30df77ef1fedd801b48bdcad242a60f4e",
"sha256:32c9b4878f48be3e75808ea7e499d6223b1eea6d54c487a66bc10a1871e3dc6a",
"sha256:32ed748ff9ac682eae7859790d3044b50e3076c7d80e17a44239683769ff485e",
"sha256:3501621d5e86f1a88521ea65d5cad0a0834c77b26f193747615b7c911e5422d2",
"sha256:437c33561edb6eb504b5a30203daf81d4a9b727e167e78b0854d9a4e18e8950b",
"sha256:48d39b1824b8d6ea7de878ef6226efbe0773f9c64333e1125e0efcfdd18a24c7",
"sha256:4ac3fcf9a2d369bd075b2c2965544036a27ccd277fc3c04f708338cc57533081",
"sha256:4ccfd74957ef53fa7380aaa1c961f523d582cd5e85a620880ffabd407f8202c0",
"sha256:52b05e21ff05729fbea9bc20b3a791c3c11da61649ff64cce8257c82a020466d",
"sha256:5389445f0173c197f4a3613713b5fb3f3879df1ded2a1a2e4bc4b5b9c5441b7e",
"sha256:5c5e7d2e300d5cb3b2693b6d60d3e8c8e7dd4ebe27cd17c9cb57020cac0acb80",
"sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530",
"sha256:5dd7106d064d05896ce28c97da3f46caa442fe5a43bc26dfb258e90853b39b44",
"sha256:5f8cb1329f42fadfb40d6211e5ff568d71ab49be36e759345f91c69d1033d633",
"sha256:61d5541f27533f803a941d3a3f8a3d10ed48c12cf918f557efcbf3cd04ef265c",
"sha256:639556758c36093b35e2e368ca485dada6afc2bd6a1b1207d85ea6dfc3deab27",
"sha256:641cf2e3447c9ecff2f7aa6e9eee9eaa286ea65d57b014543a4911ff2799d08a",
"sha256:6aed763b6a1b28c46c055692836879328f0b334a6d61572ee4113a5d0c859872",
"sha256:6e2a2d6749e1ff2c9c76a72c6530d5baa601205b14e441e6d98011000f47a7ac",
"sha256:7243c5a6523c5cfeca76e063efa5f6a656d1d74c8b1fc64b2cd1e84e507f7e2a",
"sha256:76b34c12b013d813e6cb325e6bd4f9c984db27758b16085926bbe7ceeaace626",
"sha256:781b5dd1db18c9e9eacc419027b0acb5073bdec9de1675c0be25ceb10e2ad133",
"sha256:7c611345bbe7cb44aabb877cb94b63e86f2d0db03e382667dbd037866d44b4f8",
"sha256:83b78c680d4b15d33042d330c2fa31813ca3974197bddb3836a5c635a5fd013f",
"sha256:84e87a7d75fa36839a3a432286d719975362d230c70ebfa0948549cc38bd5b46",
"sha256:89b3857652183b8206a891168af47bac10b970d275bba1f6ee46565a758c078d",
"sha256:8cd1a0644ccaf27e9d2f6d9c9474faabee21f0578fe85225cc5af9a61e1653df",
"sha256:8de4d42dffd5ced9117af2ce66ba8722402541a3aa98ffdf78dde92badb68932",
"sha256:94a7bb972178a8bfc4055db80c51efd24baefaced5e51c59b0d598a004e8305d",
"sha256:98aa8325c7f47183b45588af9c434533196e241be0a4e4ae2190b06d17675c02",
"sha256:9e658d1373c424457ddf6d55ec1db93c280b8579276bebd1f72f113072df8a5d",
"sha256:9f49585f4abadd2283034fc605961f40c638635bc60f5162276fec075f2e37a4",
"sha256:9f6cad071960ba1914fa231677d21b1b4a3acdcce463cee41ea30bc82e6040cf",
"sha256:a0cc398350ef31167e03f3ca7c19313d4e40a662adcb98a88755e4e861170bdd",
"sha256:a1133414b771619aa3c3000701c11b2e4624a7f492f12f256aedde97c28331a2",
"sha256:a33273a541f1e1a8219b2a4ed2de355848ecc0254264915b9290c8d2de1c74e1",
"sha256:a3c0ff89fe40a152e77b191b83282c9664357dce3004032d42e68c514ceff27e",
"sha256:a49994481b99cd7dedde07f2e7e93b1d86c01c0fca1c32aded18f10695ae17eb",
"sha256:abf5b17bc0cf626a8a497d89ac691308dbd825d2ac372aa990b1ca114e470151",
"sha256:ac380cacdd3b183338ba63a144a34e9044520a6fb30c58aa14077157a033c13e",
"sha256:ad81012b24b88aad4c70b2cbc2dad84018783221b7f923e926f4690ff8569da3",
"sha256:b2c00ad31fbc2cbac85d7d0fcf90853b2ca2e69d825a2d3f3edb842ef1544a2c",
"sha256:b4c153863dd6569f6511845922c53e39c8d61f6e81f228ad5443e690fca403de",
"sha256:b4f3d66dd0354b79761481fc15bdafaba0b9d9076f1f42cc9ce10d7fcbda205a",
"sha256:b99aac6bb2c37db336fa03a39b40ed4ef2818bf2dfb9441458165ebe88b793af",
"sha256:b9f6392d98c0bd70676ae41474e2eecf4c7150cb419237a41f8f96043fcb81d1",
"sha256:c537da54ce4ff7c15e78ab1292e5799d0d43a2108e006578a57f531866f64025",
"sha256:ca23db5fb195b5ef4fd1f77ce26cadefdf13dba71dab14dadd29b34d457d7c44",
"sha256:cc826b9a8176e686b67aa60fd6c6a7047b0461cae5591ea1dc73d28f72332a8a",
"sha256:cca83a629f77402cfadd58352e394d79a61c8015f1694b83ab72237ec3941f88",
"sha256:cf8d370b2fea27fb300825ec3984334f7dd54a581bde6456799ba3776915a656",
"sha256:d1175b0e0d6037fab207f05774a176d71210ebd40b1c51f480a04b65ec5c786d",
"sha256:d1996ee1330e245cd3aeda0887b4409e3930524c27642b046e4fae88ffa66c5e",
"sha256:d5a36953389f35f0a4e88dc796048829a2f467c9197265504593f0e420571547",
"sha256:da51d8928ad8b4244926fe862ba1795f0b6e68ed8c42cd2f822d435db9c2a8f4",
"sha256:e16e7297f29a544f49340012d6fc08cf14de0ab361c9eb7529f6a57a30cbfda1",
"sha256:e25b11a0417475f093d0f0809a149aff3943c2c56da50fdf2c3c88d57fe3dfbd",
"sha256:e4371591e621579cb6da8401e4ea405b33ff25a755874a3567c4075ca63d56e2",
"sha256:e653d36b1bf48fa78c7fcebb5fa679342e025121ace8c87ab05c1cefd33b34fc",
"sha256:e7d91a230c7f8af86c904a5a992b8c064b66330544693fd6759c3d6162382ecf",
"sha256:e851e6363d0dbe515d8de81fd544a2c956fdec6f8a049739562286727d4a00c3",
"sha256:ef7d48207926edbf8b16b336f779c557dd8f5a33035a85db9c4b0febb0706817",
"sha256:f7716f7e7138252d88607228ce40be22660d6608d20fd365d596e7ca0738e019",
"sha256:facaf11f21f3a4c51b62931feb13310e6fe3475f85e20d9c9fdce0d2ea561b87"
"sha256:02a239ad4e35d3c033e701bc8b7beb05013fc6617b13948497cf50e9e9c12f7f",
"sha256:091f3dfe63e9f48ae6c7da8d638e8d8e050ff11e348f69bafc20e65d11f7aa48",
"sha256:0a1a5f50ccc57a317db68582411ce6aca076ee3bc386290b416a5783e807afa1",
"sha256:0ba7d12ff77cadb59519aba674cac3a0b16d0fc5abaa3ef79b5ff3806e52e991",
"sha256:0c4a6ec469c30f4162745d6485b778432c497032b8a2cb3a0332eea9d3b6aef6",
"sha256:150cc33d5d972c1bd7b0e5c446cccfa4dc6b8e185ef54dab3327db101149f797",
"sha256:162af9c2e0743465a47e2b32d9b0a7c260b7843629b5e6f0a8a92819b5a40d27",
"sha256:168a02178b7e980899f3475ff008436eaab035d50e39cb8f7de641bbe9bbc3a6",
"sha256:1aa6e1a605c602c119a22368f7d3b9dfb102a7cd69edae5a89e55da34fd64c2b",
"sha256:1b293804f4b53be6297bc2afdeaf15aff76c1b6be69f3a3da785143eebdfb656",
"sha256:1c8836280abc310ea6260dac93126645d21284885008c317c2ac4281a90f1398",
"sha256:2264957a0e0fb92d8d538a27738178c48628c65ef0ec6b068d64d2e5d11cc00d",
"sha256:257bd1540b7c32982b17e86f024985b9c591da8542a3fb350ed7dad42d725998",
"sha256:299070f503a9a99e4af38d07da784ced28377cc62b678084b9e2e48fa51c57d3",
"sha256:2a83896925e3a2ddec736c48255572e6f15a95339024c02b800dab28e63461a3",
"sha256:2cef02d3878804909283549bc10d4789a14c610fcd286f17cd94a195b21fe469",
"sha256:2cf3e0781febf9f093eff3eca2d6dd7954ef2969ff46f6cd95173a4db8397fd8",
"sha256:2e87a8635a7db577617b50bd2f2080744ed20e556750b97e4f9988e6d20d3941",
"sha256:31c68e103b253643cf9b4704f5d655997c116626b20ef3bb953992b7f447485d",
"sha256:34ef116022119d3053ecce8ba62109c8b54a650a537b183e79de1db4078894a8",
"sha256:38e60e400e07ba678a755cc89b09955a49acb0188d47aee2829ed4efc1ccb053",
"sha256:3ac6ea86ac161198653dba8f0af047d7b718318ba6d6bf4c719bc288ac21017b",
"sha256:3ad737617d38c55c0b72cd3ea01bf5cbab0d75123de5e52c39643ddc6465cb5f",
"sha256:3ae6ea8e610143348199c1db9fea5392f6b11a0e37ac8e8e7cae9c408625c5d0",
"sha256:3bcb8cdfeb08cef0138d696e52ec08fffaf009ef4b1c7c5a40340af672bd9b60",
"sha256:3d783be54d076843f58bf061fdaf1071789fb924fb35a0eb84dbc2c8b68499a2",
"sha256:3da30fb560b37cede31306b890c982213a23fa7335d368cdc16cf7034170860b",
"sha256:430cb9ee0cf2d5a2fa91e78f306544352009dbda9ebb4bd04c5785e67fc04953",
"sha256:43ad68da1385738a1e96a188029f092e88381352e697a0687784191c1d45845b",
"sha256:478d29a270785fc58632c9d1e0f59f595d3fb06c5e63ac117136dba2d0e5f4f9",
"sha256:4c3da8aa4b28f83382f0dee288d030eb0fa212df9632dc2fe4e4fc2a8e2577a4",
"sha256:560af476d03256a65bd4ff05c83e7869ec3c2b40579d4ed1ac74075ef19e02bf",
"sha256:56225de73d69f5ee6b783648eb1936e1bbe874a529cb1e15d64038904c54efb2",
"sha256:5737f40ffb1926b8bf38d32fdac48a27c441b757f1bf44cbaa100dafef049a01",
"sha256:5a1996d963016e6486b6a672f64f868e6b4e7e9e2caf710994df11b04790903e",
"sha256:5a922601cb94f427dd96eec3a7c776537ce7490f2beb69e88a81859e537343e4",
"sha256:5bd70d75b4bbfc0a26fdbfc72e260b70090637020b0d7d27eb26c7ae5edf16da",
"sha256:5e0db6ea39ac96ae2ccc582291633ee55ae93c9cc6dca1f7ab4dd9f0b7e08f8a",
"sha256:5eb2a8415a891564054447a327a7ef7ec90e69e0e63d85d1ffb03f82e102c740",
"sha256:6193564d51d02a4aa6fc60bb40f04b65840e70e124282a7337cd94a9ed1b0b81",
"sha256:65e763769cec2fa9c79b70555e3f2a7903747d127ab14ec9eaa5f00763bb4152",
"sha256:66c108d8e02da2febb0aa7d7002e14c4a0571460993c9edf8249393cdae7eeef",
"sha256:67caf9435b1f0115042cbc37e9d60475891b2d9b2a711ade0876580da2a5e0df",
"sha256:6989ea9326f9a9171edc920a459003b1deffc6cb48605b8f2610846c7fbd201a",
"sha256:69983ee3e649e41dc41ddea68f6c2ab64d7f19fcab4e21a72a1accf6482c5605",
"sha256:6b25953a1d6a97746becbd663b49e3b436a5001c995a62662d65835a2ba996a7",
"sha256:6b7e30e4246c2cd5e6c2c907486d235e4f3e8a39979744e9e0b8090629c62da4",
"sha256:6be1182387fb9a95fd8a8cbfab2f2b61d61ea0ab4699780d4300ea207c8f8d71",
"sha256:6befa3c621caf26f5affc6121bf863b8f049f862a7a10aabb0d19cce34c6e363",
"sha256:6fbe184451480c17f1f8dac160c9f3f6d243010fdb8416de4d3d7ee69ea65aa4",
"sha256:7074f10bf324e4d38208e1bcc2b6abd87901675b1a567507035ff1e7403378c0",
"sha256:71a8ce430f6c341725aefc0031626c484e0858fd95d1bf3625e0d27919d8edca",
"sha256:71d92e846541669ae1d11e00906c408c1dc9890196e13f11d4d62bd235ac9ddb",
"sha256:72574693fad34d075a54fa771984a1a8809a9eff880b731152078e81c7d8c80f",
"sha256:72a1159144e4ad7d152b2c3f75bed8c9b60ad06e9da322e1965981f8371c0dee",
"sha256:72d15a61b76fa4b814390c21defd772893d1152157e3297e6afb328ef32d1711",
"sha256:74e45386528db2d9346618276e8746507ec6ccb91512c5f02d2049ccafb0576e",
"sha256:7594756e9607e5ffbc16c7f3b1715c0f7224a82ac389f0087fa5faaa65fb96e2",
"sha256:767ed12b1662fac32d84eb43707e4f778b12554fc12ce3c8f7793a02111a9e32",
"sha256:77e0406dc247851a1e250fdcfd46d13cdecc2ac5b8d1d038dea216da2e7fa9b7",
"sha256:79658460926a1c48b2ac10196a673cc7d81857dc268ca593a66fd661ed1b77ea",
"sha256:7bf0d43f2c56b600c6895c25a420e4b46c6a5acf91d6c9a6300713496f70e63b",
"sha256:80935f26af0eec490c4e52381a28f39b08c2bc4ef4562448890027e4a4cfa3a4",
"sha256:82bd76a12588bd2bb27965aaf9fcff2258ffb03c9a290368288ed4571ed539ab",
"sha256:864684b36e0488715aac531785abe7514e872bfe83ecc768828e9ddaadeed320",
"sha256:883c07b404229e98d30b1ef64f07d4e3422d431ecd727c2ebba8549f70b2ba16",
"sha256:88548dec2f6f69e547252fe3113bf1b3b08c0879f5b44633927be07ed18c5cc0",
"sha256:8895999e0de5ddddddd146bb1bfc0316af808580637fea158873b84785f7a9e9",
"sha256:8ab4ea5f49166b990411c522c1f5f901014932ead15a463616ec93e10fff2c05",
"sha256:904895323a59dd50387cddd1337c3eac48ec0fe6cb4d2e4106dc1a3b80091727",
"sha256:90c3c60985e7da13e44aeaaf2e1c10fe1b7825788a18c82b0f9eaeb6c4e9d9c6",
"sha256:9584441b7a603a12aa382adf8c093ddc5a22328a108dabc6a4a112fa5b4facae",
"sha256:9ca652d9c6f68535537d75502b549ed0ca07fa6d3908f84f29f92148ec7310f2",
"sha256:9efe23b3d0671eeb8b1e70791612538dcdc1b39275563ca78f338aa0ce9619d2",
"sha256:a466c14574152b7caed5d76f1e46ae2963d33f5b0eb2dd915fa33870183b0f26",
"sha256:a48fd39ba69f399f39b911885469bdfedd1de4a095146211e39e8b5db0e3a937",
"sha256:a716344c6e92551bdf842b999c7d7b34a13e32facf3e6c5942690c9883d45e3a",
"sha256:b6df1b707adab4858dfaa74877f60f07d9d6be01a57015cf4e62aa8f00f3576b",
"sha256:b7d3053742a9a559dda8598a52e0c1bcbd18258cc199cba52137ce8c8e92c537",
"sha256:b95d96a0640decaa24cd9cf386fd4d8a96c973aafa42dd9c65609f9f0d66cc34",
"sha256:bc15e81587b652bbeba9d4d7d3707bcaaa1d7b4aab2024c14be477662003a211",
"sha256:be034aa2841c57ea43a55bc123d8f3f41cc2d3d102a22390c863791ba6bf23f1",
"sha256:c5223ab45639ce93c7233d518f6c3199422b49dbd0ebfb1d7917b5da2636712e",
"sha256:d9c2b1ca98e5454b78cd434f29fc33eb8f8a2f343efc5f975225d92070b9f7f6",
"sha256:db61c6ae9ee7268dc69a078ea22deaaf861350ad2e4c194c70798b8ec9789131",
"sha256:dc4e82c5db4e65703c842b0947699dd958a7262a8b854d1c19bbfe2d27be9333",
"sha256:e18db3b38ac30d8cc1ddbdfc7f6d3d814c1abb8936c57bd1c09c5da02873c8c4",
"sha256:e31697466812ff1cdabd7a9db7fb7f37d319e58e4c8a2ce1065012c1a5cfebde",
"sha256:eeca775446b00826ec8ca34eed8c902c6a1ae7dc42d78094b1f73ecd38d1dcf8",
"sha256:fa476e5f763f59270bda050695e22078b2461dc14bc62a0d27756c5906bca86c",
"sha256:fb3269a3641b868ab7856c3cd939782ee1c802fa120b470b597286d2f9e93aee",
"sha256:fd289ef26241bc2c0b7b047417a33786790bf2a01f6c92d9688eb2693c9116df"
],
"markers": "python_version >= '3.9'",
"version": "==6.2.0"
"version": "==6.3.0"
},
"mutagen": {
"hashes": [
@ -732,107 +732,107 @@
},
"propcache": {
"hashes": [
"sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e",
"sha256:03acd9ff19021bd0567582ac88f821b66883e158274183b9e5586f678984f8fe",
"sha256:03c091bb752349402f23ee43bb2bff6bd80ccab7c9df6b88ad4322258d6960fc",
"sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829",
"sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863",
"sha256:119e244ab40f70a98c91906d4c1f4c5f2e68bd0b14e7ab0a06922038fae8a20f",
"sha256:11ae6a8a01b8a4dc79093b5d3ca2c8a4436f5ee251a9840d7790dccbd96cb649",
"sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6",
"sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c",
"sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a",
"sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c",
"sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545",
"sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e",
"sha256:26a67e5c04e3119594d8cfae517f4b9330c395df07ea65eab16f3d559b7068fe",
"sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075",
"sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57",
"sha256:2d913d36bdaf368637b4f88d554fb9cb9d53d6920b9c5563846555938d5450bf",
"sha256:3302c5287e504d23bb0e64d2a921d1eb4a03fb93a0a0aa3b53de059f5a5d737d",
"sha256:36ca5e9a21822cc1746023e88f5c0af6fce3af3b85d4520efb1ce4221bed75cc",
"sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0",
"sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1",
"sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64",
"sha256:42924dc0c9d73e49908e35bbdec87adedd651ea24c53c29cac103ede0ea1d340",
"sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db",
"sha256:46ed02532cb66612d42ae5c3929b5e98ae330ea0f3900bc66ec5f4862069519b",
"sha256:49ea05212a529c2caffe411e25a59308b07d6e10bf2505d77da72891f9a05641",
"sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626",
"sha256:507c5357a8d8b4593b97fb669c50598f4e6cccbbf77e22fa9598aba78292b4d7",
"sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92",
"sha256:58e6d2a5a7cb3e5f166fd58e71e9a4ff504be9dc61b88167e75f835da5764d07",
"sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e",
"sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787",
"sha256:5fa159dcee5dba00c1def3231c249cf261185189205073bde13797e57dd7540a",
"sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810",
"sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d",
"sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0",
"sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b",
"sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043",
"sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3",
"sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7",
"sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d",
"sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf",
"sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138",
"sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c",
"sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d",
"sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46",
"sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6",
"sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa",
"sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e",
"sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05",
"sha256:7cedd25e5f678f7738da38037435b340694ab34d424938041aa630d8bac42663",
"sha256:7e2e068a83552ddf7a39a99488bcba05ac13454fb205c847674da0352602082f",
"sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c",
"sha256:8526b0941ec5a40220fc4dfde76aed58808e2b309c03e9fa8e2260083ef7157f",
"sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7",
"sha256:8cb625bcb5add899cb8ba7bf716ec1d3e8f7cdea9b0713fa99eadf73b6d4986f",
"sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7",
"sha256:8ee1983728964d6070ab443399c476de93d5d741f71e8f6e7880a065f878e0b9",
"sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667",
"sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86",
"sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51",
"sha256:9ecde3671e62eeb99e977f5221abcf40c208f69b5eb986b061ccec317c82ebd0",
"sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a",
"sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c",
"sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568",
"sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af",
"sha256:a7080b0159ce05f179cfac592cda1a82898ca9cd097dacf8ea20ae33474fbb25",
"sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5",
"sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe",
"sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf",
"sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9",
"sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf",
"sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767",
"sha256:be90c94570840939fecedf99fa72839aed70b0ced449b415c85e01ae67422c90",
"sha256:bf0d9a171908f32d54f651648c7290397b8792f4303821c42a74e7805bfb813c",
"sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d",
"sha256:bf4298f366ca7e1ad1d21bbb58300a6985015909964077afd37559084590c929",
"sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e",
"sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32",
"sha256:cd54895e4ae7d32f1e3dd91261df46ee7483a735017dc6f987904f194aa5fd14",
"sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8",
"sha256:d383bf5e045d7f9d239b38e6acadd7b7fdf6c0087259a84ae3475d18e9a2ae8b",
"sha256:d3e7420211f5a65a54675fd860ea04173cde60a7cc20ccfbafcccd155225f8bc",
"sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa",
"sha256:df03cd88f95b1b99052b52b1bb92173229d7a674df0ab06d2b25765ee8404bce",
"sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b",
"sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e",
"sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf",
"sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9",
"sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac",
"sha256:ed7161bccab7696a473fe7ddb619c1d75963732b37da4618ba12e60899fefe4f",
"sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374",
"sha256:ee25f1ac091def37c4b59d192bbe3a206298feeb89132a470325bf76ad122a1e",
"sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d",
"sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e",
"sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121",
"sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5",
"sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54"
"sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e",
"sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b",
"sha256:069e7212890b0bcf9b2be0a03afb0c2d5161d91e1bf51569a64f629acc7defbf",
"sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b",
"sha256:0c3c3a203c375b08fd06a20da3cf7aac293b834b6f4f4db71190e8422750cca5",
"sha256:0c86e7ceea56376216eba345aa1fc6a8a6b27ac236181f840d1d7e6a1ea9ba5c",
"sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c",
"sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a",
"sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf",
"sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8",
"sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5",
"sha256:1f6cc0ad7b4560e5637eb2c994e97b4fa41ba8226069c9277eb5ea7101845b42",
"sha256:27c6ac6aa9fc7bc662f594ef380707494cb42c22786a558d95fcdedb9aa5d035",
"sha256:2d219b0dbabe75e15e581fc1ae796109b07c8ba7d25b9ae8d650da582bed01b0",
"sha256:2fce1df66915909ff6c824bbb5eb403d2d15f98f1518e583074671a30fe0c21e",
"sha256:319fa8765bfd6a265e5fa661547556da381e53274bc05094fc9ea50da51bfd46",
"sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d",
"sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24",
"sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d",
"sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de",
"sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf",
"sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7",
"sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371",
"sha256:47ef24aa6511e388e9894ec16f0fbf3313a53ee68402bc428744a367ec55b833",
"sha256:4cf9e93a81979f1424f1a3d155213dc928f1069d697e4353edb8a5eba67c6259",
"sha256:4d0dfdd9a2ebc77b869a0b04423591ea8823f791293b527dc1bb896c1d6f1136",
"sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25",
"sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005",
"sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef",
"sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7",
"sha256:5b9145c35cc87313b5fd480144f8078716007656093d23059e8993d3a8fa730f",
"sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53",
"sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0",
"sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb",
"sha256:603f1fe4144420374f1a69b907494c3acbc867a581c2d49d4175b0de7cc64566",
"sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a",
"sha256:64a956dff37080b352c1c40b2966b09defb014347043e740d420ca1eb7c9b908",
"sha256:668ddddc9f3075af019f784456267eb504cb77c2c4bd46cc8402d723b4d200bf",
"sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458",
"sha256:6f173bbfe976105aaa890b712d1759de339d8a7cef2fc0a1714cc1a1e1c47f64",
"sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9",
"sha256:730178f476ef03d3d4d255f0c9fa186cb1d13fd33ffe89d39f2cda4da90ceb71",
"sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b",
"sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5",
"sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037",
"sha256:82de5da8c8893056603ac2d6a89eb8b4df49abf1a7c19d536984c8dd63f481d5",
"sha256:83be47aa4e35b87c106fc0c84c0fc069d3f9b9b06d3c494cd404ec6747544894",
"sha256:8638f99dca15b9dff328fb6273e09f03d1c50d9b6512f3b65a4154588a7595fe",
"sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757",
"sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3",
"sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976",
"sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6",
"sha256:916cd229b0150129d645ec51614d38129ee74c03293a9f3f17537be0029a9641",
"sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7",
"sha256:967a8eec513dbe08330f10137eacb427b2ca52118769e82ebcfcab0fba92a649",
"sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120",
"sha256:9979643ffc69b799d50d3a7b72b5164a2e97e117009d7af6dfdd2ab906cb72cd",
"sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40",
"sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e",
"sha256:9e64e948ab41411958670f1093c0a57acfdc3bee5cf5b935671bbd5313bcf229",
"sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c",
"sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7",
"sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111",
"sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654",
"sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f",
"sha256:a461959ead5b38e2581998700b26346b78cd98540b5524796c175722f18b0294",
"sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da",
"sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f",
"sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7",
"sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0",
"sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073",
"sha256:b303b194c2e6f171cfddf8b8ba30baefccf03d36a4d9cab7fd0bb68ba476a3d7",
"sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11",
"sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f",
"sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27",
"sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70",
"sha256:c66d8ccbc902ad548312b96ed8d5d266d0d2c6d006fd0f66323e9d8f2dd49be7",
"sha256:cd6a55f65241c551eb53f8cf4d2f4af33512c39da5d9777694e9d9c60872f519",
"sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5",
"sha256:d4e89cde74154c7b5957f87a355bb9c8ec929c167b59c83d90654ea36aeb6180",
"sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f",
"sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee",
"sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18",
"sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815",
"sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e",
"sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a",
"sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7",
"sha256:ed5f6d2edbf349bd8d630e81f474d33d6ae5d07760c44d33cd808e2f5c8f4ae6",
"sha256:ef2e4e91fb3945769e14ce82ed53007195e616a63aa43b40fb7ebaaf907c8d4c",
"sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc",
"sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8",
"sha256:f27785888d2fdd918bc36de8b8739f2d6c791399552333721b58193f68ea3e98",
"sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256",
"sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5",
"sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744",
"sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723",
"sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277",
"sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5"
],
"markers": "python_version >= '3.9'",
"version": "==0.3.0"
"version": "==0.3.1"
},
"pycparser": {
"hashes": [
@ -1187,11 +1187,11 @@
},
"typing-extensions": {
"hashes": [
"sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d",
"sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"
"sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b",
"sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5"
],
"markers": "python_version < '3.13'",
"version": "==4.12.2"
"version": "==4.13.0"
},
"tzlocal": {
"hashes": [

View file

@ -8,6 +8,7 @@ from sqlite3 import Connection
from .config import Config
from .Download import Download
from .ItemDTO import ItemDTO
from .Utils import clean_item
class DataStore:
@ -68,7 +69,7 @@ class DataStore:
for row in cursor:
rowDate = datetime.strptime(row["created_at"], "%Y-%m-%d %H:%M:%S") # noqa: DTZ007
data: dict = json.loads(row["data"])
data, _ = clean_item(json.loads(row["data"]), keys=ItemDTO.removed_fields())
key: str = data.pop("_id")
item: ItemDTO = ItemDTO(**data)
item._id = key

View file

@ -48,7 +48,6 @@ class Download:
temp_dir: str = None
template: str = None
template_chapter: str = None
ytdl_opts: dict = None
info: ItemDTO = None
default_ytdl_opts: dict = None
debug: bool = False
@ -92,7 +91,6 @@ class Download:
self.template = info.template
self.template_chapter = info.template_chapter
self.preset = info.preset
self.ytdl_opts = info.config if info.config else {}
self.info = info
self.id = info._id
self.default_ytdl_opts = config.ytdl_options
@ -147,7 +145,7 @@ class Download:
YTDLPOpts.get_instance()
.preset(self.preset)
.add({"break_on_existing": True})
.add(self.ytdl_opts, from_user=True)
.add_cli(self.info.cli, from_user=True)
.add(
{
"color": "no_color",

View file

@ -1,12 +1,13 @@
import asyncio
import functools
import json
import glob
import logging
import os
import time
import uuid
from datetime import UTC, datetime
from email.utils import formatdate
from pathlib import Path
from sqlite3 import Connection
import anyio
@ -18,7 +19,7 @@ from .config import Config
from .DataStore import DataStore
from .Download import Download
from .Events import EventBus, Events
from .ItemDTO import ItemDTO
from .ItemDTO import Item, ItemDTO
from .Presets import Presets
from .Singleton import Singleton
from .Utils import arg_converter, calc_download_path, extract_info, is_downloaded
@ -168,45 +169,23 @@ class DownloadQueue(metaclass=Singleton):
except Exception as e:
LOG.error(f"Failed to cancel downloads. {e!s}")
async def __add_entry(
self,
entry: dict,
preset: str,
folder: str,
config: dict | None = None,
cookies: str = "",
template: str = "",
extras: dict | None = None,
cli: str = "",
already=None,
):
async def __add_entry(self, entry: dict, item: Item, already=None):
"""
Add an entry to the download queue.
Args:
entry (dict): The entry to add to the download queue.
preset (str): The preset to use for the download.
folder (str): The folder to save the download to.
config (dict): The yt-dlp configuration to use for the download.
cookies (str): The cookies to use for the download.
template (str): The output template to use for the download.
extras (dict): The extra information to add to the download.
cli (str): The yt-dlp command line options to use for the download.
item (Item): The item to add to the download queue.
already (set): The set of already downloaded items.
Returns:
dict: The status of the operation.
"""
if not config:
config = {}
if not extras:
extras = {}
else:
for key in extras.copy():
if item.has_extras():
for key in item.extras.copy():
if not str(key).startswith("playlist"):
extras.pop(key, None)
item.extras.pop(key, None)
if not entry:
return {"status": "error", "msg": "Invalid/empty data was given."}
@ -239,13 +218,7 @@ class DownloadQueue(metaclass=Singleton):
results.append(
await self.__add_entry(
entry=etr,
preset=preset,
folder=folder,
config=config,
cookies=cookies,
template=template,
extras=extras,
cli=cli,
item=item.new_with(url=etr.get("url") or etr.get("webpage_url")),
already=already,
)
)
@ -291,7 +264,7 @@ class DownloadQueue(metaclass=Singleton):
is_live = bool(entry.get("is_live") or live_in or entry.get("live_status") in live_status)
try:
download_dir = calc_download_path(base_path=self.config.download_path, folder=folder)
download_dir = calc_download_path(base_path=self.config.download_path, folder=item.folder)
except Exception as e:
LOG.exception(e)
return {"status": "error", "msg": str(e)}
@ -299,31 +272,30 @@ class DownloadQueue(metaclass=Singleton):
fields: tuple = ("uploader", "channel", "thumbnail")
for field in fields:
if entry.get(field):
extras[field] = entry.get(field)
item.extras[field] = entry.get(field)
for key in entry:
if isinstance(key, str) and key.startswith("playlist") and entry.get(key):
extras[key] = entry.get(key)
item.extras[key] = entry.get(key)
dl = ItemDTO(
id=str(entry.get("id")),
title=str(entry.get("title")),
url=str(entry.get("webpage_url") or entry.get("url")),
preset=preset,
folder=folder,
preset=item.preset,
folder=item.folder,
download_dir=download_dir,
temp_dir=self.config.temp_path,
cookies=cookies,
config=config,
template=template if template else self.config.output_template,
cookies=item.cookies,
template=item.template if item.template else self.config.output_template,
template_chapter=self.config.output_template_chapter,
datetime=formatdate(time.time()),
error=error,
is_live=is_live,
live_in=live_in,
options=options,
cli=cli,
extras=extras,
cli=item.cli,
extras=item.extras,
)
dlInfo: Download = Download(info=dl, info_dict=entry)
@ -347,44 +319,16 @@ class DownloadQueue(metaclass=Singleton):
return {"status": "ok"}
if eventType.startswith("url"):
return await self.add(
url=str(entry.get("url")),
preset=preset,
folder=folder,
config=config,
cookies=cookies,
template=template,
extras=extras,
cli=cli,
already=already,
)
return await self.add(item=item.new_with(url=entry.get("url")), already=already)
return {"status": "error", "msg": f'Unsupported resource "{eventType}"'}
async def add(
self,
url: str,
preset: str,
folder: str,
config: dict | None = None,
cookies: str = "",
template: str = "",
cli: str = "",
extras: dict | None = None,
already=None,
):
async def add(self, item: Item, already: set | None = None):
"""
Add an item to the download queue.
Args:
url (str): The url to be added to the queue.
preset (str): The preset to be used for the download.
folder (str): The folder to save the download to.
config (dict): The yt-dlp config to be used for the download.
cookies (str): The cookies to be used for the download.
template (str): The template to be used for the download.
cli (str): The yt-dlp cli options to be used for the download.
extras (dict): Extra data to be added to the download
item (Item): The item to be added to the queue.
already (set): Set of already downloaded items.
Returns:
@ -392,62 +336,47 @@ class DownloadQueue(metaclass=Singleton):
{ "status": "text" }
"""
_preset = Presets.get_instance().get(name=preset)
_preset = Presets.get_instance().get(item.preset)
config = config if config else {}
if cli:
if item.has_cli():
try:
config = arg_converter(args=cli, level=True)
arg_converter(args=item.cli, level=True)
except Exception as e:
LOG.error(f"Invalid cli options '{cli}'. {e!s}")
return {"status": "error", "msg": f"Invalid cli options '{cli}'. {e!s}"}
folder = str(folder) if folder else ""
if not extras:
extras = {}
LOG.error(f"Invalid cli options '{item.cli}'. {e!s}")
return {"status": "error", "msg": f"Invalid cli options '{item.cli}'. {e!s}"}
if _preset:
if _preset.folder and not folder:
folder = _preset.folder
if _preset.folder and not item.folder:
item.folder = _preset.folder
if _preset.template and not template:
template = _preset.template
if _preset.template and not item.template:
item.template = _preset.template
if _preset.cookies and not cookies:
cookies = _preset.cookies
if _preset.cookies and not item.cookies:
item.cookies = _preset.cookies
filePath = calc_download_path(base_path=self.config.download_path, folder=folder)
yt_conf = {}
cookie_file = os.path.join(self.config.temp_path, f"c_{uuid.uuid4().hex}.txt")
LOG.info(
f"Adding 'URL: {url}' to 'Folder: {filePath}' with 'Preset: {preset}' 'Naming: {template}', 'Cookies: {len(cookies)}/chars' 'CLI: {cli}' 'Extras: {extras}'."
)
if isinstance(config, str):
try:
config = json.loads(config)
except Exception as e:
LOG.error(f"Unable to load '{config=}'. {e!s}")
return {"status": "error", "msg": f"Failed to parse json yt-dlp config. {e!s}"}
LOG.info(f"Adding '{item.__repr__()}'.")
already = set() if already is None else already
if url in already:
LOG.warning(f"Recursion detected with url '{url}' skipping.")
if item.url in already:
LOG.warning(f"Recursion detected with url '{item.url}' skipping.")
return {"status": "ok"}
already.add(url)
already.add(item.url)
try:
downloaded, id_dict = self._is_downloaded(url)
downloaded, id_dict = self._is_downloaded(item.url)
if downloaded is True and id_dict:
message = f"This url with ID '{id_dict.get('id')}' has been downloaded already and recorded in archive."
LOG.info(message)
return {"status": "error", "msg": message}
started = time.perf_counter()
LOG.debug(f"extract_info: checking {url=}")
LOG.debug(f"extract_info: checking '{item.url}'.")
logs = []
@ -456,16 +385,18 @@ class DownloadQueue(metaclass=Singleton):
"func": lambda _, msg: logs.append(msg),
"level": logging.WARNING,
},
**YTDLPOpts.get_instance().preset(name=preset).add(config=config, from_user=True).get_all(),
**YTDLPOpts.get_instance().preset(name=item.preset).add_cli(args=item.cli, from_user=True).get_all(),
}
if cookies:
if item.cookies:
try:
async with await anyio.open_file(cookie_file, "w") as f:
await f.write(cookies)
await f.write(item.cookies)
yt_conf["cookiefile"] = f.name
except ValueError as e:
LOG.error(f"Failed to create cookie file for '{self.info.id}: {self.info.title}'. '{e!s}'.")
msg = f"Failed to create cookie file for '{self.info.id}: {self.info.title}'. '{e!s}'."
LOG.error(msg)
return {"status": "error", "msg": msg}
entry = await asyncio.wait_for(
fut=asyncio.get_running_loop().run_in_executor(
@ -473,7 +404,7 @@ class DownloadQueue(metaclass=Singleton):
functools.partial(
extract_info,
config=yt_conf,
url=url,
url=item.url,
debug=bool(self.config.ytdl_debug),
no_archive=False,
follow_redirect=True,
@ -486,7 +417,7 @@ class DownloadQueue(metaclass=Singleton):
return {"status": "error", "msg": "Unable to extract info." + "\n".join(logs)}
LOG.debug(
f"extract_info: for 'URL: {url}' is done in '{time.perf_counter() - started}'. Length: '{len(entry)}'."
f"extract_info: for 'URL: {item.url}' is done in '{time.perf_counter() - started}'. Length: '{len(entry)}/keys'."
)
except yt_dlp.utils.ExistingVideoReached as exc:
LOG.error(f"Video has been downloaded already and recorded in archive.log file. '{exc!s}'.")
@ -508,17 +439,7 @@ class DownloadQueue(metaclass=Singleton):
except Exception as e:
LOG.error(f"Failed to remove cookie file '{yt_conf['cookiefile']}'. {e!s}")
return await self.__add_entry(
entry=entry,
preset=preset,
folder=folder,
config=config,
cookies=cookies,
template=template,
already=already,
extras=extras,
cli=cli,
)
return await self.__add_entry(entry=entry, item=item, already=already)
async def cancel(self, ids: list[str]) -> dict[str, str]:
"""
@ -588,7 +509,7 @@ class DownloadQueue(metaclass=Singleton):
continue
itemRef: str = f"{id=} {item.info.id=} {item.info.title=}"
fileDeleted: bool = False
removed_files = 0
filename: str = ""
if remove_file and self.config.remove_files and "finished" == item.info.status:
@ -602,9 +523,13 @@ class DownloadQueue(metaclass=Singleton):
folder=filename,
create_path=False,
)
if realFile and os.path.exists(realFile):
os.remove(realFile)
fileDeleted = True
rf = Path(realFile)
if rf.is_file() and rf.exists():
for f in rf.parent.glob(f"{glob.escape(rf.stem)}.*"):
if f.is_file() and f.exists() and not f.name.startswith("."):
removed_files += 1
LOG.debug(f"Removing '{itemRef}' local file '{f.name}'.")
os.remove(f)
else:
LOG.warning(f"Failed to remove '{itemRef}' local file '{filename}'. File not found.")
except Exception as e:
@ -614,8 +539,8 @@ class DownloadQueue(metaclass=Singleton):
await self._notify.emit(Events.CLEARED, data=item.info.serialize())
msg = f"Deleted completed download '{itemRef}'."
if fileDeleted and filename:
msg += f" and removed local file '{filename}'."
if removed_files > 0:
msg += f" and removed '{removed_files}' local files."
LOG.info(msg=msg)
status[id] = "ok"

View file

@ -27,6 +27,7 @@ from .DownloadQueue import DownloadQueue
from .encoder import Encoder
from .Events import EventBus, Events, message
from .ffprobe import ffprobe
from .ItemDTO import Item
from .M3u8 import M3u8
from .Notifications import Notification, NotificationEvents
from .Playlist import Playlist
@ -475,7 +476,8 @@ class HttpAPI(Common):
err = err.split("\n")[-1] if "\n" in err else err
err = err.replace("main.py: error: ", "").strip().capitalize()
return web.json_response(
data={"error": f"Failed to command line arguments for yt-dlp. '{err}'."}, status=web.HTTPBadRequest.status_code
data={"error": f"Failed to parse command arguments for yt-dlp. '{err}'."},
status=web.HTTPBadRequest.status_code,
)
@route("GET", "api/yt-dlp/url/info")
@ -501,7 +503,7 @@ class HttpAPI(Common):
preset = request.query.get("preset")
if preset:
exists = Presets.get_instance().get(name=preset)
exists = Presets.get_instance().get(preset)
if not exists:
return web.json_response(
data={"status": False, "message": f"Preset '{preset}' does not exist."},
@ -674,7 +676,7 @@ class HttpAPI(Common):
preset = request.query.get("preset")
if preset:
exists = Presets.get_instance().get(name=preset)
exists = Presets.get_instance().get(preset)
if not exists:
return web.json_response(
data={"status": False, "message": f"Preset '{preset}' does not exist."},
@ -683,7 +685,7 @@ class HttpAPI(Common):
data["preset"] = preset
try:
status = await self.add(**self.format_item(data))
status = await self.add(item=Item.format(data))
except ValueError as e:
return web.json_response(data={"status": False, "message": str(e)}, status=web.HTTPBadRequest.status_code)
@ -710,15 +712,16 @@ class HttpAPI(Common):
if isinstance(data, dict):
data = [data]
items = []
for item in data:
try:
self.format_item(item)
items.append(Item.format(item))
except ValueError as e:
return web.json_response(data={"error": str(e), "data": item}, status=web.HTTPBadRequest.status_code)
return web.json_response(
data=await asyncio.wait_for(
fut=asyncio.gather(*[self.add(**self.format_item(item)) for item in data]),
fut=asyncio.gather(*[self.add(item=item) for item in items]),
timeout=None,
),
status=web.HTTPOk.status_code,
@ -790,18 +793,6 @@ class HttpAPI(Common):
if not item.get("id", None) or not validate_uuid(item.get("id"), version=4):
item["id"] = str(uuid.uuid4())
if not item.get("args", None) or str(item.get("args")).strip() == "":
item["args"] = {}
if item.get("args", None) and isinstance(item.get("args"), str):
item["args"] = json.loads(item.get("args"))
if not item.get("postprocessors", None) or str(item.get("postprocessors")).strip() == "":
item["postprocessors"] = []
if item.get("postprocessors", None) and isinstance(item.get("postprocessors"), str):
item["postprocessors"] = json.loads(item.get("postprocessors"))
try:
cls.validate(item)
except ValueError as e:

View file

@ -17,6 +17,7 @@ from .config import Config
from .DownloadQueue import DownloadQueue
from .encoder import Encoder
from .Events import Event, EventBus, Events, error
from .ItemDTO import Item
from .Presets import Presets
from .Utils import is_downloaded
@ -79,7 +80,7 @@ class HttpSocket(Common):
self._notify.subscribe(
Events.ADD_URL,
lambda data, _, **kwargs: self.add(**self.format_item(data.data)), # noqa: ARG005
lambda data, _, **kwargs: self.add(item=Item.format(data.data)), # noqa: ARG005
f"{__class__.__name__}.socket_add_url",
)
@ -188,14 +189,16 @@ class HttpSocket(Common):
return
try:
item = self.format_item(data)
await self._notify.emit(
event=Events.STATUS,
data=await self.add(item=Item.format(data)),
to=sid,
)
except ValueError as e:
LOG.exception(e)
await self._notify.emit(Events.ERROR, data=error(str(e)), to=sid)
return
status = await self.add(**item)
await self._notify.emit(Events.STATUS, data=status, to=sid)
@ws_event
async def item_cancel(self, sid: str, id: str):
if not id:

View file

@ -1,10 +1,171 @@
import json
import logging
import time
import uuid
from dataclasses import dataclass, field
from email.utils import formatdate
from typing import Any
LOG = logging.getLogger("ItemDTO")
@dataclass(kw_only=True)
class Item:
"""
This class represents a single download request for data transfer purposes.
"""
url: str
"""The URL of the item to be downloaded."""
preset: str = field(default_factory=lambda: Item._default_preset())
"""The preset to be used for the download."""
folder: str = ""
"""The folder to save the download to."""
cookies: str = ""
"""The cookies to be used for the download."""
template: str = ""
"""The template to be used for the download."""
cli: str = ""
"""The yt-dlp cli options to be used for the download."""
extras: dict = field(default_factory=dict)
"""Extra data to be added to the download."""
def serialize(self) -> dict:
return self.__dict__.copy()
def json(self) -> str:
return json.dumps(self.serialize(), default=lambda o: o.__dict__, sort_keys=True, indent=4)
def get(self, key: str, default: Any = None) -> Any:
return self.__dict__.get(key, default)
def has_extras(self) -> bool:
"""
Check if the item has any extras data associated with it.
Returns:
bool: True if the item has extras, False otherwise.
"""
return self.extras and len(self.extras) > 0
def has_cli(self) -> bool:
"""
Check if the item has any yt-dlp cli options associated with it.
Returns:
bool: True if the item has yt-dlp cli options, False otherwise.
"""
return self.cli and len(self.cli) > 2
def _default_preset() -> str:
from .config import Config
return Config.get_instance().default_preset
def new_with(self, **kwargs) -> "Item":
"""
Create a new instance of Item with the given parameters.
Args:
**kwargs: The parameters to be used for creating the new instance.
Returns:
Item: A new instance of Item with the given parameters.
"""
return Item.format({**self.serialize(), **kwargs})
def __repr__(self):
from .config import Config
from .Utils import calc_download_path, strip_newline
data = {}
for k, v in self.serialize().items():
if not v:
continue
if k == "cli":
data[k] = strip_newline(v)
elif k == "extras":
data[k] = f"{len(v)} items"
elif k == "cookies":
data[k] = f"{len(v)}/chars"
elif k == "folder":
data[k] = calc_download_path(base_path=Config.get_instance().download_path, folder=v)
else:
data[k] = v
items = "".join(f'{k}="{v}", ' for k, v in data.items() if v)
return f"Item({items.strip(', ')})"
@staticmethod
def format(item: dict) -> "Item":
"""
Format the item to be added to the download queue.
Args:
item (dict): The item to be formatted.
Raises:
ValueError: If the url is not provided.
ValueError: If the yt-cli command line arguments are not valid.
Returns:
Item: The formatted item.
"""
url: str = item.get("url")
if not url or not isinstance(url, str):
msg = "url param is required."
raise ValueError(msg)
data = {
"url": url,
}
preset = item.get("preset")
if preset and isinstance(preset, str) and preset != Item._default_preset():
data["preset"] = preset
if item.get("folder") and isinstance(item.get("folder"), str):
data["folder"] = item.get("folder")
if item.get("cookies") and isinstance(item.get("cookies"), str):
data["cookies"] = item.get("cookies")
if item.get("template") and isinstance(item.get("template"), str):
data["template"] = item.get("template")
extras = item.get("extras")
if extras and isinstance(extras, dict) and len(extras) > 0:
data["extras"] = extras
cli = item.get("cli")
if cli and len(cli) > 2:
from .Utils import arg_converter
try:
removed_options = []
arg_converter(args=cli, level=True, removed_options=removed_options)
if len(removed_options) > 0:
LOG.warning("Removed the following options '%s' for '%s'.", ", ".join(removed_options), url)
data["cli"] = cli
except Exception as e:
msg = f"Failed to parse yt-dlp cli options. {e!s}"
raise ValueError(msg) from e
return Item(**data)
@dataclass(kw_only=True)
class ItemDTO:
@ -49,45 +210,8 @@ class ItemDTO:
speed: str | None = None
eta: str | None = None
# @Deprecated: These fields are deprecated and will be removed in the future.
thumbnail: str | None = None
ytdlp_cookies: str | None = None
ytdlp_config: dict = field(default_factory=dict)
output_template: str | None = None
output_template_chapter: str | None = None
config: dict = field(default_factory=dict)
def serialize(self) -> dict:
deprecated: tuple = (
"thumbnail",
"quality",
"format",
"ytdlp_cookies",
"ytdlp_config",
"output_template",
"output_template_chapter",
"config",
)
if self.thumbnail and "thumbnail" not in self.extras:
self.extras["thumbnail"] = self.thumbnail
mapper: dict = {
"cookies": "ytdlp_cookies",
"config": "ytdlp_config",
"template": "output_template",
"template_chapter": "output_template_chapter",
}
for k, v in mapper.items():
if not self.get(k) and self.get(v):
self.__dict__[k] = self.get(v)
dump = self.__dict__.copy()
for f in deprecated:
dump.pop(f, None)
return dump
return self.__dict__.copy()
def json(self) -> str:
return json.dumps(self.serialize(), default=lambda o: o.__dict__, sort_keys=True, indent=4)
@ -97,3 +221,17 @@ class ItemDTO:
def get_id(self) -> str:
return self._id
@staticmethod
def removed_fields() -> tuple:
"""Fields that once existed but are no longer used."""
return (
"thumbnail",
"quality",
"format",
"ytdlp_cookies",
"ytdlp_config",
"output_template",
"output_template_chapter",
"config",
)

View file

@ -141,9 +141,6 @@ class Notification(metaclass=Singleton):
except Exception:
pass
if os.path.getsize(self._file) > 10:
self.load()
@staticmethod
def get_instance() -> "Notification":
if Notification._instance is None:
@ -371,7 +368,9 @@ class Notification(metaclass=Singleton):
if "form" == target.request.type.lower():
reqBody["data"]["data"] = self._encoder.encode(reqBody["data"]["data"])
logging.getLogger("httpx").setLevel(logging.WARNING)
if not self._debug:
logging.getLogger("httpx").setLevel(logging.WARNING)
response = await self._client.request(**reqBody)
respData = {"url": target.request.url, "status": response.status_code, "text": response.text}
@ -404,7 +403,7 @@ class Notification(metaclass=Singleton):
data[k] = [self._deep_unpack(i) for i in v]
if isinstance(v, datetime):
data[k] = v.isoformat()
if isinstance(v, ItemDTO):
if isinstance(v, object) and hasattr(v, "serialize"):
data[k] = v.serialize()
return data

View file

@ -11,6 +11,7 @@ from .config import Config
from .encoder import Encoder
from .Events import EventBus, Events
from .Singleton import Singleton
from .Utils import arg_converter, clean_item
LOG = logging.getLogger("presets")
@ -26,12 +27,6 @@ class Preset:
format: str
"""The format of the preset."""
args: dict[str, list[str] | bool] = field(default_factory=dict)
"""The arguments of the preset."""
postprocessors: list = field(default_factory=list)
"""The postprocessors of the preset."""
folder: str = ""
"""The default download folder to use if non is given."""
@ -83,13 +78,19 @@ class Presets(metaclass=Singleton):
pass
with open(os.path.join(os.path.dirname(__file__), "presets.json")) as f:
self._default_presets = [Preset(**preset) for preset in json.load(f)]
for i, preset in enumerate(json.load(f)):
try:
self.validate(preset)
self._default_presets.append(Preset(**preset))
except Exception as e:
LOG.error(f"Failed to parse default preset '{i}'. '{e!s}'.")
continue
EventBus.get_instance().subscribe(
Events.PRESETS_ADD,
lambda data, _, **kwargs: self.add(**data.data), # noqa: ARG005
f"{__class__.__name__}.save",
)
def event_handler(_, __):
msg = "Not implemented"
raise Exception(msg)
EventBus.get_instance().subscribe(Events.PRESETS_ADD, event_handler, f"{__class__.__name__}.save")
@staticmethod
def get_instance() -> "Presets":
@ -150,22 +151,27 @@ class Presets(metaclass=Singleton):
LOG.info(f"No presets were defined in '{self._file}'.")
return self
needSaving = False
need_save = False
for i, preset in enumerate(presets):
try:
if "id" not in preset:
preset["id"] = str(uuid.uuid4())
needSaving = True
need_save = True
preset, preset_status = clean_item(preset, keys=("args", "postprocessors"))
preset = Preset(**preset)
if preset_status:
need_save = True
self._presets.append(preset)
except Exception as e:
LOG.error(f"Failed to parse preset at list position '{i}'. '{e!s}'.")
continue
if needSaving:
LOG.info("Saving presets due to missing ids.")
if need_save:
LOG.info("Saving presets due to format, or id change.")
self.save(self._presets)
return self
@ -198,7 +204,7 @@ class Presets(metaclass=Singleton):
"""
if not isinstance(preset, dict):
if not isinstance(preset, Preset):
msg = "Invalid preset type."
msg = f"Invalid preset type. Was expecting a (Preset|dict), but got '{type(preset).__name__}'."
raise ValueError(msg) # noqa: TRY004
preset = preset.serialize()
@ -215,18 +221,8 @@ class Presets(metaclass=Singleton):
msg = "No format found."
raise ValueError(msg)
if preset.get("args") and not isinstance(preset.get("args"), dict):
msg = "Invalid args type. expected dict."
raise ValueError(msg)
if preset.get("postprocessors") and not isinstance(preset.get("postprocessors"), list):
msg = "Invalid postprocessors type. expected list."
raise ValueError(msg)
if preset.get("cli"):
try:
from .Utils import arg_converter
arg_converter(args=preset.get("cli"))
except Exception as e:
msg = f"Invalid cli options. '{e!s}'."
@ -270,26 +266,24 @@ class Presets(metaclass=Singleton):
return self
def get(self, id: str | None = None, name: str | None = None) -> Preset | None:
def get(self, id_or_name: str) -> Preset | None:
"""
Get the preset by id or name.
Args:
id (str|None): The id of the preset.
name (str|None): The name of the preset.
id_or_name (str): The id or name of the preset.
Returns:
Preset|None: The preset if found, None otherwise.
"""
if not id and not name:
if not id_or_name:
return None
for preset in self.get_all():
if preset.id == id:
return preset
if id_or_name not in (preset.id, preset.name):
continue
if preset.name == name:
return preset
return preset
return None

View file

@ -15,6 +15,7 @@ from .encoder import Encoder
from .Events import EventBus, Events, error, info, success
from .Scheduler import Scheduler
from .Singleton import Singleton
from .Utils import clean_item
LOG = logging.getLogger("tasks")
@ -140,13 +141,13 @@ class Tasks(metaclass=Singleton):
LOG.info(f"No tasks were defined in '{self._file}'.")
return self
need_update = False
need_save = False
for i, task in enumerate(tasks):
try:
task, task_status = self.clean_task(task)
task, task_status = clean_item(task, keys=("cookies", "config"))
task = Task(**task)
if task_status:
need_update = True
need_save = True
except Exception as e:
LOG.error(f"Failed to parse task at list position '{i}'. '{e!s}'.")
continue
@ -155,12 +156,20 @@ class Tasks(metaclass=Singleton):
self._scheduler.add(timer=task.timer, func=self._runner, args=(task,), id=task.id)
self._tasks.append(task)
LOG.info(f"Task '{i}: {task.name}' queued to be executed every '{task.timer}'.")
try:
from cronsim import CronSim
cs = CronSim(task.timer, datetime.now(UTC))
schedule_time = cs.explain()
except Exception:
schedule_time = task.timer
LOG.info(f"Task '{i}: {task.name}' queued to be executed '{schedule_time}'.")
except Exception as e:
LOG.exception(e)
LOG.error(f"Failed to queue task '{i}: {task.name}'. '{e!s}'.")
if need_update:
if need_save:
LOG.info("Updating tasks file to remove old keys.")
self.save(self.get_all())
@ -223,6 +232,7 @@ class Tasks(metaclass=Singleton):
if task.get("cli"):
try:
from .Utils import arg_converter
arg_converter(args=task.get("cli"))
except Exception as e:
msg = f"Invalid cli options. '{e!s}'."
@ -266,27 +276,6 @@ class Tasks(metaclass=Singleton):
return self
def clean_task(self, task: dict) -> tuple[dict, bool]:
"""
Clean the task from old keys.
Args:
task (dict): The task to clean.
Returns:
tuple[dict, bool]: The cleaned task and a status if the task was cleaned.
"""
status = False
removedKeys = ["cookies", "config"]
for key in removedKeys:
if key in task:
status = True
task.pop(key)
return task, status
async def _runner(self, task: Task):
"""
Run the task.

View file

@ -522,8 +522,6 @@ def arg_converter(
bad_options.update(item.items())
LOG.debug("Removed %i the following options: '%s'.", level, ", ".join(bad_options.values()))
for key in diff.copy():
if key not in bad_options:
continue
@ -889,3 +887,60 @@ def get_files(base_path: str, dir: str | None = None):
)
return contents
def clean_item(item: dict, keys: list | tuple) -> tuple[dict, bool]:
"""
Remove given keys from a dictionary.
This function modifies the dictionary in place and returns a tuple
containing the modified dictionary and a boolean indicating
whether any keys were removed.
Args:
item (dict): The item to clean.
keys (list|tuple): The keys to remove.
Returns:
tuple[dict, bool]: The cleaned item and a the status of cleaning operation.
Raises:
TypeError: If item is not a dictionary or keys is not a list or tuple.
"""
status = False
if not isinstance(item, dict):
msg = "Item must be a dictionary."
raise TypeError(msg)
if not isinstance(keys, list | tuple):
msg = "Keys must be a list or tuple."
raise TypeError(msg)
for key in keys:
if key not in item:
continue
status = True
item.pop(key)
return item, status
def strip_newline(string: str) -> str:
"""
Remove newlines from a string.
Args:
string (str): The string to process.
Returns:
str: The string without newlines.
"""
if not string:
return ""
res = re.sub(r"(\r\n|\r|\n)", " ", string)
return res.strip() if res else ""

View file

@ -36,6 +36,30 @@ class YTDLPOpts(metaclass=Singleton):
return YTDLPOpts._instance
def add_cli(self, args: str, from_user: int | bool = False) -> "YTDLPOpts":
"""
Prase and add yt-dlp cli options to the item options.
Args:
args (str): The cli options to add
from_user (bool): If the options are from the user
Returns:
YTDLPOpts: The instance of the class
"""
if not args or len(args) < 2 or not isinstance(args, str):
return self
removed_options = []
self._item_opts.update(arg_converter(args=args, level=from_user, removed_options=removed_options))
if len(removed_options) > 0:
LOG.warning("Removed the following options: '%s'.", ", ".join(removed_options))
return self
def add(self, config: dict, from_user: bool = False) -> "YTDLPOpts":
"""
Add the options to the item options.
@ -78,7 +102,7 @@ class YTDLPOpts(metaclass=Singleton):
YTDLPOpts: The instance of the class
"""
preset = Presets.get_instance().get(name=name)
preset = Presets.get_instance().get(name)
if not preset or "default" == name:
return self
@ -118,25 +142,6 @@ class YTDLPOpts(metaclass=Singleton):
"temp": self._config.temp_path,
}
# @Deprecated - To be removed in future versions.
if not preset.cli:
if preset.postprocessors and isinstance(preset.postprocessors, list) and len(preset.postprocessors) > 0:
self._preset_opts["postprocessors"] = preset.postprocessors
if preset.args and isinstance(preset.args, dict) and len(preset.args) > 0:
bad_options = {k: v for d in REMOVE_KEYS for k, v in d.items()}
removed_options = []
for key, value in preset.args.items():
if key in bad_options:
removed_options.append(bad_options[key])
continue
self._preset_opts[key] = value
if len(removed_options) > 0:
LOG.warning(
"Removed the following options '%s' from '%s' args.", ", ".join(removed_options), preset.name
)
return self
def get_all(self, keep: bool = False) -> dict:
@ -166,25 +171,4 @@ class YTDLPOpts(metaclass=Singleton):
if "format" in data and data["format"] in ["not_set", "default"]:
data["format"] = None
# @Deprecated - To be removed in future versions, All the checks below this line are deprecated.
if "daterange" in data and isinstance(data["daterange"], dict):
from yt_dlp.utils import DateRange
data["daterange"] = DateRange(data["daterange"]["start"], data["daterange"]["end"])
if "impersonate" in data and isinstance(data["impersonate"], str):
from yt_dlp.networking.impersonate import ImpersonateTarget
data["impersonate"] = ImpersonateTarget.from_str(data["impersonate"])
if (
"match_filter" in data
and isinstance(data["match_filter"], dict)
and "filters" in data["match_filter"]
and len(data["match_filter"]["filters"]) > 0
):
from yt_dlp.utils import match_filter_func
data["match_filter"] = match_filter_func(data["match_filter"]["filters"])
return data

View file

@ -1,10 +1,9 @@
import json
import logging
from .config import Config
from .DownloadQueue import DownloadQueue
from .encoder import Encoder
from .Utils import arg_converter
from .ItemDTO import Item
LOG = logging.getLogger("common")
@ -27,105 +26,16 @@ class Common:
config = config or Config.get_instance()
self.default_preset = config.default_preset
async def add(
self,
url: str,
preset: str,
folder: str,
cookies: str,
# @deprecated: config: dict,
config: dict,
template: str,
extras: dict | None = None,
cli: str = "",
) -> dict[str, str]:
async def add(self, item: Item) -> dict[str, str]:
"""
Add an item to the download queue.
Args:
url (str): The url to be added to the queue.
preset (str): The preset to be used for the download.
folder (str): The folder to save the download to.
cookies (str): The cookies to be used for the download.
config (dict): The yt-dlp config to be used for the download.
template (str): The template to be used for the download.
extras (dict): Extra data to be added to the download
cli (str): The yt-dlp cli options to be used for the download.
item (Item): The item to be added to the queue.
Returns:
dict[str, str]: The status of the download.
{ "status": "text" }
"""
if cookies and isinstance(cookies, dict):
cookies = self.encoder.encode(cookies)
return await self.queue.add(
url=url,
preset=preset if preset else self.config.default_preset,
folder=folder,
cookies=cookies,
config=config if isinstance(config, dict) else {},
template=template,
cli=cli,
extras=extras,
)
def format_item(self, item: dict) -> dict:
"""
Format the item to be added to the download queue.
Args:
item (dict): The item to be formatted.
Raises:
ValueError: If the url is not provided.
ValueError: If the yt-dlp config is not a valid json.
Returns:
dict: The formatted item
"""
url: str = item.get("url")
if not url:
msg = "url param is required."
raise ValueError(msg)
preset: str = str(item.get("preset", self.config.default_preset))
folder: str = str(item.get("folder")) if item.get("folder") else ""
cookies: str = str(item.get("cookies")) if item.get("cookies") else ""
template: str = str(item.get("template")) if item.get("template") else ""
extras = item.get("extras", {})
config = item.get("config")
if isinstance(config, str) and config:
try:
config = json.loads(config)
except Exception as e:
msg = f"Failed to parse json yt-dlp config for '{url}'. {e!s}"
raise ValueError(msg) from e
cli = item.get("cli")
if cli and len(cli) > 2:
try:
removed_options = []
arg_converter(args=cli, level=True, removed_options=removed_options)
if len(removed_options) > 0:
LOG.warning("Removed the following options '%s'.", ", ".join(removed_options))
config = {}
except Exception as e:
msg = f"Failed to parse yt-dlp cli options. {e!s}"
raise ValueError(msg) from e
return {
"url": url,
"preset": preset,
"folder": folder,
"cookies": cookies,
"config": config if isinstance(config, dict) else {},
"template": template,
"cli": cli if isinstance(cli, str) else "",
"extras": extras if isinstance(extras, dict) else {},
}
return await self.queue.add(item=item)

View file

@ -12,7 +12,7 @@ import coloredlogs
from dotenv import load_dotenv
from yt_dlp.version import __version__ as YTDLP_VERSION
from .Utils import REMOVE_KEYS, arg_converter, load_file, merge_dict
from .Utils import arg_converter
from .version import APP_VERSION
@ -321,11 +321,10 @@ class Config:
except Exception as e:
LOG.error(f"Error starting debugpy server at '0.0.0.0:{self.debugpy_port}'. {e}")
ytdlp_cli: str = os.path.join(self.config_path, "ytdlp.cli")
optsFile: str = os.path.join(self.config_path, "ytdlp.json")
if os.path.exists(ytdlp_cli) and os.path.getsize(ytdlp_cli) > 2:
LOG.info(f"Loading yt-dlp custom options from '{ytdlp_cli}'.")
with open(ytdlp_cli) as f:
opts_file: str = os.path.join(self.config_path, "ytdlp.cli")
if os.path.exists(opts_file) and os.path.getsize(opts_file) > 2:
LOG.info(f"Loading yt-dlp custom options from '{opts_file}'.")
with open(opts_file) as f:
ytdlp_cli_opts = f.read().strip()
if ytdlp_cli_opts:
try:
@ -343,41 +342,15 @@ class Config:
if len(removed_options) > 0:
LOG.warning(
"Removed the following options: '%s' from '%s'", ", ".join(removed_options), ytdlp_cli
"Removed the following options: '%s' from '%s'", ", ".join(removed_options), opts_file
)
except Exception as e:
msg = f"Failed to parse yt-dlp cli options from '{ytdlp_cli}'. '{e!s}'."
msg = f"Failed to parse yt-dlp cli options from '{opts_file}'. '{e!s}'."
raise ValueError(msg) from e
else:
LOG.warning(f"Empty yt-dlp custom options file '{ytdlp_cli}'.")
# @Deprecated - To be removed in future versions.
elif os.path.exists(optsFile) and os.path.getsize(optsFile) > 5:
LOG.warning("The JSON ytdlp.json options file is deprecated, please use 'ytdlp.cli' file instead.")
LOG.info(f"Loading yt-dlp custom options from '{optsFile}'.")
(opts, status, error) = load_file(optsFile, dict)
if not status:
LOG.error(f"Could not load yt-dlp custom options from '{optsFile}'. {error}")
sys.exit(1)
if isinstance(opts, dict):
bad_options = {k: v for d in REMOVE_KEYS for k, v in d.items()}
removed_options = []
for key in opts.copy():
if key not in bad_options:
continue
removed_options.append(bad_options[key])
opts.pop(key, None)
if len(removed_options) > 0:
LOG.warning(
"Removed the following options: '%s' from 'ytdlp.json' file.", ", ".join(removed_options)
)
self.ytdl_options = merge_dict(self.ytdl_options, opts)
else:
LOG.error(f"Invalid yt-dlp custom options file '{optsFile}'.")
LOG.warning(f"Empty yt-dlp custom options file '{opts_file}'.")
else:
LOG.info(f"No yt-dlp custom options found at '{ytdlp_cli}'.")
LOG.info(f"No yt-dlp custom options found at '{opts_file}'.")
self.ytdl_options["socket_timeout"] = self.socket_timeout

View file

@ -5,31 +5,39 @@
<div class="box">
<div class="columns is-multiline is-mobile">
<div class="column is-12">
<div class="control has-icons-left">
<label class="label is-inline is-unselectable" for="url">
<span class="icon"><i class="fa-solid fa-link" /></span>
URLs
</label>
<div class="control">
<input type="text" class="input" id="url" placeholder="Video or playlist link"
:disabled="!socket.isConnected || addInProgress" v-model="url">
<span class="icon is-small is-left">
<i class="fa-solid fa-link" />
</span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>You can add multiple URLs separated by a comma <code>,</code>.</span>
</span>
</div>
<div class="column is-4-tablet is-12-mobile" v-if="!config.app.basic_mode">
<div class="field has-addons">
<div class="control">
<a href="#" class="button is-static">Preset</a>
<a href="#" class="button is-static">
<span class="icon"><i class="fa-solid fa-sliders" /></span>
<span>Preset</span>
</a>
</div>
<div class="control is-expanded">
<div class="select is-fullwidth">
<select id="preset" class="is-fullwidth"
:disabled="!socket.isConnected || addInProgress || hasFormatInConfig" v-model="selectedPreset"
v-tooltip.bottom="hasFormatInConfig ? 'Presets are disabled. Format key is present in the Command arguments for yt-dlp.' : ''">
<optgroup label="Default presets">
<option v-for="item in filter_presets(true)" :key="item.name" :value="item.name">
<optgroup label="Custom presets" v-if="config?.presets.filter(p => !p?.default).length > 0">
<option v-for="item in filter_presets(false)" :key="item.name" :value="item.name">
{{ item.name }}
</option>
</optgroup>
<optgroup label="Custom presets" v-if="config?.presets.filter(p => !p?.default).length > 0">
<option v-for="item in filter_presets(false)" :key="item.name" :value="item.name">
<optgroup label="Default presets">
<option v-for="item in filter_presets(true)" :key="item.name" :value="item.name">
{{ item.name }}
</option>
</optgroup>
@ -41,7 +49,10 @@
<div class="column is-6-tablet is-12-mobile" v-if="!config.app.basic_mode">
<div class="field has-addons" v-tooltip="'Folder relative to ' + config.app.download_path">
<div class="control">
<a href="#" class="button is-static">Save in</a>
<a href="#" class="button is-static">
<span class="icon"><i class="fa-solid fa-folder" /></span>
<span>Save in</span>
</a>
</div>
<div class="control is-expanded">
<input type="text" class="input is-fullwidth" id="path" v-model="downloadPath" placeholder="Default"
@ -67,17 +78,17 @@
</div>
<div class="columns is-multiline is-mobile" v-if="showAdvanced && !config.app.basic_mode">
<div class="column is-6-tablet is-12-mobile">
<div class="column is-12">
<div class="field">
<label class="label is-inline" for="output_format"
v-tooltip="'Default Format: ' + config.app.output_template">
<label class="label is-inline is-unselectable" for="output_format"
v-tooltip="'Default: ' + config.app.output_template">
<span class="icon"><i class="fa-solid fa-file" /></span>
Output Template
</label>
<div class="control has-icons-left">
<div class="control">
<input type="text" class="input" v-model="output_template" id="output_format"
:disabled="!socket.isConnected || addInProgress"
placeholder="Uses default output template naming if empty.">
<span class="icon is-small is-left"><i class="fa-solid fa-file" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
@ -87,16 +98,16 @@
</div>
</div>
<div class="column is-6-tablet is-12-mobile">
<div class="column is-6-tablet is-4-mobile">
<div class="field">
<label class="label is-inline" for="cli_options">
<label class="label is-inline is-unselectable" for="cli_options">
<span class="icon"><i class="fa-solid fa-terminal" /></span>
Command arguments for yt-dlp
</label>
<div class="control has-icons-left">
<input type="text" class="input" v-model="ytdlp_cli" id="cli_options"
<div class="control">
<textarea class="textarea is-pre" v-model="ytdlp_cli" id="cli_options"
:disabled="!socket.isConnected || addInProgress"
placeholder="command options to use, e.g. --no-embed-metadata --no-embed-thumbnail">
<span class="icon is-small is-left"><i class="fa-solid fa-terminal" /></span>
placeholder="command options to use, e.g. --no-embed-metadata --no-embed-thumbnail" />
</div>
<span class="help">
@ -110,9 +121,10 @@
</div>
</div>
<div class="column is-12">
<div class="column is-6-tablet is-4-mobile">
<div class="field">
<label class="label is-inline" for="ytdlpCookies" v-tooltip="'Netscape HTTP Cookie format'">
<label class="label is-inline is-unselectable" for="ytdlpCookies">
<span class="icon"><i class="fa-solid fa-cookie" /></span>
Cookies
</label>
<div class="control">
@ -206,8 +218,9 @@ const resetConfig = () => {
return
}
selectedPreset.value = config.app.default_preset.value
selectedPreset.value = config.app.default_preset
ytdlpCookies.value = ''
ytdlp_cli.value = ''
output_template.value = null
url.value = null
downloadPath.value = null
@ -277,9 +290,8 @@ const hasFormatInConfig = computed(() => {
return false
}
return /(?<!\w)(-f|--format)(=|:)?(?!\w)/.test(ytdlp_cli.value)
return /(?<!\S)(-f|--format)(=|\s)(\S+)/.test(ytdlp_cli.value)
})
const filter_presets = (flag = true) => config.presets.filter(item => item.default === flag)
</script>

View file

@ -27,13 +27,13 @@
<div class="column is-12" v-if="showImport || !reference">
<label class="label is-inline" for="import_string">
<span class="icon"><i class="fa-solid fa-file-import" /></span>
Import string
</label>
<div class="field has-addons">
<div class="control has-icons-left is-expanded">
<div class="control is-expanded">
<input type="text" class="input" id="import_string" v-model="import_string" autocomplete="off">
<span class="icon is-small is-left"><i class="fa-solid fa-t" /></span>
</div>
<div class="control">
@ -51,31 +51,35 @@
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="name" v-text="'Name'" />
<div class="control has-icons-left">
<label class="label is-inline" for="name">
<span class="icon"><i class="fa-solid fa-tag" /></span>
Name
</label>
<div class="control">
<input type="text" class="input" id="name" v-model="form.name" :disabled="addInProgress">
<span class="icon is-small is-left"><i class="fa-solid fa-n" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>The name to refers to this custom settings.</span>
<span>The name to refers to this preset of settings.</span>
</span>
</div>
</div>
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="format" v-text="'Format'" />
<div class="control has-icons-left">
<label class="label is-inline" for="format">
<span class="icon"><i class="fa-solid fa-f" /></span>
Format
</label>
<div class="control">
<input type="text" class="input" id="format" v-model="form.format" :disabled="addInProgress">
<span class="icon is-small is-left"><i class="fa-solid fa-f" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>The yt-dlp <code>[--format, -f]</code> video format code. see <NuxtLink
href="https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#format-selection" target="blank">this
url</NuxtLink> for more info.</span>. Note, as this key is required, you can set the value to
<code>default</code> to let <code>yt-dlp</code> choose the best format.
page</NuxtLink> for more info. Note, as this key is required, you can set the value to
<code>default</code> to let <code>yt-dlp</code> choose the best format.</span>
</span>
</div>
</div>
@ -83,12 +87,12 @@
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="folder">
<span class="icon"><i class="fa-solid fa-folder" /></span>
Default Download path
</label>
<div class="control has-icons-left">
<div class="control">
<input type="text" class="input" id="folder" placeholder="Leave empty to use default download path"
v-model="form.folder" :disabled="addInProgress" list="folders">
<span class="icon is-small is-left"><i class="fa-solid fa-folder" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
@ -101,19 +105,19 @@
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="output_template">
<span class="icon"><i class="fa-solid fa-file" /></span>
Default Output template
</label>
<div class="control has-icons-left">
<div class="control">
<input type="text" class="input" id="output_template" :disabled="addInProgress"
placeholder="Leave empty to use default template." v-model="form.template">
<span class="icon is-small is-left"><i class="fa-solid fa-file" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Use this output template if non are given with URL. if not set, it will defaults to
<code>{{ config.app.output_template }}</code>.
For more information <NuxtLink href="https://github.com/yt-dlp/yt-dlp#output-template"
target="_blank">visit this url</NuxtLink>.
For more information visit <NuxtLink href="https://github.com/yt-dlp/yt-dlp#output-template"
target="_blank">this page</NuxtLink>.
</span>
</span>
</div>
@ -122,11 +126,12 @@
<div class="column is-12">
<div class="field">
<label class="label is-inline" for="cli_options">
<span class="icon"><i class="fa-solid fa-terminal" /></span>
Command arguments for yt-dlp
</label>
<div class="control">
<input type="text" class="input" v-model="form.cli" id="cli_options" :disabled="addInProgress"
placeholder="command options to use, e.g. --no-embed-metadata --no-embed-thumbnail">
<textarea class="textarea is-pre" v-model="form.cli" id="cli_options" :disabled="addInProgress"
placeholder="command options to use, e.g. --no-embed-metadata --no-embed-thumbnail" />
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
@ -141,8 +146,10 @@
<div class="column is-12">
<div class="field">
<label class="label is-inline" for="cookies"
v-tooltip="'Netscape HTTP Cookie format.'">Cookies</label>
<label class="label is-inline" for="cookies" v-tooltip="'Netscape HTTP Cookie format.'">
<span class="icon"><i class="fa-solid fa-cookie" /></span>
Cookies
</label>
<div class="control">
<textarea class="textarea is-pre" id="cookies" v-model="form.cookies" :disabled="addInProgress"
placeholder="Leave empty to use default cookies" />
@ -157,56 +164,7 @@
</span>
</div>
</div>
<div class="column is-12" v-if="has_data(form?.args) || has_data(form?.postprocessors)">
<Message title="Deprecation Warning" class="is-background-warning-80 has-text-dark"
icon="fas fa-exclamation-circle">
<ul>
<li>
The <code>JSON yt-dlp config</code> and <code>JSON yt-dlp Post-Processors</code> fields are
deprecated and will be removed in the future. Please use the <b>Command arguments for yt-dlp</b>
field instead. The deprecated fields will still be working for now but they will stop, we suggest
that you migrate to the new field. as soon as possible to avoid any issues. No support will be
given for the deprecated fields.
</li>
<li>
If both fields are set, the <b>Command arguments for yt-dlp</b> field will take precedence over
the deprecated fields. and when you click save it will remove the deprecated fields.
</li>
</ul>
</Message>
</div>
<div class="column is-6-tablet is-12-mobile" v-if="has_data(form?.args)">
<div class="field">
<label class="label is-inline" for="args" v-tooltip="'Extends current global yt-dlp config. (JSON)'">
JSON yt-dlp config <span class="has-text-danger">(DEPRECATED)</span>
</label>
<div class="control">
<textarea class="textarea" id="args" v-model="form.args" :disabled="addInProgress"
placeholder="{}" />
</div>
<span class="help has-text-danger">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Deprecated, use <b>Command arguments for yt-dlp</b> field instead. </span>
</span>
</div>
</div>
<div class="column is-6-tablet is-12-mobile" v-if="has_data(form?.postprocessors)">
<div class="field">
<label class="label is-inline" for="postprocessors"
v-tooltip="'Things to do after download is done.'">
JSON yt-dlp Post-Processors <span class="has-text-danger">(DEPRECATED)</span>
</label>
<div class="control">
<textarea class="textarea" id="postprocessors" v-model="form.postprocessors"
:disabled="addInProgress" placeholder="[]" />
</div>
<span class="help has-text-danger">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Deprecated, use <b>Command arguments for yt-dlp</b> field instead. </span>
</span>
</div>
</div>
</div>
</div>
@ -319,55 +277,11 @@ const checkInfo = async () => {
return;
}
if (form?.cli && '' !== form.cli) {
if ((has_data(copy?.args) || has_data(copy?.postprocessors))) {
if (false === confirm('cli options are set, this will remove the JSON yt-dlp config and Post-Processors. Are you sure?')) {
toast.warning('User cancelled the operation.')
return
}
}
if (copy?.args) {
delete copy.args
}
if (copy?.postprocessors) {
delete copy.postprocessors
}
}
else {
if (typeof copy.args === 'object') {
copy.args = JSON.stringify(copy.args, null, 2);
}
if (typeof copy.postprocessors === 'object') {
copy.postprocessors = JSON.stringify(copy.postprocessors, null, 2);
}
if (copy?.args) {
try {
copy.args = JSON.parse(copy.args)
} catch (e) {
toast.error(`Invalid JSON yt-dlp config. ${e.message}`)
return;
}
}
if (copy?.postprocessors) {
try {
copy.postprocessors = JSON.parse(copy.postprocessors)
} catch (e) {
toast.error(`Invalid JSON yt-dlp Post-Processors. ${e.message}`)
return;
}
}
}
// trim all fields in copy only if they are strings
for (const key in copy) {
if (typeof copy[key] === 'string') {
copy[key] = copy[key].trim()
if (typeof copy[key] !== 'string') {
continue
}
copy[key] = copy[key].trim()
}
emitter('submit', { reference: toRaw(props.reference), preset: toRaw(copy) });
@ -417,18 +331,13 @@ const importItem = async () => {
try {
const item = JSON.parse(val)
console.log(item)
if ('preset' !== item._type) {
toast.error(`Invalid import string. Expected type 'preset', got '${item._type}'.`)
import_string.value = ''
if (item?._type || 'preset' !== item._type) {
toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`)
return
}
if (form.format || form.cli) {
if (false === confirm('This will overwrite the current form fields. Are you sure?')) {
return
}
if ((form.format || form.cli) && false === confirm('This will overwrite the current data. Are you sure?')) {
return
}
if (item.name) {

View file

@ -25,7 +25,10 @@
Dark
</label>
</div>
<p class="help">Select the color scheme for the WebUI.</p>
<p class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Select the color scheme for the WebUI.</span>
</p>
</div>
<div class="field">
@ -34,7 +37,10 @@
<input id="random_bg" type="checkbox" class="switch is-success" v-model="bg_enable">
<label for="random_bg" class="is-unselectable">&nbsp;Enable</label>
</div>
<p class="help">Use random background image from your media backends.</p>
<p class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Use random background image.</span>
</p>
</div>
<div class="field">
@ -45,7 +51,10 @@
<input id="random_bg_opacity" style="width: 100%" type="range" v-model="bg_opacity" min="0.50" max="1.00"
step="0.05">
</div>
<p class="help">How visible the background image should be.</p>
<p class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>How visible the background image should be.</span>
</p>
</div>
<div class="field" v-if="bg_enable">
@ -61,7 +70,10 @@
</span>
</button>
</div>
<p class="help">Change the displayed picture.</p>
<p class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Change the displayed picture.</span>
</p>
</div>
</div>
</div>

View file

@ -28,13 +28,13 @@
<div class="column is-12" v-if="showImport || !reference">
<label class="label is-inline" for="import_string">
<span class="icon"><i class="fa-solid fa-file-import" /></span>
Import string
</label>
<div class="field has-addons">
<div class="control has-icons-left is-expanded">
<div class="control is-expanded">
<input type="text" class="input" id="import_string" v-model="import_string" autocomplete="off">
<span class="icon is-small is-left"><i class="fa-solid fa-t" /></span>
</div>
<div class="control">
@ -52,21 +52,27 @@
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="name" v-text="'Name'" />
<label class="label is-inline" for="name">
<span class="icon"><i class="fa-solid fa-user" /></span>
Name
</label>
<div class="control has-icons-left">
<input type="text" class="input" id="name" v-model="form.name" :disabled="addInProgress">
<span class="icon is-small is-left"><i class="fa-solid fa-user" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Task name is used to identify the task in the task list and in logs.</span>
<span>The name is used to identify this specific task.</span>
</span>
</div>
</div>
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="url" v-text="'URL'" />
<label class="label is-inline" for="url">
<span class="icon"><i class="fa-solid fa-link" /></span>
URL
</label>
<div class="control has-icons-left">
<input type="url" class="input" id="url" v-model="form.url" :disabled="addInProgress">
<span class="icon is-small is-left"><i class="fa-solid fa-link" /></span>
@ -80,8 +86,11 @@
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="preset">Preset</label>
<div class="control has-icons-left">
<label class="label is-inline" for="preset">
<span class="icon"><i class="fa-solid fa-sliders" /></span>
Preset
</label>
<div class="control">
<div class="select is-fullwidth">
<select id="preset" class="is-fullwidth" v-model="form.preset"
:disabled="addInProgress || hasFormatInConfig"
@ -98,7 +107,6 @@
</optgroup>
</select>
</div>
<span class="icon is-small is-left"><i class="fa-solid fa-tv" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
@ -113,12 +121,12 @@
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="timer">
<span class="icon"><i class="fa-solid fa-clock" /></span>
CRON expression timer.
</label>
<div class="control has-icons-left">
<div class="control">
<input type="text" class="input" id="timer" placeholder="leave empty to run once every hour."
v-model="form.timer" :disabled="addInProgress">
<span class="icon is-small is-left"><i class="fa-solid fa-clock" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
@ -135,12 +143,12 @@
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="folder">
Download path
<span class="icon"><i class="fa-solid fa-folder" /></span>
Save in
</label>
<div class="control has-icons-left">
<div class="control">
<input type="text" class="input" id="folder" placeholder="Leave empty to use default download path"
v-model="form.folder" :disabled="addInProgress" list="folders">
<span class="icon is-small is-left"><i class="fa-solid fa-folder" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
@ -153,12 +161,12 @@
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="output_template">
<span class="icon"><i class="fa-solid fa-file" /></span>
Output template
</label>
<div class="control has-icons-left">
<div class="control">
<input type="text" class="input" id="output_template" :disabled="addInProgress"
placeholder="Leave empty to use default template." v-model="form.template">
<span class="icon is-small is-left"><i class="fa-solid fa-file" /></span>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
@ -174,11 +182,13 @@
<div class="column is-12">
<div class="field">
<label class="label is-inline" for="cli_options">
<span class="icon"><i class="fa-solid fa-terminal" /></span>
Command arguments for yt-dlp
</label>
<div class="control">
<input type="text" class="input" v-model="form.cli" id="cli_options" :disabled="addInProgress"
placeholder="command options to use, e.g. --no-embed-metadata --no-embed-thumbnail">
<textarea type="text" class="textarea is-pre" v-model="form.cli" id="cli_options"
:disabled="addInProgress"
placeholder="command options to use, e.g. --no-embed-metadata --no-embed-thumbnail" />
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
@ -389,7 +399,7 @@ const hasFormatInConfig = computed(() => {
return false
}
return /(?<!\w)(-f|--format)(=|:)?(?!\w)/.test(form.cli)
return /(?<!\S)(-f|--format)(=|\s)(\S+)/.test(form.cli)
})
const filter_presets = (flag = true) => config.presets.filter(item => item.default === flag)

View file

@ -154,6 +154,16 @@ onUnmounted(() => {
if (title.value) {
window.document.title = 'YTPTube'
}
if (video.value) {
try {
video.value.pause()
video.value.querySelectorAll("source").forEach(source => source.removeAttribute("src"))
video.value.load()
}
catch (e) {
console.error(e)
}
}
})
const prepareVideoPlayer = () => {

View file

@ -12,17 +12,17 @@
"web-types": "./web-types.json",
"dependencies": {
"@pinia/nuxt": "^0.10.1",
"@sentry/nuxt": "^9.9.0",
"@sentry/nuxt": "^9.10.1",
"@vueuse/core": "^13.0.0",
"@vueuse/nuxt": "^13.0.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"cron-parser": "^5.0.6",
"cronstrue": "^2.49.0",
"cron-parser": "^5.1.0",
"cronstrue": "^2.57.0",
"floating-vue": "^5.2.2",
"hls.js": "^1.4.12",
"hls.js": "^1.6.0",
"moment": "^2.30.1",
"nuxt": "^3.16.1",
"nuxt": "^3.16.2",
"pinia": "^3.0.1",
"socket.io-client": "^4.7.2",
"vue": "^3.4",

View file

@ -71,15 +71,6 @@ div.is-centered {
</header>
<div class="card-content">
<div class="content">
<template v-if="has_data(item?.args) || has_data(item.postprocessors)">
<p class="has-text-danger is-5 has-text-bold">
<span class="icon"><i class="fa-solid fa-triangle-exclamation" /></span>
<span>The preset is using deprecated options. It is recommended to update the preset to use the
new options. It will cease to work in future versions.</span>
</p>
<hr>
</template>
<p class="is-text-overflow"
v-if="item?.format && false === ['default', 'not_set'].includes(item.format)">
<span class="icon"><i class="fa-solid fa-f" /></span>
@ -307,11 +298,6 @@ const exportItem = item => {
}
})
if (has_data(data?.args) || has_data(data?.postprocessors)) {
toast.error("v1.0 presets are no longer supported target for export. Please update your preset.")
return
}
let userData = {}
for (const key of Object.keys(data)) {

File diff suppressed because it is too large Load diff