Compare commits

...

779 Commits

Author SHA1 Message Date
Sean b246dbcd75
Merge pull request #4 from n8detar/master
Added re-scan subs and start download functionality to downloads page
2022-09-10 21:58:49 -05:00
Sean 40676990d2
Merge pull request #9 from tubearchivist/renovate/next-auth-4.x
fix(deps): update dependency next-auth to v4.7.0
2022-09-10 21:58:21 -05:00
Sean 7c4f7d8051
Merge pull request #10 from tubearchivist/renovate/react-query-3.x
fix(deps): update dependency react-query to v3.39.1
2022-09-10 21:58:12 -05:00
renovate[bot] 9b68493c9f
fix(deps): update dependency next-auth to v4.7.0 2022-06-28 17:03:15 +00:00
Renovate Bot 4865b75e51
fix(deps): update dependency react-query to v3.39.1 2022-05-29 18:27:07 +00:00
n8detar f42efe69be Merge branch 'master' of github.com:n8detar/tubearchivist-frontend 2022-04-30 11:25:09 -07:00
n8detar b5b076793d Added no token message. 2022-04-30 11:24:45 -07:00
Nathan DeTar 102eaab1f5
Merge branch 'tubearchivist:master' into master 2022-04-30 11:21:16 -07:00
Sean 870a0a901e
Merge pull request #3 from tubearchivist/docu
better CORS solution, note about unstable build
2022-04-30 12:42:17 -05:00
Sean 7dcba72cc4
Merge pull request #7 from tubearchivist/renovate/pin-dependencies
chore(deps): pin dependencies
2022-04-23 21:08:07 -05:00
Renovate Bot f7c7ba7c73
chore(deps): pin dependencies 2022-04-24 01:56:46 +00:00
Sean Norwood 950a7fe558 Merge branch 'master' of github.com:tubearchivist/tubearchivist-frontend 2022-04-24 01:55:06 +00:00
Sean Norwood 718fd73b8b chore: remove unused dependency 2022-04-24 01:54:24 +00:00
Sean 4a9259727e
Merge pull request #6 from tubearchivist/feat/ci
ci: add basic install,build,lint ci
2022-04-23 20:52:05 -05:00
Sean Norwood 097fc4fa51 chore: add prettier config 2022-04-24 01:50:10 +00:00
Sean Norwood 680fbafb9c ci: add precommit hooks 2022-04-24 01:49:15 +00:00
Sean Norwood 20acdfa2ae Merge branch 'master' of github.com:tubearchivist/tubearchivist-frontend into feat/ci 2022-04-24 01:42:02 +00:00
n8detar 30f0dbb81a Switched to type `Tasks` & message cleanup 2022-04-23 11:38:16 -07:00
n8detar e41ac9276b Switched to type `Tasks` & message cleanup 2022-04-23 11:37:46 -07:00
n8detar 0ea19defcc Added type `Tasks` 2022-04-23 11:36:55 -07:00
Sean Norwood 42362ba14b chore: fix some misc syntax bugs 2022-04-23 18:10:12 +00:00
Sean Norwood 2734edcac5 ci: remove node v18 since next-auth doesn't support it 2022-04-23 17:58:27 +00:00
Sean Norwood 8c294cec4d ci: add basic install,build,lint ci 2022-04-23 17:52:08 +00:00
Sean 11643afe4b
Merge pull request #2 from tubearchivist/renovate/configure
Configure Renovate
2022-04-23 12:44:17 -05:00
Sean Norwood ca36008df9 chore: update renovate config 2022-04-23 17:43:55 +00:00
n8detar 883f5dc801 Added rescan subs and start download functionality 2022-04-23 09:40:51 -07:00
simon 9401bf8f7f
better CORS solution, note about unstable build 2022-04-23 22:05:57 +07:00
Renovate Bot dbe21b49fa
chore(deps): add renovate.json 2022-04-22 18:17:08 +00:00
simon 2a69e0e319
remove test line 2022-04-23 00:55:31 +07:00
simon 1de6b9e83e
test to push to master 2022-04-23 00:52:28 +07:00
simon 74f562c3cf
add header 2022-04-23 00:46:30 +07:00
simon 7b5903c12c
help with errors and environment setup 2022-04-23 00:41:50 +07:00
Sean Norwood 2462eb9686 chore: remove tubearchivist backend files 2022-04-22 12:34:56 -05:00
Sean 9b4b75a11a
Merge pull request #3 from n8detar/feat/react-frontend
Downloads Page
2022-04-22 12:30:07 -05:00
n8detar 5bf24d1576 Improved channel subscribe button. 2022-04-22 09:19:20 -07:00
n8detar 0f5ad25a92 Format number of subscribers 2022-04-22 09:09:55 -07:00
n8detar 4082fe1e1c Fix for the fix for the no videos queued message. 2022-04-22 09:02:23 -07:00
n8detar 6e38df958d Fix for videos queued message. 2022-04-22 09:01:14 -07:00
n8detar b41348d961 Reset page number when switching to ignored. 2022-04-21 12:18:36 -07:00
n8detar 3d68f52d5d Change cursor type for pagination buttons. 2022-04-21 12:15:18 -07:00
n8detar bcfd89be02 Pagination improvments. 2022-04-21 12:02:03 -07:00
n8detar d948b3e8e7 Pagination improvements 2022-04-21 10:53:14 -07:00
n8detar e5fc4a3282 Added WIP Pagination 2022-04-21 10:39:48 -07:00
n8detar 3c8434eb42 Improved Error Handling 2022-04-21 09:44:59 -07:00
n8detar 3b908b29f0 Prevent connect message while loading 2022-04-21 09:08:48 -07:00
n8detar 0c0b58af71 Handle TA offline 2022-04-21 09:03:36 -07:00
n8detar b300a74669 Fix error handling when adding download to queue 2022-04-21 08:56:48 -07:00
n8detar eccf27a434 Error Handling WIP 2022-04-21 08:15:57 -07:00
n8detar c6571674a8 Merge branch 'testing' of github.com:n8detar/tubearchivist into feat/react-frontend 2022-04-21 08:02:54 -07:00
n8detar bd04ab3a72 Merge branch 'feat/react-frontend' of github.com:insuusvenerati/tubearchivist into feat/react-frontend 2022-04-21 07:57:29 -07:00
simon b76f38e0bc
API: fix downloads list sort 2022-04-21 05:45:55 +07:00
Sean Norwood b44390559e fix: video urls 2022-04-20 19:38:39 +00:00
n8detar d7c45ba966 Handle no API endpoint and API failures. 2022-04-20 11:33:58 -07:00
n8detar db148dcdf3 Merge remote-tracking branch 'TA/testing' into feat/react-frontend 2022-04-20 10:27:27 -07:00
n8detar 54882b744d Fixed button margin & pagination testing 2022-04-20 09:37:33 -07:00
n8detar 950f83cd52 Added `button-padding` class 2022-04-20 09:14:29 -07:00
simon 1477370376
init data instead of class attribute 2022-04-20 22:51:30 +07:00
simon 04fc6ed26a
API: add pagination 2022-04-20 22:43:07 +07:00
simon 7305216485
add link to ES documentation for disk usage 2022-04-19 08:07:47 +07:00
n8detar 3430d5ae17 Notifications groundwork 2022-04-18 11:57:01 -07:00
n8detar 494d97ee60 Added Ignore and Add back to queue functionality 2022-04-18 10:51:41 -07:00
n8detar db14aabcac Added Forget/Delete button functionality 2022-04-18 10:44:03 -07:00
n8detar 1dcc4c2e78 Removed No videos message when looking at ignored 2022-04-18 10:36:19 -07:00
n8detar 2159b1d39d Add delete all ignored & pending 2022-04-18 10:33:14 -07:00
n8detar 66c2799e7e Switch to TA error notification 2022-04-18 10:24:53 -07:00
n8detar a5c50fd690 Added invalid input message. 2022-04-18 09:00:53 -07:00
simon 3147df20da
skip subtitle segments without duration, take 2 2022-04-18 11:52:13 +07:00
n8detar 46c6e7e925 Use filter API for download queue 2022-04-17 16:32:10 -07:00
n8detar 93822fc43c API changes 2022-04-17 12:47:18 -07:00
Sean Norwood 374229d61d Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-17 17:15:04 +00:00
Sean d6b34a7b30
Merge pull request #2 from n8detar/feat/react-frontend
Added download page.
2022-04-17 12:13:21 -05:00
simon d086f63861
API: sort and query filter download view, delete by filter 2022-04-17 20:10:49 +07:00
simon 40bb3e880e
API: implement status update and delete of item in queue 2022-04-17 19:15:40 +07:00
Sean Norwood 5e5680a661 Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-17 04:30:34 +00:00
Nathan DeTar fa8404d73e
Adjust to API 2022-04-16 21:02:07 -07:00
Nathan DeTar 2b39f2439d
Adjust to API 2022-04-16 21:01:10 -07:00
simon eb6d6be3b9
handle 404 in channel-video and playlist-video api view 2022-04-17 10:08:24 +07:00
simon b8ca324aaf
add playlist-video and channel-video api views 2022-04-17 09:58:18 +07:00
Nathan DeTar 483db30798
Merge branch 'tubearchivist:master' into feat/react-frontend 2022-04-16 18:12:43 -07:00
n8detar 72cbf2ba04 Added add to download queue function. 2022-04-16 18:03:17 -07:00
Sean Norwood c3a9c23736 chore: add ts types and /video/id page 2022-04-17 00:49:52 +00:00
Sean Norwood df30173ba3 chore: more code cleanup and use better env vars 2022-04-17 00:49:20 +00:00
Sean Norwood 526c98ca8a Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-17 00:45:58 +00:00
n8detar add20a4a6a Use DefaultView from API. 2022-04-16 15:39:33 -07:00
n8detar d9fae64afb Download queue list & Show ignored functionality 2022-04-16 15:26:30 -07:00
simon 7d45d23767
process ta_download search results 2022-04-17 05:21:35 +07:00
simon 9224696e33 Merge branch 'api-fix' into testing 2022-04-17 05:03:05 +07:00
Sean Norwood cf11ae53d4 Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-16 19:16:07 +00:00
n8detar 7291511eb8 Switch img to NextImage. 2022-04-16 10:51:47 -07:00
simon 50f4e0e5b7
skip base64 generator, hotfix 2022-04-16 13:08:32 +07:00
simon fcdb31e848
bump release number 2022-04-16 09:30:40 +07:00
simon 70ac33928f
update roadmap 2022-04-16 09:29:49 +07:00
n8detar d3fd032a81 Added download page. 2022-04-15 19:07:29 -07:00
simon 59f1c111aa
extend timeout for sleep interval 2022-04-16 08:58:35 +07:00
Nathan DeTar 1cf82bdbbd
Cleanup & Prevent repeat of skip message. (#227)
* Cleanup & Prevent repeat of skip message.

* skip sponsorblock segments if not indexed

* disable onVideoProgress sb check if not indexed

Co-authored-by: simon <simobilleter@gmail.com>
2022-04-16 08:53:15 +07:00
Sean Norwood e71b909a27 Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-16 00:17:59 +00:00
Sean Norwood f2d537a467 Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-15 18:47:15 +00:00
simon ef803a157a
standard api key processor for list and single views 2022-04-16 00:37:56 +07:00
simon 51ceffd58f
update all links to tubearchivist organization 2022-04-15 22:35:13 +07:00
simon f05c292170
add new home message 2022-04-15 21:35:44 +07:00
simon b819c2f723
merge testing into master before repo migration 2022-04-15 21:16:28 +07:00
simon fe610fdaca
add log output for backup progress 2022-04-15 20:41:38 +07:00
simon 2fc4ed419e
add note about tubearchivist-es to readme 2022-04-15 20:07:59 +07:00
simon c84fbdfb9c
fix _check_get_sb for per channel deactivate 2022-04-15 20:04:01 +07:00
simon 7231b5d245
implement unset of sb per channel overwrite 2022-04-15 20:03:15 +07:00
simon 3d16954c79
change to tubearchivist-es, add comments 2022-04-15 17:56:17 +07:00
simon c5746089f5
auto dedect new es version 2022-04-15 17:46:16 +07:00
Ainsey11 dc29c6718c
Fix: #210 - Download UI Box being removed when file is still processing (#219)
* Fix: https://github.com/bbilly1/tubearchivist/issues/210 - Download UI element is removed when the file has finished downloading, but is still being moved on disk to the storage destination. This means the user is presented with nothing in the UI yet the file is still being processed.

* FIX: Resolving linting issue

* FIX: Set moving message to never expire and then a second message after the move with a 4 second expiry timer.

Co-authored-by: Rob Ainsworth <roba@immjsystems.com>
2022-04-15 15:21:24 +07:00
simon d4b1d97f5c
add mappings for sponsorblock segments 2022-04-15 15:16:31 +07:00
simon f71644c8bc
guarantee stats in mapping for sort 2022-04-15 15:03:43 +07:00
Sean Norwood 8a554f69d1 chore: organize and optimize some code 2022-04-14 20:34:38 +00:00
Sean Norwood d86fcd4a8f chore: fix sticky footer issue 2022-04-14 20:33:53 +00:00
Sean Norwood d305c94989 Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-14 19:45:23 +00:00
Sean Norwood 5913d31b8f chore: add comments to example .env 2022-04-14 14:49:56 +00:00
Sean Norwood eec616f1af chore: add example .env file 2022-04-14 14:46:08 +00:00
simon 80c76c22ca
add wiki documentation for sponsorblock integration 2022-04-14 16:49:27 +07:00
simon 4853a8b12b
limit video_daily to below 10k in daily refresh 2022-04-14 16:18:08 +07:00
simon fd00369859
fix last page pagination link building error, #221 2022-04-14 16:04:21 +07:00
simon a8a7edb93e
skip subtilte events without duration, #196 2022-04-14 15:53:10 +07:00
Sean Norwood b62b903ed5 chore: misc changes
Clean up some code inconsistencies
Try to handle async stuff better
2022-04-13 20:53:39 +00:00
Sean Norwood 8b86c327c5 chore: don't run client side query until token is available 2022-04-13 15:09:08 -05:00
Sean Norwood 85f1a62e84 chore: protect pages with auth 2022-04-13 18:08:32 +00:00
Sean Norwood 6bf77b05aa chore: add login page 2022-04-13 18:08:19 +00:00
Sean Norwood 3efd651db3 feat: add playlist page 2022-04-13 18:08:07 +00:00
Sean Norwood 2e4cd0b914 chore: add devcontainer config for vscode 2022-04-13 16:12:05 +00:00
Sean Norwood 6e4bb5b805 Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-13 15:10:33 +00:00
simon 5f90d21234
update roadmap, extend user created playlist functionality 2022-04-13 16:13:43 +07:00
simon c59c1fc007
improved sponsorblock indexing, #build
Changed:
- improved more flexible sb timestamps indexing
- fixed per channel sb activate/deactivate
- improved Dockerfile with multi stage build
- added playlist API list view
2022-04-13 15:56:05 +07:00
simon 3007e02fe5
fix per channel sb integration 2022-04-13 15:53:00 +07:00
Nathan DeTar 39c4bd8883
Adjust to API changes for SB integration. (#222) 2022-04-13 10:30:32 +07:00
simon cd3e9dd024
add playlist API list view 2022-04-13 09:51:15 +07:00
Sean Norwood e4f5741c0f chore: channel data can be undefined for now 2022-04-12 19:24:57 +00:00
Sean Norwood 678aacb696 chore: use react query with initial state loaded from nextjs 2022-04-12 19:18:16 +00:00
Sean Norwood bdc0c93c09 Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-12 15:06:18 +00:00
simon 8edde732b6
improved sponsorblock key with additional metadata 2022-04-12 17:47:13 +07:00
simon 3df1df8b5a
set explicit mappings for sponsorblock key 2022-04-12 17:45:06 +07:00
simon 1c233dd4f4
move install requirements after ffmpeg curl for better cache use 2022-04-11 17:58:19 +07:00
simon 385f53372c
bump django 2022-04-11 17:57:29 +07:00
simon f79d30ab54 multi stage build Dockerfile for python wheel 2022-04-11 00:05:03 +07:00
Ainsey11 a8ee0da2b8
Addiing clarification on when the speed limit change takes effect (#218)
Relates to https://github.com/bbilly1/tubearchivist/issues/216 

Speed limits work as designed, however the wording in the tooltip is a little unclear to newer users about when the speed limit takes effect on their downloads
2022-04-10 22:33:00 +07:00
simon 0c84386209
add link to installation.md to home 2022-04-10 16:53:07 +07:00
simon f52a00937f
renamed to generic installation.md wiki page 2022-04-10 16:51:40 +07:00
simon 4b33559212
build failure workaround, upgrade pip and setuptools 2022-04-10 16:45:50 +07:00
simon fd5de99674
implement sponsorblock skipping, #build
Changed:
- sponsorblock frontent implementation, #208
- per channel sponsorblock
- improved scheduler input validation
2022-04-10 16:26:47 +07:00
simon 582011d247 Merge branch 'master' into testing 2022-04-10 16:25:48 +07:00
Nathan DeTar c316d05549
Added sponsorblock skipping (#208)
* Added sponsorblock skipping.

* Basic framework for sending SB timestamps

* Sponsorblock send timestamp UI improvements

* Added Sponsorblock Icons

* Minor UI tweaks

* Revert UI changes, implement in new UI

* Added notification when sponsor segment is skipped

* Add formatting for notifications & SB messages

* Added SB messages to JS player

* Added SB skip notifcation to videos page.

* Added SB messages to video page

* Change SB messages.

* Check channel_overwrites

* Check Per Channel Settings.

* Cleanup
2022-04-10 16:20:58 +07:00
crocs 77a19a62df
added tumbnail reference in the wiki page (#213)
Co-authored-by: user.crocs <user.pairofcrocs@protonmail.com>
2022-04-10 16:19:39 +07:00
simon 31378ac756
better generic schedule validator for impossible input, #209 2022-04-10 15:58:11 +07:00
simon 10385b1414
bump bs4 2022-04-10 15:57:15 +07:00
simon d8c7b3df0b
better error message for wrong es version, #197 2022-04-09 14:12:03 +07:00
simon 99e0c1c90e
bump yt-dlp 2022-04-08 23:15:58 +07:00
simon 236215cc4c
add config var to video.html template 2022-04-08 22:10:16 +07:00
simon 552636d882
handle 404 in video item API view 2022-04-08 17:55:24 +07:00
simon 9d73dbc45a
implement video overwrites index for sponsorblock 2022-04-08 17:19:25 +07:00
simon 859bf2a28d
add target _blank to external links 2022-04-08 17:18:33 +07:00
simon 365ebf53a5
implement per channel sponsorblock 2022-04-08 17:17:39 +07:00
simon fe3e3cfacc
fix filenotfounderror for missing subtitles when deleting video 2022-04-08 15:56:34 +07:00
simon efa240440b
fix userspace for vide base config 2022-04-08 15:15:59 +07:00
simon e2f4dd124a
bump beautifulsoup4 2022-04-08 15:15:37 +07:00
simon 90337745bf Merge branch 'master' into testing 2022-04-08 14:17:10 +07:00
Sean Norwood 193c3da8ef chore: potentially use plaicholder to create blurred placedholder images 2022-04-07 22:55:10 +00:00
Sean Norwood be0c098ef9 chore: add channel page and clean up some code 2022-04-07 22:54:15 +00:00
Sean Norwood cb3a7e555b chore: setup video list to use blurred placeholders for thumbnails 2022-04-07 22:53:01 +00:00
Sean Norwood 9608c892a9 chore: fix up nav
Next requires links to use next/link
Fixed Icon sizes
2022-04-07 22:52:28 +00:00
Sean Norwood 8616959fee chore: render tab title correctly 2022-04-07 22:51:40 +00:00
Sean Norwood 67fec4ae5e chore: use the nextjs way for fonts 2022-04-07 22:51:23 +00:00
crocs f885965c2b
Merge pull request #207 from pairofcrocs/patch-7
Create AlternativeInstallation.md
2022-04-07 15:46:58 -05:00
Sean Norwood 9ddb651180 Merge branch 'testing' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-07 17:31:27 +00:00
simon a07d789e66
add base64 blur video thumb 2022-04-08 00:29:09 +07:00
simon 6a1cb15114
validate hours to not be greater than 23, #209 2022-04-07 23:02:07 +07:00
simon b1a7a6a148
use cleaned_data for config form parser 2022-04-07 22:30:20 +07:00
Sean Norwood 4e4db8f0ae chore: don't need to load script.js anymore 2022-04-06 18:48:12 +00:00
Sean Norwood 42ff29c13d chore: use env var for api url 2022-04-06 18:31:05 +00:00
Sean Norwood d86cf81719 chore: update video list
* Store current video in local state temporaily
* Default to list view temporarily
* Implements the video list and video player with basic funtionality
2022-04-06 18:29:04 +00:00
Sean Norwood 99316eeee8 chore: update links to next/link 2022-04-06 18:27:18 +00:00
Sean Norwood 9988779f3a Merge branch 'master' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-05 16:24:16 +00:00
simon 39a6fbea74
add links to roadmap issues 2022-04-05 22:56:47 +07:00
simon 573c1514bf
simulate sponsorblock voting api endpoints, #build
Changed:
- added simulated post sponsorblock api endpoints
- updated api documentation
- updated roadmap
2022-04-05 22:34:51 +07:00
simon 348a114981
update Roadmap 2022-04-05 22:32:55 +07:00
simon a84e657577
add documentation for sponsorblock api endpoints 2022-04-05 22:27:55 +07:00
simon 03dd25cff4
implement vote on sponsorblock segments api 2022-04-05 22:25:40 +07:00
simon 286b1cf9b6
simulate sponsorblock post request 2022-04-05 21:51:45 +07:00
simon bace7d41af
add random string generator helper function 2022-04-05 21:51:10 +07:00
simon 738b083a7f
create dedicated SponsorBlock class to handle integration 2022-04-05 21:50:40 +07:00
simon 1ac92254ad
add global vars for version and url 2022-04-05 18:43:15 +07:00
simon e404bac7c3
bump celery 2022-04-05 18:35:06 +07:00
Sean Norwood cfffa8bd38 chore: just render api data for now 2022-04-04 19:57:27 +00:00
simon 0a839ff960 Merge branch 'testing' 2022-04-05 00:05:47 +07:00
simon a8b62e5cdc better error message on sponsorblock fail 2022-04-04 23:28:32 +07:00
simon 730d12212f implement basic sponsblock indexing, #119 2022-04-04 23:28:32 +07:00
simon 29f8e148da bump libraries 2022-04-04 23:28:32 +07:00
simon 71c441d871
better error message on sponsorblock fail 2022-04-04 23:24:51 +07:00
crocs 00750500a1
Add files via upload 2022-04-04 11:16:24 -05:00
simon 77228ccb65
implement basic sponsblock indexing, #119 2022-04-04 23:08:44 +07:00
crocs f0fba0644d
Create AlternativeInstallation.md 2022-04-04 11:08:36 -05:00
simon 2292e6506d
bump libraries 2022-04-04 22:55:43 +07:00
Sean Norwood 97c01a2f3a chore: type info for api 2022-04-04 02:52:23 +00:00
Sean Norwood 9284b93f2a chore: start laying out the home page
* Use the layout component
* Use SSR to get videos from API
2022-04-04 02:52:03 +00:00
Sean Norwood 4fa8cd89e2 chore: external script work and comments 2022-04-04 02:51:16 +00:00
Sean Norwood a129e737d2 chore: add static images 2022-04-04 02:50:54 +00:00
Sean Norwood 8b10fd9302 chore: add various components
* Footer
* Head
* Layout
* Nav
* VideoList
2022-04-04 02:50:43 +00:00
Sean Norwood 7ac6e6c26b chore: use correct typescript version for eslint 2022-04-03 20:04:53 +00:00
Sean Norwood 2c4ab1bb42 chore: use new components 2022-04-03 20:04:28 +00:00
Sean Norwood 73106fc74a chore: add external scripts 2022-04-03 20:04:17 +00:00
Sean Norwood 3c4c15a8e3 chore: move dynamic header component to header file 2022-04-03 20:03:47 +00:00
Sean Norwood 62c219c2de chore: add custom head element 2022-04-03 20:03:30 +00:00
Sean Norwood 734078c5d7 Merge branch 'master' of https://github.com/bbilly1/tubearchivist into feat/react-frontend 2022-04-03 18:33:10 +00:00
simon 4775c9d830
connection test API endpoing, #build
Changed:
- added /api/ping/ to test your connection
- moved browserextension to dedicated repo
- merges nginx ipv6 fix, #203
2022-04-03 08:24:38 +07:00
Sean Norwood 3870a54d2e chore: enable streaming ssr 2022-04-03 00:28:12 +00:00
Sean Norwood cf9181919a chore: initial suspense setup 2022-04-03 00:27:33 +00:00
Sean Norwood ad67cb00e2 chore: remove console log 2022-04-03 00:27:00 +00:00
Sean Norwood eec288af5f chore: add external js files 2022-04-03 00:26:53 +00:00
Sean Norwood d880f95c44 chore: header component 2022-04-03 00:26:42 +00:00
Sean Norwood 1e3087c245 chore: update to react 18 2022-04-03 00:26:30 +00:00
simon cae00b032b return better data for ping api, add to readme 2022-04-01 16:24:40 +07:00
Lickitysplitted 3ae9fe5405
Modify Dockerfile and the nginx.conf file to avoid Nginx default conf… (#203)
* Modify Dockerfile and the nginx.conf file to avoid Nginx default config IPv6 conflict.

* Adjust nginx.conf name and run.sh nginx modification.

* but nginx.conf file back

Co-authored-by: simon <simobilleter@gmail.com>
2022-04-01 11:43:24 +07:00
simon 12fa5748d4
move extension to tubearchivist/browser-extension 2022-04-01 08:48:42 +07:00
simon ab9c02dfad
redefine scope of current state 2022-03-31 23:39:46 +07:00
simon 4de5b2d091 Merge branch 'browserextension' into testing 2022-03-31 23:29:11 +07:00
simon ed6521acad
reimagine browser extension, simple download button 2022-03-31 23:28:53 +07:00
simon 9ab6955125
add connection test api view 2022-03-31 21:13:21 +07:00
simon 46622d9ab2
bump redis lib 2022-03-31 21:13:04 +07:00
simon 607d9b3030
add sync_latest_es function to update tubearchivist-es:latest container 2022-03-31 14:47:19 +07:00
simon 4eb2fb01d9
a note about SSO, LDAP, 2FA in the FAQ 2022-03-30 17:49:03 +07:00
simon 3caafc6e21
update roadmap, rewording 2022-03-30 17:44:54 +07:00
simon 8a3349c6a5
a note about transcoding quality, #138 2022-03-30 17:27:53 +07:00
simon b46910ec90
update roadmap, add issue links 2022-03-30 17:21:25 +07:00
Sean Norwood 1b5534b6d4 Initial React frontend work
Uses NextJS as a React framework
Initial import of assets in `public/`:
* css/
* favicon/
* img/

Initial authentication work
* Use next-auth CredentialsProvider to pull user information from TA
* Add sign in and sign out functionality using default next-auth
login page

TODO
* Custom login page
2022-03-29 17:19:24 +00:00
simon 1cd285aec4
parse api views, #build
Changed:
- return frontend relevant paths for api views
- add video api list view
2022-03-29 18:36:54 +07:00
simon e51193f988
update roadmap 2022-03-29 18:35:01 +07:00
simon 0783d47ffe
bump python version 2022-03-29 18:34:31 +07:00
simon 6f5969f520
add video list api view 2022-03-29 17:05:22 +07:00
simon 0c8fa8f49e
clean up prints 2022-03-29 17:03:34 +07:00
simon 80af255f25
reinvent SearchProcess class for API endpoints 2022-03-29 16:48:04 +07:00
simon cdc617c382
add datestr generator 2022-03-29 16:47:21 +07:00
simon f1fb504874
fix spacing 2022-03-29 10:33:43 +07:00
simon e04690d96d
add API login view, #build
Changed:
- get token and userid from api endpoint
2022-03-29 10:18:56 +07:00
simon 5dce2441fa
add API login view 2022-03-29 10:17:47 +07:00
simon 5d8e314983
be more explicit what you are searching now 2022-03-28 12:43:57 +07:00
simon 9e37c0fab1
add ibracorp review video 2022-03-27 19:43:40 +07:00
simon cc49c256c1
note about min ES version 7.17 2022-03-27 18:28:17 +07:00
simon dc8e330b22
tail git tag output, too many versions now 2022-03-27 18:27:36 +07:00
simon 358194468e
increase version to v0.1.3 2022-03-26 19:47:50 +07:00
simon 1d3d3b13ed
reduce max-complexity to 10 2022-03-26 18:36:10 +07:00
simon f15bb50cfe
fix playlist reindex after PendingList refactor 2022-03-26 18:31:00 +07:00
simon fcadb5ead8
add random headers for requests outside of yt-dlp 2022-03-26 11:49:53 +07:00
simon 6d874f4b7a
add section for per channel customization 2022-03-26 11:13:48 +07:00
simon a0ed88580f
fix index validate_mappings for new nested object 2022-03-26 10:52:57 +07:00
simon 9537c388ab Merge branch 'master' into testing 2022-03-25 17:31:59 +07:00
simon 40d7e6da37
add secondary sort for channel and title, #193 2022-03-25 17:28:17 +07:00
simon fa60f2e5f8
Style channel configure form, #build
Changed:
- layout of the channel configure form
- hide if no overwrites set
- validate min es version 7.17.x
- improved build to incl debug tools
2022-03-25 15:44:01 +07:00
simon e4b4430e69
bump python version 2022-03-25 15:33:54 +07:00
simon bcf9185bd3
implement es version check at startup 2022-03-25 15:33:09 +07:00
simon 337b373628
improve build to install debug tools in testing 2022-03-24 19:54:47 +07:00
simon 59d58f8866
bump redis version 2022-03-24 19:51:45 +07:00
simon d0f826485f
move and rename configure button 2022-03-24 16:10:40 +07:00
simon d5cd90eb34
style and toggle hide overwrite form 2022-03-24 15:43:15 +07:00
simon 8d1d09e698
extend IndexPaginate to optionally return _source key 2022-03-23 17:17:42 +07:00
simon fb4c11bd38
refactor ElasticIndex and ElasticBackup to use ElasticWrap and IndexPaginate 2022-03-23 17:17:17 +07:00
simon bfcc538ed1
fix concurrency issue with subtitle indexing 2022-03-23 16:20:33 +07:00
simon 912c19f6cf
use ElasticWrap in FilesystemScanner 2022-03-23 15:56:53 +07:00
simon fda520ad44
refactor and consolidate Reindex class 2022-03-23 15:48:38 +07:00
simon 1f7d6871cf
use ElasticWrap to delete item from pending 2022-03-23 11:09:21 +07:00
simon 5b1c9c64de
cleanup, use ElasticWrap 2022-03-23 09:44:31 +07:00
simon f802c4d596
remove logging 2022-03-22 18:43:29 +07:00
simon 192d379a3e
refacter api views to use ElasticWrap 2022-03-22 18:43:16 +07:00
simon 9f5c9b17a5
refactor redis connection, fix sync_redis_state task setup issue 2022-03-22 17:50:54 +07:00
simon fa25a56126
fail open for in continue watching videos building 2022-03-22 13:07:22 +07:00
simon 75cd9d382d
fix duration builder on reindex by ignoring vtt files 2022-03-22 12:59:39 +07:00
simon 22a3d3f6cd
implement per channel overwrites, #build
Changes:
- merges per channel overwrite for:
  - download format
  - delete after x days
  - playlist index
- merges major refactor of download queue handeling classes
- merges improved startup checks
2022-03-22 11:49:30 +07:00
simon fd4f15ab66
refactor StartupCheck into class, lock if already run for multithreading 2022-03-22 10:35:36 +07:00
simon 1f1dfcb54f
better playlist scan message, private methods in YoutubeChannel class 2022-03-22 10:07:33 +07:00
simon 3d451e47e5
full playlist refresh and index for channel overwrites 2022-03-22 09:42:41 +07:00
simon fc10fa62e8
remove now redundant find playlist botton 2022-03-21 19:07:58 +07:00
simon 875c601f4e
implement index channel playlist from form 2022-03-21 18:27:00 +07:00
simon 91a9477bba
refactor index_channel_playlists task, move to YoutubeChannel class 2022-03-21 17:59:21 +07:00
simon c9399f61d0
bump es version 2022-03-21 17:04:38 +07:00
simon fc311a7b7e
refactor validate_playlists and move into DownloadPostProcess class 2022-03-21 11:14:44 +07:00
simon f803c5298b
fix missing update_status method call in ignore video 2022-03-21 11:09:16 +07:00
simon e67d576d79
fix missing video list in index_channel_playlists 2022-03-21 10:47:15 +07:00
simon 7c6041d686
refactor VideoDownloader and implement per channel delete older than 2022-03-19 17:37:28 +07:00
simon 1411262793
remember channel overwrites on rescan, remove unused rescape_all_channels method 2022-03-19 15:12:29 +07:00
simon 9fd7e2b11a
rename channel_overwrites attribute 2022-03-19 15:03:38 +07:00
simon 3ba6e0478d
fix filesysem rescan function 2022-03-19 14:36:46 +07:00
simon b68a00073f
fix thumbnail validation with new PendingList class 2022-03-19 14:27:37 +07:00
simon 89428297c9
add download format channel overwrite per video 2022-03-18 22:22:41 +07:00
simon a7945e30e3
fix building missing vid ids list 2022-03-18 22:11:41 +07:00
simon ac9df4e082
map channel overwrite to video id for later efficient mapping 2022-03-18 21:39:33 +07:00
simon a6937db5fd
use the refactored PendingList class 2022-03-18 18:27:25 +07:00
simon 05b8dbc02f
refacor PendingList class into subclasses 2022-03-18 17:19:21 +07:00
simon 1498fadf27
implement channel playlist index overwrite 2022-03-16 12:32:42 +07:00
simon f0e82caebb
add bool true to channel overwrite form parser 2022-03-16 12:32:02 +07:00
simon 2ef8823c2d
better channel overwrite update form 2022-03-16 12:09:50 +07:00
simon dc67293052
add fallback channel overwrite fallback to frontend 2022-03-15 17:45:43 +07:00
simon 544d842a4a
rename channel format overwrite field 2022-03-15 17:45:15 +07:00
simon 774220ab1f
add channel overwrite form to frontend, store in es 2022-03-15 12:00:48 +07:00
simon 105d5bf3f7
add auto label to subtitle track 2022-03-13 22:56:20 +07:00
simon 026cc378fe
add get_overwrites and set_overwrites methods for channel 2022-03-12 22:19:42 +07:00
simon 8170d4913b
add fallback total video length if ffmpeg extraction failes 2022-03-12 21:53:03 +07:00
simon 34708dd59f
Subtitle parser rewrite, #build
Changes:
- merges fix for progress bar issue on player close
- rewrite subtitle parser to use json3
- combining 5 cues into single es document for more efficient indexing
2022-03-12 20:37:54 +07:00
simon 7595e7501f
sort continue watching videos 2022-03-12 20:29:26 +07:00
simon f6950a2ca5
list all in progress videos 2022-03-12 17:29:34 +07:00
simon aff0cfb794
fix retiming issue for auto subtitles 2022-03-11 17:47:04 +07:00
simon d3e9646fb6
private methods for YoutubeSubtitle and SubtitleParser 2022-03-10 20:45:13 +07:00
Nathan DeTar 6e3df21f8c
Continue Watching Section (#188)
* Replaced isWatched() function.

* Switched to `updateVideoWatchStatus()` function

* Updated Onclick to `updateVideoWatchStatus(this)`

* Handle `this` input in `updateVideoWatchStatus()`
2022-03-10 20:20:23 +07:00
simon 1ce832b846
fix autocaption extraction, flatten words, #180 2022-03-10 19:58:13 +07:00
simon 6a6c8fa5d8
bump yt-dlp version 2022-03-10 17:39:35 +07:00
simon c186798e78
rewrite SubtitleParser, #180 2022-03-09 00:25:44 +07:00
simon 8c4607fee9
Continue Watching, #build
Changed:
- merges progress bar fix on 100%
- merges new content area on home for continue watching
2022-03-07 20:58:46 +07:00
simon 40c8e6d146
standardize template jinja indentation 2022-03-07 20:58:17 +07:00
simon fb12a32d4f
use generic data-id and data-status attributes for watched checkbox 2022-03-07 20:48:45 +07:00
simon 437e83b2ae
add continue watching section to home page 2022-03-07 20:26:37 +07:00
simon 25bc66ae80
bump django version 2022-03-07 20:23:23 +07:00
Nathan DeTar ad5e74cb27
Prevent setting progress bar on player close if video is watched. (#182)
* Prevent setting progress bar on close if watched.
2022-02-26 18:11:09 +07:00
simon 6822ed380d Merge video progress bar to master 2022-02-26 09:26:53 +07:00
simon b28905bbae
bump footer version v0.1.2 2022-02-25 12:27:16 +07:00
simon 36a2996cda
clean exit of progress builder when no videos in channel 2022-02-25 12:20:22 +07:00
simon b98ccd9dab
handle error if subscribed channel not available anymore, #175 2022-02-25 11:21:29 +07:00
simon 060f0d575e
change color of progress bar to dark 2022-02-25 10:51:37 +07:00
Nathan DeTar 99c97a703f
Reduce API Calls (#181)
* Reduce API calls

* Fix video id

* Updated `createVideoTag()` description.

* Fixed URL used for cast integration

* Check video duration

* Updates progress bar on watched and close.

* Set progress bar width to 0% by default

* Cleanup, function descriptions

* Cleanup console logging

* Update progress bar on cast progress every 10s

* Catch short <30s videos and mark as watched
2022-02-25 10:39:33 +07:00
simon 806448624d
return if no in progress videos 2022-02-24 21:25:12 +07:00
simon df777104af
add video progress bar to channel_id and playlist_id views 2022-02-24 19:30:12 +07:00
simon d88d6d6a61
add video progress bar 2022-02-24 18:58:26 +07:00
simon 6078d8d276
add youtube_id to progress api key 2022-02-24 18:55:52 +07:00
simon c4d6bb35a3
add list_items for wildcard matching 2022-02-24 18:55:18 +07:00
simon a25b101c3a
remember video progress, #build
Changes:
- merges video progress API endpoint
- merges fix for auto subscribe reset of playlists
- merges fix for ffmpeg install link builder
2022-02-24 09:43:04 +07:00
simon e0db73543e Merge branch 'master' into testing 2022-02-24 09:39:56 +07:00
Nathan DeTar 4812b8da55
Save Video Progress (#179)
* Added cast integration docs.

* Changed header sizes.

* Added more space above Requirements

* Added cast integration docs.

* Removed separate cast integration docs

* Further indented quote from Google

* Switch to HTML based video position.

* Ground work for API changes and video progress

* Added onpause attribute to video.

* Added save video progress feature.

* Added API check for subtitle status.

* Switch method to DELETE if position is 0

* Added `createVideoTag()` function

* Added `InsertVideoTag()` function

* Switch to JS generated video tag, add on page load

* Removed extra data from DELETE request

* Removed unused code

* Reduced duplicate code

* Cleanup & groundwork cast pull metadata from API

* Minor bug fix

* Fix saving video progress on player close.

* Only send video progress when unwatched

* Cleanup

* Added `getURL()` function

* Cast use API & save progress/mark as watched

* Added cast progress checks

* Changed thresholds for marking videos as watched

* Added `watchedThreshold()` function
2022-02-24 09:36:31 +07:00
simon 8579fb4cc1
fix ffmpeg link grep by using master 2022-02-21 21:37:49 +07:00
simon b8b95f9d79
inheirt VideoProgressView from base class to get auth 2022-02-21 20:59:04 +07:00
schizovivek 70506ad8f6
add comment for redis rejson to use arm64 fork created by bbilly1 (#178)
* updated redis rejson to use arm64 fork created by bbilly1

* added arm build as a comment instead of main option for rejson
2022-02-21 20:14:08 +07:00
simon ec00568008
fix auto reset of playlist_subscribed 2022-02-20 19:22:11 +07:00
simon 2044dba700
API endpoints improvement, #build
- merges adding config key to api return values
- add video progress api endpoint
2022-02-17 18:21:56 +07:00
simon 241d8326f7
add video progress API endpoints 2022-02-17 18:20:30 +07:00
simon 4d83af7c14
bump redis client 2022-02-17 18:19:52 +07:00
simon 265795f4a9
add config key to api response 2022-02-17 16:59:59 +07:00
Nathan DeTar 5c3f0d1e5f
Docs for the cast integration (#172)
* Added cast integration docs.

* Changed header sizes.

* Added more space above Requirements

* Added cast integration docs.

* Removed separate cast integration docs

* Further indented quote from Google
2022-02-14 17:10:25 +07:00
simon 047fc1f3f1
bump release version 2022-02-13 10:58:58 +07:00
simon 8fce31b983
fix reindex and deactivate error 2022-02-13 10:05:08 +07:00
simon 8f87c4774a
restructure and update roadmap 2022-02-12 21:59:51 +07:00
simon fef866bf0b
indicate max page size on downloads page, add note to wiki, #156 2022-02-12 19:42:07 +07:00
simon 428cc315e4
Subtitle improvements, #build
Changes:
- merges subtitle suport for JS video player
- merges hint what to do when no videos found
- merges better indexing and error handeling of subtitles
2022-02-12 19:11:47 +07:00
Nathan DeTar 385d6bace8
Fix cast support to handle new video tag format. (#169)
* Added subtitle support to JS player.

* Move `video-item` id to source tag.

* Move `video-item` id to source tag.

* Fix cast support to handle new video tag format

* Add subtitle support to cast integration, WIP

* Replace `&amp` with `&` in video titles.

* Check if the video is already marked as watched

* Switch to HTML watched check.
2022-02-12 19:08:19 +07:00
simon 78720b33b7
clean exit when auto subtitle extract fails 2022-02-12 18:52:03 +07:00
simon ce4fa8ee61
delete existing before of reindexing subtitles 2022-02-12 18:22:01 +07:00
simon 13cbada539
add section about subtitles and extend integrations 2022-02-12 17:09:25 +07:00
simon a5f6be16e1
switch subtitle source order 2022-02-12 17:09:05 +07:00
simon 5abf988810
add FAQ wiki page 2022-02-12 16:27:31 +07:00
simon b494fc10af
cover edge cases where end timestamp is after start timestamp of new cue 2022-02-12 16:03:04 +07:00
simon d99ce0d98e
add atomicparsley to container for better thumb embed, #155 2022-02-12 09:58:14 +07:00
simon 09500557c8
open fail on subtitle line parse error 2022-02-11 18:47:29 +07:00
simon 7524691b79
implement API token revoke 2022-02-11 18:19:10 +07:00
simon f1de8db4f3
hide API token by default 2022-02-11 17:18:37 +07:00
simon 08f5248e7a
make subtitle index optional 2022-02-11 13:36:36 +07:00
simon 1abe4fb4d6
add text/vtt content-type header to subtitle files 2022-02-11 13:06:13 +07:00
Stephen Herbein b1435434e6
docs: add a note that importing requires there be no subdirectories (#170)
Also include some bash snippets to help users with existing video
libraries containing subdirectories get their files all in one directory
quickly.

Co-authored-by: Stephen Herbein <sherbein@DESKTOP-EAJL82P.localdomain>
2022-02-11 12:55:14 +07:00
Stephen Herbein 4467e97f3c
When no videos are found, add a hint to start a search/download (#167) 2022-02-11 12:52:26 +07:00
simon af8e01cd8c
fix clean exit if no subtitles selected 2022-02-11 09:03:56 +07:00
Nathan DeTar 656a0c7327
Added subtitle support to JS player. (#168) 2022-02-11 08:53:09 +07:00
simon 3efa388b5a
add subtitle functionality, #build
Changes:
- merges new subtitle download and index functionality
- merges player improvements and api integrations from @n8detar
- merges fix for non ascii channel names
- merges fix for pagination error with 10k+ videos
2022-02-10 19:48:39 +07:00
simon 16f33feda0
process subtitle media url paths 2022-02-10 19:45:22 +07:00
simon 3ea5e9c537
bump dependencies 2022-02-10 19:27:05 +07:00
simon 4d30bed3cc
extend delete video to also delete subtitles 2022-02-10 19:09:07 +07:00
simon 0e56efc428
limit filesystem scan to mp4 files only 2022-02-10 18:48:35 +07:00
simon 077692987b
fix multi language subtitle extractor, and better regex for timestamp matching 2022-02-10 18:32:23 +07:00
simon b071612038
better error raising for add player info 2022-02-10 17:34:21 +07:00
simon 0414df0de0
fix key error for subtitle source 2022-02-10 17:10:30 +07:00
simon a2cae51f48
bulk import subtitle lines into es 2022-02-10 17:02:19 +07:00
simon 9f652802ae
add new mapping for subtitle index 2022-02-10 11:47:14 +07:00
simon 4e4cfe3334
pass whole video object into YoutubeSubtitle class 2022-02-09 23:40:15 +07:00
simon 4e2d0fa464
bump es version 2022-02-09 23:38:18 +07:00
simon 6cb892a811
integrate auto generated subtitle cleaner 2022-02-09 21:33:41 +07:00
simon 5f6158243e
auto generated subtitle parser and cleaner 2022-02-07 21:18:52 +07:00
simon 1664b0d4fc
restructure video tag to add subtitle tracks 2022-02-06 00:08:24 +07:00
simon e98ffc0050
add subtitles mapping to video index 2022-02-05 23:50:47 +07:00
simon 52013aff3f
fix subtitle download of first video of channel without folder 2022-02-05 23:42:42 +07:00
simon 44af78b7e3
handle NA in ffprobe duration extractor 2022-02-05 23:09:05 +07:00
simon 2bf9e9683b
error handeling in _normalize_lang to skip livechat and ignore missing 2022-02-05 22:51:38 +07:00
simon f5f46349b2
handle rescan name change 2022-02-05 22:38:59 +07:00
simon 851fbae900
fix video template dislike icon and add watched icon 2022-02-05 18:42:09 +07:00
simon 91452b5114
remove redundant video player api endpoint 2022-02-05 18:35:02 +07:00
simon 5b37bd059c Merge branch 'testing'
combine new api endpoints from #151
2022-02-05 18:27:24 +07:00
Nathan DeTar 9079a2a78b
Get Video Player Data Using New API (#151)
* Get  video player data using new API

* Spelling

* Removed extra data from play button

* Reworked createPlayer, switched functions to API

* Add theme to scrollbar

* Removed extra metadata from playlist page

* Removed extra metadat from channel page

* Reworked createPlayer, switched functions to API

* Update style.css

* Changed watched indicator to match createVideo()

* Fixed createPlayer() watched button

* Fix watched indicator duplication

* Minor clean up

* Removed player-wrapper background

* Added video/channel info to generated player

* Removed description due to textReveal() conflict

* Mark video as played at 90% playback

* Groundwork for saving video playback

* Add half and empty stars to getStarRating()

* Check videoProgress input.

* Added last refresh and date published

* Switched date in create functions to API

* Fomatted dates to match the old format

* Remove console log from formatDates()

* Cleaned up error on video player close

* Added check for ryd dislikes/rating

* Refined ryd check

* Simplified player

* Added player stats css formatting

* Formatting for playlist name/link

* Add playlist title/link to player

* Commented out no longer used code

* Fix missing end `"` on video-player class

* Additional playlist error checking

* Change setting video progress to html method

* center thumbs icon, add eye icon for watched

* add playerStats builder example, change some spacing

* Removed `-` before playlist, reordered cast button

* Minor cleanup of unused code.

* Corrected POST data formating

* consolidate video api calls into one

* remove redundant api calls for search result population

* do some jshint

* shorten unit and add K to formatNumbers

Co-authored-by: simon <simobilleter@gmail.com>
2022-02-05 18:26:31 +07:00
simon 8fe00e2152
process api return values for frontend use 2022-02-05 17:46:14 +07:00
simon ac531affb5
standardize country specific subtitle language codes 2022-02-05 16:07:11 +07:00
simon 3ef35a9d53
raise FileNotFoundError to catch for reindex 2022-02-05 09:30:34 +07:00
simon 7aaf140ccb
index subtitle url to video 2022-02-04 17:14:00 +07:00
simon ad2647c4ba
upgrade libs 2022-02-04 17:13:30 +07:00
simon a82e78f8bf
index selected subtitles if available 2022-01-30 23:57:58 +07:00
simon 365a2bf59f
add frontend subtitle dl settings 2022-01-30 20:10:29 +07:00
simon e5e83287ab
fix channel delete for channel_id fallback folders 2022-01-30 08:33:10 +07:00
simon 8e860d4f01
fix last page error for more than 10k results, #156 2022-01-27 23:39:07 +07:00
simon 0749202d5d
add fallback for none ascii channel names, #127 #146 2022-01-27 22:50:02 +07:00
simon 160676acc1
add mapping for channel_tvart_url 2022-01-27 21:56:48 +07:00
simon 8591c44ef2 major refactor, #build
Changes:
- merges new restructured and split up modules
- merges refactor channel, video, playlist index classes
- merges code clean up and readability improvements
2022-01-27 16:08:16 +07:00
simon 2b2ff814e3
red hover logout button 2022-01-27 16:04:04 +07:00
simon 87b72a571d
simplify reading json files 2022-01-27 15:32:58 +07:00
simon 00e9e4bc53
bump python version 2022-01-27 15:08:26 +07:00
simon b93a6f689b
remove previous bug workaround to set django debug env, #159 2022-01-27 14:49:22 +07:00
simon b2f69d1433
untrack vscode folder 2022-01-26 20:21:08 +07:00
simon 2eea07c85e
organize docker conf files 2022-01-26 20:05:52 +07:00
simon b6f9fb58ad refactor VideoDownloader class, better obs builder 2022-01-26 19:37:36 +07:00
simon 2fc0cbacf5
update doc strings to represent new module structure 2022-01-23 19:32:08 +07:00
simon 0fc0cc8e87
major refactor, split up modules 2022-01-22 22:13:37 +07:00
simon f3efca7464
bump yt_dlp version 2022-01-22 17:57:36 +07:00
simon c57d6c73cc
remove bandit, too many false positive 2022-01-22 17:52:13 +07:00
simon b64a2f8195
squash index.py refactor commits 2022-01-22 17:48:54 +07:00
simon 2f4d4e715b
refactor index class in to base and video classes 2022-01-19 13:04:15 +07:00
simon 44733adf4d Merge branch 'master' into testing 2022-01-18 17:28:50 +07:00
Nathan DeTar 26bc2d2af4
Add theme to scrollbar. (#153)
* Add theme to scrollbar.

* add firefox scroll bar color

Co-authored-by: simon <simobilleter@gmail.com>
2022-01-18 17:27:49 +07:00
simon 68f19b1719
make SearchHandler use new ElasticWrap class 2022-01-18 13:37:22 +07:00
simon 3eb0353fa9
new base class to make all es calls from 2022-01-18 13:36:41 +07:00
simon af2783c18a
remove unneeded auth 2022-01-18 13:35:48 +07:00
simon 8b15ea5dc8
bump redis version 2022-01-18 11:27:24 +07:00
simon 3d26f320bf
new logout icon 2022-01-18 11:26:26 +07:00
simon 7028621bc5 merge new api view, #build
Changes:
- merges new cast improvements
- merges toggle invert
- merges new video player api endpoint
- merges basic browser extension example
2022-01-15 14:19:11 +07:00
Laurant Marijnissen 385771cba0
invert the hide button to more clearly show what it is currently set as. (#150) 2022-01-15 14:18:23 +07:00
simon 79b0a989be
new api view to return video player 2022-01-15 14:14:18 +07:00
simon cccb8e570a Merge cast changes from 'master' into testing 2022-01-15 13:35:16 +07:00
Nathan DeTar b7922d171d
Improved Cast Support (#143)
* Added cast integration requirements.

HTTPS and a supported browser are required for this integration.

* Improved cast handeling.

* Theme cast button.

* Improved cast support.

* Added proper cast button.

* Moved cast button location

* Moved button location

* Reorder cast button

* Fix typo

* Revert cast setting description.

* Match master branch

* Added comments

* Added id `cast-script` to cast-videos.js

* Reworked event listener

* Add cast  button to home player

* Check if active media on cast, pause browser video

* Commented out console logging

* Uncommented cast failed console log

* Cast video at current playback position

* use theme vars for cast color buttons

* add cast variable to base ArchivistViewConfig class

Co-authored-by: simon <simobilleter@gmail.com>
2022-01-15 13:33:16 +07:00
simon e1cdd79b45
added browser extension proof of concept 2022-01-15 13:03:44 +07:00
simon fec6237907
add chrome-extension to CORS_ALLOWED_ORIGIN 2022-01-15 12:27:36 +07:00
simon 3df8f65db6
implement cors for browser extension 2022-01-14 14:46:00 +07:00
simon c703105c5e
fix ryd 404 error for reindex and deactivate 2022-01-11 18:54:40 +07:00
simon 51f63f7b71 Merge api app from branch 'testing' 2022-01-11 18:43:03 +07:00
Nathan DeTar 45518dc3d2
Basic Google Cast Support (#140)
* Added Start and Stop cast buttons.

* Modified from the internet.

* Code to initialize casting

* Added `video-item` id to video

* Renamed function to make more sense

* Renamed cast functions to make more sense

* Renamed cast functions to make more sense

* Changed console logging message.

* Make cast buttons appear if enabled in settings.

* Make cast scripts only load if enabled in settings

* add cast configuration form, #140

* fix spelling

Co-authored-by: simon <simobilleter@gmail.com>
2022-01-11 18:31:22 +07:00
simon c4a547f407
cleanup and typos 2022-01-11 17:05:04 +07:00
simon b28773b3a0
add exmples, auth docs, and list endpoints 2022-01-11 16:53:58 +07:00
simon 50006e423c
implement channel list and subscribe api 2022-01-11 16:53:02 +07:00
simon 8d5b4ac242
implement api return status code 2022-01-11 16:16:28 +07:00
simon 52a54fbe31
add to queue api endpoint 2022-01-11 15:58:50 +07:00
simon 382e89abb7
implement api token auth 2022-01-11 14:15:36 +07:00
simon 917e73ec4d
new django api app, implementing basic get views 2022-01-10 22:51:52 +07:00
Nathan DeTar edc9f3fe15
Added a button to download a video to the local device. (#137)
* Add video download button.

* Swapped <a> and <button> tags to fix theme issue.

* Download button renamed to `Download File`
2022-01-10 09:00:29 +07:00
Sean 8410b9310f
feat(docker): uses named volumes in docker-compose (#133)
Closes #132
2022-01-10 07:54:23 +07:00
Crocs fb2a7d59d4
Update README.md (#136)
misspelled :)
2022-01-10 06:25:26 +07:00
simon ee2025664f
update roadmap 2022-01-09 08:44:16 +07:00
simon b8c2c67707 fix unstable build arch issue for docker_publish 2022-01-09 08:42:59 +07:00
Sean 28923ea9c7
docs: add helm chart alternative install method (#130) 2022-01-09 08:33:33 +07:00
simon 21a76595ec
fix new screenshot links in readme 2022-01-09 00:02:02 +07:00
simon e8a533bbbe
add social links and update to v0.1.0 2022-01-08 23:58:41 +07:00
simon 9ad8177b7a
retry failed commit 2022-01-08 23:57:51 +07:00
simon 709c7b88f5
wiki refresh for new search functionality 2022-01-08 21:21:49 +07:00
simon 8ba9ced833
move ryd to avoid celery missing redis var 2022-01-08 21:10:38 +07:00
simon f62e75f343
add social links 2022-01-08 20:14:02 +07:00
simon 1a95797997
improve scheduler validation with regex, #126 2022-01-08 20:04:51 +07:00
simon c660edd997
bump library versions 2022-01-07 18:35:59 +07:00
simon 9498cf94f3
fix playlist view_origin 2022-01-07 18:30:40 +07:00
simon 099f9dfe00
make search results follow default view styles 2022-01-07 18:29:25 +07:00
simon 1cbe0bbfc0
delete playlists of channel when deleting channel, #118 2022-01-05 15:39:41 +07:00
simon 49cd272557
size limit for ryd missing rating query 2022-01-05 15:10:33 +07:00
simon 1c27934d77
remove redundant lock 2022-01-05 14:51:02 +07:00
simon c85be45846
extend ryd to refresh when missing average_rating 2022-01-05 14:43:24 +07:00
simon 7a1f77e548
bump library versions 2022-01-05 14:42:28 +07:00
simon 3999d679e3
fix linting 2022-01-05 14:09:35 +07:00
simon dc2fb65312
add optin returnyoutubedislike.com integration 2022-01-05 14:06:07 +07:00
simon 6aef3dfb96
remove leftovers 2022-01-05 11:10:11 +07:00
simon 49eb565e2a
fix sort_by selected attribute and animation toggle 2022-01-02 16:25:05 +07:00
simon 2ce90077b6
fix notification for nownload now button 2022-01-02 15:47:32 +07:00
simon fd334bcafe
fix legacy import alias for yt-dlp 2022-01-02 15:38:45 +07:00
simon 6d16d75ef9
bump es version 2022-01-02 12:24:25 +07:00
simon d5b3b90202
fix missing auth of search, reduce nginx alias to relevant paths only 2022-01-01 22:17:44 +07:00
simon 37140551d2
improved build cache add nginx after pip 2022-01-01 22:16:34 +07:00
simon 08f74caef0
simplify the sort_order and sort_by and standardize player location in page 2022-01-01 18:13:12 +07:00
simon f7fce001c4
simplify the subscribe buttons 2022-01-01 17:25:57 +07:00
simon 4915aa0c11
remove now redundant search forms 2021-12-31 13:42:07 +07:00
simon 5616d3ee0d
add search page link to top move about to footer 2021-12-31 12:41:54 +07:00
simon e823d6e1b3
add multi search js functions to dynamically populate results 2021-12-30 22:13:47 +07:00
simon e8eb7077ed
add initial search endpoint and improve results$ 2021-12-30 20:42:42 +07:00
simon 5fb15b79a4
extend SearchForm class with multisearch method for frontend 2021-12-30 15:58:15 +07:00
simon 2a774c3d3e
add english analyzer for main text fields for serching 2021-12-30 15:54:20 +07:00
simon b6424e0e56
update requirements libs 2021-12-30 15:53:38 +07:00
simon 386220ee94
consolidate request attributes into parent class 2021-12-27 17:23:39 +07:00
simon 4c46f0bdbd
refactor playlist and playlist-id views inheritance, #116 2021-12-27 16:58:08 +07:00
simon 30cf54c49c
refactor channel and channel-id views inheritance, #116 2021-12-27 15:23:52 +07:00
simon fb512b26ea
further consolidate by adding query results in parent class 2021-12-27 12:01:49 +07:00
simon 2ae25540d1
refacter DownloadView to use new inheritance structure, #116 2021-12-27 10:58:49 +07:00
simon 6229d92333
add dedicated default_view for playlists 2021-12-26 22:51:37 +07:00
simon 6e348892f2
refactor home view by inherinting from dedicated view classes, #115 #116 2021-12-26 22:49:00 +07:00
simon 665d0a1b24
bump versions 2021-12-26 18:22:04 +07:00
simon 42f4f4cb6f
improved sort order mobile layout on home 2021-12-18 18:38:01 +07:00
simon 2d6cc43a9e
better label placement on toggle box 2021-12-18 18:22:57 +07:00
simon bf0b07f3d7
fix error when restarting player with same video 2021-12-18 17:15:53 +07:00
simon 88ebb6d648
fix autodelete error on empty download queue 2021-12-18 17:05:49 +07:00
simon 3015e59a29
fix styling of sub unsub frontent feedback with span tag 2021-12-18 16:56:32 +07:00
simon 7a3609e611
add unstable tag to sync_docker deploy 2021-12-18 16:35:01 +07:00
simon ad8d75c3e8
add autodelete videos to ignore list, #112 2021-12-17 23:28:53 +07:00
simon f36ae6c4b9
hotfix: explicitly set DJANGO_DEBUG env, #111 2021-12-17 23:02:38 +07:00
simon 8678cdcadb
add missing favicon to login template 2021-12-17 23:00:30 +07:00
simon 1ac6da1f3e
bumb django version 2021-12-17 18:45:32 +07:00
simon e5b9b1caef
remove now unused favicon 2021-12-17 18:32:46 +07:00
simon 8f80cf2d22
update roadmap 2021-12-17 18:29:25 +07:00
simon 5f3d52c76a
update docs for v0.0.9 release 2021-12-17 16:59:52 +07:00
simon 816c68464b
add unstable tag for docker deploy 2021-12-17 16:57:10 +07:00
simon a500f9ec63
add notifications and error messages to settings page 2021-12-17 15:09:21 +07:00
simon 8e976922e9
add Scheduler Setup documentation 2021-12-17 11:52:32 +07:00
simon eb5483c6b2
bumb es version 2021-12-17 09:38:44 +07:00
simon 3231dbe29a
the favicons the whole favicons and nothing but the favicons, #93 2021-12-16 21:23:54 +07:00
simon ae082eb309
add sync_redis_state before building schedule for update path 2021-12-16 21:22:05 +07:00
simon 23f9894154
move validate_thumbnails out of startup has it's own schedule now 2021-12-16 21:20:14 +07:00
simon f6159a48d5
change sub and unsub to colored toggle button 2021-12-16 18:28:52 +07:00
simon ada2c1fe59
add autofocus and autocomplete to user form field, #104 2021-12-16 18:00:36 +07:00
simon 5b59c3296f
port overwrite with TA_PORT and TA_UWSGI_PORT for collisions, #103 2021-12-16 17:42:59 +07:00
simon c9373eee15
add remember me checkbox to login view and set session expire, #77 2021-12-16 15:17:58 +07:00
simon e858bba944
fix linting, update doc string 2021-12-16 12:48:12 +07:00
simon 9a2dead76c
implement autodelete watched videos after x days, #56 2021-12-16 12:44:37 +07:00
simon 53f93a7bb9
fix missing average_rating due to lack of dislikes 2021-12-15 17:39:52 +07:00
simon 7924f28ccb
implement backup rotate from config value 2021-12-14 19:40:46 +07:00
simon ff2de43752
update index restore information 2021-12-14 19:21:07 +07:00
simon 275cf1263b
fix mobile backup-grid-row layout 2021-12-14 19:15:29 +07:00
simon f49895917a
change settings for backup restore 2021-12-14 19:06:47 +07:00
simon f7c73f7eba
change backup and restore to tag files 2021-12-14 19:05:58 +07:00
simon 6ad1c8e73b
add reason to backup as file name tag 2021-12-13 21:20:48 +07:00
simon 21f70493bb
add scheduler config to regular redis config 2021-12-13 20:28:41 +07:00
simon 5cb6076a1c use config check_reindex_days for refresh_interval 2021-12-13 19:19:49 +07:00
Wesley Mauk f660b05a20
Make copyright year dynamic (#107)
Never have to update this again!
2021-12-13 19:10:11 +07:00
simon 8f7179ad0c
fix some spacing issues 2021-12-13 10:57:29 +07:00
simon 3df9a53a24
add scheduler settings with some sane defaults 2021-12-13 10:55:57 +07:00
simon fde1a37563
add check_reindex_days and run_backup_rotate fields to form 2021-12-13 10:54:54 +07:00
simon c475700cdd
store beat schedule on volume 2021-12-08 16:15:25 +07:00
simon 9bb68aac66
extend theater mode to video id page template 2021-12-08 14:30:13 +07:00
simon 279c4538ca
fix player wrapper and player channel links 2021-12-07 08:25:46 +07:00
simon 42eec604a7
reimagining the video player in theater mode, #98 2021-12-06 22:14:42 +07:00
simon e2238c16f5 restructure html to allow for non boxed-content classes within content blocks, #98 2021-12-06 18:07:57 +07:00
simon 8c5bb0bc1f
add missing mapping for playlist_last_refresh to fix empty index scan, #101 2021-12-06 16:56:56 +07:00
simon 5c26be43e6
skip instead of cancle update 2021-12-06 16:55:55 +07:00
simon 82a71ed75d
clean up playlist subscribe notification 2021-12-05 22:35:46 +07:00
simon 7516090e14
handle findPlaylists notification and fix spacing 2021-12-05 22:15:10 +07:00
simon 8874168291
clean up progress hook title and speed up playlist validation 2021-12-05 21:57:33 +07:00
simon 19871d55e2
faster release message timer with reduced redis exipre time on last message 2021-12-05 21:27:43 +07:00
simon c7d69b4fa1
use new notification for channel subscribe feedback 2021-12-05 20:26:39 +07:00
simon 5b47c9cde7
use filter to search for relevant messages 2021-12-05 20:26:17 +07:00
simon 67bde4c7ec
fix notification channel for video thumb download 2021-12-05 17:24:57 +07:00
simon a0d5837519
use new checkMessages for frontend button calls 2021-12-05 17:24:20 +07:00
simon fbdbfd744d
rewrite of progress.js, now potentially better and flexible 2021-12-05 16:42:33 +07:00
simon 1d262fc936
fix old status variables for message 2021-12-05 16:41:06 +07:00
simon f7b5082983
use message:playlistscan for index_channel_playlists 2021-12-03 21:05:34 +07:00
simon b35440253d
transform add to queue, rescan, and download progress to channel messages 2021-12-03 19:47:43 +07:00
simon 1691bdadf5
transform progress message to list, change subscribe messages 2021-12-03 19:00:26 +07:00
simon 5a2d7c07bf
variable expire value and simpler REDIS_PORT declaration 2021-12-03 13:51:34 +07:00
simon 3d0bb40da6
for some strange reason timezone needs to be defined here again... 2021-12-03 12:00:40 +07:00
simon 0b0634273a
validate form entries, set auto sensible default 2021-12-03 11:27:26 +07:00
simon ab8dc0ddee
bump versions 2021-12-03 11:26:35 +07:00
simon 4136a2a4f2
add rescan lock 2021-12-02 19:11:45 +07:00
simon 40c7a6a3f7
initial scheduler_form for settings 2021-12-02 18:38:32 +07:00
simon b2861e0369
ScheduleBuilder config class for celery beat 2021-12-02 18:37:54 +07:00
simon 012e28d4ef
initial beat scheduler setup 2021-12-02 15:54:29 +07:00
simon 3cc5db7ad1
use TZ environ for scheduler 2021-12-02 14:44:37 +07:00
simon 7e97284374
auto index playlists when adding to download 2021-11-29 15:49:45 +07:00
simon b8359a4249
match_all to show all playlists, even when empty 2021-11-29 14:28:23 +07:00
simon b14a820a4c
add 18 char len to valid playlists 2021-11-29 14:21:19 +07:00
simon 929311a943 merge roadmap edits into testing 2021-11-28 19:16:16 +07:00
Simon c754badbaa
Update roadmap 2021-11-28 19:13:29 +07:00
simon eacfd4bc84
refactor and move frontend PostData class to dedicated module 2021-11-28 18:42:04 +07:00
simon 78b85fd42f
add note about channel playlists 2021-11-27 17:39:18 +07:00
simon 525618a7a1
update footer to v0.0.8 2021-11-27 14:41:10 +07:00
simon 436196738e
remove untested warning of arm64, specify broser codec limitations 2021-11-27 14:40:03 +07:00
simon d95bf4272c
speedup by only testing selected formats 2021-11-27 14:13:48 +07:00
simon 5401722d88
bump versions 2021-11-27 14:13:10 +07:00
simon 2d69f64121
fix some wording 2021-11-27 14:11:06 +07:00
simon b8f7aa3298
cleaner handeling of findPlaylists 2021-11-27 12:07:27 +07:00
simon 64c9269ee2
fix channel info for playlistid view to use actual owner 2021-11-27 11:55:00 +07:00
simon 295ea0cde0
handling deactivating playlist 2021-11-27 11:47:12 +07:00
simon 1f82f0c40d
index playlist channel if doesn't exist and beter user feedback 2021-11-26 17:34:59 +07:00
simon 31515961e1
fix prev and nex playlist order 2021-11-26 16:47:39 +07:00
simon 5d7a609285
skip backing up not yet existing index 2021-11-26 16:19:18 +07:00
simon e3de9a0184
fix mobile playlist nav layout 2021-11-26 10:10:45 +07:00
simon ee436ea2d5
update docs with playlist info 2021-11-25 23:38:00 +07:00
simon 32d2659658
handling genering index write errors, #91 2021-11-25 22:10:12 +07:00
simon 3f31e50b69
add delete download queue pending or ignored, #85 2021-11-25 20:02:25 +07:00
simon 408b0a0c34
fix mobile layout for playlist pages 2021-11-25 17:22:52 +07:00
simon 39304ff2bd
add subscribe and unsubscribe buttons for channels and playlists, #81 2021-11-25 16:41:58 +07:00
simon bad129d630
consolidate subscribe to single task for channel and playlist 2021-11-25 15:49:08 +07:00
simon 2ebd0a3e2f
extend unsubscribe for channels and playlists 2021-11-25 11:52:14 +07:00
simon 407325e0a9
add original youtube link if available, #81 2021-11-25 11:32:51 +07:00
simon e3f9bd98de
use --check-formats for downloader, #90 2021-11-25 11:17:25 +07:00
simon b1783d6be3
handle multifeed videos with noplaylist 2021-11-22 22:58:25 +07:00
simon 9fceb98f13
add playlists to reindex class 2021-11-22 20:40:11 +07:00
simon 220d020c76
delete playlist buttons to frontend 2021-11-20 18:27:10 +07:00
simon b292a5e309
add delete playlist backend functionality 2021-11-20 14:32:36 +07:00
simon 8d708fcdc8
add mark_playlist_watched and refactor mark_channel_watched to update_by_query 2021-11-20 10:58:25 +07:00
simon 85d12d6c68
dont index playlists without videos 2021-11-19 16:57:07 +07:00
simon fef26a06b2
force refresh for new videos to avoid racing condition 2021-11-19 16:26:05 +07:00
simon 3b6e70b4dc
progress message on validate_playlists 2021-11-19 15:53:06 +07:00
simon 3881d305f9
add subscribe to playlist form and better task management 2021-11-19 11:52:27 +07:00
simon fcddf85245
validate_playlists after run_queue from VideoDownloader 2021-11-18 22:35:08 +07:00
simon d0e0903d48
use remote data for playlist find_missing 2021-11-18 21:50:35 +07:00
simon 3efdc1962d
fix playlist nav builder using correct index 2021-11-18 21:16:18 +07:00
simon f26fbc0dc5
implement show_subed_only on playlist view 2021-11-18 17:14:11 +07:00
simon a1167bac4e
use same page size for playlist rescan 2021-11-18 16:58:39 +07:00
simon bb889f7f67
use find_missing to also add missing videos from playlist to download queue 2021-11-18 16:37:05 +07:00
simon 1af0196208
add subscribe to playlist backend functionality 2021-11-18 16:00:55 +07:00
simon 8b69be645c
use new IndexPaginate class for get_channels and get_playlists 2021-11-18 14:48:31 +07:00
simon f371a03cc7
use new IndexPaginate class for get_all_indexed videos 2021-11-18 12:16:21 +07:00
simon 509b0097fe
bump celery version 2021-11-18 12:11:58 +07:00
simon 7b36cc53e7
refactor get_all_pending to use new IndexPaginate class 2021-11-17 18:30:24 +07:00
simon ed9d5aef0a
fix playlist get_entries when not passing all youtube_ids 2021-11-17 18:16:22 +07:00
simon c4b0f900f8
dedicated search_after class to scroll through index 2021-11-17 17:54:47 +07:00
simon 0e9c0d9f6b
add channel playlist link 2021-11-15 13:19:40 +07:00
simon d2414434fb
implement playlist earch form 2021-11-15 13:08:32 +07:00
simon 2fe8936e85
add playlist position index 2021-11-15 13:00:17 +07:00
simon 62ba1ecf4a
handle empty playlist nav 2021-11-14 19:48:03 +07:00
simon e450b38702
add video playlist navigation, previous and next 2021-11-14 19:34:57 +07:00
simon 3d969c25fa
only show playlist if at least one entry 2021-11-14 15:51:55 +07:00
simon 8932a6bc02
validate existence of playlist entry in index 2021-11-14 15:46:24 +07:00
simon e3f0075132
use original sort order for playlist_id list 2021-11-13 18:36:48 +07:00
simon b9ddf6a299
bump celery version 2021-11-13 17:54:07 +07:00
simon c3ece970ee
add playlist discovery button to channel_id page 2021-11-13 17:35:48 +07:00
simon ac14cd5ebb
playlist relation take 2 2021-11-13 17:34:58 +07:00
simon 7930da5242
fix youtu.be extractor, #40 2021-11-12 19:26:47 +07:00
simon 58303047aa
validate thumbnails after filesystem rescan 2021-11-12 17:12:53 +07:00
simon b1c82759bb
extend playlist_id with metadata and links 2021-11-12 15:01:39 +07:00
simon 94576b4b76
initial PlaylistIdView paths and basic template 2021-11-12 11:44:18 +07:00
simon 16833a4377
auto add videos to playlist 2021-11-12 11:43:44 +07:00
simon 44e1720aab
add thumbnail extraction to channel playlist scraper 2021-11-11 21:17:04 +07:00
simon 3485f9b1c8
define playlist field types in index 2021-11-11 20:57:28 +07:00
simon c499a130da
add sync playlist meta data and position to video index 2021-11-11 17:56:29 +07:00
simon 824ba35c14
add basic playlists to template 2021-11-10 18:53:38 +07:00
Crocs bb73d531a4
Update settings.html (#87)
* Update settings.html

Fixed the example as you wouldn't put KB/s at the end, reworded the explanation, and added "KB/s" at the end of the "Current throttled rate limit:"

Also added KB/s to the download speed limit, and added an example.

* Update settings.html
2021-11-10 17:59:11 +07:00
simon 5ef9c5b059
download playlist artwork 2021-11-10 17:55:34 +07:00
simon 2550016ac8
send playlist hits to template 2021-11-10 17:29:11 +07:00
simon eaf107d0d2
add new ta_playlist index and task to find playlists of channel 2021-11-08 14:54:17 +07:00
simon 1bfb912d09
basic routes and template for playlist page 2021-11-08 14:52:46 +07:00
simon bd37b653c2
extend playlists to get all playlists of a channel and upload single playlist to index 2021-11-06 22:30:39 +07:00
simon 15845da3a8
use check_formats to validate thumb url, #83 2021-11-06 20:23:59 +07:00
simon 72bc6a4d02
basic methods of YoutubePlaylist class 2021-11-05 16:24:05 +07:00
simon 661ee2616c
bump django version 2021-11-05 16:23:15 +07:00
simon 3bc6074ef3
slimming some UI spacing issues 2021-11-04 15:53:47 +07:00
simon 232afee3f6 update for v0.0.7 2021-11-01 17:16:00 +07:00
simon b2a4f88980 add re_sync_thumbs to frontend 2021-11-01 16:42:07 +07:00
simon 1f0e305527 add re_sync_thumbs backend functionality 2021-11-01 16:20:21 +07:00
simon 4d9cda3a24 better subscribe_to task with notification 2021-10-31 17:48:56 +07:00
simon 283a72308e handle deleting channel without videos 2021-10-31 17:30:29 +07:00
simon 164ee5c523 more valueerrors in url extraction 2021-10-31 17:05:13 +07:00
simon bce1022677 userspace channelID view_style 2021-10-31 16:15:33 +07:00
simon 1ba3090db1 rewrite url_str extractor to convert channel names into channel ids, #40 2021-10-31 16:04:28 +07:00
simon c92800701a be more specific about multi-single user 2021-10-30 19:32:47 +07:00
simon caff9ce189 standardize parse_url_list to only return ids 2021-10-30 17:51:50 +07:00
simon 3375cbc767 cleanup logging 2021-10-30 17:51:07 +07:00
simon cc90e437a9 show progress message for thumbnail download 2021-10-30 17:06:54 +07:00
simon 29fc5fbda2 improved progress message for frontend feedback 2021-10-30 17:06:22 +07:00
simon 20aab04fbf fix keyerror 2021-10-30 16:35:10 +07:00
simon 45c2215dc2 validate subscribe form and handover to scheduler, #75 2021-10-30 15:00:10 +07:00
simon aebf44ee7e validate add to queue form 2021-10-30 14:14:16 +07:00
simon 266d3703cd validate search forms 2021-10-30 13:19:16 +07:00
simon a8ded25b35 userspace color 2021-10-29 23:43:19 +07:00
simon a4397b5204 validate settings form and userspace archive page_size 2021-10-29 22:37:31 +07:00
simon f24a3dd1f5 userspace sort_order and sort_by 2021-10-29 14:42:12 +07:00
simon ef13b9259e userspace hide_watched in home and channelid view 2021-10-29 14:02:21 +07:00
simon 180a63d6dd userspace show_subed_only in channel view 2021-10-29 13:35:37 +07:00
simon 88f28a7d1e userspace show_ignored_only on downloads view 2021-10-29 12:23:33 +07:00
simon b2ca7710cd userspace gird-list view 2021-10-29 11:45:13 +07:00
simon 21d55561a0 extend sort and filter to channel_id view 2021-10-28 18:00:59 +07:00
simon 8255fe9e55 validate expected env vars before starting 2021-10-28 16:17:47 +07:00
simon 254c518505 basic auth for elasticsearch 2021-10-28 15:49:58 +07:00
simon ebd8368856 namespace redis keys to ta: 2021-10-27 18:07:35 +07:00
simon 78561981ad extend sort_by and sort_order in home view 2021-10-27 15:29:17 +07:00
simon d4e266f9c0 refactor home view to view_config dict 2021-10-27 11:01:09 +07:00
simon bb909d866c consolidating elasticsearch details 2021-10-26 18:38:43 +07:00
simon 969b6bff0d add throttled-rate and user management docs 2021-10-26 18:21:36 +07:00
simon 5f3fc460c1 implementing throttled-rate option 2021-10-26 17:14:26 +07:00
simon 581314a8a4 bump yt_dlp version 2021-10-26 17:07:52 +07:00
simon 50396a666d use expose instead of publish 2021-10-26 16:23:41 +07:00
simon 3526d62540 custom user models for admin login 2021-10-25 19:28:05 +07:00
simon 6f58dc47cc add admin interface link 2021-10-25 14:41:21 +07:00
simon 1283f437c3 use admin pw hash as secret_key 2021-10-25 14:22:23 +07:00
simon 247c0845e0 put db on persistend volume 2021-10-25 13:40:09 +07:00
simon 66f184b41b increase timeout to wait for es 2021-10-25 13:23:54 +07:00
simon 0ce5183056 handle failed login 2021-10-24 15:34:00 +07:00
simon dffe974f9e move redundant keywords from downlaod title to badge, #64 2021-10-22 18:24:18 +07:00
simon 05b25d004e fix linting 2021-10-22 18:23:06 +07:00
simon 825ebd874e handle next url parameter in login 2021-10-22 12:01:30 +07:00
simon 01ccca16e4 added logout functionality 2021-10-22 11:45:06 +07:00
simon 3d0859ceec add basic usermanagement and login functionality 2021-10-21 19:33:27 +07:00
simon 401aa6c2f8 Merge branch 'master' into testing 2021-10-21 11:24:26 +07:00
simon aeb6f48ac4 add iOS compatible format example to settings page 2021-10-21 11:15:38 +07:00
borgmanJeremy d03c45e580
Updated readme to specify how to operate with iOS and MacOS. (#66) issue #61 2021-10-21 11:13:39 +07:00
simon e4352e5bec playsinline fix for mobile safari 2021-10-20 20:49:20 +07:00
simon 58efe64c5d make chown command optional by omitting HOST_UID and HOST_GID, #58 2021-10-20 18:41:39 +07:00
simon 6f4bab41d5 increase uwsgi buffer-size, #60 #48 2021-10-20 18:16:53 +07:00
simon d250ed645a initial template for login route 2021-10-18 17:14:59 +07:00
Crocs 09584e43b8
Update README.md (#57)
Unraid installation instructions
2021-10-18 10:20:59 +07:00
simon 4ef98b7448 simplify docker deployment 2021-10-17 13:29:22 +07:00
simon 528938942a update docs for v0.0.6 take 2 2021-10-17 11:31:21 +07:00
simon de2371c43c handle running empty dl queue, #55 2021-10-17 11:28:05 +07:00
simon 1a88c85436 skip video if is_live now 2021-10-17 10:49:02 +07:00
simon 63be6afcfe allow duration_sec extraction to fail cleanly for add to queue 2021-10-17 09:40:46 +07:00
simon 559a4c9a58 a note about multi arch builds 2021-10-16 18:12:41 +07:00
simon 08338f3270 add thumbnail download for manual import 2021-10-16 17:03:39 +07:00
simon 0c3c1b650a disable notifiation on rescan, fix keyerror issue in missing_thumbs build 2021-10-15 13:16:35 +07:00
simon 45d8b7e218 ignore missing video rating 2021-10-14 23:12:30 +07:00
simon 5edc78a826 update docs for v0.0.6 2021-10-14 22:48:43 +07:00
simon fd2fa628e3 make sure to get both banner and icon 2021-10-14 22:48:23 +07:00
simon ba12c2ad25 simple progress message for thumbnail bulk download 2021-10-14 21:28:27 +07:00
simon 0d061d8601 get art work for new subscribed channels 2021-10-14 17:47:07 +07:00
simon 0844f7b91a add delayed retry for non 404 thumb download error 2021-10-14 14:44:50 +07:00
simon 044b12d5a5 remove now unused thumbnail methods from searching module 2021-10-14 14:42:35 +07:00
simon ce5f8b6a38 cleanup method for unused thumbs 2021-10-14 11:59:30 +07:00
simon 9ad42dfa38 add artwork refresh on rescan 2021-10-14 11:18:32 +07:00
simon 952bc4f97c add thumb dl after rescan 2021-10-14 10:03:54 +07:00
simon bfd51e6804 extend test deploy to take platform argument 2021-10-13 19:34:55 +07:00
simon 72af95acd8 fallback channel and video artwork for 404 errors 2021-10-13 19:34:16 +07:00
simon 3473c79d63 use TARGETPLATFORM to install repo ffmpeg for arm64 and patched ffmpeg for amd64 builds 2021-10-12 23:00:25 +07:00
simon a4ae6ca1ca Merge branch 'testing' of github.com:bbilly1/tubearchivist into testing 2021-10-12 17:49:18 +07:00
lamusmaser 48a4827ad5
Update deploy.sh to utilize buildx for multiarch builds. This is inline with Issue #6. (#51) 2021-10-12 17:47:48 +07:00
simon 29f17680bf consolidate delete thumbnails into ThumbManager class 2021-10-11 19:29:27 +07:00
simon 519c710e7d testing tests 2021-10-11 16:55:47 +07:00
simon 1b04c00ae7 standardizing on progress/ url 2021-10-11 16:55:08 +07:00
simon 9f12966ef7 bumping yt_dlp version, requires additional noprogress option 2021-10-11 16:34:19 +07:00
simon 76f49b4cf7 comparing with instance instead 2021-10-11 16:11:30 +07:00
simon 35a5eda36e downloading channel artwork after scraping 2021-10-11 16:03:25 +07:00
simon 567e9d473b fix killing task with missing task id 2021-10-11 15:54:36 +07:00
simon e202265a5f extended validate_thumbnails for channel art 2021-10-11 14:27:48 +07:00
simon bc84696792 new thumbnails module and new cache layout 2021-10-11 13:33:28 +07:00
simon f5621954fb star icons for video rating 2021-10-10 16:09:02 +07:00
simon ef75f6dd16 add delete channel to frontend 2021-10-09 20:33:32 +07:00
simon b53a1763c8 better channel folder path builder with clean_string 2021-10-09 20:32:42 +07:00
simon 11780a0ff0 add delete channel backend 2021-10-09 19:54:36 +07:00
simon 8d3e2f14fc add delete video to frontend 2021-10-09 17:11:13 +07:00
simon 7405e960e5 delete video backend functionality 2021-10-08 16:18:01 +07:00
simon 8c97e6786a chown to fix permissions on new channel folders 2021-10-08 15:16:02 +07:00
simon 563222a26e fix linting 2021-10-08 15:10:44 +07:00
simon 64ccd3830e implementing filesystem rescan to clean index 2021-10-08 14:56:07 +07:00
simon c2a6ac6f44 fix wiki link 2021-10-08 11:36:18 +07:00
simon 0b88fd8b1f implement watched-unwatched toggle, #39 2021-10-07 23:38:17 +07:00
simon 744780f4bd add optional EmbedThumbnail postprocessor 2021-10-07 22:28:32 +07:00
simon 4d152e1e54 bumping versions 2021-10-07 20:08:38 +07:00
simon 3500044b0d adding woff files and OFL license 2021-10-07 19:41:52 +07:00
simon 7e0abc0d20 fix run sync_redis_state on startup 2021-10-04 01:07:15 +07:00
simon b0fb2bbb00 update roadmap 2021-10-03 20:25:35 +07:00
simon 4e7cba70d5 update to new ffmpeg naming convention 2021-10-03 20:25:23 +07:00
simon 28216e455e bumping versions for new release 2021-10-03 19:35:33 +07:00
simon 7df4233224 cleaning up leftover code from old vesions 2021-10-03 19:33:58 +07:00
simon 5c2a8286d3 cleanup and fixing some spacing issues 2021-10-03 18:54:04 +07:00
simon f5f3617e96 implement toggle view to hide watched videos 2021-10-03 18:52:29 +07:00
simon ccc8a658b7 implement toggle view to show subscribed only 2021-10-03 18:44:44 +07:00
simon ed73bff8fa implement toggle view ignored only in downloads page 2021-10-03 18:17:07 +07:00
simon 26a0f8930a renaming 'download queue' button to 'start download' 2021-10-03 12:25:56 +07:00
simon 30d95315b2 consolidate documentation in wiki 2021-10-03 12:05:39 +07:00
simon c95c16208d removed documentation with links to wiki and more 2021-10-03 11:47:29 +07:00
simon 18459e2486 about closing issues 2021-10-03 11:24:31 +07:00
simon 280e83bb2c kibana for testing ES queries 2021-10-02 19:48:17 +07:00
simon 95cdcbae9a grid and list view for channels overview page 2021-10-02 19:39:02 +07:00
simon 0b0502e246 grid and list view for downloads template 2021-10-02 18:37:22 +07:00
simon 99781290db grid and list view for home and channel id templates 2021-10-01 16:24:39 +07:00
simon 7c34ceb9f8 framework to switch between grid and list view 2021-10-01 14:42:17 +07:00
simon 21ad1fd832 use latest tag instead 2021-10-01 13:44:14 +07:00
simon b12df3f312 using ffmpeg patched builds instead of repo version, #37 #26 2021-09-30 22:01:30 +07:00
simon 4d348955a3 allowing custom redis port 2021-09-30 18:03:23 +07:00
simon 2b89408ec1 ignore wiki from container 2021-09-30 17:58:56 +07:00
simon e52becd50e a word about setting up your testing environment 2021-09-30 14:21:48 +07:00
simon 2b6a805c1d fix links and icons 2021-09-29 13:19:21 +07:00
simon 108b1bac1f fix links 2021-09-28 22:09:58 +07:00
simon 12ad8fcead documenting main pages 2021-09-28 21:58:43 +07:00
simon 2905a67ab9 show ignored videos, forget or download ids 2021-09-28 16:53:45 +07:00
simon f5a56fca86 refactored redis functions into dedicated RedisArchivist class 2021-09-28 10:33:00 +07:00
simon a242f744d5 moove startup functions to ready() method to avoid double execs 2021-09-27 17:45:44 +07:00
simon 3d79f08311 handle return value of del_message 2021-09-27 17:44:42 +07:00
simon f22e02d2f1 hotfix to release leftover locks on restart 2021-09-27 10:38:57 +07:00
simon 9528e11ca2 updated roadmap 2021-09-26 12:59:58 +07:00
simon 85eaac0b57 bumping versions for new release 2021-09-26 12:05:41 +07:00
simon fab918db79 setting limit_count default to false after implementing dynamic dl queue 2021-09-26 12:01:51 +07:00
simon 72e45bcf5a restructured, added comment about updating and donating 2021-09-26 11:34:54 +07:00
simon ea2d0bcb6c initial wiki pages 2021-09-26 11:21:54 +07:00
simon 11067094b2 implement os.listdir sanitizer for hidden files, #30 2021-09-25 18:59:54 +07:00
simon 2de99d7e37 better implementation for dl icon directly in message builder 2021-09-25 18:54:37 +07:00
simon c165f152a9 allowing to cancle download_now tasks 2021-09-25 17:40:33 +07:00
simon 51ec765433 add kill queue function to frontend 2021-09-25 15:35:36 +07:00
simon 47020e0cfa implement kill function for dl queue 2021-09-24 23:37:26 +07:00
simon f53391c1bb add limit_queue setting back, make buttons only show up while downlaoding 2021-09-24 21:27:53 +07:00
simon d0b54f8a88 add stop queue button to frontend 2021-09-24 18:03:22 +07:00
simon 91a57cc780 fix duplication issue with download_now 2021-09-24 16:58:48 +07:00
simon fb1913f912 ignore now also removes itself from redis queue 2021-09-24 16:58:05 +07:00
simon ea6d81102f a word about updating tube archivist 2021-09-23 22:40:38 +07:00
simon 8fc5926ce7 rewrote download_single and download_pending tasks for redis queue 2021-09-23 18:10:45 +07:00
simon 78d8bc9d24 changed dl queue to redis, changed sort order to represent actual queue order 2021-09-23 18:09:46 +07:00
simon 7f57cabbc6 initial dynamic dl queue in redis 2021-09-23 16:58:47 +07:00
simon 214a248821 better error handeling in add to download form 2021-09-23 10:50:44 +07:00
simon ef3447cbfb fix read waiting for return issue 2021-09-22 18:11:05 +07:00
194 changed files with 10656 additions and 5814 deletions

28
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,28 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.224.3/containers/python-3/.devcontainer/base.Dockerfile
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
ARG VARIANT="3.10-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
RUN sed -i 's/required/sufficient/g' /etc/pam.d/chsh
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
COPY tubearchivist/requirements.txt /tmp/pip-tmp/
RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
&& rm -rf /tmp/pip-tmp
# [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends fish
ENV SHELL /usr/bin/fish
USER vscode
RUN fish -c "curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher"
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View File

@ -0,0 +1,64 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.224.3/containers/python-3
{
"name": "Python 3",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.10-bullseye",
// Options
"NODE_VERSION": "16"
}
},
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
"typescript.tsdk": "tubearchivist/www/node_modules/typescript/lib",
"terminal.integrated.defaultProfile.linux": "fish"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"batisteo.vscode-django",
"christian-kohler.path-intellisense",
"quicktype.quicktype"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000, 8000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "chsh -s /usr/bin/fish && fish -c 'fisher install matchai/spacefish'",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
"features": {
// "fish": "latest",
"github-cli": "latest",
"docker-in-docker": {
"version": "latest",
"moby": true
},
"git": "os-provided"
}
}

View File

@ -17,5 +17,8 @@ venv/
# Unneeded graphics
assets/*
# Unneeded docs
docs/*
# for local testing only
testing.sh

8
.env.local.example Normal file
View File

@ -0,0 +1,8 @@
# https://next-auth.js.org/configuration/options#nextauth_secret Used to encrypt JWT
NEXTAUTH_SECRET=
# https://next-auth.js.org/configuration/options#nextauth_url When deploying to production, set the NEXTAUTH_URL environment variable to the canonical URL of your site.
NEXTAUTH_URL=
# URL of the Tubearchivist server without a trailing /
NEXT_PUBLIC_TUBEARCHIVIST_URL=

3
.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

27
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Node.js CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn lint

View File

@ -1,23 +0,0 @@
name: lint_python
on: [pull_request, push]
jobs:
lint_python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- run: pip install --upgrade pip wheel
- run: pip install bandit black codespell flake8 flake8-bugbear
flake8-comprehensions isort
- run: bandit --recursive --skip B105,B108,B404,B603,B607 .
- run: black --check --diff --line-length 79 .
- run: codespell
- run: flake8 . --count --max-complexity=12 --max-line-length=79
--show-source --statistics
- run: isort --check-only --line-length 79 --profile black .
# - run: pip install -r tubearchivist/requirements.txt
# - run: mkdir --parents --verbose .mypy_cache
# - run: mypy --ignore-missing-imports --install-types --non-interactive .
# - run: python3 tubearchivist/manage.py test || true
# - run: shopt -s globstar && pyupgrade --py36-plus **/*.py || true
# - run: safety check

39
.gitignore vendored
View File

@ -1,8 +1,35 @@
# python testing cache
__pycache__
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# django testing db
db.sqlite3
# dependencies
/node_modules
/.pnp
.pnp.js
# frontend fonts
*ttf.woff
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

10
.prettierrc Normal file
View File

@ -0,0 +1,10 @@
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always"
}

View File

@ -1,5 +0,0 @@
{
"python.linting.pylintEnabled": true,
"python.linting.pycodestyleEnabled": false,
"python.linting.enabled": true
}

View File

@ -5,8 +5,38 @@ If you haven't already, the best place to start is the README. This will give yo
## Report a bug
If you notice something is not working as expected, check to see if it has been previously reported in the [open issues](https://github.com/bbilly1/tubearchivist/issues).
If it has not yet been disclosed, go ahead and create an issue.
If you notice something is not working as expected, check to see if it has been previously reported in the [open issues](https://github.com/tubearchivist/tubearchivist/issues).
If it has not yet been disclosed, go ahead and create an issue.
If the issue doesn't move forward due to a lack of response, I assume it's solved and will close it after some time to keep the list fresh.
## Wiki
The wiki is where all user functions are explained in detail. These pages are mirrored into the **docs** folder of the repo. This allows for pull requests and all other features like regular code. Make any changes there, and I'll sync them with the wiki tab.
## Development Environment
I have learned the hard way, that working on a dockerized application outside of docker is very error prone and in general not a good idea. So if you want to test your changes, it's best to run them in a docker testing environment.
This is my setup I have landed on, YMMV:
- Clone the repo, work on it with your favorite code editor in your local filesystem. *testing* branch is the where all the changes are happening, might be unstable and is WIP.
- Then I have a VM on KVM hypervisor running standard Ubuntu Server LTS with docker installed. The VM keeps my projects separate and offers convenient snapshot functionality. The VM also offers ways to simulate lowend environments by limiting CPU cores and memory. But you could also just run docker on your host system.
- The `Dockerfile` is structured in a way that the actual application code is in the last layer so rebuilding the image with only code changes utilizes the build cache for everything else and will just take a few seconds.
- Take a look at the `deploy.sh` file. I have my local DNS resolve `tubearchivist.local` to the IP of the VM for convenience. To deploy the latest changes and rebuild the application to the testing VM run:
```bash
./deploy.sh test
```
- The command above will call the docker build command with `--build-arg INSTALL_DEBUG=1` to install additional useful debug tools.
- The `test` argument takes another optional argument to build for a specific architecture valid options are: `amd64`, `arm64` and `multi`, default is `amd64`.
- This `deploy.sh` file is not meant to be universally usable for every possible environment but could serve as an idea on how to automatically rebuild containers to test changes - customize to your liking.
## Working with Elasticsearch
Additionally to the required services as listed in the example docker-compose file, the **Dev Tools** of [Kibana](https://www.elastic.co/guide/en/kibana/current/docker.html) are invaluable for running and testing Elasticsearch queries.
If you want to run queries in on the Elasticsearch container directly from your host with for example `curl` or something like *postman*, you might want to **publish** the port 9200 instead of just **exposing** it.
## Implementing a new feature
Do you see anything on the roadmap that you would like to take a closer look at but you are not sure, what's the best way to tackle that? Or anything not on there yet you'd like to implement but are not sure how? Open up an issue and we try to find a solution together.
## Making changes
@ -14,7 +44,12 @@ To fix a bug or implement a feature, fork the repository and make all changes to
## Releases
Everything on the master branch is what's in the latest release and is what you get in your container when you `pull` either the *:latest* tag or the newest named version. If you want to test the newest changes and improvements, clone the repository and build the docker container with the Dockerfile from the testing branch.
There are three different docker tags:
- **latest**: As the name implies is the latest multiarch release for regular usage.
- **unstable**: Intermediate amd64 builds for quick testing and improved collaboration. Don't mix with a *latest* installation, for your testing environment only. This is untested and WIP and will have breaking changes between commits that might require a reset to resolve.
- **semantic versioning**: There will be a handful named version tags that will also have a matching release and tag on github.
If you want to see what's in your container, checkout the matching release tag. A merge to **master** usually means a *latest* or *unstable* release. If you want to preview changes in your testing environment, pull the *unstable* tag or clone the repository and build the docker container with the Dockerfile from the **testing** branch.
## Code formatting and linting

View File

@ -1,32 +1,71 @@
# build the tube archivist image from default python slim image
# multi stage to build tube archivist
# first stage to build python wheel, copy into final image
FROM python:3.9.7-slim-bullseye
# First stage to build python wheel
FROM python:3.10.4-slim-bullseye AS builder
ARG TARGETPLATFORM
RUN apt-get update
RUN apt-get install -y --no-install-recommends build-essential gcc curl
# get newest patched ffmpeg and ffprobe builds for amd64 fall back to repo ffmpeg for arm64
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ] ; then \
curl -s https://api.github.com/repos/yt-dlp/FFmpeg-Builds/releases/latest \
| grep browser_download_url \
| grep ".*master.*linux64.*tar.xz" \
| cut -d '"' -f 4 \
| xargs curl -L --output ffmpeg.tar.xz && \
tar -xf ffmpeg.tar.xz --strip-components=2 --no-anchored -C /usr/bin/ "ffmpeg" && \
tar -xf ffmpeg.tar.xz --strip-components=2 --no-anchored -C /usr/bin/ "ffprobe" && \
rm ffmpeg.tar.xz \
; elif [ "$TARGETPLATFORM" = "linux/arm64" ] ; then \
apt-get -y update && apt-get -y install --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/* \
; fi
# install requirements
COPY ./tubearchivist/requirements.txt /requirements.txt
RUN pip install --user -r requirements.txt
# build final image
FROM python:3.10.4-slim-bullseye as tubearchivist
ARG TARGETPLATFORM
ARG INSTALL_DEBUG
ENV PYTHONUNBUFFERED 1
# copy build requirements
COPY --from=builder /root/.local /root/.local
COPY --from=builder /usr/bin/ffmpeg /usr/bin/ffmpeg
COPY --from=builder /usr/bin/ffprobe /usr/bin/ffprobe
ENV PATH=/root/.local/bin:$PATH
# install distro packages needed
RUN apt-get clean && apt-get -y update && apt-get -y install --no-install-recommends \
build-essential \
ffmpeg \
nginx \
atomicparsley \
curl && rm -rf /var/lib/apt/lists/*
# copy config files
COPY nginx.conf /etc/nginx/conf.d/
# install debug tools for testing environment
RUN if [ "$INSTALL_DEBUG" ] ; then \
apt-get -y update && apt-get -y install --no-install-recommends \
vim htop bmon net-tools iputils-ping procps \
&& pip install --user ipython \
; fi
# make folders
RUN mkdir /cache
RUN mkdir /youtube
RUN mkdir /app
# install python dependencies
COPY ./tubearchivist/requirements.txt /requirements.txt
RUN pip install --no-cache-dir -r requirements.txt --src /usr/local/src
# copy config files
COPY docker_assets/nginx.conf /etc/nginx/sites-available/default
# copy application into container
COPY ./tubearchivist /app
COPY ./run.sh /app
COPY ./uwsgi.ini /app
COPY ./docker_assets/run.sh /app
COPY ./docker_assets/uwsgi.ini /app
# volumes
VOLUME /cache

157
README.md
View File

@ -1,138 +1,51 @@
![Tube Archivist](assets/tube-archivist-banner.jpg?raw=true "Tube Archivist Banner")
# Tube Archivist Frontend
<center><h1>Your self hosted YouTube media server</h1></center>
This repo is WIP, recreation of [Tube Archivist](https://github.com/tubearchivist/tubearchivist) frontend in NextJS/React.
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Core functionality
* Subscribe to your favorite YouTube channels
* Download Videos using **yt-dlp**
* Index and make videos searchable
* Play videos
* Keep track of viewed and unviewed videos
## Setup your environment
Copy *.env.local.example* to *.env.local* and set:
- **NEXTAUTH_SECRET**: Some long random string
- **NEXTAUTH_URL**: Your frontend, most likely `http://localhost:3000`
- **NEXT_PUBLIC_TUBEARCHIVIST_URL**: Your Tube Archivist backend testing server, e.g. `http://localhost:8000`
## Screenshots
![home screenshot](assets/tube-archivist-screenshot-home.png?raw=true "Tube Archivist Home")
*Home Page*
![channels screenshot](assets/tube-archivist-screenshot-channels.png?raw=true "Tube Archivist Channels")
*All Channels*
![single channel screenshot](assets/tube-archivist-screenshot-single-channel.png?raw=true "Tube Archivist Single Channel")
*Single Channel*
![video page screenshot](assets/tube-archivist-screenshot-video.png?raw=true "Tube Archivist Video Page")
*Video Page*
![video page screenshot](assets/tube-archivist-screenshot-download.png?raw=true "Tube Archivist Video Page")
*Downloads Page*
## Problem Tube Archivist tries to solve
Once your YouTube video collection grows, it becomes hard to search and find a specific video. That's where Tube Archivist comes in: By indexing your video collection with metadata from YouTube, you can organize, search and enjoy your archived YouTube videos without hassle offline through a convenient web interface.
## Installation
Take a look at the example `docker-compose.yml` file provided. Tube Archivist depends on three main components split up into separate docker containers:
### Tube Archivist
The main Python application that displays and serves your video collection, built with Django.
- Serves the interface on port `8000`
- Needs a mandatory volume for the video archive at **/youtube**
- And another recommended volume to save the cache for thumbnails and artwork at **/cache**.
- The environment variables `ES_URL` and `REDIS_HOST` are needed to tell Tube Archivist where Elasticsearch and Redis respectively are located.
- The environment variables `HOST_UID` and `HOST_GID` allows Tube Archivist to `chown` the video files to the main host system user instead of the container user.
### Elasticsearch
Stores video meta data and makes everything searchable. Also keeps track of the download queue.
- Needs to be accessible over the default port `9200`
- Needs a volume at **/usr/share/elasticsearch/data** to store data
Follow the [documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html) for additional installation details.
### Redis JSON
Functions as a cache and temporary link between the application and the file system. Used to store and display messages and configuration variables.
- Needs to be accessible over the default port `6379`
- Takes an optional volume at **/data** to make your configuration changes permanent.
In general: Use the [unstable builds](https://github.com/tubearchivist/tubearchivist/blob/master/CONTRIBUTING.md#releases) from Tube Archivist or build the image yourself from *testing* branch.
## Getting Started
1. Go through the **settings** page and look at the available options. Particularly set *Download Format* to your desired video quality before downloading. **Tube Archivist** downloads the best available quality by default.
2. Subscribe to some of your favorite YouTube channels on the **channels** page.
3. On the **downloads** page, click on *Rescan subscriptions* to add videos from the subscribed channels to your Download queue or click on *Add to download queue* to manually add Video IDs, links, channels or playlists.
4. Click on *Download queue* and let Tube Archivist to it's thing.
5. Enjoy your archived collection!
## Import your existing library
So far this depends on the video you are trying to import to be still available on YouTube to get the metadata. Add the files you like to import to the */cache/import* folder. Then start the process from the settings page *Manual media files import*. Make sure to follow one of the two methods below.
### Method 1:
Add a matching *.json* file with the media file. Both files need to have the same base name, for example:
- For the media file: \<base-name>.mp4
- For the JSON file: \<base-name>.info.json
- Alternate JSON file: \<base-name>.json
First, run the development server:
**Tube Archivist** then looks for the 'id' key within the JSON file to identify the video.
### Method 2:
Detect the YouTube ID from filename, this accepts the default yt-dlp naming convention for file names like:
- \<base-name>[\<youtube-id>].mp4
- The YouTube ID in square brackets at the end of the filename is the crucial part.
### Some notes:
- This will **consume** the files you put into the import folder: Files will get converted to mp4 if needed (this might take a long time...) and moved to the archive, *.json* files will get deleted upon completion to avoid having duplicates on the next run.
- Maybe start with a subset of your files to import to make sure everything goes well...
- Follow the logs to monitor progress and errors: `docker-compose logs -f tubearchivist`.
## Backup and restore
From the settings page you can backup your metadata into a zip file. The file will get stored at *cache/backup* and will contain the necessary files to restore the Elasticsearch index formatted **nd-json** files as well a complete export of the index in a set of conventional **json** files.
The restore functionality will expect the same zip file in *cache/backup* and will recreate the index from the snapshot.
BE AWARE: This will **replace** your current index with the one from the backup file.
## Potential pitfalls
### vm.max_map_count
**Elastic Search** in Docker requires the kernel setting of the host machine `vm.max_map_count` to be set to at least 262144.
To temporary set the value run:
```bash
npm run dev
# or
yarn dev
```
sudo sysctl -w vm.max_map_count=262144
```
To apply the change permanently depends on your host operating system:
- For example on Ubuntu Server add `vm.max_map_count = 262144` to the file */etc/sysctl.conf*.
- On Arch based systems create a file */etc/sysctl.d/max_map_count.conf* with the content `vm.max_map_count = 262144`.
- On any other platform look up in the documentation on how to pass kernel parameters.
### Errors:
- *next command not found*: Install next with `npm install next`
- *Error: Invalid src prop [...] hostname [...] is not configured under images in your `next.config.js`*: Add the *NEXT_PUBLIC_TUBEARCHIVIST_URL* to the list of *domains*.
- *CORS errors in console*: Set the environment variable `DISABLE_CORS=True` to the Tube Archivist container to circumvent this protection. NEVER do that on network accessible installation.
### Permissions for elasticsearch
If you see a message similar to `AccessDeniedException[/usr/share/elasticsearch/data/nodes]` when initially starting elasticsearch, that means the container is not allowed to write files to the volume.
That's most likely the case when you run `docker-compose` as an unprivileged user. To fix that issue, shutdown the container and on your host machine run:
```
chown 1000:0 /path/to/mount/point
```
This will match the permissions with the **UID** and **GID** of elasticsearch within the container and should fix the issue.
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Roadmap
This should be considered as a **minimal viable product**, there is an extensive list of future functions and improvements planned.
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
### Functionality
- [ ] Access control
- [ ] User roles
- [ ] Delete videos and channel
- [ ] Create playlists
- [ ] Podcast mode to serve channel as mp3
- [ ] Implement [PyFilesystem](https://github.com/PyFilesystem/pyfilesystem2) for flexible video storage
- [ ] Dynamic download queue
- [ ] Un-ignore videos
- [X] Backup and restore [2021-09-22]
- [X] Scan your file system to index already downloaded videos [2021-09-14]
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
### UI
- [ ] Create a github wiki for user documentation
- [ ] Show similar videos on video page
- [ ] Multi language support
- [ ] Grid and list view for both channel and video list pages
- [ ] Show total video downloaded vs total videos available in channel
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
## Known limitations
- Video files created by Tube Archivist need to be **mp4** video files for best browser compatibility.
- Every limitation of **yt-dlp** will also be present in Tube Archivist. If **yt-dlp** can't download or extract a video for any reason, Tube Archivist won't be able to either.
- For now this is meant to be run in a trusted network environment.
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 238 KiB

146
deploy.sh
View File

@ -1,146 +0,0 @@
#!/bin/bash
# deploy all needed project files to different servers:
# test for local vm for testing
# blackhole for local production
# docker to publish
set -e
function sync_blackhole {
# docker commands need sudo
host="blackhole.local"
read -sp 'Password: ' remote_pw
export PASS=$remote_pw
rsync -a --progress --delete-after \
--exclude ".git" \
--exclude ".gitignore" \
--exclude "**/cache" \
--exclude "**/__pycache__/" \
--exclude "db.sqlite3" \
. -e ssh "$host":tubearchivist
echo "$PASS" | ssh "$host" 'sudo -S docker build -t bbilly1/tubearchivist:latest tubearchivist 2>/dev/null'
echo "$PASS" | ssh "$host" 'sudo -S docker-compose up -d 2>/dev/null'
}
function sync_test {
# docker commands don't need sudo in testing vm
host="tubearchivist.local"
rsync -a --progress --delete-after \
--exclude ".git" \
--exclude ".gitignore" \
--exclude "**/cache" \
--exclude "**/__pycache__/" \
--exclude "db.sqlite3" \
. -e ssh "$host":tubearchivist
rsync -r --progress --delete docker-compose.yml -e ssh "$host":docker
ssh "$host" 'docker build -t bbilly1/tubearchivist:latest tubearchivist'
ssh "$host" 'docker-compose -f docker/docker-compose.yml up -d'
ssh "$host" 'docker cp tubearchivist/tubearchivist/testing.sh tubearchivist:/app/testing.sh'
ssh "$host" 'docker exec tubearchivist chmod +x /app/testing.sh'
}
# run same tests and checks as with github action but locally
# takes filename to validate as optional argument
function validate {
if [[ $1 ]]; then
check_path="$1"
else
check_path="."
fi
echo "run validate on $check_path"
echo "running bandit"
bandit --recursive --skip B105,B108,B404,B603,B607 "$check_path"
echo "running black"
black --diff --color --check -l 79 "$check_path"
echo "running codespell"
codespell --skip="./.git" "$check_path"
echo "running flake8"
flake8 "$check_path" --count --max-complexity=12 --max-line-length=79 \
--show-source --statistics
echo "running isort"
isort --check-only --diff --profile black -l 79 "$check_path"
printf " \n> all validations passed\n"
}
function sync_docker {
# check things
if [[ $(git branch --show-current) != 'master' ]]; then
echo 'you are not on master, dummy!'
return
fi
if [[ $(systemctl is-active docker) != 'active' ]]; then
echo "starting docker"
sudo systemctl start docker
fi
echo "latest tags:"
git tag
echo "latest docker images:"
sudo docker image ls bbilly1/tubearchivist
printf "\ncreate new version:\n"
read -r VERSION
# start build
sudo docker build -t bbilly1/tubearchivist:latest -t bbilly1/tubearchivist:"$VERSION" .
printf "\nlatest images:\n"
sudo docker image ls bbilly1/tubearchivist
read -s "Push?"
# push to docker
echo "pushing latest:"
sudo docker push bbilly1/tubearchivist:latest
echo "pushing $VERSION"
sudo docker push bbilly1/tubearchivist:"$VERSION"
# create release tag
echo "commits since last version:"
git log "$(git describe --tags --abbrev=0)"..HEAD --oneline
git tag -a "$VERSION" -m "new release version $VERSION"
git push all "$VERSION"
}
# check package versions in requirements.txt for updates
python version_check.py
if [[ $1 == "blackhole" ]]; then
sync_blackhole
elif [[ $1 == "test" ]]; then
sync_test
elif [[ $1 == "validate" ]]; then
validate "$2"
elif [[ $1 == "docker" ]]; then
sync_docker
else
echo "valid options are: blackhole | test | validate | docker"
fi
##
exit 0

View File

@ -8,31 +8,37 @@ services:
ports:
- 8000:8000
volumes:
- ./volumes/tubearchivist/media:/youtube
- ./volumes/tubearchivist/cache:/cache
- media:/youtube
- cache:/cache
environment:
- ES_URL=http://archivist-es:9200
- REDIS_HOST=archivist-redis
- ES_URL=http://archivist-es:9200 # needs protocol e.g. http and port
- REDIS_HOST=archivist-redis # don't add protocol
- HOST_UID=1000
- HOST_GID=1000
- TA_USERNAME=tubearchivist # your initial TA credentials
- TA_PASSWORD=verysecret # your initial TA credentials
- ELASTIC_PASSWORD=verysecret # set password for Elasticsearch
- TZ=America/New_York # set your time zone
depends_on:
- archivist-es
- archivist-redis
archivist-redis:
image: redislabs/rejson:latest
image: redislabs/rejson:latest # for arm64 use bbilly1/rejson
container_name: archivist-redis
restart: always
ports:
- 6379:6379
expose:
- "6379"
volumes:
- ./volumes/tubearchivist/redis:/data
- redis:/data
depends_on:
- archivist-es
archivist-es:
image: docker.elastic.co/elasticsearch/elasticsearch:7.14.1
image: bbilly1/tubearchivist-es # only for amd64, or use official es 7.17.2
container_name: archivist-es
restart: always
environment:
- "xpack.security.enabled=true"
- "ELASTIC_PASSWORD=verysecret" # matching Elasticsearch password
- "discovery.type=single-node"
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
@ -40,6 +46,12 @@ services:
soft: -1
hard: -1
volumes:
- ./volumes/tubearchivist/es:/usr/share/elasticsearch/data
ports:
- 9200:9200
- es:/usr/share/elasticsearch/data # check for permission error when using bind mount, see readme
expose:
- "9200"
volumes:
media:
cache:
redis:
es:

5
next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

9
next.config.js Normal file
View File

@ -0,0 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ["localhost", "tube.stiforr.tech"],
},
reactStrictMode: true,
};
module.exports = nextConfig;

View File

@ -1,18 +0,0 @@
server {
listen 8000;
location /cache/ {
alias /cache/;
}
location /media/ {
alias /youtube/;
}
location / {
include uwsgi_params;
uwsgi_pass localhost:8080;
}
}

36
package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "tubearchivist-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"prepare": "husky install"
},
"dependencies": {
"next": "12.1.1",
"next-auth": "4.7.0",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-player": "2.10.0",
"react-query": "3.39.1",
"sharp": "0.30.3"
},
"devDependencies": {
"@types/node": "17.0.23",
"@types/react": "17.0.43",
"@types/react-dom": "17.0.14",
"eslint": "8.12.0",
"eslint-config-next": "12.1.1",
"husky": "7.0.4",
"lint-staged": "12.4.0",
"prettier": "2.6.1",
"typescript": "4.5.5"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --cache --fix",
"*.{ts,tsx,css,md}": "prettier --write"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/static/favicon/mstile-150x150.png"/>
<TileColor>#01202e</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/favicon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,100 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1000.000000pt" height="1000.000000pt" viewBox="0 0 1000.000000 1000.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,1000.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M4521 9370 c-4 -3 -7 -31 -6 -63 1 -32 -3 -61 -9 -66 -6 -5 -36 -12
-66 -16 -55 -7 -68 -9 -125 -19 -16 -3 -48 -8 -70 -12 -161 -24 -504 -118
-709 -193 -208 -77 -507 -215 -636 -294 -19 -12 -47 -29 -62 -37 -14 -8 -41
-24 -60 -34 -84 -48 -288 -189 -423 -293 -309 -239 -654 -606 -886 -943 -53
-78 -169 -258 -169 -264 0 -3 -21 -40 -47 -83 -97 -164 -313 -646 -294 -658
14 -8 670 -195 686 -195 8 0 15 9 15 19 0 11 5 23 10 26 6 3 10 13 10 21 0 8
14 44 30 79 17 35 30 67 30 70 0 3 21 48 47 100 l47 95 148 0 c81 0 276 1 433
1 157 0 310 -1 340 -1 l55 0 0 -615 0 -615 415 1 415 0 1 142 c0 78 0 355 1
615 l1 472 46 0 c25 0 322 0 659 0 l612 1 0 32 c1 44 -1 538 -2 660 0 63 -5
97 -12 98 -6 1 -581 1 -1278 0 -698 0 -1268 2 -1268 5 0 7 143 147 241 235 94
85 232 193 329 260 36 24 67 47 68 52 2 4 7 7 12 7 4 0 52 28 106 62 126 79
371 202 510 257 60 23 118 46 129 51 131 56 685 193 713 176 4 -2 7 -34 6 -70
l0 -66 162 0 162 0 0 503 0 502 -155 0 c-85 0 -158 -2 -162 -5z"/>
<path d="M5210 8905 l0 -365 48 -1 c26 0 54 -2 62 -4 8 -1 45 -5 82 -9 72 -6
198 -23 232 -31 12 -3 32 -7 46 -9 284 -48 643 -169 935 -314 527 -262 984
-651 1324 -1127 39 -55 75 -105 81 -112 5 -7 10 -17 10 -23 0 -5 4 -10 9 -10
5 0 14 -12 20 -27 6 -16 24 -48 40 -73 31 -47 61 -101 117 -208 30 -57 32 -65
16 -69 -75 -20 -117 -40 -113 -53 9 -35 83 -285 85 -287 1 -2 18 2 37 8 19 5
41 12 49 14 49 12 856 241 876 249 11 4 10 14 -2 57 -9 29 -17 60 -19 68 -7
32 -53 183 -57 186 -2 2 -22 -2 -43 -9 -93 -31 -88 -33 -126 46 -232 488 -557
933 -950 1303 -285 269 -647 520 -1014 705 -533 268 -1067 412 -1682 455 l-63
4 0 -364z"/>
<path d="M6122 7336 c-4 -6 -14 -31 -23 -56 -17 -47 -238 -630 -288 -760 -16
-41 -61 -160 -100 -265 -40 -104 -76 -199 -80 -210 -10 -23 -54 -138 -104
-275 -20 -52 -41 -108 -48 -125 -7 -16 -13 -32 -13 -35 -1 -3 -7 -18 -13 -35
-6 -16 -55 -145 -108 -285 -53 -140 -102 -271 -110 -290 -8 -19 -48 -125 -89
-235 -41 -110 -90 -240 -109 -290 -19 -49 -76 -200 -127 -335 -133 -356 -151
-402 -166 -437 -8 -17 -14 -34 -14 -37 0 -3 -15 -45 -34 -94 -19 -48 -61 -160
-95 -248 -33 -89 -65 -174 -72 -190 -22 -55 -28 -70 -98 -258 -39 -103 -71
-192 -71 -197 0 -5 182 -8 441 -7 l441 3 114 320 c63 176 126 352 140 390 14
39 46 129 70 200 25 72 50 139 55 150 5 11 22 58 38 105 29 84 89 254 171 480
23 63 47 133 54 155 12 36 70 199 131 365 13 36 60 169 105 295 44 127 85 239
90 250 4 11 62 173 129 360 197 557 186 530 195 504 3 -8 18 -52 35 -99 16
-47 54 -155 84 -240 30 -85 96 -272 146 -415 51 -143 96 -269 100 -280 5 -11
27 -72 49 -135 120 -343 143 -407 152 -422 6 -10 10 -24 10 -33 0 -8 6 -29 14
-47 8 -18 44 -116 80 -218 182 -521 327 -927 336 -942 6 -10 10 -23 10 -29 0
-9 134 -393 176 -502 8 -21 28 -78 45 -128 l31 -90 -49 -56 c-26 -31 -78 -88
-115 -127 -64 -68 -68 -70 -81 -51 -8 11 -28 37 -44 57 l-30 37 -124 -91 c-68
-50 -123 -96 -123 -104 1 -9 281 -404 327 -459 4 -5 62 -84 128 -175 65 -91
124 -166 129 -168 8 -3 73 39 90 58 3 3 42 32 88 64 l82 58 -40 56 -39 56 94
96 c558 566 945 1258 1130 2025 18 77 38 167 44 200 6 33 13 69 15 80 6 33 19
119 21 140 2 11 6 43 9 70 35 253 40 715 11 930 -2 17 -9 75 -15 129 -7 55
-16 109 -20 122 -5 12 -7 24 -5 26 11 11 -85 428 -98 428 -11 0 -680 -190
-685 -195 -2 -1 8 -47 21 -101 14 -55 28 -115 31 -134 3 -19 8 -46 11 -60 10
-53 19 -109 29 -195 4 -33 9 -70 11 -82 8 -53 18 -259 18 -369 -1 -400 -74
-812 -213 -1194 -23 -63 -44 -123 -47 -132 -9 -29 -20 -21 -35 25 -22 62 -198
529 -208 552 -5 11 -69 178 -141 370 -72 193 -187 499 -256 680 -68 182 -164
436 -213 565 -48 129 -93 246 -98 260 -6 14 -27 70 -47 125 -19 55 -40 109
-45 120 -8 18 -191 503 -224 595 -15 42 -95 253 -139 366 l-31 80 -398 0
c-270 0 -401 -3 -406 -10z"/>
<path d="M720 6002 c-47 -160 -68 -237 -64 -243 2 -4 28 -13 56 -19 29 -7 56
-14 59 -16 4 -2 3 -23 -1 -46 -5 -24 -11 -61 -14 -83 -4 -23 -8 -50 -10 -60
-2 -11 -7 -47 -10 -80 -4 -33 -9 -73 -11 -90 -31 -231 -26 -701 11 -955 3 -25
8 -61 10 -80 2 -19 5 -44 8 -55 3 -11 8 -42 12 -70 3 -27 8 -53 10 -56 2 -4 6
-24 10 -45 20 -134 154 -594 209 -719 7 -16 27 -66 45 -110 59 -147 189 -400
297 -579 89 -147 270 -400 359 -502 11 -12 39 -46 64 -75 74 -89 358 -369 465
-459 55 -46 110 -92 121 -102 22 -19 22 -19 107 99 48 65 90 123 94 128 35 42
243 336 242 342 0 5 -38 40 -85 78 -313 258 -611 618 -818 988 -63 114 -176
341 -176 355 0 5 -9 26 -19 48 -54 113 -171 502 -200 669 -6 33 -13 69 -15 80
-8 43 -21 134 -31 210 -19 156 -24 420 -12 605 8 119 13 166 33 295 5 28 7 53
6 58 -2 8 24 3 99 -18 l45 -13 23 82 c12 44 29 106 38 136 30 104 34 95 -54
118 -43 11 -109 29 -148 40 -38 11 -104 30 -145 41 -41 12 -79 23 -85 25 -5 1
-28 8 -50 14 -22 7 -112 33 -200 58 -88 25 -181 52 -207 59 l-48 14 -20 -67z"/>
<path d="M2693 5210 c-48 -11 -119 -56 -148 -95 -112 -146 -57 -352 115 -428
43 -19 68 -20 560 -21 449 -1 521 1 563 15 65 22 114 64 151 129 27 48 31 64
31 130 0 116 -49 200 -149 253 -41 22 -44 22 -566 23 -289 1 -539 -2 -557 -6z"/>
<path d="M6425 5138 c-50 -19 -102 -55 -128 -90 -56 -73 -55 -54 -55 -901 -1
-430 -1 -793 -2 -807 0 -21 -26 1 -169 148 -93 94 -173 172 -178 172 -4 0 -58
-50 -118 -110 l-110 -110 25 -24 c14 -12 202 -204 419 -424 217 -221 397 -402
401 -402 3 0 199 192 435 426 l429 425 -114 114 -115 114 -177 -178 -178 -178
-1 381 c-2 1238 -2 1258 -24 1301 -26 52 -71 98 -126 126 -51 26 -166 35 -214
17z"/>
<path d="M2814 4501 c-2 -2 -4 -415 -4 -918 l0 -913 415 0 415 0 0 28 c4 223
0 1795 -4 1799 -6 7 -816 10 -822 4z"/>
<path d="M3181 2038 c-8 -13 -44 -63 -81 -113 -36 -49 -101 -137 -143 -195
-42 -58 -80 -109 -84 -115 -16 -20 -88 -119 -103 -141 -8 -13 -48 -68 -89
-124 -40 -55 -72 -103 -70 -105 2 -2 60 -44 129 -94 l125 -91 25 32 c62 79 35
81 231 -15 382 -187 717 -298 1149 -382 8 -1 36 -5 61 -9 25 -4 57 -9 70 -11
45 -7 183 -24 264 -32 105 -10 629 -10 725 0 116 13 131 14 195 22 58 7 70 9
125 19 14 3 42 8 63 11 48 7 174 32 252 51 33 8 67 16 76 19 145 33 419 126
599 202 122 52 413 195 418 206 2 4 8 7 12 7 11 0 193 108 273 162 53 36 57
41 45 59 -107 155 -406 558 -415 558 -7 1 -44 -19 -82 -44 -236 -153 -537
-295 -814 -386 -97 -31 -291 -85 -342 -94 -16 -2 -41 -7 -55 -10 -39 -9 -240
-43 -288 -49 -212 -28 -609 -29 -822 -2 -14 2 -52 7 -85 11 -33 4 -69 9 -80
12 -11 2 -33 6 -50 9 -211 34 -520 123 -735 211 -86 36 -291 130 -299 138 -2
3 15 30 38 60 24 31 41 58 39 60 -11 11 -243 178 -252 181 -6 2 -17 -6 -25
-18z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,19 @@
{
"name": "TubeArchivist",
"short_name": "TubeArchivist",
"icons": [
{
"src": "/static/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#01202e",
"background_color": "#01202e",
"display": "standalone"
}

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

75
public/img/icon-exit.svg Normal file
View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="210mm"
viewBox="0 0 210 210"
version="1.1"
id="svg1566"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Icons_exit 05.svg">
<defs
id="defs1560" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35355339"
inkscape:cx="963.7258"
inkscape:cy="291.01609"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata1563">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-87)">
<path
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.35654187;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
d="M 106.49932,87.901069 C 49.504302,87.900974 3.3006913,134.10459 3.3007713,191.0996 c 0,0.30098 0.003,0.60131 0.005,0.90167 v 0 c -0.003,0.29952 -0.006,0.59901 -0.006,0.89912 -8e-5,56.99502 46.2035307,103.19865 103.1985287,103.19854 23.01714,-0.0773 45.34783,-7.84709 63.44155,-22.07425 0,0 9.01874,-8.71006 2.40579,-16.41737 -6.61297,-7.70731 -19.11222,0.3185 -19.11222,0.3185 -13.60985,9.81394 -29.95596,15.11012 -46.73512,15.14236 -44.275428,0 -80.167758,-35.89234 -80.167758,-80.16778 0,-0.30097 0.003,-0.60148 0.006,-0.90166 h -5.2e-4 c -0.003,-0.29934 -0.006,-0.59901 -0.006,-0.89913 0,-44.27545 35.89234,-80.16777 80.167778,-80.16777 16.77916,0.0322 33.12527,5.32843 46.73512,15.14236 0,0 12.49925,8.02581 19.11222,0.3185 6.61295,-7.70732 -2.4058,-16.41739 -2.4058,-16.41739 C 151.84561,95.74815 129.51494,87.97828 106.4978,87.901069 Z m 54.30959,56.450221 -12.13663,11.69622 20.15864,20.93332 -93.932488,-1.4899 c -9.22763,-0.17349 -16.77655,6.07423 -16.92587,14.00904 l 0.002,0.002 c -0.0149,1.82673 -0.0235,3.40102 0,4.99598 l -0.002,0.002 c 0.14932,7.93483 7.69824,14.18254 16.92587,14.00905 l 93.932488,-1.48991 -20.15864,20.93333 12.13663,11.69622 34.0585,-35.35536 11.82982,-12.29208 h 0.003 l -9.9e-4,-0.002 9.9e-4,-9.9e-4 h -0.003 l -11.82982,-12.29208 z"
id="path1405"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccsccsccsccscccccccccccccccccccccc" />
<path
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.39729571;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
d="m 506.57967,92.503023 c -57.98068,-1e-4 -104.98336,47.002567 -104.98326,104.983257 1.9e-4,57.98049 47.00276,104.98284 104.98326,104.98273 23.42489,-0.0758 46.15146,-7.98387 57.83458,-18.08923 11.68313,-10.10537 12.15613,-18.62993 7.38675,-23.04107 v -0.002 c -4.7711,-4.41269 -12.38099,-1.9587 -17.69245,2.25103 -13.83538,9.99805 -30.45915,15.40285 -47.52888,15.4528 -45.04116,0 -81.55421,-36.51305 -81.5542,-81.55419 0,-45.04114 36.51307,-81.5542 81.5542,-81.5542 17.06933,0.0328 33.21884,5.19482 43.16812,12.86758 9.94929,7.67275 17.33418,9.17607 22.1053,4.76338 v -0.002 c 4.77116,-4.41278 5.55882,-12.9887 -0.73482,-18.60197 -18.40654,-14.47308 -41.1234,-22.377337 -64.5386,-22.455877 z m 55.24881,57.426467 -12.34652,11.8985 20.50728,21.29534 -95.55697,-1.51567 c -9.38721,-0.17649 -17.06669,6.17929 -17.21858,14.25133 l 0.003,0.002 c -0.15192,8.07203 7.28245,14.71295 16.66978,14.88953 l 95.22519,1.50947 -21.06332,20.28455 12.04579,12.49846 36.06808,-34.74464 0.005,0.005 12.34654,-11.89954 -12.03701,-12.50724 z m 35.17874,98.71801 0.69918,0.67386 c 0.13539,-0.22412 0.26991,-0.44874 0.4036,-0.67386 z"
id="path1405-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccsczccccccccccccccccccccccc" />
<path
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.39729571;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
d="m 740.89945,94.730897 c -57.98068,-9.6e-5 -104.98334,47.002563 -104.98325,104.983253 1.9e-4,57.98049 47.00276,104.98284 104.98325,104.98274 23.42488,-0.0758 46.15145,-7.98387 64.5635,-22.46581 l -17.03461,-16.41553 c -13.83537,9.99805 -30.45916,15.40285 -47.52889,15.4528 -45.04113,0 -81.55419,-36.51306 -81.55419,-81.5542 0,-45.04114 36.51306,-81.55419 81.55419,-81.55419 17.06934,0.0328 33.69814,5.42058 47.54336,15.40423 l 16.99534,-16.3773 c -18.40663,-14.4732 -41.12349,-22.377447 -64.5387,-22.455993 z m 55.24882,57.426473 -12.34653,11.8985 20.50728,21.29534 -95.55696,-1.51567 c -9.38721,-0.17649 -17.06668,6.17928 -17.21858,14.25132 l 0.002,0.002 c -0.1519,8.07203 7.28245,14.71295 16.66978,14.88953 l 95.22519,1.50947 -21.06332,20.28455 12.04578,12.49846 36.06808,-34.74465 0.005,0.005 12.34653,-11.89953 -12.03699,-12.50725 z m 35.17873,98.718 0.69919,0.67386 c 0.13538,-0.22412 0.26991,-0.44874 0.40359,-0.67386 z"
id="path1405-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccsccccccccccccccccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -15,7 +15,7 @@
version="1.1"
id="svg1303"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Icons_thumbs.svg">
sodipodi:docname="Icons_seen.svg">
<defs
id="defs1297" />
<sodipodi:namedview
@ -25,9 +25,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.71458126"
inkscape:cx="-133.80736"
inkscape:cy="163.34297"
inkscape:zoom="1.0105705"
inkscape:cx="84.208758"
inkscape:cy="136.94831"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
@ -55,9 +55,9 @@
id="layer1"
transform="translate(0,-164.70764)">
<path
style="opacity:1;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
d="m 74.762046,169.84647 c -0.757275,-0.0266 -1.556205,0.0266 -2.398918,0.17006 -17.977757,2.9811 5.863528,29.19697 -15.075776,44.15933 -17.503229,14.60104 -23.870308,10.01972 -24.044759,14.80451 v 47.00292 c 2.623747,6.94761 20.093043,14.05087 38.660635,15.56434 18.567588,1.51346 44.529822,-2.41107 44.816272,-10.49357 0.15245,-7.31882 -4.01785,-7.48655 -4.01785,-7.48655 0,0 9.14077,-0.56532 9.44575,-8.4939 0.15212,-7.77626 -8.85445,-8.88809 -8.85445,-8.88809 0,0 11.53636,-0.3275 11.53636,-8.34902 0,-8.20546 -8.87381,-8.85175 -11.6189,-8.99092 1.70324,-0.10363 8.42562,-2.42967 8.3861,-10.53741 -0.0399,-8.37053 -12.87508,-7.67987 -35.956784,-9.20144 2.66326,-15.52873 4.52035,-48.76031 -10.87768,-49.26032 z m -63.387663,53.22425 c -2.167221,0 -3.911718,1.45375 -3.911718,3.25977 v 53.07599 c 0,1.80609 1.744497,3.25983 3.911718,3.25983 h 14.602385 c 2.167228,0 3.911725,-1.45374 3.911725,-3.25983 v -53.07599 c 0,-1.80602 -1.744497,-3.25977 -3.911725,-3.25977 z"
id="rect1278"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
d="M 66.145984,191.3255 A 71.797122,73.404487 0 0 0 2.5025987,230.88314 71.797122,73.404487 0 0 0 66.145984,270.38145 71.797122,73.404487 0 0 0 129.78937,230.82387 71.797122,73.404487 0 0 0 66.145984,191.3255 Z m -4.921549,7.22394 a 32.724755,32.724755 0 0 0 -13.334395,5.17759 12.828107,12.828107 0 0 1 2.180958,-0.18652 12.828107,12.828107 0 0 1 12.828141,12.82816 12.828107,12.828107 0 0 1 -12.828141,12.82822 12.828107,12.828107 0 0 1 -12.828192,-12.82822 12.828107,12.828107 0 0 1 0.04509,-0.91548 32.724755,32.724755 0 0 0 -3.86581,15.39964 32.724755,32.724755 0 0 0 27.161922,32.24981 A 59.09757,60.420619 0 0 1 13.7604,230.8774 59.09757,60.420619 0 0 1 61.224664,198.54948 Z m 10.482345,0.0563 a 59.09757,60.420619 0 0 1 46.82502,32.22523 59.09757,60.420619 0 0 1 -47.393377,32.32497 32.724755,32.724755 0 0 0 27.73169,-32.30187 32.724755,32.724755 0 0 0 -27.163333,-32.24833 z"
id="path1091"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="2000"
height="2000"
viewBox="0 0 529.16666 529.16735"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Gridview.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35729063"
inkscape:cx="901.7564"
inkscape:cy="1021.9111"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<sodipodi:guide
position="247.25932,291.92959"
orientation="1,0"
id="guide853"
inkscape:locked="false" />
<sodipodi:guide
position="337.22901,167.98535"
orientation="0,1"
id="guide855"
inkscape:locked="false" />
<sodipodi:guide
position="266.76325,305.4565"
orientation="0,1"
id="guide857"
inkscape:locked="false" />
<sodipodi:guide
position="257.79774,279.50371"
orientation="0,1"
id="guide861"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,232.16736)">
<g
id="g873"
transform="matrix(1.3431799,0,0,1.3431799,-84.854433,26.13855)"
style="stroke:none">
<rect
ry="7.445024"
rx="7.445024"
y="-121.39048"
x="79.903137"
height="113.24854"
width="167.35619"
id="rect815"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56300002;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:1.12600005, 0.56300002999999998;stroke-dashoffset:0;stroke-opacity:0.22508042;paint-order:markers fill stroke" />
<rect
ry="7.445024"
rx="7.445024"
y="-121.7837"
x="273.05484"
height="113.24854"
width="167.35619"
id="rect815-4"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56300002;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:1.12600006, 0.56300004000000003;stroke-dashoffset:0;stroke-opacity:0.22508042;paint-order:markers fill stroke" />
<rect
ry="7.445024"
rx="7.445024"
y="17.882772"
x="79.903137"
height="113.24854"
width="167.35619"
id="rect815-2"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56300002;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:1.12600006, 0.56300004000000003;stroke-dashoffset:0;stroke-opacity:0.22508042;paint-order:markers fill stroke" />
<rect
ry="7.445024"
rx="7.445024"
y="17.453594"
x="273.05484"
height="113.24854"
width="167.35619"
id="rect815-7"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56300002;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:1.12600006, 0.56300004000000003;stroke-dashoffset:0;stroke-opacity:0.22508042;paint-order:markers fill stroke" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="2000"
height="2000"
viewBox="0 0 529.16666 529.16735"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Listview.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.42053519"
inkscape:cx="851.82064"
inkscape:cy="1105.7974"
inkscape:document-units="mm"
inkscape:current-layer="g873"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<sodipodi:guide
position="247.25932,291.92959"
orientation="1,0"
id="guide853"
inkscape:locked="false" />
<sodipodi:guide
position="337.22901,167.98535"
orientation="0,1"
id="guide855"
inkscape:locked="false" />
<sodipodi:guide
position="266.76325,305.4565"
orientation="0,1"
id="guide857"
inkscape:locked="false" />
<sodipodi:guide
position="257.79774,279.50371"
orientation="0,1"
id="guide861"
inkscape:locked="false" />
<sodipodi:guide
position="22.413775,336.67894"
orientation="1,0"
id="guide926"
inkscape:locked="false" />
<sodipodi:guide
position="503.71355,217.50031"
orientation="1,0"
id="guide928"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,232.16736)">
<g
id="g873"
transform="matrix(1.3431799,0,0,1.3431799,-84.854433,26.13855)">
<rect
ry="7.445024"
rx="7.445024"
y="-121.34892"
x="79.944702"
height="70.107315"
width="358.24551"
id="rect815"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.64810181;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:1.29620369, 0.64810185;stroke-dashoffset:0;stroke-opacity:0.22508042;paint-order:markers fill stroke" />
<rect
ry="7.4450235"
rx="7.4450235"
y="-31.167784"
x="79.861153"
height="70.107315"
width="358.32907"
id="rect815-20"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.64817739;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:1.29635485, 0.64817743;stroke-dashoffset:0;stroke-opacity:0.22508042;paint-order:markers fill stroke" />
<rect
ry="7.4450235"
rx="7.4450231"
y="60.024326"
x="79.908524"
height="70.106117"
width="358.28171"
id="rect815-8"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.64812905;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:1.29625813, 0.64812907;stroke-dashoffset:0;stroke-opacity:0.22508042;paint-order:markers fill stroke" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

67
public/img/icon-sort.svg Normal file
View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="500"
height="500"
viewBox="0 0 132.29197 132.29167"
version="1.1"
id="svg1303"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Icons_sort.svg">
<defs
id="defs1297" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.6482696"
inkscape:cx="214.8721"
inkscape:cy="136.02434"
inkscape:document-units="mm"
inkscape:current-layer="g855"
showgrid="false"
units="px"
inkscape:window-width="957"
inkscape:window-height="893"
inkscape:window-x="941"
inkscape:window-y="13"
inkscape:window-maximized="0" />
<metadata
id="metadata1300">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-164.70764)">
<g
id="g855"
transform="matrix(1.9016362,0,0,1.9016362,-197.93838,-58.9418)">
<path
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.55118108;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
d="M 341.125 82.275391 L 315.50977 106.92773 L 241.84375 177.89258 L 266.21289 203.17969 L 309.82617 161.17969 L 306.72266 379.375 C 306.36114 398.60059 319.37807 414.32761 335.91016 414.63867 L 335.91406 414.63477 C 352.44586 414.94597 366.04647 399.71987 366.4082 380.49414 L 369.5 162.97852 L 411.04297 206.11719 L 436.64062 181.44727 L 365.48242 107.57812 L 365.49609 107.5625 L 341.125 82.275391 z M 175.93359 82.277344 L 175.92969 82.28125 C 159.39782 81.970041 145.79728 97.196144 145.43555 116.42188 L 142.34375 333.9375 L 100.80078 290.79883 L 75.203125 315.46875 L 146.36133 389.33789 L 146.3457 389.35156 L 170.7168 414.63867 L 196.33203 389.98633 L 270 319.02344 L 245.63086 293.73633 L 202.01758 335.73633 L 205.12109 117.54102 C 205.48261 98.315428 192.46568 82.588409 175.93359 82.277344 z "
transform="matrix(0.13913489,0,0,0.13913489,104.08846,117.60887)"
id="path814" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="500"
height="500"
viewBox="0 0 132.29197 132.29167"
version="1.1"
id="svg1303"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
sodipodi:docname="icon-star-empty.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1297" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.4005076"
inkscape:cx="40.40812"
inkscape:cy="211.20533"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2112"
inkscape:window-x="0"
inkscape:window-y="48"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="0" />
<metadata
id="metadata1300">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-164.70764)">
<path
id="path1255"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
inkscape:transform-center-y="-6.4703827"
d="M 250 21.158203 L 189.78906 194.37891 L 6.4375 198.11523 L 152.57617 308.9082 L 99.470703 484.43945 L 250 379.69141 L 400.5293 484.43945 L 347.42383 308.9082 L 493.5625 198.11523 L 310.21094 194.37891 L 250 21.158203 z M 250 96.408203 L 291.69141 216.34766 L 418.64453 218.93555 L 317.45703 295.65039 L 354.22852 417.18945 L 250 344.66211 L 145.77148 417.18945 L 182.54297 295.65039 L 81.355469 218.93555 L 208.30859 216.34766 L 250 96.408203 z "
transform="matrix(0.26458394,0,0,0.26458394,0,164.70749)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="500"
height="500"
viewBox="0 0 132.29197 132.29167"
version="1.1"
id="svg1303"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Icons_star.svg">
<defs
id="defs1297" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.0105705"
inkscape:cx="61.891881"
inkscape:cy="148.25167"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata1300">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-164.70764)">
<path
sodipodi:type="star"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
id="path1255"
sodipodi:sides="5"
sodipodi:cx="66.145981"
sodipodi:cy="237.32387"
sodipodi:r1="67.758904"
sodipodi:r2="27.103561"
sodipodi:arg1="-1.5707963"
sodipodi:arg2="-0.9424778"
inkscape:flatsided="false"
inkscape:rounded="-3.46945e-18"
inkscape:randomized="0"
d="m 66.145983,169.56496 15.931071,45.83167 48.511476,0.98859 L 91.923,245.69933 105.97366,292.14197 66.145981,264.42743 26.318295,292.14197 40.368962,245.69933 1.7034347,216.38521 50.214907,215.39663 Z"
inkscape:transform-center-y="-6.4703827" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="500"
height="500"
viewBox="0 0 132.29197 132.29167"
version="1.1"
id="svg1303"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
sodipodi:docname="icon-star-half.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1297" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.0105705"
inkscape:cx="44.034533"
inkscape:cy="168.22181"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="3840"
inkscape:window-height="2078"
inkscape:window-x="0"
inkscape:window-y="82"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="0" />
<metadata
id="metadata1300">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-164.70764)">
<path
id="path1255"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
inkscape:transform-center-x="1.1220981e-06"
inkscape:transform-center-y="-6.4703827"
d="M 250 21.158203 L 189.78906 194.37891 L 6.4375 198.11523 L 152.57617 308.9082 L 99.470703 484.43945 L 250 379.69141 L 400.5293 484.43945 L 347.42383 308.9082 L 493.5625 198.11523 L 310.21094 194.37891 L 250 21.158203 z M 250 96.408203 L 291.69141 216.34766 L 418.64453 218.93555 L 317.45703 295.65039 L 354.22852 417.18945 L 250 344.66211 L 250 96.408203 z "
transform="matrix(0.26458394,0,0,0.26458394,0,164.70749)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

67
public/img/icon-stop.svg Normal file
View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="500"
height="500"
viewBox="0 0 132.29197 132.29167"
version="1.1"
id="svg1303"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="Icons_stop.svg">
<defs
id="defs1297" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.0105705"
inkscape:cx="43.182711"
inkscape:cy="168.09972"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata1300">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-164.70764)">
<rect
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
id="rect836"
width="118.86465"
height="118.86465"
x="6.7136617"
y="171.42116"
rx="10.00003"
ry="10.00003" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

76
public/img/icon-thumb.svg Normal file
View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="500"
height="500"
viewBox="0 0 132.29197 132.29167"
version="1.1"
id="svg1303"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="icon_thumb.svg">
<defs
id="defs1297" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.5046797"
inkscape:cx="35.718548"
inkscape:cy="203.39339"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
showguides="true"
inkscape:guide-bbox="true">
<sodipodi:guide
position="80.99058,65.965029"
orientation="0,1"
id="guide816"
inkscape:locked="false" />
<sodipodi:guide
position="65.965178,49.107299"
orientation="1,0"
id="guide818"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata1300">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-164.70764)">
<path
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
d="m 72.35681,178.12175 c -0.545876,-0.0192 -1.121779,0.0192 -1.729242,0.12259 -12.959134,2.14891 4.226681,21.04642 -10.867262,31.83193 -12.617074,10.52505 -17.206737,7.22264 -17.332488,10.67172 v 33.88171 c 1.891308,5.00813 14.483922,10.12847 27.868234,11.21944 13.384309,1.09096 32.098988,-1.73801 32.305468,-7.56421 0.10989,-5.27572 -2.896223,-5.39662 -2.896223,-5.39662 0,0 6.589043,-0.40752 6.808883,-6.12278 0.10966,-5.60545 -6.38265,-6.40691 -6.38265,-6.40691 0,0 8.31589,-0.23607 8.31589,-6.01832 0,-5.91485 -6.39662,-6.38072 -8.37539,-6.48104 1.22776,-0.0747 6.07353,-1.75141 6.04505,-7.59582 -0.0287,-6.03384 -9.280896,-5.53597 -25.919173,-6.63279 1.919791,-11.19377 3.258461,-35.14851 -7.841097,-35.50894 z m -45.69253,38.36631 c -1.562226,0 -2.819733,1.04793 -2.819733,2.34979 v 38.25943 c 0,1.30191 1.257507,2.34983 2.819733,2.34983 h 10.526021 c 1.562231,0 2.819739,-1.04792 2.819739,-2.34983 v -38.25943 c 0,-1.30186 -1.257508,-2.34979 -2.819739,-2.34979 z"
id="rect1278"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

148
public/js/cast-videos.js Normal file
View File

@ -0,0 +1,148 @@
function initializeCastApi() {
cast.framework.CastContext.getInstance().setOptions({
receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, // Use built in reciver app on cast device, see https://developers.google.com/cast/docs/styled_receiver if you want to be able to add a theme, splash screen or watermark. Has a $5 one time fee.
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
});
var player = new cast.framework.RemotePlayer();
var playerController = new cast.framework.RemotePlayerController(player);
// Add event listerner to check if a connection to a cast device is initiated
playerController.addEventListener(
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED, function() {
castConnectionChange(player)
}
);
playerController.addEventListener(
cast.framework.RemotePlayerEventType.CURRENT_TIME_CHANGED, function() {
castVideoProgress(player)
}
);
playerController.addEventListener(
cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED, function() {
castVideoPaused(player)
}
);
}
function castConnectionChange(player) {
// If cast connection is initialized start cast
if (player.isConnected) {
// console.log("Cast Connected.");
castStart();
} else if (!player.isConnected) {
// console.log("Cast Disconnected.");
}
}
function castVideoProgress(player) {
var videoId = getVideoPlayerVideoId();
if (player.mediaInfo.contentId.includes(videoId)) {
var currentTime = player.currentTime;
var duration = player.duration;
if ((currentTime % 10) <= 1.0 && currentTime != 0 && duration != 0) { // Check progress every 10 seconds or else progress is checked a few times a second
postVideoProgress(videoId, currentTime);
setProgressBar(videoId, currentTime, duration);
if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched
if (watchedThreshold(currentTime, duration)) {
isWatched(videoId);
}
}
}
}
}
function castVideoPaused(player) {
var videoId = getVideoPlayerVideoId();
var currentTime = player.currentTime;
var duration = player.duration;
if (player.mediaInfo != null) {
if (player.mediaInfo.contentId.includes(videoId)) {
if (currentTime != 0 && duration != 0) {
postVideoProgress(videoId, currentTime);
}
}
}
}
function castStart() {
var castSession = cast.framework.CastContext.getInstance().getCurrentSession();
// Check if there is already media playing on the cast target to prevent recasting on page reload or switching to another video page
if (!castSession.getMediaSession()) {
var videoId = getVideoPlayerVideoId();
var videoData = getVideoData(videoId);
var contentId = getURL() + videoData.data.media_url;
var contentTitle = videoData.data.title;
var contentImage = getURL() + videoData.data.vid_thumb_url;
contentType = 'video/mp4'; // Set content type, only videos right now so it is hard coded
contentCurrentTime = getVideoPlayerCurrentTime(); // Get video's current position
contentActiveSubtitle = [];
// Check if a subtitle is turned on.
for (var i = 0; i < getVideoPlayer().textTracks.length; i++) {
if (getVideoPlayer().textTracks[i].mode == "showing") {
contentActiveSubtitle =[i + 1];
}
}
contentSubtitles = [];
var videoSubtitles = videoData.data.subtitles; // Array of subtitles
if (typeof(videoSubtitles) != 'undefined' && videoData.config.downloads.subtitle) {
for (var i = 0; i < videoSubtitles.length; i++) {
subtitle = new chrome.cast.media.Track(i, chrome.cast.media.TrackType.TEXT);
subtitle.trackContentId = videoSubtitles[i].media_url;
subtitle.trackContentType = 'text/vtt';
subtitle.subtype = chrome.cast.media.TextTrackType.SUBTITLES;
subtitle.name = videoSubtitles[i].name;
subtitle.language = videoSubtitles[i].lang;
subtitle.customData = null;
contentSubtitles.push(subtitle);
}
}
mediaInfo = new chrome.cast.media.MediaInfo(contentId, contentType); // Create MediaInfo var that contains url and content type
// mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED; // Set type of stream, BUFFERED, LIVE, OTHER
mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); // Create metadata var and add it to MediaInfo
mediaInfo.metadata.title = contentTitle.replace("&amp;", "&"); // Set the video title
mediaInfo.metadata.images = [new chrome.cast.Image(contentImage)]; // Set the video thumbnail
// mediaInfo.textTrackStyle = new chrome.cast.media.TextTrackStyle();
mediaInfo.tracks = contentSubtitles;
var request = new chrome.cast.media.LoadRequest(mediaInfo); // Create request with the previously set MediaInfo.
// request.queueData = new chrome.cast.media.QueueData(); // See https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.media.QueueData for playlist support.
request.currentTime = shiftCurrentTime(contentCurrentTime); // Set video start position based on the browser video position
request.activeTrackIds = contentActiveSubtitle; // Set active subtitle based on video player
// request.autoplay = false; // Set content to auto play, true by default
castSession.loadMedia(request).then(
function() {
castSuccessful();
},
function() {
castFailed(errorCode);
}
); // Send request to cast device
}
}
function shiftCurrentTime(contentCurrentTime) { // Shift media back 3 seconds to prevent missing some of the content
if (contentCurrentTime > 5) {
return(contentCurrentTime - 3);
} else {
return(0);
}
}
function castSuccessful() {
// console.log('Cast Successful.');
getVideoPlayer().pause(); // Pause browser video on successful cast
}
function castFailed(errorCode) {
console.log('Error code: ' + errorCode);
}
window['__onGCastApiAvailable'] = function(isAvailable) {
if (isAvailable) {
initializeCastApi();
}
}

106
public/js/progress.js Normal file
View File

@ -0,0 +1,106 @@
/**
* Handle multi channel notifications
*
*/
checkMessages()
// page map to notification status
const messageTypes = {
"download": ["message:download", "message:add", "message:rescan", "message:playlistscan"],
"channel": ["message:subchannel"],
"channel_id": ["message:playlistscan"],
"playlist": ["message:subplaylist"],
"setting": ["message:setting"]
}
// start to look for messages
function checkMessages() {
var notifications = document.getElementById("notifications");
if (notifications) {
var dataOrigin = notifications.getAttribute("data");
getMessages(dataOrigin);
}
}
// get messages for page on timer
function getMessages(dataOrigin) {
fetch('/progress/').then(response => {
return response.json();
}).then(responseData => {
var messages = buildMessage(responseData, dataOrigin);
if (messages.length > 0) {
// restart itself
setTimeout(function() {
getMessages(dataOrigin);
}, 3000);
};
});
}
// make div for all messages, return relevant
function buildMessage(responseData, dataOrigin) {
// filter relevan messages
var allMessages = responseData["messages"];
var messages = allMessages.filter(function(value) {
return messageTypes[dataOrigin].includes(value["status"])
}, dataOrigin);
// build divs
var notificationDiv = document.getElementById("notifications");
var nots = notificationDiv.childElementCount;
notificationDiv.innerHTML = "";
for (let i = 0; i < messages.length; i++) {
var messageData = messages[i];
var messageStatus = messageData["status"];
var messageBox = document.createElement("div");
var title = document.createElement("h3");
title.innerHTML = messageData["title"];
var message = document.createElement("p");
message.innerHTML = messageData["message"];
messageBox.appendChild(title);
messageBox.appendChild(message);
messageBox.classList.add(messageData["level"], "notification");
notificationDiv.appendChild(messageBox);
if (messageStatus === "message:download") {
checkDownloadIcons();
};
};
// reload page when no more notifications
if (nots > 0 && messages.length === 0) {
location.reload();
};
return messages
}
// check if download icons are needed
function checkDownloadIcons() {
var iconBox = document.getElementById("downloadControl");
if (iconBox.childElementCount === 0) {
var downloadIcons = buildDownloadIcons();
iconBox.appendChild(downloadIcons);
};
}
// add dl control icons
function buildDownloadIcons() {
var downloadIcons = document.createElement('div');
downloadIcons.classList = 'dl-control-icons';
// stop icon
var stopIcon = document.createElement('img');
stopIcon.setAttribute('id', "stop-icon");
stopIcon.setAttribute('title', "Stop Download Queue");
stopIcon.setAttribute('src', "/static/img/icon-stop.svg");
stopIcon.setAttribute('alt', "stop icon");
stopIcon.setAttribute('onclick', 'stopQueue()');
// kill icon
var killIcon = document.createElement('img');
killIcon.setAttribute('id', "kill-icon");
killIcon.setAttribute('title', "Kill Download Queue");
killIcon.setAttribute('src', "/static/img/icon-close.svg");
killIcon.setAttribute('alt', "kill icon");
killIcon.setAttribute('onclick', 'killQueue()');
// stich together
downloadIcons.appendChild(stopIcon);
downloadIcons.appendChild(killIcon);
return downloadIcons
}

912
public/js/script.js Normal file
View File

@ -0,0 +1,912 @@
function sortChange(sortValue) {
var payload = JSON.stringify({ sort_order: sortValue });
sendPost(payload);
setTimeout(function () {
location.reload();
return false;
}, 500);
}
// Updates video watch status when passed a video id and it's current state (ex if the video was unwatched but you want to mark it as watched you will pass "unwatched")
function updateVideoWatchStatus(input1, videoCurrentWatchStatus) {
if (videoCurrentWatchStatus) {
videoId = input1;
} else if (input1.getAttribute("data-id")) {
videoId = input1.getAttribute("data-id");
videoCurrentWatchStatus = input1.getAttribute("data-status");
}
postVideoProgress(videoId, 0); // Reset video progress on watched/unwatched;
removeProgressBar(videoId);
if (videoCurrentWatchStatus == "watched") {
var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched");
var payload = JSON.stringify({ un_watched: videoId });
sendPost(payload);
} else if (videoCurrentWatchStatus == "unwatched") {
var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched");
var payload = JSON.stringify({ watched: videoId });
sendPost(payload);
}
var watchButtons = document.getElementsByClassName("watch-button");
for (let i = 0; i < watchButtons.length; i++) {
if (watchButtons[i].getAttribute("data-id") == videoId) {
watchButtons[i].outerHTML = watchStatusIndicator;
}
}
}
// Creates a watch status indicator when passed a video id and the videos watch status
function createWatchStatusIndicator(videoId, videoWatchStatus) {
if (videoWatchStatus == "watched") {
var seen = "seen";
var title = "Mark as unwatched";
} else if (videoWatchStatus == "unwatched") {
var seen = "unseen";
var title = "Mark as watched";
}
var watchStatusIndicator = `<img src="/static/img/icon-${seen}.svg" alt="${seen}-icon" data-id="${videoId}" data-status="${videoWatchStatus}" onclick="updateVideoWatchStatus(this)" class="watch-button" title="${title}">`;
return watchStatusIndicator;
}
// function isWatched(youtube_id) {
// var payload = JSON.stringify({'watched': youtube_id});
// sendPost(payload);
// var seenIcon = document.createElement('img');
// seenIcon.setAttribute('src', "/static/img/icon-seen.svg");
// seenIcon.setAttribute('alt', 'seen-icon');
// seenIcon.setAttribute('id', youtube_id);
// seenIcon.setAttribute('title', "Mark as unwatched");
// seenIcon.setAttribute('onclick', "isUnwatched(this.id)");
// seenIcon.classList = 'seen-icon';
// document.getElementById(youtube_id).replaceWith(seenIcon);
// }
// Removes the progress bar when passed a video id
function removeProgressBar(videoId) {
setProgressBar(videoId, 0, 1);
}
function isWatchedButton(button) {
youtube_id = button.getAttribute("data-id");
var payload = JSON.stringify({ watched: youtube_id });
button.remove();
sendPost(payload);
setTimeout(function () {
location.reload();
return false;
}, 1000);
}
// function isUnwatched(youtube_id) {
// postVideoProgress(youtube_id, 0); // Reset video progress on unwatched;
// var payload = JSON.stringify({'un_watched': youtube_id});
// sendPost(payload);
// var unseenIcon = document.createElement('img');
// unseenIcon.setAttribute('src', "/static/img/icon-unseen.svg");
// unseenIcon.setAttribute('alt', 'unseen-icon');
// unseenIcon.setAttribute('id', youtube_id);
// unseenIcon.setAttribute('title', "Mark as watched");
// unseenIcon.setAttribute('onclick', "isWatched(this.id)");
// unseenIcon.classList = 'unseen-icon';
// document.getElementById(youtube_id).replaceWith(unseenIcon);
// }
function unsubscribe(id_unsub) {
var payload = JSON.stringify({ unsubscribe: id_unsub });
sendPost(payload);
var message = document.createElement("span");
message.innerText = "You are unsubscribed.";
document.getElementById(id_unsub).replaceWith(message);
}
function subscribe(id_sub) {
var payload = JSON.stringify({ subscribe: id_sub });
sendPost(payload);
var message = document.createElement("span");
message.innerText = "You are subscribed.";
document.getElementById(id_sub).replaceWith(message);
}
function changeView(image) {
var sourcePage = image.getAttribute("data-origin");
var newView = image.getAttribute("data-value");
var payload = JSON.stringify({ change_view: sourcePage + ":" + newView });
sendPost(payload);
setTimeout(function () {
location.reload();
return false;
}, 500);
}
function toggleCheckbox(checkbox) {
// pass checkbox id as key and checkbox.checked as value
var toggleId = checkbox.id;
var toggleVal = checkbox.checked;
var payloadDict = {};
payloadDict[toggleId] = toggleVal;
var payload = JSON.stringify(payloadDict);
sendPost(payload);
setTimeout(function () {
var currPage = window.location.pathname;
window.location.replace(currPage);
return false;
}, 500);
}
// download page buttons
function rescanPending() {
var payload = JSON.stringify({ rescan_pending: true });
animate("rescan-icon", "rotate-img");
sendPost(payload);
setTimeout(function () {
checkMessages();
}, 500);
}
function dlPending() {
var payload = JSON.stringify({ dl_pending: true });
animate("download-icon", "bounce-img");
sendPost(payload);
setTimeout(function () {
checkMessages();
}, 500);
}
function toIgnore(button) {
var youtube_id = button.getAttribute("data-id");
var payload = JSON.stringify({ ignore: youtube_id });
sendPost(payload);
document.getElementById("dl-" + youtube_id).remove();
}
function downloadNow(button) {
var youtube_id = button.getAttribute("data-id");
var payload = JSON.stringify({ dlnow: youtube_id });
sendPost(payload);
document.getElementById(youtube_id).remove();
setTimeout(function () {
checkMessages();
}, 500);
}
function forgetIgnore(button) {
var youtube_id = button.getAttribute("data-id");
var payload = JSON.stringify({ forgetIgnore: youtube_id });
sendPost(payload);
document.getElementById("dl-" + youtube_id).remove();
}
function addSingle(button) {
var youtube_id = button.getAttribute("data-id");
var payload = JSON.stringify({ addSingle: youtube_id });
sendPost(payload);
document.getElementById("dl-" + youtube_id).remove();
setTimeout(function () {
checkMessages();
}, 500);
}
function deleteQueue(button) {
var to_delete = button.getAttribute("data-id");
var payload = JSON.stringify({ deleteQueue: to_delete });
sendPost(payload);
setTimeout(function () {
location.reload();
return false;
}, 1000);
}
function stopQueue() {
var payload = JSON.stringify({ queue: "stop" });
sendPost(payload);
document.getElementById("stop-icon").remove();
}
function killQueue() {
var payload = JSON.stringify({ queue: "kill" });
sendPost(payload);
document.getElementById("kill-icon").remove();
}
// settings page buttons
function manualImport() {
var payload = JSON.stringify({ "manual-import": true });
sendPost(payload);
// clear button
var message = document.createElement("p");
message.innerText = "processing import";
var toReplace = document.getElementById("manual-import");
toReplace.innerHTML = "";
toReplace.appendChild(message);
}
function reEmbed() {
var payload = JSON.stringify({ "re-embed": true });
sendPost(payload);
// clear button
var message = document.createElement("p");
message.innerText = "processing thumbnails";
var toReplace = document.getElementById("re-embed");
toReplace.innerHTML = "";
toReplace.appendChild(message);
}
function dbBackup() {
var payload = JSON.stringify({ "db-backup": true });
sendPost(payload);
// clear button
var message = document.createElement("p");
message.innerText = "backing up archive";
var toReplace = document.getElementById("db-backup");
toReplace.innerHTML = "";
toReplace.appendChild(message);
}
function dbRestore(button) {
var fileName = button.getAttribute("data-id");
var payload = JSON.stringify({ "db-restore": fileName });
sendPost(payload);
// clear backup row
var message = document.createElement("p");
message.innerText = "restoring from backup";
var toReplace = document.getElementById(fileName);
toReplace.innerHTML = "";
toReplace.appendChild(message);
}
function fsRescan() {
var payload = JSON.stringify({ "fs-rescan": true });
sendPost(payload);
// clear button
var message = document.createElement("p");
message.innerText = "File system scan in progress";
var toReplace = document.getElementById("fs-rescan");
toReplace.innerHTML = "";
toReplace.appendChild(message);
}
function resetToken() {
var payload = JSON.stringify({ "reset-token": true });
sendPost(payload);
var message = document.createElement("p");
message.innerText = "Token revoked";
document.getElementById("text-reveal").replaceWith(message);
}
// delete from file system
function deleteConfirm() {
to_show = document.getElementById("delete-button");
document.getElementById("delete-item").style.display = "none";
to_show.style.display = "block";
}
function deleteVideo(button) {
var to_delete = button.getAttribute("data-id");
var to_redirect = button.getAttribute("data-redirect");
var payload = JSON.stringify({ "delete-video": to_delete });
sendPost(payload);
setTimeout(function () {
var redirect = "/channel/" + to_redirect;
window.location.replace(redirect);
return false;
}, 1000);
}
function deleteChannel(button) {
var to_delete = button.getAttribute("data-id");
var payload = JSON.stringify({ "delete-channel": to_delete });
sendPost(payload);
setTimeout(function () {
window.location.replace("/channel/");
return false;
}, 1000);
}
function deletePlaylist(button) {
var playlist_id = button.getAttribute("data-id");
var playlist_action = button.getAttribute("data-action");
var payload = JSON.stringify({
"delete-playlist": {
"playlist-id": playlist_id,
"playlist-action": playlist_action,
},
});
sendPost(payload);
setTimeout(function () {
window.location.replace("/playlist/");
return false;
}, 1000);
}
function cancelDelete() {
document.getElementById("delete-button").style.display = "none";
document.getElementById("delete-item").style.display = "block";
}
// player
function createPlayer(button) {
var videoId = button.getAttribute("data-id");
var videoData = getVideoData(videoId);
var videoProgress = getVideoProgress(videoId).position;
var videoName = videoData.data.title;
var videoTag = createVideoTag(videoData, videoProgress);
var playlist = "";
var videoPlaylists = videoData.data.playlist; // Array of playlists the video is in
if (typeof videoPlaylists != "undefined") {
var subbedPlaylists = getSubbedPlaylists(videoPlaylists); // Array of playlist the video is in that are subscribed
if (subbedPlaylists.length != 0) {
var playlistData = getPlaylistData(subbedPlaylists[0]); // Playlist data for first subscribed playlist
var playlistId = playlistData.playlist_id;
var playlistName = playlistData.playlist_name;
var playlist = `<h5><a href="/playlist/${playlistId}/">${playlistName}</a></h5>`;
}
}
var videoViews = formatNumbers(videoData.data.stats.view_count);
var channelId = videoData.data.channel.channel_id;
var channelName = videoData.data.channel.channel_name;
removePlayer();
// document.getElementById(videoId).outerHTML = ''; // Remove watch indicator from video info
// If cast integration is enabled create cast button
var castButton = "";
if (videoData.config.application.enable_cast) {
var castButton = `<google-cast-launcher id="castbutton"></google-cast-launcher>`;
}
// Watched indicator
if (videoData.data.player.watched) {
var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched");
} else {
var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched");
}
var playerStats = `<div class="thumb-icon player-stats"><img src="/static/img/icon-eye.svg" alt="views icon"><span>${videoViews}</span>`;
if (videoData.data.stats.like_count) {
var likes = formatNumbers(videoData.data.stats.like_count);
playerStats += `<span>|</span><img src="/static/img/icon-thumb.svg" alt="thumbs-up"><span>${likes}</span>`;
}
if (
videoData.data.stats.dislike_count &&
videoData.config.downloads.integrate_ryd
) {
var dislikes = formatNumbers(videoData.data.stats.dislike_count);
playerStats += `<span>|</span><img class="dislike" src="/static/img/icon-thumb.svg" alt="thumbs-down"><span>${dislikes}</span>`;
}
playerStats += "</div>";
const markup = `
<div class="video-player" data-id="${videoId}">
${videoTag}
<div class="player-title boxed-content">
<img class="close-button" src="/static/img/icon-close.svg" alt="close-icon" data="${videoId}" onclick="removePlayer()" title="Close player">
${watchStatusIndicator}
${castButton}
${playerStats}
<div class="player-channel-playlist">
<h3><a href="/channel/${channelId}/">${channelName}</a></h3>
${playlist}
</div>
<a href="/video/${videoId}/"><h2 id="video-title">${videoName}</h2></a>
</div>
</div>
`;
const divPlayer = document.getElementById("player");
divPlayer.innerHTML = markup;
}
// Add video tag to video page when passed a video id, function loaded on page load `video.html (115-117)`
function insertVideoTag(videoData, videoProgress) {
var videoTag = createVideoTag(videoData, videoProgress);
var videoMain = document.getElementsByClassName("video-main");
videoMain[0].innerHTML = videoTag;
}
// Generates a video tag with subtitles when passed videoData and videoProgress.
function createVideoTag(videoData, videoProgress) {
var videoId = videoData.data.youtube_id;
var videoUrl = videoData.data.media_url;
var videoThumbUrl = videoData.data.vid_thumb_url;
var subtitles = "";
var videoSubtitles = videoData.data.subtitles; // Array of subtitles
if (
typeof videoSubtitles != "undefined" &&
videoData.config.downloads.subtitle
) {
for (var i = 0; i < videoSubtitles.length; i++) {
let label = videoSubtitles[i].name;
if (videoSubtitles[i].source == "auto") {
label += " - auto";
}
subtitles += `<track label="${label}" kind="subtitles" srclang="${videoSubtitles[i].lang}" src="${videoSubtitles[i].media_url}">`;
}
}
var videoTag = `
<video poster="${videoThumbUrl}" ontimeupdate="onVideoProgress()" onpause="onVideoPause()" onended="onVideoEnded()" controls autoplay width="100%" playsinline id="video-item">
<source src="${videoUrl}#t=${videoProgress}" type="video/mp4" id="video-source" videoid="${videoId}">
${subtitles}
</video>
`;
return videoTag;
}
// Gets video tag
function getVideoPlayer() {
var videoElement = document.getElementById("video-item");
return videoElement;
}
// Gets the video source tag
function getVideoPlayerVideoSource() {
var videoPlayerVideoSource = document.getElementById("video-source");
return videoPlayerVideoSource;
}
// Gets the current progress of the video currently in the player
function getVideoPlayerCurrentTime() {
var videoElement = getVideoPlayer();
if (videoElement != null) {
return videoElement.currentTime;
}
}
// Gets the video id of the video currently in the player
function getVideoPlayerVideoId() {
var videoPlayerVideoSource = getVideoPlayerVideoSource();
if (videoPlayerVideoSource != null) {
return videoPlayerVideoSource.getAttribute("videoid");
}
}
// Gets the duration of the video currently in the player
function getVideoPlayerDuration() {
var videoElement = getVideoPlayer();
if (videoElement != null) {
return videoElement.duration;
}
}
// Gets current watch status of video based on watch button
function getVideoPlayerWatchStatus() {
var videoId = getVideoPlayerVideoId();
var watched = false;
var watchButtons = document.getElementsByClassName("watch-button");
for (let i = 0; i < watchButtons.length; i++) {
if (
watchButtons[i].getAttribute("data-id") == videoId &&
watchButtons[i].getAttribute("data-status") == "watched"
) {
watched = true;
}
}
return watched;
}
// Runs on video playback, marks video as watched if video gets to 90% or higher, sends position to api
function onVideoProgress() {
var videoId = getVideoPlayerVideoId();
var currentTime = getVideoPlayerCurrentTime();
var duration = getVideoPlayerDuration();
if ((currentTime % 10).toFixed(1) <= 0.2) {
// Check progress every 10 seconds or else progress is checked a few times a second
postVideoProgress(videoId, currentTime);
if (!getVideoPlayerWatchStatus()) {
// Check if video is already marked as watched
if (watchedThreshold(currentTime, duration)) {
updateVideoWatchStatus(videoId, "unwatched");
}
}
}
}
// Runs on video end, marks video as watched
function onVideoEnded() {
var videoId = getVideoPlayerVideoId();
if (!getVideoPlayerWatchStatus()) {
// Check if video is already marked as watched
updateVideoWatchStatus(videoId, "unwatched");
}
}
function watchedThreshold(currentTime, duration) {
var watched = false;
if (duration <= 1800) {
// If video is less than 30 min
if (currentTime / duration >= 0.9) {
// Mark as watched at 90%
var watched = true;
}
} else {
// If video is more than 30 min
if (currentTime >= duration - 120) {
// Mark as watched if there is two minutes left
var watched = true;
}
}
return watched;
}
// Runs on video pause. Sends current position.
function onVideoPause() {
var videoId = getVideoPlayerVideoId();
var currentTime = getVideoPlayerCurrentTime();
postVideoProgress(videoId, currentTime);
}
// Format numbers for frontend
function formatNumbers(number) {
var numberUnformatted = parseFloat(number);
if (numberUnformatted > 999999999) {
var numberFormatted =
(numberUnformatted / 1000000000).toFixed(1).toString() + "B";
} else if (numberUnformatted > 999999) {
var numberFormatted =
(numberUnformatted / 1000000).toFixed(1).toString() + "M";
} else if (numberUnformatted > 999) {
var numberFormatted =
(numberUnformatted / 1000).toFixed(1).toString() + "K";
} else {
var numberFormatted = numberUnformatted;
}
return numberFormatted;
}
// Gets video data when passed video ID
function getVideoData(videoId) {
var apiEndpoint = "/api/video/" + videoId + "/";
var videoData = apiRequest(apiEndpoint, "GET");
return videoData;
}
// Gets channel data when passed channel ID
function getChannelData(channelId) {
var apiEndpoint = "/api/channel/" + channelId + "/";
var channelData = apiRequest(apiEndpoint, "GET");
return channelData.data;
}
// Gets playlist data when passed playlist ID
function getPlaylistData(playlistId) {
var apiEndpoint = "/api/playlist/" + playlistId + "/";
var playlistData = apiRequest(apiEndpoint, "GET");
return playlistData.data;
}
// Get video progress data when passed video ID
function getVideoProgress(videoId) {
var apiEndpoint = "/api/video/" + videoId + "/progress/";
var videoProgress = apiRequest(apiEndpoint, "GET");
return videoProgress;
}
// Given an array of playlist ids it returns an array of subbed playlist ids from that list
function getSubbedPlaylists(videoPlaylists) {
var subbedPlaylists = [];
for (var i = 0; i < videoPlaylists.length; i++) {
if (getPlaylistData(videoPlaylists[i]).playlist_subscribed) {
subbedPlaylists.push(videoPlaylists[i]);
}
}
return subbedPlaylists;
}
// Send video position when given video id and progress in seconds
function postVideoProgress(videoId, videoProgress) {
var apiEndpoint = "/api/video/" + videoId + "/progress/";
var duartion = getVideoPlayerDuration();
if (!isNaN(videoProgress) && duartion != "undefined") {
var data = {
position: videoProgress,
};
if (videoProgress == 0) {
apiRequest(apiEndpoint, "DELETE");
// console.log("Deleting Video Progress for Video ID: " + videoId + ", Progress: " + videoProgress);
} else if (!getVideoPlayerWatchStatus()) {
apiRequest(apiEndpoint, "POST", data);
// console.log("Saving Video Progress for Video ID: " + videoId + ", Progress: " + videoProgress);
}
}
}
// Makes api requests when passed an endpoint and method ("GET", "POST", "DELETE")
function apiRequest(apiEndpoint, method, data) {
const xhttp = new XMLHttpRequest();
var sessionToken = getCookie("sessionid");
xhttp.open(method, apiEndpoint, false);
xhttp.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); // Used for video progress POST requests
xhttp.setRequestHeader("Authorization", "Token " + sessionToken);
xhttp.setRequestHeader("Content-Type", "application/json");
xhttp.send(JSON.stringify(data));
return JSON.parse(xhttp.responseText);
}
// Gets origin URL
function getURL() {
return window.location.origin;
}
function removePlayer() {
var currentTime = getVideoPlayerCurrentTime();
var duration = getVideoPlayerDuration();
var videoId = getVideoPlayerVideoId();
postVideoProgress(videoId, currentTime);
setProgressBar(videoId, currentTime, duration);
var playerElement = document.getElementById("player");
if (playerElement.hasChildNodes()) {
var youtubeId = playerElement.childNodes[1].getAttribute("data-id");
var playedStatus = document.createDocumentFragment();
var playedBox = document.getElementById(youtubeId);
if (playedBox) {
playedStatus.appendChild(playedBox);
}
playerElement.innerHTML = "";
// append played status
var videoInfo = document.getElementById("video-info-" + youtubeId);
videoInfo.insertBefore(playedStatus, videoInfo.firstChild);
}
}
// Sets the progress bar when passed a video id, video progress and video duration
function setProgressBar(videoId, currentTime, duration) {
var progressBarWidth = (currentTime / duration) * 100 + "%";
var progressBars = document.getElementsByClassName("video-progress-bar");
for (let i = 0; i < progressBars.length; i++) {
if (progressBars[i].id == "progress-" + videoId) {
if (!getVideoPlayerWatchStatus()) {
progressBars[i].style.width = progressBarWidth;
} else {
progressBars[i].style.width = "0%";
}
}
}
// progressBar = document.getElementById("progress-" + videoId);
}
// multi search form
function searchMulti(query) {
if (query.length > 1) {
var payload = JSON.stringify({ multi_search: query });
var http = new XMLHttpRequest();
http.onreadystatechange = function () {
if (http.readyState === 4) {
allResults = JSON.parse(http.response).results;
populateMultiSearchResults(allResults);
}
};
http.open("POST", "/process/", true);
http.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
http.setRequestHeader("Content-type", "application/json");
http.send(payload);
}
}
function getViewDefaults(view) {
var defaultView = document.getElementById("id_" + view).value;
return defaultView;
}
function populateMultiSearchResults(allResults) {
// videos
var defaultVideo = getViewDefaults("home");
var allVideos = allResults.video_results;
var videoBox = document.getElementById("video-results");
videoBox.innerHTML = "";
for (let index = 0; index < allVideos.length; index++) {
const video = allVideos[index].source;
const videoDiv = createVideo(video, defaultVideo);
videoBox.appendChild(videoDiv);
}
// channels
var defaultChannel = getViewDefaults("channel");
var allChannels = allResults.channel_results;
var channelBox = document.getElementById("channel-results");
channelBox.innerHTML = "";
for (let index = 0; index < allChannels.length; index++) {
const channel = allChannels[index].source;
const channelDiv = createChannel(channel, defaultChannel);
channelBox.appendChild(channelDiv);
}
// playlists
var defaultPlaylist = getViewDefaults("playlist");
var allPlaylists = allResults.playlist_results;
var playlistBox = document.getElementById("playlist-results");
playlistBox.innerHTML = "";
for (let index = 0; index < allPlaylists.length; index++) {
const playlist = allPlaylists[index].source;
const playlistDiv = createPlaylist(playlist, defaultPlaylist);
playlistBox.appendChild(playlistDiv);
}
}
function createVideo(video, viewStyle) {
// create video item div from template
const videoId = video.youtube_id;
const mediaUrl = video.media_url;
const thumbUrl = "/cache/" + video.vid_thumb_url;
const videoTitle = video.title;
const videoPublished = video.published;
const videoDuration = video.player.duration_str;
if (video.player.watched) {
var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched");
} else {
var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched");
}
const channelId = video.channel.channel_id;
const channelName = video.channel.channel_name;
// build markup
const markup = `
<a href="#player" data-src="/media/${mediaUrl}" data-thumb="${thumbUrl}" data-title="${videoTitle}" data-channel="${channelName}" data-channel-id="${channelId}" data-id="${videoId}" onclick="createPlayer(this)">
<div class="video-thumb-wrap ${viewStyle}">
<div class="video-thumb">
<img src="${thumbUrl}" alt="video-thumb">
</div>
<div class="video-play">
<img src="/static/img/icon-play.svg" alt="play-icon">
</div>
</div>
</a>
<div class="video-desc ${viewStyle}">
<div class="video-desc-player" id="video-info-${videoId}">
${watchStatusIndicator}
<span>${videoPublished} | ${videoDuration}</span>
</div>
<div>
<a href="/channel/${channelId}/"><h3>${channelName}</h3></a>
<a class="video-more" href="/video/${videoId}/"><h2>${videoTitle}</h2></a>
</div>
</div>
`;
const videoDiv = document.createElement("div");
videoDiv.setAttribute("class", "video-item " + viewStyle);
videoDiv.innerHTML = markup;
return videoDiv;
}
function createChannel(channel, viewStyle) {
// create channel item div from template
const channelId = channel.channel_id;
const channelName = channel.channel_name;
const channelSubs = channel.channel_subs;
const channelLastRefresh = channel.channel_last_refresh;
if (channel.channel_subscribed) {
var button = `<button class="unsubscribe" type="button" id="${channelId}" onclick="unsubscribe(this.id)" title="Unsubscribe from ${channelName}">Unsubscribe</button>`;
} else {
var button = `<button type="button" id="${channelId}" onclick="subscribe(this.id)" title="Subscribe to ${channelName}">Subscribe</button>`;
}
// build markup
const markup = `
<div class="channel-banner ${viewStyle}">
<a href="/channel/${channelId}/">
<img src="/cache/channels/${channelId}_banner.jpg" alt="${channelId}-banner">
</a>
</div>
<div class="info-box info-box-2 ${viewStyle}">
<div class="info-box-item">
<div class="round-img">
<a href="/channel/${channelId}/">
<img src="/cache/channels/${channelId}_thumb.jpg" alt="channel-thumb">
</a>
</div>
<div>
<h3><a href="/channel/${channelId}/">${channelName}</a></h3>
<p>Subscribers: ${channelSubs}</p>
</div>
</div>
<div class="info-box-item">
<div>
<p>Last refreshed: ${channelLastRefresh}</p>
${button}
</div>
</div>
</div>
`;
const channelDiv = document.createElement("div");
channelDiv.setAttribute("class", "channel-item " + viewStyle);
channelDiv.innerHTML = markup;
return channelDiv;
}
function createPlaylist(playlist, viewStyle) {
// create playlist item div from template
const playlistId = playlist.playlist_id;
const playlistName = playlist.playlist_name;
const playlistChannelId = playlist.playlist_channel_id;
const playlistChannel = playlist.playlist_channel;
const playlistLastRefresh = playlist.playlist_last_refresh;
if (playlist.playlist_subscribed) {
var button = `<button class="unsubscribe" type="button" id="${playlistId}" onclick="unsubscribe(this.id)" title="Unsubscribe from ${playlistName}">Unsubscribe</button>`;
} else {
var button = `<button type="button" id="${playlistId}" onclick="subscribe(this.id)" title="Subscribe to ${playlistName}">Subscribe</button>`;
}
const markup = `
<div class="playlist-thumbnail">
<a href="/playlist/${playlistId}/">
<img src="/cache/playlists/${playlistId}.jpg" alt="${playlistId}-thumbnail">
</a>
</div>
<div class="playlist-desc ${viewStyle}">
<a href="/channel/${playlistChannelId}/"><h3>${playlistChannel}</h3></a>
<a href="/playlist/${playlistId}/"><h2>${playlistName}</h2></a>
<p>Last refreshed: ${playlistLastRefresh}</p>
${button}
</div>
`;
const playlistDiv = document.createElement("div");
playlistDiv.setAttribute("class", "playlist-item " + viewStyle);
playlistDiv.innerHTML = markup;
return playlistDiv;
}
// generic
function sendPost(payload) {
var http = new XMLHttpRequest();
http.open("POST", "/process/", true);
http.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
http.setRequestHeader("Content-type", "application/json");
http.send(payload);
}
function getCookie(c_name) {
if (document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + "=");
if (c_start != -1) {
c_start = c_start + c_name.length + 1;
c_end = document.cookie.indexOf(";", c_start);
if (c_end == -1) c_end = document.cookie.length;
return unescape(document.cookie.substring(c_start, c_end));
}
}
return "";
}
// animations
function textReveal() {
var textBox = document.getElementById("text-reveal");
var button = document.getElementById("text-reveal-button");
var textBoxHeight = textBox.style.height;
if (textBoxHeight === "unset") {
textBox.style.height = "0px";
button.innerText = "Show";
} else {
textBox.style.height = "unset";
button.innerText = "Hide";
}
}
function showForm() {
var formElement = document.getElementById("hidden-form");
var displayStyle = formElement.style.display;
if (displayStyle === "") {
formElement.style.display = "block";
} else {
formElement.style.display = "";
}
animate("animate-icon", "pulse-img");
}
function showOverwrite() {
var overwriteDiv = document.getElementById("overwrite-form");
if (overwriteDiv.classList.contains("hidden-overwrite")) {
overwriteDiv.classList.remove("hidden-overwrite");
} else {
overwriteDiv.classList.add("hidden-overwrite");
}
}
function animate(elementId, animationClass) {
var toAnimate = document.getElementById(elementId);
if (toAnimate.className !== animationClass) {
toAnimate.className = animationClass;
} else {
toAnimate.classList.remove(animationClass);
}
}

4
public/vercel.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

4
renovate.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": ["config:base"],
"dependencyDashboardApproval": true
}

20
run.sh
View File

@ -1,20 +0,0 @@
#!/bin/bash
# startup script inside the container for tubearchivist
counter=0
until curl "$ES_URL" -fs; do
echo "waiting for elastic search to start"
counter=$((counter+1))
if [[ $counter -eq 12 ]]; then
# fail after 1 min
echo "failed to connect to elastic search, exiting..."
exit 1
fi
sleep 5
done
python manage.py migrate
python manage.py collectstatic --noinput -c
nginx &
celery -A home.tasks worker --loglevel=INFO &
uwsgi --ini uwsgi.ini

View File

@ -0,0 +1,3 @@
export const BoxedContent: React.FC = ({ children }) => (
<div className="boxed-content">{children}</div>
);

View File

@ -0,0 +1,52 @@
import Head from "next/head";
/**
* TODO: Dynamically get the title
* TODO: NextJS recommended pattern for SEO
*/
export const CustomHead = ({ title }: { title?: string }) => {
return (
<Head>
<meta charSet="UTF-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/favicon/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon/favicon-16x16.png"
/>
<link rel="manifest" href="/favicon/site.webmanifest" />
<link
rel="mask-icon"
href="/favicon/safari-pinned-tab.svg"
color="#01202e"
/>
<link rel="shortcut icon" href="/favicon/favicon.ico" />
<meta name="apple-mobile-web-app-title" content="TubeArchivist" />
<meta name="application-name" content="TubeArchivist" />
<meta name="msapplication-TileColor" content="#01202e" />
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
<meta name="theme-color" content="#01202e" />
{title ? <title>TA | {title}</title> : <title>TubeArchivist</title>}
{/* {% if colors == "dark" %} */}
{/* <link rel="stylesheet" href="/css/dark.css" /> */}
{/* {% else %} */}
{/* <link rel="stylesheet" href="/css/light.css" /> */}
{/* {% endif %} */}
</Head>
);
};

34
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,34 @@
export const Footer = () => (
<div className="footer">
<div className="boxed-content">
<span>© 2021 - {new Date().getFullYear()} TubeArchivist v0.1.3 </span>
<span>
<a href="{% url 'about' %}">About</a> |{" "}
<a
href="https://github.com/bbilly1/tubearchivist"
rel="noreferrer"
target="_blank"
>
GitHub
</a>{" "}
|{" "}
<a
href="https://hub.docker.com/r/bbilly1/tubearchivist"
target="_blank"
rel="noreferrer"
>
Docker Hub
</a>{" "}
|{" "}
<a
href="https://discord.gg/AFwz8nE7BK"
rel="noreferrer"
target="_blank"
>
Discord
</a>{" "}
| <a href="https://www.reddit.com/r/TubeArchivist/">Reddit</a>
</span>
</div>
</div>
);

20
src/components/Header.tsx Normal file
View File

@ -0,0 +1,20 @@
import dynamic from "next/dynamic";
const Header = ({ authData }) => {
const { session, status } = authData;
return (
<>
<h1>Name: {session?.user?.name}</h1>
<h1>Status: {status}</h1>
<h1>Token: {session?.ta_token?.token}</h1>
<h1>User ID: {session?.ta_token?.user_id}</h1>
</>
);
};
export const DynamicHeader = dynamic(() => import("../components/Header"), {
suspense: true,
});
export default Header;

14
src/components/Layout.tsx Normal file
View File

@ -0,0 +1,14 @@
import { Footer } from "./Footer";
import { Nav } from "./Nav";
export const Layout = ({ children }) => {
return (
<>
<div style={{ minHeight: "100vh" }} className="main-content">
<Nav />
{children}
</div>
<Footer />
</>
);
};

95
src/components/Nav.tsx Executable file
View File

@ -0,0 +1,95 @@
import NextImage from "next/image";
import NextLink from "next/link";
import BannerDark from "../images/banner-tube-archivist-dark.png";
import IconSearch from "../images/icon-search.svg";
import IconGear from "../images/icon-gear.svg";
import IconExit from "../images/icon-exit.svg";
import { signIn, signOut, useSession } from "next-auth/react";
/** TODO: Fix these nav links */
export const Nav = () => {
const { data: session } = useSession();
const handleSigninSignout = () => {
if (!session) {
signIn();
}
signOut();
};
return (
<div className="boxed-content">
<div className="top-banner">
<NextLink href="/">
<a>
{/* {% if colors == 'dark */}
<NextImage
width={700}
height={150}
src={BannerDark}
alt="tube-archivist-banner"
/>
{/* {% endif %} */}
{/* {% if colors == 'light */}
{/* <img src="/img/banner-tube-archivist-light.png" alt="tube-archivist-banner"> */}
{/* {% endif %} */}
</a>
</NextLink>
</div>
<div className="top-nav">
<div className="nav-items">
<NextLink href="/">
<a>
<div className="nav-item">home</div>
</a>
</NextLink>
<NextLink href="/channel">
<a>
<div className="nav-item">channels</div>
</a>
</NextLink>
<NextLink href="/playlist">
<a>
<div className="nav-item">playlists</div>
</a>
</NextLink>
<NextLink href="/download">
<a>
<div className="nav-item">downloads</div>
</a>
</NextLink>
</div>
<div className="nav-icons">
<a href="/search">
<NextImage
width={50}
height={40}
src={IconSearch}
alt="search-icon"
title="Search"
/>
</a>
<a href="/settings">
<NextImage
width={50}
height={40}
src={IconGear}
alt="gear-icon"
title="Settings"
/>
</a>
<a style={{ cursor: "pointer" }} onClick={handleSigninSignout}>
<NextImage
width={50}
height={40}
className="alert-hover"
src={IconExit}
alt="exit-icon"
title="Logout"
/>
</a>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,223 @@
import { useSession } from "next-auth/react";
import NextImage from "next/image";
import { useState } from "react";
import { useQuery } from "react-query";
import IconPlay from "../../images/icon-play.svg";
import { getTAUrl } from "../../lib/constants";
import { getVideos } from "../../lib/getVideos";
import type { Data } from "../../types/video";
import VideoPlayer from "../VideoPlayer";
type ViewStyle = "grid" | "list";
const TA_BASE_URL = getTAUrl();
const VideoList = () => {
const [selectedVideoUrl, setSelectedVideoUrl] = useState<Data>();
const [viewStyle, setViewStyle] = useState<ViewStyle>("grid");
const { data: session } = useSession();
const { data, error, isLoading } = useQuery(
["videos", session.ta_token.token],
() => getVideos(session.ta_token.token),
{
enabled: !!session?.ta_token?.token,
}
);
const handleSelectedVideo = (video: Data) => {
setSelectedVideoUrl(video);
};
const handleRemoveVideoPlayer = () => {
setSelectedVideoUrl(undefined);
};
const handleSetViewstyle = (selectedViewStyle: ViewStyle) => {
setViewStyle(selectedViewStyle);
};
if (!isLoading && !data?.data) {
return (
<div className="boxed-content">
<h2>No videos found...</h2>
<p>
If you&apos;ve already added a channel or playlist, try going to the{" "}
<a href="{% url 'downloads">downloads page</a> to start the scan and
download tasks.
</p>
</div>
);
}
return (
<>
<VideoPlayer
handleRemoveVideoPlayer={handleRemoveVideoPlayer}
selectedVideo={selectedVideoUrl}
/>
<div className="boxed-content">
<div className="title-bar">
<h1>Recent Videos</h1>
</div>
<div className="view-controls">
<div className="toggle">
<span>Hide watched:</span>
<div className="toggleBox">
<input
id="hide_watched"
// onClick="toggleCheckbox(this)"
type="checkbox"
/>
{/* {% if not hide_watched %} */}
<label htmlFor="" className="ofbtn">
Off
</label>
{/* {% else %} */}
<label htmlFor="" className="onbtn">
On
</label>
{/* {% endif %} */}
</div>
</div>
<div className="sort">
<div id="hidden-form">
<span>Sort by:</span>
<select
name="sort"
id="sort"
onChange={() => console.log("onChange sort")}
>
<option value="published">date published</option>
<option value="downloaded">date downloaded</option>
<option value="views">views</option>
<option value="likes">likes</option>
</select>
<select
name="sord-order"
id="sort-order"
onChange={() => console.log("onChange sort-order")}
>
<option value="asc">asc</option>
<option value="desc">desc</option>
</select>
</div>
</div>
<div className="view-icons">
<img
src="/img/icon-sort.svg"
alt="sort-icon"
onClick={() => console.log("showForm")}
id="animate-icon"
/>
<img
src="/img/icon-gridview.svg"
onClick={() => handleSetViewstyle("grid")}
alt="grid view"
/>
<img
src="/img/icon-listview.svg"
onClick={() => handleSetViewstyle("list")}
alt="list view"
/>
</div>
</div>
<div className={`video-list ${viewStyle}`}>
{data &&
data?.data?.map((video) => {
return (
<div
key={video.youtube_id}
className={`video-item ${viewStyle}`}
>
<a
style={{ cursor: "pointer" }}
onClick={() => handleSelectedVideo(video)}
>
<div className="video-thumb-wrap list">
<div className="video-thumb">
<NextImage
src={`${TA_BASE_URL.client}${video.vid_thumb_url}`}
alt="video-thumb"
width={640}
height={360}
// blurDataURL={video.vid_thumb_base64}
// placeholder="blur"
/>
{/* {% if video.source.player.progress %} */}
<div
className="video-progress-bar"
id={`progress-${video.youtube_id}`}
// style={{ width: video.player.progress }} // TODO: /video/youtube_id/progress
></div>
{/* {% else %} */}
<div
className="video-progress-bar"
id={`progress-${video.youtube_id}`}
style={{ width: "0%" }}
></div>
{/* {% endif %} */}
</div>
<div className="video-play">
<NextImage
width={40}
height={40}
src={IconPlay}
alt="play-icon"
/>
</div>
</div>
</a>
<div className="video-desc list">
<div
className="video-desc-player"
id={`video-info-${video.youtube_id}`}
>
{video?.player?.watched ? (
<img
src="/img/icon-seen.svg"
alt="seen-icon"
data-id={video.youtube_id}
data-status="watched"
// onClick="updateVideoWatchStatus(this)"
className="watch-button"
title="Mark as unwatched"
/>
) : (
<img
src="/img/icon-unseen.svg"
alt="unseen-icon"
data-id={video.youtube_id}
data-status="unwatched"
// onClick="updateVideoWatchStatus(this)"
className="watch-button"
title="Mark as watched"
/>
)}
<span>
{video.published} | {video.player.duration_str}
</span>
</div>
<div>
<a href={`/channel/${video.channel.channel_id}`}>
<h3>{video.channel.channel_name}</h3>
</a>
<a
className="video-more"
href={`/video/${video.youtube_id}`}
>
<h2>{video.title}</h2>
</a>
</div>
</div>
</div>
);
})}
</div>
</div>
</>
);
};
export default VideoList;

View File

@ -0,0 +1,4 @@
import dynamic from "next/dynamic";
const DynamicVideoList = dynamic(() => import("./VideoList"));
export default DynamicVideoList;

View File

@ -0,0 +1,126 @@
import NextImage from "next/image";
import NextLink from "next/link";
import ReactPlayer from "react-player";
import IconClose from "../../images/icon-close.svg";
import { getTAUrl } from "../../lib/constants";
import { formatNumbers } from "../../lib/utils";
import { Data } from "../../types/video";
const TA_BASE_URL = getTAUrl();
type VideoPlayerProps = {
selectedVideo: Data;
handleRemoveVideoPlayer?: () => void;
isHome?: boolean;
showStats?: boolean;
};
const VideoPlayer = ({
selectedVideo,
handleRemoveVideoPlayer,
isHome = true,
showStats = true,
}: VideoPlayerProps) => {
if (!selectedVideo) return;
return (
<>
{selectedVideo && (
<div className="player-wrapper">
<div className="video-player">
<ReactPlayer
controls={true}
width="100%"
height="100%"
light={false}
playing // TODO: Not currently working
playsinline
url={`${TA_BASE_URL.client}${selectedVideo?.media_url}`}
/>
<SponsorBlock />
{showStats ? (
<div className="player-title boxed-content">
<NextImage
className="close-button"
src={IconClose}
width={30}
height={30}
alt="close-icon"
onClick={handleRemoveVideoPlayer}
title="Close player"
/>
<div className="thumb-icon player-stats">
<img src="/img/icon-eye.svg" alt="views icon" />
<span>
{formatNumbers(selectedVideo.stats.view_count.toString())}
</span>
<span>|</span>
<img src="/img/icon-thumb.svg" alt="thumbs-up" />
<span>
{formatNumbers(selectedVideo.stats.like_count.toString())}
</span>
</div>
<div className="player-channel-playlist">
<h3>
<a href="/channel/${channelId}/">
{selectedVideo.channel.channel_name}
</a>
</h3>
{/* ${playlist} */}
</div>
<NextLink href={`/video/${selectedVideo.youtube_id}/`}>
<a>
<h2 id="video-title">{selectedVideo.title}</h2>
</a>
</NextLink>
</div>
) : null}
</div>
</div>
)}
</>
);
};
export default VideoPlayer;
function SponsorBlock() {
return (
<>
{/* <div className="notifications" id="notifications"></div> */}
<div className="sponsorblock" id="sponsorblock">
{/* {% if video.sponsorblock.is_enabled %} */}
{/* {% if video.sponsorblock.segments|length == 0 %} */}
<h4>
This video doesn&apos;t have any sponsor segments added. To add a
segment go to{" "}
<u>
<a href="https://www.youtube.com/watch?v={{ video.youtube_id }}">
this video on YouTube
</a>
</u>{" "}
and add a segment using the{" "}
<u>
<a href="https://sponsor.ajay.app/">SponsorBlock</a>
</u>{" "}
extension.
</h4>
{/* {% elif video.sponsorblock.has_unlocked %} */}
<h4>
This video has unlocked sponsor segments. Go to{" "}
<u>
<a href="https://www.youtube.com/watch?v={{ video.youtube_id }}">
this video on YouTube
</a>
</u>{" "}
and vote on the segments using the{" "}
<u>
<a href="https://sponsor.ajay.app/">SponsorBlock</a>
</u>{" "}
extension.
</h4>
{/* {% endif %} */}
{/* {% endif %} */}
</div>
</>
);
}

View File

@ -0,0 +1,4 @@
import dynamic from "next/dynamic";
const DynamicVideoPlayer = dynamic(() => import("./VideoPlayer"));
export default DynamicVideoPlayer;

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Some files were not shown because too many files have changed in this diff Show More