Merge pull request #41 from arabcoders/dev
initial code to support adding tasks via WebGUI
This commit is contained in:
commit
41d09d2753
17 changed files with 611 additions and 220 deletions
2
.github/workflows/delete-builds.yml
vendored
2
.github/workflows/delete-builds.yml
vendored
|
|
@ -12,8 +12,6 @@ on:
|
|||
- info
|
||||
- warning
|
||||
- debug
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
delete-builds:
|
||||
|
|
|
|||
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
|
@ -33,7 +33,8 @@
|
|||
"YTP_DOWNLOAD_PATH": "${workspaceFolder}/var/downloads",
|
||||
"YTP_TEMP_PATH": "${workspaceFolder}/var/tmp",
|
||||
"YTP_YTDL_OPTIONS_FILE": "${workspaceFolder}/var/config/ytdlp.json",
|
||||
"YTP_URL_HOST": "http://localhost:8081"
|
||||
"YTP_URL_HOST": "http://localhost:8081",
|
||||
"YTP_YTDL_DEBUG": "true",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ ENV YTP_TEMP_PATH=/tmp
|
|||
ENV YTP_DOWNLOAD_PATH=/downloads
|
||||
|
||||
RUN mkdir /config /downloads && ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && \
|
||||
apk add --update --no-cache bash ffmpeg aria2 coreutils curl shadow sqlite tzdata && \
|
||||
apk add --update --no-cache bash ffmpeg mkvtoolnix patch aria2 coreutils curl shadow sqlite tzdata && \
|
||||
useradd -u ${USER_ID:-1000} -U -d /app -s /bin/bash app && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
|
|
|
|||
1
Pipfile
1
Pipfile
|
|
@ -9,7 +9,6 @@ aiohttp = "*"
|
|||
yt-dlp = "*"
|
||||
caribou = "*"
|
||||
coloredlogs = "*"
|
||||
ffprobe-python = "*"
|
||||
aiocron = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
|
|
|||
332
Pipfile.lock
generated
332
Pipfile.lock
generated
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "9256047e3c0597d835039eadb3c589948874236a04f2392e5293ee7dd29084bc"
|
||||
"sha256": "fe1facc749dd399988b1a97d60121af298edf76c14b60ceff6aa398114b15030"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
|
@ -361,80 +361,88 @@
|
|||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"ffprobe-python": {
|
||||
"hashes": [
|
||||
"sha256:b02576228f6da0b0bb973e15e5dadb132013a4ded202dabb89ac77779e9634d6",
|
||||
"sha256:b1d3e69e65e0814e28575f63a47b836a0b7cc8f1ffedd62d27152d9e4488214a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"frozenlist": {
|
||||
"hashes": [
|
||||
"sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6",
|
||||
"sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01",
|
||||
"sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251",
|
||||
"sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9",
|
||||
"sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b",
|
||||
"sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87",
|
||||
"sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf",
|
||||
"sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f",
|
||||
"sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0",
|
||||
"sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2",
|
||||
"sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b",
|
||||
"sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc",
|
||||
"sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c",
|
||||
"sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467",
|
||||
"sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9",
|
||||
"sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1",
|
||||
"sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a",
|
||||
"sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79",
|
||||
"sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167",
|
||||
"sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300",
|
||||
"sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf",
|
||||
"sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea",
|
||||
"sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2",
|
||||
"sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab",
|
||||
"sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3",
|
||||
"sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb",
|
||||
"sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087",
|
||||
"sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc",
|
||||
"sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8",
|
||||
"sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62",
|
||||
"sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f",
|
||||
"sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326",
|
||||
"sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c",
|
||||
"sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431",
|
||||
"sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963",
|
||||
"sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7",
|
||||
"sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef",
|
||||
"sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3",
|
||||
"sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956",
|
||||
"sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781",
|
||||
"sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472",
|
||||
"sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc",
|
||||
"sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839",
|
||||
"sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672",
|
||||
"sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3",
|
||||
"sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503",
|
||||
"sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d",
|
||||
"sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8",
|
||||
"sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b",
|
||||
"sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc",
|
||||
"sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f",
|
||||
"sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559",
|
||||
"sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b",
|
||||
"sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95",
|
||||
"sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb",
|
||||
"sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963",
|
||||
"sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919",
|
||||
"sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f",
|
||||
"sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3",
|
||||
"sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1",
|
||||
"sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"
|
||||
"sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7",
|
||||
"sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98",
|
||||
"sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad",
|
||||
"sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5",
|
||||
"sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae",
|
||||
"sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e",
|
||||
"sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a",
|
||||
"sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701",
|
||||
"sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d",
|
||||
"sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6",
|
||||
"sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6",
|
||||
"sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106",
|
||||
"sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75",
|
||||
"sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868",
|
||||
"sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a",
|
||||
"sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0",
|
||||
"sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1",
|
||||
"sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826",
|
||||
"sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec",
|
||||
"sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6",
|
||||
"sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950",
|
||||
"sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19",
|
||||
"sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0",
|
||||
"sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8",
|
||||
"sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a",
|
||||
"sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09",
|
||||
"sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86",
|
||||
"sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c",
|
||||
"sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5",
|
||||
"sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b",
|
||||
"sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b",
|
||||
"sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d",
|
||||
"sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0",
|
||||
"sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea",
|
||||
"sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776",
|
||||
"sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a",
|
||||
"sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897",
|
||||
"sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7",
|
||||
"sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09",
|
||||
"sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9",
|
||||
"sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe",
|
||||
"sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd",
|
||||
"sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742",
|
||||
"sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09",
|
||||
"sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0",
|
||||
"sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932",
|
||||
"sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1",
|
||||
"sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a",
|
||||
"sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49",
|
||||
"sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d",
|
||||
"sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7",
|
||||
"sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480",
|
||||
"sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89",
|
||||
"sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e",
|
||||
"sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b",
|
||||
"sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82",
|
||||
"sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb",
|
||||
"sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068",
|
||||
"sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8",
|
||||
"sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b",
|
||||
"sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb",
|
||||
"sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2",
|
||||
"sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11",
|
||||
"sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b",
|
||||
"sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc",
|
||||
"sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0",
|
||||
"sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497",
|
||||
"sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17",
|
||||
"sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0",
|
||||
"sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2",
|
||||
"sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439",
|
||||
"sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5",
|
||||
"sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac",
|
||||
"sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825",
|
||||
"sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887",
|
||||
"sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced",
|
||||
"sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"
|
||||
],
|
||||
"markers": "python_version >= '3.8'",
|
||||
"version": "==1.4.0"
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"h11": {
|
||||
"hashes": [
|
||||
|
|
@ -745,99 +753,99 @@
|
|||
},
|
||||
"yarl": {
|
||||
"hashes": [
|
||||
"sha256:09c19e5f4404574fcfb736efecf75844ffe8610606f3fccc35a1515b8b6712c4",
|
||||
"sha256:0ab5baaea8450f4a3e241ef17e3d129b2143e38a685036b075976b9c415ea3eb",
|
||||
"sha256:0d155a092bf0ebf4a9f6f3b7a650dc5d9a5bbb585ef83a52ed36ba46f55cc39d",
|
||||
"sha256:126638ab961633f0940a06e1c9d59919003ef212a15869708dcb7305f91a6732",
|
||||
"sha256:1a0a4f3aaa18580038cfa52a7183c8ffbbe7d727fe581300817efc1e96d1b0e9",
|
||||
"sha256:1d93461e2cf76c4796355494f15ffcb50a3c198cc2d601ad8d6a96219a10c363",
|
||||
"sha256:26a1a8443091c7fbc17b84a0d9f38de34b8423b459fb853e6c8cdfab0eacf613",
|
||||
"sha256:271d63396460b6607b588555ea27a1a02b717ca2e3f2cf53bdde4013d7790929",
|
||||
"sha256:28a108cb92ce6cf867690a962372996ca332d8cda0210c5ad487fe996e76b8bb",
|
||||
"sha256:29beac86f33d6c7ab1d79bd0213aa7aed2d2f555386856bb3056d5fdd9dab279",
|
||||
"sha256:2c757f64afe53a422e45e3e399e1e3cf82b7a2f244796ce80d8ca53e16a49b9f",
|
||||
"sha256:2dad8166d41ebd1f76ce107cf6a31e39801aee3844a54a90af23278b072f1ccf",
|
||||
"sha256:2dc72e891672343b99db6d497024bf8b985537ad6c393359dc5227ef653b2f17",
|
||||
"sha256:2f3c8822bc8fb4a347a192dd6a28a25d7f0ea3262e826d7d4ef9cc99cd06d07e",
|
||||
"sha256:32435d134414e01d937cd9d6cc56e8413a8d4741dea36af5840c7750f04d16ab",
|
||||
"sha256:3cfa4dbe17b2e6fca1414e9c3bcc216f6930cb18ea7646e7d0d52792ac196808",
|
||||
"sha256:3d5434b34100b504aabae75f0622ebb85defffe7b64ad8f52b8b30ec6ef6e4b9",
|
||||
"sha256:4003f380dac50328c85e85416aca6985536812c082387255c35292cb4b41707e",
|
||||
"sha256:44e91a669c43f03964f672c5a234ae0d7a4d49c9b85d1baa93dec28afa28ffbd",
|
||||
"sha256:4a14907b597ec55740f63e52d7fee0e9ee09d5b9d57a4f399a7423268e457b57",
|
||||
"sha256:4ce77d289f8d40905c054b63f29851ecbfd026ef4ba5c371a158cfe6f623663e",
|
||||
"sha256:4d6d74a97e898c1c2df80339aa423234ad9ea2052f66366cef1e80448798c13d",
|
||||
"sha256:51382c72dd5377861b573bd55dcf680df54cea84147c8648b15ac507fbef984d",
|
||||
"sha256:525cd69eff44833b01f8ef39aa33a9cc53a99ff7f9d76a6ef6a9fb758f54d0ff",
|
||||
"sha256:53ec65f7eee8655bebb1f6f1607760d123c3c115a324b443df4f916383482a67",
|
||||
"sha256:5f74b015c99a5eac5ae589de27a1201418a5d9d460e89ccb3366015c6153e60a",
|
||||
"sha256:6280353940f7e5e2efaaabd686193e61351e966cc02f401761c4d87f48c89ea4",
|
||||
"sha256:632c7aeb99df718765adf58eacb9acb9cbc555e075da849c1378ef4d18bf536a",
|
||||
"sha256:6465d36381af057d0fab4e0f24ef0e80ba61f03fe43e6eeccbe0056e74aadc70",
|
||||
"sha256:66a6dbf6ca7d2db03cc61cafe1ee6be838ce0fbc97781881a22a58a7c5efef42",
|
||||
"sha256:6d350388ba1129bc867c6af1cd17da2b197dff0d2801036d2d7d83c2d771a682",
|
||||
"sha256:7217234b10c64b52cc39a8d82550342ae2e45be34f5bff02b890b8c452eb48d7",
|
||||
"sha256:721ee3fc292f0d069a04016ef2c3a25595d48c5b8ddc6029be46f6158d129c92",
|
||||
"sha256:72a57b41a0920b9a220125081c1e191b88a4cdec13bf9d0649e382a822705c65",
|
||||
"sha256:73cc83f918b69110813a7d95024266072d987b903a623ecae673d1e71579d566",
|
||||
"sha256:778df71c8d0c8c9f1b378624b26431ca80041660d7be7c3f724b2c7a6e65d0d6",
|
||||
"sha256:79e1df60f7c2b148722fb6cafebffe1acd95fd8b5fd77795f56247edaf326752",
|
||||
"sha256:7c86d0d0919952d05df880a1889a4f0aeb6868e98961c090e335671dea5c0361",
|
||||
"sha256:7eaf13af79950142ab2bbb8362f8d8d935be9aaf8df1df89c86c3231e4ff238a",
|
||||
"sha256:828235a2a169160ee73a2fcfb8a000709edf09d7511fccf203465c3d5acc59e4",
|
||||
"sha256:8535e111a064f3bdd94c0ed443105934d6f005adad68dd13ce50a488a0ad1bf3",
|
||||
"sha256:88d2c3cc4b2f46d1ba73d81c51ec0e486f59cc51165ea4f789677f91a303a9a7",
|
||||
"sha256:8a2538806be846ea25e90c28786136932ec385c7ff3bc1148e45125984783dc6",
|
||||
"sha256:8dab30b21bd6fb17c3f4684868c7e6a9e8468078db00f599fb1c14e324b10fca",
|
||||
"sha256:8f18a7832ff85dfcd77871fe677b169b1bc60c021978c90c3bb14f727596e0ae",
|
||||
"sha256:946db4511b2d815979d733ac6a961f47e20a29c297be0d55b6d4b77ee4b298f6",
|
||||
"sha256:96758e56dceb8a70f8a5cff1e452daaeff07d1cc9f11e9b0c951330f0a2396a7",
|
||||
"sha256:9a172c3d5447b7da1680a1a2d6ecdf6f87a319d21d52729f45ec938a7006d5d8",
|
||||
"sha256:9a5211de242754b5e612557bca701f39f8b1a9408dff73c6db623f22d20f470e",
|
||||
"sha256:9df9a0d4c5624790a0dea2e02e3b1b3c69aed14bcb8650e19606d9df3719e87d",
|
||||
"sha256:aa4643635f26052401750bd54db911b6342eb1a9ac3e74f0f8b58a25d61dfe41",
|
||||
"sha256:aed37db837ecb5962469fad448aaae0f0ee94ffce2062cf2eb9aed13328b5196",
|
||||
"sha256:af52725c7c39b0ee655befbbab5b9a1b209e01bb39128dce0db226a10014aacc",
|
||||
"sha256:b0b8c06afcf2bac5a50b37f64efbde978b7f9dc88842ce9729c020dc71fae4ce",
|
||||
"sha256:b61e64b06c3640feab73fa4ff9cb64bd8182de52e5dc13038e01cfe674ebc321",
|
||||
"sha256:b7831566595fe88ba17ea80e4b61c0eb599f84c85acaa14bf04dd90319a45b90",
|
||||
"sha256:b8bc5b87a65a4e64bc83385c05145ea901b613d0d3a434d434b55511b6ab0067",
|
||||
"sha256:b8d51817cf4b8d545963ec65ff06c1b92e5765aa98831678d0e2240b6e9fd281",
|
||||
"sha256:b9f9cafaf031c34d95c1528c16b2fa07b710e6056b3c4e2e34e9317072da5d1a",
|
||||
"sha256:bb72d2a94481e7dc7a0c522673db288f31849800d6ce2435317376a345728225",
|
||||
"sha256:c25ec06e4241e162f5d1f57c370f4078797ade95c9208bd0c60f484834f09c96",
|
||||
"sha256:c405d482c320a88ab53dcbd98d6d6f32ada074f2d965d6e9bf2d823158fa97de",
|
||||
"sha256:c4472fe53ebf541113e533971bd8c32728debc4c6d8cc177f2bff31d011ec17e",
|
||||
"sha256:c4b1efb11a8acd13246ffb0bee888dd0e8eb057f8bf30112e3e21e421eb82d4a",
|
||||
"sha256:c5f3faeb8100a43adf3e7925d556801d14b5816a0ac9e75e22948e787feec642",
|
||||
"sha256:c6f034386e5550b5dc8ded90b5e2ff7db21f0f5c7de37b6efc5dac046eb19c10",
|
||||
"sha256:c99ddaddb2fbe04953b84d1651149a0d85214780e4d0ee824e610ab549d98d92",
|
||||
"sha256:ca6b66f69e30f6e180d52f14d91ac854b8119553b524e0e28d5291a724f0f423",
|
||||
"sha256:cccdc02e46d2bd7cb5f38f8cc3d9db0d24951abd082b2f242c9e9f59c0ab2af3",
|
||||
"sha256:cd49a908cb6d387fc26acee8b7d9fcc9bbf8e1aca890c0b2fdfd706057546080",
|
||||
"sha256:cf7a4e8de7f1092829caef66fd90eaf3710bc5efd322a816d5677b7664893c93",
|
||||
"sha256:cfd77e8e5cafba3fb584e0f4b935a59216f352b73d4987be3af51f43a862c403",
|
||||
"sha256:d34c4f80956227f2686ddea5b3585e109c2733e2d4ef12eb1b8b4e84f09a2ab6",
|
||||
"sha256:d61a0ca95503867d4d627517bcfdc28a8468c3f1b0b06c626f30dd759d3999fd",
|
||||
"sha256:d81657b23e0edb84b37167e98aefb04ae16cbc5352770057893bd222cdc6e45f",
|
||||
"sha256:d92d897cb4b4bf915fbeb5e604c7911021a8456f0964f3b8ebbe7f9188b9eabb",
|
||||
"sha256:dd318e6b75ca80bff0b22b302f83a8ee41c62b8ac662ddb49f67ec97e799885d",
|
||||
"sha256:dd952b9c64f3b21aedd09b8fe958e4931864dba69926d8a90c90d36ac4e28c9a",
|
||||
"sha256:e0e7e83f31e23c5d00ff618045ddc5e916f9e613d33c5a5823bc0b0a0feb522f",
|
||||
"sha256:e0f17d1df951336a02afc8270c03c0c6e60d1f9996fcbd43a4ce6be81de0bd9d",
|
||||
"sha256:e2a16ef5fa2382af83bef4a18c1b3bcb4284c4732906aa69422cf09df9c59f1f",
|
||||
"sha256:e36021db54b8a0475805acc1d6c4bca5d9f52c3825ad29ae2d398a9d530ddb88",
|
||||
"sha256:e73db54c967eb75037c178a54445c5a4e7461b5203b27c45ef656a81787c0c1b",
|
||||
"sha256:e741bd48e6a417bdfbae02e088f60018286d6c141639359fb8df017a3b69415a",
|
||||
"sha256:f7271d6bd8838c49ba8ae647fc06469137e1c161a7ef97d778b72904d9b68696",
|
||||
"sha256:fc391e3941045fd0987c77484b2799adffd08e4b6735c4ee5f054366a2e1551d",
|
||||
"sha256:fc94441bcf9cb8c59f51f23193316afefbf3ff858460cb47b5758bf66a14d130",
|
||||
"sha256:fe34befb8c765b8ce562f0200afda3578f8abb159c76de3ab354c80b72244c41",
|
||||
"sha256:fe8080b4f25dfc44a86bedd14bc4f9d469dfc6456e6f3c5d9077e81a5fedfba7",
|
||||
"sha256:ff34cb09a332832d1cf38acd0f604c068665192c6107a439a92abfd8acf90fe2"
|
||||
"sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51",
|
||||
"sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce",
|
||||
"sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559",
|
||||
"sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0",
|
||||
"sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81",
|
||||
"sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc",
|
||||
"sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4",
|
||||
"sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c",
|
||||
"sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130",
|
||||
"sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136",
|
||||
"sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e",
|
||||
"sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec",
|
||||
"sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7",
|
||||
"sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1",
|
||||
"sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455",
|
||||
"sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099",
|
||||
"sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129",
|
||||
"sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10",
|
||||
"sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142",
|
||||
"sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98",
|
||||
"sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa",
|
||||
"sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7",
|
||||
"sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525",
|
||||
"sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c",
|
||||
"sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9",
|
||||
"sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c",
|
||||
"sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8",
|
||||
"sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b",
|
||||
"sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf",
|
||||
"sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23",
|
||||
"sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd",
|
||||
"sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27",
|
||||
"sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f",
|
||||
"sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece",
|
||||
"sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434",
|
||||
"sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec",
|
||||
"sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff",
|
||||
"sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78",
|
||||
"sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d",
|
||||
"sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863",
|
||||
"sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53",
|
||||
"sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31",
|
||||
"sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15",
|
||||
"sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5",
|
||||
"sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b",
|
||||
"sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57",
|
||||
"sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3",
|
||||
"sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1",
|
||||
"sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f",
|
||||
"sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad",
|
||||
"sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c",
|
||||
"sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7",
|
||||
"sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2",
|
||||
"sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b",
|
||||
"sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2",
|
||||
"sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b",
|
||||
"sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9",
|
||||
"sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be",
|
||||
"sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e",
|
||||
"sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984",
|
||||
"sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4",
|
||||
"sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074",
|
||||
"sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2",
|
||||
"sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392",
|
||||
"sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91",
|
||||
"sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541",
|
||||
"sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf",
|
||||
"sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572",
|
||||
"sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66",
|
||||
"sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575",
|
||||
"sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14",
|
||||
"sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5",
|
||||
"sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1",
|
||||
"sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e",
|
||||
"sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551",
|
||||
"sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17",
|
||||
"sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead",
|
||||
"sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0",
|
||||
"sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe",
|
||||
"sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234",
|
||||
"sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0",
|
||||
"sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7",
|
||||
"sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34",
|
||||
"sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42",
|
||||
"sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385",
|
||||
"sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78",
|
||||
"sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be",
|
||||
"sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958",
|
||||
"sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749",
|
||||
"sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.9.3"
|
||||
"version": "==1.9.4"
|
||||
},
|
||||
"yt-dlp": {
|
||||
"hashes": [
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ docker run -d --name ytptube -p 8081:8081 -v ./config:/config:rw -v ./downloads:
|
|||
## Run using docker-compose
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
version: "3.9"
|
||||
services:
|
||||
ytptube:
|
||||
user: "1000:1000"
|
||||
|
|
@ -51,8 +51,10 @@ services:
|
|||
ports:
|
||||
- "8081:8081"
|
||||
volumes:
|
||||
- ./config:/config
|
||||
- ./downloads:/downloads
|
||||
- ./config:/config:rw
|
||||
- ./downloads:/downloads:rw
|
||||
tmpfs:
|
||||
- /tmp
|
||||
```
|
||||
|
||||
## Configuration via environment variables
|
||||
|
|
|
|||
43
app/main.py
43
app/main.py
|
|
@ -107,9 +107,21 @@ class Main:
|
|||
f'YTPTube v{self.config.version} - listening on http://{self.config.host}:{self.config.port}'),
|
||||
)
|
||||
|
||||
async def connect(self, sid, environ):
|
||||
await self.sio.emit('all', self.serializer.encode(self.dqueue.get()), to=sid)
|
||||
await self.sio.emit('configuration', self.serializer.encode(self.config), to=sid)
|
||||
async def connect(self, sid, _):
|
||||
data: dict = {
|
||||
**self.dqueue.get(),
|
||||
"config": self.config,
|
||||
"tasks": [],
|
||||
}
|
||||
|
||||
if os.path.exists(os.path.join(self.config.config_path, 'tasks.json')):
|
||||
try:
|
||||
with open(os.path.join(self.config.config_path, 'tasks.json'), 'r') as f:
|
||||
data['tasks'] = json.load(f)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
await self.sio.emit('initial_data', self.serializer.encode(data), to=sid)
|
||||
|
||||
def addTasks(self):
|
||||
tasks_file: str = os.path.join(self.config.config_path, 'tasks.json')
|
||||
|
|
@ -118,8 +130,13 @@ class Main:
|
|||
f'No tasks file found at {tasks_file}. Skipping Tasks.')
|
||||
return
|
||||
|
||||
with open(tasks_file, 'r') as f:
|
||||
tasks = json.load(f)
|
||||
try:
|
||||
with open(tasks_file, 'r') as f:
|
||||
tasks = json.load(f)
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f'Could not load tasks file [{tasks_file}]. Error message [{str(e)}]. Skipping Tasks.')
|
||||
return
|
||||
|
||||
for task in tasks:
|
||||
if not task.get('url'):
|
||||
|
|
@ -188,6 +205,22 @@ class Main:
|
|||
|
||||
return web.Response(text=self.serializer.encode(status))
|
||||
|
||||
@self.routes.get(self.config.url_prefix + 'tasks')
|
||||
async def tasks(_: Request) -> Response:
|
||||
tasks_file: str = os.path.join(
|
||||
self.config.config_path, 'tasks.json')
|
||||
|
||||
if not os.path.exists(tasks_file):
|
||||
return web.json_response({"error": "No tasks defined."}, status=404)
|
||||
|
||||
try:
|
||||
with open(tasks_file, 'r') as f:
|
||||
tasks = json.load(f)
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
return web.json_response(tasks)
|
||||
|
||||
@self.routes.post(self.config.url_prefix + 'add_batch')
|
||||
async def add_batch(request: Request) -> Response:
|
||||
status = {}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import re
|
|||
import sys
|
||||
import coloredlogs
|
||||
|
||||
|
||||
class Config:
|
||||
config_path: str = '.'
|
||||
download_path: str = '.'
|
||||
|
|
|
|||
|
|
@ -104,9 +104,6 @@ class Download:
|
|||
|
||||
if self.debug:
|
||||
params['verbose'] = True
|
||||
params['logger'] = logging.getLogger('YTPTube-ytdl')
|
||||
else:
|
||||
params['quiet'] = True
|
||||
|
||||
if self.info.ytdlp_cookies:
|
||||
try:
|
||||
|
|
@ -122,8 +119,8 @@ class Download:
|
|||
logging.error(
|
||||
f'Invalid cookies: was provided for {self.info.title} - {str(e)}')
|
||||
|
||||
logging.debug(
|
||||
f'Downloading {self.info._id} {self.info.title}... {params}')
|
||||
logging.info(
|
||||
f'Downloading {self.info._id=} {self.info.title=}... {params=}')
|
||||
ret = yt_dlp.YoutubeDL(params=params).download([self.info.url])
|
||||
|
||||
self.status_queue.put(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import math
|
|||
import os
|
||||
from urllib.parse import quote
|
||||
from src.Utils import calcDownloadPath
|
||||
from ffprobe import FFProbe
|
||||
from src.ffprobe import FFProbe
|
||||
from src.Config import Config
|
||||
|
||||
|
||||
|
|
@ -31,14 +31,15 @@ class M3u8:
|
|||
if not os.path.exists(realFile):
|
||||
raise Exception(f"File {realFile} does not exist.")
|
||||
|
||||
metadata = FFProbe(realFile)
|
||||
try:
|
||||
ffprobe = FFProbe(realFile)
|
||||
except UnicodeDecodeError as e:
|
||||
pass
|
||||
|
||||
# video duration.
|
||||
|
||||
if not 'Duration' in metadata.metadata:
|
||||
if not 'duration' in ffprobe.metadata:
|
||||
raise Exception(f"Unable to get {realFile} duration.")
|
||||
|
||||
duration: float = self.parseDuration(metadata.metadata.get('Duration'))
|
||||
duration: float = float(ffprobe.metadata.get('duration'))
|
||||
|
||||
m3u8 = "#EXTM3U\n"
|
||||
m3u8 += "#EXT-X-VERSION:3\n"
|
||||
|
|
@ -52,11 +53,11 @@ class M3u8:
|
|||
segmentParams = {
|
||||
}
|
||||
|
||||
for stream in metadata.streams:
|
||||
if stream.codec_type == 'video':
|
||||
for stream in ffprobe.streams:
|
||||
if stream.is_video():
|
||||
if stream.codec_name not in self.ok_vcodecs:
|
||||
segmentParams['vc'] = 1
|
||||
if stream.codec_type == 'audio':
|
||||
if stream.is_audio():
|
||||
if stream.codec_name not in self.ok_acodecs:
|
||||
segmentParams['ac'] = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ class Segments:
|
|||
|
||||
ffmpegCmd.append('-i')
|
||||
ffmpegCmd.append(f'file:{tmpFile}')
|
||||
ffmpegCmd.append('-map_metadata')
|
||||
ffmpegCmd.append('-1')
|
||||
|
||||
ffmpegCmd.append('-pix_fmt')
|
||||
ffmpegCmd.append('yuv420p')
|
||||
|
|
|
|||
|
|
@ -191,6 +191,8 @@ def mergeDict(source: dict, destination: dict) -> dict:
|
|||
source=value,
|
||||
destination=destination_copy.setdefault(key, {})
|
||||
)
|
||||
elif isinstance(value, list) and isinstance(destination_key_value, list):
|
||||
destination_copy[key] = destination_key_value + value
|
||||
else:
|
||||
destination_copy[key] = value
|
||||
|
||||
|
|
@ -202,6 +204,7 @@ def mergeConfig(config: dict, new_config: dict) -> dict:
|
|||
|
||||
ignored_keys: tuple = (
|
||||
'cookiefile',
|
||||
'download_archive'
|
||||
'paths',
|
||||
'outtmpl',
|
||||
'progress_hooks',
|
||||
|
|
|
|||
247
app/src/ffprobe.py
Normal file
247
app/src/ffprobe.py
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
"""
|
||||
Python wrapper for ffprobe command line tool. ffprobe must exist in the path.
|
||||
"""
|
||||
import functools
|
||||
import json
|
||||
import operator
|
||||
import os
|
||||
import pipes
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
|
||||
class FFProbeError(Exception):
|
||||
pass
|
||||
|
||||
class FFStream:
|
||||
"""
|
||||
An object representation of an individual stream in a multimedia file.
|
||||
"""
|
||||
|
||||
def __init__(self, json_data: dict):
|
||||
for key, val in json_data.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
try:
|
||||
self.__dict__['framerate'] = round(
|
||||
functools.reduce(
|
||||
operator.truediv, map(int, self.__dict__.get(
|
||||
'avg_frame_rate', '').split('/'))
|
||||
)
|
||||
)
|
||||
except ValueError:
|
||||
self.__dict__['framerate'] = None
|
||||
except ZeroDivisionError:
|
||||
self.__dict__['framerate'] = 0
|
||||
|
||||
def __repr__(self):
|
||||
if not 'codec_long_name' in self.__dict__:
|
||||
self.codec_long_name = self.__dict__.get('codec_name', '')
|
||||
|
||||
if self.is_video():
|
||||
return f"<Stream: #{self.index} [{self.codec_type}] {self.codec_long_name}, {self.framerate}, ({self.width}x{self.height})>"
|
||||
|
||||
if self.is_audio():
|
||||
return f"<Stream: #{self.index} [{self.codec_type}] {self.codec_long_name}, channels: {self.channels} ({self.channel_layout}), " \
|
||||
"{sample_rate}Hz> "
|
||||
|
||||
if self.is_subtitle() or self.is_attachment():
|
||||
return f"<Stream: #{self.index} [{self.codec_type}] {self.codec_long_name}>"
|
||||
|
||||
return f"<Stream: #{self.index} [{self.codec_type}]>"
|
||||
|
||||
def is_audio(self):
|
||||
"""
|
||||
Is this stream labelled as an audio stream?
|
||||
"""
|
||||
return self.__dict__.get('codec_type', None) == 'audio'
|
||||
|
||||
def is_video(self):
|
||||
"""
|
||||
Is the stream labelled as a video stream.
|
||||
"""
|
||||
return self.__dict__.get('codec_type', None) == 'video'
|
||||
|
||||
def is_subtitle(self):
|
||||
"""
|
||||
Is the stream labelled as a subtitle stream.
|
||||
"""
|
||||
return self.__dict__.get('codec_type', None) == 'subtitle'
|
||||
|
||||
def is_attachment(self):
|
||||
"""
|
||||
Is the stream labelled as a attachment stream.
|
||||
"""
|
||||
return self.__dict__.get('codec_type', None) == 'attachment'
|
||||
|
||||
def frame_size(self):
|
||||
"""
|
||||
Returns the pixel frame size as an integer tuple (width,height) if the stream is a video stream.
|
||||
Returns None if it is not a video stream.
|
||||
"""
|
||||
size = None
|
||||
if self.is_video():
|
||||
width = self.__dict__['width']
|
||||
height = self.__dict__['height']
|
||||
|
||||
if width and height:
|
||||
try:
|
||||
size = (int(width), int(height))
|
||||
except ValueError:
|
||||
raise FFProbeError(
|
||||
"None integer size {}:{}".format(width, height))
|
||||
else:
|
||||
return None
|
||||
|
||||
return size
|
||||
|
||||
def pixel_format(self):
|
||||
"""
|
||||
Returns a string representing the pixel format of the video stream. e.g. yuv420p.
|
||||
Returns none is it is not a video stream.
|
||||
"""
|
||||
return self.__dict__.get('pix_fmt', None)
|
||||
|
||||
def frames(self):
|
||||
"""
|
||||
Returns the length of a video stream in frames. Returns 0 if not a video stream.
|
||||
"""
|
||||
if self.is_video() or self.is_audio():
|
||||
try:
|
||||
frame_count = int(self.__dict__.get('nb_frames', ''))
|
||||
except ValueError:
|
||||
raise FFProbeError('None integer frame count')
|
||||
else:
|
||||
frame_count = 0
|
||||
|
||||
return frame_count
|
||||
|
||||
def duration_seconds(self):
|
||||
"""
|
||||
Returns the runtime duration of the video stream as a floating point number of seconds.
|
||||
Returns 0.0 if not a video stream.
|
||||
"""
|
||||
if self.is_video() or self.is_audio():
|
||||
try:
|
||||
duration = float(self.__dict__.get('duration', ''))
|
||||
except ValueError:
|
||||
raise FFProbeError('None numeric duration')
|
||||
else:
|
||||
duration = 0.0
|
||||
|
||||
return duration
|
||||
|
||||
def language(self):
|
||||
"""
|
||||
Returns language tag of stream. e.g. eng
|
||||
"""
|
||||
return self.__dict__.get('TAG:language', None)
|
||||
|
||||
def codec(self):
|
||||
"""
|
||||
Returns a string representation of the stream codec.
|
||||
"""
|
||||
return self.__dict__.get('codec_name', None)
|
||||
|
||||
def codec_description(self):
|
||||
"""
|
||||
Returns a long representation of the stream codec.
|
||||
"""
|
||||
return self.__dict__.get('codec_long_name', None)
|
||||
|
||||
def codec_tag(self):
|
||||
"""
|
||||
Returns a short representative tag of the stream codec.
|
||||
"""
|
||||
return self.__dict__.get('codec_tag_string', None)
|
||||
|
||||
def bit_rate(self):
|
||||
"""
|
||||
Returns bit_rate as an integer in bps
|
||||
"""
|
||||
try:
|
||||
return int(self.__dict__.get('bit_rate', ''))
|
||||
except ValueError:
|
||||
raise FFProbeError('None integer bit_rate')
|
||||
|
||||
class FFProbe:
|
||||
"""
|
||||
FFProbe wraps the ffprobe command and pulls the data into an object form::
|
||||
metadata=FFProbe('multimedia-file.mov')
|
||||
"""
|
||||
audio: list[FFStream] = []
|
||||
attachment: list[FFStream] = []
|
||||
streams: list[FFStream] = []
|
||||
subtitle: list[FFStream] = []
|
||||
video: list[FFStream] = []
|
||||
metadata: dict = {}
|
||||
path_to_video: str = ''
|
||||
|
||||
def __init__(self, path_to_video):
|
||||
self.path_to_video = path_to_video
|
||||
|
||||
try:
|
||||
with open(os.devnull, 'w') as tempf:
|
||||
subprocess.check_call(
|
||||
["ffprobe", "-h"], stdout=tempf, stderr=tempf)
|
||||
except FileNotFoundError:
|
||||
raise IOError('ffprobe not found.')
|
||||
|
||||
if os.path.isfile(self.path_to_video):
|
||||
cmd: list = [
|
||||
"ffprobe -v quiet -of json -show_format -show_streams " +
|
||||
pipes.quote(self.path_to_video)
|
||||
]
|
||||
|
||||
p = subprocess.Popen(
|
||||
args=cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
shell=True
|
||||
)
|
||||
p.wait()
|
||||
data, err = p.communicate()
|
||||
if p.returncode == 0:
|
||||
parsed: dict = json.loads(data.decode('utf-8'))
|
||||
else:
|
||||
raise FFProbeError(f"FFProbe error: {err}")
|
||||
|
||||
stream = False
|
||||
self.streams = []
|
||||
self.video = []
|
||||
self.audio = []
|
||||
self.subtitle = []
|
||||
self.attachment = []
|
||||
|
||||
for stream in parsed['streams'] if 'streams' in parsed else []:
|
||||
self.streams.append(FFStream(stream))
|
||||
|
||||
self.metadata = parsed['format'] if 'format' in parsed else {}
|
||||
|
||||
for stream in self.streams:
|
||||
if stream.is_audio():
|
||||
self.audio.append(stream)
|
||||
elif stream.is_video():
|
||||
self.video.append(stream)
|
||||
elif stream.is_subtitle():
|
||||
self.subtitle.append(stream)
|
||||
elif stream.is_attachment():
|
||||
self.attachment.append(stream)
|
||||
else:
|
||||
raise IOError('No such media file ' + self.path_to_video)
|
||||
|
||||
def __repr__(self):
|
||||
return "<FFprobe: {metadata}, {video}, {audio}, {subtitle}, {attachment}>".format(**vars(self))
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: ffprobe.py <path to media file>")
|
||||
sys.exit(1)
|
||||
|
||||
ff = FFProbe(sys.argv[1])
|
||||
print(ff)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<PageHeader :config="config" @toggleForm="addForm = !addForm" />
|
||||
<PageHeader :config="config" @toggleForm="addForm = !addForm" @toggleTasks="showTasks = !showTasks" />
|
||||
<formAdd v-if="addForm" :config="config" @addItem="addItem" />
|
||||
<pageTasks v-if="showTasks" :tasks="config.tasks" @removeTask="deleteTask" />
|
||||
<DownloadingList :config="config" :queue="downloading" @deleteItem="deleteItem" />
|
||||
<PageCompleted :config="config" :completed="completed" @deleteItem="deleteItem" @addItem="addItem"
|
||||
@playItem="playItem" />
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
import { onMounted, reactive, ref } from 'vue'
|
||||
import PageHeader from './components/Page-Header'
|
||||
import formAdd from './components/Form-Add'
|
||||
import pageTasks from './components/Page-Tasks'
|
||||
import DownloadingList from './components/Page-Downloading'
|
||||
import PageCompleted from './components/Page-Completed'
|
||||
import PageFooter from './components/Page-Footer'
|
||||
|
|
@ -34,12 +36,14 @@ const bus = useEventBus('item_added');
|
|||
const config = reactive({
|
||||
isConnected: false,
|
||||
app: {},
|
||||
tasks: [],
|
||||
});
|
||||
|
||||
const downloading = reactive({});
|
||||
const completed = reactive({});
|
||||
const video_link = ref('');
|
||||
const addForm = useStorage('addForm', true)
|
||||
const showTasks = useStorage('showTasks', false)
|
||||
|
||||
onMounted(() => {
|
||||
const socket = io(process.env.VUE_APP_BASE_URL, {
|
||||
|
|
@ -49,19 +53,17 @@ onMounted(() => {
|
|||
socket.on('connect', () => config.isConnected = true);
|
||||
socket.on('disconnect', () => config.isConnected = false);
|
||||
|
||||
socket.on('all', stream => {
|
||||
socket.on('initial_data', stream => {
|
||||
const initialData = JSON.parse(stream);
|
||||
for (const key in initialData) {
|
||||
const element = initialData[key];
|
||||
if (key === 'queue') {
|
||||
for (const id in element) {
|
||||
downloading[id] = element[id];
|
||||
}
|
||||
} else if (key === 'done') {
|
||||
for (const id in element) {
|
||||
completed[id] = element[id];
|
||||
}
|
||||
}
|
||||
config.app = initialData['config'];
|
||||
config.tasks = initialData['tasks'];
|
||||
|
||||
for (const id in initialData['queue']) {
|
||||
downloading[id] = initialData['queue'][id];
|
||||
}
|
||||
|
||||
for (const id in initialData['done']) {
|
||||
completed[id] = initialData['done'][id];
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -104,10 +106,6 @@ onMounted(() => {
|
|||
data.deleting = dl?.deleting;
|
||||
downloading[data._id] = data;
|
||||
});
|
||||
|
||||
socket.on('configuration', stream => {
|
||||
config.app = JSON.parse(stream);
|
||||
});
|
||||
});
|
||||
|
||||
const deleteItem = (type, item) => {
|
||||
|
|
@ -172,6 +170,14 @@ const playItem = (item) => {
|
|||
video_link.value = config.app.url_host + config.app.url_prefix + baseDir + encodeURIComponent(item.filename);
|
||||
};
|
||||
|
||||
const deleteTask = (indexNumber, item) => {
|
||||
if (!confirm(`Are you sure you want to delete this task ${item?.name || item.url}?`)) {
|
||||
return;
|
||||
}
|
||||
config.tasks = config.tasks.filter((_, index) => {
|
||||
return index !== indexNumber;
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
<template>
|
||||
<nav class="navbar is-mobile is-dark">
|
||||
<div class="navbar-brand pl-5">
|
||||
<a class="navbar-item" href="#">
|
||||
<a class="navbar-item has-tooltip-bottom" :class="config.isConnected ? 'has-text-success' : 'has-text-danger'"
|
||||
:data-tooltip="config.isConnected ? 'Connected' : 'Connecting'" href="javascript:void(0);">
|
||||
<b>YTPTube</b>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<button class="button is-dark has-tooltip-bottom" :data-tooltip="config.isConnected ? 'Connected' : 'Connecting'">
|
||||
<span class="icon-text" :class="config.isConnected ? 'has-text-success' : 'has-text-danger'">
|
||||
<span class="icon">
|
||||
<font-awesome-icon :icon="config.isConnected ? 'fa-solid fa-wifi' : 'fa-solid fa-signal'" />
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<button data-tooltip="Show/Hide Add Form" class="button is-dark has-tooltip-bottom" @click="$emit('toggleForm')">
|
||||
<font-awesome-icon icon="fa-solid fa-plus" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<button data-tooltip="Show/Hide Tasks" class="button is-dark has-tooltip-bottom" @click="$emit('toggleTasks')">
|
||||
<font-awesome-icon icon="fa-solid fa-tasks" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<button data-tooltip="Switch to Light theme" class="button is-dark has-tooltip-bottom"
|
||||
@click="selectedTheme = 'light'" v-if="selectedTheme == 'dark'">🌞</button>
|
||||
|
|
@ -37,7 +33,7 @@ import { useStorage } from '@vueuse/core'
|
|||
|
||||
const selectedTheme = useStorage('theme', (() => window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')());
|
||||
|
||||
defineEmits(['toggleForm'])
|
||||
defineEmits(['toggleForm', 'toggleTasks'])
|
||||
|
||||
defineProps({
|
||||
config: {
|
||||
|
|
@ -115,6 +111,7 @@ watch(selectedTheme, (value) => {
|
|||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
|
|
|
|||
96
frontend/src/components/Page-Tasks.vue
Normal file
96
frontend/src/components/Page-Tasks.vue
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<div class="mt-3 is-unselectable">
|
||||
<p class="title is-3">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<font-awesome-icon icon="fa-solid fa-tasks" />
|
||||
</span>
|
||||
<span>Tasks</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="subtitle is-4 has-text-danger">
|
||||
This section is not working yet.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12 has-text-right">
|
||||
<button class="button is-primary" @click="$emit('task_new')">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<font-awesome-icon icon="fa-solid fa-plus" />
|
||||
</span>
|
||||
<span>New Task</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="column is-12">
|
||||
<div class="table-container">
|
||||
<table class="table is-bordered is-narrow is-hoverable is-striped is-fullwidth is-fixed">
|
||||
<thead class="has-text-centered">
|
||||
<tr>
|
||||
<th class="has-text-centered" width="20%">Name</th>
|
||||
<th class="has-text-centered" width="50%">URL</th>
|
||||
<th class="has-text-centered" width="20%">Timer</th>
|
||||
<th class="has-text-centered" width="10%">Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item, key in tasks" :key="key">
|
||||
<td class="is-text-overflow has-text-centered" v-if="item.name">{{ item.name }}</td>
|
||||
<td class="has-text-centered" v-else>Not set</td>
|
||||
<td class="is-text-overflow">{{ item.url }}</td>
|
||||
<td class="has-text-centered" v-if="item.timer">{{ item.timer }}</td>
|
||||
<td class="has-text-centered" v-else>once an hour</td>
|
||||
<td class="has-text-centered">
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-expanded">
|
||||
<button class="button is-small is-danger" @click="$emit('removeTask', key, item)"
|
||||
data-tooltip="Remove task">
|
||||
<span class="icon">
|
||||
<font-awesome-icon icon="fa-solid fa-trash" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="control is-expanded">
|
||||
<button class="button is-small is-info" data-tooltip="Modify task">
|
||||
<span class="icon">
|
||||
<font-awesome-icon icon="fa-solid fa-cog" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="tasks.length < 1">
|
||||
<td colspan="4" class="has-text-centered has-text-danger">
|
||||
No tasks are defined.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
defineEmits(['task_new', 'task_add', 'removeTask', 'tasks_updated'])
|
||||
|
||||
defineProps({
|
||||
tasks: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
table.is-fixed {
|
||||
table-layout: fixed;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -5,7 +5,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
|
|||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
import {
|
||||
faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleCheck, faRotateRight, faDownload, faUpRightFromSquare,
|
||||
faSpinner, faWifi, faSignal, faArrowUp, faArrowDown,
|
||||
faSpinner, faArrowUp, faArrowDown, faTasks
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { faSquare, faSquareCheck } from '@fortawesome/free-regular-svg-icons'
|
||||
|
|
@ -17,7 +17,7 @@ import './assets/css/style.css'
|
|||
import '@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css';
|
||||
|
||||
library.add(faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleCheck, faRotateRight, faDownload, faUpRightFromSquare,
|
||||
faSquare, faSquareCheck, faSpinner, faWifi, faSignal, faArrowUp, faArrowDown,)
|
||||
faSquare, faSquareCheck, faSpinner, faArrowUp, faArrowDown, faTasks,)
|
||||
const app = createApp(App);
|
||||
|
||||
app.config.globalProperties.capitalize = s => s && s[0].toUpperCase() + s.slice(1);
|
||||
|
|
|
|||
Loading…
Reference in a new issue