First commit

This commit is contained in:
Agie Ashwood 2023-10-03 22:39:43 -05:00
parent 86b5aac577
commit a529f0ccf3
15 changed files with 1427 additions and 86 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/brotlicffi
/emsdk
/node_modules
/pycryptodome
index.pyodidetest.html
/static/js/pyodide.js
/static/js/custom.pyodide.js
/static/pyodide

View File

@ -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 Once this code is hosted, add the host url to allowedorigins in conf.json in yt-dlp-web and reload the server
- [ ] [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.

BIN
android-chrome-192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
android-chrome-512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

BIN
favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

251
index.html Normal file
View File

@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>yt-dlp web ui</title>
<link rel="stylesheet" href="/node_modules/turretcss/dist/turretcss.min.css">
<link rel="stylesheet" href="/node_modules/video.js/dist/video-js.min.css">
<link rel="stylesheet" href="/static/css/custom.css">
<script src="/node_modules/socket.io-client/dist/socket.io.min.js"></script>
<script src="/node_modules/video.js/dist/video.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
</head>
<body>
<header>
</header>
<main>
<br>
<input type="hidden" id="step" value="1">
<input type="hidden" id="languageCode">
<input type="hidden" id="autoSub" value="false">
<img src="/static/img/logo05background.png">
<br>
Server limits (in seconds)
<table id="limits">
<thead>
<th>Maximum video length</th>
<th>Maximum number of videos in playlist</th>
<th>Maximum length of individual video in playlist</th>
<th>Maximum gif length</th>
<th>Maximum gif resolution</th>
</thead>
<tr>
<td id="maxLength"></td>
<td id="maxPlaylistLength"></td>
<td id="maxLengthPlaylistVideo"></td>
<td id="maxGifLength"></td>
<td id="maxGifResolution"></td>
</tr>
</table>
<div>
<h6>User Guides:</h6>
<p onclick="toggle('#guides')" id="guides_toggle"> Show</p>
<ul id="guides" style="display: none;">
<li>
Convert to MP3s:
<ul>
<li onclick="toggle('#ctm_guide')" id="ctm_guide_toggle"> Show</li>
<li style="display:none;" id="ctm_guide">
First make sure your video is under the maximum length for videos
<br>
Next choose Convert to MP3 from the method dropdown
<br>
Next, determine whether you want to add ID3 metadata to your file, this metadata makes it so that your chosen media player will show the correct title and other information.
<br>
Next, enter the video url in the Video/Playlist URL field
<br>
If you have determined you want to add ID3 data click the Show ID3 form button, fill out your desired field and click Submit
<br>
Otherwise click Process.
<br>
A spinning wheel will show up and when the conversion is done, or if there's an error you will see your results in the table at the bottom of the page. You can right click/hold and release the link to get the option to download your file.
</li>
</ul>
</li>
<li>
Get all download links for video:
<ul>
<li onclick="toggle('#dll_guide')" id="dll_guide_toggle"> Show</li>
<li style="display:none;" id="dll_guide">
First choose Get all download links for video from the method dropdown
<br>
Second, enter the video url in the Video/Playlist URL field and click Process
<br>
A select will appear in the table at the bottom of the page, select your desired video and click Download selected. You will be sent to the link for the file, you can right click/hold and release on the media to get the option to download it
</li>
</ul>
</li>
<li>
Download playlist as MP3s
<ul>
<li onclick="toggle('#dlp_guide')" id="dlp_guide_toggle"> Show</li>
<li style="display:none;" id="dlp_guide">
First make sure your playlist and all videos on the playlist fall within the limits defined for them.
<br>
Next, enter the playlist url in the Video/Playlist URL field and click Process
<br>
Your results will appear when done in the table at the bottom of the page
</li>
</ul>
</li>
<li>
Download subtitles:
<ul>
<li onclick="toggle('#dls_guide')" id="dls_guide_toggle"> Show</li>
<li style="display:none;" id="dls_guide">
First enter the video url in the Video/Playlist URL and click Process
<br>
Next, choose the subtitle language you'd like to download in the details row of the results from the previous step in the table at the bottom of the page, choose whether or not to download auto subtitles, and click Get subtitles
<br>
Your results will appear when done in the table at the bottom of the page
</li>
</ul>
</li>
<li>
Clip video:
<ul>
<li onclick="toggle('#clip_guide')" id="clip_guide_toggle"> Show</li>
<li style="display:none;" id="clip_guide">
First enter the video url in the Video/Playlist URL and click Process
<br>
Next, using the sliders select the beginning and end of your clip. Also choose whether to make the clip into a gif.
<br>
Once you're done click Clip.
<br>
Your results will appear when done in the table at the bottom of the page
</li>
</ul>
</li>
<li>
Themes:
<ul>
<li onclick="toggle('#themes_guide')" id="themes_guide_toggle"> Show</li>
<li style="display:none;" id="themes_guide">
To configure your theme (page colors) click the Show text, the themes configuration tools will display below
<br>
To choose a theme click on the Theme select and pick any theme
<br>
To configure a custom theme choose Custom from the Theme select and choose the background and text colors
<br>
If you want a gradient background configure both Gradient color options
</li>
</ul>
</li>
</ul>
</div>
<p>If you have any questions that are not addressed by the guide please check our gitlab to see if the question has been addressed: <a href="https://gitlab.com/wizdevgirl1/yt-dlp-web-ui/-/issues">https://gitlab.com/wizdevgirl1/yt-dlp-web-ui/-/issues</a></p>
<h6>Themes</h6>
<p onclick="toggle('#themes')" id="themes_toggle"> Show</p>
<div id="themes" style="display: none;">
<label class="select" for="theme">
Theme
<select id="theme" onchange="setTheme()">
<option value="white">White </option>
<option value="yellow">Yellow</option>
<option value="purple">Purple</option>
<option value="grayblue">Gray blue</option>
<option value="sky">Sky</option>
<option value="black">Black</option>
<option value="custom">Custom</option>
</select>
</label>
Custom colors:
<br>
<p class="field">
<label for="bgcolor">Background color</label>
<input type="color" onchange="setTheme()" id="bgcolor" value="#ffffff" />
</p>
<p class="field">
<label for="textcolor">Text color</label>
<input type="color" onchange="setTheme()" id="textcolor" value="#000000" />
</p>
<p class="field">
<label for="gradienttop">Top color of gradient</label>
<input type="color" onchange="setTheme(true)" id="gradienttop" value="none" />
</p>
<p class="field">
<label for="gradientbottom">Bottom color of gradient</label>
<input type="color" onchange="setTheme(true)" id="gradientbottom" value="none"/>
</p>
</div>
<label class="select" for="method">
Method
<select id="method">
<option value="toMP3">Convert to MP3</option>
<option value="streams">Get all download links for video</option>
<option value="playlist">Download playlist as MP3s</option>
<option value="subtitles">Download subtitles</option>
<option value="clip">Clip video</option>
</select>
</label>
<br><br>
<button onclick="toggleID3Form()" id="id3toggle">Show ID3 form</button>
<br>
<form id="id3" style="display:none;">
<br>
</form>
<br>
<label for="url">Video/Playlist URL</label>
<input type="url" id="url" placeholder="https://" />
<br>
<div id="urlWarning" style="display:none;">
<p style="color: red;">Not a valid URL</p>
</div>
<br><br>
<button onclick="process()">Process</button>
<br>
<div id="clipper" style="display:none;">
<div id="videoContainer"></div>
<br>
<select id="srcSelect" onchange="setSource(this.value)">
</select>
<br>
<input type="text" style="width: 10%; display: inline;" id="timeStart" value="00:00"> - <input style="width: 10%; display: inline;" type="text" id="timeEnd" value="00:00">
<br><input id="timeA" type="range" min="0" max="100" oninput="updatePlayerTime(this)" />
<br><input id="timeB" type="range" min="0" max="100" oninput="updatePlayerTime(this)" />
<br><input id="toGif" type="checkbox" name="toGif" />
<label for="toGif">Convert to gif?</label>
<br><button onclick="clipSecond()">Clip</button>
</div>
<br>
<div id="spinners"><br></div>
<br>
<div id="clipperWarning" style="display:hidden;">
<p style="color: red;">Video is too long</p>
</div>
<div id="clipperWarning2" style="display: none;">
<p style="color: red;">End marker is before start marker</p>
</div>
<div id="clipperWarning3" style="display: none;">
<p style="color: red;">Range is too long for gif</p>
</div>
<div id="clipperWarning4" style="display: none;">
<p style="color: red;">Source resolution is too high</p>
</div>
<br>
<p>Results: </p>
<div class="table-responsive">
<table>
<thead>
<tr>
<th scope="col">Status</th>
<th scope="col">Title</th>
<th scope="col">Link</th>
<th scope="col">Details</th>
</tr>
</thead>
<tbody id="results">
</tbody>
</table>
</div>
</main>
<footer>
</footer>
<script src="/static/js/custom.js"></script>
</body>
</html>

569
package-lock.json generated Normal file
View File

@ -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"
}
}
}
}

7
package.json Normal file
View File

@ -0,0 +1,7 @@
{
"dependencies": {
"socket.io-client": "^4.7.2",
"turretcss": "^5.2.2",
"video.js": "^8.5.2"
}
}

52
static/css/custom.css Normal file
View File

@ -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);
}

BIN
static/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

533
static/js/custom.js Normal file
View File

@ -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 += '<label for="' + eid3fields[it] + '">' + eid3fields[it] + '</label>';
document.querySelector("#id3").innerHTML += '<input type="text" id="' + eid3fields[it] + '" />';
}
// Adds submit button to easy id3 configuration form
document.querySelector("#id3").innerHTML += '<br><button onclick="return id3submit()">Submit</button><br>';
// 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 += "<td>";
// If the column is status, display either an error indicator or a success indicator
if (col == "status") {
if (data["error"]) {
lhtml += "<button class='button error'>Error</button>";
} else {
lhtml += "<button class='button success'>Success</button></td>";
}
} else if (col == "link") {
// If the column is link then wrap the link in the requisite html to be clickable
lhtml += "<a href='" + data[col] + "'>" + data[col] + "</a>";
} else if (col == "details") {
// If the column is details first display whatever data has been set for this column
lhtml += "<p>" + data[col] + "</p>";
// 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 += "<br><select id='" + iid + "'>";
for (it in ginfo["formats"]) {
lhtml += "<option value='" + it.toString() + "'>" + ginfo["formats"][it]["format"] + " / " + ginfo["formats"][it]["ext"] + " / audio: " + ginfo["formats"][it]["audio_ext"] + "</option>";
}
lhtml += "</select>";
lhtml += "<br><button onclick='dlStream(`" + iid + "`)'>Download selected</button>"
} else {
var iid = crypto.randomUUID();
lhtml += "<select data-iid='" + iid + "' id='langSelect'>";
for (it in data["select"]) {
lhtml += "<option value='" + data["select"][it] + "'>" + data["select"][it] + "</option>";
}
lhtml += "</select>";
lhtml += '<input id="autoSubs" data-iid="' + iid + '" type="checkbox" name="checkbox" />';
lhtml += '<label for="autoSubs">Download automatic subs?</label>';
lhtml += '<br><button data-iid="' + iid + '" onclick="process(this)">Get subtitles</button>';
}
}
} else {
// Anything else just display the data directly
lhtml += "<p>" + data[col] + "</p>";
}
// Close out the column
lhtml += "</td>";
} else {
// Otherwise write the row with nothing in it so all the other rows display properly
lhtml += "<td></td>"
}
}
// 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 += "<option value='" + it + "'>" + sources[it]["resolution"] + " " + sources[it]["type"] + "</option>";
}
// 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";
}
}