Compare commits

..

1072 Commits

Author SHA1 Message Date
Glassed Silver
aa8f602856 Added Security Policy 2022-04-24 11:12:22 +02:00
Isaac Abadi
21507ee36d Updated methodology of calculating download progress to rely on fs.readdir instead of glob 2022-04-24 04:15:38 -04:00
Isaac Abadi
0585943d67 Fixed bug where task time was not properly set with values of 0
Fixed issue where time field was not properly populated in the schedule dialog
2022-04-24 04:10:27 -04:00
Isaac Abadi
0bc2193f25 Updated downloadFile API request 2022-04-23 21:41:39 -04:00
Isaac Abadi
f3398fce1a Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2022-04-23 21:20:51 -04:00
Isaac Abadi
d94857b0a5 Rolled back passport update 2022-04-23 18:14:44 -04:00
Glassed Silver
5fda56d7af Merge pull request #560 from Tzahi12345/revert-556-dependabot/npm_and_yarn/async-2.6.4
Revert "Bump async from 2.6.3 to 2.6.4"
2022-04-23 23:53:29 +02:00
Glassed Silver
abb80b33f3 Revert "Bump async from 2.6.3 to 2.6.4" 2022-04-23 23:53:15 +02:00
Glassed Silver
9977340161 Merge pull request #558 from Tzahi12345/angular-13-update
Angular/dependencies updates
2022-04-23 08:44:27 +02:00
Glassed Silver
8ded160775 Merge branch 'master' into angular-13-update 2022-04-23 08:43:01 +02:00
Glassed Silver
2ee64c7a65 Merge pull request #515 from depuits/master
Added deleteAllFiles api endpoint
2022-04-23 08:27:54 +02:00
Glassed Silver
2ec7efa1ac Merge pull request #555 from Tzahi12345/dependabot/npm_and_yarn/backend/async-3.2.2
Bump async from 3.2.0 to 3.2.2 in /backend
2022-04-23 08:06:54 +02:00
Glassed Silver
4d51384ce6 Merge pull request #556 from Tzahi12345/dependabot/npm_and_yarn/async-2.6.4
Bump async from 2.6.3 to 2.6.4
2022-04-23 08:06:30 +02:00
Isaac Abadi
aa616af118 Fixed issue where navigating from one sub to another didn't cause the new one to load
Fixed subscription downloading as zip

Minor code cleanuip
2022-04-22 23:09:06 -04:00
Isaac Abadi
feebe3e2ba Fixed accidental reversion of styles.scss to much older version 2022-04-22 22:49:05 -04:00
Isaac Abadi
02e90fe818 Fixed issue where icons would not render properly 2022-04-22 22:43:25 -04:00
Isaac Abadi
a4cfafe63c Updated frontend and backend dependencies, fixed some security issues 2022-04-22 22:34:29 -04:00
Isaac Abadi
63e2e6dd3c Fixed build warnings 2022-04-22 22:14:21 -04:00
Isaac Abadi
5a44229e24 Fixed styles.scss 2022-04-22 22:07:50 -04:00
Isaac Abadi
5025b235b7 Updated angular material to v13 2022-04-22 19:03:55 -04:00
Isaac Abadi
5d540fc52a Updated angular to v13 2022-04-22 18:55:11 -04:00
Isaac Abadi
55dfc17d62 Updated ngx-file-drop to support angular v13 2022-04-22 17:51:20 -04:00
Isaac Abadi
2459403b22 Updated angular material 2022-04-22 17:46:45 -04:00
Isaac Abadi
ed5f910c33 Updated angular to v12 2022-04-22 17:40:53 -04:00
Isaac Abadi
468e7153e4 Updated ngx-videogular 2022-04-22 17:28:15 -04:00
dependabot[bot]
1bd713fe17 Bump async from 2.6.3 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-22 19:47:54 +00:00
dependabot[bot]
3df377a260 Bump async from 3.2.0 to 3.2.2 in /backend
Bumps [async](https://github.com/caolan/async) from 3.2.0 to 3.2.2.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/master/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v3.2.0...v3.2.2)

---
updated-dependencies:
- dependency-name: async
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-22 19:47:28 +00:00
Glassed Silver
8314ab8fce Merge pull request #554 from Tzahi12345/tasks-and-maintenence-page
Tasks and maintenence page
2022-04-22 21:46:56 +02:00
Isaac Abadi
d32df84e3a Fixed build error 2022-04-22 15:25:26 -04:00
Isaac Abadi
2c3813f302 Removed httpclient import from player component 2022-04-22 00:40:34 -04:00
Isaac Abadi
df687263c5 Fixed bug that prevented files from being downloaded from the server. Reverted some changes from #528 as they are not needed 2022-04-22 00:35:58 -04:00
Isaac Abadi
7a4d91cea0 Removed import of unregistered files on startup as it's a task now 2022-04-21 22:08:47 -04:00
Isaac Abadi
b53d9c9710 Added ability to reset tasks
Refactored youtube-dl updating and added youtube-dl update task
2022-04-21 22:04:45 -04:00
Isaac Abadi
d2d125743e Fixed issue where restoring a DB backup would cause backup_local_db task to be stuck running
Slightly updated tasks UI
2022-04-21 19:56:09 -04:00
Isaac Abadi
a288163644 Added ability to backup remote DB
Added ability to restore DB
2022-04-21 19:29:50 -04:00
Isaac Abadi
091f81bb38 Added UI for managing tasks
Added ability to schedule tasks based on timestamp

Fixed mismatched types between frontend and openapi yaml

Simplified imports for several backend components
2022-04-21 03:01:49 -04:00
Isaac Abadi
5b4d4d5f81 Added scheduler for tasks 2022-04-19 22:29:41 -04:00
Glassed Silver
0f4f5293de Merge pull request #551 from GlassedSilver/master
Code Cleanup in some places, fix for conversion errors, possibly better webm support, added dependency checks in docker compose file
2022-04-18 10:56:37 +02:00
GlassedSilver
16943847fc fix ffmpeg download with variable 2022-04-18 08:57:21 +02:00
GlassedSilver
f79b254040 using more recent ffmpeg + code cleanup 2022-04-18 07:29:28 +02:00
GlassedSilver
e7989e41f9 Merge branch 'master' of https://github.com/GlassedSilver/YoutubeDL-Material into master 2022-04-18 05:54:09 +02:00
GlassedSilver
a4d421d398 add downloader script for JVS's ffmpeg master blds 2022-04-18 05:54:01 +02:00
Isaac Abadi
2b1771d30d Began work on tasks 2022-04-17 23:37:47 -04:00
Glassed Silver
d6ed82134b Merge pull request #550 from GlassedSilver/master
revert ffmpeg changees
2022-04-18 01:57:31 +02:00
GlassedSilver
74f5a9983d revert ffmpeg changees 2022-04-18 01:56:16 +02:00
Glassed Silver
07a259f128 Merge pull request #549 from GlassedSilver/master
try to fix ffmpeg install from edge
2022-04-17 23:48:12 +02:00
GlassedSilver
de79efafa6 try to fix ffmpeg install from edge 2022-04-17 23:46:08 +02:00
Glassed Silver
ea214ca953 Merge pull request #548 from GlassedSilver/master
Switch to alpine edge community repo for ffmpeg (fixed syntax)
2022-04-17 21:40:29 +02:00
GlassedSilver
f11baf6d4b fix missing \ in DOCKEFILE 2022-04-17 21:38:01 +02:00
Glassed Silver
7d0d665798 Merge pull request #547 from GlassedSilver/master
Switch to alpine edge community repo for ffmpeg
2022-04-17 21:35:02 +02:00
GlassedSilver
9e35e0fe4d Switch to alpine edge community repo for ffmpeg 2022-04-17 21:15:52 +02:00
Glassed Silver
4a148d5148 Merge pull request #537 from Tzahi12345/dependabot/npm_and_yarn/backend/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6 in /backend
2022-04-09 11:51:42 +02:00
Glassed Silver
4fd60c8a5d Merge pull request #539 from Tzahi12345/dependabot/npm_and_yarn/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6
2022-04-09 11:51:34 +02:00
Glassed Silver
d8cee00e7a Merge pull request #538 from Tzahi12345/dependabot/npm_and_yarn/backend/ansi-regex-3.0.1
Bump ansi-regex from 3.0.0 to 3.0.1 in /backend
2022-04-09 11:50:58 +02:00
dependabot[bot]
478d0c8fad Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-09 09:39:19 +00:00
dependabot[bot]
d15709008c Bump ansi-regex from 3.0.0 to 3.0.1 in /backend
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-09 09:38:59 +00:00
dependabot[bot]
80aba6b4a7 Bump minimist from 1.2.5 to 1.2.6 in /backend
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-09 09:38:56 +00:00
Glassed Silver
c4cf981e57 Merge pull request #536 from Tzahi12345/dependabot/npm_and_yarn/backend/moment-2.29.2
Bump moment from 2.29.1 to 2.29.2 in /backend
2022-04-09 11:38:29 +02:00
dependabot[bot]
7d3079f042 Bump moment from 2.29.1 to 2.29.2 in /backend
Bumps [moment](https://github.com/moment/moment) from 2.29.1 to 2.29.2.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.1...2.29.2)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-09 07:19:13 +00:00
Glassed Silver
ffa1737df3 Merge pull request #535 from GlassedSilver/Readme-Update
Made Global custom args hint clearer. (scope: EN)
2022-04-08 19:18:57 +02:00
GlassedSilver
f1a7986e7a Made Global custom args hint clearer. (scope: EN) 2022-04-08 19:17:14 +02:00
Glassed Silver
8456accda0 Adding link to MongoDB transfer guide in README 2022-03-25 13:26:57 +01:00
Glassed Silver
d4ef7066df Update README to include legal disclaimer
and one small change in the notice about best practices. (because I'm forgetful ok)
2022-03-25 11:17:37 +01:00
Glassed Silver
b76a7f2e43 Update README to highlight usage of nightlies...
... and best practices with large datasets.
2022-03-25 11:09:54 +01:00
Glassed Silver
98e77f65f9 Merge pull request #343 from controlol/patch-1
solve path problem subscriptions
2022-03-25 10:48:36 +01:00
Glassed Silver
ee5d6dfba8 Merge branch 'master' into patch-1 2022-03-25 10:41:31 +01:00
Glassed Silver
3538132d24 Merge pull request #533 from Tzahi12345/dependabot/npm_and_yarn/follow-redirects-1.14.9
Bump follow-redirects from 1.13.0 to 1.14.9
2022-03-23 15:31:41 +01:00
dependabot[bot]
b6399eb876 Bump follow-redirects from 1.13.0 to 1.14.9
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.13.0 to 1.14.9.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.13.0...v1.14.9)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-23 13:21:08 +00:00
Glassed Silver
bcab211ed7 Merge pull request #479 from u0a1009/Added-Shortcut
Added Shortcut description
2022-03-23 14:06:29 +01:00
Glassed Silver
a753b6db95 Merge pull request #483 from GlassedSilver/Readme-Update
Git Bug Report template; better user guidance
2022-03-23 14:05:36 +01:00
Glassed Silver
26eb687ece Merge pull request #528 from chepe263/master
Add download video button on player component.
2022-03-23 14:03:19 +01:00
Glassed Silver
327e1efc95 Merge pull request #531 from Tzahi12345/dependabot/npm_and_yarn/url-parse-1.5.10
Bump url-parse from 1.5.1 to 1.5.10
2022-03-23 14:02:50 +01:00
Glassed Silver
3a4eb8afdb Merge pull request #532 from Tzahi12345/dependabot/npm_and_yarn/electron-13.6.6
Bump electron from 9.4.0 to 13.6.6
2022-03-23 14:01:51 +01:00
Glassed Silver
93d35dd97c Merge pull request #530 from Tzahi12345/dependabot/npm_and_yarn/backend/node-fetch-2.6.7
Bump node-fetch from 2.6.1 to 2.6.7 in /backend
2022-03-23 12:23:23 +01:00
dependabot[bot]
343a9bf70b Bump electron from 9.4.0 to 13.6.6
Bumps [electron](https://github.com/electron/electron) from 9.4.0 to 13.6.6.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v9.4.0...v13.6.6)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-23 09:32:46 +00:00
dependabot[bot]
699b3f5316 Bump url-parse from 1.5.1 to 1.5.10
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.1 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.1...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-23 09:30:37 +00:00
dependabot[bot]
910ae90882 Bump node-fetch from 2.6.1 to 2.6.7 in /backend
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-23 09:30:07 +00:00
Glassed Silver
605042fdf8 Merge pull request #478 from weblate/weblate-youtubedl-material-ytdl-material
Translations update from Weblate
2022-03-23 10:10:12 +01:00
Glassed Silver
118bed551a Merge pull request #517 from Tzahi12345/dependabot/npm_and_yarn/backend/ajv-6.12.6
Bump ajv from 6.12.0 to 6.12.6 in /backend
2022-03-23 10:09:54 +01:00
Glassed Silver
70fc4150d4 Merge pull request #518 from Tzahi12345/dependabot/npm_and_yarn/backend/follow-redirects-1.14.8
Bump follow-redirects from 1.14.4 to 1.14.8 in /backend
2022-03-23 10:09:39 +01:00
Glassed Silver
68e1388178 Merge pull request #524 from Tzahi12345/dependabot/npm_and_yarn/karma-6.3.16
Bump karma from 5.0.9 to 6.3.16
2022-03-23 10:09:19 +01:00
Glassed Silver
aeaa653b27 Merge pull request #529 from EgorBakanov/master
Fixed file type dropdown margin
2022-03-23 10:06:02 +01:00
Egor Bakanov
033d0d0658 Fixed file type dropdown margin 2022-03-22 13:16:13 +07:00
Guillermo Chavez
1980893d9c Add download video button on player component. 2022-03-20 23:14:56 -06:00
dependabot[bot]
a7c36898fa Bump karma from 5.0.9 to 6.3.16
Bumps [karma](https://github.com/karma-runner/karma) from 5.0.9 to 6.3.16.
- [Release notes](https://github.com/karma-runner/karma/releases)
- [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md)
- [Commits](https://github.com/karma-runner/karma/compare/v5.0.9...v6.3.16)

---
updated-dependencies:
- dependency-name: karma
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-02 02:19:07 +00:00
Maite Guix
9cb3b71b0f Translated using Weblate (Catalan)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ca/
2022-02-28 18:57:37 +01:00
Maxime Leroy
3dc03b3fa0 Translated using Weblate (French)
Currently translated at 99.6% (301 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/fr/
2022-02-26 07:57:20 +01:00
S3aBreeze
2c49b6e260 Translated using Weblate (Russian)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ru/
2022-02-18 15:54:31 +01:00
Heimen Stoffels
1faabda5f0 Translated using Weblate (Dutch)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/nl/
2022-02-15 11:55:51 +01:00
Allan Nordhøy
82321f28cd Translated using Weblate (Norwegian Bokmål)
Currently translated at 66.5% (201 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/nb_NO/
2022-02-14 09:55:26 +01:00
Maite Guix
c80670d0a3 Translated using Weblate (Catalan)
Currently translated at 99.6% (301 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ca/
2022-02-14 09:55:25 +01:00
Allan Nordhøy
084367cb50 Translated using Weblate (English)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/en/
2022-02-14 09:55:24 +01:00
dependabot[bot]
72af057a0e Bump follow-redirects from 1.14.4 to 1.14.8 in /backend
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.4 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.4...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-13 21:18:23 +00:00
dependabot[bot]
8d88a14a11 Bump ajv from 6.12.0 to 6.12.6 in /backend
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.0 to 6.12.6.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.0...v6.12.6)

---
updated-dependencies:
- dependency-name: ajv
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-13 10:19:47 +00:00
Joeri Colman
b46b9ea386 Added deleteAllFiles api endpoint 2022-02-08 10:07:37 +01:00
Dawson
f305deadc7 Added translation using Weblate (Hindi) 2022-02-04 10:15:59 +01:00
Kachelkaiser
850a3ba12f Translated using Weblate (German)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/de/
2022-01-30 16:52:35 +01:00
Maite Guix
0fb4593dc3 Translated using Weblate (Catalan)
Currently translated at 99.6% (301 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ca/
2022-01-25 03:31:55 +01:00
Vitor V
cf6546dd02 Translated using Weblate (Portuguese)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/pt/
2022-01-17 19:53:51 +01:00
Jagadeesh Vijay Varma
dd7354bd77 Translated using Weblate (Telugu)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/te/
2021-12-27 08:50:41 +01:00
Jagadeesh Vijay Varma
4a0000af5f Added translation using Weblate (Telugu) 2021-12-25 08:44:10 +01:00
Nikita Epifanov
71eaf70b2e Translated using Weblate (Russian)
Currently translated at 99.6% (301 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ru/
2021-12-17 14:51:32 +01:00
Diamond
548cb654d5 Translated using Weblate (Russian)
Currently translated at 97.0% (293 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ru/
2021-12-16 06:51:02 +01:00
Biepa
0747c28d8a Translated using Weblate (German)
Currently translated at 88.0% (266 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/de/
2021-12-16 06:51:00 +01:00
Maxime Leroy
4e2b7c4a56 Translated using Weblate (French)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/fr/
2021-11-16 07:51:14 +01:00
Bitpaint
ef2309d2f3 Translated using Weblate (French)
Currently translated at 89.7% (271 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/fr/
2021-11-02 03:37:23 +01:00
GlassedSilver
cae88433b6 Git Bug Report template; better user guidance 2021-10-12 07:25:20 +02:00
Minhyuk Lee
b922a904d0 Update README.md 2021-10-07 16:02:04 +09:00
min
86fc02f9e4 Translated using Weblate (Korean)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ko/
2021-10-07 04:04:04 +02:00
Tzahi12345
88cc8d0e81 Merge pull request #226 from weblate/weblate-youtubedl-material-ytdl-material
Translations update from Weblate
2021-10-01 02:03:14 -06:00
Isaac Abadi
829b8af942 Fixed mangled merge where getSubscriptions became getAllSubscriptions 2021-10-01 01:54:44 -06:00
Isaac Abadi
be94bc81c8 Removed prepare statement in package.json 2021-10-01 01:38:23 -06:00
Isaac Abadi
b9dabcf2f4 Updated dev default.json 2021-09-30 22:48:17 -06:00
Isaac Abadi
609f749d6c Updated API docs to fix errors 2021-09-30 22:38:57 -06:00
Tzahi12345
cc75e94408 Merge pull request #218 from NotWoods/api-generator
Generate types from OpenAPI
2021-09-30 22:29:03 -06:00
Isaac Abadi
bff40d97bb Invalid locales do not prevent languages from being selected anymore
Fixed video previews on mouse over when in multi-user mode
2021-09-30 22:27:44 -06:00
Isaac Abadi
c5db1d30e2 Added missing routes to API
Changed getDBInfo from post to get request
2021-09-30 22:18:55 -06:00
Isaac Abadi
94006ef794 Updated API
Removed unused component
2021-09-30 19:37:21 -06:00
Isaac Abadi
45be270b6f Dockerfile forces PM2_HOME to be in /app directory 2021-09-30 08:55:38 -06:00
Isaac Abadi
b2d8c4ef55 Disabled PM2 logging to $HOME/.pm2 2021-09-30 08:42:20 -06:00
Isaac Abadi
a7f1f1eb8e Fixed issue where language file generation occured after supported_locales.json was created 2021-09-29 23:29:26 -06:00
Isaac Abadi
3937700eff Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into api-generator 2021-09-29 02:38:42 -06:00
Isaac Abadi
f0c3837ee5 Translation JSONs are now generated at build time, removing the necessity to manually run xliff-to-json
- added postbuild.mjs to facilitate this
- all ng build --prod's have been replaced with npm run build
2021-09-29 00:36:56 -06:00
Isaac Abadi
c5f7cd1874 Converted input on the home page to textarea, maintaining same style but allowing an arbitrary number of urls to be entered 2021-09-28 21:36:36 -06:00
Tzahi12345
69767a82a9 Merge pull request #468 from Tzahi12345/forever-to-pm2
Use PM2 instead of ForeverJS
2021-09-28 20:34:05 -06:00
Isaac Abadi
84fa425a99 Fixed issue where selecting video quality would
Main component cleanup

Removed deprecated file card component
2021-09-28 20:27:01 -06:00
Isaac Abadi
84187b9474 Fixed issue where selecting video quality would
Main component cleanup

Removed deprecated file card component
2021-09-28 20:14:57 -06:00
Hosted Weblate
3fc83e636b Merge remote-tracking branch 'origin/master' 2021-09-29 03:45:10 +02:00
dejan995
9b88150555 Translated using Weblate (Macedonian)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/mk/
2021-09-28 22:15:33 +02:00
MeblIkea
90120e821d Translated using Weblate (French)
Currently translated at 88.4% (267 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/fr/
2021-09-28 22:15:30 +02:00
min
90dd39b9eb Translated using Weblate (Korean)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ko/
2021-09-28 13:49:01 +02:00
dejan995
5fee3fd281 Added translation using Weblate (Macedonian) 2021-09-28 13:48:58 +02:00
Isaac Abadi
dbeeb32d48 Updated Dockerfile and entrypoint to use pm2 instead of forever 2021-09-27 18:11:38 -06:00
min
17e8861c40 Translated using Weblate (Korean)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ko/
2021-09-27 06:37:55 +02:00
Isaac Abadi
46087f622e Switched forever.js to pm2
Updated winston
2021-09-26 02:40:05 -06:00
Isaac Abadi
5dd48035fb Improved archive management for subscription downloads
Downloads that fail due to existing in the archive now appears as an error in the manager

Fixed issue where redownloading sub videos wouldn't occur if it was not cleared from the download manager
2021-09-25 22:33:22 -06:00
min
40cd4ead1b Translated using Weblate (Korean)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ko/
2021-09-25 19:37:00 +02:00
Heimen Stoffels
d60af699dc Translated using Weblate (Dutch)
Currently translated at 100.0% (302 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/nl/
2021-09-25 19:36:58 +02:00
Tzahi12345
8981657084 Translated using Weblate (Spanish)
Currently translated at 84.1% (254 of 302 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/es/
2021-09-25 19:36:57 +02:00
Hosted Weblate
b5b9e84950 Merge remote-tracking branch 'origin/master' 2021-09-23 05:31:50 +02:00
Isaac Abadi
db53a12635 Added Korean translations and updated source translations file 2021-09-22 21:29:15 -06:00
Tzahi12345
8cd21bf433 Merge pull request #322 from Tzahi12345/dependabot/npm_and_yarn/electron-9.4.0
Bump electron from 8.2.0 to 9.4.0
2021-09-22 20:24:53 -06:00
Tzahi12345
c33acfb3de Merge pull request #447 from Tzahi12345/dependabot/npm_and_yarn/backend/axios-0.21.2
Bump axios from 0.21.1 to 0.21.2 in /backend
2021-09-22 20:24:43 -06:00
Isaac Abadi
562eaa1b9b Added support for generate NFO files for Kodi
Minor UI updates to settings
2021-09-22 19:27:25 -06:00
Isaac Abadi
ec7f04552f Fixed mangled Subject initialization in main component 2021-09-21 23:56:04 -06:00
Isaac Abadi
75fc09ed99 Improved arg simulation -- now uses same method as the actual download
Added checkbox for advanced custom args to either replace all args or append
2021-09-21 23:51:07 -06:00
Isaac Abadi
8aa354ac24 Fixed issue where navigating to a sub's video would play all videos from the subscription 2021-09-21 20:05:09 -06:00
Isaac Abadi
58a0dc4afe Version and commit info is now generated during autobuilds and can be viewed in the about dialog
Prepared removal of JSON translations from repo to move towards XLIFF-only
2021-09-21 20:05:09 -06:00
Glassed Silver
0e37d83740 Merge pull request #455 from GlassedSilver/Readme-Update
Readme update
2021-09-20 00:29:23 +02:00
Isaac Abadi
27faff054e Recent videos component now remembers sort order between page reloads 2021-09-19 14:56:32 -04:00
Isaac Abadi
a71d9f5c7e Added tests for arg generation and laid some plumbing for better arg simulation in the UI 2021-09-19 14:44:02 -04:00
Isaac Abadi
759637c1cf Fixed issue where per-subscription custom args were not being applied 2021-09-19 14:29:12 -04:00
Isaac Abadi
33f23c3ca9 Fixed issue where youtube-dl autoupdates broke if checkExistsWithTimeout failed the first time 2021-09-19 14:24:18 -04:00
GlassedSilver
176c99f813 Reference host-specific instructions 2021-09-18 17:41:25 +02:00
GlassedSilver
f7e0b3e86b [DRAFT!] Bump alpine: pinned '3.12' → 'latest' 2021-09-18 17:22:11 +02:00
GlassedSilver
bd2443b1e9 docker-compose.yml: Use YoutubeDL-Material nightly 2021-09-18 16:59:49 +02:00
min
d545926821 Translated using Weblate (Korean)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ko/
2021-09-18 08:38:46 +02:00
min
70cc611dfe Added translation using Weblate (Korean) 2021-09-18 08:38:46 +02:00
Allan Nordhøy
244e394924 Translated using Weblate (Norwegian Bokmål)
Currently translated at 56.7% (147 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/nb_NO/
2021-09-18 08:38:46 +02:00
Reza Almanda
60030ac525 Translated using Weblate (Indonesian)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/id/
2021-09-18 08:38:46 +02:00
Allan Nordhøy
3651a021ce Added translation using Weblate (Norwegian Bokmål) 2021-09-18 08:38:46 +02:00
Kaantaja
1cdae9f26f Translated using Weblate (Finnish)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/fi/
2021-09-18 08:38:46 +02:00
Kaantaja
f4854e10ad Added translation using Weblate (Finnish) 2021-09-18 08:38:46 +02:00
mamingwang
8f5361bd1a Added translation using Weblate (Basa (Cameroon)) 2021-09-18 08:38:46 +02:00
UnlimitedCookies
7be4ad4d41 Translated using Weblate (German)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/de/
2021-09-18 08:38:46 +02:00
Adolfo Jayme Barrientos
ea5756293d Translated using Weblate (Spanish)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/es/
2021-09-18 08:38:46 +02:00
Nikita Epifanov
d53c6d88ef Translated using Weblate (Russian)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ru/
2021-09-18 08:38:46 +02:00
Adolfo Jayme Barrientos
62fe940b2f Translated using Weblate (Catalan)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/ca/
2021-09-18 08:38:46 +02:00
Adolfo Jayme Barrientos
09beaa6c39 Translated using Weblate (Spanish)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/es/
2021-09-18 08:38:46 +02:00
Heimen Stoffels
71ed7c45ac Translated using Weblate (Dutch)
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/nl/
2021-09-18 08:38:46 +02:00
Eric
a9244e28a7 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (259 of 259 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/zh_Hans/
2021-09-18 08:38:46 +02:00
Isaac Abadi
f689609941 Fixed missing rxjs import 2021-09-17 01:15:08 -04:00
dependabot[bot]
1e9eec1b55 Bump electron from 8.2.0 to 9.4.0
Bumps [electron](https://github.com/electron/electron) from 8.2.0 to 9.4.0.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/master/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v8.2.0...v9.4.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-16 19:36:01 +00:00
dependabot[bot]
677af3712b Bump axios from 0.21.1 to 0.21.2 in /backend
Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-16 19:35:35 +00:00
Tzahi12345
5fd9d93007 Merge pull request #420 from Tzahi12345/download-manager
Download manager
2021-09-16 15:34:41 -04:00
Isaac Abadi
7ee34d21eb Disables download cancelling for now 2021-09-16 15:28:25 -04:00
Isaac Abadi
66c184a2e6 Fixes issue with broken builds 2021-09-16 15:25:19 -04:00
Isaac Abadi
13f6f698b7 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into download-manager 2021-09-16 15:22:24 -04:00
Isaac Abadi
b08325c1e3 Added ability to filter for only audio and only video files in the home page 2021-09-15 10:47:46 -06:00
Isaac Abadi
070d3fed57 Improved error handling for downloads 2021-09-15 10:04:25 -06:00
Isaac Abadi
775a1766d8 Added max concurrent downloads setting
Fixed issue where navigating to a subscription video would make the player behave like a playlist for the whole sub
2021-09-15 09:44:31 -06:00
Isaac Abadi
dbefb66021 Fixed issue where errored downloads would result in an infinite loop of error messages in the home page
Added dialog to view error from an errored out download
2021-09-13 23:19:59 -06:00
Isaac Abadi
3241d6aaaf Added download manager to home page if autoplay is disabled
Fixed bug where the UI attempted to generate a preview URL for placeholder file cards

Fixed bug where file renaming was always attempted even when not necessary
2021-09-13 22:42:37 -06:00
Tzahi12345
d014c6facb Merge pull request #443 from KuroSetsuna29/fix-ldap-login
Fixed issue preventing LDAP to create new account
2021-09-12 12:57:51 -06:00
KuroSetsuna29
b25ab70732 Fixed issue preventing LDAP to create new account 2021-09-11 02:48:53 -04:00
Isaac Abadi
f9b8e78655 Fixed bug where auto builds would create a users file instead of a directory 2021-09-06 16:18:52 -06:00
Isaac Abadi
acad7cc057 Minor code cleanup 2021-09-06 16:15:52 -06:00
Isaac Abadi
c3d91e89a8 Get downloads now supports filtering by uids 2021-09-06 16:12:51 -06:00
Isaac Abadi
97c5102eb9 Limited video previews to video files only 2021-08-25 23:54:56 -06:00
Isaac Abadi
865185d277 Added ability to pause and resume all downloads
Removed backend dependency on queue library
2021-08-25 23:45:56 -06:00
Isaac Abadi
a36794fd4f Improved video preview behavior 2021-08-25 23:44:22 -06:00
Isaac Abadi
6639305771 Added video previews when hovering over a file card 2021-08-25 23:36:31 -06:00
Isaac Abadi
cca76dd248 Code cleanup 2021-08-24 22:05:02 -06:00
Isaac Abadi
d899f88164 Added button to edit a subscription from the subscriptions page 2021-08-24 21:34:10 -06:00
Isaac Abadi
09b3c752d9 Removed downlload delay setting for subscriptions
Subscription downloads already queued are now not requeued on the next check

Headers in download queue table are now sortable

Added button to clear all finished downloads in the downloads manager
2021-08-24 21:33:43 -06:00
Isaac Abadi
71bb91b6e6 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into download-manager 2021-08-23 20:22:22 -06:00
Isaac Abadi
f9b1414460 Logic to avoid duplicates for subscription files now uses the video URL instead of its path 2021-08-23 20:18:28 -06:00
Isaac Abadi
6eb1e2f898 Fixed issue where different path formatting would lead files to get duplicated in the DB 2021-08-22 23:06:16 -06:00
Isaac Abadi
30505d0e8b Cleaned up unused code in subscriptions 2021-08-22 22:50:16 -06:00
Isaac Abadi
48ab1836ca Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into download-manager 2021-08-22 22:34:19 -06:00
Isaac Abadi
20cedb6c29 Pagination and filtering of files is now server-side
Importing unregistered files does not block server start anymore
2021-08-22 22:31:01 -06:00
Isaac Abadi
9f5b6122fa Added additional protections to verify that the DB is initialized before downloader does
Began work on watching entire subscriptions as a playlist

Subscriptions now use the new download manager to download files
2021-08-21 21:54:40 -06:00
GlassedSilver
5321624604 Update README.md
Fixed Contributors link
2021-08-20 10:00:03 +02:00
Isaac Abadi
8828af4174 Fixed issue where config items that defaulted to false would not be created if they were missing 2021-08-19 23:22:37 -06:00
Isaac Abadi
2bb4860a36 Fixed issue where if multi user mode was not defined, subscriptions could not be retrieved 2021-08-19 23:09:00 -06:00
Isaac Abadi
ce3d540633 Forces file registration to avoid registering a file that already exists in an atomic fasion 2021-08-13 19:40:06 -06:00
Isaac Abadi
f7b152fcf6 Download manager is now per user
Replaced multi download mode with autoplay checkbox
2021-08-13 16:28:28 -06:00
Isaac Abadi
f892a4a305 Download manager is now thread safe 2021-08-10 23:57:26 -06:00
Isaac Abadi
fc55961822 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into download-manager 2021-08-10 21:33:31 -06:00
Isaac Abadi
ebfa49240c Added methods to modify download state
Added missing optionalJwt calls in several routes
2021-08-10 21:32:13 -06:00
Isaac Abadi
9e60d9fe3e Fixed issue where some some videos would send many requests to SponsorBlock when only one was needed 2021-08-09 00:23:09 -06:00
Isaac Abadi
ecef8842ae Converted downloads page to new downloads schema 2021-08-09 00:22:15 -06:00
Isaac Abadi
8cc653787f Cleaned up app.js backend code 2021-08-09 00:21:36 -06:00
Isaac Abadi
0360469c5a Download manager is now functional
Added UI support for new downloads schema

Implemented draft test for downloads

Cleaned up unused code snippets
2021-08-08 21:29:31 -06:00
Isaac Abadi
5a90be7703 Logger is now separated into its own module
Added eslint and fixed many logic errors based on its recommendations
2021-08-08 14:54:24 -06:00
Isaac Abadi
ff403d18d1 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into download-manager 2021-08-08 13:43:49 -06:00
Isaac Abadi
11284cb1b3 Fixed unnecessary (and mispelled) class for settings element 2021-08-08 06:00:15 -06:00
Isaac Abadi
8b1a1a56e3 Added SponsorBlock support for skipping ads when viewing supported videos
Updated default value for subscriptions check interval (new value of 86,400 only existed in the default.json)

Text inputs in settings menu are now larger
2021-08-08 05:56:47 -06:00
Tzahi12345
32370280ab Merge pull request #416 from BrianCArnold/master
Added change to make player work on iOS without being full screen.
2021-08-07 22:39:34 -06:00
Brian C. Arnold
240d6569fa Added change to make player work on iOS inline. 2021-08-06 15:50:11 -04:00
Isaac Abadi
2927a4564d Additional scaffolding for download manager
Added queue to npm backend dependencies
2021-08-05 18:57:54 -06:00
Isaac Abadi
5c94036625 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into download-manager 2021-08-02 19:59:54 -06:00
Isaac Abadi
7be90ccd94 Fixed bug where subscription videos would get duplicated 2021-08-02 18:50:44 -06:00
Isaac Abadi
01b6e22f83 Began scaffolding work for download manager 2021-08-02 18:41:30 -06:00
Isaac Abadi
b1385f451b Added option to rate limit downloads
Added option to force delay between videos in a subscription

Fixed issue where file handle was maintained on files deleted through unsubscribing
2021-08-01 22:19:15 -06:00
Tzahi12345
f40ac49082 Merge pull request #413 from Tzahi12345/cleaner-playlists-and-settings
Dedicated settings page and UI cleanup
2021-08-01 21:17:50 -06:00
Isaac Abadi
2756cfae17 Login component is now a lot prettier 2021-08-01 20:41:36 -06:00
Isaac Abadi
dac5919ffb Updated look of buttons and several home page elements 2021-08-01 20:41:13 -06:00
Isaac Abadi
34245bd339 Updated styling for settings page
Fixed issue where redirects to home occured when reloading the settings page

Fixed errors that occured when loading the settings page
2021-08-01 20:40:29 -06:00
Isaac Abadi
8d6ec819e6 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into cleaner-playlists-and-settings 2021-08-01 17:27:35 -06:00
Isaac Abadi
b03b4d173b Fixed issue where testing the connecting string would fail if local DB was being used
Fixed issue where blacklisting video in with archiving would not work

Cleaned up unused functions in app.js
2021-08-01 14:38:34 -06:00
Tzahi12345
c8f219d5b0 See previous commit, MongoDB is no longer on by default for all installs 2021-08-01 06:24:47 -06:00
Tzahi12345
ec3ab17507 MongoDB is no longer the default in config, this will just be set through the docker-compose.yml 2021-08-01 06:23:27 -06:00
Tzahi12345
5124e3b333 Update issue templates 2021-07-29 21:35:33 -06:00
Isaac Abadi
d09b244bc2 Fixed bug where unsubscribing from a channel would clear the entire files table
Fixed issue where yt-dlp did not work with subscriptions
2021-07-28 19:44:05 -06:00
Isaac Abadi
c0a385ce78 Default file output now applies to subscriptions 2021-07-27 22:36:32 -06:00
Isaac Abadi
258d5ff495 Test connection string now uses the currently typed in connection string rather than the last saved one 2021-07-26 20:31:35 -07:00
Isaac Abadi
fb5c13db27 Fixed issue where files could be added to playlists of the wrong type 2021-07-26 20:14:13 -07:00
Isaac Abadi
92413bd360 Added ability to add file to playlist using the context menu 2021-07-26 20:10:22 -07:00
Isaac Abadi
7174ef5f57 Fixed issue where config initialization did not occur early enough in lifecycle, causing db.js to throw an error if the config did not exist 2021-07-26 18:25:41 -07:00
Isaac Abadi
73b9cf7893 Settings is now a route instead of a dialog 2021-07-26 18:18:07 -07:00
Tzahi12345
7ff906fd35 Added issue templates 2021-07-22 23:04:29 -06:00
Isaac Abadi
6e084bd94a Fixed issue where subscriptions check interval would only update after restart 2021-07-22 20:56:42 -06:00
Tzahi12345
21b97911e8 Merge pull request #401 from Tzahi12345/python3-docker-test
yt-dlp python3 bugfix
2021-07-22 20:39:09 -06:00
Isaac Abadi
ccb4819a94 Adds small timeout to restart server API call
Fixes typo in translation description for video cropping
2021-07-22 20:38:42 -06:00
Isaac Abadi
ce8f90ca1d Reverted python3->python dockerfile changes and re-added python2 to dockerfile 2021-07-22 02:13:11 -06:00
Isaac Abadi
8469ae10ad Fixed issue where backend would crash if the details bin did not exist for youtube-dl 2021-07-22 02:10:14 -06:00
Isaac Abadi
117255b0b7 Fixed bug where adding content to playlist wouldn't enable save button 2021-07-22 01:53:49 -06:00
Isaac Abadi
f0e73c1708 python3 now aliases as python in Dockerfile 2021-07-22 01:50:51 -06:00
Isaac Abadi
aa1e36ae35 Updated dockerfile to download python3 for yt-dlp support 2021-07-21 23:59:00 -06:00
Isaac Abadi
a1841e84ca Added translations for Catalan, Czech, Indonesian, Portuguese, and Russian
Updated translations for German and French, and updated source translation files
2021-07-21 23:47:57 -06:00
Isaac Abadi
05909877f4 Fixed translation description typo 2021-07-21 23:27:59 -06:00
Isaac Abadi
90af895552 Updated style of settings for DB
MongoDB connection string test now only tests once
2021-07-21 23:25:59 -06:00
Isaac Abadi
9f908aa3fc Added ability to randomize playlists
Missing videos now show a more verbose error in the logs
2021-07-21 20:03:53 -06:00
Tzahi12345
b56b371ece Merge pull request #398 from Tzahi12345/dependabot/npm_and_yarn/backend/color-string-1.6.0
Bump color-string from 1.5.3 to 1.6.0 in /backend
2021-07-21 19:29:18 -06:00
Isaac Abadi
84e54cb4d5 Updated npm in auto build to v12
Added vscode tasks for launching frontend and backend in dev mode
2021-07-21 18:52:43 -06:00
Isaac Abadi
42aaecc13a Fixed bug where downloaded videos did not have a user_uid field 2021-07-20 23:40:06 -06:00
Isaac Abadi
aac11b2105 Set MongoDB port back to its default 2021-07-20 23:24:28 -06:00
Isaac Abadi
bbf94ef982 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2021-07-20 23:20:43 -06:00
Isaac Abadi
2876cf55db Added env var to docker-compose to enable config mutations by default 2021-07-20 23:20:31 -06:00
Tzahi12345
375d3b4f38 Merge pull request #336 from Tzahi12345/add-yt-dlp
Added yt-dlp support
2021-07-20 22:11:03 -06:00
Isaac Abadi
160cffc737 Added support for yt-dlp's --no-clean-infojson 2021-07-20 22:09:40 -06:00
Isaac Abadi
7aad7b7d24 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into add-yt-dlp 2021-07-20 21:55:18 -06:00
Isaac Abadi
380475b33e Updated tests to include query speed check and removed dubious test 2021-07-20 21:54:49 -06:00
Tzahi12345
384d365cf9 Merge pull request #378 from Tzahi12345/concurrent-streams-and-player-refactor
MongoDB support, concurrent streams, player/backend file handling refactor, and more!
2021-07-20 21:37:06 -06:00
Isaac Abadi
d6a43c76a4 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into concurrent-streams-and-player-refactor 2021-07-20 21:36:48 -06:00
Isaac Abadi
407333a314 Updated dev default.json 2021-07-20 21:34:33 -06:00
Isaac Abadi
0fb01469c4 Fixed issue in player component where errors were displayed in the console due to vars being changed after Angular detection
Fixed spooky issue where recent videos' navigateToFile stopped working
2021-07-20 21:29:49 -06:00
Isaac Abadi
d10eb4f2eb Fixed issue where old DB backup didn't work
Massive insertions to local DB are now split up into 30k chunks
2021-07-20 20:55:47 -06:00
Isaac Abadi
148ed9aa65 Added support for MongoDB indexing to increase query performance
Fixed db backup functionality
2021-07-18 23:18:46 -06:00
dependabot[bot]
1125de43d7 Bump color-string from 1.5.3 to 1.6.0 in /backend
Bumps [color-string](https://github.com/Qix-/color-string) from 1.5.3 to 1.6.0.
- [Release notes](https://github.com/Qix-/color-string/releases)
- [Changelog](https://github.com/Qix-/color-string/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Qix-/color-string/commits/1.6.0)

---
updated-dependencies:
- dependency-name: color-string
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-19 01:05:37 +00:00
Tzahi12345
00b591a9a4 Merge pull request #392 from itsthejoker/patch-1
Update default.json to use a longer subscription interval
2021-07-18 18:15:44 -06:00
Tzahi12345
06d9793d1a Merge pull request #389 from Tzahi12345/dependabot/npm_and_yarn/backend/glob-parent-5.1.2
Bump glob-parent from 5.1.1 to 5.1.2 in /backend
2021-07-18 18:13:49 -06:00
Isaac Abadi
0a2529330d Fixes issue in some browsers where the audio player disappears 2021-07-18 18:10:33 -06:00
Tzahi12345
19317dbddb Merge pull request #383 from ErwanGit/master
Update API docs links in settings
2021-07-18 17:46:37 -06:00
Isaac Abadi
3b74a2b5da Updated docker-compose to include mongodb instance 2021-07-18 17:41:46 -06:00
Isaac Abadi
a810628f15 Fixed DB migration for tables with no docs 2021-07-17 20:00:49 -06:00
Isaac Abadi
a7d349a71a Updated ES to 2019/2020 and local default.json is ignored for reloads when in dev mode 2021-07-17 19:42:32 -06:00
Isaac Abadi
f8c4653ae0 Added migration from old to new DB system 2021-07-16 00:10:35 -06:00
Isaac Abadi
bb6503e86d Changed DB structure again
Added support for MongoDB

Added tests relating to new DB system

Category rules are now case insensitive

Fixed playlist modification change state
2021-07-16 00:05:08 -06:00
Joe Kaufeld
dbbfc041a4 Update default.json to use a longer update period
See https://github.com/Tzahi12345/YoutubeDL-Material/issues/385 for context; setting this to a daily value instead of every five minutes means that updates still come in but it doesn't completely trample all other network traffic, especially if you have a lot of subscriptions.
2021-06-23 10:42:12 -04:00
dependabot[bot]
342dafd52a Bump glob-parent from 5.1.1 to 5.1.2 in /backend
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 14:32:16 +00:00
Isaac Abadi
984e990103 Fixed issue where categories could not be viewed 2021-06-08 16:32:35 -06:00
Isaac Abadi
4ea239170e If multiple videos exist in one URL, a playlist will be auto generated
Removed tomp3 and tomp4 routes, replaced with /downloadFile

Simplified category->playlist conversion

Simplified playlist creation

Simplified file deletion

Playlist duration calculation is now done on the backend (categories uses this now too)

removeIDFromArchive moved from subscriptions->utils

Added plumbing to support type agnostic playlists
2021-05-30 00:39:00 -06:00
Isaac Abadi
e2c31319cf Migrated playlist and subscription (per video and sub-wide) video downloading functionality to new schema
Migrated modify playlist component to new schema

Moved wait function and playlist generation function(s) to utils
- added tests for zip generation
2021-05-23 03:59:38 -06:00
Erwan
b933af03e2 Update API docs links in settings 2021-05-22 14:58:48 +02:00
Isaac Abadi
419fe3c3c6 Fixed frontend security issues for several depepndencies 2021-05-16 02:58:16 -06:00
Isaac Abadi
07b48a4da1 Fixed backend security issues with several dependencies 2021-05-16 02:55:27 -06:00
Isaac Abadi
a11445b80d Added backend tests and made authentication more testable 2021-05-16 02:54:15 -06:00
Isaac Abadi
297a4a3f34 Simplified streaming and file deletion functions 2021-05-16 02:53:36 -06:00
Isaac Abadi
1d2ab0dc41 401 errors will now not cause redirects in the /player route 2021-05-12 22:56:38 -06:00
Isaac Abadi
46f8579439 Refactored player component to utilize uids instead of fileNames to improve maintainability, consistency, and reliability
Playlists now use uids instead of fileNames

Added generic getPlaylist and updatePlaylist functions
2021-05-12 22:56:16 -06:00
Isaac Abadi
b3744e616d Users can now stream videos concurrently with other users with the new concurrent stream component 2021-05-12 22:52:46 -06:00
Isaac Abadi
de154a9c3e Updated dockerfile to fix UID/GID bug related to forever.js 2021-05-12 21:57:42 -06:00
Tzahi12345
9e71b1ff12 Merge pull request #359 from benashby/helm-chart
Helm chart improvements
2021-05-12 21:48:59 -06:00
Tzahi12345
6d318234b6 Merge pull request #360 from s55ma/patch-1
Update README.md
2021-03-28 19:23:04 -04:00
Isaac Abadi
49925848ff Material Icons are now hosted locally to avoid requesting them from Google for proxied users 2021-03-28 15:51:53 -04:00
s55ma
356a807cad Update README.md
Some packages are missing for Ubuntu/Debian install, especially python. Without python package, you get the following error when trying to download from youtube:

2021-03-28T15:28:30.461Z ERROR: Error while retrieving info on video with URL https://www.youtube.com/watch?v=[some_ID] with the following message: Error: Command failed with exit code 127: /root/youtubedl-material/node_modules/youtube-dl/bin/youtube-dl --dump-json -o video/%(title)s.mp4 --write-info-json --print-json -f bestvideo+bestaudio --merge-output-format mp4 --write-thumbnail http://www.youtube.com/watch?v=[some_ID]
2021-03-28T15:28:30.461Z ERROR: /usr/bin/env: 'python': No such file or directory
2021-03-28 17:33:47 +02:00
Ben Ashby
4e07440ed2 Removed Accidental Dir 2021-03-27 16:34:14 -06:00
Ben Ashby
59c9237be5 integrated pvc's 2021-03-26 09:59:02 -06:00
Ben Ashby
4ba4710741 Added helm chart 2021-03-26 09:46:20 -06:00
Isaac Abadi
addd54fefd Switched nodemon to foreverjs to hopefully enable restarting internally and fix runtime errors 2021-03-20 16:22:59 -06:00
Isaac Abadi
aefdde5401 Fixed issue (hopefully) where nodemon is not properly installed on Docker 2021-03-18 20:59:46 -06:00
Isaac Abadi
4c1f975eae Force nodemon to install during the container setup
Docker now starts through nodemon directly
2021-03-18 19:29:03 -06:00
Isaac Abadi
4c06bc750c Fixed issue where on some Docker environments the container failed to start due to the error "nodemon update check failed" 2021-03-17 19:13:52 -06:00
Isaac Abadi
4643efbae0 Added ability to restart the server from the frontend
Dockerfile/entrypoint.sh now uses nodemon enabling restarting from the UI in a container
2021-03-16 22:41:07 -06:00
Isaac Abadi
d11f77a6c9 Updated yt-dlp paths 2021-03-16 22:16:57 -06:00
Isaac Abadi
1f0153b17e Subscription videos being downloaded will get registered into the database as they are added to avoid having to wait until the subscription completes 2021-03-16 20:06:05 -06:00
controlol
7e9d1d30da patch qualityPath
qualityPath should not be escaped, this results in `could not find format error`
2021-03-04 13:46:39 +01:00
controlol
b9f6d29061 escape paths for use with commandline
escape qualityPath and fullOutput for use with commandline
In order to successfully download files from subscriptions these strings should be  escaped to work properly in the commandline. 
I have seen you use almost the same function (generateArgs()) in app.js. Even though I have never had a problem with this outside subscriptions I would suggest to do the same for that function starting on line 1405
2021-03-04 12:45:54 +01:00
Isaac Abadi
f32b394715 Added maxBuffer option to all downloads 2021-02-22 12:55:30 -07:00
Isaac Abadi
9d09eeffe3 Added maxbuffer option to subscriptions 2021-02-22 12:54:28 -07:00
Isaac Abadi
c660c28422 youtube-dl now updates in the same way as the other forks 2021-02-22 12:53:21 -07:00
Isaac Abadi
669c87dd1b Removed unecessary suffix in crop file inputs 2021-02-12 21:21:45 -07:00
Isaac Abadi
023df9c29d Fixed issue where playlists couldn't be favorited after downloading 2021-02-12 21:21:09 -07:00
Isaac Abadi
433d08e9df Added ability to crop files
Fixed bug in downloading playlists
2021-02-12 21:20:48 -07:00
Isaac Abadi
e34aa4d9d6 Adds Dutch language support 2021-01-31 19:47:14 -05:00
Isaac Abadi
3f9314a0c3 Fixed bug where categories selection logic had an out of range exception 2021-01-28 22:11:04 -05:00
Isaac Abadi
00a0ab460b Subscription's videos are now stripped from HTTP requests where they are not needed 2021-01-20 08:50:15 -05:00
Isaac Abadi
a1b32e2851 Added yt-dlp support
Simplified update youtube-dl code
2021-01-20 08:32:16 -05:00
Tzahi12345
b8cab673ae Merge pull request #316 from Tzahi12345/categories-playlist-fix
Categories playlist download fix
2021-01-13 16:13:22 -05:00
Isaac Abadi
6481102e01 Changes forEach loops in categorize() to regular for loops to facilitate early breaking 2021-01-13 16:12:11 -05:00
Isaac Abadi
af58854f0e Added info button to the player component 2021-01-13 12:50:18 -05:00
Isaac Abadi
d7d861ef0e Fixed typo in default custom output key for categories 2021-01-12 22:32:27 -05:00
Isaac Abadi
1d5490c0ff Allows playlists to be categorized based on the first video that matches 2021-01-12 22:08:42 -05:00
Isaac Abadi
28ee77cee0 Hotfix that allows playlists to be downloaded with categories 2021-01-12 16:42:30 -05:00
Isaac Abadi
133d848729 Fixed bug where deleting a file card wasn't possible if it was already deleted manually 2021-01-11 13:55:02 -05:00
Isaac Abadi
a78f4e99d0 Removed trivial browser log that occured at file deletion 2021-01-11 01:20:53 -05:00
Isaac Abadi
539bc5094a Fixed bug where sometimes a subscription video's thumbnail would get deleted twice and throw an error 2021-01-11 01:20:07 -05:00
Isaac Abadi
f0f2faa398 Sub's videos are removed from the post request when deleting a video as it's not needed 2021-01-11 01:19:29 -05:00
Isaac Abadi
7835185fe0 Made file card deletion much more reliable by finding out the index of the file on deletion rather than attempting to maintain a valid index 2021-01-11 01:18:58 -05:00
Isaac Abadi
95bb69f16b Fixed bug where videos would not delete in single-user mode 2021-01-10 17:14:10 -05:00
Isaac Abadi
a93aa080b3 Fixed bug where playlistd could not be made 2021-01-09 17:25:46 -05:00
Isaac Abadi
ed1375d40b Fixed bug where deleting videos while searching caused them to still show up in the UI 2021-01-09 14:07:51 -05:00
Isaac Abadi
db78e4ad5e Fixed bug where playlist downloads would fail and progress would not show (for playlist downloads) 2021-01-09 14:07:51 -05:00
Tzahi12345
6ef0082563 Merge pull request #304 from Tzahi12345/dependabot/npm_and_yarn/backend/axios-0.21.1
Bump axios from 0.21.0 to 0.21.1 in /backend
2021-01-06 09:55:01 -05:00
dependabot[bot]
b978007472 Bump axios from 0.21.0 to 0.21.1 in /backend
Bumps [axios](https://github.com/axios/axios) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.0...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 10:25:29 +00:00
Isaac Abadi
c09dd7a03b Updated Chinese and Spanish translations and added Italian translations 2021-01-01 17:38:08 -05:00
Isaac Abadi
b6c09324d9 Updated error messages to make them more verbose and fixed ID3 tagging for file names 2021-01-01 17:35:14 -05:00
Tzahi12345
1a900399d8 Merge pull request #292 from diveflo/ci/releaseassetnamefix
Fix asset name in automated release creation
2020-12-29 18:13:18 -05:00
Isaac Abadi
ea959547fd Fixed bug where file indices were incorrectly assigned 2020-12-28 00:22:14 -05:00
Isaac Abadi
085849c7ee Fixed bug that prevented the menu for file cards from being opened (2) 2020-12-27 21:13:24 -05:00
Isaac Abadi
cf1dd43d36 Fixed bug that prevented the menu for file cards from being opened 2020-12-26 19:27:03 -05:00
Isaac Abadi
250f150587 Download checker now only runs if the video info was successfully retrieved 2020-12-26 18:56:01 -05:00
Isaac Abadi
dbf08e1276 Fixed bug where audio files that had a stale webm extension in the metadata file path would fail to register 2020-12-26 15:51:13 -05:00
Isaac Abadi
f74ce4b865 Fixed bug that caused the UI to fail loading after creating a user in multi-user mode 2020-12-26 15:35:13 -05:00
Florian Gabsteiger
8e4e0c7908 fix wrongly named ci step 2020-12-25 18:32:32 +01:00
Florian Gabsteiger
b0cb09309d Fix release asset name creation
The complete git ref name was used as part of the release asset filename for tagged commits.
This includes the refs/tags prefix, which fails as "/" characters can't be part of filenames.
To work around this, a step is added that extracts the pure tag name first.
2020-12-25 18:21:05 +01:00
Isaac Abadi
75c1c9e9b7 Fixed name of docker release workflow 2020-12-24 16:10:57 -05:00
Isaac Abadi
c19e0bb881 Adds manually-triggered GH workflow for release builds 2020-12-24 16:09:58 -05:00
Tzahi12345
a1af5496c7 Update README.md
Updated preview images in README
2020-12-24 03:41:38 -05:00
Isaac Abadi
3c206c31d5 Updated translations base file 2020-12-24 03:21:56 -05:00
Tzahi12345
3ffcfac28b Merge pull request #290 from Tzahi12345/updated-player
Updated player & much more (v4.2)
2020-12-24 03:13:50 -05:00
Isaac Abadi
0e7bc1979f Updated versioning info 2020-12-24 02:05:30 -05:00
Isaac Abadi
33fc74b7e7 Updated dev config 2020-12-24 02:04:43 -05:00
Isaac Abadi
c08993e20b Old database files are now backed up prior to migration to simplified structure 2020-12-24 02:02:05 -05:00
Isaac Abadi
4835093606 Fixed issue where some non-YT videos would fail as the pre-check was incompatible 2020-12-24 00:10:54 -05:00
Isaac Abadi
c63a64ebef Categories will now auto-generate playlists 2020-12-23 01:29:22 -05:00
Isaac Abadi
9a57080bb3 Category is now properly stored in the database 2020-12-23 01:24:43 -05:00
Isaac Abadi
1cc4df2829 Updated translation file to v4.2 2020-12-22 01:29:19 -05:00
Isaac Abadi
6eb6ffa5e4 Get user videos now accepts an optional type parameter 2020-12-22 01:25:12 -05:00
Isaac Abadi
2656147570 Optimized get/set subscription process 2020-12-22 01:24:50 -05:00
Isaac Abadi
88a1c31090 Removed unused code in home page 2020-12-22 01:24:27 -05:00
Isaac Abadi
3f1532b4c6 Updated migration
- Fixed bug in migration process for single-user mode
- Changed name of migration

Removed unused code for getmp3/mp4 and fixed bug when retrieving playlist if it didn't exist

Fixed bug in streaming code where playlist audio files would not play if the file path was not present

Fixed bug in getallsubscriptions for single user mode
2020-12-22 01:23:43 -05:00
Isaac Abadi
afb5e3800c In the subscription page, the subscription is now continuously retrieved at an interval of 1s to update for new videos or the downloading state
- There is now a visual indicator for when a subscription is retrieving videos
2020-12-20 00:30:48 -05:00
Isaac Abadi
2971580f91 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into updated-player 2020-12-19 14:37:00 -05:00
Isaac Abadi
4cbfab20e0 Added category info to video info dialog (if present)
- you can now search by category on the home page
2020-12-19 14:33:19 -05:00
Isaac Abadi
7e06d30205 401 unauthorized requests now redirect users to the login page 2020-12-19 13:03:49 -05:00
Isaac Abadi
e75b56ad3f Added ability to pause specific subscriptions 2020-12-19 04:12:27 -05:00
Isaac Abadi
441a470990 Added ability to view playlist in reverse order in the playlist editing dialog 2020-12-19 03:51:30 -05:00
Isaac Abadi
eb7661c14a Fixed bug in file deletion where file indexes became stale 2020-12-19 02:06:52 -05:00
Isaac Abadi
59c38321fd Fixed bug in file deletion 2020-12-19 01:46:19 -05:00
Isaac Abadi
9847577431 Added setting for redownloading fresh uploads
Fixed bug in implementation of fresh upload redownloader
2020-12-19 00:24:36 -05:00
Isaac Abadi
0fec9d71a0 Updated Chrome and Firefox extension zips 2020-12-18 18:41:25 -05:00
Isaac Abadi
5f13205017 Removed background script declaration from the Chrome/Firefox extension 2020-12-18 18:36:39 -05:00
Isaac Abadi
cd93313cfc Updated Chrome/Firefox extension to 0.4 2020-12-18 18:34:30 -05:00
Isaac Abadi
8058b743eb Added support for redownloading fresh uploads, which will eventually be hidden behind an opt-in setting 2020-12-18 18:31:23 -05:00
Isaac Abadi
e3374c573a Args incompatible with video mode and audio-only mode will now get removed 2020-12-15 20:16:53 -05:00
Isaac Abadi
29b8dc227c Updated location/style of the share and download icons on the player 2020-12-15 20:04:02 -05:00
Isaac Abadi
c30350205f Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into updated-player 2020-12-15 18:16:15 -05:00
Isaac Abadi
ff8886d2e0 Simplified rxjs imports on the home page and potentially removed an erroneous error 2020-12-15 17:20:04 -05:00
Isaac Abadi
43b0c2fb9e Fixed bug that prevented subscription videos from being shown in the subscription page 2020-12-15 17:19:15 -05:00
Isaac Abadi
e39e8f3dba Home page paginator no longer disappears for empty pages
Paginator length fixed

Updated styling on paginator

Added new text if videos are not present on the home page (due to filter or no downloads in general)
2020-12-15 01:11:15 -05:00
Isaac Abadi
da3bd2600f Fixed bug where sharing didn't work for some videos
View count now increments on each play unless the video is shared
2020-12-15 00:42:24 -05:00
Isaac Abadi
6ad590497b Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into updated-player 2020-12-14 18:20:33 -05:00
Isaac Abadi
4f693d4eda Added description to player component and simplified the database by un-splitting videos and playlists by type 2020-12-14 18:19:50 -05:00
Isaac Abadi
9de403245b Twitch chat now supports subscriptions
- refactored code to be cleaner and more modularized

Updated scrolling on twitch chat to actually scroll to the bottom with new messages

Fast forwarding in videos with a twitch chat is now faster and provides a smoother transition
2020-12-10 21:04:53 -05:00
Isaac Abadi
ff1bb8dee1 Reduced the max number of ghost cards to 10 for performance reasons 2020-12-10 19:34:15 -05:00
Isaac Abadi
3f10986cdf Updated Angular to version 11
- ngx-videogular was replaced by @videogular/ngx-videogular
2020-12-10 19:33:53 -05:00
Isaac Abadi
c6fc5352c5 Added ability to add more metadata to db through migrations, and added scaffolding for supporting description and play count in the player component 2020-12-09 17:28:00 -05:00
Isaac Abadi
f425b9842f Updated twitch chat component to support user colors and to auto open if the chat has already been downloaded 2020-12-08 22:57:09 -05:00
Isaac Abadi
8c916d8fe4 Fixed bug that prevented search on the home page from working 2020-12-05 04:19:48 -05:00
Isaac Abadi
bb18e1427e Added paginator to the home page 2020-12-05 03:40:59 -05:00
Isaac Abadi
1542436e96 Passwords now must be provided when registering a user 2020-11-29 18:37:16 -05:00
Isaac Abadi
b0acb63123 Updated backend dependencies (caused build to fail) 2020-11-29 13:07:17 -05:00
Tzahi12345
0713eda7e2 Merge pull request #272 from Tzahi12345/twitch-chat
Added chat sidebar for Twitch VODs
2020-11-29 03:21:31 -05:00
Isaac Abadi
d08fee1223 Added v1 of chat sidebar for Twitch VODs 2020-11-29 03:18:28 -05:00
Isaac Abadi
8938844ffa Added ability to select the max quality for a subscription. It defaults to 'best' which will get the best native mp4 video 2020-11-28 00:45:47 -05:00
Tzahi12345
9895d77e01 Merge pull request #258 from diveflo/fix/dockerreadme
Cleanup & clarify README.md
2020-11-27 15:06:46 -05:00
Florian Gabsteiger
27437a615f Cleanup README and clarify docker port usage 2020-11-27 12:32:51 +01:00
Isaac Abadi
b730bc5adc Added option to set a default file output - custom file output in the advanced expansion panel will override this 2020-11-27 00:25:31 -05:00
Isaac Abadi
d15d262b87 Fixed bug that resulted in the "download videos in the last X days" timerange in edit subscriptions to come up blank 2020-11-26 15:49:14 -05:00
Tzahi12345
1aade1202d Merge pull request #259 from diveflo/feat/cibuildandrelease
Automated build & release via GitHub Actions
2020-11-25 15:38:50 -05:00
Isaac Abadi
2f541a49df Thumbnails now load using a faster method with a dedicated API route rather than sending blobs directly.
- In cases of lots of files, loading should be significantly faster
2020-11-25 15:36:00 -05:00
Florian Gabsteiger
d93481640c automated release creation for tagged commits 2020-11-24 11:45:27 +01:00
Florian Gabsteiger
71814cbdc9 build via github actions 2020-11-24 11:43:04 +01:00
Isaac Abadi
09832ad15b Multi download mode and download-only mode now reloads recent videos 2020-11-24 03:39:30 -05:00
Tzahi12345
cc78091403 Merge pull request #262 from diveflo/fix/dockerci
do not push new docker images for pull requests
2020-11-23 15:09:58 -05:00
Florian Gabsteiger
cb88c7bc7c do not push new docker images for pull requests 2020-11-23 14:39:16 +01:00
Tzahi12345
98f4828db4 Merge pull request #257 from diveflo/feat/multiarchdockerci
Multi-arch docker image build via GitHub Actions
2020-11-22 17:15:08 -05:00
Tzahi12345
8f0739c0f9 Removes extra line
Co-authored-by: Sandro <sandro.jaeckel@gmail.com>
2020-11-22 00:40:53 -05:00
Tzahi12345
ab355d62a0 GitHub autobuild now uses nightly tag
Co-authored-by: Sandro <sandro.jaeckel@gmail.com>
2020-11-21 23:13:58 -05:00
Florian Gabsteiger
4d2d9a6b10 change docker image tag name to align with upstream 2020-11-21 20:58:58 +01:00
Florian Gabsteiger
89dfac1249 update job name to better reflect what it's actually doing 2020-11-21 20:55:08 +01:00
Florian Gabsteiger
d4f81eb0ab add platform emulator 2020-11-21 20:16:49 +01:00
Florian Gabsteiger
6b7d0681d2 add automated multi-arch docker image build and push to dockerhub 2020-11-21 20:15:12 +01:00
Isaac Abadi
b32fdb2445 Tab title now matches the top title set in the settings 2020-11-20 17:39:44 -05:00
Tzahi12345
b059c7ed5e Update README.md 2020-11-18 01:55:49 -05:00
Isaac Abadi
8d87cbb08d Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-11-14 16:55:12 -05:00
Isaac Abadi
1bb2f54eba Fixed bug where updating a subscription would not work correctly 2020-11-14 16:54:20 -05:00
Tzahi12345
7392338d6e Merge pull request #247 from hwalker928/master
Update README.md
2020-11-14 04:04:03 -05:00
hwalker928
82df92a72d Update README.md 2020-11-14 09:00:45 +00:00
Isaac Abadi
9e4b328f91 Default youtube downloader switched back to youtube-dl after testing
Fixed bug that caused some non-youtube downloads from failing
2020-11-01 20:21:36 -05:00
Isaac Abadi
3a049a99ac Fixed bug where non-youtube downloads would fail 2020-11-01 19:38:22 -05:00
Isaac Abadi
b323b548ca Added ability to use youtube-dl forks
Downloader now defaults to youtube-dlc because of the recent DMCA requests
2020-11-01 19:16:41 -05:00
Tzahi12345
568463487f Merge pull request #236 from Tzahi12345/categories
Adds rule-based categories
2020-10-24 01:13:26 -04:00
Isaac Abadi
3318ac364d Code cleanup and changed proposed handling of existing tags for suggestions 2020-10-24 00:29:42 -04:00
Isaac Abadi
1ce85813fb Saving a category will now cause the UI to refresh the cache of categories 2020-10-24 00:20:39 -04:00
Isaac Abadi
6ea4176d63 Added missing code that makes category paths relative to the root dir 2020-10-24 00:15:47 -04:00
Isaac Abadi
3aa08e1817 Added scaffolding for tags 2020-10-23 02:44:24 -04:00
Isaac Abadi
727b047c39 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into categories 2020-10-23 02:00:57 -04:00
Isaac Abadi
d659a7614f updated default.json 2020-10-23 01:49:27 -04:00
Tzahi12345
6ad9d5ea8e Merge pull request #235 from Tzahi12345/locale-based-dates
File cards now use the locale to format dates
2020-10-23 01:40:48 -04:00
Isaac Abadi
edc22cc47b File cards now use the locale to format dates 2020-10-23 01:38:44 -04:00
Isaac Abadi
0189d292a8 Fixed bug that prevented categorized files from being deletes and simplified the two delete file API calls into one 2020-10-18 02:20:06 -04:00
Isaac Abadi
deac54e8d6 Fixed bug in goToPlaylist 2020-10-15 17:00:56 -04:00
Isaac Abadi
d4e5082039 Confirm dialog can now optionally use warn colors (used for deletion or breaking changes)
Category re-ordering is fixed

Category deletion in settings is now functional
2020-10-15 17:00:48 -04:00
Isaac Abadi
6f089491a5 Updated player component to support categories 2020-10-15 16:59:33 -04:00
Isaac Abadi
0a38b01971 Updated posts service to allow for category deletion and subscription retrieval based on name 2020-10-15 16:59:14 -04:00
Isaac Abadi
fe7303a191 Replaced /audio and /video APIs with /stream that now requires a type parameter to simplify future code changes
getSubscription can now accept a subscription name instead of just an ID

Added API call to delete a category

Categories can now have a custom path

Minor code cleanup
2020-10-15 16:57:45 -04:00
Isaac Abadi
dff4b141b0 Blobs are now only included in getAllFiles() if the config option for including thumbnail is set to true 2020-10-12 22:47:11 -04:00
Isaac Abadi
fed0a54145 Updated styling on edit category dialog 2020-10-12 22:46:23 -04:00
Tiger Oakes
2cf0c61fac Default booleans to false 2020-10-04 19:19:31 -07:00
Tiger Oakes
389c5b5df3 Take out import type 2020-10-04 19:19:31 -07:00
Tiger Oakes
d1311d00ea Use named arguments with download file 2020-10-04 19:19:18 -07:00
Tiger Oakes
1112548246 Commit api types 2020-10-04 19:19:18 -07:00
Tiger Oakes
70d1afce76 Move user schemas so they can be imported 2020-10-04 19:19:18 -07:00
Tiger Oakes
fe7a3075d6 Rename last few active API routes 2020-10-04 19:19:18 -07:00
Tiger Oakes
4d74c375f4 Add playlist types 2020-10-04 19:18:57 -07:00
Tiger Oakes
62c79c267e Add additional types, mainly for subscriptions 2020-10-04 19:15:51 -07:00
Tiger Oakes
b667e1dc79 Add additional named types 2020-10-04 19:15:51 -07:00
Tiger Oakes
bce0115285 Generate types from OpenAPI 2020-10-04 19:15:51 -07:00
Isaac Abadi
8366089444 Added support for French, Chinese (Mandarin, simplified), and Norweigan. Updated German and Spanish translations
- Updated README to reflect new official translator
- XLIFFs to come later
2020-10-02 03:05:54 -04:00
Tzahi12345
44445f0b67 Added code analysis GH action 2020-10-01 14:46:04 -04:00
Isaac Abadi
7dcc38c26d Updated string in settings: "Select a logger level" -> "Log Level"
Made modify playlist component fully translatable

Fixed typo in cookies settings text
2020-09-30 04:52:43 -04:00
Isaac Abadi
79b4b993f8 Fixed bug in translation source file (2) 2020-09-30 04:41:15 -04:00
Isaac Abadi
37a19eabe6 Fixed bug in source translation file 2020-09-30 04:34:52 -04:00
Isaac Abadi
91b892b21a Updated source translation file 2020-09-30 04:19:04 -04:00
Tzahi12345
fb72dee26f Merge pull request #216 from NotWoods/await
Use async versions of filesystem methods
2020-09-29 17:41:19 -04:00
Tiger Oakes
3e4e7edd90 Oops.
for in -> for of
2020-09-29 14:32:28 -07:00
Isaac Abadi
b8280e8646 Updated spanish translation file (2) 2020-09-29 16:58:17 -04:00
Isaac Abadi
70ee071e57 Cleaned up spanish translation file 2020-09-29 14:41:38 -04:00
Tiger Oakes
e26ac82c66 Fix missing keywords 2020-09-29 08:53:36 -07:00
Isaac Abadi
cdd2f78998 Fixed bug that prevented video playlists from being deleted 2020-09-27 05:05:45 -04:00
Tiger Oakes
21eafeab22 Make utils.recFindByExt and utils.getDownloadedFilesByType async 2020-09-26 15:24:41 -07:00
Tiger Oakes
f535d18cb9 Use async methods in auth and subscriptions 2020-09-26 15:14:37 -07:00
Tiger Oakes
2c43ce3c47 Use async versions of filesystem methods 2020-09-26 14:57:23 -07:00
Isaac Abadi
3d2d4efb31 Added context menu on right click of the unified file cards, with options to open a file in the player or do so in a new tab 2020-09-26 03:00:26 -04:00
Isaac Abadi
10922fedd7 Fixed bugs that prevented subscription videos from being downloaded and non-users from accessing shared videos 2020-09-26 00:29:13 -04:00
Isaac Abadi
96cf1b87d1 Fixed bug in subscriptions that caused audio files to be downloaded as webm 2020-09-26 00:08:22 -04:00
Tzahi12345
6bed5851ed Merge pull request #220 from Tzahi12345/fix-playlist-downloading-bug
Fixed bug that preventing playlists from being downloaded a zip
2020-09-26 00:04:43 -04:00
Isaac Abadi
6717a59422 Fixed bug that preventing playlists from being downloaded a zip 2020-09-24 02:26:58 -04:00
Isaac Abadi
899633e124 Fixed bug that showed users their subscription videos after subscriptions were disabled 2020-09-20 23:13:56 -04:00
Isaac Abadi
8fdc231f08 Updated new home page UI to support file manager disabling and permissions
- file manager enabled state is now cached for faster loading
2020-09-18 11:22:45 -04:00
Isaac Abadi
ae8f7a2a33 Fixed bug that prevented playlists from being navigated to 2020-09-18 11:05:13 -04:00
Tzahi12345
d0782bb444 Update README.md
Updated API docs

Fixes #213
2020-09-18 00:46:42 -04:00
Isaac Abadi
49210abb49 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-09-17 03:15:29 -04:00
Isaac Abadi
8595864118 Added basic categorization functionality in the server & UI 2020-09-17 03:14:24 -04:00
Isaac Abadi
851bfb81ba File cards are now properly centered 2020-09-17 03:12:09 -04:00
Isaac Abadi
35d0d439fa Control-clicking file cards will now open the player in a new tab 2020-09-17 03:11:52 -04:00
Tzahi12345
ded3ad6dfc Merge pull request #212 from Tzahi12345/dependabot/npm_and_yarn/backend/node-fetch-2.6.1
Bump node-fetch from 2.6.0 to 2.6.1 in /backend
2020-09-12 17:07:53 -04:00
dependabot[bot]
61daf26641 Bump node-fetch from 2.6.0 to 2.6.1 in /backend
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-12 20:55:44 +00:00
Tzahi12345
95e53b9549 Fixed bug where unix paths would improperly parsed while importing unregistered files 2020-09-07 16:06:25 -04:00
Tzahi12345
46ed0fe992 Fixed bug in import unregistered logic where files in subfolders could not be found 2020-09-07 00:39:27 -04:00
Isaac Abadi
082252ab1e Updated sidenav logic for "side" mode, where it will now autoclose in the player, be open everywhere else 2020-08-31 15:21:58 -04:00
Tzahi12345
5eccaa13e5 Merge pull request #206 from Tzahi12345/downloader-improvements
Downloader improvements - updated system and bug fixes
2020-08-31 15:17:40 -04:00
Isaac Abadi
71633950b2 Comments cleanup 2020-08-31 15:03:04 -04:00
Isaac Abadi
f31dad0215 JSON metadata files are no longer kept if the associated setting is not enabled 2020-08-30 05:56:25 -04:00
Isaac Abadi
7efbe40bb2 Added setting for including metadata/thumbnails in the UI 2020-08-30 05:55:50 -04:00
Isaac Abadi
5b768b5bda JSON blobs were accidentally inserted into DB, stringifying then parsing the video file object fixes this 2020-08-30 05:42:52 -04:00
Isaac Abadi
365cbc3ffa Mkv/webm formats are now included for quality select (will get merged into mp4 at the end) 2020-08-29 23:08:23 -04:00
Isaac Abadi
44647f3306 Download progress is now shown when downloads are 1% complete or more (it was 15% before) 2020-08-29 23:06:40 -04:00
Isaac Abadi
8a7409478a Added the ability to download videos at higher resolutions than the highest mp4 (fixes #76)
Deprecates normal downloading method. The "safe" method is now always used, and download progress is now estimated using the predicted end file size

Thumbnails are now auto downloaded along with the other metadata
2020-08-29 23:05:37 -04:00
Tzahi12345
70159813e5 Merge pull request #205 from Tzahi12345/add-ldap-auth
Added ability to register/login through LDAP
2020-08-26 04:30:43 -04:00
Isaac Abadi
babba9aa30 Added ability to register/login through LDAP
- Added ability to edit LDAP settings and whether to use LDAP or not in the users tab in the settings
2020-08-26 04:18:29 -04:00
Tzahi12345
d292275956 Unfinished subscriptions will no longer cause an error during server startup 2020-08-24 05:13:27 -04:00
Tzahi12345
ba2acedb94 Files are now reloaded when you navigate back home 2020-08-24 05:13:01 -04:00
Tzahi12345
aa0558b770 Subscriptions are now reloaded on subscribe/unsubscribe in PostsService 2020-08-24 05:11:56 -04:00
Tzahi12345
d7f04fc90a Text for file duration in the unified file card component is now always black 2020-08-24 05:11:04 -04:00
Tzahi12345
087c9f1bb1 Added public directory to the gitignore 2020-08-24 02:44:52 -04:00
Tzahi12345
f874617965 Fixes bug where cached JWT token could prevent default admin creation 2020-08-24 02:44:39 -04:00
Tzahi12345
8fb8543829 Merge pull request #203 from Tzahi12345/arm-autobuild-test
Fix ARM autobuild
2020-08-24 02:20:19 -04:00
Tzahi12345
70d89d310c Removed unneeded hooks 2020-08-24 02:18:39 -04:00
Tzahi12345
c48aaaf13c Possible fix for arm autobuild (2) 2020-08-24 00:25:59 -04:00
Tzahi12345
6cf7ea193a Possible fix for arm autobuild 2020-08-24 00:21:10 -04:00
Isaac Abadi
c9016f446d Ghosting colors for loading videos is now customizable in an internal config, allowing dark mode to have different colors for the ghosting 2020-08-22 18:55:24 -04:00
Isaac Abadi
919e2a649a Updated styling of recent videos
Added content loader module which will add ghosting to the recent videos component while the files are loading

Updated custom playlists component to support large sized cards
2020-08-22 18:34:28 -04:00
Isaac Abadi
dc6dd5f5a2 Added support for "large" sized cards 2020-08-22 00:47:18 -04:00
Tzahi12345
2e4ef3b224 Moves hooks back into their proper directory and updated arm dockerfile to avoid installing curl just go get qemu 2020-08-18 16:43:52 -04:00
Tzahi12345
0e35b2ca1b Proxy now forces safe download mode 2020-08-18 15:34:03 -04:00
Isaac Abadi
9937bc9bb6 Updated ARM dockerfile to fix auto build 2020-08-15 18:31:57 -04:00
Tzahi12345
178a61c381 Merge pull request #198 from Tzahi12345/docker-frontend
Rework Dockerfile to build the frontend (with improved frontend build)
2020-08-15 15:38:34 -04:00
Isaac Abadi
b455d8a900 ARM dockerfile now builds frontend using Angular CLI 2020-08-15 14:07:06 -04:00
Isaac Abadi
72fa439569 Dockerfile now uses angular CLI directly for building process 2020-08-15 13:43:14 -04:00
Sandro Jäckel
33b1affa73 Rework Dockerfile to build the frontend
which removes the commited build files from the repo
2020-08-15 18:32:57 +02:00
Isaac Abadi
da73e47f08 Fixes bug where an error would occur on startup if a user's file folder was missing (such as "{user}/audio" if they had not downloaded any audio files) 2020-08-14 20:04:40 -04:00
Tzahi12345
3faf715b88 Merge pull request #196 from Tzahi12345/remove-use-encryption-settings
Removed "use encryption" options
2020-08-14 18:01:23 -04:00
Isaac Abadi
eda75e9a19 Updated docs and removed deprecated encrypted.json file from repo 2020-08-14 18:00:14 -04:00
Isaac Abadi
560aaadca1 Removed "use encryption" options, if you'd like to encrypt your web page, use a reverse proxy 2020-08-14 17:56:44 -04:00
Isaac Abadi
eb7fdb649d Updated frontend binaries 2020-08-14 17:48:51 -04:00
Isaac Abadi
533dd49ed0 Subscription buttons are now fixed 2020-08-14 17:42:51 -04:00
Isaac Abadi
001d907c3a Fixed UI bug where quality options spinner would clip through the URL input card 2020-08-14 17:42:31 -04:00
Isaac Abadi
4302625858 Updated styling of recent videos component, the search/sort options now look good on mobile 2020-08-14 17:41:55 -04:00
Isaac Abadi
e2cec9321e Importing of videos during startup now uses standard registering of videos into db process and refactored registering to support aforementioned feature
Removed erroneous console log
2020-08-14 02:44:27 -04:00
Tzahi12345
2add31af6d Merge pull request #195 from Tzahi12345/import-files-on-startup
Import files on startup
2020-08-14 02:18:04 -04:00
Isaac Abadi
5de37f6fbf Importing of unregistered files now happens on startup
recFindByExt in app.js removed, now uses utils.recFindByExt

Minor code cleanup
2020-08-14 02:10:40 -04:00
Isaac Abadi
7aace85ef4 Added ability to import unregistered files into the db if they are missing from the db but exist in their expected folder 2020-08-14 02:08:51 -04:00
Isaac Abadi
736c3f5cab Added ability to discover existing files regardless of type in a directory
- added recFindByExt helper function to utils.js
2020-08-14 02:07:39 -04:00
Isaac Abadi
c3c7667c17 Added quotations to existing sub error message 2020-08-13 18:34:57 -04:00
Isaac Abadi
52bee8b280 Subscriptions with the same URL can now be added as long as the new subscription is named 2020-08-13 18:34:30 -04:00
Isaac Abadi
945ba268fb Fixed bug where non-shared videos could be viewed by others
Fixed bug where non-users couldn't download a shared video
2020-08-12 16:23:28 -04:00
Isaac Abadi
d49a67dfd0 Updated styling for videos in video player 2020-08-11 21:04:28 -04:00
Isaac Abadi
96c52f2d5b Fixed bug where subscription videos could not be downloaded from the player 2020-08-11 14:45:37 -04:00
Isaac Abadi
61c03b6681 Sidepanel defaults to open when in 'side' mode AND in the home page 2020-08-11 01:25:33 -04:00
Isaac Abadi
7c349163b6 Updated max height for video player 2020-08-10 14:37:52 -04:00
Isaac Abadi
a0acdd1d86 Updated frontend binaries 2020-08-10 14:34:12 -04:00
Isaac Abadi
25bfb9e518 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-08-10 14:27:46 -04:00
Isaac Abadi
c9b615c659 Added option to change JWT expiration time called "Login expiration" in the Advanced tab 2020-08-10 14:26:59 -04:00
Tzahi12345
c4f21dc1cc Merge pull request #192 from Tzahi12345/homepage-redesign
Homepage redesign (v1)
2020-08-09 21:01:00 -04:00
Isaac Abadi
e678f4b476 Rebuilt frontend binaries 2020-08-09 21:00:10 -04:00
Isaac Abadi
356f31343e Adjusted positioning of add playlist button on home screen 2020-08-09 20:47:23 -04:00
Isaac Abadi
38c46b5be5 Updated frontend binaries 2020-08-09 20:43:18 -04:00
Isaac Abadi
12dcdfcb3c Sidepanel mode and card size is now configurable and can be set from the about dialog (temp location) 2020-08-09 20:39:59 -04:00
Isaac Abadi
47c19c0cdc Updated player styling 2020-08-09 20:17:01 -04:00
Isaac Abadi
d0eff42f2a Removed unneeded comments 2020-08-09 20:16:49 -04:00
Isaac Abadi
4472aae3e9 Removed redundant header in main component 2020-08-09 20:16:27 -04:00
Isaac Abadi
c55d3de9a0 Recent videos component now includes header, search, and sort capabilities 2020-08-09 20:16:10 -04:00
Isaac Abadi
1cdc1640ac Unified file card now supports playlists
Added custom playlists component

Removed legacy file manager from home screen
2020-08-09 19:24:29 -04:00
Isaac Abadi
fcaf8b5a62 Updated create playlist dialog to not require a type to be set prior
- duration and registered variables are now set for playlists
2020-08-09 19:23:29 -04:00
Isaac Abadi
8c7b2dfc79 Added ability to delete files in the recent videos component w/ archive support 2020-08-09 14:08:22 -04:00
Isaac Abadi
59ad74ed79 Fixed bug where subscriptions may register the same file multiple times 2020-08-09 14:07:36 -04:00
Isaac Abadi
690871f6b2 Moved hooks to their proper directory 2020-08-08 16:55:04 -04:00
Isaac Abadi
5088ce0291 Potentially fixed autobuild issue for ARM images 2020-08-08 16:02:41 -04:00
Isaac Abadi
576e2109d7 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-08-08 16:00:09 -04:00
Isaac Abadi
c6c80579df Updated package.json to add ngx-avatar dependency 2020-08-08 15:59:44 -04:00
Isaac Abadi
0ab6535fec Added ability to download files for recent videos component
Updated styling for unified file card (elevation on hover)
2020-08-08 15:59:29 -04:00
Isaac Abadi
d7aa39599d Removed subscriptions_use_youtubedl_archive setting, to use youtube-dl archive functionality, there is now just one setting for both subscription and non-subscription videos 2020-08-08 15:58:48 -04:00
Tzahi12345
ee169cd7ce If chown fails during container setup, then a warning will be shown 2020-08-05 21:29:03 -04:00
Tzahi12345
835790e69c Chown that fails will not crash startup anymore for Docker 2020-08-03 20:07:02 -04:00
Isaac Abadi
68037613d8 Added icon for file type (audio/video) next to the download date 2020-08-02 18:25:41 -04:00
Isaac Abadi
3df384de22 Recent videos now supports 2 card sizes 2020-08-02 18:03:50 -04:00
Isaac Abadi
f0c4ed4590 Unified file card now supports small and medium size
Duration styling/position updated and added download date time
2020-08-02 18:03:29 -04:00
Isaac Abadi
fd35153721 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into homepage-redesign 2020-08-02 16:24:07 -04:00
Tzahi12345
d0aed8f144 Merge pull request #184 from Tzahi12345/edit-subscriptions
Adds ability to edit subscriptions
2020-08-01 21:31:46 -04:00
Isaac Abadi
d17b68d76e Updated frontend binaries 2020-08-01 21:28:49 -04:00
Isaac Abadi
a481869166 Slightly update subscribe dialog styling 2020-08-01 21:28:22 -04:00
Isaac Abadi
eb4ed32fcb Added edit button to subscription 2020-08-01 21:27:35 -04:00
Isaac Abadi
9aee6e91cd Added API to update subscription
Edit subscription component now works
2020-08-01 21:26:47 -04:00
Isaac Abadi
37d3e9326c Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into edit-subscriptions 2020-08-01 20:10:47 -04:00
Isaac Abadi
dbf8f9ebfd Updated package-locks 2020-08-01 20:09:29 -04:00
Tzahi12345
7858e26b15 Update README.md
Updated Docker section in README to show how to use a custom UID/GID
2020-08-01 14:06:47 -04:00
Isaac Abadi
057ad67672 Fixed bug where subscribing to a private playlist failed 2020-08-01 06:55:25 -04:00
Isaac Abadi
3ebc903ce9 Fixed bug where non-admins in multi user mode could change the settings if they could open the dialog 2020-07-27 22:09:15 -04:00
Isaac Abadi
21c5795f1c Rebuilt frontend binaries 2020-07-27 22:02:23 -04:00
Isaac Abadi
f12ea017bc Re-added parameter to checkAdminCreationStatus 2020-07-27 22:01:47 -04:00
Isaac Abadi
cd18bce509 Fixed bug where settings menu could be accessed from the login menu in multi user mode 2020-07-27 21:55:01 -04:00
Isaac Abadi
5ef4388d73 Fixed bug where checking admin creation status would not run 2020-07-27 21:51:31 -04:00
Isaac Abadi
a78c0cb56c Updated frontend binaries 2020-07-25 16:13:09 -04:00
Isaac Abadi
ae9e4e6857 Updated styling in settings and about page for a cleaner look 2020-07-25 16:02:36 -04:00
Isaac Abadi
333556c305 Removed erroneous code and added the ability to kill all downlaods 2020-07-25 15:57:10 -04:00
Isaac Abadi
c5b0a7a697 Updated confirm dialog to support async requests with loading spinner 2020-07-25 15:55:52 -04:00
Isaac Abadi
d7631360cc Erroneous quality args are no longer added when using then normal downloading method 2020-07-23 23:29:05 -04:00
Isaac Abadi
c800308b9d Removed all instances of res.end() as it caused errors on debian-based systems and is a redundant call 2020-07-22 21:58:35 -04:00
Isaac Abadi
c1c57135ba Fixed bug where deleting an audio file would result in an error 2020-07-22 21:57:09 -04:00
Isaac Grynsztein
8384b73c4c Added support for navigating to files in recent videos (subscription and not). No support for download-only mode yet
Added navigate to subscription menu item for the files

Sidenav mode is "side" now for testing, likely not a permanent change and will be optional in the future
2020-07-18 20:37:43 -04:00
Isaac Grynsztein
834ac00694 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-07-15 23:48:34 -04:00
Isaac Grynsztein
935ae3452c Repo cleanup 2020-07-15 23:46:25 -04:00
Isaac Grynsztein
cc189a3abd Unified videos videos are now properly retrieved from the server 2020-07-15 23:37:00 -04:00
Isaac Grynsztein
335b588c3a Added edit subscription dialog (WIP) 2020-07-15 22:58:17 -04:00
Tzahi12345
493abc3a4c Merge pull request #170 from Tzahi12345/dependabot/npm_and_yarn/backend/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /backend
2020-07-15 17:37:18 -04:00
dependabot[bot]
238abc1686 Bump lodash from 4.17.15 to 4.17.19 in /backend
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-15 21:35:09 +00:00
Tzahi12345
fbfad6c3e2 Merge pull request #168 from fluxtendu/master
Switch from bcrypt to bcryptjs to keep compatibility with Raspberry Pi and a Dockerfile-armhf bonus
2020-07-15 17:34:34 -04:00
fluxtendu
a6ff65f004 Merge branch 'master' of github.com:fluxtendu/YoutubeDL-Material 2020-07-15 00:02:06 +02:00
fluxtendu
89cd969fcb Adapted for Raspberry Pi:
- Added Dockerfile-armhf (arm32v7 alpine)
 - Switched from bcrypt to bcrypt.js

new file : Dockerfile-armhf
modified : authentication/auth.js
modified : package.json
2020-07-14 23:00:08 +02:00
Isaac Grynsztein
4ebb2d4297 Created unified file card component, recent videos component (not done) and started scaffolding work on the backend 2020-07-13 22:09:48 -04:00
Isaac Grynsztein
d371ccf094 Fixed bug in about page where "You can update from the settings menu" was erroneously displayed 2020-07-12 03:41:23 -04:00
Tzahi12345
e7181b57c7 Update README.md
Updated contributing section to include the Contributing wiki page
2020-07-11 22:03:13 -04:00
Tzahi12345
38fa39d765 Merge pull request #159 from UnlimitedCookies/patch-1
Make more items translatable
2020-07-11 19:26:16 -04:00
UnlimitedCookies
9e5de88675 Even more internationalization improvements :) 2020-07-10 15:00:54 +02:00
Isaac Grynsztein
9cf4949c30 Updated frontend binaries 2020-07-09 22:37:24 -04:00
Isaac Grynsztein
dd80c51f16 Removed pin setting functionality
- Simplifies security options: use multi user mode if you want to restrict access to the settings menu
2020-07-09 22:33:07 -04:00
UnlimitedCookies
84d83f228e Make close button in About Dialog translatable 2020-07-10 01:31:59 +02:00
Isaac Grynsztein
14bd82c508 Fixed bug where navigating home would reset the cached volume setting 2020-07-07 15:27:15 -04:00
Isaac Grynsztein
0b6606cafb Updated spanish translations 2020-07-05 22:18:08 -04:00
Isaac Grynsztein
e97e9ec717 Logs viewer will now color-code logs based on type (error, warning, info, etc.)
You can also clear logs from the logs viewer as well
2020-07-05 22:17:54 -04:00
Isaac Grynsztein
990b3d4037 Added confirm dialog component to help with confirming actions 2020-07-05 22:12:07 -04:00
Isaac Grynsztein
2e7b1c2d53 Fixed wording for youtube-dl archive setting in the downloader tab 2020-07-04 21:34:47 -04:00
Isaac Grynsztein
a4eb7fb745 Updated README to reflect new changes to Docker installation process and removed config items due to clutter and out-of-dateness 2020-07-04 12:17:16 -04:00
Isaac Grynsztein
2442067ca0 Manually updated spanish translations 2020-07-04 12:07:22 -04:00
Isaac Grynsztein
41d4dfeba1 Updated version to 4.1 2020-07-03 17:57:47 -04:00
Isaac Grynsztein
a9f197e46d Updated logs viewer component
- now by default last 50 lines are showed
- added copy to clipboard button
- added loading spinner to indicate to users when the logs are loading

app.get('/api/logs') is now app.post to allow for additional parameters (such as lines to retrieve)
2020-07-03 03:46:58 -04:00
Isaac Grynsztein
3732d13562 Implemented greater transparency for login/registration errors on frontend 2020-07-02 17:42:05 -04:00
Isaac Grynsztein
cf14880d21 Empty URL setting will result in the default being applied 2020-07-01 23:20:11 -04:00
Isaac Grynsztein
e81d0cab42 Fixed bug where changing a user's password would change the admin's password 2020-07-01 17:26:32 -04:00
Isaac Grynsztein
7e24180f03 Fixed bug in globalArgsRequiresSafeDownload function 2020-06-30 23:03:07 -04:00
Isaac Grynsztein
053c8db9dd Fixed bug where config api would call itself 2020-06-30 22:55:08 -04:00
Isaac Grynsztein
5537852134 Deleting a file will now delete its downloaded thumbnail as well
Thumbnails will now have their permissions auto updated to align themselves with the other downloaded files
2020-06-30 22:38:01 -04:00
Isaac Grynsztein
efdc471ccf Fixed bug where if multi-user mode was enabled, old subscriptions would keep downloading and vice versa 2020-06-29 23:23:31 -04:00
Tzahi12345
6f1b37d5eb Merge pull request #149 from Tzahi12345/player-improvements
Playlist and player improvements
2020-06-29 20:30:31 -04:00
Isaac Grynsztein
06557673a2 Updated frontend binaries 2020-06-29 20:20:07 -04:00
Isaac Grynsztein
c20d09e902 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into player-improvements 2020-06-29 20:11:20 -04:00
Isaac Grynsztein
a68ecfa730 Modifying playlist in dialog will now update the file manager automatically 2020-06-29 20:06:37 -04:00
Isaac Grynsztein
86c609c1b2 Player component now remembers previously set volume
Updated name of updatePlaylist->updatePlaylistFiles for clarity and added updatePlaylist route

Added smarter safe download override, will auto activate if subtitle args are included.
2020-06-29 19:39:47 -04:00
Isaac Grynsztein
d100e80ccf Added ability to clear all downloads in a session 2020-06-29 19:35:59 -04:00
Isaac Grynsztein
5511c94071 Added modify playlist component 2020-06-29 19:35:34 -04:00
Isaac Grynsztein
b21886d8f8 Rebuilt frontend binaries and included Deutsch translation in public directory 2020-06-29 18:45:40 -04:00
Tzahi12345
e535603103 Merge pull request #145 from UnlimitedCookies/master
Add German Translation
2020-06-29 18:21:06 -04:00
UnlimitedCookies
9415901f17 Revert to 1:1 translation 2020-06-29 18:58:46 +02:00
UnlimitedCookies
92e5716f93 More clarification 2020-06-29 17:03:07 +02:00
UnlimitedCookies
5b5c93f783 Minor changes to increase coherence 2020-06-29 16:42:05 +02:00
UnlimitedCookies
4db6a49df5 Make custom Arg description more clear
Co-authored-by: Sandro <sandro.jaeckel@gmail.com>
2020-06-29 16:12:51 +02:00
Isaac Grynsztein
3abb29eee4 Removed exe binaries from repo 2020-06-29 02:28:00 -04:00
Isaac Grynsztein
e5461de2f7 Ignore youtube-dl cookies and executable files from the repo 2020-06-29 02:26:55 -04:00
UnlimitedCookies
4a69a0d362 modified: src/app/settings/settings.component.ts
new file:   src/assets/i18n/messages.de.json
	new file:   src/assets/i18n/messages.de.xlf
2020-06-29 03:48:29 +02:00
Tzahi12345
97f7f0b462 Merge pull request #141 from web-connect/feature/remove_whitespace
Removing extra white spaces
2020-06-27 12:43:44 -04:00
Tzahi12345
d3cbfa265e Merge pull request #140 from web-connect/feature/readme_update
Updating README regarding output dir
2020-06-27 12:42:30 -04:00
Justin Turner
42bd219ed6 Removing extra white spaces 2020-06-27 01:09:41 -05:00
Justin Turner
f8123cf03b Updating output dir 2020-06-27 00:55:55 -05:00
Isaac Grynsztein
94df98e5d0 Fixed bug that prevented subscription archives from being downloaded if their path was express as a full path 2020-06-23 00:01:23 -04:00
Isaac Grynsztein
2998562655 Added the ability to view logs from the settings menu 2020-06-22 23:15:21 -04:00
Tzahi12345
09d8ce04d7 Merge pull request #128 from web-connect/bug/i127_nan_not_found
Fixes #127 by adding nan to dependencies
2020-06-22 18:33:35 -04:00
Tzahi12345
504c818c2f Merge pull request #136 from Tzahi12345/subscriptions-custom-path
Subscriptions V3
2020-06-22 00:11:12 -04:00
Isaac Grynsztein
ca0e6b993d Re-compiled frontend 2020-06-22 00:03:42 -04:00
Isaac Grynsztein
0346833c3b Merged changes from master 2020-06-21 23:54:18 -04:00
Isaac Grynsztein
32da9dd9dd format in custom args for subscriptions now overrides default format (allows for users to specify custom formats for subs) 2020-06-21 23:49:00 -04:00
Isaac Grynsztein
20f162d794 Added args modifier dialog to custom args input in the subscribe dialog 2020-06-21 23:40:39 -04:00
Isaac Grynsztein
319bb0160b Finished adding support for audio subscriptions, custom args for subscriptions, and custom output for subscription downloads 2020-06-21 23:27:14 -04:00
Tzahi12345
5983a8bd52 Merge pull request #129 from web-connect/feature/i100_UI-typo-for-logger-level
#100 Typo fix for logger
2020-06-13 16:56:02 -04:00
Justin Turner
49b8cd416e Typo fix for logger 2020-06-13 13:39:42 -05:00
Justin Turner
58f71469b5 Fixes #127 by adding nan to dependencies 2020-06-13 01:21:03 -05:00
Tzahi12345
db81120645 Added audioOnlyMode, customArgs, and customFileOutput fields to the subscribe dialog 2020-06-12 17:57:34 -04:00
Tzahi12345
163a88bcfd DB implementation of subs now can properly delete subs 2020-06-10 21:41:05 -04:00
Tzahi12345
2441270d88 Removed redundant redirect when in the login screen
Fixed bug that prevented user registration with a faulty token
2020-06-09 21:19:42 -04:00
Tzahi12345
a518ac680f Fixed bug that prevented new users from accessing the login screen 2020-06-09 18:12:55 -04:00
Tzahi12345
78d3145e0b Deleting a video with an extension in the filename will now work UI-side 2020-06-09 18:02:46 -04:00
Tzahi12345
b8a4e0773f Added new utils.js module to assist backend with shared helper functions
Subscription files are now stored in the database, and will be primarily managed through it
2020-06-09 18:02:25 -04:00
Tzahi12345
f04139634a Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into subscriptions-custom-path 2020-06-08 21:24:46 -04:00
Tzahi12345
a074166903 Added catch statement if youtube-dl tags could not be retrieved 2020-06-08 13:04:16 -04:00
Tzahi12345
6893dbd506 Merge pull request #118 from Tzahi12345/Windows-build-fix-for-new-dockerfile
Updated Dockerfile to support Windows builds
2020-06-06 13:08:45 -04:00
Tzahi12345
e8ee4ffb64 Made additional cleanups as per recs by SuperSandro 2020-06-06 13:07:50 -04:00
Tzahi12345
378025bd9d Updated dockerfile to support Windows builds 2020-06-03 20:56:48 -04:00
Tzahi12345
d8e85df6d6 Scaffolding for registering subscription downloads 2020-06-03 19:18:10 -04:00
Tzahi12345
0c864c3d8d Merge pull request #117 from SuperSandro2000/docker-fix
Docker: Fix startup error in entrypoint, ownership of node_modules
2020-06-03 09:46:58 -04:00
Sandro Jäckel
dd8ab9be29 Fix default uid/gid of node_modules 2020-06-03 07:12:14 +02:00
Sandro Jäckel
bab354ce81 Fix variable expansion 2020-06-03 07:11:30 +02:00
Tzahi12345
d3d0f92ea5 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into subscriptions-custom-path 2020-06-02 23:24:54 -04:00
Tzahi12345
b37d912e04 Merge pull request #116 from SuperSandro2000/docker-non-root
Run docker as non root
2020-06-02 21:47:22 -04:00
Tzahi12345
1c93a4f9f2 Updated frontend files to support video sharing with non-users in multi user mode 2020-06-02 20:09:40 -04:00
Tzahi12345
abfe0dad03 Prevents login redirect for shared videos in multi user mode 2020-06-02 20:03:01 -04:00
Sandro Jäckel
5bfecfcefe Run docker as non root, copy package-json.lock 2020-06-03 01:53:06 +02:00
Tzahi12345
ffe3133635 Merge pull request #115 from SuperSandro2000/alpine-12
Docker: Update Alpine to 3.12
2020-06-02 17:43:57 -04:00
Sandro Jäckel
68c67ca7d5 Update alpine to 3.12 2020-06-02 23:34:05 +02:00
Sandro Jäckel
c4d50c9018 Format 2020-06-02 23:32:16 +02:00
Isaac Grynsztein
42b749a101 Updated frontend binaries 2020-05-30 16:39:11 -04:00
Isaac Grynsztein
9c729abfaa Added new safe download override setting to config manager (forgot to do this before) 2020-05-30 16:30:28 -04:00
Isaac Grynsztein
dcc7fbd81c Added new setting to force a safe download (removes features like progress bar) 2020-05-30 16:28:00 -04:00
Isaac Grynsztein
b3c8f9e57a Fixed bug that caused downloads to fail when archiving was enabled
Removed error message on URL input on the home page

Fixed bug that prevented file deletion in multi user mode with archiving enabled
2020-05-30 16:20:03 -04:00
Isaac Grynsztein
80f214fdde Fixed bug that caused non-YT videos to be downloading using the best format 2020-05-27 00:12:17 -04:00
Isaac Grynsztein
57a9434b3c File cards now include the video's real ID (for YT videos). Otherwise the file name will be used as a fallback 2020-05-25 16:51:49 -04:00
Isaac Grynsztein
cec0ed78ec Fixed bug that prevented user registration from occuring, and added new "Login" option to the hamburger menu when appropriate
Related: made it so non-logged in users (in multi user mode) don't have the option to go "home" or "subscriptions" or "downloads". It would error regardless, but it looks cleaner now
2020-05-25 16:35:02 -04:00
Isaac Grynsztein
c8a8046056 Fixed bug where if a subscription name was missing, the wrong folder was used
Fixed bug that caused high CPU usage when the subscriptions check took longer than the interval in the settings (thus they piled on)
2020-05-25 15:35:49 -04:00
Tzahi12345
73cd142b77 Cookies now work in subscriptions, too 2020-05-19 21:52:45 -04:00
Tzahi12345
b071fb9e2e Merge pull request #94 from Tzahi12345/cookies-helper
Cookies upload dialog
2020-05-19 21:43:39 -04:00
Tzahi12345
f485da06b5 Implemented cookies upload dialog and the ability to "enable cookies" to hopefully circumvent 429 errors 2020-05-19 21:37:19 -04:00
Tzahi12345
59098d4693 Minor update to support alternate youtube links 2020-05-19 19:31:41 -04:00
Tzahi12345
4cf92b8f3d Fixed bugged override 2020-05-19 18:15:07 -04:00
Tzahi12345
e07acfd4b3 Added support for a popular adult website 2020-05-18 20:45:36 -04:00
Tzahi12345
05d962328b Fixed bug that prevented non-youtube videos from downloading 2020-05-18 19:32:00 -04:00
Tzahi12345
0816cb7046 Fixed bug that preventing reddit videos from downloading 2020-05-18 18:44:39 -04:00
Tzahi12345
c6553d99c6 Hotfix for bug that prevented large twitch videos from downloading 2020-05-10 05:20:53 -04:00
Tzahi12345
8bf3680b6f Fixed bug that prevented soundcloud audio files from downloading correctly 2020-05-10 04:56:21 -04:00
Tzahi12345
9e5ad66a9d Added scaffolding for custom paths in subscriptions 2020-05-10 04:53:49 -04:00
Tzahi12345
3487813cb5 Updated frontend files 2020-05-09 03:41:27 -04:00
Tzahi12345
ecc2737a05 Pressing enter on URL input now triggers the download 2020-05-09 03:34:59 -04:00
Tzahi12345
39e737024f Pressing enter on url input now triggers download 2020-05-09 03:03:37 -04:00
Tzahi12345
550013a2e7 Config file is now created when missing and set with default values 2020-05-08 14:28:19 -04:00
Tzahi12345
b6f8551cfa Updated docker-compose.yml to support multi-user mode 2020-05-06 02:49:18 -04:00
Tzahi12345
98e94d3c38 Updated frontend binaries 2020-05-06 02:46:44 -04:00
Tzahi12345
8c94255f61 Updated version number to 4.0 2020-05-06 02:43:46 -04:00
Tzahi12345
409fd0fe20 Updated translations and frontend binaries 2020-05-06 02:39:20 -04:00
Tzahi12345
d4ad1f9fce Added additional sentence to custom args hint and fixed issue where empty args could be added through the args modifier 2020-05-06 02:38:39 -04:00
Tzahi12345
cc47823b0c Updated gitignore and re-added package-lock.json
Translations updated
2020-05-05 20:12:15 -04:00
Tzahi12345
747735dffe Arg modifier chip list now supports auto complete and arg description as the chip tooltip
Fixed bug that caused custom args to reset after exiting arg modifier without hitting cancel
2020-05-05 20:11:23 -04:00
Tzahi12345
76b63329ca Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-05-05 19:32:08 -04:00
Tzahi12345
f094d18e03 Changed /api/changeUser endpoint to /api/updateUser 2020-05-05 19:31:47 -04:00
Tzahi12345
c1b0b583e4 Updated API to reflect changes for 4.0 2020-05-05 19:29:04 -04:00
Tzahi12345
a1c9c97616 Updated security schema in repository api docs 2020-05-05 16:47:28 -04:00
Tzahi12345
0504167734 Arg modifier improvements: args are now shown as removable chips which can be directly typed as well (w/o using the adder) 2020-05-04 15:39:33 -04:00
Tzahi12345
49081db8cb Config items are now checked on start. Missing ones will be autofilled with the default values automatically on startup 2020-05-04 05:18:06 -04:00
Tzahi12345
98e33ac399 Updated frontend files 2020-05-04 04:38:21 -04:00
Tzahi12345
a3424f973e Output on global args will now override specific output 2020-05-03 21:14:09 -04:00
Tzahi12345
8e5db3e9d1 Custom args and global custom args now use double comma as a delimiter. This should allow file names with spaces when using custom args (global and not) 2020-05-03 21:05:56 -04:00
Tzahi12345
1861011fb0 Cleaned up code and added missing translation units 2020-05-03 19:35:38 -04:00
Tzahi12345
74e47b7d04 Fixed bug that prevented audio files from being played after username change
Downloads with custom args or custom quality config now use the old downloader to ensure stability and prevent arg conflict
2020-05-03 19:34:01 -04:00
Tzahi12345
68f791eea3 Downloads now show new spinner after download completes, indicating processing has begun 2020-05-03 18:56:07 -04:00
Tzahi12345
f73ec2dd94 Fixed bug that caused users with large amounts of data to have extremely large tokens
Subfolders are now ensured to exist with the normal downloading method

Initialization now happens after token retrieval to avoid failed requests

Fixed bug that caused login to be called twice, introducing a possible race condition
2020-05-03 18:55:42 -04:00
Tzahi12345
26ad195597 Adds ability to set umask through an environment variable. Does not work on Windows, and it's untested on Linux 2020-05-03 13:40:54 -04:00
Tzahi12345
fb23d7c41e Audio downloads now work with progress bar, but it requires file conversion at the end. It ends up being around the same speed as the regular method 2020-05-03 03:24:25 -04:00
Tzahi12345
4e6d68d9e6 Updated video playing/sharing logic to support sharing of playlists in multi user mode and when multi user mode is disabled
Fixed bug that caused normal archive to be used in multi-user mode

Updated login logic when username is not found or user file is missing

Fixed bug that prevented playlist sharing from working

Added ability to use timestamps when sharing videos
2020-05-02 20:36:30 -04:00
Tzahi12345
8bc99fb557 Fixed bug that prevented registration from occuring 2020-05-02 17:25:35 -04:00
Tzahi12345
8277c95c4e Updated youtube-dl binary for windows 2020-05-02 17:10:08 -04:00
Tzahi12345
e5db376914 All config values are now reloaded on config set
Added 4 new settings: user files folder, enable registration, enable downloads manager, and logging level selection
2020-05-02 17:09:46 -04:00
Tzahi12345
661b96cfe5 Fixed bug that prevented default config items to be set 2020-05-02 15:06:43 -04:00
Tzahi12345
2eef1b062c Updated frontend binaries 2020-05-01 16:26:14 -04:00
Tzahi12345
d376ee4409 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-05-01 16:18:47 -04:00
Tzahi12345
7661b1e79e Fixed bug that prevented admin creation prompt from occuring in the settings 2020-05-01 16:18:39 -04:00
Tzahi12345
0401769968 Merge pull request #69 from SuperSandro2000/patch-1
Update badge links
2020-05-01 14:54:26 -04:00
Tzahi12345
c901efebc4 Merge pull request #68 from SuperSandro2000/docker
Docker improvements
2020-05-01 14:49:32 -04:00
Sandro
4e550da4f6 Update badge links 2020-05-01 20:41:01 +02:00
Sandro Jäckel
ae76e9db8d Install dependencies first, remove duplicated workdir 2020-05-01 19:45:19 +02:00
Sandro Jäckel
d763f88ceb Remove comments 2020-05-01 19:40:35 +02:00
Sandro Jäckel
a8b188cd22 Don't create cache with apk, use cdn network 2020-05-01 19:40:02 +02:00
Sandro Jäckel
1034aa1980 Don't copy Docker related files into image 2020-05-01 19:39:34 +02:00
Tzahi12345
da26d88ba9 Updated frontend binaries 2020-05-01 03:55:17 -04:00
Tzahi12345
93e117a7aa Adds fingerprintjs2 as frontend dependency 2020-05-01 03:52:31 -04:00
Tzahi12345
ae9c52a14d Merge pull request #67 from Tzahi12345/multi-user-mode
Adds multi-user mode
2020-05-01 03:48:50 -04:00
Tzahi12345
b685b955df Added roles and permissions system, as well as the ability to modify users and their roles
Downloads manager now uses device fingerprint as identifier rather than a randomly generated sessionID
2020-05-01 03:34:35 -04:00
Tzahi12345
e7b841c056 Added UI flow for creating default admin account. Dialog will show up after enabling or in the login menu if the admin account isn't present 2020-04-30 16:31:36 -04:00
Tzahi12345
e5f9694da0 Fixed bug where downloading individual files failed for channel subscriptions 2020-04-30 13:29:47 -04:00
Tzahi12345
81b0ef4a72 Refactored initialization process to better facilitate auth if necessary
Date in user profile dialog now shows date
2020-04-30 13:28:58 -04:00
Tzahi12345
31f581c642 Subscriptions now support multi-user-mode
Fixed bug where playlist subscription downloads would fail due to a mislabeled parameter

Components that are routes now make sure auth is finished before sending requests to the backend
2020-04-30 04:54:41 -04:00
Tzahi12345
d2af233a1f Fixed bug that when multi-download mode was enabled, videos could not be navigated to 2020-04-29 23:09:00 -04:00
Adam Verga
0fb00bac12 Initialization on auth component happens in a separate function, users_db primarily sits in app.js
Fixed bug where current download would set to null, but maincomponent still tried to parse it
2020-04-29 20:46:29 -04:00
Adam Verga
6980828853 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into multi-user-mode 2020-04-29 20:16:42 -04:00
Adam Verga
a48e122763 Settings are now more centralized in the frontend 2020-04-29 20:15:15 -04:00
Tzahi12345
2d66d653f6 Updated frontend files to reflect commits since v3.6 2020-04-29 00:52:45 -04:00
Tzahi12345
03ea04f8d8 Fixed missing double ampersand 2020-04-29 00:40:14 -04:00
Tzahi12345
8fbb1c9bbd Fixed repo for atomicparsley on docker/alpine 2020-04-29 00:35:51 -04:00
Tzahi12345
c67d6ea89a Added atomicparsley as a dependency to Docker, and listed it as an optional dependency for normal installs 2020-04-29 00:27:39 -04:00
Tzahi12345
a701d0fe83 Fixes bug (hopefully) that causes stale data to be saved to the db due to multiple adapters instances being used. Now the db adapter gets passed as a parameter 2020-04-29 00:12:50 -04:00
Tzahi12345
ff51a49d1b Removed unused import 2020-04-29 00:04:04 -04:00
Tzahi12345
46b595db45 Update README.md
Updated API info
2020-04-27 04:55:46 -04:00
Isaac Grynsztein
4b2b278439 Sharing and video downloads on shared videos now work for multi-user mode 2020-04-27 04:31:39 -04:00
Isaac Grynsztein
1ac6683f33 Custom quality configurations now use the old downloading method to avoid errors
postsservice now does jwt auth after checking if multi user mode is enabled

Minor update to user profile UI

Added setting for enabling and disabling multi user mode
2020-04-26 21:37:08 -04:00
Isaac Grynsztein
e790c9fadf File descriptors are now stored in the config_api until they find a better home
File deletion now works in multi-user mode. Sharing and subscriptions are the last holdouts for porting over to multi-user-mode

Fixed bug with archive mode that defaulted to storing the ID in the video archive all the time (rather than audio if it's an mp3)
2020-04-26 18:33:23 -04:00
Isaac Grynsztein
c18bf568c7 Fixed bug that prevented video download or archive download from occuring 2020-04-26 17:41:54 -04:00
Isaac Grynsztein
fa1b291f97 Added video downloading functionality to multi user mode, as well as playlist management and saving of videos locally. Still missing video deletions and subscriptions
Simplified code for downloading videos to client (locally)
2020-04-26 17:40:28 -04:00
Isaac Grynsztein
cb6451ef96 Added new settings: multi user mode and users base path 2020-04-26 17:37:49 -04:00
Isaac Grynsztein
912a419bd4 Getting current download refactored to work and display less errors
Player component now sends jwt token if logged in
2020-04-26 17:34:38 -04:00
Isaac Grynsztein
a7c810136b Added basic user profile component 2020-04-26 17:33:29 -04:00
Isaac Grynsztein
e6ea2238f8 Fixed bug where HTTP headers were sent when params should have been sent instead
sessionID now gets sent after logging in
2020-04-26 17:32:50 -04:00
Isaac Grynsztein
98f1d003c3 Fixed bug that prevented migrations from succeeding
Added scaffolding required for jwt authentication for certain routes

Added logger to auth_api

Added necessary routing rules for multi-user mode

Registration is now possible
2020-04-24 21:03:00 -04:00
Isaac Grynsztein
c3cc28540f Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into multi-user-mode 2020-04-23 14:56:14 -04:00
Tzahi12345
68ed66072e Merge pull request #57 from Tzahi12345/current-downloads-refactor
Downloads system refactor
2020-04-22 23:15:26 -04:00
Isaac Grynsztein
eca06a7fb1 Downloads on the home page now show the progress bar 2020-04-22 21:42:21 -04:00
Isaac Grynsztein
b583305940 Downloads in the download manager now get updated smoothly, preventing the DOM from updating on object reassign 2020-04-21 20:10:53 -04:00
Isaac Grynsztein
f361b8a974 Furrther simplified download process and fixed a couple bugs
Audio files will not show download progress as enabling this feature causes it to be really slow

Fixed bug where downloading the same video twice produced duplicate files in the file manager
2020-04-21 18:56:52 -04:00
Isaac Grynsztein
1565c328d5 If a video is a playlist, it will download the normal way 2020-04-21 16:19:19 -04:00
Isaac Grynsztein
a6534f66a6 migrated audio file downloads to new system. still untested with playlists
video/audio player now doesnt show share button when uid isn't present, user will be notified of this through a snackbar as well
2020-04-21 03:16:39 -04:00
Isaac Grynsztein
a78ccefc83 Updated package.json 2020-04-20 18:40:21 -04:00
Isaac Grynsztein
6fe7d20498 downloads refactor half done - videos are now implement, but audo files are now
Added downloads manager in the UI where downloads can be viewed/cleared
2020-04-20 18:39:55 -04:00
Isaac Grynsztein
d887380fd1 Added new methods to facilitate server-side download management 2020-04-18 01:31:32 -04:00
Isaac Grynsztein
1f3572a630 jwt auth scaffolding
logging in now works

UI login component created
2020-04-16 22:35:34 -04:00
Isaac Grynsztein
da8571fb1a Added additional info when requests are rejected due to no auth
Added two additional auth methods: registering and logging in. They have minimal functionality right now

Added auth module which will handle all auth-related requests
2020-04-16 16:33:32 -04:00
Isaac Grynsztein
4617362270 New default youtube-dl.exe binary
Updated public dir in backend
2020-04-15 18:52:18 -04:00
Isaac Grynsztein
bdb5072014 API key is now passed as a query param 2020-04-15 18:46:13 -04:00
Isaac Grynsztein
e5baf094c9 chmodsync will not run if app is running on windows 2020-04-15 03:40:26 -04:00
Isaac Grynsztein
264b3606d6 Modified automatic permissions for json files 2020-04-15 03:36:56 -04:00
Isaac Grynsztein
2408184cc7 new video json files created now get read perms across the board 2020-04-15 03:19:28 -04:00
Isaac Grynsztein
e4851253dd Docker now ignores executable files 2020-04-15 02:15:15 -04:00
Isaac Grynsztein
87696f71f8 Added subscription folders to repo
Added .dockerignore to ignore node_modules

Removed unnecessary whitespace from docker-compose.yml
2020-04-15 02:10:12 -04:00
Isaac Grynsztein
d6fe2a5720 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-04-15 02:02:27 -04:00
Isaac Grynsztein
90c2d3f70b Moved docker files to backend 2020-04-15 02:02:14 -04:00
Isaac Grynsztein
0d54cb9872 Updated player view to prevent video from being too large 2020-04-15 02:02:03 -04:00
Isaac Grynsztein
a8d6298cfd Adds preliminary support for tiktok and periscope
Added alternate json path for mp4s in case it's not found in the main location
2020-04-15 02:01:25 -04:00
Tzahi12345
65b31633d9 Update README.md
Changed start command to use npm
2020-04-14 15:41:18 -04:00
Isaac Grynsztein
3db3077dec Fixed misspelling in docs 2020-04-13 16:01:37 -04:00
Tzahi12345
61633e817b Updated API docs 2020-04-13 15:59:46 -04:00
Tzahi12345
6cc93ba4f9 Update README.md
Added badges to README, additional links to reverse proxy info, and updated the API section.
2020-04-13 02:16:35 -04:00
Isaac Grynsztein
9b8b92b8df Added latest release to repo 2020-04-12 23:54:16 -04:00
Isaac Grynsztein
d9f6b8c64c Simplified archive creation for subscriptions to reduce risk of error
If no subscriptions have ever been made, "No channel/playlist subscriptions" text will now show
2020-04-12 21:00:47 -04:00
Isaac Grynsztein
10b59191f6 Updated public directory 2020-04-12 20:47:29 -04:00
Isaac Grynsztein
a89787698b Fixed version in package.json 2020-04-12 20:42:47 -04:00
Isaac Grynsztein
3d3ab5180f fixed bug that prevented non-api routes from loading without an auth header 2020-04-10 21:03:37 -04:00
Isaac Grynsztein
eddc25566d Updated behavior of file card deletion to prevent compilation error 2020-04-10 20:49:34 -04:00
Isaac Grynsztein
b5a82b9385 Updated middleware to support API tokens. Frontend now uses an admin token for its requests
Fixed version numbers
2020-04-10 20:44:42 -04:00
Isaac Grynsztein
2082a78846 Updated version number 2020-04-10 20:41:14 -04:00
Isaac Grynsztein
fe170a4de8 Updated public API link 2020-04-10 15:25:23 -04:00
Tzahi12345
18dab72b51 Updated public API 2020-04-10 15:24:26 -04:00
Isaac Grynsztein
6849bd00d5 Adding public API docs 2020-04-10 15:13:07 -04:00
Isaac Grynsztein
1e96e31053 Added new API key and using API key config items to enable a public API
API key config items are implemented UI-side

Added ability to generate API keys through the settings

Switched getmp3s and getmp4s api calls to be GET requests rather than POST

Removed unused code from settings dialog
2020-04-10 14:46:36 -04:00
Isaac Grynsztein
02441ac846 Fixed bug where docker would start building on certain systems when using docker-compose up, and refuse to pull with docker-compose pull 2020-04-10 01:33:36 -04:00
Tzahi12345
4666aa15b3 Merge pull request #49 from Tzahi12345/streaming-only-mode
Streaming-only mode
2020-04-09 23:41:44 -04:00
Isaac Grynsztein
f6f54c0e53 Fixed bug where video infos caused an error for streaming-only subscriptions 2020-04-09 23:41:21 -04:00
Isaac Grynsztein
e15141c5e0 Added backend and database support for video streaming
Added UI support for video streaming. branch is now feature-complete
2020-04-09 23:33:58 -04:00
Isaac Grynsztein
a61606b69f Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into streaming-only-mode 2020-04-09 22:29:56 -04:00
Isaac Grynsztein
d96fab49f5 added ability to use url in player component. streaming mode will need this 2020-04-09 22:29:23 -04:00
Isaac Grynsztein
346d41d3e1 Fixed bug that broke server backups 2020-04-09 01:57:43 -04:00
Isaac Grynsztein
597e1f5b60 Fixed bug that prevented updates from succeeding 2020-04-09 01:47:10 -04:00
Isaac Grynsztein
dd0f58d421 Fixed bug where updater kept asking for updates on the update after it completed 2020-04-09 01:36:46 -04:00
Isaac Grynsztein
2ca6aa7bd7 Video URL now wraps in video info dialog 2020-04-09 01:36:02 -04:00
Isaac Grynsztein
ba2b837cc5 Fixed accidentally commented out functionality to download new updates 2020-04-09 01:35:22 -04:00
Isaac Grynsztein
22f0ee834b backups now occur in appdata/backups folder 2020-04-08 15:58:29 -04:00
Isaac Grynsztein
cb02227302 fixed bug where recently saved playlists could not be shared 2020-04-07 22:08:18 -04:00
Isaac Grynsztein
1b4f2830f5 changed logging in backend to support extra logging in debug mode 2020-04-07 21:41:13 -04:00
Isaac Grynsztein
720fceefb6 Fixed bug where subscription videos could not be downloaded from the player 2020-04-07 19:16:15 -04:00
Isaac Grynsztein
fa488015c3 Minor import change 2020-04-07 18:43:27 -04:00
Tzahi12345
88f1b3daff Merge pull request #48 from Tzahi12345/video-sharing
Video sharing
2020-04-07 18:29:29 -04:00
Isaac Grynsztein
0ea8a11c85 Subscriptions now don't have a share button (it's broken anyways) 2020-04-07 18:26:47 -04:00
Isaac Grynsztein
14bf2248cf Added UI support for sharing videos 2020-04-07 18:19:12 -04:00
Isaac Grynsztein
822aec4de8 added API endpoint to get file from database
video/audio files can now be retrieved by just uid, allowing for easy sharing

added API endpoints for sharing/unsharing a video (no UI support yet)
2020-04-07 14:49:05 -04:00
Isaac Grynsztein
2a1aa4036c New checkbox to select streaming only mode when subscribing 2020-04-07 01:52:59 -04:00
Isaac Grynsztein
2414e16021 videos now deleted by UID ui-side 2020-04-07 01:52:22 -04:00
Isaac Grynsztein
69cd22d992 file deletions now remove the file from the db as well 2020-04-07 01:38:35 -04:00
Isaac Grynsztein
1905129201 getMp3s and getMp4s now have dedicated functions
downloaded files now get recorded in db.json. So when the server wants to get audio/video files, it doesn't need to recursively go through the respective folders each time
- getMp4s/getMp3s API request latency is reduced ~2x (130ms -> 60ms) in testing

Modified tomp3/tomp4 code to automatically add newly downloaded files to the db

Added a migration so users on 3.5 or below will get their files automatically added to the db on the first run

All these changes are necessary to enable easy sharing with features like timestamps
2020-04-07 00:00:25 -04:00
Isaac Grynsztein
7ef6c78612 merged new checkbox for bookmarklet: enables ability to set bookmarklet to audio only
fixed two bugs for audio only files: sometimes downloads failed as extensions were improperly removed and readded, removing a single character from the filename.

Fixed another extension-related bug where metadata from deleted audio files persisted
2020-04-05 15:19:12 -04:00
Tzahi12345
1d9595d056 Merge pull request #47 from Tzahi12345/better-settings-menu
Better settings menu
2020-04-04 23:35:13 -04:00
Isaac Grynsztein
d258bc2218 Updated appearance of settings menu to improve organization and performance 2020-04-04 23:29:42 -04:00
Isaac Grynsztein
4d3a687d34 Fixed bug where toggling dark mode using the toggle rather than the adjacent menu item caused visual errors 2020-04-04 23:28:34 -04:00
Tzahi12345
2b91293abd Merge pull request #45 from Tzahi12345/better-logging
Better logging system using winston
2020-04-02 23:16:31 -04:00
Isaac Grynsztein
3990e25c18 added logging to config api and subscriptions api, meaning the entire backend has the new logging system 2020-04-02 23:14:07 -04:00
Isaac Grynsztein
2f0bbca15c added better logging in app.js using winston 2020-04-02 23:05:17 -04:00
Tzahi12345
717f6deb11 Merge pull request #44 from Tzahi12345/auto-update-youtubedl-material
Ability to update YoutubeDL-Material
2020-04-02 21:56:44 -04:00
Isaac Grynsztein
c36867d368 Added progress bar to file downloads
Added two new API calls, to update the server to a particular version and to get the updater status

You can now update through the UI, and a status dialog displays after
2020-04-02 21:53:08 -04:00
Isaac Grynsztein
458e4b45f8 Removed @locl dependency for translations
Added CommonModule to fix intellisense

Added ability to load json assets by name, and an http call to update youtubedl-material
2020-04-01 19:44:22 -04:00
Isaac Grynsztein
c40513ba4a docker-compose now uses latest version tag 2020-04-01 19:42:44 -04:00
Isaac Grynsztein
6fa52cecbc Updated docker compose version 2020-04-01 19:42:21 -04:00
Isaac Grynsztein
a5474141bb Removed unused dependencies 2020-04-01 19:36:57 -04:00
Isaac Grynsztein
89ececdbeb Dependencies now install during update 2020-04-01 19:31:41 -04:00
Isaac Grynsztein
58718b6e3b Removed @ngular/http dependency 2020-04-01 01:11:26 -04:00
Isaac Grynsztein
a5224f80a8 nodemon now runs silently 2020-04-01 01:07:08 -04:00
Isaac Grynsztein
c2ee6b6230 update package.json version 2020-04-01 01:06:55 -04:00
Isaac Grynsztein
37614a1611 Changed backend logging for server start to give more information (namely version) 2020-03-31 04:20:46 -04:00
Isaac Grynsztein
b71bdfcec2 Updated nodemon package.json config 2020-03-31 04:19:42 -04:00
Isaac Grynsztein
1b09bf4881 nodemon now supported 2020-03-31 01:56:15 -04:00
Tzahi12345
82df232f03 Update process now properly gets required backend files 2020-03-30 23:36:35 -04:00
Tzahi12345
af4de44016 Further merge 2020-03-30 23:25:58 -04:00
Tzahi12345
61f27d6fe9 merged changes 2020-03-30 23:25:48 -04:00
Tzahi12345
b3dbdd1790 Cleaning working folder 2020-03-30 23:24:33 -04:00
Isaac Grynsztein
785306c59a Added debug statements 2020-03-30 23:20:52 -04:00
Tzahi12345
38774d8593 Updater now grab new backend files
youtube-dl auto updater now guesses binary path, which makes the update process work much more reliably
2020-03-30 23:15:31 -04:00
Isaac Grynsztein
df11aca1e0 Added preliminary support for updating YoutubeDL-Material
Config items that are not found use and set the default value

Fixed potential error while updated youtube-dl binaries
2020-03-30 18:35:44 -04:00
Isaac Grynsztein
bcff631936 Updated translations and 'backend/public' folder 2020-03-29 10:46:35 -04:00
Isaac Grynsztein
347df89aa7 Updated default title_top value in config 2020-03-29 10:45:31 -04:00
Isaac Grynsztein
eb084d03b2 Downloaded file names are now converted to their fully decoded forms 2020-03-28 08:04:45 -04:00
Isaac Grynsztein
8c942b0343 Updated top bar color 2020-03-27 20:02:18 -04:00
Isaac Grynsztein
baad97182a Updated version tag 2020-03-27 20:02:06 -04:00
Isaac Grynsztein
26ca5d51a5 Fixed about dialog translations 2020-03-27 16:59:50 -04:00
Isaac Grynsztein
0c5cd291fe Changed comment for clarity 2020-03-27 15:57:51 -04:00
Isaac Grynsztein
57234b4690 File card download progress bars now maintain same width as file cards, appear rounded at the bottom, and are positioned right at the bottom of each card 2020-03-27 15:56:34 -04:00
Isaac Grynsztein
b993c8e1d6 Fixed bug were downloadOnlyMode failed to work when multiDownloadMode was enabled 2020-03-27 15:54:14 -04:00
Isaac Grynsztein
66cb0af762 Settings dialog now says "cancel" when settings are changed and "close" otherwise 2020-03-27 13:56:55 -04:00
Isaac Grynsztein
8331c319ce File formats are not searched anymore if video is a playlist. Prior to this they simply errored 2020-03-27 13:55:50 -04:00
Isaac Grynsztein
da9dcc0249 Added border radius to progress bar to make it look less "blocky" 2020-03-27 13:55:21 -04:00
Isaac Grynsztein
4b9dc4a950 Updated gitignore 2020-03-27 13:55:01 -04:00
Isaac Grynsztein
5af0d742ef Fixed bug where updating an audio playlist would cause it to believe it was a video playlist 2020-03-26 13:28:26 -04:00
Isaac Grynsztein
ca3a42c075 Changed location of archive path to appdata/archives. If the folder doesn't exist, it gets auto-generated. In the future this path will be configurable 2020-03-26 10:52:43 -04:00
Isaac Grynsztein
4906e52c57 Adjusted styling for advanced download bar to make it look more natural and part of the page 2020-03-26 10:48:46 -04:00
Isaac Grynsztein
d33346b11d Final fix for bug where failed downloads still appeared in the multi download menu 2020-03-26 10:47:56 -04:00
Isaac Grynsztein
b07f16bdd0 Added subtle drop shadow to topBarTitle 2020-03-25 23:28:14 -04:00
Isaac Grynsztein
a5c47737c7 Fixed bug where simulated output did not include the base path 2020-03-25 23:27:51 -04:00
Isaac Grynsztein
603c13eb4c Modified about dialog to have a more consistent design, and added the logo & github logo to the top 2020-03-25 17:07:23 -04:00
Isaac Grynsztein
a35d85d7df Added elevation to top toolbar and made it "sticky". That means when users scroll, the toolbar will scroll with 2020-03-25 17:05:50 -04:00
Isaac Grynsztein
25b5b28df8 Minor visual change to settings 2020-03-25 07:40:14 -04:00
Isaac Grynsztein
0c60c12124 Append to last commit 2020-03-25 07:39:50 -04:00
Isaac Grynsztein
72b42dea5a About dialog now informs user when update is available or when they are up to date based on the github release api 2020-03-25 07:39:39 -04:00
Isaac Grynsztein
d0221f2233 Updated gitignore and public dir 2020-03-25 04:22:44 -04:00
Isaac Grynsztein
43ea401d53 Removed old releases from repo 2020-03-25 04:16:37 -04:00
Isaac Grynsztein
1ed415d733 Fixed bug where if using download-only mode, downloading additonal videos would be blocked 2020-03-25 04:16:11 -04:00
Isaac Grynsztein
1808281b50 updated gitignore 2020-03-25 04:05:40 -04:00
Isaac Grynsztein
b4dc655f2f Re-added contents of public directory to repo 2020-03-25 04:05:00 -04:00
Isaac Grynsztein
47a1173a80 Updated app.js to remove error when not run from backend directory 2020-03-25 03:57:15 -04:00
Isaac Grynsztein
6c22c0e708 fixed heroku build process so it skips building the frontend 2020-03-25 03:46:56 -04:00
Isaac Grynsztein
0d756c4c97 If no config exists, one will be auto generated from the default 2020-03-24 20:59:58 -04:00
Isaac Grynsztein
d4664bad45 Additional fix for bug that caused server to crash when failing to update youtube-dl 2020-03-24 19:36:45 -04:00
Isaac Grynsztein
03e3eb9a81 Fixed bug where failed youtube-dl updates crashed the server (it should just continue uninterrupted) 2020-03-24 19:27:48 -04:00
Isaac Grynsztein
9e4d36e6ed Updated look of about dialog's close button 2020-03-24 19:27:25 -04:00
Isaac Grynsztein
7c605d83cd Updated look of dark mode to be more "dark" and more friendly 2020-03-24 19:26:56 -04:00
Isaac Grynsztein
a4f97d3814 Removed config writing in docker compose 2020-03-22 03:32:21 -04:00
Isaac Grynsztein
c003b02153 updated gitignores 2020-03-22 03:12:24 -04:00
Isaac Grynsztein
f478f14de1 heroku fix (7) 2020-03-22 03:01:18 -04:00
Isaac Grynsztein
d0ffb4d90e heroku fix (6) 2020-03-22 02:47:25 -04:00
Isaac Grynsztein
6d62669a43 heroku fix (5) 2020-03-22 02:33:44 -04:00
Isaac Grynsztein
8c6478bfef Final fix for heroku procfile (3) 2020-03-22 02:25:35 -04:00
Isaac Grynsztein
9384436f7e fixed procfile for heroku (2) 2020-03-22 02:07:56 -04:00
Isaac Grynsztein
bb3c85b0e1 Made changes to support heroku (1) 2020-03-22 01:38:15 -04:00
Isaac Grynsztein
d7068953a8 changed env to prod 2020-03-22 01:11:15 -04:00
Isaac Grynsztein
7d9ad0fce1 removed debug logging 2020-03-22 00:55:00 -04:00
Isaac Grynsztein
b3b2175c67 added debug messages to debug heroku 2020-03-22 00:37:06 -04:00
Isaac Grynsztein
d8ea848e26 Attempted to fix heroku server port 2020-03-22 00:13:08 -04:00
Isaac Grynsztein
fb5054a1d7 when using heroku, port is auto set as heroku's port 2020-03-21 22:11:50 -04:00
Isaac Grynsztein
25dc8d137a Fixed heroku port 2020-03-21 21:58:13 -04:00
Isaac Grynsztein
1d6fddf386 removed package-lock 2020-03-21 21:41:20 -04:00
Isaac Grynsztein
5dce997e3a Updated built location 2020-03-21 21:30:59 -04:00
Isaac Grynsztein
3a6d0f38d7 updated procfile and made server heroku compatible 2020-03-21 21:28:29 -04:00
Isaac Grynsztein
17d877f44a Added heroku-required app.json 2020-03-21 19:51:57 -04:00
Isaac Grynsztein
c9a95e48fb Added new step to docker setup 2020-03-21 01:02:38 -04:00
Isaac Grynsztein
5cced5aed8 Updated README docker instructions to reflect changes in v3.5 2020-03-21 00:50:54 -04:00
Isaac Grynsztein
fdba83bd91 image urls hotfix 2020-03-20 23:56:15 -04:00
Isaac Grynsztein
8bcf46d6aa Updated images and instructions on README to reflect v3.5 2020-03-20 23:55:28 -04:00
Isaac Grynsztein
945d2abd1a UI hotfix 2020-03-20 23:07:35 -04:00
Isaac Grynsztein
4094fda71d Updated spanish translations to final 3.5 version
Removed erroneous log
2020-03-20 22:59:56 -04:00
Tzahi12345
aed64b09fe Merge pull request #36 from Tzahi12345/settings-pin
Added ability to set pin for settings
2020-03-20 22:48:55 -04:00
Isaac Grynsztein
fa2800a391 merge fixd import 2020-03-20 22:47:04 -04:00
Isaac Grynsztein
4aaaddac92 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into settings-pin 2020-03-20 22:46:06 -04:00
Isaac Grynsztein
c3220dcb60 Updated language select size 2020-03-20 16:35:28 -04:00
Isaac Grynsztein
996d5989e7 Updated spanish translation 2020-03-20 16:34:57 -04:00
Isaac Grynsztein
7681ac2e5a Updated source translation file 2020-03-20 16:21:15 -04:00
Isaac Grynsztein
87e7f00aef Updated chrome extension 2020-03-20 16:19:23 -04:00
Isaac Grynsztein
0ac87fe86d Updated spanish language string label to avoid confusion
Fixed bug where if no locale was set, it would *not* default to English
2020-03-20 16:18:51 -04:00
Isaac Grynsztein
ce2f294a3d Removed potential race condition with youtube-dl archives in main downloader
Fixed bug where downloaded subscriptions' zip files would include erroneous folders for their videos
2020-03-20 16:17:15 -04:00
Isaac Grynsztein
2b2a033b7e Added extensions settings where information on extensions can be found and bookmarklet is generated
Created arg modifier dialog to assist in editing youtube-dl args
- This arg dialog contains all the available args and their description
- Includes a convenient search bar and categorized list of args to help you find the one you're looking for, or just explore what's available.

Arg modifier is available for both global args (in settings) and local args (in the advanced mode)
2020-03-20 16:16:10 -04:00
Isaac Grynsztein
f796a5863c Added package-lock to .gitignore 2020-03-18 00:33:12 -04:00
Isaac Grynsztein
57e3f1b2ac removed frivolous logging 2020-03-18 00:32:43 -04:00
Tzahi12345
8f17305710 Merge pull request #35 from Tzahi12345/id3-tagging
Added automatic ID3 tagging for mp3 files
2020-03-18 00:29:31 -04:00
Tzahi12345
4b67527bd5 Merge branch 'master' into id3-tagging 2020-03-18 00:29:23 -04:00
Tzahi12345
4f119d081e Merge pull request #34 from Tzahi12345/auto-update-youtubedl
Auto update youtube-dl binaries on startup
2020-03-18 00:17:25 -04:00
Isaac Grynsztein
2e71a0bef1 fixed bug that caused youtube downloader update to fail when the binary was being used to check for new subscription videos. now it waits for file access with a 10 second timeout 2020-03-18 00:04:18 -04:00
Isaac Grynsztein
1f9f07ac56 youtube-dl now auto updates on server restart 2020-03-17 23:22:26 -04:00
Tzahi12345
c4fe1a2fb0 Merge pull request #32 from Tzahi12345/subscriptions-v2
Subscriptions v2
2020-03-17 22:35:32 -04:00
Isaac Grynsztein
b987e258b5 Updated firefox extension zip 2020-03-17 22:25:21 -04:00
Isaac Grynsztein
bdb6a08274 Added ability to download subscription videos as zip 2020-03-17 22:24:52 -04:00
Isaac Grynsztein
b2730926c8 Updated translation details to improve clarity
Added upload date property to files in UI

Subscription videos can now be filtered by some of their properties (size, upload date, name, duration)

Subscription videos are now centered
2020-03-17 21:38:49 -04:00
Isaac Grynsztein
393267b919 fixed bug where youtube search API failed to load when enabled through the settings 2020-03-17 18:49:19 -04:00
Isaac Grynsztein
35f8454db3 Fixed bug where auto started downloads would begin if navigated back from the player 2020-03-17 18:20:25 -04:00
Isaac Grynsztein
1837101083 Updated manifest 2020-03-17 18:15:17 -04:00
Isaac Grynsztein
aa80f52838 Updated firefox extension to v0.3, focused on making the settings menu look nicer 2020-03-17 18:15:07 -04:00
Isaac Grynsztein
a5e1906196 Fixed bug that prevented auto start from working 2020-03-17 18:14:11 -04:00
Isaac Grynsztein
9dc607e7ee fixed bug where no subscriptions resulted in a client error 2020-03-17 18:07:35 -04:00
Isaac Grynsztein
1a79b489ab Added video info dialog
File cards and subscription file cards now use video info dialog so that users can see info on each individual video

Ellipsis are now added client-side to video titles in file cards
2020-03-17 06:58:05 -04:00
Isaac Grynsztein
3bdacd4b52 Added additional volumes to docker compose 2020-03-17 05:29:25 -04:00
Isaac Grynsztein
f08506d690 When locale 'en' is selected, no translation is retrieved 2020-03-16 01:23:35 -04:00
Isaac Grynsztein
1d29dd8df4 Updated translations 2020-03-16 01:23:08 -04:00
Isaac Grynsztein
25b953cebd Updated look of file cards again to prevent aspect ratio from being changed 2020-03-16 01:22:48 -04:00
Isaac Grynsztein
ff2f5c89da Changed location of db and config to one unified directory, 'appdata'
Archive files now get generated if nonexistent during deletions

Simplified docker-compose.yml to not require environment variables. Added volume for appdata folder which will be automatically shipped with docker builds
2020-03-16 01:22:09 -04:00
Isaac Grynsztein
2cd35cccd1 Added about page 2020-03-15 22:05:00 -04:00
Isaac Grynsztein
da399601e1 Added ability to select any supported custom downloader 2020-03-15 20:59:21 -04:00
Tzahi12345
4ceec26a0e Merge pull request #31 from Tzahi12345/use-youtubedl-archive-with-downloader
Implements youtube-dl archive functionality for downloader
2020-03-15 20:39:18 -04:00
Isaac Grynsztein
b765830584 Using youtubedl archive for downloads defaults to false 2020-03-15 20:37:26 -04:00
Isaac Grynsztein
44bff55a88 Adds youtube dl args to simulated output 2020-03-15 20:35:51 -04:00
Isaac Grynsztein
84c9ec1300 Updated file card image appearance 2020-03-15 20:31:04 -04:00
Isaac Grynsztein
f1c09a5fa9 Added firefox extension zip to repo 2020-03-15 20:30:48 -04:00
Isaac Grynsztein
d39f6f7a17 File cards modified to support blacklisting videos when using youtube-dl archive 2020-03-15 20:28:18 -04:00
Isaac Grynsztein
190d1567ca youtube-dl archive can now be used for regular downloads. a new blacklist system exists which subscriptions will shortly follow
Added new setting whether to use youtubedl archive
2020-03-15 20:27:06 -04:00
Isaac Grynsztein
e71dc0b5e5 Updated angular version on readme 2020-03-15 15:39:19 -04:00
Tzahi12345
7d6ad6336c Merge pull request #30 from Tzahi12345/localization
Added localization support
2020-03-15 15:18:00 -04:00
Isaac Grynsztein
087b9aac7e updated translations v2 2020-03-15 15:05:10 -04:00
Isaac Grynsztein
d61573d8af Updated spanish translation 2020-03-15 14:55:41 -04:00
Isaac Grynsztein
c7885e8726 Added ability to select language in settings
Fixed locale support so that it actually works at runtime

Removed unused library
2020-03-15 14:52:07 -04:00
Isaac Grynsztein
ebd0a419df Further fix for videogular compilation error 2020-03-15 04:09:30 -04:00
Isaac Grynsztein
c96aec7ba3 fixed videogular in angular 9 2020-03-15 03:59:00 -04:00
Isaac Grynsztein
ddcd1d0772 Updated spanish translation xliff and json
locale is now retrieved from cookies
2020-03-15 03:58:31 -04:00
Tzahi12345
bcd706f477 Merge pull request #29 from Xanthus1/custom-videopath-fix
Fix for custom video folder path
2020-03-15 03:55:38 -04:00
Isaac Grynsztein
c17d29075e Updated angular material to v9
Added further translation support
2020-03-15 00:48:16 -04:00
WScottN
25b65b08d5 Fix for custom video folder path
This fixes an issue with video playback when using a different video path setting than the default "video/" .  Using the videoFolderPath variable rather than "video/" string.
2020-03-15 00:24:44 -04:00
Isaac Grynsztein
0981dd216d Migrated to angular 9 2020-03-14 23:37:27 -04:00
Isaac Grynsztein
20b580b76f updated to latest angular 8 version 2020-03-14 23:32:44 -04:00
Isaac Grynsztein
bc4cc3ccce Added spanish language support 2020-03-14 23:29:38 -04:00
Isaac Grynsztein
21797f3901 Added preliminary localization support to almost all strings in the program 2020-03-14 17:32:52 -04:00
Isaac Grynsztein
91f6dbcb5b added ability to set pin for settings. pin is md5'd by the backend 2020-03-13 02:20:07 -04:00
Isaac Grynsztein
d9edb40cd5 Added basic id3 tagging functionality and simplified mp3 downloading logic 2020-03-13 00:34:27 -04:00
Isaac Grynsztein
c752b13732 Updated look of file cards so that the images are of equal size 2020-03-13 00:33:02 -04:00
Isaac Grynsztein
bad4b8630b potentially made it so saved settings get applied with no reload in most situations 2020-03-11 22:04:22 -04:00
Isaac Grynsztein
4f4a82c3c4 updated firefox/chrome extension to support firefox with no errors 2020-03-11 03:35:57 -04:00
Isaac Grynsztein
25307dc46b fixed bug where downloaded audio files did not properly get converted to mp3s 2020-03-10 16:24:04 -04:00
Isaac Grynsztein
d873597ea2 fixed formatting in readme 2020-03-10 03:36:46 -04:00
Isaac Grynsztein
f92a5549b5 implemented allowSubscriptions in frontend 2020-03-10 02:28:48 -04:00
Isaac Grynsztein
69bf4e1ad5 updated docker version 2020-03-10 02:10:36 -04:00
Isaac Grynsztein
9d1aaf95ed Refactored subscribing process to remove bugs in the old system
images are now deleted from subscription videos when unsubscribing
2020-03-10 02:03:10 -04:00
Isaac Grynsztein
bb925ac0c8 fixed bug where video titles were used instead of IDs for the player component
fixed bug that caused a crash when no subscriptions existed
2020-03-09 01:19:16 -04:00
Isaac Grynsztein
74cda25c63 added search functionality
made subscription file cards more responsive on mobile layouts

removed unused shell code
2020-03-09 00:22:03 -04:00
Isaac Grynsztein
54dcbe452e fixed compiler error 2020-03-08 22:52:29 -04:00
Isaac Grynsztein
946abd2e92 implemented global custom args functionality
fixed bad logic in settings
2020-03-08 22:47:08 -04:00
Isaac Grynsztein
73d4cca615 added new config items to docker compose 2020-03-08 22:30:43 -04:00
Isaac Grynsztein
846dd7e250 Added the ability to download (export) archives from subscriptions 2020-03-08 22:24:59 -04:00
Isaac Grynsztein
6f3e94cf24 hamburger menu button now avoids focus and has no outline
theme change behavior slightly modified to improve accessibility

added hammerjs

settings menu now has minimum width, updated colors, and additional hints
2020-03-08 22:23:50 -04:00
Isaac Grynsztein
3cbb517d64 cleaned up some code
youtube-dl commands are now simulated and displayed in the advanced panel
2020-03-08 22:21:34 -04:00
Isaac Grynsztein
5d945d729b hotfix to readme formatting 2020-03-08 17:27:15 -04:00
Isaac Grynsztein
cda842a6c7 Updated readme to include API info 2020-03-08 17:26:13 -04:00
Isaac Grynsztein
480ed7d000 added new custom args setting 2020-03-08 10:15:24 -04:00
Isaac Grynsztein
881a103051 Added duration of video in subscription file card along with implementations of deleting subscribed videos. Subscribed videos now get reloaded after deletion
sidenav now closes when navigating

Updated subscription info to include more info
2020-03-07 17:00:50 -05:00
Isaac Grynsztein
4172b0b355 fixed bug where in chrome, sometimes the video player would not appear 2020-03-07 13:16:16 -05:00
Isaac Grynsztein
f6b7c41666 fixed router outlet in sidenav
subscription settings implemented
2020-03-06 03:05:51 -05:00
Tzahi12345
3d1874c69b Merge pull request #22 from Tzahi12345/subscribe_to_channel_and_playlist
Adds the ability to subscribe to channels and playlists
2020-03-05 22:59:08 -05:00
Tzahi12345
ccfe7901c9 Merge branch 'master' into subscribe_to_channel_and_playlist 2020-03-05 22:57:57 -05:00
Tzahi12345
17e85196ae Merge pull request #21 from Tzahi12345/settings
Add settings page
2020-03-05 22:48:45 -05:00
Isaac Grynsztein
ae605d5f70 Added ability to set config from settings
theme slide toggle is now in top right menu
2020-03-05 22:38:23 -05:00
Isaac Grynsztein
e57839e8de updated .gitignore 2020-03-05 21:25:29 -05:00
Isaac Grynsztein
09bcac1c14 added settings 2020-03-05 21:24:29 -05:00
Isaac Grynsztein
f5073b83ed subscriptions without names will not have files retrieved any longer 2020-03-05 21:19:36 -05:00
Isaac Grynsztein
41bfc80c4e fixed bug in retrieving videos for subscription when name was not present 2020-03-05 21:18:36 -05:00
Isaac Grynsztein
3d2e138f50 updated chrome extension 2020-03-05 21:17:30 -05:00
Isaac Grynsztein
717f024c42 updated .gitignore 2020-03-05 20:16:23 -05:00
Isaac Grynsztein
a70abb3945 added basic subscriptions support for playlists and channels
update youtube-dl binary on windows

updated favicon to the new icon
2020-03-05 20:14:36 -05:00
Isaac Grynsztein
a755b0b281 fixed bug that prevented custom quality path from working 2020-03-01 16:18:01 -05:00
Isaac Grynsztein
434f6751d0 added releases to repo (will only include latest release) 2020-03-01 16:17:41 -05:00
Isaac Grynsztein
dfecf3645b updated README
renamed chrome extension
2020-03-01 03:07:52 -05:00
Isaac Grynsztein
62a000b631 fixed bug where custom paths failed to stream 2020-03-01 02:31:47 -05:00
Isaac Grynsztein
2673f4ee98 updated README 2020-03-01 00:54:11 -05:00
Tzahi12345
a8d2e1d890 Merge pull request #12 from Tzahi12345/serve-nodejs
Serve frontend app through nodejs
2020-03-01 00:50:20 -05:00
Isaac Grynsztein
0511996b26 fixed margins on advanced mode UI and temporarily disabled youtube auth until youtube-dl fixes it
advanced mode inputs now get saved in cookies

fixed bug in UI where delete button was missing by making it more mobile-friendly
2020-03-01 00:48:22 -05:00
Isaac Grynsztein
f29a29bf2f fixed bug that prevented custom args from working 2020-03-01 00:46:42 -05:00
Isaac Grynsztein
24d4107311 Clarified old config in README 2020-02-29 04:31:23 -05:00
Isaac Grynsztein
a46f9c37c6 fixed bug where old config item was fetched 2020-02-29 04:31:06 -05:00
Isaac Grynsztein
2b1c68bad0 update docker-compose and dockerfile 2020-02-29 04:30:56 -05:00
Isaac Grynsztein
e2d23404ce removed unused variable 2020-02-29 04:30:34 -05:00
Isaac Grynsztein
db208ed55e Updated README to reflect changes in the config 2020-02-29 03:30:21 -05:00
Isaac Grynsztein
b87a9f1e2f fixed bug where playlist titles included their relative path 2020-02-29 03:08:02 -05:00
Tzahi12345
a1ac1e450d Merge pull request #11 from Tzahi12345/auth-params
Add the ability to use youtube authentication
2020-02-28 22:12:29 -05:00
Tzahi12345
6764ea6c3b Merge pull request #10 from Tzahi12345/url_params
Add URL params home page
2020-02-28 22:07:53 -05:00
Isaac Grynsztein
8a52020186 resized favicon 2020-02-28 22:05:16 -05:00
Isaac Grynsztein
695b836852 added url params on home page to auto download content
created chrome extension to facilitate this feature
2020-02-28 21:21:17 -05:00
Isaac Grynsztein
71d7c30032 updated backend to support youtube auth
frontend now support youtube auth as well
2020-02-28 20:09:59 -05:00
Isaac Grynsztein
5ca4f036c7 fixed bug where if multi mode was enabled, click on file card URLs didn't work 2020-02-28 00:32:33 -05:00
Isaac Grynsztein
1ffe61f01f removed path-base and updated docker-compose.yml & README 2020-02-28 00:20:08 -05:00
Isaac Grynsztein
5e331b9ffa config settings now just have url and port
fixed bug where multi download mode would not allow file card link clicking
2020-02-28 00:14:46 -05:00
Isaac Grynsztein
09bdae90e2 refactored code so node.js serves the angular app, and all the backend routes are prepended with /api/
nodejs now compressed requests
2020-02-27 22:52:50 -05:00
Isaac Grynsztein
37cc8f4fe1 fixed error in docker compose 2020-02-27 04:15:19 -05:00
Isaac Grynsztein
925e083b8d added new config settings to README 2020-02-27 04:12:27 -05:00
Isaac Grynsztein
6dc42278c4 updated docker-compose to new youtubedl-material version number 2020-02-27 03:57:41 -05:00
Isaac Grynsztein
12c227badb temporarily disabled advanced mode until further testing 2020-02-27 03:47:11 -05:00
Isaac Grynsztein
181a9f842c fixed bug where downloading files failed if the name had to be encoded 2020-02-27 03:46:57 -05:00
Isaac Grynsztein
b79d801c0f Added support for custom arguments and custom output patch 2020-02-27 03:27:57 -05:00
Isaac Grynsztein
fc3691336d added allow multi download mode setting frontend implementation 2020-02-27 01:10:23 -05:00
Isaac Grynsztein
bcd879ebc8 added multiple download support
lazy loaded images now reload after a new download
2020-02-27 01:06:32 -05:00
Isaac Grynsztein
b646db4828 Added the ability to cancel downloads
Audio only checkbox now disabled when downloading

Laid the groundwork for multiple simulataneous downloads
2020-02-26 19:04:02 -05:00
Isaac Grynsztein
426d52e359 fixed tabindex ordering of file cards (delete came before url) 2020-02-26 19:03:13 -05:00
Isaac Grynsztein
17199dd9c0 Updated readme 2020-02-26 18:11:37 -05:00
Isaac Grynsztein
c680c2827b reworded docker steps 2020-02-26 03:18:19 -05:00
Isaac Grynsztein
2dbf8d31f7 Updated docker info in readme 2020-02-26 03:17:29 -05:00
Isaac Grynsztein
a3753e557c Updated docker instructions 2020-02-26 03:16:42 -05:00
Isaac Grynsztein
ec80abdc8e Updated readme.md to include docker steps 2020-02-26 03:07:22 -05:00
Isaac Grynsztein
1ef7d24c22 downgraded required docker-compose.yml to 2 2020-02-26 01:35:21 -05:00
Isaac Grynsztein
4b6f6996ae fixed bug in docker during building 2020-02-26 01:28:46 -05:00
Isaac Grynsztein
c930ee94c5 added docker support
reworked backend to allow for containerization. config items can now be overwritten by environment vars

fixed bug during building

updated youtube-dl version in backend
2020-02-26 00:34:13 -05:00
Isaac Grynsztein
e88edbef5a Updated README to fix missing step 2020-02-24 16:22:54 -05:00
Isaac Grynsztein
ac13ed3359 updated package.json to remove bug during building 2020-02-24 16:18:09 -05:00
Isaac Grynsztein
faae0d44e6 make it better 2020-02-24 07:47:36 -05:00
Isaac Grynsztein
7d8ec04ad6 make it better 2020-02-24 07:39:00 -05:00
Isaac Grynsztein
8629e6ae9e make it better 2020-02-24 07:28:11 -05:00
Isaac Grynsztein
6e311d46a6 make it better 2020-02-24 06:41:13 -05:00
Isaac Grynsztein
006e983c14 make it better 2020-02-24 06:13:56 -05:00
Isaac Grynsztein
5db3e06a81 make it better 2020-02-24 06:05:24 -05:00
Isaac Grynsztein
2ced7b7f91 researching heroku support 2020-02-24 05:58:25 -05:00
Isaac Grynsztein
042baa418b updated .gitignore 2020-02-24 04:12:45 -05:00
Isaac Grynsztein
deb928da12 sorting and updating now only possible on favorited (saved) playlists
fixed compilation bug in app.module
2020-02-24 04:11:22 -05:00
Isaac Grynsztein
a7f5cc01d3 update youtube-dl binary 2020-02-24 03:51:27 -05:00
Isaac Grynsztein
414b6a26d9 backend playlist updating endpoint implemented
tomp3/tomp4 errors are now logged
2020-02-24 03:51:11 -05:00
Isaac Grynsztein
c069672e62 ngmodule drag and drop import commit 2020-02-24 03:50:29 -05:00
Isaac Grynsztein
167d9dafa2 added title to create playlist dialog 2020-02-24 03:50:10 -05:00
Isaac Grynsztein
9302084f60 playlists can now be rearranged and updated 2020-02-24 03:49:43 -05:00
Isaac Grynsztein
ac0199f596 iOS is now checked by the cdk platform component 2020-02-24 03:49:01 -05:00
Isaac Grynsztein
8e8ab7ac6c added min-height to app component 2020-02-23 22:30:09 -05:00
Isaac Grynsztein
f06c9ba44a fixed bug where non-themed white space that appeared when file manager was expanded 2020-02-23 22:29:42 -05:00
Isaac Grynsztein
6e593472d9 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2020-02-23 03:21:13 -05:00
Isaac Grynsztein
0bddbda36d updated favicon 2020-02-23 03:20:24 -05:00
Isaac Grynsztein
23feb05fab downloading agent is now the default of youtube-dl by default instead of aria2c. testing showed it performed better over multipled trials
added a setting to use aria2c optionally

added debug timing to getURLInfos
2020-02-23 03:20:07 -05:00
Isaac Grynsztein
a0eff4d96d images on file cards now load when the accordion is hovered over to increase responsiveness. images are loading maybe a second before clicking so hopefully they're done by the time the expansion finishes
added the ability to create playlists in the gui through a new dialog

reloading mp3s/mp4s doesn't cause an image refresh anymore when the list is unchanged

fixed loading spinner of available formats so it now only shows when it is loading the current url

file card images now don't show when errored or thumbnailurl doesn't exist
2020-02-23 03:18:26 -05:00
Isaac Grynsztein
b87b49d77b removed outline for video player 2020-02-23 03:13:09 -05:00
Tzahi12345
c05026aa15 Update README.md 2020-02-21 01:33:31 -05:00
Tzahi12345
883df63d2f Update README.md 2020-02-20 21:22:53 -05:00
Tzahi12345
da1d49b541 Update README.md 2020-02-20 21:22:25 -05:00
406 changed files with 94645 additions and 1818 deletions

7
.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
.git
db
appdata
audio
video
subscriptions
users

20
.eslintrc.json Normal file
View File

@@ -0,0 +1,20 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
}
}

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment**
- YoutubeDL-Material version
- Docker tag: <tag> (optional)
Ideally you'd copy the info as presented on the "About" dialogue
in YoutubeDL-Material.
(for that, click on the three dots on the top right and then
check "installation details". On later versions of YoutubeDL-
Material you will find pretty much all the crucial information
here that we need in most cases!)
**Additional context**
Add any other context about the problem here. For example, a YouTube link.

View File

@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

111
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,111 @@
name: continuous integration
on:
push:
branches: [master, feat/*]
tags:
- v*
pull_request:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v2
- name: setup node
uses: actions/setup-node@v2
with:
node-version: '12'
cache: 'npm'
- name: install dependencies
run: |
npm install
cd backend
npm install
sudo npm install -g @angular/cli
- name: Set hash
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: create-json
id: create-json
uses: jsdaniell/create-json@1.1.2
with:
name: "version.json"
json: '{"type": "autobuild", "tag": "N/A", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
dir: 'backend/'
- name: build
run: npm run build
- name: prepare artifact upload
shell: pwsh
run: |
New-Item -Name build -ItemType Directory
New-Item -Path build -Name youtubedl-material -ItemType Directory
Copy-Item -Path ./backend/appdata -Recurse -Destination ./build/youtubedl-material
Copy-Item -Path ./backend/audio -Recurse -Destination ./build/youtubedl-material
Copy-Item -Path ./backend/authentication -Recurse -Destination ./build/youtubedl-material
Copy-Item -Path ./backend/public -Recurse -Destination ./build/youtubedl-material
Copy-Item -Path ./backend/subscriptions -Recurse -Destination ./build/youtubedl-material
Copy-Item -Path ./backend/video -Recurse -Destination ./build/youtubedl-material
New-Item -Path ./build/youtubedl-material -Name users -ItemType Directory
Copy-Item -Path ./backend/*.js -Destination ./build/youtubedl-material
Copy-Item -Path ./backend/*.json -Destination ./build/youtubedl-material
- name: upload build artifact
uses: actions/upload-artifact@v1
with:
name: youtubedl-material
path: build
release:
runs-on: ubuntu-latest
needs: build
if: contains(github.ref, '/tags/v')
steps:
- name: checkout code
uses: actions/checkout@v2
- name: create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: YoutubeDL-Material ${{ github.ref }}
body: |
# New features
# Minor additions
# Bug fixes
draft: true
prerelease: false
- name: download build artifact
uses: actions/download-artifact@v1
with:
name: youtubedl-material
path: ${{runner.temp}}/youtubedl-material
- name: extract tag name
id: tag_name
run: echo ::set-output name=tag_name::${GITHUB_REF#refs/tags/}
- name: prepare release asset
shell: pwsh
run: Compress-Archive -Path ${{runner.temp}}/youtubedl-material -DestinationPath youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip
- name: upload release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip
asset_name: youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip
asset_content_type: application/zip
- name: upload docker-compose asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./docker-compose.yml
asset_name: docker-compose.yml
asset_content_type: text/plain

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 12 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

45
.github/workflows/docker-release.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: docker-release
on:
workflow_dispatch:
inputs:
tags:
description: 'Docker tags'
required: true
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v2
- name: Set hash
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: create-json
id: create-json
uses: jsdaniell/create-json@1.1.2
with:
name: "version.json"
json: '{"type": "docker", "tag": "latest", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
dir: 'backend/'
- name: setup platform emulator
uses: docker/setup-qemu-action@v1
- name: setup multi-arch docker build
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: build & push images
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm,linux/arm64/v8
push: true
tags: ${{ github.event.inputs.tags }}

42
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: docker
on:
push:
branches: [master]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v2
- name: Set hash
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Get current date
id: date
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
- name: create-json
id: create-json
uses: jsdaniell/create-json@1.1.2
with:
name: "version.json"
json: '{"type": "docker", "tag": "nightly", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
dir: 'backend/'
- name: setup platform emulator
uses: docker/setup-qemu-action@v1
- name: setup multi-arch docker build
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: build & push images
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm,linux/arm64/v8
push: true
tags: tzahi12345/youtubedl-material:nightly

33
.gitignore vendored
View File

@@ -25,6 +25,7 @@
!.vscode/extensions.json
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
@@ -44,6 +45,34 @@ Thumbs.db
node_modules/*
backend/node_modules/*
YoutubeDL-Material/node_modules/*
backend/video/*
backend/audio/*
backend/subscriptions/archives/*
backend/subscriptions/playlists/*
backend/subscriptions/channels/*
backend/db.json
backend/public/assets/i18n/*.xlf
backend/public/assets/default.json
backend/subscriptions/channels/*
backend/subscriptions/playlists/*
backend/subscriptions/archives/*
backend/*.exe
src/assets/default.json
backend/appdata/db.json
backend/appdata/archives/archive_audio.txt
backend/appdata/archives/archive_video.txt
backend/appdata/archives/blacklist_audio.txt
backend/appdata/archives/blacklist_video.txt
backend/appdata/logs/combined.log
backend/appdata/logs/error.log
backend/appdata/users.json
backend/users/*
backend/appdata/cookies.txt
backend/public
src/assets/i18n/*.json
# User Files
db/
appdata/
audio/
video/
subscriptions/
users/

25
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,25 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"problemMatcher": [],
"label": "Dev: start frontend",
"detail": "ng serve"
},
{
"label": "Dev: start backend",
"type": "shell",
"command": "set YTDL_MODE=debug && node app.js",
"options": {
"cwd": "./backend"
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
}

54
Dockerfile Normal file
View File

@@ -0,0 +1,54 @@
FROM alpine:latest AS ffmpeg
COPY docker-build.sh .
RUN sh ./docker-build.sh
FROM alpine:latest as frontend
RUN apk add --no-cache \
npm
RUN npm install -g @angular/cli
WORKDIR /build
COPY [ "package.json", "package-lock.json", "/build/" ]
RUN npm install
COPY [ "angular.json", "tsconfig.json", "/build/" ]
COPY [ "src/", "/build/src/" ]
RUN npm run build
#--------------#
FROM alpine:latest
ENV UID=1000 \
GID=1000 \
USER=youtube
ENV NO_UPDATE_NOTIFIER=true
RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID
RUN apk add --no-cache \
npm \
python2 \
python3 \
su-exec \
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
atomicparsley
WORKDIR /app
COPY --from=ffmpeg /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=ffmpeg /usr/local/bin/ffprobe /usr/local/bin/ffprobe
COPY --chown=$UID:$GID [ "backend/package.json", "backend/package-lock.json", "/app/" ]
ENV PM2_HOME=/app/pm2
RUN npm install pm2 -g
RUN npm install && chown -R $UID:$GID ./
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
COPY --chown=$UID:$GID [ "/backend/", "/app/" ]
EXPOSE 17442
ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD [ "pm2-runtime", "pm2.config.js" ]

1
Procfile Normal file
View File

@@ -0,0 +1 @@
web: npm start --prefix backend

2818
Public API v1.yaml Normal file

File diff suppressed because it is too large Load Diff

128
README.md
View File

@@ -1,6 +1,26 @@
# YoutubeDL-Material
YoutubeDL-Material is a material design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 5](https://angular.io/) for the frontend, and [Nodejs](https://nodejs.org/) on the backend.
[![Docker pulls badge](https://img.shields.io/docker/pulls/tzahi12345/youtubedl-material.svg)](https://hub.docker.com/r/tzahi12345/youtubedl-material)
[![Docker image size badge](https://img.shields.io/docker/image-size/tzahi12345/youtubedl-material?sort=date)](https://hub.docker.com/r/tzahi12345/youtubedl-material)
[![Heroku deploy badge](https://img.shields.io/badge/%E2%86%91_Deploy_to-Heroku-7056bf.svg)](https://heroku.com/deploy?template=https://github.com/Tzahi12345/YoutubeDL-Material)
[![GitHub issues badge](https://img.shields.io/github/issues/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/issues)
[![License badge](https://img.shields.io/github/license/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/LICENSE.md)
YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 11](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend.
Now with [Docker](#Docker) support!
<hr>
### USAGE OF THE NIGHTLY BUILDS IS HIGHLY RECOMMENDED.
For much better scaling with large datasets please run your YTDL-M instance with a MongoDB backend rather than the json file-based default.
It will fix a lot of performance problems (especially with datasets in the tens of thousands videos/audios)!
The (closed) issues as well as the project's Wiki will give you good starting points for your journey!
For MongoDB specifically there is [this little guide](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Setting-a-MongoDB-backend-to-use-as-database-provider-for-YTDL-M).
<hr>
## Getting Started
@@ -8,58 +28,134 @@ Check out the prerequisites, and go to the installation section. Easy as pie!
Here's an image of what it'll look like once you're done:
![frontpage](https://i.imgur.com/m3xozES.png)
<img src="https://i.imgur.com/C6vFGbL.png" width="800">
With optional file management enabled (default):
Dark mode:
![frontpage_with_files](https://i.imgur.com/z9vME2O.png)
<img src="https://i.imgur.com/vOtvH5w.png" width="800">
### Prerequisites
You need to have a functioning web server for this to work. Also make sure you have these dependencies installed on your system: ffmpeg, nodejs, python. If you don't, run this command:
NOTE: If you would like to use Docker, you can skip down to the [Docker](#Docker) section for a setup guide.
Debian/Ubuntu:
```bash
sudo apt-get install nodejs youtube-dl ffmpeg unzip python npm
```
sudo apt-get install ffmpeg nodejs python
CentOS 7:
```bash
sudo yum install epel-release
sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm
sudo yum install centos-release-scl-rh
sudo yum install rh-nodejs12
scl enable rh-nodejs12 bash
sudo yum install nodejs youtube-dl ffmpeg ffmpeg-devel
```
Optional dependencies:
* AtomicParsley (for embedding thumbnails, package name `atomicparsley`)
### Installing
First, download the [latest release](https://github.com/Tzahi12345/YoutubeDL-Material/releases/latest)!
1. First, download the [latest release](https://github.com/Tzahi12345/YoutubeDL-Material/releases/latest)!
Drag all the files in `youtubedl-material` to a location accessible to a web server. It works best if it's the root (usually right inside `public_html`. Once that's done, navigate to `backend` and edit the `default.json` file. If you're using SSL encryption, look at the `encrypted.json` file for a template.
2. Drag the `youtubedl-material` directory to an easily accessible directory. Navigate to the `appdata` folder and edit the `default.json` file.
Port forward `17442` if you're going to access YoutubeDL-Material from out of your network. This is an important step. Make sure the configuration reflects this appropriately.
NOTE: If you are intending to use a [reverse proxy](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Reverse-Proxy-Setup), this next step is not necessary
Once the configuration is done, type `sudo nodejs app.js`. This will run the backend server. On your browser, navigate to your installation folder. Try putting in a youtube link to see if it works. If it does, viola! YoutubeDL-Material is now up and running.
3. Port forward the port listed in `default.json`, which defaults to `17442`.
4. Once the configuration is done, run `npm install` to install all the backend dependencies. Once that is finished, type `npm start`. This will run the backend server, which serves the frontend as well. On your browser, navigate to to the server (url with the specified port). Try putting in a youtube link to see if it works. If it does, viola! YoutubeDL-Material is now up and running.
If you experience problems, know that it's usually caused by a configuration problem. The first thing you should do is check the console. To get there, right click anywhere on the page and click "Inspect element." Then on the menu that pops up, click console. Look at the error there, and try to investigate.
## Deployment
## Build it yourself
If you'd like to install YoutubeDL-Material, go to the Installation section. If you want to build it yourself and/or develop the repository, then this section is for you.
To deploy, simply clone the repository, and go into the `youtubedl-material` directory. Type `npm install` and all the dependencies will install. Then type `cd backend` and again type `npm install` to install the dependencies for the backend.
Once you do that, you're almost up and running. All you need to do is edit the configuration in `youtubedl-material/backend/config`, go back into the `youtubedl-material` directory, and type `ng build --prod`. This will build the app, and put the output files in the `youtubedl-material/dist` folder. Drag those files into a web server, and drag the `backend` directory into the same folder. This folder should have `index.html` in it as well. If it does **not**, you're in the wrong directory.
Once you do that, you're almost up and running. All you need to do is edit the configuration in `youtubedl-material/appdata`, go back into the `youtubedl-material` directory, and type `npm build`. This will build the app, and put the output files in the `youtubedl-material/backend/public` folder.
The frontend is now complete. The backend is much easier. Just go into the `youtubedl-material/backend` folder, and type `sudo nodejs app.js`.
The frontend is now complete. The backend is much easier. Just go into the `backend` folder, and type `npm start`.
Finally, port forward the port `17442` and point it to the server's IP address. Make sure the port is also allowed through the firewall.
Finally, if you want your instance to be available from outside your network, you can set up a [reverse proxy](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Reverse-Proxy-Setup).
Alternatively, you can port forward the port specified in the config (defaults to `17442`) and point it to the server's IP address. Make sure the port is also allowed through the server's firewall.
## Docker
### Host-specific instructions
If you're on a Synology NAS, unRAID or any other possible special case you can check if there's known issues or instructions both in the issue tracker and in the [Wiki!](https://github.com/Tzahi12345/YoutubeDL-Material/wiki#environment-specific-guideshelp)
### Setup
If you are looking to setup YoutubeDL-Material with Docker, this section is for you. And you're in luck! Docker setup is quite simple.
1. Run `curl -L https://github.com/Tzahi12345/YoutubeDL-Material/releases/latest/download/docker-compose.yml -o docker-compose.yml` to download the latest Docker Compose, or go to the [releases](https://github.com/Tzahi12345/YoutubeDL-Material/releases/) page to grab the version you'd like.
2. Run `docker-compose pull`. This will download the official YoutubeDL-Material docker image.
3. Run `docker-compose up` to start it up. If successful, it should say "HTTP(S): Started on port 17443" or something similar. This tells you the *container-internal* port of the application. Please check your `docker-compose.yml` file for the *external* port. If you downloaded the file as described above, it defaults to **8998**.
4. Make sure you can connect to the specified URL + *external* port, and if so, you are done!
NOTE: It is currently recommended that you use the `nightly` tag on Docker. To do so, simply update the docker-compose.yml `image` field so that it points to `tzahi12345/youtubedl-material:nightly`.
### Custom UID/GID
By default, the Docker container runs as non-root with UID=1000 and GID=1000. To set this to your own UID/GID, simply update the `environment` section in your `docker-compose.yml` like so:
```yml
environment:
UID: YOUR_UID
GID: YOUR_GID
```
## API
[API Docs](https://youtubedl-material.stoplight.io/docs/youtubedl-material/Public%20API%20v1.yaml)
To get started, go to the settings menu and enable the public API from the *Extra* tab. You can generate an API key if one is missing.
Once you have enabled the API and have the key, you can start sending requests by adding the query param `apiKey=API_KEY`. Replace `API_KEY` with your actual API key, and you should be good to go! Nearly all of the backend should be at your disposal. View available endpoints in the link above.
## iOS Shortcut
If you are using iOS, try YoutubeDL-Material more conveniently with a Shortcut. With this Shorcut, you can easily start downloading YouTube video with just two taps! (Or maybe three?)
You can download Shortcut [here.](https://routinehub.co/shortcut/10283/)
## Contributing
Feel free to submit a pull request! I have no guidelines as of yet, so no need to worry about that.
If you're interested in contributing, first: awesome! Second, please refer to the guidelines/setup information located in the [Contributing](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Contributing) wiki page, it's a helpful way to get you on your feet and coding away.
Pull requests are always appreciated! If you're a bit rusty with coding, that's no problem: we can always help you learn. And if that's too scary, that's OK too! You can create issues for features you'd like to see or bugs you encounter, it all helps this project grow.
If you're interested in translating the app into a new language, check out the [Translate](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Translate) wiki page.
## Authors
* **Isaac Grynsztein** (me!) - *Initial work*
See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project.
Official translators:
* Spanish - tzahi12345
* German - UnlimitedCookies
* Chinese - TyRoyal
See also the list of [contributors](https://github.com/Tzahi12345/YoutubeDL-Material/graphs/contributors) who participated in this project.
## License
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
## Legal Disclaimer
This project is in no way affiliated with Google LLC, Alphabet Inc. or YouTube (or their subsidiaries) nor endorsed by them.
## Acknowledgments
* youtube-dl

21
SECURITY.md Normal file
View File

@@ -0,0 +1,21 @@
# Security Policy
## Supported Versions
Currently all work on this project goes into the nightly builds.
4.2's RELEASE build is now quite old and should be considered legacy.
We urge users to use the nightly releases, because the project
constantly sees fixes.
| Version | Supported |
| ------------- | ------------------ |
| 4.2 Nightlies | :white_check_mark: |
| 4.2 Release | :x: |
| < 4.2 | :x: |
## Reporting a Vulnerability
Please file an issue in our GitHub's repo, because this app
isn't meant to be safe to run as public instance yet, but rather as a LAN facing app.
We welcome PRs and help in general in making YTDL-M more secure, but it's not a priority as of now.

View File

@@ -7,11 +7,17 @@
"root": "",
"sourceRoot": "src",
"projectType": "application",
"i18n": {
"sourceLocale": "en-US",
"locales": {
"es": "src/locale/messages.es.xlf"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"outputPath": "backend/public",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
@@ -26,16 +32,29 @@
"styles": [
"src/styles.scss"
],
"scripts": []
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true,
"allowedCommonJsDependencies": [
"rxjs",
"crypto-js"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
@@ -45,8 +64,12 @@
"with": "src/environments/environment.prod.ts"
}
]
},
"es": {
"localize": ["es"]
}
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
@@ -56,6 +79,9 @@
"configurations": {
"production": {
"browserTarget": "youtube-dl-material:build:production"
},
"es": {
"browserTarget": "youtube-dl-material:build:es"
}
}
},
@@ -135,16 +161,6 @@
"src/backend"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": []
}
}
}
},
@@ -159,15 +175,6 @@
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "youtube-dl-material:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"e2e/tsconfig.e2e.json"
],
"exclude": []
}
}
}
}
@@ -176,7 +183,7 @@
"schematics": {
"@schematics/angular:component": {
"prefix": "app",
"styleext": "scss"
"style": "scss"
},
"@schematics/angular:directive": {
"prefix": "app"

7
app.json Normal file
View File

@@ -0,0 +1,7 @@
{
"name": "YoutubeDL-Material",
"description": "An open-source and self-hosted YouTube downloader based on Google's Material Design specifications.",
"repository": "https://github.com/Tzahi12345/YoutubeDL-Material",
"logo": "https://i.imgur.com/GPzvPiU.png",
"keywords": ["youtube-dl", "youtubedl-material", "nodejs"]
}

49
armhf.Dockerfile Normal file
View File

@@ -0,0 +1,49 @@
FROM alpine:3.12 as frontend
RUN apk add --no-cache \
npm \
curl
RUN npm install -g @angular/cli
WORKDIR /build
RUN curl -L https://github.com/balena-io/qemu/releases/download/v3.0.0%2Bresin/qemu-3.0.0+resin-arm.tar.gz | tar zxvf - -C . && mv qemu-3.0.0+resin-arm/qemu-arm-static .
COPY [ "package.json", "package-lock.json", "/build/" ]
RUN npm install
COPY [ "angular.json", "tsconfig.json", "/build/" ]
COPY [ "src/", "/build/src/" ]
RUN ng build --prod
#--------------#
FROM arm32v7/alpine:3.12
COPY --from=frontend /build/qemu-arm-static /usr/bin
ENV UID=1000 \
GID=1000 \
USER=youtube
RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID
RUN apk add --no-cache \
ffmpeg \
npm \
python2 \
su-exec \
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
atomicparsley
WORKDIR /app
COPY --chown=$UID:$GID [ "backend/package.json", "backend/package-lock.json", "/app/" ]
RUN npm install && chown -R $UID:$GID ./
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
COPY --chown=$UID:$GID [ "/backend/", "/app/" ]
EXPOSE 17442
ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD [ "node", "app.js" ]

4
backend/.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
*.exe
docker-compose.yml
Dockerfile

18
backend/.eslintrc.json Normal file
View File

@@ -0,0 +1,18 @@
{
"env": {
"node": true,
"es2021": true
},
"extends": [
"eslint:recommended"
],
"parser": "esprima",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [],
"rules": {
},
"root": true
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
{
"YoutubeDLMaterial": {
"Host": {
"url": "http://example.com",
"port": "17442"
},
"Downloader": {
"path-audio": "audio/",
"path-video": "video/",
"default_file_output": "",
"use_youtubedl_archive": false,
"custom_args": "",
"safe_download_override": false,
"include_thumbnail": true,
"include_metadata": true,
"max_concurrent_downloads": 5,
"download_rate_limit": ""
},
"Extra": {
"title_top": "YoutubeDL-Material",
"file_manager_enabled": true,
"allow_quality_select": true,
"download_only_mode": false,
"allow_autoplay": true,
"enable_downloads_manager": true,
"allow_playlist_categorization": true
},
"API": {
"use_API_key": false,
"API_key": "",
"use_youtube_API": false,
"youtube_API_key": "",
"use_twitch_API": false,
"twitch_API_key": "",
"twitch_auto_download_chat": false,
"use_sponsorblock_API": false,
"generate_NFO_files": false
},
"Themes": {
"default_theme": "default",
"allow_theme_change": true
},
"Subscriptions": {
"allow_subscriptions": true,
"subscriptions_base_path": "subscriptions/",
"subscriptions_check_interval": "300",
"redownload_fresh_uploads": false
},
"Users": {
"base_path": "users/",
"allow_registration": true,
"auth_method": "internal",
"ldap_config": {
"url": "ldap://localhost:389",
"bindDN": "cn=root",
"bindCredentials": "secret",
"searchBase": "ou=passport-ldapauth",
"searchFilter": "(uid={{username}})"
}
},
"Database": {
"use_local_db": true,
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
},
"Advanced": {
"default_downloader": "youtube-dl",
"use_default_downloading_agent": true,
"custom_downloading_agent": "",
"multi_user_mode": false,
"allow_advanced_download": false,
"use_cookies": false,
"jwt_expiration": 86400,
"logger_level": "info"
}
}
}

Binary file not shown.

4
backend/audio/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@@ -0,0 +1,405 @@
const config_api = require('../config');
const consts = require('../consts');
const logger = require('../logger');
const db_api = require('../db');
const jwt = require('jsonwebtoken');
const { uuid } = require('uuidv4');
const bcrypt = require('bcryptjs');
var LocalStrategy = require('passport-local').Strategy;
var LdapStrategy = require('passport-ldapauth');
var JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
// other required vars
let SERVER_SECRET = null;
let JWT_EXPIRATION = null;
let opts = null;
let saltRounds = null;
exports.initialize = function() {
/*************************
* Authentication module
************************/
saltRounds = 10;
JWT_EXPIRATION = config_api.getConfigItem('ytdl_jwt_expiration');
SERVER_SECRET = null;
if (db_api.users_db.get('jwt_secret').value()) {
SERVER_SECRET = db_api.users_db.get('jwt_secret').value();
} else {
SERVER_SECRET = uuid();
db_api.users_db.set('jwt_secret', SERVER_SECRET).write();
}
opts = {}
opts.jwtFromRequest = ExtractJwt.fromUrlQueryParameter('jwt');
opts.secretOrKey = SERVER_SECRET;
exports.passport.use(new JwtStrategy(opts, async function(jwt_payload, done) {
const user = await db_api.getRecord('users', {uid: jwt_payload.user});
if (user) {
return done(null, user);
} else {
return done(null, false);
// or you could create a new account
}
}));
}
exports.passport = require('passport');
exports.passport.serializeUser(function(user, done) {
done(null, user);
});
exports.passport.deserializeUser(function(user, done) {
done(null, user);
});
/***************************************
* Register user with hashed password
**************************************/
exports.registerUser = async function(req, res) {
var userid = req.body.userid;
var username = req.body.username;
var plaintextPassword = req.body.password;
if (userid !== 'admin' && !config_api.getConfigItem('ytdl_allow_registration') && !req.isAuthenticated() && (!req.user || !exports.userHasPermission(req.user.uid, 'settings'))) {
res.sendStatus(409);
logger.error(`Registration failed for user ${userid}. Registration is disabled.`);
return;
}
if (plaintextPassword === "") {
res.sendStatus(400);
logger.error(`Registration failed for user ${userid}. A password must be provided.`);
return;
}
bcrypt.hash(plaintextPassword, saltRounds)
.then(async function(hash) {
let new_user = generateUserObject(userid, username, hash);
// check if user exists
if (await db_api.getRecord('users', {uid: userid})) {
// user id is taken!
logger.error('Registration failed: UID is already taken!');
res.status(409).send('UID is already taken!');
} else if (await db_api.getRecord('users', {name: username})) {
// user name is taken!
logger.error('Registration failed: User name is already taken!');
res.status(409).send('User name is already taken!');
} else {
// add to db
await db_api.insertRecordIntoTable('users', new_user);
logger.verbose(`New user created: ${new_user.name}`);
res.send({
user: new_user
});
}
})
.then(function(result) {
})
.catch(function(err) {
logger.error(err);
if( err.code == 'ER_DUP_ENTRY' ) {
res.status(409).send('UserId already taken');
} else {
res.sendStatus(409);
}
});
}
/***************************************
* Login methods
**************************************/
/*************************************************
* This gets called when passport.authenticate()
* gets called.
*
* This checks that the credentials are valid.
* If so, passes the user info to the next middleware.
************************************************/
exports.login = async (username, password) => {
const user = await db_api.getRecord('users', {name: username});
if (!user) { logger.error(`User ${username} not found`); return false }
if (user.auth_method && user.auth_method !== 'internal') { return false }
return await bcrypt.compare(password, user.passhash) ? user : false;
}
exports.passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'},
async function(username, password, done) {
return done(null, await exports.login(username, password));
}
));
var getLDAPConfiguration = function(req, callback) {
const ldap_config = config_api.getConfigItem('ytdl_ldap_config');
const opts = {server: ldap_config};
callback(null, opts);
};
exports.passport.use(new LdapStrategy(getLDAPConfiguration,
async function(user, done) {
// check if ldap auth is enabled
const ldap_enabled = config_api.getConfigItem('ytdl_auth_method') === 'ldap';
if (!ldap_enabled) return done(null, false);
const user_uid = user.uid;
let db_user = await db_api.getRecord('users', {uid: user_uid});
if (!db_user) {
// generate DB user
let new_user = generateUserObject(user_uid, user_uid, null, 'ldap');
await db_api.insertRecordIntoTable('users', new_user);
db_user = new_user;
logger.verbose(`Generated new user ${user_uid} using LDAP`);
}
return done(null, db_user);
}
));
/**********************************
* Generating/Signing a JWT token
* And attaches the user info into
* the payload to be sent on every
* request.
*********************************/
exports.generateJWT = function(req, res, next) {
var payload = {
exp: Math.floor(Date.now() / 1000) + JWT_EXPIRATION
, user: req.user.uid
};
req.token = jwt.sign(payload, SERVER_SECRET);
next();
}
exports.returnAuthResponse = async function(req, res) {
res.status(200).json({
user: req.user,
token: req.token,
permissions: await exports.userPermissions(req.user.uid),
available_permissions: consts['AVAILABLE_PERMISSIONS']
});
}
/***************************************
* Authorization: middleware that checks the
* JWT token for validity before allowing
* the user to access anything.
*
* It also passes the user object to the next
* middleware through res.locals
**************************************/
exports.ensureAuthenticatedElseError = (req, res, next) => {
var token = getToken(req.query);
if( token ) {
try {
var payload = jwt.verify(token, SERVER_SECRET);
// console.log('payload: ' + JSON.stringify(payload));
// check if user still exists in database if you'd like
res.locals.user = payload.user;
next();
} catch(err) {
res.status(401).send('Invalid Authentication');
}
} else {
res.status(401).send('Missing Authorization header');
}
}
// change password
exports.changeUserPassword = async (user_uid, new_pass) => {
try {
const hash = await bcrypt.hash(new_pass, saltRounds);
await db_api.updateRecord('users', {uid: user_uid}, {passhash: hash});
return true;
} catch (err) {
return false;
}
}
// change user permissions
exports.changeUserPermissions = async (user_uid, permission, new_value) => {
try {
await db_api.pullFromRecordsArray('users', {uid: user_uid}, 'permissions', permission);
await db_api.pullFromRecordsArray('users', {uid: user_uid}, 'permission_overrides', permission);
if (new_value === 'yes') {
await db_api.pushToRecordsArray('users', {uid: user_uid}, 'permissions', permission);
await db_api.pushToRecordsArray('users', {uid: user_uid}, 'permission_overrides', permission);
} else if (new_value === 'no') {
await db_api.pushToRecordsArray('users', {uid: user_uid}, 'permission_overrides', permission);
}
return true;
} catch (err) {
logger.error(err);
return false;
}
}
// change role permissions
exports.changeRolePermissions = async (role, permission, new_value) => {
try {
await db_api.pullFromRecordsArray('roles', {key: role}, 'permissions', permission);
if (new_value === 'yes') {
await db_api.pushToRecordsArray('roles', {key: role}, 'permissions', permission);
}
return true;
} catch (err) {
logger.error(err);
return false;
}
}
exports.adminExists = async function() {
return !!(await db_api.getRecord('users', {uid: 'admin'}));
}
// video stuff
exports.getUserVideos = async function(user_uid, type) {
const files = await db_api.getRecords('files', {user_uid: user_uid});
return type ? files.filter(file => file.isAudio === (type === 'audio')) : files;
}
exports.getUserVideo = async function(user_uid, file_uid, requireSharing = false) {
let file = await db_api.getRecord('files', {file_uid: file_uid});
// prevent unauthorized users from accessing the file info
if (file && !file['sharingEnabled'] && requireSharing) file = null;
return file;
}
exports.removePlaylist = async function(user_uid, playlistID) {
await db_api.removeRecord('playlist', {playlistID: playlistID});
return true;
}
exports.getUserPlaylists = async function(user_uid) {
return await db_api.getRecords('playlists', {user_uid: user_uid});
}
exports.getUserPlaylist = async function(user_uid, playlistID, requireSharing = false) {
let playlist = await db_api.getRecord('playlists', {id: playlistID});
// prevent unauthorized users from accessing the file info
if (requireSharing && !playlist['sharingEnabled']) playlist = null;
return playlist;
}
exports.changeSharingMode = async function(user_uid, file_uid, is_playlist, enabled) {
let success = false;
is_playlist ? await db_api.updateRecord(`playlists`, {id: file_uid}, {sharingEnabled: enabled}) : await db_api.updateRecord(`files`, {uid: file_uid}, {sharingEnabled: enabled});
success = true;
return success;
}
exports.userHasPermission = async function(user_uid, permission) {
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));
const role = user_obj['role'];
if (!role) {
// role doesn't exist
logger.error('Invalid role ' + role);
return false;
}
const role_permissions = (await db_api.getRecords('roles'))['permissions'];
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
// check if user has a negative/positive override
if (user_has_explicit_permission && permission_in_overrides) {
// positive override
return true;
} else if (!user_has_explicit_permission && permission_in_overrides) {
// negative override
return false;
}
// no overrides, let's check if the role has the permission
if (role_permissions.includes(permission)) {
return true;
} else {
logger.verbose(`User ${user_uid} failed to get permission ${permission}`);
return false;
}
}
exports.userPermissions = async function(user_uid) {
let user_permissions = [];
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));
const role = user_obj['role'];
if (!role) {
// role doesn't exist
logger.error('Invalid role ' + role);
return null;
}
const role_obj = await db_api.getRecord('roles', {key: role});
const role_permissions = role_obj['permissions'];
for (let i = 0; i < consts['AVAILABLE_PERMISSIONS'].length; i++) {
let permission = consts['AVAILABLE_PERMISSIONS'][i];
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
// check if user has a negative/positive override
if (user_has_explicit_permission && permission_in_overrides) {
// positive override
user_permissions.push(permission);
} else if (!user_has_explicit_permission && permission_in_overrides) {
// negative override
continue;
}
// no overrides, let's check if the role has the permission
if (role_permissions.includes(permission)) {
user_permissions.push(permission);
} else {
continue;
}
}
return user_permissions;
}
function getToken(queryParams) {
if (queryParams && queryParams.jwt) {
var parted = queryParams.jwt.split(' ');
if (parted.length === 2) {
return parted[1];
} else {
return null;
}
} else {
return null;
}
};
function generateUserObject(userid, username, hash, auth_method = 'internal') {
let new_user = {
name: username,
uid: userid,
passhash: auth_method === 'internal' ? hash : null,
files: [],
playlists: [],
subscriptions: [],
created: Date.now(),
role: userid === 'admin' && auth_method === 'internal' ? 'admin' : 'user',
permissions: [],
permission_overrides: [],
auth_method: auth_method
};
return new_user;
}

135
backend/categories.js Normal file
View File

@@ -0,0 +1,135 @@
const utils = require('./utils');
const logger = require('./logger');
const db_api = require('./db');
/*
Categories:
Categories are a way to organize videos based on dynamic rules set by the user. Categories are universal (so not per-user).
Categories, besides rules, have an optional custom output. This custom output can help users create their
desired directory structure.
Rules:
A category rule consists of a property, a comparison, and a value. For example, "uploader includes 'VEVO'"
Rules are stored as an object with the above fields. In addition to those fields, it also has a preceding_operator, which
is either OR or AND, and signifies whether the rule should be ANDed with the previous rules, or just ORed. For the first
rule, this field is null.
Ex. (title includes 'Rihanna' OR title includes 'Beyonce' AND uploader includes 'VEVO')
*/
async function categorize(file_jsons) {
// to make the logic easier, let's assume the file metadata is an array
if (!Array.isArray(file_jsons)) file_jsons = [file_jsons];
let selected_category = null;
const categories = await getCategories();
if (!categories) {
logger.warn('Categories could not be found.');
return null;
}
for (let i = 0; i < file_jsons.length; i++) {
const file_json = file_jsons[i];
for (let j = 0; j < categories.length; j++) {
const category = categories[j];
const rules = category['rules'];
// if rules for current category apply, then that is the selected category
if (applyCategoryRules(file_json, rules, category['name'])) {
selected_category = category;
logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`);
return selected_category;
}
}
}
return selected_category;
}
async function getCategories() {
const categories = await db_api.getRecords('categories');
return categories ? categories : null;
}
async function getCategoriesAsPlaylists(files = null) {
const categories_as_playlists = [];
const available_categories = await getCategories();
if (available_categories && files) {
for (let category of available_categories) {
const files_that_match = utils.addUIDsToCategory(category, files);
if (files_that_match && files_that_match.length > 0) {
category['thumbnailURL'] = files_that_match[0].thumbnailURL;
category['thumbnailPath'] = files_that_match[0].thumbnailPath;
category['duration'] = files_that_match.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
category['id'] = category['uid'];
categories_as_playlists.push(category);
}
}
}
return categories_as_playlists;
}
function applyCategoryRules(file_json, rules, category_name) {
let rules_apply = false;
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
let rule_applies = null;
let preceding_operator = rule['preceding_operator'];
switch (rule['comparator']) {
case 'includes':
rule_applies = file_json[rule['property']].toLowerCase().includes(rule['value'].toLowerCase());
break;
case 'not_includes':
rule_applies = !(file_json[rule['property']].toLowerCase().includes(rule['value'].toLowerCase()));
break;
case 'equals':
rule_applies = file_json[rule['property']] === rule['value'];
break;
case 'not_equals':
rule_applies = file_json[rule['property']] !== rule['value'];
break;
default:
logger.warn(`Invalid comparison used for category ${category_name}`)
break;
}
// OR the first rule with rules_apply, which will be initially false
if (i === 0) preceding_operator = 'or';
// update rules_apply based on current rule
if (preceding_operator === 'or')
rules_apply = rules_apply || rule_applies;
else
rules_apply = rules_apply && rule_applies;
}
return rules_apply;
}
// async function addTagToVideo(tag, video, user_uid) {
// // TODO: Implement
// }
// async function removeTagFromVideo(tag, video, user_uid) {
// // TODO: Implement
// }
// // adds tag to list of existing tags (used for tag suggestions)
// async function addTagToExistingTags(tag) {
// const existing_tags = db.get('tags').value();
// if (!existing_tags.includes(tag)) {
// db.get('tags').push(tag).write();
// }
// }
module.exports = {
categorize: categorize,
getCategories: getCategories,
getCategoriesAsPlaylists: getCategoriesAsPlaylists
}

251
backend/config.js Normal file
View File

@@ -0,0 +1,251 @@
const logger = require('./logger');
const fs = require('fs');
let CONFIG_ITEMS = require('./consts.js')['CONFIG_ITEMS'];
const debugMode = process.env.YTDL_MODE === 'debug';
let configPath = debugMode ? '../src/assets/default.json' : 'appdata/default.json';
function initialize() {
ensureConfigFileExists();
ensureConfigItemsExist();
}
function ensureConfigItemsExist() {
const config_keys = Object.keys(CONFIG_ITEMS);
for (let i = 0; i < config_keys.length; i++) {
const config_key = config_keys[i];
getConfigItem(config_key);
}
}
function ensureConfigFileExists() {
if (!fs.existsSync(configPath)) {
logger.info('Cannot find config file. Creating one with default values...');
fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2));
}
}
// https://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key
Object.byString = function(o, s) {
s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
s = s.replace(/^\./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
}
function getParentPath(path) {
let elements = path.split('.');
elements.splice(elements.length - 1, 1);
return elements.join('.');
}
function getElementNameInConfig(path) {
let elements = path.split('.');
return elements[elements.length - 1];
}
/**
* Check if config exists. If not, write default config to config path
*/
function configExistsCheck() {
let exists = fs.existsSync(configPath);
if (!exists) {
setConfigFile(DEFAULT_CONFIG);
}
}
/*
* Gets config file and returns as a json
*/
function getConfigFile() {
try {
let raw_data = fs.readFileSync(configPath);
let parsed_data = JSON.parse(raw_data);
return parsed_data;
} catch(e) {
logger.error('Failed to get config file');
return null;
}
}
function setConfigFile(config) {
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
return true;
} catch(e) {
return false;
}
}
function getConfigItem(key) {
let config_json = getConfigFile();
if (!CONFIG_ITEMS[key]) {
logger.error(`Config item with key '${key}' is not recognized.`);
return null;
}
let path = CONFIG_ITEMS[key]['path'];
const val = Object.byString(config_json, path);
if (val === undefined && Object.byString(DEFAULT_CONFIG, path) !== undefined) {
logger.warn(`Cannot find config with key '${key}'. Creating one with the default value...`);
setConfigItem(key, Object.byString(DEFAULT_CONFIG, path));
return Object.byString(DEFAULT_CONFIG, path);
}
return Object.byString(config_json, path);
}
function setConfigItem(key, value) {
let success = false;
let config_json = getConfigFile();
let path = CONFIG_ITEMS[key]['path'];
let element_name = getElementNameInConfig(path);
let parent_path = getParentPath(path);
let parent_object = Object.byString(config_json, parent_path);
if (!parent_object) {
let parent_parent_path = getParentPath(parent_path);
let parent_parent_object = Object.byString(config_json, parent_parent_path);
let parent_path_arr = parent_path.split('.');
let parent_parent_single_key = parent_path_arr[parent_path_arr.length-1];
parent_parent_object[parent_parent_single_key] = {};
parent_object = Object.byString(config_json, parent_path);
}
if (value === 'false' || value === 'true') {
parent_object[element_name] = (value === 'true');
} else {
parent_object[element_name] = value;
}
success = setConfigFile(config_json);
return success;
};
function setConfigItems(items) {
let success = false;
let config_json = getConfigFile();
for (let i = 0; i < items.length; i++) {
let key = items[i].key;
let value = items[i].value;
// if boolean strings, set to booleans again
if (value === 'false' || value === 'true') {
value = (value === 'true');
}
let item_path = CONFIG_ITEMS[key]['path'];
let item_parent_path = getParentPath(item_path);
let item_element_name = getElementNameInConfig(item_path);
let item_parent_object = Object.byString(config_json, item_parent_path);
item_parent_object[item_element_name] = value;
}
success = setConfigFile(config_json);
return success;
}
function globalArgsRequiresSafeDownload() {
const globalArgs = getConfigItem('ytdl_custom_args').split(',,');
const argsThatRequireSafeDownload = ['--write-sub', '--write-srt', '--proxy'];
const failedArgs = globalArgs.filter(arg => argsThatRequireSafeDownload.includes(arg));
return failedArgs && failedArgs.length > 0;
}
module.exports = {
getConfigItem: getConfigItem,
setConfigItem: setConfigItem,
setConfigItems: setConfigItems,
getConfigFile: getConfigFile,
setConfigFile: setConfigFile,
configExistsCheck: configExistsCheck,
CONFIG_ITEMS: CONFIG_ITEMS,
initialize: initialize,
descriptors: {},
globalArgsRequiresSafeDownload: globalArgsRequiresSafeDownload
}
const DEFAULT_CONFIG = {
"YoutubeDLMaterial": {
"Host": {
"url": "http://example.com",
"port": "17442"
},
"Downloader": {
"path-audio": "audio/",
"path-video": "video/",
"default_file_output": "",
"use_youtubedl_archive": false,
"custom_args": "",
"safe_download_override": false,
"include_thumbnail": true,
"include_metadata": true,
"max_concurrent_downloads": 5,
"download_rate_limit": ""
},
"Extra": {
"title_top": "YoutubeDL-Material",
"file_manager_enabled": true,
"allow_quality_select": true,
"download_only_mode": false,
"allow_autoplay": true,
"enable_downloads_manager": true,
"allow_playlist_categorization": true
},
"API": {
"use_API_key": false,
"API_key": "",
"use_youtube_API": false,
"youtube_API_key": "",
"use_twitch_API": false,
"twitch_API_key": "",
"twitch_auto_download_chat": false,
"use_sponsorblock_API": false,
"generate_NFO_files": false
},
"Themes": {
"default_theme": "default",
"allow_theme_change": true
},
"Subscriptions": {
"allow_subscriptions": true,
"subscriptions_base_path": "subscriptions/",
"subscriptions_check_interval": "86400",
"redownload_fresh_uploads": false
},
"Users": {
"base_path": "users/",
"allow_registration": true,
"auth_method": "internal",
"ldap_config": {
"url": "ldap://localhost:389",
"bindDN": "cn=root",
"bindCredentials": "secret",
"searchBase": "ou=passport-ldapauth",
"searchFilter": "(uid={{username}})"
}
},
"Database": {
"use_local_db": true,
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
},
"Advanced": {
"default_downloader": "youtube-dl",
"use_default_downloading_agent": true,
"custom_downloading_agent": "",
"multi_user_mode": false,
"allow_advanced_download": false,
"use_cookies": false,
"jwt_expiration": 86400,
"logger_level": "info"
}
}
}

View File

@@ -1,32 +0,0 @@
{
"YoutubeDLMaterial": {
"Host": {
"frontendurl": "http://example.com",
"backendurl": "http://example.com:17442/"
},
"Encryption": {
"use-encryption": false,
"cert-file-path": "/etc/letsencrypt/live/example.com/fullchain.pem",
"key-file-path": "/etc/letsencrypt/live/example.com/privkey.pem"
},
"Downloader": {
"path-base": "http://example.com:17442/",
"path-audio": "audio/",
"path-video": "video/"
},
"Extra": {
"title_top": "Youtube Downloader",
"file_manager_enabled": true,
"allow_quality_select": true,
"download_only_mode": false
},
"API": {
"use_youtube_API": false,
"youtube_API_key": ""
},
"Themes": {
"default_theme": "default",
"allow_theme_change": true
}
}
}

View File

@@ -1,32 +0,0 @@
{
"YoutubeDLMaterial": {
"Host": {
"frontendurl": "https://example.com",
"backendurl": "https://example.com:17442/"
},
"Encryption": {
"use-encryption": true,
"cert-file-path": "/etc/letsencrypt/live/example.com/fullchain.pem",
"key-file-path": "/etc/letsencrypt/live/example.com/privkey.pem"
},
"Downloader": {
"path-base": "https://example.com:17442/",
"path-audio": "audio/",
"path-video": "video/"
},
"Extra": {
"title_top": "Youtube Downloader",
"file_manager_enabled": true,
"allow_quality_select": true,
"download_only_mode": false
},
"API": {
"use_youtube_API": false,
"youtube_API_key": ""
},
"Themes": {
"default_theme": "default",
"allow_theme_change": true
}
}
}

225
backend/consts.js Normal file
View File

@@ -0,0 +1,225 @@
exports.CONFIG_ITEMS = {
// Host
'ytdl_url': {
'key': 'ytdl_url',
'path': 'YoutubeDLMaterial.Host.url'
},
'ytdl_port': {
'key': 'ytdl_port',
'path': 'YoutubeDLMaterial.Host.port'
},
// Downloader
'ytdl_audio_folder_path': {
'key': 'ytdl_audio_folder_path',
'path': 'YoutubeDLMaterial.Downloader.path-audio'
},
'ytdl_video_folder_path': {
'key': 'ytdl_video_folder_path',
'path': 'YoutubeDLMaterial.Downloader.path-video'
},
'ytdl_default_file_output': {
'key': 'ytdl_default_file_output',
'path': 'YoutubeDLMaterial.Downloader.default_file_output'
},
'ytdl_use_youtubedl_archive': {
'key': 'ytdl_use_youtubedl_archive',
'path': 'YoutubeDLMaterial.Downloader.use_youtubedl_archive'
},
'ytdl_custom_args': {
'key': 'ytdl_custom_args',
'path': 'YoutubeDLMaterial.Downloader.custom_args'
},
'ytdl_safe_download_override': {
'key': 'ytdl_safe_download_override',
'path': 'YoutubeDLMaterial.Downloader.safe_download_override'
},
'ytdl_include_thumbnail': {
'key': 'ytdl_include_thumbnail',
'path': 'YoutubeDLMaterial.Downloader.include_thumbnail'
},
'ytdl_include_metadata': {
'key': 'ytdl_include_metadata',
'path': 'YoutubeDLMaterial.Downloader.include_metadata'
},
'ytdl_max_concurrent_downloads': {
'key': 'ytdl_max_concurrent_downloads',
'path': 'YoutubeDLMaterial.Downloader.max_concurrent_downloads'
},
'ytdl_download_rate_limit': {
'key': 'ytdl_download_rate_limit',
'path': 'YoutubeDLMaterial.Downloader.download_rate_limit'
},
// Extra
'ytdl_title_top': {
'key': 'ytdl_title_top',
'path': 'YoutubeDLMaterial.Extra.title_top'
},
'ytdl_file_manager_enabled': {
'key': 'ytdl_file_manager_enabled',
'path': 'YoutubeDLMaterial.Extra.file_manager_enabled'
},
'ytdl_allow_quality_select': {
'key': 'ytdl_allow_quality_select',
'path': 'YoutubeDLMaterial.Extra.allow_quality_select'
},
'ytdl_download_only_mode': {
'key': 'ytdl_download_only_mode',
'path': 'YoutubeDLMaterial.Extra.download_only_mode'
},
'ytdl_allow_autoplay': {
'key': 'ytdl_allow_autoplay',
'path': 'YoutubeDLMaterial.Extra.allow_autoplay'
},
'ytdl_enable_downloads_manager': {
'key': 'ytdl_enable_downloads_manager',
'path': 'YoutubeDLMaterial.Extra.enable_downloads_manager'
},
'ytdl_allow_playlist_categorization': {
'key': 'ytdl_allow_playlist_categorization',
'path': 'YoutubeDLMaterial.Extra.allow_playlist_categorization'
},
// API
'ytdl_use_api_key': {
'key': 'ytdl_use_api_key',
'path': 'YoutubeDLMaterial.API.use_API_key'
},
'ytdl_api_key': {
'key': 'ytdl_api_key',
'path': 'YoutubeDLMaterial.API.API_key'
},
'ytdl_use_youtube_api': {
'key': 'ytdl_use_youtube_api',
'path': 'YoutubeDLMaterial.API.use_youtube_API'
},
'ytdl_youtube_api_key': {
'key': 'ytdl_youtube_api_key',
'path': 'YoutubeDLMaterial.API.youtube_API_key'
},
'ytdl_use_twitch_api': {
'key': 'ytdl_use_twitch_api',
'path': 'YoutubeDLMaterial.API.use_twitch_API'
},
'ytdl_twitch_api_key': {
'key': 'ytdl_twitch_api_key',
'path': 'YoutubeDLMaterial.API.twitch_API_key'
},
'ytdl_twitch_auto_download_chat': {
'key': 'ytdl_twitch_auto_download_chat',
'path': 'YoutubeDLMaterial.API.twitch_auto_download_chat'
},
'ytdl_use_sponsorblock_api': {
'key': 'ytdl_use_sponsorblock_api',
'path': 'YoutubeDLMaterial.API.use_sponsorblock_API'
},
'ytdl_generate_nfo_files': {
'key': 'ytdl_generate_nfo_files',
'path': 'YoutubeDLMaterial.API.generate_NFO_files'
},
// Themes
'ytdl_default_theme': {
'key': 'ytdl_default_theme',
'path': 'YoutubeDLMaterial.Themes.default_theme'
},
'ytdl_allow_theme_change': {
'key': 'ytdl_allow_theme_change',
'path': 'YoutubeDLMaterial.Themes.allow_theme_change'
},
// Subscriptions
'ytdl_allow_subscriptions': {
'key': 'ytdl_allow_subscriptions',
'path': 'YoutubeDLMaterial.Subscriptions.allow_subscriptions'
},
'ytdl_subscriptions_base_path': {
'key': 'ytdl_subscriptions_base_path',
'path': 'YoutubeDLMaterial.Subscriptions.subscriptions_base_path'
},
'ytdl_subscriptions_check_interval': {
'key': 'ytdl_subscriptions_check_interval',
'path': 'YoutubeDLMaterial.Subscriptions.subscriptions_check_interval'
},
'ytdl_subscriptions_redownload_fresh_uploads': {
'key': 'ytdl_subscriptions_redownload_fresh_uploads',
'path': 'YoutubeDLMaterial.Subscriptions.redownload_fresh_uploads'
},
// Users
'ytdl_users_base_path': {
'key': 'ytdl_users_base_path',
'path': 'YoutubeDLMaterial.Users.base_path'
},
'ytdl_allow_registration': {
'key': 'ytdl_allow_registration',
'path': 'YoutubeDLMaterial.Users.allow_registration'
},
'ytdl_auth_method': {
'key': 'ytdl_auth_method',
'path': 'YoutubeDLMaterial.Users.auth_method'
},
'ytdl_ldap_config': {
'key': 'ytdl_ldap_config',
'path': 'YoutubeDLMaterial.Users.ldap_config'
},
// Database
'ytdl_use_local_db': {
'key': 'ytdl_use_local_db',
'path': 'YoutubeDLMaterial.Database.use_local_db'
},
'ytdl_mongodb_connection_string': {
'key': 'ytdl_mongodb_connection_string',
'path': 'YoutubeDLMaterial.Database.mongodb_connection_string'
},
// Advanced
'ytdl_default_downloader': {
'key': 'ytdl_default_downloader',
'path': 'YoutubeDLMaterial.Advanced.default_downloader'
},
'ytdl_use_default_downloading_agent': {
'key': 'ytdl_use_default_downloading_agent',
'path': 'YoutubeDLMaterial.Advanced.use_default_downloading_agent'
},
'ytdl_custom_downloading_agent': {
'key': 'ytdl_custom_downloading_agent',
'path': 'YoutubeDLMaterial.Advanced.custom_downloading_agent'
},
'ytdl_multi_user_mode': {
'key': 'ytdl_multi_user_mode',
'path': 'YoutubeDLMaterial.Advanced.multi_user_mode'
},
'ytdl_allow_advanced_download': {
'key': 'ytdl_allow_advanced_download',
'path': 'YoutubeDLMaterial.Advanced.allow_advanced_download'
},
'ytdl_use_cookies': {
'key': 'ytdl_use_cookies',
'path': 'YoutubeDLMaterial.Advanced.use_cookies'
},
'ytdl_jwt_expiration': {
'key': 'ytdl_jwt_expiration',
'path': 'YoutubeDLMaterial.Advanced.jwt_expiration'
},
'ytdl_logger_level': {
'key': 'ytdl_logger_level',
'path': 'YoutubeDLMaterial.Advanced.logger_level'
}
};
exports.AVAILABLE_PERMISSIONS = [
'filemanager',
'settings',
'subscriptions',
'sharing',
'advanced_download',
'downloads_manager'
];
exports.DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
exports.CURRENT_VERSION = 'v4.2';

1114
backend/db.js Normal file

File diff suppressed because it is too large Load Diff

630
backend/downloader.js Normal file
View File

@@ -0,0 +1,630 @@
const fs = require('fs-extra');
const { uuid } = require('uuidv4');
const path = require('path');
const mergeFiles = require('merge-files');
const NodeID3 = require('node-id3')
const Mutex = require('async-mutex').Mutex;
const youtubedl = require('youtube-dl');
const logger = require('./logger');
const config_api = require('./config');
const twitch_api = require('./twitch');
const { create } = require('xmlbuilder2');
const categories_api = require('./categories');
const utils = require('./utils');
const db_api = require('./db');
const mutex = new Mutex();
let should_check_downloads = true;
const archivePath = path.join(__dirname, 'appdata', 'archives');
if (db_api.database_initialized) {
setupDownloads();
} else {
db_api.database_initialized_bs.subscribe(init => {
if (init) setupDownloads();
});
}
exports.createDownload = async (url, type, options, user_uid = null, sub_id = null, sub_name = null) => {
return await mutex.runExclusive(async () => {
const download = {
url: url,
type: type,
title: '',
user_uid: user_uid,
sub_id: sub_id,
sub_name: sub_name,
options: options,
uid: uuid(),
step_index: 0,
paused: false,
running: false,
finished_step: true,
error: null,
percent_complete: null,
finished: false,
timestamp_start: Date.now()
};
await db_api.insertRecordIntoTable('download_queue', download);
should_check_downloads = true;
return download;
});
}
exports.pauseDownload = async (download_uid) => {
const download = await db_api.getRecord('download_queue', {uid: download_uid});
if (download['paused']) {
logger.warn(`Download ${download_uid} is already paused!`);
return false;
} else if (download['finished']) {
logger.info(`Download ${download_uid} could not be paused before completing.`);
return false;
}
return await db_api.updateRecord('download_queue', {uid: download_uid}, {paused: true, running: false});
}
exports.resumeDownload = async (download_uid) => {
return await mutex.runExclusive(async () => {
const download = await db_api.getRecord('download_queue', {uid: download_uid});
if (!download['paused']) {
logger.warn(`Download ${download_uid} is not paused!`);
return false;
}
const success = db_api.updateRecord('download_queue', {uid: download_uid}, {paused: false});
should_check_downloads = true;
return success;
})
}
exports.restartDownload = async (download_uid) => {
const download = await db_api.getRecord('download_queue', {uid: download_uid});
await exports.clearDownload(download_uid);
const success = !!(await exports.createDownload(download['url'], download['type'], download['options'], download['user_uid']));
should_check_downloads = true;
return success;
}
exports.cancelDownload = async (download_uid) => {
const download = await db_api.getRecord('download_queue', {uid: download_uid});
if (download['cancelled']) {
logger.warn(`Download ${download_uid} is already cancelled!`);
return false;
} else if (download['finished']) {
logger.info(`Download ${download_uid} could not be cancelled before completing.`);
return false;
}
return await db_api.updateRecord('download_queue', {uid: download_uid}, {cancelled: true, running: false});
}
exports.clearDownload = async (download_uid) => {
return await db_api.removeRecord('download_queue', {uid: download_uid});
}
async function handleDownloadError(download_uid, error_message) {
await db_api.updateRecord('download_queue', {uid: download_uid}, {error: error_message, finished: true, running: false});
}
async function setupDownloads() {
await fixDownloadState();
setInterval(checkDownloads, 1000);
}
async function fixDownloadState() {
const downloads = await db_api.getRecords('download_queue');
downloads.sort((download1, download2) => download1.timestamp_start - download2.timestamp_start);
const running_downloads = downloads.filter(download => !download['finished'] && !download['error']);
for (let i = 0; i < running_downloads.length; i++) {
const running_download = running_downloads[i];
const update_obj = {finished_step: true, paused: true, running: false};
if (running_download['step_index'] > 0) {
update_obj['step_index'] = running_download['step_index'] - 1;
}
await db_api.updateRecord('download_queue', {uid: running_download['uid']}, update_obj);
}
}
async function checkDownloads() {
if (!should_check_downloads) return;
const downloads = await db_api.getRecords('download_queue');
downloads.sort((download1, download2) => download1.timestamp_start - download2.timestamp_start);
await mutex.runExclusive(async () => {
// avoid checking downloads unnecessarily, but double check that should_check_downloads is still true
const running_downloads = downloads.filter(download => !download['paused'] && !download['finished']);
if (running_downloads.length === 0) {
should_check_downloads = false;
logger.verbose('Disabling checking downloads as none are available.');
}
return;
});
let running_downloads_count = downloads.filter(download => download['running']).length;
const waiting_downloads = downloads.filter(download => !download['paused'] && download['finished_step'] && !download['finished']);
for (let i = 0; i < waiting_downloads.length; i++) {
const waiting_download = waiting_downloads[i];
const max_concurrent_downloads = config_api.getConfigItem('ytdl_max_concurrent_downloads');
if (max_concurrent_downloads < 0 || running_downloads_count >= max_concurrent_downloads) break;
if (waiting_download['finished_step'] && !waiting_download['finished']) {
// move to next step
running_downloads_count++;
if (waiting_download['step_index'] === 0) {
collectInfo(waiting_download['uid']);
} else if (waiting_download['step_index'] === 1) {
downloadQueuedFile(waiting_download['uid']);
}
}
}
}
async function collectInfo(download_uid) {
const download = await db_api.getRecord('download_queue', {uid: download_uid});
if (download['paused']) {
return;
}
logger.verbose(`Collecting info for download ${download_uid}`);
await db_api.updateRecord('download_queue', {uid: download_uid}, {step_index: 1, finished_step: false, running: true});
const url = download['url'];
const type = download['type'];
const options = download['options'];
if (download['user_uid'] && !options.customFileFolderPath) {
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
const user_path = path.join(usersFileFolder, download['user_uid'], type);
options.customFileFolderPath = user_path + path.sep;
}
let args = await exports.generateArgs(url, type, options, download['user_uid']);
// get video info prior to download
let info = await getVideoInfoByURL(url, args, download_uid);
if (!info) {
// info failed, error presumably already recorded
return;
}
let category = null;
// check if it fits into a category. If so, then get info again using new args
if (!Array.isArray(info) || config_api.getConfigItem('ytdl_allow_playlist_categorization')) category = await categories_api.categorize(info);
// set custom output if the category has one and re-retrieve info so the download manager has the right file name
if (category && category['custom_output']) {
options.customOutput = category['custom_output'];
options.noRelativePath = true;
args = await exports.generateArgs(url, type, options, download['user_uid']);
info = await getVideoInfoByURL(url, args, download_uid);
}
// setup info required to calculate download progress
const expected_file_size = utils.getExpectedFileSize(info);
const files_to_check_for_progress = [];
// store info in download for future use
if (Array.isArray(info)) {
for (let info_obj of info) files_to_check_for_progress.push(utils.removeFileExtension(info_obj['_filename']));
} else {
files_to_check_for_progress.push(utils.removeFileExtension(info['_filename']));
}
const playlist_title = Array.isArray(info) ? info[0]['playlist_title'] || info[0]['playlist'] : null;
await db_api.updateRecord('download_queue', {uid: download_uid}, {args: args,
finished_step: true,
running: false,
options: options,
files_to_check_for_progress: files_to_check_for_progress,
expected_file_size: expected_file_size,
title: playlist_title ? playlist_title : info['title']
});
}
async function downloadQueuedFile(download_uid) {
const download = await db_api.getRecord('download_queue', {uid: download_uid});
if (download['paused']) {
return;
}
logger.verbose(`Downloading ${download_uid}`);
return new Promise(async resolve => {
const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path');
const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path');
await db_api.updateRecord('download_queue', {uid: download_uid}, {step_index: 2, finished_step: false, running: true});
const url = download['url'];
const type = download['type'];
const options = download['options'];
const args = download['args'];
const category = download['category'];
let fileFolderPath = type === 'audio' ? audioFolderPath : videoFolderPath; // TODO: fix
if (options.customFileFolderPath) {
fileFolderPath = options.customFileFolderPath;
}
fs.ensureDirSync(fileFolderPath);
const start_time = Date.now();
const download_checker = setInterval(() => checkDownloadPercent(download['uid']), 1000);
// download file
youtubedl.exec(url, args, {maxBuffer: Infinity}, async function(err, output) {
const file_objs = [];
let end_time = Date.now();
let difference = (end_time - start_time)/1000;
logger.debug(`${type === 'audio' ? 'Audio' : 'Video'} download delay: ${difference} seconds.`);
clearInterval(download_checker);
if (err) {
logger.error(err.stderr);
await handleDownloadError(download_uid, err.stderr);
resolve(false);
return;
} else if (output) {
if (output.length === 0 || output[0].length === 0) {
// ERROR!
const error_message = `No output received for video download, check if it exists in your archive.`;
await handleDownloadError(download_uid, error_message);
logger.warn(error_message);
resolve(false);
return;
}
for (let i = 0; i < output.length; i++) {
let output_json = null;
try {
output_json = JSON.parse(output[i]);
} catch(e) {
output_json = null;
}
if (!output_json) {
continue;
}
// get filepath with no extension
const filepath_no_extension = utils.removeFileExtension(output_json['_filename']);
const ext = type === 'audio' ? '.mp3' : '.mp4';
var full_file_path = filepath_no_extension + ext;
var file_name = filepath_no_extension.substring(fileFolderPath.length, filepath_no_extension.length);
if (type === 'video' && url.includes('twitch.tv/videos/') && url.split('twitch.tv/videos/').length > 1
&& config_api.getConfigItem('ytdl_use_twitch_api') && config_api.getConfigItem('ytdl_twitch_auto_download_chat')) {
let vodId = url.split('twitch.tv/videos/')[1];
vodId = vodId.split('?')[0];
twitch_api.downloadTwitchChatByVODID(vodId, file_name, type, download['user_uid']);
}
// renames file if necessary due to bug
if (!fs.existsSync(output_json['_filename']) && fs.existsSync(output_json['_filename'] + '.webm')) {
try {
fs.renameSync(output_json['_filename'] + '.webm', output_json['_filename']);
logger.info('Renamed ' + file_name + '.webm to ' + file_name);
} catch(e) {
logger.error(`Failed to rename file ${output_json['_filename']} to its appropriate extension.`);
}
}
if (type === 'audio') {
let tags = {
title: output_json['title'],
artist: output_json['artist'] ? output_json['artist'] : output_json['uploader']
}
let success = NodeID3.write(tags, utils.removeFileExtension(output_json['_filename']) + '.mp3');
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + output_json['_filename']);
}
if (config_api.getConfigItem('ytdl_generate_nfo_files')) {
exports.generateNFOFile(output_json, `${filepath_no_extension}.nfo`);
}
if (options.cropFileSettings) {
await utils.cropFile(full_file_path, options.cropFileSettings.cropFileStart, options.cropFileSettings.cropFileEnd, ext);
}
// registers file in DB
const file_obj = await db_api.registerFileDB(full_file_path, type, download['user_uid'], category, download['sub_id'] ? download['sub_id'] : null, options.cropFileSettings);
file_objs.push(file_obj);
}
if (options.merged_string !== null && options.merged_string !== undefined) {
const archive_folder = getArchiveFolder(fileFolderPath, options, download['user_uid']);
const current_merged_archive = fs.readFileSync(path.join(archive_folder, `merged_${type}.txt`), 'utf8');
const diff = current_merged_archive.replace(options.merged_string, '');
const archive_path = path.join(archive_folder, `archive_${type}.txt`);
fs.appendFileSync(archive_path, diff);
}
let container = null;
if (file_objs.length > 1) {
// create playlist
const playlist_name = file_objs.map(file_obj => file_obj.title).join(', ');
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, download['user_uid']);
} else if (file_objs.length === 1) {
container = file_objs[0];
} else {
const error_message = 'Downloaded file failed to result in metadata object.';
logger.error(error_message);
await handleDownloadError(download_uid, error_message);
}
const file_uids = file_objs.map(file_obj => file_obj.uid);
await db_api.updateRecord('download_queue', {uid: download_uid}, {finished_step: true, finished: true, running: false, step_index: 3, percent_complete: 100, file_uids: file_uids, container: container});
resolve();
}
});
});
}
// helper functions
exports.generateArgs = async (url, type, options, user_uid = null, simulated = false) => {
const audioFolderPath = config_api.getConfigItem('ytdl_audio_folder_path');
const videoFolderPath = config_api.getConfigItem('ytdl_video_folder_path');
const videopath = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
const globalArgs = config_api.getConfigItem('ytdl_custom_args');
const useCookies = config_api.getConfigItem('ytdl_use_cookies');
const is_audio = type === 'audio';
let fileFolderPath = is_audio ? audioFolderPath : videoFolderPath;
if (options.customFileFolderPath) fileFolderPath = options.customFileFolderPath;
const customArgs = options.customArgs;
let customOutput = options.customOutput;
const customQualityConfiguration = options.customQualityConfiguration;
// video-specific args
const selectedHeight = options.selectedHeight;
// audio-specific args
const maxBitrate = options.maxBitrate;
const youtubeUsername = options.youtubeUsername;
const youtubePassword = options.youtubePassword;
let downloadConfig = null;
let qualityPath = (is_audio && !options.skip_audio_args) ? ['-f', 'bestaudio'] : ['-f', 'bestvideo+bestaudio', '--merge-output-format', 'mp4'];
const is_youtube = url.includes('youtu');
if (!is_audio && !is_youtube) {
// tiktok videos fail when using the default format
qualityPath = null;
} else if (!is_audio && !is_youtube && (url.includes('reddit') || url.includes('pornhub'))) {
qualityPath = ['-f', 'bestvideo+bestaudio']
}
if (customArgs) {
downloadConfig = customArgs.split(',,');
} else {
if (customQualityConfiguration) {
qualityPath = ['-f', customQualityConfiguration, '--merge-output-format', 'mp4'];
} else if (selectedHeight && selectedHeight !== '' && !is_audio) {
qualityPath = ['-f', `'(mp4)[height=${selectedHeight}'`];
} else if (is_audio) {
qualityPath = ['--audio-quality', maxBitrate ? maxBitrate : '0']
}
if (customOutput) {
customOutput = options.noRelativePath ? customOutput : path.join(fileFolderPath, customOutput);
downloadConfig = ['-o', `${customOutput}.%(ext)s`, '--write-info-json', '--print-json'];
} else {
downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), '--write-info-json', '--print-json'];
}
if (qualityPath) downloadConfig.push(...qualityPath);
if (is_audio && !options.skip_audio_args) {
downloadConfig.push('-x');
downloadConfig.push('--audio-format', 'mp3');
}
if (youtubeUsername && youtubePassword) {
downloadConfig.push('--username', youtubeUsername, '--password', youtubePassword);
}
if (useCookies) {
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
} else {
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
}
}
const useDefaultDownloadingAgent = config_api.getConfigItem('ytdl_use_default_downloading_agent');
const customDownloadingAgent = config_api.getConfigItem('ytdl_custom_downloading_agent');
if (!useDefaultDownloadingAgent && customDownloadingAgent) {
downloadConfig.splice(0, 0, '--external-downloader', customDownloadingAgent);
}
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
if (useYoutubeDLArchive) {
const archive_folder = getArchiveFolder(fileFolderPath, options, user_uid);
const archive_path = path.join(archive_folder, `archive_${type}.txt`);
await fs.ensureDir(archive_folder);
await fs.ensureFile(archive_path);
const blacklist_path = path.join(archive_folder, `blacklist_${type}.txt`);
await fs.ensureFile(blacklist_path);
const merged_path = path.join(archive_folder, `merged_${type}.txt`);
await fs.ensureFile(merged_path);
// merges blacklist and regular archive
let inputPathList = [archive_path, blacklist_path];
await mergeFiles(inputPathList, merged_path);
options.merged_string = await fs.readFile(merged_path, "utf8");
downloadConfig.push('--download-archive', merged_path);
}
if (config_api.getConfigItem('ytdl_include_thumbnail')) {
downloadConfig.push('--write-thumbnail');
}
if (globalArgs && globalArgs !== '') {
// adds global args
if (downloadConfig.indexOf('-o') !== -1 && globalArgs.split(',,').indexOf('-o') !== -1) {
// if global args has an output, replce the original output with that of global args
const original_output_index = downloadConfig.indexOf('-o');
downloadConfig.splice(original_output_index, 2);
}
downloadConfig = downloadConfig.concat(globalArgs.split(',,'));
}
if (options.additionalArgs && options.additionalArgs !== '') {
downloadConfig = downloadConfig.concat(options.additionalArgs.split(',,'));
}
const rate_limit = config_api.getConfigItem('ytdl_download_rate_limit');
if (rate_limit && downloadConfig.indexOf('-r') === -1 && downloadConfig.indexOf('--limit-rate') === -1) {
downloadConfig.push('-r', rate_limit);
}
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
if (default_downloader === 'yt-dlp') {
downloadConfig.push('--no-clean-infojson');
}
}
// filter out incompatible args
downloadConfig = filterArgs(downloadConfig, is_audio);
if (!simulated) logger.verbose(`youtube-dl args being used: ${downloadConfig.join(',')}`);
return downloadConfig;
}
async function getVideoInfoByURL(url, args = [], download_uid = null) {
return new Promise(resolve => {
// remove bad args
const new_args = [...args];
const archiveArgIndex = new_args.indexOf('--download-archive');
if (archiveArgIndex !== -1) {
new_args.splice(archiveArgIndex, 2);
}
new_args.push('--dump-json');
youtubedl.exec(url, new_args, {maxBuffer: Infinity}, async (err, output) => {
if (output) {
let outputs = [];
try {
for (let i = 0; i < output.length; i++) {
let output_json = null;
try {
output_json = JSON.parse(output[i]);
} catch(e) {
output_json = null;
}
if (!output_json) {
continue;
}
outputs.push(output_json);
}
resolve(outputs.length === 1 ? outputs[0] : outputs);
} catch(e) {
const error = `Error while retrieving info on video with URL ${url} with the following message: output JSON could not be parsed. Output JSON: ${output}`;
logger.error(error);
if (download_uid) {
await handleDownloadError(download_uid, error);
}
resolve(null);
}
} else {
let error_message = `Error while retrieving info on video with URL ${url} with the following message: ${err}`;
if (err.stderr) error_message += `\n\n${err.stderr}`;
logger.error(error_message);
if (download_uid) {
await handleDownloadError(download_uid, error_message);
}
resolve(null);
}
});
});
}
function filterArgs(args, isAudio) {
const video_only_args = ['--add-metadata', '--embed-subs', '--xattrs'];
const audio_only_args = ['-x', '--extract-audio', '--embed-thumbnail'];
const args_to_remove = isAudio ? video_only_args : audio_only_args;
return args.filter(x => !args_to_remove.includes(x));
}
async function checkDownloadPercent(download_uid) {
/*
This is more of an art than a science, we're just selecting files that start with the file name,
thus capturing the parts being downloaded in files named like so: '<video title>.<format>.<ext>.part'.
Any file that starts with <video title> will be counted as part of the "bytes downloaded", which will
be divided by the "total expected bytes."
*/
const download = await db_api.getRecord('download_queue', {uid: download_uid});
const files_to_check_for_progress = download['files_to_check_for_progress'];
const resulting_file_size = download['expected_file_size'];
if (!resulting_file_size) return;
let sum_size = 0;
for (let i = 0; i < files_to_check_for_progress.length; i++) {
const file_to_check_for_progress = files_to_check_for_progress[i];
const dir = path.dirname(file_to_check_for_progress);
if (!fs.existsSync(dir)) continue;
fs.readdir(dir, async (err, files) => {
for (let j = 0; j < files.length; j++) {
const file = files[j];
if (!file.includes(path.basename(file_to_check_for_progress))) continue;
try {
const file_stats = fs.statSync(path.join(dir, file));
if (file_stats && file_stats.size) {
sum_size += file_stats.size;
}
} catch (e) {}
}
const percent_complete = (sum_size/resulting_file_size * 100).toFixed(2);
await db_api.updateRecord('download_queue', {uid: download_uid}, {percent_complete: percent_complete});
});
}
}
exports.generateNFOFile = (info, output_path) => {
const nfo_obj = {
episodedetails: {
title: info['fulltitle'],
episode: info['playlist_index'] ? info['playlist_index'] : undefined,
premiered: utils.formatDateString(info['upload_date']),
plot: `${info['uploader_url']}\n${info['description']}\n${info['playlist_title'] ? info['playlist_title'] : ''}`,
director: info['artist'] ? info['artist'] : info['uploader']
}
};
const doc = create(nfo_obj);
const xml = doc.end({ prettyPrint: true });
fs.writeFileSync(output_path, xml);
}
function getArchiveFolder(fileFolderPath, options, user_uid) {
if (options.customArchivePath) {
return path.join(options.customArchivePath);
} else if (user_uid) {
return path.join(fileFolderPath, 'archives');
} else {
return path.join(archivePath);
}
}

View File

@@ -0,0 +1,8 @@
module.exports = {
apps : [{
name : "YoutubeDL-Material",
script : "./app.js",
watch : "placeholder",
watch_delay: 5000
}]
}

17
backend/entrypoint.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
set -eu
CMD="pm2-runtime pm2.config.js"
# if the first arg starts with "-" pass it to program
if [ "${1#-}" != "$1" ]; then
set -- "$CMD" "$@"
fi
# chown current working directory to current user
if [ "$*" = "$CMD" ] && [ "$(id -u)" = "0" ]; then
find . \! -user "$UID" -exec chown "$UID:$GID" -R '{}' + || echo "WARNING! Could not change directory ownership. If you manage permissions externally this is fine, otherwise you may experience issues when downloading or deleting videos."
exec su-exec "$UID:$GID" "$0" "$@"
fi
exec "$@"

Binary file not shown.

Binary file not shown.

Binary file not shown.

23
backend/logger.js Normal file
View File

@@ -0,0 +1,23 @@
const winston = require('winston');
let debugMode = process.env.YTDL_MODE === 'debug';
const defaultFormat = winston.format.printf(({ level, message, label, timestamp }) => {
return `${timestamp} ${level.toUpperCase()}: ${message}`;
});
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(winston.format.timestamp(), defaultFormat),
defaultMeta: {},
transports: [
//
// - Write to all logs with level `info` and below to `combined.log`
// - Write all logs error (and below) to `error.log`.
//
new winston.transports.File({ filename: 'appdata/logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'appdata/logs/combined.log' }),
new winston.transports.Console({level: !debugMode ? 'info' : 'debug', name: 'console'})
]
});
module.exports = logger;

3726
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,20 @@
"description": "backend for YoutubeDL-Material",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon app.js",
"debug": "set YTDL_MODE=debug && node app.js"
},
"nodemonConfig": {
"ignore": [
"*.js",
"appdata/*",
"public/*"
],
"watch": [
"restart_update.json",
"restart_general.json"
]
},
"repository": {
"type": "git",
@@ -17,13 +30,42 @@
},
"homepage": "",
"dependencies": {
"archiver": "^3.1.1",
"async": "^3.1.0",
"archiver": "^5.3.1",
"async": "^3.2.3",
"async-mutex": "^0.3.1",
"axios": "^0.21.2",
"bcryptjs": "^2.4.0",
"compression": "^1.7.4",
"config": "^3.2.3",
"exe": "^1.0.2",
"express": "^4.17.1",
"express": "^4.17.3",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^9.0.0",
"jsonwebtoken": "^8.5.1",
"lowdb": "^1.0.0",
"md5": "^2.2.1",
"merge-files": "^0.1.2",
"mocha": "^9.2.2",
"moment": "^2.29.2",
"mongodb": "^3.6.9",
"multer": "^1.4.2",
"node-fetch": "^2.6.7",
"node-id3": "^0.1.14",
"node-schedule": "^2.1.0",
"nodemon": "^2.0.7",
"passport": "^0.4.1",
"passport-http": "^0.3.0",
"passport-jwt": "^4.0.0",
"passport-ldapauth": "^3.0.1",
"passport-local": "^1.0.0",
"progress": "^2.0.3",
"ps-node": "^0.1.6",
"read-last-lines": "^1.7.2",
"rxjs": "^7.3.0",
"shortid": "^2.2.15",
"youtube-dl": "^2.3.0"
"unzipper": "^0.10.10",
"uuidv4": "^6.0.6",
"winston": "^3.7.2",
"xmlbuilder2": "^3.0.2",
"youtube-dl": "^3.0.2"
}
}

9
backend/pm2.config.js Normal file
View File

@@ -0,0 +1,9 @@
module.exports = {
apps : [{
name : "YoutubeDL-Material",
script : "./app.js",
watch : "placeholder",
out_file: "/dev/null",
error_file: "/dev/null"
}]
}

540
backend/subscriptions.js Normal file
View File

@@ -0,0 +1,540 @@
const fs = require('fs-extra');
const path = require('path');
const youtubedl = require('youtube-dl');
const config_api = require('./config');
const utils = require('./utils');
const logger = require('./logger');
const debugMode = process.env.YTDL_MODE === 'debug';
const db_api = require('./db');
const downloader_api = require('./downloader');
async function subscribe(sub, user_uid = null) {
const result_obj = {
success: false,
error: ''
};
return new Promise(async resolve => {
// sub should just have url and name. here we will get isPlaylist and path
sub.isPlaylist = sub.url.includes('playlist');
sub.videos = [];
let url_exists = !!(await db_api.getRecord('subscriptions', {url: sub.url, user_uid: user_uid}));
if (!sub.name && url_exists) {
logger.error(`Sub with the same URL "${sub.url}" already exists -- please provide a custom name for this new subscription.`);
result_obj.error = 'Subcription with URL ' + sub.url + ' already exists! Custom name is required.';
resolve(result_obj);
return;
}
sub['user_uid'] = user_uid ? user_uid : undefined;
await db_api.insertRecordIntoTable('subscriptions', sub);
let success = await getSubscriptionInfo(sub);
if (success) {
getVideosForSub(sub, user_uid);
} else {
logger.error('Subscribe: Failed to get subscription info. Subscribe failed.')
}
result_obj.success = success;
result_obj.sub = sub;
resolve(result_obj);
});
}
async function getSubscriptionInfo(sub) {
// get videos
let downloadConfig = ['--dump-json', '--playlist-end', '1'];
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
if (useCookies) {
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
} else {
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
}
}
return new Promise(async resolve => {
youtubedl.exec(sub.url, downloadConfig, {maxBuffer: Infinity}, async (err, output) => {
if (debugMode) {
logger.info('Subscribe: got info for subscription ' + sub.id);
}
if (err) {
logger.error(err.stderr);
resolve(false);
} else if (output) {
if (output.length === 0 || (output.length === 1 && output[0] === '')) {
logger.verbose('Could not get info for ' + sub.id);
resolve(false);
}
for (let i = 0; i < output.length; i++) {
let output_json = null;
try {
output_json = JSON.parse(output[i]);
} catch(e) {
output_json = null;
}
if (!output_json) {
continue;
}
if (!sub.name) {
if (sub.isPlaylist) {
sub.name = output_json.playlist_title ? output_json.playlist_title : output_json.playlist;
} else {
sub.name = output_json.uploader;
}
// if it's now valid, update
if (sub.name) {
await db_api.updateRecord('subscriptions', {id: sub.id}, {name: sub.name});
}
}
// TODO: get even more info
resolve(true);
}
resolve(false);
}
});
});
}
async function unsubscribe(sub, deleteMode, user_uid = null) {
let basePath = null;
if (user_uid)
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
else
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
let id = sub.id;
const sub_files = await db_api.getRecords('files', {sub_id: id});
for (let i = 0; i < sub_files.length; i++) {
const sub_file = sub_files[i];
if (config_api.descriptors[sub_file['uid']]) {
try {
for (let i = 0; i < config_api.descriptors[sub_file['uid']].length; i++) {
config_api.descriptors[sub_file['uid']][i].destroy();
}
} catch(e) {
continue;
}
}
}
await db_api.removeRecord('subscriptions', {id: id});
await db_api.removeAllRecords('files', {sub_id: id});
// failed subs have no name, on unsubscribe they shouldn't error
if (!sub.name) {
return;
}
const appendedBasePath = getAppendedBasePath(sub, basePath);
if (deleteMode && (await fs.pathExists(appendedBasePath))) {
if (sub.archive && (await fs.pathExists(sub.archive))) {
const archive_file_path = path.join(sub.archive, 'archive.txt');
// deletes archive if it exists
if (await fs.pathExists(archive_file_path)) {
await fs.unlink(archive_file_path);
}
await fs.rmdir(sub.archive);
}
await fs.remove(appendedBasePath);
}
}
async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null, user_uid = null) {
// TODO: combine this with deletefile
let basePath = null;
basePath = user_uid ? path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions')
: config_api.getConfigItem('ytdl_subscriptions_base_path');
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
const appendedBasePath = getAppendedBasePath(sub, basePath);
const name = file;
let retrievedID = null;
await db_api.removeRecord('files', {uid: file_uid});
let filePath = appendedBasePath;
const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
var jsonPath = path.join(__dirname,filePath,name+'.info.json');
var videoFilePath = path.join(__dirname,filePath,name+ext);
var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
var altImageFilePath = path.join(__dirname,filePath,name+'.webp');
const [jsonExists, videoFileExists, imageFileExists, altImageFileExists] = await Promise.all([
fs.pathExists(jsonPath),
fs.pathExists(videoFilePath),
fs.pathExists(imageFilePath),
fs.pathExists(altImageFilePath),
]);
if (jsonExists) {
retrievedID = JSON.parse(await fs.readFile(jsonPath, 'utf8'))['id'];
await fs.unlink(jsonPath);
}
if (imageFileExists) {
await fs.unlink(imageFilePath);
}
if (altImageFileExists) {
await fs.unlink(altImageFilePath);
}
if (videoFileExists) {
await fs.unlink(videoFilePath);
if ((await fs.pathExists(jsonPath)) || (await fs.pathExists(videoFilePath))) {
return false;
} else {
// check if the user wants the video to be redownloaded (deleteForever === false)
if (!deleteForever && useArchive && sub.archive && retrievedID) {
const archive_path = path.join(sub.archive, 'archive.txt')
// if archive exists, remove line with video ID
if (await fs.pathExists(archive_path)) {
utils.removeIDFromArchive(archive_path, retrievedID);
}
}
return true;
}
} else {
// TODO: tell user that the file didn't exist
return true;
}
}
async function getVideosForSub(sub, user_uid = null) {
const latest_sub_obj = await getSubscription(sub.id);
if (!latest_sub_obj || latest_sub_obj['downloading']) {
return false;
}
updateSubscriptionProperty(sub, {downloading: true}, user_uid);
// get basePath
let basePath = null;
if (user_uid)
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
else
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
let appendedBasePath = getAppendedBasePath(sub, basePath);
fs.ensureDirSync(appendedBasePath);
const downloadConfig = await generateArgsForSubscription(sub, user_uid);
// get videos
logger.verbose(`Subscription: getting videos for subscription ${sub.name} with args: ${downloadConfig.join(',')}`);
return new Promise(async resolve => {
youtubedl.exec(sub.url, downloadConfig, {maxBuffer: Infinity}, async function(err, output) {
// cleanup
updateSubscriptionProperty(sub, {downloading: false}, user_uid);
logger.verbose('Subscription: finished check for ' + sub.name);
if (err && !output) {
logger.error(err.stderr ? err.stderr : err.message);
if (err.stderr.includes('This video is unavailable')) {
logger.info('An error was encountered with at least one video, backup method will be used.')
try {
// TODO: reimplement
// const outputs = err.stdout.split(/\r\n|\r|\n/);
// for (let i = 0; i < outputs.length; i++) {
// const output = JSON.parse(outputs[i]);
// await handleOutputJSON(sub, output, i === 0, multiUserMode)
// if (err.stderr.includes(output['id']) && archive_path) {
// // we found a video that errored! add it to the archive to prevent future errors
// if (sub.archive) {
// archive_dir = sub.archive;
// archive_path = path.join(archive_dir, 'archive.txt')
// fs.appendFileSync(archive_path, output['id']);
// }
// }
// }
} catch(e) {
logger.error('Backup method failed. See error below:');
logger.error(e);
}
}
resolve(false);
} else if (output) {
if (output.length === 0 || (output.length === 1 && output[0] === '')) {
logger.verbose('No additional videos to download for ' + sub.name);
resolve(true);
return;
}
const output_jsons = [];
for (let i = 0; i < output.length; i++) {
let output_json = null;
try {
output_json = JSON.parse(output[i]);
output_jsons.push(output_json);
} catch(e) {
output_json = null;
}
if (!output_json) {
continue;
}
}
const files_to_download = await getFilesToDownload(sub, output_jsons);
const base_download_options = generateOptionsForSubscriptionDownload(sub, user_uid);
for (let j = 0; j < files_to_download.length; j++) {
const file_to_download = files_to_download[j];
await downloader_api.createDownload(file_to_download['webpage_url'], sub.type || 'video', base_download_options, user_uid, sub.id, sub.name);
}
resolve(files_to_download);
if (config_api.getConfigItem('ytdl_subscriptions_redownload_fresh_uploads')) {
await setFreshUploads(sub, user_uid);
checkVideosForFreshUploads(sub, user_uid);
}
resolve(true);
}
});
}, err => {
logger.error(err);
updateSubscriptionProperty(sub, {downloading: false}, user_uid);
});
}
function generateOptionsForSubscriptionDownload(sub, user_uid) {
let basePath = null;
if (user_uid)
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
else
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
let default_output = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
const base_download_options = {
selectedHeight: sub.maxQuality && sub.maxQuality !== 'best' ? sub.maxQuality : null,
customFileFolderPath: getAppendedBasePath(sub, basePath),
customOutput: sub.custom_output ? `${sub.custom_output}` : `${default_output}`,
customArchivePath: path.join(__dirname, basePath, 'archives', sub.name),
additionalArgs: sub.custom_args
}
return base_download_options;
}
async function generateArgsForSubscription(sub, user_uid, redownload = false, desired_path = null) {
// get basePath
let basePath = null;
if (user_uid)
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
else
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
let appendedBasePath = getAppendedBasePath(sub, basePath);
const file_output = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
let fullOutput = `"${appendedBasePath}/${file_output}.%(ext)s"`;
if (desired_path) {
fullOutput = `"${desired_path}.%(ext)s"`;
} else if (sub.custom_output) {
fullOutput = `"${appendedBasePath}/${sub.custom_output}.%(ext)s"`;
}
let downloadConfig = ['--dump-json', '-o', fullOutput, !redownload ? '-ciw' : '-ci', '--write-info-json', '--print-json'];
let qualityPath = null;
if (sub.type && sub.type === 'audio') {
qualityPath = ['-f', 'bestaudio']
qualityPath.push('-x');
qualityPath.push('--audio-format', 'mp3');
} else {
if (!sub.maxQuality || sub.maxQuality === 'best') qualityPath = ['-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4'];
else qualityPath = ['-f', `bestvideo[height<=${sub.maxQuality}]+bestaudio/best[height<=${sub.maxQuality}]`, '--merge-output-format', 'mp4'];
}
downloadConfig.push(...qualityPath)
if (sub.custom_args) {
const customArgsArray = sub.custom_args.split(',,');
if (customArgsArray.indexOf('-f') !== -1) {
// if custom args has a custom quality, replce the original quality with that of custom args
const original_output_index = downloadConfig.indexOf('-f');
downloadConfig.splice(original_output_index, 2);
}
downloadConfig.push(...customArgsArray);
}
let archive_dir = null;
let archive_path = null;
if (useArchive && !redownload) {
if (sub.archive) {
archive_dir = sub.archive;
archive_path = path.join(archive_dir, 'archive.txt')
}
downloadConfig.push('--download-archive', archive_path);
}
// if streaming only mode, just get the list of videos
if (sub.streamingOnly) {
downloadConfig = ['-f', 'best', '--dump-json'];
}
if (sub.timerange && !redownload) {
downloadConfig.push('--dateafter', sub.timerange);
}
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
if (useCookies) {
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
} else {
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
}
}
if (config_api.getConfigItem('ytdl_include_thumbnail')) {
downloadConfig.push('--write-thumbnail');
}
const rate_limit = config_api.getConfigItem('ytdl_download_rate_limit');
if (rate_limit && downloadConfig.indexOf('-r') === -1 && downloadConfig.indexOf('--limit-rate') === -1) {
downloadConfig.push('-r', rate_limit);
}
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
if (default_downloader === 'yt-dlp') {
downloadConfig.push('--no-clean-infojson');
}
return downloadConfig;
}
async function getFilesToDownload(sub, output_jsons) {
const files_to_download = [];
for (let i = 0; i < output_jsons.length; i++) {
const output_json = output_jsons[i];
const file_missing = !(await db_api.getRecord('files', {sub_id: sub.id, url: output_json['webpage_url']})) && !(await db_api.getRecord('download_queue', {sub_id: sub.id, url: output_json['webpage_url'], error: null, finished: false}));
if (file_missing) {
const file_with_path_exists = await db_api.getRecord('files', {sub_id: sub.id, path: output_json['_filename']});
if (file_with_path_exists) {
// or maybe just overwrite???
logger.info(`Skipping adding file ${output_json['_filename']} for subscription ${sub.name} as a file with that path already exists.`)
}
files_to_download.push(output_json);
}
}
return files_to_download;
}
async function getSubscriptions(user_uid = null) {
return await db_api.getRecords('subscriptions', {user_uid: user_uid});
}
async function getAllSubscriptions() {
const all_subs = await db_api.getRecords('subscriptions');
const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode');
return all_subs.filter(sub => !!(sub.user_uid) === !!multiUserMode);
}
async function getSubscription(subID) {
return await db_api.getRecord('subscriptions', {id: subID});
}
async function getSubscriptionByName(subName, user_uid = null) {
return await db_api.getRecord('subscriptions', {name: subName, user_uid: user_uid});
}
async function updateSubscription(sub) {
await db_api.updateRecord('subscriptions', {id: sub.id}, sub);
return true;
}
async function updateSubscriptionPropertyMultiple(subs, assignment_obj) {
subs.forEach(async sub => {
await updateSubscriptionProperty(sub, assignment_obj, sub.user_uid);
});
}
async function updateSubscriptionProperty(sub, assignment_obj) {
// TODO: combine with updateSubscription
await db_api.updateRecord('subscriptions', {id: sub.id}, assignment_obj);
return true;
}
async function setFreshUploads(sub, user_uid) {
const current_date = new Date().toISOString().split('T')[0].replace(/-/g, '');
sub.videos.forEach(async video => {
if (current_date === video['upload_date'].replace(/-/g, '')) {
// set upload as fresh
const video_uid = video['uid'];
await db_api.setVideoProperty(video_uid, {'fresh_upload': true}, user_uid, sub['id']);
}
});
}
async function checkVideosForFreshUploads(sub, user_uid) {
const current_date = new Date().toISOString().split('T')[0].replace(/-/g, '');
sub.videos.forEach(async video => {
if (video['fresh_upload'] && current_date > video['upload_date'].replace(/-/g, '')) {
await checkVideoIfBetterExists(video, sub, user_uid)
}
});
}
async function checkVideoIfBetterExists(file_obj, sub, user_uid) {
const new_path = file_obj['path'].substring(0, file_obj['path'].length - 4);
const downloadConfig = await generateArgsForSubscription(sub, user_uid, true, new_path);
logger.verbose(`Checking if a better version of the fresh upload ${file_obj['id']} exists.`);
// simulate a download to verify that a better version exists
youtubedl.getInfo(file_obj['url'], downloadConfig, async (err, output) => {
if (err) {
// video is not available anymore for whatever reason
} else if (output) {
const metric_to_compare = sub.type === 'audio' ? 'abr' : 'height';
if (output[metric_to_compare] > file_obj[metric_to_compare]) {
// download new video as the simulated one is better
youtubedl.exec(file_obj['url'], downloadConfig, {maxBuffer: Infinity}, async (err, output) => {
if (err) {
logger.verbose(`Failed to download better version of video ${file_obj['id']}`);
} else if (output) {
logger.verbose(`Successfully upgraded video ${file_obj['id']}'s ${metric_to_compare} from ${file_obj[metric_to_compare]} to ${output[metric_to_compare]}`);
await db_api.setVideoProperty(file_obj['uid'], {[metric_to_compare]: output[metric_to_compare]}, user_uid, sub['id']);
}
});
}
}
});
await db_api.setVideoProperty(file_obj['uid'], {'fresh_upload': false}, user_uid, sub['id']);
}
// helper functions
function getAppendedBasePath(sub, base_path) {
return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name);
}
module.exports = {
getSubscription : getSubscription,
getSubscriptionByName : getSubscriptionByName,
getSubscriptions : getSubscriptions,
getAllSubscriptions : getAllSubscriptions,
updateSubscription : updateSubscription,
subscribe : subscribe,
unsubscribe : unsubscribe,
deleteSubscriptionFile : deleteSubscriptionFile,
getVideosForSub : getVideosForSub,
updateSubscriptionPropertyMultiple : updateSubscriptionPropertyMultiple,
generateOptionsForSubscriptionDownload: generateOptionsForSubscriptionDownload
}

View File

@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

195
backend/tasks.js Normal file
View File

@@ -0,0 +1,195 @@
const db_api = require('./db');
const youtubedl_api = require('./youtube-dl');
const fs = require('fs-extra');
const logger = require('./logger');
const scheduler = require('node-schedule');
const TASKS = {
backup_local_db: {
run: db_api.backupDB,
title: 'Backup DB',
job: null
},
missing_files_check: {
run: checkForMissingFiles,
confirm: deleteMissingFiles,
title: 'Missing files check',
job: null
},
missing_db_records: {
run: db_api.importUnregisteredFiles,
title: 'Import missing DB records',
job: null
},
duplicate_files_check: {
run: checkForDuplicateFiles,
confirm: removeDuplicates,
title: 'Find duplicate files in DB',
job: null
},
youtubedl_update_check: {
run: youtubedl_api.checkForYoutubeDLUpdate,
confirm: youtubedl_api.updateYoutubeDL,
title: 'Update youtube-dl',
job: null
}
}
function scheduleJob(task_key, schedule) {
// schedule has to be converted from our format to one node-schedule can consume
let converted_schedule = null;
if (schedule['type'] === 'timestamp') {
converted_schedule = new Date(schedule['data']['timestamp']);
} else if (schedule['type'] === 'recurring') {
const dayOfWeek = schedule['data']['dayOfWeek'] != null ? schedule['data']['dayOfWeek'] : null;
const hour = schedule['data']['hour'] != null ? schedule['data']['hour'] : null;
const minute = schedule['data']['minute'] != null ? schedule['data']['minute'] : null;
converted_schedule = new scheduler.RecurrenceRule(null, null, null, dayOfWeek, hour, minute);
} else {
logger.error(`Failed to schedule job '${task_key}' as the type '${schedule['type']}' is invalid.`)
return null;
}
return scheduler.scheduleJob(converted_schedule, async () => {
const task_state = await db_api.getRecord('tasks', {key: task_key});
if (task_state['running'] || task_state['confirming']) {
logger.verbose(`Skipping running task ${task_state['key']} as it is already in progress.`);
return;
}
// remove schedule if it's a one-time task
if (task_state['schedule']['type'] !== 'recurring') await db_api.updateRecord('tasks', {key: task_key}, {schedule: null});
// we're just "running" the task, any confirmation should be user-initiated
exports.executeRun(task_key);
});
}
if (db_api.database_initialized) {
exports.setupTasks();
} else {
db_api.database_initialized_bs.subscribe(init => {
if (init) exports.setupTasks();
});
}
exports.setupTasks = async () => {
const tasks_keys = Object.keys(TASKS);
for (let i = 0; i < tasks_keys.length; i++) {
const task_key = tasks_keys[i];
const task_in_db = await db_api.getRecord('tasks', {key: task_key});
if (!task_in_db) {
// insert task metadata into table if missing
await db_api.insertRecordIntoTable('tasks', {
key: task_key,
title: TASKS[task_key]['title'],
last_ran: null,
last_confirmed: null,
running: false,
confirming: false,
data: null,
error: null,
schedule: null,
options: {}
});
} else {
// reset task if necessary
await db_api.updateRecord('tasks', {key: task_key}, {running: false, confirming: false});
// schedule task and save job
if (task_in_db['schedule']) {
// prevent timestamp schedules from being set to the past
if (task_in_db['schedule']['type'] === 'timestamp' && task_in_db['schedule']['data']['timestamp'] < Date.now()) {
await db_api.updateRecord('tasks', {key: task_key}, {schedule: null});
continue;
}
TASKS[task_key]['job'] = scheduleJob(task_key, task_in_db['schedule']);
}
}
}
}
exports.executeTask = async (task_key) => {
if (!TASKS[task_key]) {
logger.error(`Task ${task_key} does not exist!`);
return;
}
logger.verbose(`Executing task ${task_key}`);
await exports.executeRun(task_key);
if (!TASKS[task_key]['confirm']) return;
await exports.executeConfirm(task_key);
logger.verbose(`Finished executing ${task_key}`);
}
exports.executeRun = async (task_key) => {
logger.verbose(`Running task ${task_key}`);
// don't set running to true when backup up DB as it will be stick "running" if restored
if (task_key !== 'backup_local_db') await db_api.updateRecord('tasks', {key: task_key}, {running: true});
const data = await TASKS[task_key].run();
await db_api.updateRecord('tasks', {key: task_key}, {data: TASKS[task_key]['confirm'] ? data : null, last_ran: Date.now()/1000, running: false});
logger.verbose(`Finished running task ${task_key}`);
}
exports.executeConfirm = async (task_key) => {
logger.verbose(`Confirming task ${task_key}`);
if (!TASKS[task_key]['confirm']) {
return null;
}
await db_api.updateRecord('tasks', {key: task_key}, {confirming: true});
const task_obj = await db_api.getRecord('tasks', {key: task_key});
const data = task_obj['data'];
await TASKS[task_key].confirm(data);
await db_api.updateRecord('tasks', {key: task_key}, {confirming: false, last_confirmed: Date.now()/1000, data: null});
logger.verbose(`Finished confirming task ${task_key}`);
}
exports.updateTaskSchedule = async (task_key, schedule) => {
logger.verbose(`Updating schedule for task ${task_key}`);
await db_api.updateRecord('tasks', {key: task_key}, {schedule: schedule});
if (TASKS[task_key]['job']) {
TASKS[task_key]['job'].cancel();
}
if (schedule) {
TASKS[task_key]['job'] = scheduleJob(task_key, schedule);
}
}
// missing files check
async function checkForMissingFiles() {
const missing_files = [];
const all_files = await db_api.getRecords('files');
for (let i = 0; i < all_files.length; i++) {
const file_to_check = all_files[i];
const file_exists = fs.existsSync(file_to_check['path']);
if (!file_exists) missing_files.push(file_to_check['uid']);
}
return {uids: missing_files};
}
async function deleteMissingFiles(data) {
const uids = data['uids'];
for (let i = 0; i < uids.length; i++) {
const uid = uids[i];
await db_api.removeRecord('files', {uid: uid});
}
}
// duplicate files check
async function checkForDuplicateFiles() {
const duplicate_files = await db_api.findDuplicatesByKey('files', 'path');
const duplicate_uids = duplicate_files.map(duplicate_file => duplicate_file['uid']);
if (duplicate_uids && duplicate_uids.length > 0) {
return {uids: duplicate_uids};
}
return {uids: []};
}
async function removeDuplicates(data) {
for (let i = 0; i < data['uids'].length; i++) {
await db_api.removeRecord('files', {uid: data['uids'][i]});
}
}
exports.TASKS = TASKS;

File diff suppressed because one or more lines are too long

481
backend/test/tests.js Normal file
View File

@@ -0,0 +1,481 @@
var assert = require('assert');
const low = require('lowdb')
var winston = require('winston');
process.chdir('./backend')
const FileSync = require('lowdb/adapters/FileSync');
const adapter = new FileSync('./appdata/db.json');
const db = low(adapter)
const users_adapter = new FileSync('./appdata/users.json');
const users_db = low(users_adapter);
const defaultFormat = winston.format.printf(({ level, message, label, timestamp }) => {
return `${timestamp} ${level.toUpperCase()}: ${message}`;
});
let debugMode = process.env.YTDL_MODE === 'debug';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(winston.format.timestamp(), defaultFormat),
defaultMeta: {},
transports: [
//
// - Write to all logs with level `info` and below to `combined.log`
// - Write all logs error (and below) to `error.log`.
//
new winston.transports.File({ filename: 'appdata/logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'appdata/logs/combined.log' }),
new winston.transports.Console({level: 'debug', name: 'console'})
]
});
var auth_api = require('../authentication/auth');
var db_api = require('../db');
const utils = require('../utils');
const subscriptions_api = require('../subscriptions');
const fs = require('fs-extra');
const { uuid } = require('uuidv4');
db_api.initialize(db, users_db);
describe('Database', async function() {
describe('Import', async function() {
it('Migrate', async function() {
await db_api.connectToDB();
await db_api.removeAllRecords();
const success = await db_api.importJSONToDB(db.value(), users_db.value());
assert(success);
});
it('Transfer to remote', async function() {
await db_api.removeAllRecords('test');
await db_api.insertRecordIntoTable('test', {test: 'test'});
await db_api.transferDB(true);
const success = await db_api.getRecord('test', {test: 'test'});
assert(success);
});
it('Transfer to local', async function() {
await db_api.connectToDB();
await db_api.removeAllRecords('test');
await db_api.insertRecordIntoTable('test', {test: 'test'});
await db_api.transferDB(false);
const success = await db_api.getRecord('test', {test: 'test'});
assert(success);
});
it('Restore db', async function() {
const db_stats = await db_api.getDBStats();
const file_name = await db_api.backupDB();
await db_api.restoreDB(file_name);
const new_db_stats = await db_api.getDBStats();
assert(JSON.stringify(db_stats), JSON.stringify(new_db_stats));
});
});
describe('Export', function() {
});
describe('Basic functions', async function() {
beforeEach(async function() {
await db_api.connectToDB();
await db_api.removeAllRecords('test');
});
it('Add and read record', async function() {
this.timeout(120000);
await db_api.insertRecordIntoTable('test', {test_add: 'test', test_undefined: undefined, test_null: undefined});
const added_record = await db_api.getRecord('test', {test_add: 'test', test_undefined: undefined, test_null: null});
assert(added_record['test_add'] === 'test');
await db_api.removeRecord('test', {test_add: 'test'});
});
it('Find duplicates by key', async function() {
const test_duplicates = [
{
test: 'testing',
key: '1'
},
{
test: 'testing',
key: '2'
},
{
test: 'testing_missing',
key: '3'
},
{
test: 'testing',
key: '4'
}
];
await db_api.insertRecordsIntoTable('test', test_duplicates);
const duplicates = await db_api.findDuplicatesByKey('test', 'test');
console.log(duplicates);
});
it('Update record', async function() {
await db_api.insertRecordIntoTable('test', {test_update: 'test'});
await db_api.updateRecord('test', {test_update: 'test'}, {added_field: true});
const updated_record = await db_api.getRecord('test', {test_update: 'test'});
assert(updated_record['added_field']);
await db_api.removeRecord('test', {test_update: 'test'});
});
it('Remove record', async function() {
await db_api.insertRecordIntoTable('test', {test_remove: 'test'});
const delete_succeeded = await db_api.removeRecord('test', {test_remove: 'test'});
assert(delete_succeeded);
const deleted_record = await db_api.getRecord('test', {test_remove: 'test'});
assert(!deleted_record);
});
it('Push to record array', async function() {
await db_api.insertRecordIntoTable('test', {test: 'test', test_array: []});
await db_api.pushToRecordsArray('test', {test: 'test'}, 'test_array', 'test_item');
const record = await db_api.getRecord('test', {test: 'test'});
assert(record);
assert(record['test_array'].length === 1);
});
it('Pull from record array', async function() {
await db_api.insertRecordIntoTable('test', {test: 'test', test_array: ['test_item']});
await db_api.pullFromRecordsArray('test', {test: 'test'}, 'test_array', 'test_item');
const record = await db_api.getRecord('test', {test: 'test'});
assert(record);
assert(record['test_array'].length === 0);
});
it('Bulk add', async function() {
this.timeout(120000);
const NUM_RECORDS_TO_ADD = 2002; // max batch ops is 1000
const test_records = [];
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
test_records.push({
uid: uuid()
});
}
const succcess = await db_api.bulkInsertRecordsIntoTable('test', test_records);
const received_records = await db_api.getRecords('test');
assert(succcess && received_records && received_records.length === NUM_RECORDS_TO_ADD);
});
it('Bulk update', async function() {
// bulk add records
const NUM_RECORDS_TO_ADD = 100; // max batch ops is 1000
const test_records = [];
const update_obj = {};
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
const test_uid = uuid();
test_records.push({
uid: test_uid
});
update_obj[test_uid] = {added_field: true};
}
let success = await db_api.bulkInsertRecordsIntoTable('test', test_records);
assert(success);
// makes sure they are added
const received_records = await db_api.getRecords('test');
assert(received_records && received_records.length === NUM_RECORDS_TO_ADD);
success = await db_api.bulkUpdateRecords('test', 'uid', update_obj);
assert(success);
const received_updated_records = await db_api.getRecords('test');
for (let i = 0; i < received_updated_records.length; i++) {
success &= received_updated_records[i]['added_field'];
}
assert(success);
});
it('Stats', async function() {
const stats = await db_api.getDBStats();
assert(stats);
});
it('Query speed', async function() {
this.timeout(120000);
const NUM_RECORDS_TO_ADD = 300004; // max batch ops is 1000
const test_records = [];
let random_uid = '06241f83-d1b8-4465-812c-618dfa7f2943';
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
const uid = uuid();
if (i === NUM_RECORDS_TO_ADD/2) random_uid = uid;
test_records.push({"id":"A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J","title":"A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J","thumbnailURL":"https://i.ytimg.com/vi/tt7gP_IW-1w/maxresdefault.jpg","isAudio":true,"duration":312,"url":"https://www.youtube.com/watch?v=tt7gP_IW-1w","uploader":"asapmobVEVO","size":5060157,"path":"audio\\A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J.mp3","upload_date":"2016-05-11","description":"A$AP Mob ft. Juicy J - \"Yamborghini High\" Get it now on:\niTunes: http://smarturl.it/iYAMH?IQid=yt\nListen on Spotify: http://smarturl.it/sYAMH?IQid=yt\nGoogle Play: http://smarturl.it/gYAMH?IQid=yt\nAmazon: http://smarturl.it/aYAMH?IQid=yt\n\nFollow A$AP Mob:\nhttps://www.facebook.com/asapmobofficial\nhttps://twitter.com/ASAPMOB\nhttp://instagram.com/asapmob \nhttp://www.asapmob.com/\n\n#AsapMob #YamborghiniHigh #Vevo #HipHop #OfficialMusicVideo #JuicyJ","view_count":118689353,"height":null,"abr":160,"uid": uid,"registered":1626672120632});
}
const insert_start = Date.now();
let success = await db_api.bulkInsertRecordsIntoTable('test', test_records);
const insert_end = Date.now();
console.log(`Insert time: ${(insert_end - insert_start)/1000}s`);
const query_start = Date.now();
const random_record = await db_api.getRecord('test', {uid: random_uid});
const query_end = Date.now();
console.log(random_record)
console.log(`Query time: ${(query_end - query_start)/1000}s`);
success = !!random_record;
assert(success);
});
});
});
describe('Multi User', async function() {
let user = null;
const user_to_test = 'admin';
const sub_to_test = 'dc834388-3454-41bf-a618-e11cb8c7de1c';
const playlist_to_test = 'ysabVZz4x';
beforeEach(async function() {
await db_api.connectToDB();
auth_api.initialize(db_api, logger);
subscriptions_api.initialize(db_api, logger);
user = await auth_api.login('admin', 'pass');
});
describe('Authentication', function() {
it('login', async function() {
assert(user);
});
});
describe('Video player - normal', function() {
const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e';
it('Get video', async function() {
const video_obj = db_api.getVideo(video_to_test, 'admin');
assert(video_obj);
});
it('Video access - disallowed', async function() {
await db_api.setVideoProperty(video_to_test, {sharingEnabled: false}, user_to_test);
const video_obj = auth_api.getUserVideo('admin', video_to_test, true);
assert(!video_obj);
});
it('Video access - allowed', async function() {
await db_api.setVideoProperty(video_to_test, {sharingEnabled: true}, user_to_test);
const video_obj = auth_api.getUserVideo('admin', video_to_test, true);
assert(video_obj);
});
});
describe('Zip generators', function() {
it('Playlist zip generator', async function() {
const playlist = await db_api.getPlaylist(playlist_to_test, user_to_test);
assert(playlist);
const playlist_files_to_download = [];
for (let i = 0; i < playlist['uids'].length; i++) {
const uid = playlist['uids'][i];
const playlist_file = await db_api.getVideo(uid, user_to_test);
playlist_files_to_download.push(playlist_file);
}
const zip_path = await utils.createContainerZipFile(playlist, playlist_files_to_download);
const zip_exists = fs.pathExistsSync(zip_path);
assert(zip_exists);
if (zip_exists) fs.unlinkSync(zip_path);
});
it('Subscription zip generator', async function() {
const sub = await subscriptions_api.getSubscription(sub_to_test, user_to_test);
const sub_videos = await db_api.getRecords('files', {sub_id: sub.id});
assert(sub);
const sub_files_to_download = [];
for (let i = 0; i < sub_videos.length; i++) {
const sub_file = sub_videos[i];
sub_files_to_download.push(sub_file);
}
const zip_path = await utils.createContainerZipFile(sub, sub_files_to_download);
const zip_exists = fs.pathExistsSync(zip_path);
assert(zip_exists);
if (zip_exists) fs.unlinkSync(zip_path);
});
});
// describe('Video player - subscription', function() {
// const sub_to_test = '';
// const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e';
// it('Get video', async function() {
// const video_obj = db_api.getVideo(video_to_test, 'admin', );
// assert(video_obj);
// });
// it('Video access - disallowed', async function() {
// await db_api.setVideoProperty(video_to_test, {sharingEnabled: false}, user_to_test, sub_to_test);
// const video_obj = auth_api.getUserVideo('admin', video_to_test, true);
// assert(!video_obj);
// });
// it('Video access - allowed', async function() {
// await db_api.setVideoProperty(video_to_test, {sharingEnabled: true}, user_to_test, sub_to_test);
// const video_obj = auth_api.getUserVideo('admin', video_to_test, true);
// assert(video_obj);
// });
// });
});
describe('Downloader', function() {
const downloader_api = require('../downloader');
const url = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
const sub_id = 'dc834388-3454-41bf-a618-e11cb8c7de1c';
const options = {
ui_uid: uuid(),
user: 'admin'
}
beforeEach(async function() {
await db_api.connectToDB();
await db_api.removeAllRecords('download_queue');
});
it('Get file info', async function() {
});
it('Download file', async function() {
this.timeout(300000);
const returned_download = await downloader_api.createDownload(url, 'video', options);
console.log(returned_download);
await utils.wait(20000);
});
it('Queue file', async function() {
this.timeout(300000);
const returned_download = await downloader_api.createDownload(url, 'video', options);
console.log(returned_download);
await utils.wait(20000);
});
it('Pause file', async function() {
});
it('Generate args', async function() {
const args = await downloader_api.generateArgs(url, 'video', options);
console.log(args);
});
it('Generate args - subscription', async function() {
subscriptions_api.initialize(db_api, logger);
const sub = await subscriptions_api.getSubscription(sub_id);
const sub_options = subscriptions_api.generateOptionsForSubscriptionDownload(sub, 'admin');
const args = await downloader_api.generateArgs(url, 'video', sub_options, 'admin');
console.log(args);
});
it('Generate kodi NFO file', async function() {
const nfo_file_path = './test/sample.nfo';
if (fs.existsSync(nfo_file_path)) {
fs.unlinkSync(nfo_file_path);
}
const sample_json = fs.readJSONSync('./test/sample.info.json');
downloader_api.generateNFOFile(sample_json, nfo_file_path);
assert(fs.existsSync(nfo_file_path), true);
fs.unlinkSync(nfo_file_path);
});
});
describe('Tasks', function() {
const tasks_api = require('../tasks');
beforeEach(async function() {
await db_api.connectToDB();
await db_api.removeAllRecords('tasks');
const dummy_task = {
run: async () => { await utils.wait(500); return true; },
confirm: async () => { await utils.wait(500); return true; },
title: 'Dummy task',
job: null
};
tasks_api.TASKS['dummy_task'] = dummy_task;
await tasks_api.initialize();
});
it('Backup db', async function() {
const backups_original = await utils.recFindByExt('appdata', 'bak');
const original_length = backups_original.length;
await tasks_api.executeTask('backup_local_db');
const backups_new = await utils.recFindByExt('appdata', 'bak');
const new_length = backups_new.length;
assert(original_length, new_length-1);
});
it('Check for missing files', async function() {
await db_api.removeAllRecords('files', {uid: 'test'});
const test_missing_file = {uid: 'test', path: 'test/missing_file.mp4'};
await db_api.insertRecordIntoTable('files', test_missing_file);
await tasks_api.executeTask('missing_files_check');
const task_obj = await db_api.getRecord('tasks', {key: 'missing_files_check'});
assert(task_obj['data'] && task_obj['data']['uids'] && task_obj['data']['uids'].length >= 1, true);
});
it('Check for duplicate files', async function() {
this.timeout(300000);
await db_api.removeAllRecords('files', {uid: 'test1'});
await db_api.removeAllRecords('files', {uid: 'test2'});
const test_duplicate_file1 = {uid: 'test1', path: 'test/missing_file.mp4'};
const test_duplicate_file2 = {uid: 'test2', path: 'test/missing_file.mp4'};
const test_duplicate_file3 = {uid: 'test3', path: 'test/missing_file.mp4'};
await db_api.insertRecordIntoTable('files', test_duplicate_file1);
await db_api.insertRecordIntoTable('files', test_duplicate_file2);
await db_api.insertRecordIntoTable('files', test_duplicate_file3);
await tasks_api.executeTask('duplicate_files_check');
const task_obj = await db_api.getRecord('tasks', {key: 'duplicate_files_check'});
const duplicated_record_count = await db_api.getRecords('files', {path: 'test/missing_file.mp4'}, true);
assert(task_obj['data'] && task_obj['data']['uids'] && task_obj['data']['uids'].length >= 1, true);
assert(duplicated_record_count == 1, true);
});
it('Import unregistered files', async function() {
this.timeout(300000);
// pre-test cleanup
await db_api.removeAllRecords('files', {title: 'Sample File'});
if (fs.existsSync('video/sample.info.json')) fs.unlinkSync('video/sample.info.json');
if (fs.existsSync('video/sample.mp4')) fs.unlinkSync('video/sample.mp4');
// copies in files
fs.copyFileSync('test/sample.info.json', 'video/sample.info.json');
fs.copyFileSync('test/sample.mp4', 'video/sample.mp4');
await tasks_api.executeTask('missing_db_records');
const imported_file = await db_api.getRecord('files', {title: 'Sample File'});
assert(!!imported_file, true);
// post-test cleanup
if (fs.existsSync('video/sample.info.json')) fs.unlinkSync('video/sample.info.json');
if (fs.existsSync('video/sample.mp4')) fs.unlinkSync('video/sample.mp4');
});
it('Schedule and cancel task', async function() {
const today_4_hours = new Date();
today_4_hours.setHours(today_4_hours.getHours() + 4);
await tasks_api.updateTaskSchedule('dummy_task', today_4_hours);
assert(!!tasks_api.TASKS['dummy_task']['job'], true);
await tasks_api.updateTaskSchedule('dummy_task', null);
assert(!!tasks_api.TASKS['dummy_task']['job'], false);
});
it('Schedule and run task', async function() {
this.timeout(5000);
const today_1_second = new Date();
today_1_second.setSeconds(today_1_second.getSeconds() + 1);
await tasks_api.updateTaskSchedule('dummy_task', today_1_second);
assert(!!tasks_api.TASKS['dummy_task']['job'], true);
await utils.wait(2000);
const dummy_task_obj = await db_api.getRecord('tasks', {key: 'dummy_task'});
assert(dummy_task_obj['data'], true);
});
});

128
backend/twitch.js Normal file
View File

@@ -0,0 +1,128 @@
var moment = require('moment');
var Axios = require('axios');
var fs = require('fs-extra')
var path = require('path');
const config_api = require('./config');
async function getCommentsForVOD(clientID, vodId) {
let url = `https://api.twitch.tv/v5/videos/${vodId}/comments?content_offset_seconds=0`,
batch,
cursor;
let comments = null;
try {
do {
batch = (await Axios.get(url, {
headers: {
'Client-ID': clientID,
Accept: 'application/vnd.twitchtv.v5+json; charset=UTF-8',
'Content-Type': 'application/json; charset=UTF-8',
}
})).data;
const str = batch.comments.map(c => {
let {
created_at: msgCreated,
content_offset_seconds: timestamp,
commenter: {
name,
_id,
created_at: acctCreated
},
message: {
body: msg,
user_color: user_color
}
} = c;
const timestamp_str = moment.duration(timestamp, 'seconds')
.toISOString()
.replace(/P.*?T(?:(\d+?)H)?(?:(\d+?)M)?(?:(\d+).*?S)?/,
(_, ...ms) => {
const seg = v => v ? v.padStart(2, '0') : '00';
return `${seg(ms[0])}:${seg(ms[1])}:${seg(ms[2])}`;
});
acctCreated = moment(acctCreated).utc();
msgCreated = moment(msgCreated).utc();
if (!comments) comments = [];
comments.push({
timestamp: timestamp,
timestamp_str: timestamp_str,
name: name,
message: msg,
user_color: user_color
});
// let line = `${timestamp},${msgCreated.format(tsFormat)},${name},${_id},"${msg.replace(/"/g, '""')}",${acctCreated.format(tsFormat)}`;
// return line;
}).join('\n');
cursor = batch._next;
url = `https://api.twitch.tv/v5/videos/${vodId}/comments?cursor=${cursor}`;
await new Promise(res => setTimeout(res, 300));
} while (cursor);
} catch (err) {
console.error(err);
}
return comments;
}
async function getTwitchChatByFileID(id, type, user_uid, uuid, sub) {
let file_path = null;
if (user_uid) {
if (sub) {
file_path = path.join('users', user_uid, 'subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json');
} else {
file_path = path.join('users', user_uid, type, id + '.twitch_chat.json');
}
} else {
if (sub) {
file_path = path.join('subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json');
} else {
file_path = path.join(type, id + '.twitch_chat.json');
}
}
var chat_file = null;
if (fs.existsSync(file_path)) {
chat_file = fs.readJSONSync(file_path);
}
return chat_file;
}
async function downloadTwitchChatByVODID(vodId, id, type, user_uid, sub) {
const twitch_api_key = config_api.getConfigItem('ytdl_twitch_api_key');
const chat = await getCommentsForVOD(twitch_api_key, vodId);
// save file if needed params are included
let file_path = null;
if (user_uid) {
if (sub) {
file_path = path.join('users', user_uid, 'subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json');
} else {
file_path = path.join('users', user_uid, type, id + '.twitch_chat.json');
}
} else {
if (sub) {
file_path = path.join('subscriptions', sub.isPlaylist ? 'playlists' : 'channels', sub.name, id + '.twitch_chat.json');
} else {
file_path = path.join(type, id + '.twitch_chat.json');
}
}
if (chat) fs.writeJSONSync(file_path, chat);
return chat;
}
module.exports = {
getCommentsForVOD: getCommentsForVOD,
getTwitchChatByFileID: getTwitchChatByFileID,
downloadTwitchChatByVODID: downloadTwitchChatByVODID
}

462
backend/utils.js Normal file
View File

@@ -0,0 +1,462 @@
const fs = require('fs-extra');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const archiver = require('archiver');
const fetch = require('node-fetch');
const ProgressBar = require('progress');
const config_api = require('./config');
const logger = require('./logger');
const CONSTS = require('./consts');
const is_windows = process.platform === 'win32';
// replaces .webm with appropriate extension
function getTrueFileName(unfixed_path, type) {
let fixed_path = unfixed_path;
const new_ext = (type === 'audio' ? 'mp3' : 'mp4');
let unfixed_parts = unfixed_path.split('.');
const old_ext = unfixed_parts[unfixed_parts.length-1];
if (old_ext !== new_ext) {
unfixed_parts[unfixed_parts.length-1] = new_ext;
fixed_path = unfixed_parts.join('.');
}
return fixed_path;
}
async function getDownloadedFilesByType(basePath, type, full_metadata = false) {
// return empty array if the path doesn't exist
if (!(await fs.pathExists(basePath))) return [];
let files = [];
const ext = type === 'audio' ? 'mp3' : 'mp4';
var located_files = await recFindByExt(basePath, ext);
for (let i = 0; i < located_files.length; i++) {
let file = located_files[i];
var file_path = file.substring(basePath.includes('\\') ? basePath.length+1 : basePath.length, file.length);
var stats = await fs.stat(file);
var id = file_path.substring(0, file_path.length-4);
var jsonobj = await getJSONByType(type, id, basePath);
if (!jsonobj) continue;
if (full_metadata) {
jsonobj['id'] = id;
files.push(jsonobj);
continue;
}
var upload_date = formatDateString(jsonobj.upload_date);
var isaudio = type === 'audio';
var file_obj = new File(id, jsonobj.title, jsonobj.thumbnail, isaudio, jsonobj.duration, jsonobj.webpage_url, jsonobj.uploader,
stats.size, file, upload_date, jsonobj.description, jsonobj.view_count, jsonobj.height, jsonobj.abr);
files.push(file_obj);
}
return files;
}
async function createContainerZipFile(file_name, container_file_objs) {
const container_files_to_download = [];
for (let i = 0; i < container_file_objs.length; i++) {
const container_file_obj = container_file_objs[i];
container_files_to_download.push(container_file_obj.path);
}
return await createZipFile(path.join('appdata', file_name + '.zip'), container_files_to_download);
}
async function createZipFile(zip_file_path, file_paths) {
let output = fs.createWriteStream(zip_file_path);
var archive = archiver('zip', {
gzip: true,
zlib: { level: 9 } // Sets the compression level.
});
archive.on('error', function(err) {
logger.error(err);
throw err;
});
// pipe archive data to the output file
archive.pipe(output);
for (let file_path of file_paths) {
const file_name = path.parse(file_path).base;
archive.file(file_path, {name: file_name})
}
await archive.finalize();
// wait a tiny bit for the zip to reload in fs
await wait(100);
return zip_file_path;
}
function getJSONMp4(name, customPath, openReadPerms = false) {
var obj = null; // output
if (!customPath) customPath = config_api.getConfigItem('ytdl_video_folder_path');
var jsonPath = path.join(customPath, name + ".info.json");
var alternateJsonPath = path.join(customPath, name + ".mp4.info.json");
if (fs.existsSync(jsonPath))
{
obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
} else if (fs.existsSync(alternateJsonPath)) {
obj = JSON.parse(fs.readFileSync(alternateJsonPath, 'utf8'));
}
else obj = 0;
return obj;
}
function getJSONMp3(name, customPath, openReadPerms = false) {
var obj = null;
if (!customPath) customPath = config_api.getConfigItem('ytdl_audio_folder_path');
var jsonPath = path.join(customPath, name + ".info.json");
var alternateJsonPath = path.join(customPath, name + ".mp3.info.json");
if (fs.existsSync(jsonPath)) {
obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
}
else if (fs.existsSync(alternateJsonPath)) {
obj = JSON.parse(fs.readFileSync(alternateJsonPath, 'utf8'));
}
else
obj = 0;
return obj;
}
function getJSON(file_path, type) {
const ext = type === 'audio' ? '.mp3' : '.mp4';
let obj = null;
var jsonPath = removeFileExtension(file_path) + '.info.json';
var alternateJsonPath = removeFileExtension(file_path) + `${ext}.info.json`;
if (fs.existsSync(jsonPath))
{
obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
} else if (fs.existsSync(alternateJsonPath)) {
obj = JSON.parse(fs.readFileSync(alternateJsonPath, 'utf8'));
}
else obj = 0;
return obj;
}
function getJSONByType(type, name, customPath, openReadPerms = false) {
return type === 'audio' ? getJSONMp3(name, customPath, openReadPerms) : getJSONMp4(name, customPath, openReadPerms)
}
function getDownloadedThumbnail(file_path) {
const file_path_no_extension = removeFileExtension(file_path);
let jpgPath = file_path_no_extension + '.jpg';
let webpPath = file_path_no_extension + '.webp';
let pngPath = file_path_no_extension + '.png';
if (fs.existsSync(jpgPath))
return jpgPath;
else if (fs.existsSync(webpPath))
return webpPath;
else if (fs.existsSync(pngPath))
return pngPath;
else
return null;
}
function getExpectedFileSize(input_info_jsons) {
// treat single videos as arrays to have the file sizes checked/added to. makes the code cleaner
const info_jsons = Array.isArray(input_info_jsons) ? input_info_jsons : [input_info_jsons];
let expected_filesize = 0;
info_jsons.forEach(info_json => {
const formats = info_json['format_id'].split('+');
let individual_expected_filesize = 0;
formats.forEach(format_id => {
info_json.formats.forEach(available_format => {
if (available_format.format_id === format_id && available_format.filesize) {
individual_expected_filesize += available_format.filesize;
}
});
});
expected_filesize += individual_expected_filesize;
});
return expected_filesize;
}
function fixVideoMetadataPerms(file_path, type) {
if (is_windows) return;
const ext = type === 'audio' ? '.mp3' : '.mp4';
const file_path_no_extension = removeFileExtension(file_path);
const files_to_fix = [
// JSONs
file_path_no_extension + '.info.json',
file_path_no_extension + ext + '.info.json',
// Thumbnails
file_path_no_extension + '.webp',
file_path_no_extension + '.jpg'
];
for (const file of files_to_fix) {
if (!fs.existsSync(file)) continue;
fs.chmodSync(file, 0o644);
}
}
function deleteJSONFile(file_path, type) {
const ext = type === 'audio' ? '.mp3' : '.mp4';
const file_path_no_extension = removeFileExtension(file_path);
let json_path = file_path_no_extension + '.info.json';
let alternate_json_path = file_path_no_extension + ext + '.info.json';
if (fs.existsSync(json_path)) fs.unlinkSync(json_path);
if (fs.existsSync(alternate_json_path)) fs.unlinkSync(alternate_json_path);
}
async function removeIDFromArchive(archive_path, id) {
let data = await fs.readFile(archive_path, {encoding: 'utf-8'});
if (!data) {
logger.error('Archive could not be found.');
return;
}
let dataArray = data.split('\n'); // convert file data in an array
const searchKeyword = id; // we are looking for a line, contains, key word id in the file
let lastIndex = -1; // let say, we have not found the keyword
for (let index=0; index<dataArray.length; index++) {
if (dataArray[index].includes(searchKeyword)) { // check if a line contains the id keyword
lastIndex = index; // found a line includes a id keyword
break;
}
}
const line = dataArray.splice(lastIndex, 1); // remove the keyword id from the data Array
// UPDATE FILE WITH NEW DATA
const updatedData = dataArray.join('\n');
await fs.writeFile(archive_path, updatedData);
if (line) return line;
}
function durationStringToNumber(dur_str) {
if (typeof dur_str === 'number') return dur_str;
let num_sum = 0;
const dur_str_parts = dur_str.split(':');
for (let i = dur_str_parts.length-1; i >= 0; i--) {
num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i));
}
return num_sum;
}
function getMatchingCategoryFiles(category, files) {
return files && files.filter(file => file.category && file.category.uid === category.uid);
}
function addUIDsToCategory(category, files) {
const files_that_match = getMatchingCategoryFiles(category, files);
category['uids'] = files_that_match.map(file => file.uid);
return files_that_match;
}
function getCurrentDownloader() {
const details_json = fs.readJSONSync(CONSTS.DETAILS_BIN_PATH);
return details_json['downloader'];
}
async function recFindByExt(base, ext, files, result, recursive = true)
{
files = files || (await fs.readdir(base))
result = result || []
for (const file of files) {
var newbase = path.join(base,file)
if ( (await fs.stat(newbase)).isDirectory() )
{
if (!recursive) continue;
result = await recFindByExt(newbase,ext,await fs.readdir(newbase),result)
}
else
{
if ( file.substr(-1*(ext.length+1)) == '.' + ext )
{
result.push(newbase)
}
}
}
return result
}
function removeFileExtension(filename) {
const filename_parts = filename.split('.');
filename_parts.splice(filename_parts.length - 1);
return filename_parts.join('.');
}
function formatDateString(date_string) {
return date_string ? `${date_string.substring(0, 4)}-${date_string.substring(4, 6)}-${date_string.substring(6, 8)}` : 'N/A';
}
function createEdgeNGrams(str) {
if (str && str.length > 3) {
const minGram = 3
const maxGram = str.length
return str.split(" ").reduce((ngrams, token) => {
if (token.length > minGram) {
for (let i = minGram; i <= maxGram && i <= token.length; ++i) {
ngrams = [...ngrams, token.substr(0, i)]
}
} else {
ngrams = [...ngrams, token]
}
return ngrams
}, []).join(" ")
}
return str
}
// ffmpeg helper functions
async function cropFile(file_path, start, end, ext) {
return new Promise(resolve => {
const temp_file_path = `${file_path}.cropped${ext}`;
let base_ffmpeg_call = ffmpeg(file_path);
if (start) {
base_ffmpeg_call = base_ffmpeg_call.seekOutput(start);
}
if (end) {
base_ffmpeg_call = base_ffmpeg_call.duration(end - start);
}
base_ffmpeg_call
.on('end', () => {
logger.verbose(`Cropping for '${file_path}' complete.`);
fs.unlinkSync(file_path);
fs.moveSync(temp_file_path, file_path);
resolve(true);
})
.on('error', (err) => {
logger.error(`Failed to crop ${file_path}.`);
logger.error(err);
resolve(false);
}).save(temp_file_path);
});
}
/**
* setTimeout, but its a promise.
* @param {number} ms
*/
async function wait(ms) {
await new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function checkExistsWithTimeout(filePath, timeout) {
return new Promise(function (resolve, reject) {
var timer = setTimeout(function () {
if (watcher) watcher.close();
reject(new Error('File did not exists and was not created during the timeout.'));
}, timeout);
fs.access(filePath, fs.constants.R_OK, function (err) {
if (!err) {
clearTimeout(timer);
if (watcher) watcher.close();
resolve();
}
});
var dir = path.dirname(filePath);
var basename = path.basename(filePath);
var watcher = fs.watch(dir, function (eventType, filename) {
if (eventType === 'rename' && filename === basename) {
clearTimeout(timer);
if (watcher) watcher.close();
resolve();
}
});
});
}
// helper function to download file using fetch
async function fetchFile(url, path, file_label) {
var len = null;
const res = await fetch(url);
len = parseInt(res.headers.get("Content-Length"), 10);
var bar = new ProgressBar(` Downloading ${file_label} [:bar] :percent :etas`, {
complete: '=',
incomplete: ' ',
width: 20,
total: len
});
const fileStream = fs.createWriteStream(path);
await new Promise((resolve, reject) => {
res.body.pipe(fileStream);
res.body.on("error", (err) => {
reject(err);
});
res.body.on('data', function (chunk) {
bar.tick(chunk.length);
});
fileStream.on("finish", function() {
resolve();
});
});
}
// objects
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) {
this.id = id;
this.title = title;
this.thumbnailURL = thumbnailURL;
this.isAudio = isAudio;
this.duration = duration;
this.url = url;
this.uploader = uploader;
this.size = size;
this.path = path;
this.upload_date = upload_date;
this.description = description;
this.view_count = view_count;
this.height = height;
this.abr = abr;
}
module.exports = {
getJSONMp3: getJSONMp3,
getJSONMp4: getJSONMp4,
getJSON: getJSON,
getTrueFileName: getTrueFileName,
getDownloadedThumbnail: getDownloadedThumbnail,
getExpectedFileSize: getExpectedFileSize,
fixVideoMetadataPerms: fixVideoMetadataPerms,
deleteJSONFile: deleteJSONFile,
removeIDFromArchive: removeIDFromArchive,
getDownloadedFilesByType: getDownloadedFilesByType,
createContainerZipFile: createContainerZipFile,
durationStringToNumber: durationStringToNumber,
getMatchingCategoryFiles: getMatchingCategoryFiles,
addUIDsToCategory: addUIDsToCategory,
getCurrentDownloader: getCurrentDownloader,
recFindByExt: recFindByExt,
removeFileExtension: removeFileExtension,
formatDateString: formatDateString,
cropFile: cropFile,
createEdgeNGrams: createEdgeNGrams,
wait: wait,
checkExistsWithTimeout: checkExistsWithTimeout,
fetchFile: fetchFile,
File: File
}

4
backend/video/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

Binary file not shown.

127
backend/youtube-dl.js Normal file
View File

@@ -0,0 +1,127 @@
const fs = require('fs-extra');
const fetch = require('node-fetch');
const logger = require('./logger');
const utils = require('./utils');
const CONSTS = require('./consts');
const config_api = require('./config.js');
const is_windows = process.platform === 'win32';
const download_sources = {
'youtube-dl': {
'tags_url': 'https://api.github.com/repos/ytdl-org/youtube-dl/tags',
'func': downloadLatestYoutubeDLBinary
},
'youtube-dlc': {
'tags_url': 'https://api.github.com/repos/blackjack4494/yt-dlc/tags',
'func': downloadLatestYoutubeDLCBinary
},
'yt-dlp': {
'tags_url': 'https://api.github.com/repos/yt-dlp/yt-dlp/tags',
'func': downloadLatestYoutubeDLPBinary
}
}
exports.checkForYoutubeDLUpdate = async () => {
return new Promise(async resolve => {
const default_downloader = config_api.getConfigItem('ytdl_default_downloader');
const tags_url = download_sources[default_downloader]['tags_url'];
// get current version
let current_app_details_exists = fs.existsSync(CONSTS.DETAILS_BIN_PATH);
if (!current_app_details_exists) {
logger.warn(`Failed to get youtube-dl binary details at location '${CONSTS.DETAILS_BIN_PATH}'. Generating file...`);
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, {"version":"2020.00.00", "downloader": default_downloader});
}
let current_app_details = JSON.parse(fs.readFileSync(CONSTS.DETAILS_BIN_PATH));
let current_version = current_app_details['version'];
let current_downloader = current_app_details['downloader'];
let stored_binary_path = current_app_details['path'];
if (!stored_binary_path || typeof stored_binary_path !== 'string') {
// logger.info(`INFO: Failed to get youtube-dl binary path at location: ${CONSTS.DETAILS_BIN_PATH}, attempting to guess actual path...`);
const guessed_base_path = 'node_modules/youtube-dl/bin/';
const guessed_file_path = guessed_base_path + 'youtube-dl' + (is_windows ? '.exe' : '');
if (fs.existsSync(guessed_file_path)) {
stored_binary_path = guessed_file_path;
// logger.info('INFO: Guess successful! Update process continuing...')
} else {
logger.error(`Guess '${guessed_file_path}' is not correct. Cancelling update check. Verify that your youtube-dl binaries exist by running npm install.`);
resolve(null);
return;
}
}
// got version, now let's check the latest version from the youtube-dl API
fetch(tags_url, {method: 'Get'})
.then(async res => res.json())
.then(async (json) => {
// check if the versions are different
if (!json || !json[0]) {
logger.error(`Failed to check ${default_downloader} version for an update.`)
resolve(null);
return;
}
const latest_update_version = json[0]['name'];
if (current_version !== latest_update_version || default_downloader !== current_downloader) {
// versions different or different downloader is being used, download new update
resolve(latest_update_version);
} else {
resolve(null);
}
return;
})
.catch(err => {
logger.error(`Failed to check ${default_downloader} version for an update.`)
logger.error(err);
resolve(null);
return;
});
});
}
exports.updateYoutubeDL = async (latest_update_version) => {
const default_downloader = config_api.getConfigItem('ytdl_default_downloader');
await download_sources[default_downloader]['func'](latest_update_version);
}
async function downloadLatestYoutubeDLBinary(new_version) {
const file_ext = is_windows ? '.exe' : '';
const download_url = `https://github.com/ytdl-org/youtube-dl/releases/latest/download/youtube-dl${file_ext}`;
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
await utils.fetchFile(download_url, output_path, `youtube-dl ${new_version}`);
updateDetailsJSON(new_version, 'youtube-dl');
}
async function downloadLatestYoutubeDLCBinary(new_version) {
const file_ext = is_windows ? '.exe' : '';
const download_url = `https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc${file_ext}`;
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
await utils.fetchFile(download_url, output_path, `youtube-dlc ${new_version}`);
updateDetailsJSON(new_version, 'youtube-dlc');
}
async function downloadLatestYoutubeDLPBinary(new_version) {
const file_ext = is_windows ? '.exe' : '';
const download_url = `https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp${file_ext}`;
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
await utils.fetchFile(download_url, output_path, `yt-dlp ${new_version}`);
updateDetailsJSON(new_version, 'yt-dlp');
}
function updateDetailsJSON(new_version, downloader) {
const details_json = fs.readJSONSync(CONSTS.DETAILS_BIN_PATH);
if (new_version) details_json['version'] = new_version;
details_json['downloader'] = downloader;
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, details_json);
}

23
chart/.helmignore Normal file
View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

24
chart/Chart.yaml Normal file
View File

@@ -0,0 +1,24 @@
apiVersion: v2
name: youtubedl-material
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "4.2"

22
chart/templates/NOTES.txt Normal file
View File

@@ -0,0 +1,22 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "youtubedl-material.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "youtubedl-material.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "youtubedl-material.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "youtubedl-material.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "youtubedl-material.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "youtubedl-material.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "youtubedl-material.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "youtubedl-material.labels" -}}
helm.sh/chart: {{ include "youtubedl-material.chart" . }}
{{ include "youtubedl-material.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "youtubedl-material.selectorLabels" -}}
app.kubernetes.io/name: {{ include "youtubedl-material.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "youtubedl-material.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "youtubedl-material.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,21 @@
{{- if and .Values.persistence.appdata.enabled (not .Values.persistence.appdata.existingClaim) }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ template "youtubedl-material.fullname" . }}-appdata
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.appdata.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.appdata.size | quote }}
{{- if .Values.persistence.appdata.storageClass }}
{{- if (eq "-" .Values.persistence.appdata.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.persistence.appdata.storageClass }}"
{{- end }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,21 @@
{{- if and .Values.persistence.audio.enabled (not .Values.persistence.audio.existingClaim) }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ template "youtubedl-material.fullname" . }}-audio
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.audio.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.audio.size | quote }}
{{- if .Values.persistence.audio.storageClass }}
{{- if (eq "-" .Values.persistence.audio.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.persistence.audio.storageClass }}"
{{- end }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,121 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "youtubedl-material.fullname" . }}
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "youtubedl-material.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "youtubedl-material.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "youtubedl-material.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 17442
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- mountPath: /app/appdata
name: appdata
{{- if .Values.persistence.appdata.subPath }}
subPath: {{ .Values.persistence.appdata.subPath }}
{{- end }}
- mountPath: /app/audio
name: audio
{{- if .Values.persistence.audio.subPath }}
subPath: {{ .Values.persistence.audio.subPath }}
{{- end }}
- mountPath: /app/video
name: video
{{- if .Values.persistence.video.subPath }}
subPath: {{ .Values.persistence.video.subPath }}
{{- end }}
- mountPath: /app/subscriptions
name: subscriptions
{{- if .Values.persistence.subscriptions.subPath }}
subPath: {{ .Values.persistence.subscriptions.subPath }}
{{- end }}
- mountPath: /app/users
name: users
{{- if .Values.persistence.users.subPath }}
subPath: {{ .Values.persistence.users.subPath }}
{{- end }}
volumes:
- name: appdata
{{- if .Values.persistence.appdata.enabled}}
persistentVolumeClaim:
claimName: {{ if .Values.persistence.appdata.existingClaim }}{{ .Values.persistence.appdata.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-appdata{{- end }}
{{- else }}
emptyDir: {}
{{- end }}
- name: audio
{{- if .Values.persistence.audio.enabled}}
persistentVolumeClaim:
claimName: {{ if .Values.persistence.audio.existingClaim }}{{ .Values.persistence.audio.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-audio{{- end }}
{{- else }}
emptyDir: {}
{{- end }}
- name: subscriptions
{{- if .Values.persistence.subscriptions.enabled}}
persistentVolumeClaim:
claimName: {{ if .Values.persistence.subscriptions.existingClaim }}{{ .Values.persistence.subscriptions.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-subscriptions{{- end }}
{{- else }}
emptyDir: {}
{{- end }}
- name: users
{{- if .Values.persistence.users.enabled}}
persistentVolumeClaim:
claimName: {{ if .Values.persistence.users.existingClaim }}{{ .Values.persistence.users.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-users{{- end }}
{{- else }}
emptyDir: {}
{{- end }}
- name: video
{{- if .Values.persistence.video.enabled}}
persistentVolumeClaim:
claimName: {{ if .Values.persistence.video.existingClaim }}{{ .Values.persistence.video.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-video{{- end }}
{{- else }}
emptyDir: {}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,41 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "youtubedl-material.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
backend:
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "youtubedl-material.fullname" . }}
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "youtubedl-material.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "youtubedl-material.serviceAccountName" . }}
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,21 @@
{{- if and .Values.persistence.subscriptions.enabled (not .Values.persistence.subscriptions.existingClaim) }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ template "youtubedl-material.fullname" . }}-subscriptions
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.subscriptions.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.subscriptions.size | quote }}
{{- if .Values.persistence.subscriptions.storageClass }}
{{- if (eq "-" .Values.persistence.subscriptions.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.persistence.subscriptions.storageClass }}"
{{- end }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "youtubedl-material.fullname" . }}-test-connection"
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "youtubedl-material.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@@ -0,0 +1,21 @@
{{- if and .Values.persistence.users.enabled (not .Values.persistence.users.existingClaim) }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ template "youtubedl-material.fullname" . }}-users
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.users.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.users.size | quote }}
{{- if .Values.persistence.users.storageClass }}
{{- if (eq "-" .Values.persistence.users.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.persistence.users.storageClass }}"
{{- end }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,21 @@
{{- if and .Values.persistence.video.enabled (not .Values.persistence.video.existingClaim) }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ template "youtubedl-material.fullname" . }}-video
labels:
{{- include "youtubedl-material.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.video.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.video.size | quote }}
{{- if .Values.persistence.video.storageClass }}
{{- if (eq "-" .Values.persistence.video.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: "{{ .Values.persistence.video.storageClass }}"
{{- end }}
{{- end }}
{{- end -}}

153
chart/values.yaml Normal file
View File

@@ -0,0 +1,153 @@
# Default values for youtubedl-material.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: tzahi12345/youtubedl-material
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 17442
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths: []
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
persistence:
appdata:
enabled: true
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
## If you want to reuse an existing claim, you can pass the name of the PVC using
## the existingClaim variable
# existingClaim: your-claim
# subPath: some-subpath
accessMode: ReadWriteOnce
size: 1Gi
audio:
enabled: true
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
##
## If you want to reuse an existing claim, you can pass the name of the PVC using
## the existingClaim variable
# existingClaim: your-claim
# subPath: some-subpath
accessMode: ReadWriteOnce
size: 50Gi
video:
enabled: true
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
##
## If you want to reuse an existing claim, you can pass the name of the PVC using
## the existingClaim variable
# existingClaim: your-claim
# subPath: some-subpath
accessMode: ReadWriteOnce
size: 50Gi
subscriptions:
enabled: true
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
##
## If you want to reuse an existing claim, you can pass the name of the PVC using
## the existingClaim variable
# existingClaim: your-claim
# subPath: some-subpath
accessMode: ReadWriteOnce
size: 50Gi
users:
enabled: true
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
##
## If you want to reuse an existing claim, you can pass the name of the PVC using
## the existingClaim variable
# existingClaim: your-claim
# subPath: some-subpath
accessMode: ReadWriteOnce
size: 50Gi
nodeSelector: {}
tolerations: []
affinity: {}

28
chrome-extension.pem Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMX9Wk5SM5cIfY
6ReKX3ybY1rsbNbOzG8ceN7yyeXB0mor8pVsX1MOna2HewOyBuaaYNJRO4tJBxic
7a8zQErfgHL/i/QrVvVCpfJ7xKvq6zij5NYoqd/FBUwawqjeH5/voIcAp9z5Vmsr
kL0sxJUKy6b4IWNp3noU7Nvq2RwxnXQbKDhz8FrX6oQAnDC6gsG5a2OSPsaE4oqw
6nmonORJypmpP5hqyHY8ffXBT2lAxjHT7OnYbaCBe2TQP8+rH6rDBOhjVNtUJ089
ocTQL6LtQEPkcF4yKJmtcOwHl8OPGZs5l9i8xb4j9RuSPkm2lbzZX8sOsdGGoqJZ
q68nYhsHAgMBAAECggEAXmtKEzfPObq88B/kAcgSk+FngMHZzcmR7bgD3GwdSxnQ
dkRI9zvk7eQ35tcUwntAr4Lat6/ILjFqlBmVLxrdXHuF5Xz9jcZLYgKzz61xdYM9
dC6FKF0u5eGIIvbauGAo7jaeGFX1F3Zu5b4lP9kEOGwU1B7sxF0FzsQM5+dtCJgv
We/hWQeF+9gtoVnkCSS/Mq2p0UomXXHW0Bz4+HuHlTR9aiYbviYnotABiLUhZyzt
v5yUaktb9qniBfdLpRlq8cp06xYlTEA9gJpa4Pnok8OWUsbAiW6EiXUSaZ/cchVa
AnO8WWYvVOnnt6WHI3+QdFTnqVjE5TBX4N/7bVhHGQKBgQD0dtbFqp7vZK/jVYvE
z0WPdySOg2ZDmoSfk5ZlR1+Y9zWToHv0qu8zqoOjL8Ubxrh9fGlOow+cCVdkEuaa
jWC2AWetuRvW0Z5A3XMXr0/N/1fgOkTqtp3WNrUPjVJahEg3lN+90opgFoT8swSi
s1oxW0oLcVIlrjhGBXAPCfsAuQKBgQDWBLRhHsRAvGcK5wGuVnxVApTIyBOermsW
3bJt+7+UI+4sYrBAwkWdQG93IG0cQtn48TEPBgmR2fjRF5IFT9M4/u+QOeeByT7I
we7nVtHgSY5ByC9N0mjWbcmSg8fktz/LonjldNC4kWdOFb75fxGf8kOGS5rUaMA4
zHucfB6ZvwKBgQCPHJrysMXGY21MaqIeHzEboaX3ABl37hdBzAa5V6UxSVdGCydF
vmO2HVZey/JaJmWOoKyNaowSzq0oWqBBTg6VvhDR9JHFmoVId9uOvAS+FYN+Mt5x
gWK5KuGoLxVNBC+6yh6JY526TrSfsrU+Aj0Es+qO9FIg2PL8muZVB4S3kQKBgH/5
CDMaxpc/EQ5/2413wZjDllwI51J3USm3Hz6Mzp2ybnSz/lh60k2Zfg1polTH1Lb6
4i7tmUNRZ2sAARyUAuWN64n+VeRRhe1dqZFDZPQMh7fmEAMk0fOGaoXlrt2ghdEq
Mchi9Xun1nHmpu9hgBR4NNBU3RwuFuLfwvprbZDZAoGAWa62QJChE86xQGP1MrL2
SbIzw3cfeP5xdQ3MKldJiy5IkbMR7Z13WZ7FwvPTy0g/onLHD1rqlm1kUMsGRHpD
5vH06PNpKXQ6x8BYaRGtE6P39jLycO/X+WK/lYTrWo1bR+mGCebDh4B5XrwT3gI6
x4Gvz134pZCTyQCf5JCwbQs=
-----END PRIVATE KEY-----

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

7
chrome-extension/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5
chrome-extension/js/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,26 @@
{
"manifest_version": 2,
"name": "YoutubeDL-Material",
"version": "0.4",
"description": "The Official Firefox & Chrome Extension of YoutubeDL-Material, an open-source and self-hosted YouTube downloader.",
"browser_action": {
"default_icon": "favicon.png",
"default_popup": "popup.html",
"default_title": "YoutubeDL-Material"
},
"permissions": [
"tabs",
"storage",
"contextMenus"
],
"options_ui": {
"page": "options.html",
"open_in_tab": false
},
"browser_specific_settings": {
"gecko": {
"id": "IsaacMGrynsztein@gmail.com",
"strict_min_version": "57.0"
}
}
}

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>YoutubeDL-Material Extension Options</title>
<!-- Scripts -->
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/popper.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<!-- Cascading Style Sheets -->
<link href="css/bootstrap.min.css" rel="stylesheet" media="screen">
</head>
<body>
<h2>Settings</h2>
<div style="width: 95%; margin: 0 auto;">
<div class="form-group">
<label for="frontend_url">Frontend URL:</label>
<input class="form-control" type="text" id="frontend_url">
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="audio_only">
Audio only
</label>
</div>
<div id="status"></div>
<button style="margin-bottom: 10px;" class="btn btn-primary" data-toggle="collapse" data-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample" id="save">Save</button>
<div class="collapse" id="collapseExample">
<div class="card card-body">
Settings successfully saved!
</div>
</div>
</div>
<script src="options.js"></script>
</body>
</html>

View File

@@ -0,0 +1,30 @@
// Saves options to chrome.storage
function save_options() {
var frontend_url = document.getElementById('frontend_url').value;
var audio_only = document.getElementById('audio_only').checked;
chrome.storage.sync.set({
frontend_url: frontend_url,
audio_only: audio_only
}, function() {
// Update status to let user know options were saved.
$('#collapseExample').collapse('show');
setTimeout(function() {
$('#collapseExample').collapse('hide');
}, 2000);
});
}
// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
chrome.storage.sync.get({
frontend_url: 'http://localhost',
audio_only: false
}, function(items) {
document.getElementById('frontend_url').value = items.frontend_url;
document.getElementById('audio_only').checked = items.audio_only;
});
}
document.addEventListener('DOMContentLoaded', restore_options);
document.getElementById('save').addEventListener('click',
save_options);

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<!-- Scripts -->
<script src="js/jquery-3.4.1.min.js"></script>
<script src="js/popper.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<!-- Cascading Style Sheets -->
<link href="css/bootstrap.min.css" rel="stylesheet" media="screen">
</head>
<body>
<div style="width: 400px; margin: 0 auto;">
<div style="margin: 10px;">
<div class="checkbox">
<label>
<input type="checkbox" id="audio_only">
Audio only
</label>
</div>
<div class="input-group mb-3">
<input id="url_input" type="text" class="form-control" placeholder="URL" aria-label="URL" aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="download">Download</button>
</div>
</div>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>

50
chrome-extension/popup.js Normal file
View File

@@ -0,0 +1,50 @@
function audioOnlyClicked() {
console.log('audio only clicked');
var audio_only = document.getElementById("audio_only").checked;
// save state
chrome.storage.sync.set({
audio_only: audio_only
}, function() {});
}
function downloadVideo() {
var input_url = document.getElementById("url_input").value
// get the frontend_url
chrome.storage.sync.get({
frontend_url: 'http://localhost',
audio_only: false
}, function(items) {
var download_url = items.frontend_url + '/#/home;url=' + encodeURIComponent(input_url) + ';audioOnly=' + items.audio_only;
chrome.tabs.create({ url: download_url });
});
}
function loadInputs() {
// load audio-only input
chrome.storage.sync.get({
frontend_url: 'http://localhost',
audio_only: false
}, function(items) {
document.getElementById("audio_only").checked = items.audio_only;
});
// load url input
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var activeTab = tabs[0];
var current_url = activeTab.url;
console.log(current_url);
if (current_url && current_url.includes('youtube.com')) {
document.getElementById("url_input").value = current_url;
}
});
}
document.getElementById('download').addEventListener('click',
downloadVideo);
document.getElementById('audio_only').addEventListener('click',
audioOnlyClicked);
document.addEventListener('DOMContentLoaded', loadInputs);

31
docker-build.sh Normal file
View File

@@ -0,0 +1,31 @@
#!/bin/sh
# THANK YOU TALULAH (https://github.com/nottalulah) for your help in figuring this out
# and also optimizing some code with this commit.
# xoxo :D
set -xeuo pipefail
case $(uname -m) in
x86_64)
ARCH=amd64;;
aarch64)
ARCH=arm64;;
armhf)
ARCH=armhf;;
armv7)
ARCH=armel;;
armv7l)
ARCH=armel;;
*)
echo "Unsupported architecture: $(uname -m)"
exit 1
esac
echo "Architecture: $ARCH"
wget "https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-${ARCH}-static.tar.xz" -O ffmpeg.txz
mkdir /tmp/ffmpeg
tar xf ffmpeg.txz -C /tmp/ffmpeg
cp /tmp/ffmpeg/*/ffmpeg /usr/local/bin/ffmpeg
cp /tmp/ffmpeg/*/ffprobe /usr/local/bin/ffprobe
rm -rf /tmp/ffmpeg ffmpeg.txz

30
docker-compose.yml Normal file
View File

@@ -0,0 +1,30 @@
version: "2"
services:
ytdl_material:
environment:
ALLOW_CONFIG_MUTATIONS: 'true'
ytdl_mongodb_connection_string: 'mongodb://ytdl-mongo-db:27017'
ytdl_use_local_db: 'false'
write_ytdl_config: 'true'
restart: always
depends_on:
- ytdl-mongo-db
volumes:
- ./appdata:/app/appdata
- ./audio:/app/audio
- ./video:/app/video
- ./subscriptions:/app/subscriptions
- ./users:/app/users
ports:
- "8998:17442"
image: tzahi12345/youtubedl-material:nightly
ytdl-mongo-db:
image: mongo
ports:
- "27017:27017"
logging:
driver: "none"
container_name: mongo-db
restart: always
volumes:
- ./db/:/data/db

12661
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +1,83 @@
{
"name": "youtube-dl-material",
"version": "0.0.0",
"version": "4.2.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"build": "ng build --configuration production",
"prebuild": "node src/postbuild.mjs",
"heroku-postbuild": "npm install --prefix backend",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"electron": "ng build --base-href ./ && electron ."
"electron": "ng build --base-href ./ && electron .",
"generate": "openapi --input ./\"Public API v1.yaml\" --output ./src/api-types --exportCore false --exportServices false --exportModels true"
},
"engines": {
"node": "12.3.1",
"npm": "6.10.3"
},
"private": true,
"dependencies": {
"@angular-devkit/core": "^8.3.12",
"@angular/animations": "^8.2.11",
"@angular/cdk": "^8.2.3",
"@angular/common": "^8.2.11",
"@angular/compiler": "^8.2.11",
"@angular/core": "^8.2.11",
"@angular/forms": "^8.2.11",
"@angular/http": "^7.2.15",
"@angular/material": "^8.2.3",
"@angular/platform-browser": "^8.2.11",
"@angular/platform-browser-dynamic": "^8.2.11",
"@angular/router": "^8.2.11",
"@angular-devkit/core": "^13.3.3",
"@angular/animations": "^13.3.4",
"@angular/cdk": "^13.3.4",
"@angular/common": "^13.3.4",
"@angular/compiler": "^13.3.4",
"@angular/core": "^13.3.4",
"@angular/forms": "^13.3.4",
"@angular/localize": "^13.3.4",
"@angular/material": "^13.3.4",
"@angular/platform-browser": "^13.3.4",
"@angular/platform-browser-dynamic": "^13.3.4",
"@angular/router": "^13.3.4",
"@fontsource/material-icons": "^4.5.4",
"@ngneat/content-loader": "^5.0.0",
"@videogular/ngx-videogular": "^5.0.1",
"core-js": "^2.4.1",
"crypto-js": "^4.1.1",
"file-saver": "^2.0.2",
"filesize": "^6.1.0",
"fingerprintjs2": "^2.1.0",
"fs-extra": "^10.0.0",
"material-icons": "^1.10.8",
"nan": "^2.14.1",
"ng-lazyload-image": "^7.0.1",
"ng4-configure": "^0.1.7",
"ngx-content-loading": "^0.1.3",
"rxjs": "^6.5.3",
"ngx-avatars": "^1.3.1",
"ngx-file-drop": "^13.0.0",
"rxjs": "^6.6.3",
"rxjs-compat": "^6.0.0-rc.0",
"tslib": "^1.10.0",
"videogular2": "^7.0.1",
"web-animations-js": "^2.3.2",
"zone.js": "~0.9.1"
"tslib": "^2.0.0",
"typescript": "~4.6.3",
"xliff-to-json": "^1.0.4",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.803.24",
"@angular/cli": "^8.3.12",
"@angular/compiler-cli": "^8.2.11",
"@angular/language-service": "^8.2.11",
"@angular-devkit/build-angular": "^13.3.3",
"@angular/cli": "^13.3.3",
"@angular/compiler-cli": "^13.3.4",
"@angular/language-service": "^13.3.4",
"@types/core-js": "^2.5.2",
"@types/file-saver": "^2.0.1",
"@types/jasmine": "2.5.45",
"@types/node": "~6.0.60",
"codelyzer": "^5.0.1",
"electron": "^8.0.1",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "~1.7.0",
"karma-chrome-launcher": "~2.1.1",
"@types/jasmine": "~3.6.0",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "^4.29.0",
"@typescript-eslint/parser": "^4.29.0",
"codelyzer": "^6.0.0",
"electron": "^13.6.6",
"eslint": "^7.32.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.16",
"karma-chrome-launcher": "~3.1.0",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"openapi-typescript-codegen": "^0.21.0",
"protractor": "~7.0.0",
"ts-node": "~3.0.4",
"tslint": "~5.3.2",
"typescript": "~3.5.3"
"tslint": "~6.1.0"
}
}

Binary file not shown.

View File

@@ -1,11 +1,11 @@
/* Coolors Exported Palette - coolors.co/e8aeb7-b8e1ff-a9fff7-94fbab-82aba1 */
/* HSL */
$color1: hsla(351%, 56%, 80%, 1);
$softblue: hsla(205%, 100%, 86%, 1);
$color3: hsla(174%, 100%, 83%, 1);
$color4: hsla(133%, 93%, 78%, 1);
$color5: hsla(165%, 20%, 59%, 1);
$color1: hsla(351, 56%, 80%, 1);
$softblue: hsla(205, 100%, 86%, 1);
$color3: hsla(174, 100%, 83%, 1);
$color4: hsla(133, 93%, 78%, 1);
$color5: hsla(165, 20%, 59%, 1);
/* RGB */
$color1: rgba(232, 174, 183, 1);

111
src/api-types/index.ts Normal file
View File

@@ -0,0 +1,111 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type { AddFileToPlaylistRequest } from './models/AddFileToPlaylistRequest';
export type { BaseChangePermissionsRequest } from './models/BaseChangePermissionsRequest';
export type { body_19 } from './models/body_19';
export type { body_20 } from './models/body_20';
export type { Category } from './models/Category';
export { CategoryRule } from './models/CategoryRule';
export type { ChangeRolePermissionsRequest } from './models/ChangeRolePermissionsRequest';
export type { ChangeUserPermissionsRequest } from './models/ChangeUserPermissionsRequest';
export type { CheckConcurrentStreamRequest } from './models/CheckConcurrentStreamRequest';
export type { CheckConcurrentStreamResponse } from './models/CheckConcurrentStreamResponse';
export type { ConcurrentStream } from './models/ConcurrentStream';
export type { Config } from './models/Config';
export type { ConfigResponse } from './models/ConfigResponse';
export type { CreateCategoryRequest } from './models/CreateCategoryRequest';
export type { CreateCategoryResponse } from './models/CreateCategoryResponse';
export type { CreatePlaylistRequest } from './models/CreatePlaylistRequest';
export type { CreatePlaylistResponse } from './models/CreatePlaylistResponse';
export type { CropFileSettings } from './models/CropFileSettings';
export type { DatabaseFile } from './models/DatabaseFile';
export { DBBackup } from './models/DBBackup';
export type { DBInfoResponse } from './models/DBInfoResponse';
export type { DeleteCategoryRequest } from './models/DeleteCategoryRequest';
export type { DeleteMp3Mp4Request } from './models/DeleteMp3Mp4Request';
export type { DeletePlaylistRequest } from './models/DeletePlaylistRequest';
export type { DeleteSubscriptionFileRequest } from './models/DeleteSubscriptionFileRequest';
export type { DeleteUserRequest } from './models/DeleteUserRequest';
export type { Download } from './models/Download';
export type { DownloadArchiveRequest } from './models/DownloadArchiveRequest';
export type { DownloadFileRequest } from './models/DownloadFileRequest';
export type { DownloadRequest } from './models/DownloadRequest';
export type { DownloadResponse } from './models/DownloadResponse';
export type { DownloadTwitchChatByVODIDRequest } from './models/DownloadTwitchChatByVODIDRequest';
export type { DownloadTwitchChatByVODIDResponse } from './models/DownloadTwitchChatByVODIDResponse';
export type { DownloadVideosForSubscriptionRequest } from './models/DownloadVideosForSubscriptionRequest';
export type { File } from './models/File';
export { FileType } from './models/FileType';
export type { GenerateArgsResponse } from './models/GenerateArgsResponse';
export type { GenerateNewApiKeyResponse } from './models/GenerateNewApiKeyResponse';
export type { GetAllCategoriesResponse } from './models/GetAllCategoriesResponse';
export type { GetAllDownloadsRequest } from './models/GetAllDownloadsRequest';
export type { GetAllDownloadsResponse } from './models/GetAllDownloadsResponse';
export type { GetAllFilesResponse } from './models/GetAllFilesResponse';
export type { GetAllSubscriptionsResponse } from './models/GetAllSubscriptionsResponse';
export type { GetAllTasksResponse } from './models/GetAllTasksResponse';
export type { GetDBBackupsResponse } from './models/GetDBBackupsResponse';
export type { GetDownloadRequest } from './models/GetDownloadRequest';
export type { GetDownloadResponse } from './models/GetDownloadResponse';
export type { GetFileFormatsRequest } from './models/GetFileFormatsRequest';
export type { GetFileFormatsResponse } from './models/GetFileFormatsResponse';
export type { GetFileRequest } from './models/GetFileRequest';
export type { GetFileResponse } from './models/GetFileResponse';
export type { GetFullTwitchChatRequest } from './models/GetFullTwitchChatRequest';
export type { GetFullTwitchChatResponse } from './models/GetFullTwitchChatResponse';
export type { GetLogsRequest } from './models/GetLogsRequest';
export type { GetLogsResponse } from './models/GetLogsResponse';
export type { GetMp3sResponse } from './models/GetMp3sResponse';
export type { GetMp4sResponse } from './models/GetMp4sResponse';
export type { GetPlaylistRequest } from './models/GetPlaylistRequest';
export type { GetPlaylistResponse } from './models/GetPlaylistResponse';
export type { GetPlaylistsRequest } from './models/GetPlaylistsRequest';
export type { GetPlaylistsResponse } from './models/GetPlaylistsResponse';
export type { GetRolesResponse } from './models/GetRolesResponse';
export type { GetSubscriptionRequest } from './models/GetSubscriptionRequest';
export type { GetSubscriptionResponse } from './models/GetSubscriptionResponse';
export type { GetTaskRequest } from './models/GetTaskRequest';
export type { GetTaskResponse } from './models/GetTaskResponse';
export type { GetUsersResponse } from './models/GetUsersResponse';
export type { IncrementViewCountRequest } from './models/IncrementViewCountRequest';
export type { inline_response_200_15 } from './models/inline_response_200_15';
export type { LoginRequest } from './models/LoginRequest';
export type { LoginResponse } from './models/LoginResponse';
export type { Playlist } from './models/Playlist';
export type { RegisterRequest } from './models/RegisterRequest';
export type { RegisterResponse } from './models/RegisterResponse';
export type { RestoreDBBackupRequest } from './models/RestoreDBBackupRequest';
export { Schedule } from './models/Schedule';
export type { SetConfigRequest } from './models/SetConfigRequest';
export type { SharingToggle } from './models/SharingToggle';
export type { SubscribeRequest } from './models/SubscribeRequest';
export type { SubscribeResponse } from './models/SubscribeResponse';
export type { Subscription } from './models/Subscription';
export type { SubscriptionRequestData } from './models/SubscriptionRequestData';
export type { SuccessObject } from './models/SuccessObject';
export type { TableInfo } from './models/TableInfo';
export type { Task } from './models/Task';
export type { TestConnectionStringRequest } from './models/TestConnectionStringRequest';
export type { TestConnectionStringResponse } from './models/TestConnectionStringResponse';
export type { TransferDBRequest } from './models/TransferDBRequest';
export type { TransferDBResponse } from './models/TransferDBResponse';
export type { TwitchChatMessage } from './models/TwitchChatMessage';
export type { UnsubscribeRequest } from './models/UnsubscribeRequest';
export type { UnsubscribeResponse } from './models/UnsubscribeResponse';
export type { UpdateCategoriesRequest } from './models/UpdateCategoriesRequest';
export type { UpdateCategoryRequest } from './models/UpdateCategoryRequest';
export type { UpdateConcurrentStreamRequest } from './models/UpdateConcurrentStreamRequest';
export type { UpdateConcurrentStreamResponse } from './models/UpdateConcurrentStreamResponse';
export type { UpdatePlaylistRequest } from './models/UpdatePlaylistRequest';
export type { UpdaterStatus } from './models/UpdaterStatus';
export type { UpdateServerRequest } from './models/UpdateServerRequest';
export type { UpdateTaskDataRequest } from './models/UpdateTaskDataRequest';
export type { UpdateTaskScheduleRequest } from './models/UpdateTaskScheduleRequest';
export type { UpdateUserRequest } from './models/UpdateUserRequest';
export type { User } from './models/User';
export { UserPermission } from './models/UserPermission';
export type { Version } from './models/Version';
export type { VersionInfoResponse } from './models/VersionInfoResponse';
export { YesNo } from './models/YesNo';

View File

@@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export interface AddFileToPlaylistRequest {
file_uid: string;
playlist_id: string;
}

View File

@@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { UserPermission } from './UserPermission';
import { YesNo } from './YesNo';
export interface BaseChangePermissionsRequest {
permission: UserPermission;
new_value: YesNo;
}

View File

@@ -0,0 +1,15 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { CategoryRule } from './CategoryRule';
export interface Category {
name?: string;
uid?: string;
rules?: Array<CategoryRule>;
/**
* Overrides file output for downloaded files in category
*/
custom_output?: string;
}

View File

@@ -0,0 +1,26 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export interface CategoryRule {
preceding_operator?: CategoryRule.preceding_operator;
comparator?: CategoryRule.comparator;
}
export namespace CategoryRule {
export enum preceding_operator {
OR = 'or',
AND = 'and',
}
export enum comparator {
INCLUDES = 'includes',
NOT_INCLUDES = 'not_includes',
EQUALS = 'equals',
NOT_EQUALS = 'not_equals',
}
}

View File

@@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
export interface ChangeRolePermissionsRequest extends BaseChangePermissionsRequest {
role: string;
}

View File

@@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { BaseChangePermissionsRequest } from './BaseChangePermissionsRequest';
export interface ChangeUserPermissionsRequest extends BaseChangePermissionsRequest {
user_uid: string;
}

View File

@@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export interface CheckConcurrentStreamRequest {
/**
* UID of the concurrent stream
*/
uid: string;
}

View File

@@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { ConcurrentStream } from './ConcurrentStream';
export interface CheckConcurrentStreamResponse {
stream: ConcurrentStream;
}

View File

@@ -0,0 +1,10 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export interface ConcurrentStream {
playback_timestamp?: number;
unix_timestamp?: number;
playing?: boolean;
}

View File

@@ -0,0 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export interface Config {
YoutubeDLMaterial: any;
}

View File

@@ -0,0 +1,10 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { Config } from './Config';
export interface ConfigResponse {
config_file: Config;
success: boolean;
}

View File

@@ -0,0 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export interface CreateCategoryRequest {
name: string;
}

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