diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf1e72c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/brotlicffi +/emsdk +/node_modules +/pycryptodome +index.pyodidetest.html +/static/js/pyodide.js +/static/js/custom.pyodide.js +/static/pyodide \ No newline at end of file diff --git a/README.md b/README.md index 893d3fa..58845ef 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,13 @@ -# yt-dlp-web-ui +#yt-dlp-web-ui +Requirements: +node/npm -## Getting started +How to: -To make it easy for you to get started with GitLab, here's a list of recommended next steps. +`npm install` -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! +That's all! -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin http://gitlab.local/rabbithutch/yt-dlp-web-ui.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](http://gitlab.local/rabbithutch/yt-dlp-web-ui/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +Once this code is hosted, add the host url to allowedorigins in conf.json in yt-dlp-web and reload the server \ No newline at end of file diff --git a/android-chrome-192x192.png b/android-chrome-192x192.png new file mode 100644 index 0000000..763bd9f Binary files /dev/null and b/android-chrome-192x192.png differ diff --git a/android-chrome-512x512.png b/android-chrome-512x512.png new file mode 100644 index 0000000..ddd04c8 Binary files /dev/null and b/android-chrome-512x512.png differ diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..1bc8eac Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100644 index 0000000..2f17d24 Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100644 index 0000000..cab9198 Binary files /dev/null and b/favicon-32x32.png differ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..ae1fe35 Binary files /dev/null and b/favicon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..3b8a35a --- /dev/null +++ b/index.html @@ -0,0 +1,251 @@ + + + + + yt-dlp web ui + + + + + + + + + + +
+ +
+
+
+ + + + +
+ Server limits (in seconds) + + + + + + + + + + + + + + + +
Maximum video lengthMaximum number of videos in playlistMaximum length of individual video in playlistMaximum gif lengthMaximum gif resolution
+
+
User Guides:
+

➕ Show

+ +
+

If you have any questions that are not addressed by the guide please check our gitlab to see if the question has been addressed: https://gitlab.com/wizdevgirl1/yt-dlp-web-ui/-/issues

+
Themes
+

➕ Show

+ + +

+ +
+ +
+ + +
+ +

+ +
+ +
+

+
+
+

Video is too long

+
+ + + +
+

Results:

+
+ + + + + + + + + + + +
StatusTitleLinkDetails
+
+
+ + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c98fa25 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,569 @@ +{ + "name": "yt-dlp-web-ui", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@ffmpeg/ffmpeg": "^0.12.5", + "@ffmpeg/util": "^0.12.0", + "pristinejs": "^0.1.1", + "pyodide": "^0.23.4", + "socket.io-client": "^4.7.2", + "turretcss": "^5.2.2", + "video.js": "^8.5.2", + "videojs-youtube": "^3.0.1" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", + "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ffmpeg/ffmpeg": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@ffmpeg/ffmpeg/-/ffmpeg-0.12.5.tgz", + "integrity": "sha512-grDWTaYSm0g/LEtpwkJtw0Kp1Iix/WF3KmtUo0Fm0V5GqKMUVzj1yzyaILg83o96wHSc8Y2d9Q3C37pYAF2wxQ==", + "dependencies": { + "@ffmpeg/types": "^0.12.0" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@ffmpeg/types": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@ffmpeg/types/-/types-0.12.0.tgz", + "integrity": "sha512-AuR4K+L6v1/9hVOsikU4rGGT5nKulQa8HrtYhpgBEq0HojoWB1c9bq3TTkNBpEvS/gC17WDMVJrqIGgXOj1DXA==", + "engines": { + "node": ">=16.6.0" + } + }, + "node_modules/@ffmpeg/util": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@ffmpeg/util/-/util-0.12.0.tgz", + "integrity": "sha512-8sHCW8H/ngqVhbRvCCX4e4uDNgZVoz8uNRry1zphIyzdX6flfBa2TmVwJ4g9/Qw//eAubEnuHaSxDGWWxcOTjg==", + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@videojs/http-streaming": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.5.3.tgz", + "integrity": "sha512-dty8lsZk9QPc0i4It79tjWsmPiaC3FpgARFM0vJGko4k3yKNZIYkAk8kjiDRfkAQH/HZ3rYi5dDTriFNzwSsIg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "4.0.0", + "aes-decrypter": "4.0.1", + "global": "^4.4.0", + "m3u8-parser": "^7.1.0", + "mpd-parser": "^1.1.1", + "mux.js": "7.0.0", + "video.js": "^7 || ^8" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "video.js": "^7 || ^8" + } + }, + "node_modules/@videojs/http-streaming/node_modules/m3u8-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.1.0.tgz", + "integrity": "sha512-7N+pk79EH4oLKPEYdgRXgAsKDyA/VCo0qCHlUwacttQA0WqsjZQYmNfywMvjlY9MpEBVZEt0jKFd73Kv15EBYQ==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0" + } + }, + "node_modules/@videojs/http-streaming/node_modules/m3u8-parser/node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/@videojs/http-streaming/node_modules/mux.js": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.0.0.tgz", + "integrity": "sha512-DeZmr+3NDrO02k4SREtl4VB5GyGPCz2fzMjDxBIlamkxffSTLge97rtNMoonnmFHTp96QggDucUtKv3fmyObrA==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "global": "^4.4.0" + }, + "bin": { + "muxjs-transmux": "bin/transmux.js" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/@videojs/vhs-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.0.0.tgz", + "integrity": "sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/@videojs/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "global": "~4.4.0", + "is-function": "^1.0.1" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aes-decrypter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.1.tgz", + "integrity": "sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0", + "pkcs7": "^1.0.4" + } + }, + "node_modules/aes-decrypter/node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/engine.io-client": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", + "integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/individual": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", + "integrity": "sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==" + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "node_modules/keycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha512-ps3I9jAdNtRpJrbBvQjpzyFbss/skHqzS+eu4RxKLaEAtFqkjZaB6TZMSivPbLxf4K7VI4SjR0P5mRCX5+Q25A==" + }, + "node_modules/m3u8-parser": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-6.2.0.tgz", + "integrity": "sha512-qlC00JTxYOxawcqg+RB8jbyNwL3foY/nCY61kyWP+RCuJE9APLeqB/nSlTjb4Mg0yRmyERgjswpdQxMvkeoDrg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0" + } + }, + "node_modules/m3u8-parser/node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/mpd-parser": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.2.2.tgz", + "integrity": "sha512-QCfB1koOoZw6E5La1cx+W/Yd0EZlRhHMqMr4TAJez0eRTuPDzPM5FWoiOqjyo37W+ISPLzmfJACSbJFEBjbL4Q==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "@xmldom/xmldom": "^0.8.3", + "global": "^4.4.0" + }, + "bin": { + "mpd-to-m3u8-json": "bin/parse.js" + } + }, + "node_modules/mpd-parser/node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mux.js": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.3.0.tgz", + "integrity": "sha512-/QTkbSAP2+w1nxV+qTcumSDN5PA98P0tjrADijIzQHe85oBK3Akhy9AHlH0ne/GombLMz1rLyvVsmrgRxoPDrQ==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "global": "^4.4.0" + }, + "bin": { + "muxjs-transmux": "bin/transmux.js" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, + "node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pkcs7": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", + "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==", + "dependencies": { + "@babel/runtime": "^7.5.5" + }, + "bin": { + "pkcs7": "bin/cli.js" + } + }, + "node_modules/pristinejs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pristinejs/-/pristinejs-0.1.1.tgz", + "integrity": "sha512-dhIBdMDCVSTIwmkIDBA+sPMN5OPTKaJO/d8nlnmnjghjFyTFN6KDnHX35CZ8OjzpmVjujSGvIgwDxpQtNKJ1rA==" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/pyodide": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.23.4.tgz", + "integrity": "sha512-WpQUHaIXQ1xede5BMqPAjBcmopxN22s5hEsYOR8T7/UW/fkNLFUn07SaemUgthbtvedD5JGymMMj4VpD9sGMTg==", + "dependencies": { + "base-64": "^1.0.0", + "node-fetch": "^2.6.1", + "ws": "^8.5.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/rust-result": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", + "integrity": "sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==", + "dependencies": { + "individual": "^2.0.0" + } + }, + "node_modules/safe-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", + "integrity": "sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==", + "dependencies": { + "rust-result": "^1.0.0" + } + }, + "node_modules/socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/turretcss": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/turretcss/-/turretcss-5.2.2.tgz", + "integrity": "sha512-N9rnnRrwEWCz5+P+cHW+HUmHWco0XuvcriXa6aFcMnJazDDTGixVI2hOB0Uu8wk7SvwNKi6qiEiaqweeiRst9g==" + }, + "node_modules/url-toolkit": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", + "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==" + }, + "node_modules/video.js": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.5.2.tgz", + "integrity": "sha512-6/uNXQV3xSaKLpaPf/bVvr7omd+82sKUp0RMBgIt4PxHIe28GtX+O+GcNfI2fuwBvcDRDqk5Ei5AG9bJJOpulA==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/http-streaming": "3.5.3", + "@videojs/vhs-utils": "^4.0.0", + "@videojs/xhr": "2.6.0", + "aes-decrypter": "^4.0.1", + "global": "4.4.0", + "keycode": "2.2.0", + "m3u8-parser": "^6.0.0", + "mpd-parser": "^1.0.1", + "mux.js": "^6.2.0", + "safe-json-parse": "4.0.0", + "videojs-contrib-quality-levels": "4.0.0", + "videojs-font": "4.1.0", + "videojs-vtt.js": "0.15.5" + } + }, + "node_modules/videojs-contrib-quality-levels": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.0.0.tgz", + "integrity": "sha512-u5rmd8BjLwANp7XwuQ0Q/me34bMe6zg9PQdHfTS7aXgiVRbNTb4djcmfG7aeSrkpZjg+XCLezFNenlJaCjBHKw==", + "dependencies": { + "global": "^4.4.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6" + }, + "peerDependencies": { + "video.js": "^8" + } + }, + "node_modules/videojs-font": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.1.0.tgz", + "integrity": "sha512-X1LuPfLZPisPLrANIAKCknZbZu5obVM/ylfd1CN+SsCmPZQ3UMDPcvLTpPBJxcBuTpHQq2MO1QCFt7p8spnZ/w==" + }, + "node_modules/videojs-vtt.js": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", + "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", + "dependencies": { + "global": "^4.3.1" + } + }, + "node_modules/videojs-youtube": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/videojs-youtube/-/videojs-youtube-3.0.1.tgz", + "integrity": "sha512-0gKgag7Zno/dDwIdk+h48ODKDulR4IW62RxGE81PrMwi0OX/wUcKO6m1j+DFYI+7qjtWMZTKnbtQoHGxvUrFQg==", + "dependencies": { + "video.js": "5.x || 6.x || 7.x || 8.x" + }, + "peerDependencies": { + "video.js": "5.x || 6.x || 7.x || 8.x" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3429307 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "socket.io-client": "^4.7.2", + "turretcss": "^5.2.2", + "video.js": "^8.5.2" + } +} diff --git a/static/css/custom.css b/static/css/custom.css new file mode 100644 index 0000000..33b1020 --- /dev/null +++ b/static/css/custom.css @@ -0,0 +1,52 @@ +:root { + --bgcolor: #ffffff; + --textcolor: #000000; + --gradienttop: #ffffff; + --gradientbottom: #ffffff; +} + +body { + margin: 20px; + background: var(--bgcolor); + color: var(--textcolor); + background-image: linear-gradient(var(--gradienttop), var(--gradientbottom)); +} + +table { + background: var(--bgcolor); +} + +input { + background: var(--bgcolor); + color: var(--textcolor); +} + +input:hover { + background: var(--bgcolor); + color: var(--textcolor); +} + +input:active { + background: var(--bgcolor); + color: var(--textcolor); +} + +select { + background: var(--bgcolor) !important; + color: var(--textcolor) !important; +} + +label { + background: var(--bgcolor); + color: var(--textcolor); +} + +th { + background: var(--bgcolor) !important; + color: var(--textcolor) !important; +} + +button { + background: var(--bgcolor); + color: var(--textcolor); +} \ No newline at end of file diff --git a/static/img/logo.png b/static/img/logo.png new file mode 100644 index 0000000..6dd764a Binary files /dev/null and b/static/img/logo.png differ diff --git a/static/img/logo05background.png b/static/img/logo05background.png new file mode 100644 index 0000000..1922ccf Binary files /dev/null and b/static/img/logo05background.png differ diff --git a/static/js/custom.js b/static/js/custom.js new file mode 100644 index 0000000..47e623d --- /dev/null +++ b/static/js/custom.js @@ -0,0 +1,533 @@ +// Global socketio variable for socketio operations +var socket = ""; +// Notification count +var notifications = 0; +// Web page title, used to help with notifications +var title = "yt-dlp web ui"; +// Global videojs player variable for videojs operations +var player = ""; +// Sources for video being clipped +var vsources = ""; +// All the easy id3 fields to add to the easy id3 form +var eid3fields = ["album", "bpm", "compilation", "composer", "encodedby", "lyricist", "length", "media", "mood", "title", "version", "artist", "albumartist", "conductor", "arranger", "discnumber", "tracknumber", "author", "albumartistsort", "albumsort", "composersort", "artistsort", "titlesort", "language", "genre", "date", "performer", "asin", "performer", "catalognumber"]; +// Info for current video +var ginfo = []; +// Limits of the server +var glimits = []; +// Predefined themes +var themes = { + "white": { + "bgcolor": "#ffffff", + "textcolor": "#000000" + }, + "yellow": { + "bgcolor": "#ffff00", + "textcolor": "#000000" + }, + "purple": { + "bgcolor": "#b642f5", + "textcolor": "#ffffff" + }, + "grayblue": { + "bgcolor": "#636987", + "textcolor": "#ffffff" + }, + "sky": { + "bgcolor": "#aaaaff", + "textcolor": "#ffffff" + }, + "black": { + "bgcolor": "#000000", + "textcolor": "#ffffff" + } +}; +// Do when the document has loaded +document.addEventListener("DOMContentLoaded", function () { + // Check url for youtube link and if there's a youtube link prefill the Video/Playlist URL field with it + var here = window.location.href; + if (here.includes("?h")) { + document.querySelector("#url").value = "https://youtube.com/watch?v=" + here.split("?v=")[1]; + } + // Load theme from last theme settings + // If a gradient is set up load the gradient + if (document.querySelector("#gradienttop").value != "none"){ + if (document.querySelector("#gradientbottom").value != "none"){ + setTheme(true); + } else { + setTheme(); + } + } else { + setTheme(); + } + // Connect to the socketio server + socket = io("localhost:8888"); + // Emit signal to query the limits of the server + socket.emit("limits", {}); + // Preset the step variable to 1, this makes sure step based functionality won't break + document.querySelector("#step").value = 1; + // Hide all warnings, in some cases this is unncessary, but better safe than sorry + document.querySelector("#clipperWarning").style.display = "none"; + document.querySelector("#clipperWarning2").style.display = "none"; + document.querySelector("#clipperWarning3").style.display = "none"; + document.querySelector("#clipperWarning4").style.display = "none"; + document.querySelector('#urlWarning').style.display = 'none'; + // Populate easy id3 configuration form with fields from the eid3fields variable + for (it in eid3fields) { + document.querySelector("#id3").innerHTML += ''; + document.querySelector("#id3").innerHTML += ''; + } + // Adds submit button to easy id3 configuration form + document.querySelector("#id3").innerHTML += '

'; + // All emissions return here + socket.on("done", (data) => { + // If necessary add a notification to the tab of the site + notify(); + // If there's an error in the result just make the row with the given data, logic for what to actually show is in makeRow + if (data["error"]) { + var tr = makeRow(data); + document.querySelector("#results").prepend(tr); + } else if (data["method"] == "limits") { + // For the limits method we don't want to show snything in the results table, but the limits table at the top of the page + var limits = data["limits"]; + glimits = limits; + // Populate the limits table + for (it in limits) { + document.querySelector("#" + limits[it]["limitid"]).innerHTML = limits[it]["limitvalue"]; + } + } else { + // Get method + var method = data["method"]; + // If done with step 1 of subtitles increment to step 2, otherwise reset to step 1 + if (method == "subtitles") { + if ("step" in data) { + document.querySelector("#step").value = 2; + } else { + document.querySelector("#step").value = 1; + } + } + // When done with clipping reset to step 1 + if (method == "clip") { + document.querySelector("#step").value = 1; + } + // When done wih getting streams set global video info + if (method == "streams") { + ginfo = data["info"]; + } + // When done with step 1 of clipping (getting the tracks), populate the sources for the videojs player and reload it + if (method == "getClipperTracks") { + var sources = []; + for (i in data["info"]["formats"]) { + var cob = data["info"]["formats"][i]; + if (cob["vcodec"] != "none") { + var codec = cob["video_ext"]; + sources.push({ + "type": "video/" + codec, + "src": cob["url"], + "resolution": cob["resolution"], + "format_id": cob["format_id"] + }); + } + } + loadVideo(sources); + } else { + // For any other case just make a row with the given data + var tr = makeRow(data); + document.querySelector("#results").prepend(tr); + } + } + // Remove the loading spinner if it exists + if (data["method"] != "limits") { + document.querySelector("[id='" + data["spinnerid"] + "']").remove(); + } + }); + // When the tab is viewed the notifications are reset to 0 and the notifications are removed from the tab title + document.addEventListener("visibilitychange", () => { + if (!document.hidden) { + document.title = title; + notifications = 0; + } + }); +}); + +// When downloading a stream from the select the Get all download links for video method provides just open the link in a new tab +function dlStream(selectID) { + var index = document.getElementById(selectID).value; + window.open(ginfo["formats"][Number(index)]["url"], "_blank"); +} + +// Method for showing notifications in title +function notify() { + notifications += 1; + document.title = "(" + notifications.toString() + ") " + title; +} + +// Very important, the logic here decides what is returned to the user via the results tables +function makeRow(data) { + // Create a table row element + var tr = document.createElement("tr"); + // Create variable to store generated html for later adding to the tr element + var lhtml = ""; + // Columns of the table + const cols = ["status", "title", "link", "details"]; + // Iterate through each column of the table and construct and add the html for each column + for (col in cols) { + col = cols[col]; + // If the column is in the data or it's status then... + if ((col in data) || (col == "status")) { + // Starts the column html + lhtml += ""; + // If the column is status, display either an error indicator or a success indicator + if (col == "status") { + if (data["error"]) { + lhtml += ""; + } else { + lhtml += ""; + } + } else if (col == "link") { + // If the column is link then wrap the link in the requisite html to be clickable + lhtml += "" + data[col] + ""; + } else if (col == "details") { + // If the column is details first display whatever data has been set for this column + lhtml += "

" + data[col] + "

"; + // If select is in our data keys... + if ("select" in data) { + // Generate specially formatted selects based on whether this is for streams or another method (subtitles) + if (data["method"] == "streams") { + // Used to differentiate selects when there are multiple + var iid = crypto.randomUUID(); + lhtml += "
"; + lhtml += "
" + } else { + var iid = crypto.randomUUID(); + lhtml += ""; + lhtml += ''; + lhtml += ''; + lhtml += '
'; + } + } + } else { + // Anything else just display the data directly + lhtml += "

" + data[col] + "

"; + } + // Close out the column + lhtml += ""; + } else { + // Otherwise write the row with nothing in it so all the other rows display properly + lhtml += "" + } + } + // Add html to tr element and return it for display + tr.innerHTML = lhtml; + return tr; +} + +// Generate a turretcss spinner with a random id (so it can be removed later) and append it to the spinners div (just above the results table) +function genSpinner() { + var spinnerid = crypto.randomUUID(); + var sp = document.createElement("button"); + sp.setAttribute("id", spinnerid); + sp.setAttribute("class", "spinner"); + sp.setAttribute("title", "Loading"); + document.querySelector("#spinners").append(sp); + return spinnerid; +} + +// When an ID3 submission is made we use a submit button, this way we run the code we need and return false, meaning that the form won't actually post +// Constructs an object with all the id3 form values to use with toMP3, which will in turn set these values to be added to the metadata of the MP3 +function id3submit() { + var form = document.getElementById('id3'); + var values = {}; + for (var it in form.elements) { + values[form.elements[it].id] = form.elements[it].value; + } + var url = document.querySelector('#url').value; + toMP3(url, values); + return false; +} + +// Hide/show the ID3 form +function toggleID3Form() { + var form = document.querySelector("#id3"); + var button = document.querySelector("#id3toggle"); + if (form.style.display == "none") { + form.style.display = "block"; + button.textContent = "Hide ID3 form"; + } else { + form.style.display = "none"; + button.textContent = "Show ID3 form"; + } +} + +// Generate spinner and emit toMP3 with the spinnerid (to remove the spinner later) to download mp3 and optionally add metadata to the file +function toMP3(url, id3data = null) { + var spinnerid = genSpinner(); + socket.emit("toMP3", { "url": url, "spinnerid": spinnerid, "id3": id3data }); +} + +// Emit playlist to download a playlist as MP3s and zip them for download +function playlist(url) { + var spinnerid = genSpinner(); + socket.emit("playlist", { "url": url, "spinnerid": spinnerid }); +} + +// Emit either step of subtitles +// Step 1 will give us a list of the subtitle languages +// Step 2 will give us a download of the selected subtitle +function subtitles(url, iid = null) { + var spinnerid = genSpinner(); + var step = Number(document.querySelector("#step").value); + var data = { "url": url, "spinnerid": spinnerid, "step": step } + if (step == 2) { + data["languageCode"] = document.querySelector('[data-iid="' + iid + '"]#langSelect').value; + data["autoSub"] = document.querySelector('[data-iid="' + iid + '"]#autoSubs').checked; + } + socket.emit("subtitles", data); +} + +// Generic getInfo method to get all the info for a given link +function getInfo(url, method) { + var spinnerid = genSpinner() + var data = { "url": url, "spinnerid": spinnerid, "method": method }; + socket.emit("getInfoEvent", data); +} + +// Clip method, ultimately produces a clip of a video or a gif of that clip +function clip(url, directURL = null, gif = null, format_id = null, resolution = null) { + // Get the start and end times for the clip + var timeA = document.querySelector("#timeA").value; + var timeB = document.querySelector("#timeB").value; + // If there is a set resolution, check to make sure that resolution is not larger then the servers limit + // Otherwise display an error message + if (resolution != null) { + if (resolution > glimits[4]["limitvalue"]) { + document.querySelector("#clipperWarning4").style.display = "block"; + } + } else if (gif && ((timeB - timeA) > glimits[3]["limitvalue"])) { + // If this is a gif check if the length of the clip is longer than the servers limit + // If so, display an error message + document.querySelector("#clipperWarning3").style.display = "block"; + } else { + // If the start of the clip is after the end of the clip display an error message + if (Number(timeA) > Number(timeB)) { + document.querySelector("#clipperWarning2").style.display = "block"; + } else { + var spinnerid = genSpinner(); + // Set up the required values for the clip method and then set up additional values if they are not null + var data = { "url": url, "spinnerid": spinnerid, "timeA": timeA, "timeB": timeB }; + if (format_id != null) { + data["format_id"] = format_id + } + if (directURL != null) { + data["directURL"] = directURL + } + if (gif != null) { + data["gif"] = true; + } + socket.emit("clip", data); + } + } +} + +// This loads a video into videojs with multiple sources and generates a list of options in a select for the user to choose a stream +// setSource handles actually setting the stream +function loadVideo(sources) { + // Clear the video container, to be populated later + document.querySelector("#videoContainer").innerHTML = ""; + // Create video element to be used to create videojs player + var video = document.createElement("video"); + // Sources object for videojs initialization + var sdata = { + "sources": sources + }; + // Set global sources for the video + vsources = sources; + // Set some basic attributes for the videojs player + video.setAttribute("id", "clipPlayer"); + video.setAttribute("class", "video-js vjs-default-skin"); + video.setAttribute("controls", ""); + video.setAttribute("width", "720"); + // Add the video to the DOM + document.querySelector("#videoContainer").append(video); + // Clear the source selector + document.querySelector("#srcSelect").innerHTML = ""; + // Populate source selector + for (it in sources) { + document.querySelector("#srcSelect").innerHTML += ""; + } + // If the videojs player object isn't empty then dispose it + if (player != "") { + player.dispose(); + } + // Test if this is doing anything + //document.querySelector("#clipPlayer").innerHTML = ""; + // Initialize the videojs player + player = videojs('clipPlayer', sdata, function onPlayerReady() { + this.on("loadedmetadata", function () { + // If the video is too long display a warning + if (this.duration() < glimits[0]["limitvalue"]) { + document.querySelector("#timeA").max = this.duration(); + document.querySelector("#timeB").max = this.duration(); + document.querySelector("#clipper").style.display = "block"; + } else { + document.querySelector("#clipper").innerHTML = ""; + document.querySelector("#clipperWarning").style.display = "block"; + } + }); + }); +} + +// Set the current source of the videojs player based on the selected source +function setSource(srcIndex) { + player.src(vsources[Number(srcIndex)]); +} + +// Set the current time of the video player when the time sliders change, format the time display to minutes and seconds +function updatePlayerTime(range) { + player.currentTime(range.value); + if (range.id == "timeA") { + document.querySelector("#timeStart").value = (Math.floor(range.value / 60)).toString().padStart(2, "0") + ":" + (range.value % 60).toString().padStart(2, "0"); + } else if (range.id == "timeB") { + document.querySelector("#timeEnd").value = (Math.floor(range.value / 60)).toString().padStart(2, "0") + ":" + (range.value % 60).toString().padStart(2, "0"); + } +} + +// Submit the clip to be cut +function clipSecond() { + document.querySelector("#step").value = 2; + process(); +} + +// Semi generic method tying buttons to emissions +// This should be reworked and the extraSteps stuff should be removed completely +// Recommendation: just a set of if elses for the exact method that was passed +function process(button = null, extraSteps = null) { + // Get method from method select + var method = document.querySelector('#method').value; + // Get the url from the Video/Playlist URL field + var url = document.querySelector('#url').value; + // Check if the url is valid + if (isValidUrl(url)) { + // Hide warnings until needed + document.querySelector('#urlWarning').style.display = 'none'; + document.querySelector("#clipperWarning").style.display = "none"; + // Run toMP3 function + if (method == "toMP3") { + toMP3(url); + } else if (method == "playlist") { + // Run playlist function + playlist(url); + } else if (method == "subtitles") { + // Run subtitles function + // If a button has been passed (in order to identify the select) pass the iid to subtitles for selected subtitles retrieval + if (button == null) { + subtitles(url); + } else { + subtitles(url, iid = button.getAttribute('data-iid')); + } + } else if (method == "streams") { + // Run getInfo function with the streams method + getInfo(url, "streams"); + } else if (method == "clip") { + // Get the step of clipping we're at + var step = Number(document.querySelector("#step").value); + // Add extraSteps if specified + if (extraSteps != null) { + extraSteps = Number(extraSteps); + step += extraSteps; + } + // If we're at step 1 get a list of tracks for the video + if (step == 1) { + getInfo(url, "getClipperTracks"); + } else if (step == 2) { + // If we're at step 2 get some info and clip the video + var format = vsources[Number(document.querySelector("#srcSelect").value)]["format_id"] + var format_id = format["format_id"]; + var resolution = format["width"] + if (document.querySelector("#toGif").checked) { + clip(url, null, true, format_id = format_id, resolution = resolution); + } else { + clip(url, null, null, format_id = format_id, resolution = resolution); + } + } + } + } else { + // If the url isn't valid display a warning + document.querySelector('#urlWarning').style.display = 'block'; + } +} + +// Checks if an entered url is valid +// https://www.freecodecamp.org/news/check-if-a-javascript-string-is-a-url/ +const isValidUrl = urlString => { + try { + return Boolean(new URL(urlString)); + } + catch (e) { + return false; + } +} + +// Get a random integer in an inclusive range +function randomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min) +} + +// Set theme based on the selected theme or custom values +function setTheme(customgradient=false) { + var r = document.querySelector(':root'); + var theme = document.querySelector("#theme").value; + if (theme == "custom") { + theme = { + "bgcolor": document.querySelector("#bgcolor").value, + "textcolor": document.querySelector("#textcolor").value + } + if (customgradient){ + // Set the gradient with the custom values from Top/Bottom color of gradient + theme["gradienttop"] = document.querySelector("#gradienttop").value; + theme["gradientbottom"] = document.querySelector("#gradientbottom").value; + } else { + // Set the gradient to a solid color + theme["gradienttop"] = theme["bgcolor"]; + theme["gradientbottom"] = theme["bgcolor"]; + } + } else { + var themename = theme; + theme = themes[theme]; + if (themename != "sky") { + theme["gradienttop"] = theme["bgcolor"]; + theme["gradientbottom"] = theme["bgcolor"]; + } else { + // Generate two light blues colors in hex format and set the top and bottom of the gradient to these + var top0 = randomInt(50, 99).toString().padStart(2, "0"); + top0 = "#" + top0 + top0 + "ff"; + var bottom0 = randomInt(50, 99).toString().padStart(2, "0"); + bottom0 = "#" + bottom0 + bottom0 + "ff"; + theme["gradienttop"] = top0; + theme["gradientbottom"] = bottom0; + } + } + for (it in theme) { + r.style.setProperty('--' + it, theme[it]); + } + var rs = getComputedStyle(r); +} + + +// Generic toggle method, requires an element with an id and a element that will be used for toggling the visibility of the main element with an id of the main element + _toggle +function toggle(id) { + if (document.querySelector(id).style.display == "none") { + document.querySelector(id).style.display = "block"; + document.querySelector(id + "_toggle").innerHTML = "➖ Hide"; + } else { + document.querySelector(id).style.display = "none"; + document.querySelector(id + "_toggle").innerHTML = "➕ Show"; + } +}