Refresh frontend with cohesive modern UI styling
This commit is contained in:
parent
5d96a581b9
commit
44c42b3773
18 changed files with 290 additions and 185 deletions
|
|
@ -13,9 +13,9 @@ body:
|
|||
options:
|
||||
- label: I have searched existing discussions and issues for similar configuration problems
|
||||
required: true
|
||||
- label: I have read the [configuration section](https://github.com/alexta69/metube#%EF%B8%8F-configuration-via-environment-variables) in the README
|
||||
- label: I have read the [configuration section](https://github.com/TonyBlur/mytube#%EF%B8%8F-configuration-via-environment-variables) in the README
|
||||
required: true
|
||||
- label: I have checked the [Wiki](https://github.com/alexta69/metube/wiki) for configuration examples
|
||||
- label: I have checked the [Wiki](https://github.com/TonyBlur/mytube/wiki) for configuration examples
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
|
|
@ -24,9 +24,9 @@ body:
|
|||
## Configuration Resources
|
||||
|
||||
Before asking for help, please check these resources:
|
||||
- **[Configuration Guide](https://github.com/alexta69/metube#%EF%B8%8F-configuration-via-environment-variables)** - All available environment variables
|
||||
- **[YTDL_OPTIONS Cookbook](https://github.com/alexta69/metube/wiki/YTDL_OPTIONS-Cookbook)** - Common yt-dlp configurations
|
||||
- **[OUTPUT_TEMPLATE Cookbook](https://github.com/alexta69/metube/wiki/OUTPUT_TEMPLATE-Cookbook)** - Filename template examples
|
||||
- **[Configuration Guide](https://github.com/TonyBlur/mytube#%EF%B8%8F-configuration-via-environment-variables)** - All available environment variables
|
||||
- **[YTDL_OPTIONS Cookbook](https://github.com/TonyBlur/mytube/wiki/YTDL_OPTIONS-Cookbook)** - Common yt-dlp configurations
|
||||
- **[OUTPUT_TEMPLATE Cookbook](https://github.com/TonyBlur/mytube/wiki/OUTPUT_TEMPLATE-Cookbook)** - Filename template examples
|
||||
|
||||
- type: textarea
|
||||
id: configuration-goal
|
||||
|
|
@ -53,7 +53,7 @@ body:
|
|||
|
||||
Environment Details:
|
||||
- MeTube version: [e.g., latest, specific version]
|
||||
- Docker image: [e.g., ghcr.io/alexta69/metube:latest]
|
||||
- Docker image: [e.g., ghcr.io/tonyblur/mytube:latest]
|
||||
- Operating System: [e.g., Ubuntu 20.04, Windows 10, macOS 12]
|
||||
|
||||
Error Messages or Issues:
|
||||
|
|
|
|||
2
.github/DISCUSSION_TEMPLATE/general.yml
vendored
2
.github/DISCUSSION_TEMPLATE/general.yml
vendored
|
|
@ -13,7 +13,7 @@ body:
|
|||
options:
|
||||
- label: I have searched existing discussions to ensure this topic hasn't been discussed before
|
||||
required: true
|
||||
- label: I have read the [README](https://github.com/alexta69/metube#readme) and relevant sections
|
||||
- label: I have read the [README](https://github.com/TonyBlur/mytube#readme) and relevant sections
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
|
|
|
|||
10
.github/DISCUSSION_TEMPLATE/q-and-a.yml
vendored
10
.github/DISCUSSION_TEMPLATE/q-and-a.yml
vendored
|
|
@ -13,9 +13,9 @@ body:
|
|||
options:
|
||||
- label: I have searched existing discussions and issues to see if my question has been answered before
|
||||
required: true
|
||||
- label: I have read the [README](https://github.com/alexta69/metube#readme) and relevant sections
|
||||
- label: I have read the [README](https://github.com/TonyBlur/mytube#readme) and relevant sections
|
||||
required: true
|
||||
- label: I have checked the [Wiki](https://github.com/alexta69/metube/wiki) for configuration examples
|
||||
- label: I have checked the [Wiki](https://github.com/TonyBlur/mytube/wiki) for configuration examples
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
|
|
@ -24,9 +24,9 @@ body:
|
|||
## Quick Resources
|
||||
|
||||
Before asking your question, please check these resources:
|
||||
- **[README](https://github.com/alexta69/metube#readme)** - Complete setup and configuration guide
|
||||
- **[YTDL_OPTIONS Cookbook](https://github.com/alexta69/metube/wiki/YTDL_OPTIONS-Cookbook)** - Common yt-dlp configurations
|
||||
- **[OUTPUT_TEMPLATE Cookbook](https://github.com/alexta69/metube/wiki/OUTPUT_TEMPLATE-Cookbook)** - Filename template examples
|
||||
- **[README](https://github.com/TonyBlur/mytube#readme)** - Complete setup and configuration guide
|
||||
- **[YTDL_OPTIONS Cookbook](https://github.com/TonyBlur/mytube/wiki/YTDL_OPTIONS-Cookbook)** - Common yt-dlp configurations
|
||||
- **[OUTPUT_TEMPLATE Cookbook](https://github.com/TonyBlur/mytube/wiki/OUTPUT_TEMPLATE-Cookbook)** - Filename template examples
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
|
|
|
|||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -13,7 +13,7 @@ body:
|
|||
options:
|
||||
- label: I have searched existing issues and discussions to ensure this bug hasn't been reported before
|
||||
required: true
|
||||
- label: I have read the [troubleshooting section](https://github.com/alexta69/metube#-troubleshooting-and-submitting-issues) in the README
|
||||
- label: I have read the [troubleshooting section](https://github.com/TonyBlur/mytube#-troubleshooting-and-submitting-issues) in the README
|
||||
required: true
|
||||
- label: I have tested this issue with yt-dlp directly (not just through MeTube UI) as described in the README
|
||||
required: true
|
||||
|
|
@ -62,7 +62,7 @@ body:
|
|||
placeholder: |
|
||||
Environment:
|
||||
- MeTube version: [e.g., latest, specific version]
|
||||
- Docker image: [e.g., ghcr.io/alexta69/metube:latest]
|
||||
- Docker image: [e.g., ghcr.io/tonyblur/mytube:latest]
|
||||
- Operating System: [e.g., Ubuntu 20.04, Windows 10, macOS 12]
|
||||
|
||||
Configuration:
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,7 +1,7 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: MeTube Community Discussions
|
||||
url: https://github.com/alexta69/metube/discussions
|
||||
url: https://github.com/TonyBlur/mytube/discussions
|
||||
about: Ask questions and discuss MeTube with the community
|
||||
- name: yt-dlp Issues
|
||||
url: https://github.com/yt-dlp/yt-dlp/issues
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
|
@ -13,7 +13,7 @@ body:
|
|||
options:
|
||||
- label: I have searched existing issues and discussions to ensure this feature hasn't been requested before
|
||||
required: true
|
||||
- label: I have read the [feature request guidelines](https://github.com/alexta69/metube#-submitting-feature-requests) in the README
|
||||
- label: I have read the [feature request guidelines](https://github.com/TonyBlur/mytube#-submitting-feature-requests) in the README
|
||||
required: true
|
||||
- label: I understand that MeTube development relies on community contributions and the maintainer is not likely to implement this feature
|
||||
required: true
|
||||
|
|
|
|||
12
.github/ISSUE_TEMPLATE/question.yml
vendored
12
.github/ISSUE_TEMPLATE/question.yml
vendored
|
|
@ -13,11 +13,11 @@ body:
|
|||
options:
|
||||
- label: I have searched existing issues and discussions to see if my question has been answered before
|
||||
required: true
|
||||
- label: I have read the [README](https://github.com/alexta69/metube#readme) and relevant sections
|
||||
- label: I have read the [README](https://github.com/TonyBlur/mytube#readme) and relevant sections
|
||||
required: true
|
||||
- label: I have checked the [Wiki](https://github.com/alexta69/metube/wiki) for configuration examples
|
||||
- label: I have checked the [Wiki](https://github.com/TonyBlur/mytube/wiki) for configuration examples
|
||||
required: true
|
||||
- label: I have read the [troubleshooting section](https://github.com/alexta69/metube#-troubleshooting-and-submitting-issues) if this is a technical issue
|
||||
- label: I have read the [troubleshooting section](https://github.com/TonyBlur/mytube#-troubleshooting-and-submitting-issues) if this is a technical issue
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
|
|
@ -26,9 +26,9 @@ body:
|
|||
## Quick Resources
|
||||
|
||||
Before asking your question, please check these resources:
|
||||
- **[README](https://github.com/alexta69/metube#readme)** - Complete setup and configuration guide
|
||||
- **[YTDL_OPTIONS Cookbook](https://github.com/alexta69/metube/wiki/YTDL_OPTIONS-Cookbook)** - Common yt-dlp configurations
|
||||
- **[OUTPUT_TEMPLATE Cookbook](https://github.com/alexta69/metube/wiki/OUTPUT_TEMPLATE-Cookbook)** - Filename template examples
|
||||
- **[README](https://github.com/TonyBlur/mytube#readme)** - Complete setup and configuration guide
|
||||
- **[YTDL_OPTIONS Cookbook](https://github.com/TonyBlur/mytube/wiki/YTDL_OPTIONS-Cookbook)** - Common yt-dlp configurations
|
||||
- **[OUTPUT_TEMPLATE Cookbook](https://github.com/TonyBlur/mytube/wiki/OUTPUT_TEMPLATE-Cookbook)** - Filename template examples
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
|
|
|
|||
47
.github/workflows/sync-upstream.yml
vendored
Normal file
47
.github/workflows/sync-upstream.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: Upstream Sync
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync_latest_from_upstream:
|
||||
name: Sync latest commits from upstream repo
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.repository.fork }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Clean issue notice
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: close-issues
|
||||
labels: "🚨 Sync Fail"
|
||||
|
||||
- name: Sync upstream changes
|
||||
id: sync
|
||||
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
|
||||
with:
|
||||
upstream_sync_repo: alexta69/metube
|
||||
upstream_sync_branch: master
|
||||
target_sync_branch: master
|
||||
target_repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
test_mode: false
|
||||
|
||||
- name: Sync check
|
||||
if: failure()
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: create-issue
|
||||
title: "🚨 同步失败 | Sync Fail"
|
||||
labels: "🚨 Sync Fail"
|
||||
body: |
|
||||
Due to conflicts or workflow changes in the [alexta69/metube](https://github.com/alexta69/metube) upstream repository, the automatic sync has failed. Please manually sync your fork and resolve any conflicts.
|
||||
|
||||
由于上游仓库 [alexta69/metube](https://github.com/alexta69/metube) 存在冲突或 workflow 变更,自动同步失败。请手动 Sync Fork 并解决冲突。
|
||||
2
.github/workflows/update-yt-dlp.yml
vendored
2
.github/workflows/update-yt-dlp.yml
vendored
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
if [ -n "$LATEST_VERSION" ] && [ "$LATEST_VERSION" != "$CURRENT_VERSION" ]; then
|
||||
echo "Updating yt-dlp from $CURRENT_VERSION to $LATEST_VERSION"
|
||||
uv lock --upgrade-package "yt-dlp[default,curl-cffi]"
|
||||
git config --global user.email "updater@metube"
|
||||
git config --global user.email "updater@mytube"
|
||||
git config --global user.name "AutoUpdater"
|
||||
git add uv.lock
|
||||
git commit -m "upgrade yt-dlp from $CURRENT_VERSION to $LATEST_VERSION"
|
||||
|
|
|
|||
113
README.md
113
README.md
|
|
@ -1,30 +1,33 @@
|
|||
# MeTube
|
||||
## Fork note
|
||||
This project is forked from [alexta69/metube](https://github.com/alexta69/metube).
|
||||
|
||||

|
||||

|
||||
# MyTube
|
||||
|
||||
MeTube is a self-hosted web UI for `yt-dlp`, for downloading media from YouTube and [dozens of other sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md).
|
||||

|
||||

|
||||
|
||||
MyTube is a self-hosted web UI for `yt-dlp`, for downloading media from YouTube and [dozens of other sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md).
|
||||
|
||||
Key capabilities:
|
||||
* Download videos, audio, captions, and thumbnails from a browser UI.
|
||||
* Download playlists and channels, with configurable output and download options.
|
||||
* Subscribe to channels and playlists, periodically check for new items, and queue new uploads automatically.
|
||||
|
||||

|
||||

|
||||
|
||||
## 🐳 Run using Docker
|
||||
|
||||
```bash
|
||||
docker run -d -p 8081:8081 -v /path/to/downloads:/downloads ghcr.io/alexta69/metube
|
||||
docker run -d -p 8081:8081 -v /path/to/downloads:/downloads ghcr.io/tonyblur/mytube
|
||||
```
|
||||
|
||||
## 🐳 Run using docker-compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
metube:
|
||||
image: ghcr.io/alexta69/metube
|
||||
container_name: metube
|
||||
mytube:
|
||||
image: ghcr.io/tonyblur/mytube
|
||||
container_name: mytube
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8081:8081"
|
||||
|
|
@ -54,11 +57,11 @@ Certain values can be set via environment variables, using the `-e` parameter on
|
|||
* __CREATE_CUSTOM_DIRS__: Whether to support automatically creating directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__) if they do not exist. When enabled, the download directory selector supports free-text input, and the specified directory will be created recursively. Defaults to `true`.
|
||||
* __CUSTOM_DIRS_EXCLUDE_REGEX__: Regular expression to exclude some custom directories from the dropdown. Empty regex disables exclusion. Defaults to `(^|/)[.@].*$`, which means directories starting with `.` or `@`.
|
||||
* __DOWNLOAD_DIRS_INDEXABLE__: If `true`, the download directories (__DOWNLOAD_DIR__ and __AUDIO_DOWNLOAD_DIR__) are indexable on the web server. Defaults to `false`.
|
||||
* __STATE_DIR__: Path to where MeTube will store its persistent state files (`queue.json`, `pending.json`, `completed.json`, `subscriptions.json`). Defaults to `/downloads/.metube` in the Docker image, and `.` otherwise.
|
||||
* __STATE_DIR__: Path to where mytube will store its persistent state files (`queue.json`, `pending.json`, `completed.json`, `subscriptions.json`). Defaults to `/downloads/.mytube` in the Docker image, and `.` otherwise.
|
||||
* __TEMP_DIR__: Path where intermediary download files will be saved. Defaults to `/downloads` in the Docker image, and `.` otherwise.
|
||||
* Set this to an SSD or RAM filesystem (e.g., `tmpfs`) for better performance.
|
||||
* __Note__: Using a RAM filesystem may prevent downloads from being resumed.
|
||||
* __CHOWN_DIRS__: If `false`, ownership of `DOWNLOAD_DIR`, `STATE_DIR`, and `TEMP_DIR` (and their contents) will not be set on container start. Ensure user under which MeTube runs has necessary access to these directories already. Defaults to `true`.
|
||||
* __CHOWN_DIRS__: If `false`, ownership of `DOWNLOAD_DIR`, `STATE_DIR`, and `TEMP_DIR` (and their contents) will not be set on container start. Ensure user under which mytube runs has necessary access to these directories already. Defaults to `true`.
|
||||
|
||||
### 📝 File Naming & yt-dlp
|
||||
|
||||
|
|
@ -77,26 +80,26 @@ Certain values can be set via environment variables, using the `-e` parameter on
|
|||
* __HOST__: The host address the web server will bind to. Defaults to `0.0.0.0` (all interfaces).
|
||||
* __PORT__: The port number the web server will listen on. Defaults to `8081`.
|
||||
* __URL_PREFIX__: Base path for the web server (for use when hosting behind a reverse proxy). Defaults to `/`.
|
||||
* __PUBLIC_HOST_URL__: Base URL for the download links shown in the UI for completed files. By default, MeTube serves them under its own URL. If your download directory is accessible on another URL and you want the download links to be based there, use this variable to set it.
|
||||
* __PUBLIC_HOST_URL__: Base URL for the download links shown in the UI for completed files. By default, mytube serves them under its own URL. If your download directory is accessible on another URL and you want the download links to be based there, use this variable to set it.
|
||||
* __PUBLIC_HOST_AUDIO_URL__: Same as PUBLIC_HOST_URL but for audio downloads.
|
||||
* __HTTPS__: Use `https` instead of `http` (__CERTFILE__ and __KEYFILE__ required). Defaults to `false`.
|
||||
* __CERTFILE__: HTTPS certificate file path.
|
||||
* __KEYFILE__: HTTPS key file path.
|
||||
* __CORS_ALLOWED_ORIGINS__: Comma-separated list of origins permitted to make cross-origin requests to the MeTube API. When unset or empty, all cross-origin requests are denied. Set to `*` to allow all origins. This must be configured for [browser extensions](#-browser-extensions), [bookmarklets](#-bookmarklet), and any other browser-based tools that contact MeTube from a different origin. For browser extensions use `*` (see below); for bookmarklets you can list specific sites, e.g. `https://www.youtube.com,https://www.vimeo.com`.
|
||||
* __CORS_ALLOWED_ORIGINS__: Comma-separated list of origins permitted to make cross-origin requests to the mytube API. When unset or empty, all cross-origin requests are denied. Set to `*` to allow all origins. This must be configured for [browser extensions](#-browser-extensions), [bookmarklets](#-bookmarklet), and any other browser-based tools that contact mytube from a different origin. For browser extensions use `*` (see below); for bookmarklets you can list specific sites, e.g. `https://www.youtube.com,https://www.vimeo.com`.
|
||||
* __ROBOTS_TXT__: A path to a `robots.txt` file mounted in the container.
|
||||
|
||||
### 🏠 Basic Setup
|
||||
|
||||
* __PUID__: User under which MeTube will run. Defaults to `1000` (legacy `UID` also supported).
|
||||
* __PGID__: Group under which MeTube will run. Defaults to `1000` (legacy `GID` also supported).
|
||||
* __UMASK__: Umask value used by MeTube. Defaults to `022`.
|
||||
* __PUID__: User under which mytube will run. Defaults to `1000` (legacy `UID` also supported).
|
||||
* __PGID__: Group under which mytube will run. Defaults to `1000` (legacy `GID` also supported).
|
||||
* __UMASK__: Umask value used by mytube. Defaults to `022`.
|
||||
* __DEFAULT_THEME__: Default theme to use for the UI, can be set to `light`, `dark`, or `auto`. Defaults to `auto`.
|
||||
* __LOGLEVEL__: Log level, can be set to `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`, or `NONE`. Defaults to `INFO`.
|
||||
* __ENABLE_ACCESSLOG__: Whether to enable access log. Defaults to `false`.
|
||||
|
||||
## 🎛️ Configuring yt-dlp options
|
||||
|
||||
MeTube lets you customize how [yt-dlp](https://github.com/yt-dlp/yt-dlp) behaves at three levels, from broadest to most specific:
|
||||
MyTube lets you customize how [yt-dlp](https://github.com/yt-dlp/yt-dlp) behaves at three levels, from broadest to most specific:
|
||||
|
||||
1. **Global options** — apply to every download by default.
|
||||
2. **Presets** — named bundles of options that users can pick per download from the UI.
|
||||
|
|
@ -108,7 +111,7 @@ In JSON presets and overrides, setting an option to **`null`** clears that optio
|
|||
|
||||
### Option format
|
||||
|
||||
yt-dlp options in MeTube are expressed as JSON objects. The keys are yt-dlp API option names, which roughly correspond to command-line flags with dashes replaced by underscores. For example, the command-line flag `--write-subs` becomes `"writesubtitles": true` in JSON.
|
||||
yt-dlp options in MyTube are expressed as JSON objects. The keys are yt-dlp API option names, which roughly correspond to command-line flags with dashes replaced by underscores. For example, the command-line flag `--write-subs` becomes `"writesubtitles": true` in JSON.
|
||||
|
||||
> **Tip:** Some command-line flags don't have a direct single-key equivalent — for instance, `--embed-thumbnail` and `--recode-video` must be expressed via `"postprocessors"`. A full list of available API options can be found [in the yt-dlp source](https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/YoutubeDL.py#L224), and [this conversion script](https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/cli_to_api.py) can help translate command-line flags to their API equivalents.
|
||||
|
||||
|
|
@ -187,7 +190,7 @@ When multiple presets are selected for a download, they are applied in order. If
|
|||
|
||||
### Per-download overrides
|
||||
|
||||
For one-off tweaks, MeTube can expose a free-text JSON field in the UI ("Custom yt-dlp Options") where users type yt-dlp options that apply only to that single download. This is disabled by default:
|
||||
For one-off tweaks, MyTube can expose a free-text JSON field in the UI ("Custom yt-dlp Options") where users type yt-dlp options that apply only to that single download. This is disabled by default:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
|
|
@ -206,79 +209,79 @@ When a download starts, the final set of yt-dlp options is built in this order:
|
|||
2. Apply each selected **preset** in order (later presets overwrite earlier ones for conflicting keys).
|
||||
3. Apply any **per-download overrides** on top (overwrite everything else for conflicting keys).
|
||||
|
||||
MeTube always forces its own flat-extract behaviour during the initial metadata fetch (`extract_flat`, `noplaylist`, etc.); presets cannot override those keys for that phase.
|
||||
MyTube always forces its own flat-extract behaviour during the initial metadata fetch (`extract_flat`, `noplaylist`, etc.); presets cannot override those keys for that phase.
|
||||
|
||||
**Example:** Suppose your global options set `"writesubtitles": false`, but you select a preset that sets `"writesubtitles": true`. Subtitles will be written for that download because the preset overrides the global setting. If you additionally enter `{"writesubtitles": false}` in the per-download overrides field, that value wins and subtitles will not be written.
|
||||
|
||||
### Configuration cookbooks
|
||||
|
||||
The project's Wiki contains examples of useful configurations contributed by users of MeTube:
|
||||
* [YTDL_OPTIONS Cookbook](https://github.com/alexta69/metube/wiki/YTDL_OPTIONS-Cookbook)
|
||||
* [OUTPUT_TEMPLATE Cookbook](https://github.com/alexta69/metube/wiki/OUTPUT_TEMPLATE-Cookbook)
|
||||
The project's Wiki contains examples of useful configurations contributed by users of MyTube:
|
||||
* [YTDL_OPTIONS Cookbook](https://github.com/TonyBlur/mytube/wiki/YTDL_OPTIONS-Cookbook)
|
||||
* [OUTPUT_TEMPLATE Cookbook](https://github.com/TonyBlur/mytube/wiki/OUTPUT_TEMPLATE-Cookbook)
|
||||
|
||||
## 🍪 Using browser cookies
|
||||
|
||||
In case you need to use your browser's cookies with MeTube, for example to download restricted or private videos:
|
||||
In case you need to use your browser's cookies with MyTube, for example to download restricted or private videos:
|
||||
|
||||
* Install in your browser an extension to extract cookies:
|
||||
* [Firefox](https://addons.mozilla.org/en-US/firefox/addon/export-cookies-txt/)
|
||||
* [Chrome](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc)
|
||||
* Extract the cookies you need with the extension and save/export them as `cookies.txt`.
|
||||
* In MeTube, open **Advanced Options** and use the **Upload Cookies** button to upload the file.
|
||||
* In MyTube, open **Advanced Options** and use the **Upload Cookies** button to upload the file.
|
||||
* After upload, the cookie indicator should show as active.
|
||||
* Use **Delete Cookies** in the same section to remove uploaded cookies.
|
||||
|
||||
## 🔌 Browser extensions
|
||||
|
||||
Browser extensions allow right-clicking videos and sending them directly to MeTube. If you're on an HTTPS page, your MeTube instance must be behind an HTTPS reverse proxy (see below) for extensions to work.
|
||||
Browser extensions allow right-clicking videos and sending them directly to MyTube. If you're on an HTTPS page, your MyTube instance must be behind an HTTPS reverse proxy (see below) for extensions to work.
|
||||
|
||||
Since browser extensions make requests from their own origin (`chrome-extension://...` or `moz-extension://...`), you must set `CORS_ALLOWED_ORIGINS=*` for them to work.
|
||||
|
||||
__Chrome:__ contributed by [Rpsl](https://github.com/rpsl). You can install it from [Google Chrome Webstore](https://chrome.google.com/webstore/detail/metube-downloader/fbmkmdnlhacefjljljlbhkodfmfkijdh) or use developer mode and install [from sources](https://github.com/Rpsl/metube-browser-extension).
|
||||
__Chrome:__ contributed by [Rpsl](https://github.com/rpsl). You can install it from [Google Chrome Webstore](https://chrome.google.com/webstore/detail/mytube-downloader/fbmkmdnlhacefjljljlbhkodfmfkijdh) or use developer mode and install [from sources](https://github.com/Rpsl/mytube-browser-extension).
|
||||
|
||||
__Firefox:__ contributed by [nanocortex](https://github.com/nanocortex). You can install it from [Firefox Addons](https://addons.mozilla.org/en-US/firefox/addon/metube-downloader) or get sources from [here](https://github.com/nanocortex/metube-firefox-addon).
|
||||
__Firefox:__ contributed by [nanocortex](https://github.com/nanocortex). You can install it from [Firefox Addons](https://addons.mozilla.org/en-US/firefox/addon/mytube-downloader) or get sources from [here](https://github.com/nanocortex/mytube-firefox-addon).
|
||||
|
||||
## 📱 iOS Shortcut
|
||||
|
||||
[rithask](https://github.com/rithask) created an iOS shortcut to send URLs to MeTube from Safari. Enter the MeTube instance address when prompted which will be saved for later use. You can run the shortcut from Safari’s share menu. The shortcut can be downloaded from [this iCloud link](https://www.icloud.com/shortcuts/66627a9f334c467baabdb2769763a1a6).
|
||||
[rithask](https://github.com/rithask) created an iOS shortcut to send URLs to MyTube from Safari. Enter the mytube instance address when prompted which will be saved for later use. You can run the shortcut from Safari’s share menu. The shortcut can be downloaded from [this iCloud link](https://www.icloud.com/shortcuts/66627a9f334c467baabdb2769763a1a6).
|
||||
|
||||
## 🔖 Bookmarklet
|
||||
|
||||
[kushfest](https://github.com/kushfest) has created a Chrome bookmarklet for sending the currently open webpage to MeTube. Please note that if you're on an HTTPS page, your MeTube instance must be configured with `HTTPS` as `true` in the environment, or be behind an HTTPS reverse proxy (see below) for the bookmarklet to work.
|
||||
[kushfest](https://github.com/kushfest) has created a Chrome bookmarklet for sending the currently open webpage to mytube. Please note that if you're on an HTTPS page, your MyTube instance must be configured with `HTTPS` as `true` in the environment, or be behind an HTTPS reverse proxy (see below) for the bookmarklet to work.
|
||||
|
||||
Since bookmarklets run in the context of the current page (e.g. youtube.com), the requests they make to MeTube are cross-origin. You must add the origins of sites where you use the bookmarklet to the __CORS_ALLOWED_ORIGINS__ environment variable, otherwise the browser will block the requests. For example, to use the bookmarklet on YouTube and Vimeo: `CORS_ALLOWED_ORIGINS=https://www.youtube.com,https://www.vimeo.com`.
|
||||
Since bookmarklets run in the context of the current page (e.g. youtube.com), the requests they make to mytube are cross-origin. You must add the origins of sites where you use the bookmarklet to the __CORS_ALLOWED_ORIGINS__ environment variable, otherwise the browser will block the requests. For example, to use the bookmarklet on YouTube and Vimeo: `CORS_ALLOWED_ORIGINS=https://www.youtube.com,https://www.vimeo.com`.
|
||||
|
||||
GitHub doesn't allow embedding JavaScript as a link, so the bookmarklet has to be created manually by copying the following code to a new bookmark you create on your bookmarks bar. Change the hostname in the URL below to point to your MeTube instance.
|
||||
GitHub doesn't allow embedding JavaScript as a link, so the bookmarklet has to be created manually by copying the following code to a new bookmark you create on your bookmarks bar. Change the hostname in the URL below to point to your MyTube instance.
|
||||
|
||||
```javascript
|
||||
javascript:!function(){xhr=new XMLHttpRequest();xhr.open("POST","https://metube.domain.com/add");xhr.withCredentials=true;xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function(){if(xhr.status==200){alert("Sent to metube!")}else{alert("Send to metube failed. Check the javascript console for clues.")}}}();
|
||||
javascript:!function(){xhr=new XMLHttpRequest();xhr.open("POST","https://mytube.domain.com/add");xhr.withCredentials=true;xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function(){if(xhr.status==200){alert("Sent to mytube!")}else{alert("Send to mytube failed. Check the javascript console for clues.")}}}();
|
||||
```
|
||||
|
||||
[shoonya75](https://github.com/shoonya75) has contributed a Firefox version:
|
||||
|
||||
```javascript
|
||||
javascript:(function(){xhr=new XMLHttpRequest();xhr.open("POST","https://metube.domain.com/add");xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function(){if(xhr.status==200){alert("Sent to metube!")}else{alert("Send to metube failed. Check the javascript console for clues.")}}})();
|
||||
javascript:(function(){xhr=new XMLHttpRequest();xhr.open("POST","https://mytube.domain.com/add");xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function(){if(xhr.status==200){alert("Sent to mytube!")}else{alert("Send to mytube failed. Check the javascript console for clues.")}}})();
|
||||
```
|
||||
|
||||
The above bookmarklets use `alert()` for notifications. This variant shows a toast instead (Chrome — for Firefox, replace the `!function(){...}()` wrapper with `(function(){...})()`):
|
||||
|
||||
```javascript
|
||||
javascript:!function(){function notify(msg) {var sc = document.scrollingElement.scrollTop; var text = document.createElement('span');text.innerHTML=msg;var ts = text.style;ts.all = 'revert';ts.color = '#000';ts.fontFamily = 'Verdana, sans-serif';ts.fontSize = '15px';ts.backgroundColor = 'white';ts.padding = '15px';ts.border = '1px solid gainsboro';ts.boxShadow = '3px 3px 10px';ts.zIndex = '100';document.body.appendChild(text);ts.position = 'absolute'; ts.top = 50 + sc + 'px'; ts.left = (window.innerWidth / 2)-(text.offsetWidth / 2) + 'px'; setTimeout(function () { text.style.visibility = "hidden"; }, 1500);}xhr=new XMLHttpRequest();xhr.open("POST","https://metube.domain.com/add");xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function() { if(xhr.status==200){notify("Sent to metube!")}else {notify("Send to metube failed. Check the javascript console for clues.")}}}();
|
||||
javascript:!function(){function notify(msg) {var sc = document.scrollingElement.scrollTop; var text = document.createElement('span');text.innerHTML=msg;var ts = text.style;ts.all = 'revert';ts.color = '#000';ts.fontFamily = 'Verdana, sans-serif';ts.fontSize = '15px';ts.backgroundColor = 'white';ts.padding = '15px';ts.border = '1px solid gainsboro';ts.boxShadow = '3px 3px 10px';ts.zIndex = '100';document.body.appendChild(text);ts.position = 'absolute'; ts.top = 50 + sc + 'px'; ts.left = (window.innerWidth / 2)-(text.offsetWidth / 2) + 'px'; setTimeout(function () { text.style.visibility = "hidden"; }, 1500);}xhr=new XMLHttpRequest();xhr.open("POST","https://mytube.domain.com/add");xhr.send(JSON.stringify({"url":document.location.href,"quality":"best"}));xhr.onload=function() { if(xhr.status==200){notify("Sent to mytube!")}else {notify("Send to mytube failed. Check the javascript console for clues.")}}}();
|
||||
```
|
||||
|
||||
## ⚡ Raycast extension
|
||||
|
||||
[dotvhs](https://github.com/dotvhs) has created an [extension for Raycast](https://www.raycast.com/dot/metube) for adding videos to MeTube directly from Raycast.
|
||||
[dotvhs](https://github.com/dotvhs) has created an [extension for Raycast](https://www.raycast.com/dot/mytube) for adding videos to MyTube directly from Raycast.
|
||||
|
||||
## 🔒 HTTPS support, and running behind a reverse proxy
|
||||
|
||||
It's possible to configure MeTube to listen in HTTPS mode. `docker-compose` example:
|
||||
It's possible to configure MyTube to listen in HTTPS mode. `docker-compose` example:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
metube:
|
||||
image: ghcr.io/alexta69/metube
|
||||
container_name: metube
|
||||
mytube:
|
||||
image: ghcr.io/tonyblur/mytube
|
||||
container_name: mytube
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8081:8081"
|
||||
|
|
@ -292,15 +295,15 @@ services:
|
|||
- KEYFILE=/ssl/key.pem
|
||||
```
|
||||
|
||||
MeTube can also run behind a reverse proxy for HTTPS termination or authentication. When serving under a subdirectory, set `URL_PREFIX` accordingly.
|
||||
MyTube can also run behind a reverse proxy for HTTPS termination or authentication. When serving under a subdirectory, set `URL_PREFIX` accordingly.
|
||||
|
||||
The [linuxserver/swag](https://docs.linuxserver.io/general/swag) image includes ready-made snippets for MeTube in [subfolder](https://github.com/linuxserver/reverse-proxy-confs/blob/master/metube.subfolder.conf.sample) and [subdomain](https://github.com/linuxserver/reverse-proxy-confs/blob/master/metube.subdomain.conf.sample) modes, plus Authelia for authentication.
|
||||
The [linuxserver/swag](https://docs.linuxserver.io/general/swag) image includes ready-made snippets for mytube in [subfolder](https://github.com/linuxserver/reverse-proxy-confs/blob/master/mytube.subfolder.conf.sample) and [subdomain](https://github.com/linuxserver/reverse-proxy-confs/blob/master/mytube.subdomain.conf.sample) modes, plus Authelia for authentication.
|
||||
|
||||
### 🌐 NGINX
|
||||
|
||||
```nginx
|
||||
location /metube/ {
|
||||
proxy_pass http://metube:8081;
|
||||
location /mytube/ {
|
||||
proxy_pass http://mytube:8081;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
|
@ -316,13 +319,13 @@ Contributed by [PIE-yt](https://github.com/PIE-yt). Source [here](https://gist.g
|
|||
|
||||
```apache
|
||||
# For putting in your Apache sites site.conf
|
||||
# Serves MeTube under a /metube/ subdir (http://yourdomain.com/metube/)
|
||||
<Location /metube/>
|
||||
# Serves mytube under a /mytube/ subdir (http://yourdomain.com/mytube/)
|
||||
<Location /mytube/>
|
||||
ProxyPass http://localhost:8081/ retry=0 timeout=30
|
||||
ProxyPassReverse http://localhost:8081/
|
||||
</Location>
|
||||
|
||||
<Location /metube/socket.io>
|
||||
<Location /mytube/socket.io>
|
||||
RewriteEngine On
|
||||
RewriteCond %{QUERY_STRING} transport=websocket [NC]
|
||||
RewriteRule /(.*) ws://localhost:8081/socket.io/$1 [P,L]
|
||||
|
|
@ -337,29 +340,29 @@ The following example Caddyfile gets a reverse proxy going behind [caddy](https:
|
|||
|
||||
```caddyfile
|
||||
example.com {
|
||||
route /metube/* {
|
||||
uri strip_prefix metube
|
||||
reverse_proxy metube:8081
|
||||
route /mytube/* {
|
||||
uri strip_prefix mytube
|
||||
reverse_proxy mytube:8081
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 Updating yt-dlp
|
||||
|
||||
MeTube is powered by [yt-dlp](https://github.com/yt-dlp/yt-dlp), which requires frequent updates as video sites change their layouts. A nightly build automatically publishes a new Docker image whenever a new yt-dlp version is available, so keep your container up to date — [watchtower](https://github.com/nicholas-fedor/watchtower) works well for this.
|
||||
MyTube is powered by [yt-dlp](https://github.com/yt-dlp/yt-dlp), which requires frequent updates as video sites change their layouts. A nightly build automatically publishes a new Docker image whenever a new yt-dlp version is available, so keep your container up to date — [watchtower](https://github.com/nicholas-fedor/watchtower) works well for this.
|
||||
|
||||
## 🔧 Troubleshooting and submitting issues
|
||||
|
||||
MeTube is only a UI for [yt-dlp](https://github.com/yt-dlp/yt-dlp). Issues with authentication, postprocessing, permissions, or `YTDL_OPTIONS` should be debugged with yt-dlp directly first — once working, import those options into MeTube. To test inside the container:
|
||||
MyTube is only a UI for [yt-dlp](https://github.com/yt-dlp/yt-dlp). Issues with authentication, postprocessing, permissions, or `YTDL_OPTIONS` should be debugged with yt-dlp directly first — once working, import those options into MyTube. To test inside the container:
|
||||
|
||||
```bash
|
||||
docker exec -ti metube sh
|
||||
docker exec -ti mytube sh
|
||||
cd /downloads
|
||||
```
|
||||
|
||||
## 💡 Submitting feature requests
|
||||
|
||||
MeTube development relies on community contributions. If you need additional features, please submit a PR. Create an issue first to discuss the implementation — some PRs may not be accepted to reduce bloat. Feature requests without an accompanying PR are unlikely to be fulfilled.
|
||||
MyTube development relies on community contributions. If you need additional features, please submit a PR. Create an issue first to discuss the implementation — some PRs may not be accepted to reduce bloat. Feature requests without an accompanying PR are unlikely to be fulfilled.
|
||||
|
||||
## 🛠️ Building and running locally
|
||||
|
||||
|
|
@ -382,7 +385,7 @@ uv run python3 app/main.py
|
|||
A Docker image can be built locally (it will build the UI too):
|
||||
|
||||
```bash
|
||||
docker build -t metube .
|
||||
docker build -t mytube .
|
||||
```
|
||||
|
||||
Note that if you're running the server in VSCode, your downloads will go to your user's Downloads folder (this is configured via the environment in `.vscode/launch.json`).
|
||||
|
|
|
|||
22
app/main.py
22
app/main.py
|
|
@ -73,9 +73,10 @@ class Config:
|
|||
'MAX_CONCURRENT_DOWNLOADS': '3',
|
||||
'LOGLEVEL': 'INFO',
|
||||
'ENABLE_ACCESSLOG': 'false',
|
||||
'PUBLIC_MODE': 'false',
|
||||
}
|
||||
|
||||
_BOOLEAN = ('DOWNLOAD_DIRS_INDEXABLE', 'CUSTOM_DIRS', 'CREATE_CUSTOM_DIRS', 'DELETE_FILE_ON_TRASHCAN', 'HTTPS', 'ENABLE_ACCESSLOG', 'ALLOW_YTDL_OPTIONS_OVERRIDES')
|
||||
_BOOLEAN = ('DOWNLOAD_DIRS_INDEXABLE', 'CUSTOM_DIRS', 'CREATE_CUSTOM_DIRS', 'DELETE_FILE_ON_TRASHCAN', 'HTTPS', 'ENABLE_ACCESSLOG', 'ALLOW_YTDL_OPTIONS_OVERRIDES', 'PUBLIC_MODE')
|
||||
|
||||
def __init__(self):
|
||||
for k, v in self._DEFAULTS.items():
|
||||
|
|
@ -135,6 +136,7 @@ class Config:
|
|||
'DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT',
|
||||
'SUBSCRIPTION_DEFAULT_CHECK_INTERVAL',
|
||||
'ALLOW_YTDL_OPTIONS_OVERRIDES',
|
||||
'PUBLIC_MODE',
|
||||
)
|
||||
|
||||
def frontend_safe(self) -> dict:
|
||||
|
|
@ -760,6 +762,8 @@ async def cancel_add(request):
|
|||
|
||||
@routes.post(config.URL_PREFIX + 'subscribe')
|
||||
async def subscribe(request):
|
||||
if config.PUBLIC_MODE:
|
||||
raise web.HTTPForbidden(reason='subscriptions are disabled in public mode')
|
||||
post = await _read_json_request(request)
|
||||
try:
|
||||
o = parse_download_options(post)
|
||||
|
|
@ -811,11 +815,15 @@ async def subscribe(request):
|
|||
|
||||
@routes.get(config.URL_PREFIX + 'subscriptions')
|
||||
async def subscriptions_list(request):
|
||||
if config.PUBLIC_MODE:
|
||||
return web.Response(text=serializer.encode([]))
|
||||
return web.Response(text=serializer.encode([s.to_public_dict() for s in submgr.list_all()]))
|
||||
|
||||
|
||||
@routes.post(config.URL_PREFIX + 'subscriptions/update')
|
||||
async def subscriptions_update(request):
|
||||
if config.PUBLIC_MODE:
|
||||
raise web.HTTPForbidden(reason='subscriptions are disabled in public mode')
|
||||
post = await _read_json_request(request)
|
||||
sub_id = post.get('id')
|
||||
if not sub_id:
|
||||
|
|
@ -835,6 +843,8 @@ async def subscriptions_update(request):
|
|||
|
||||
@routes.post(config.URL_PREFIX + 'subscriptions/delete')
|
||||
async def subscriptions_delete(request):
|
||||
if config.PUBLIC_MODE:
|
||||
raise web.HTTPForbidden(reason='subscriptions are disabled in public mode')
|
||||
post = await _read_json_request(request)
|
||||
ids = post.get('ids')
|
||||
if not ids or not isinstance(ids, list):
|
||||
|
|
@ -845,6 +855,8 @@ async def subscriptions_delete(request):
|
|||
|
||||
@routes.post(config.URL_PREFIX + 'subscriptions/check')
|
||||
async def subscriptions_check(request):
|
||||
if config.PUBLIC_MODE:
|
||||
raise web.HTTPForbidden(reason='subscriptions are disabled in public mode')
|
||||
post = await _read_json_request(request)
|
||||
ids = post.get('ids')
|
||||
if ids is not None and not isinstance(ids, list):
|
||||
|
|
@ -955,8 +967,12 @@ async def history(request):
|
|||
@sio.event
|
||||
async def connect(sid, environ):
|
||||
log.info(f"Client connected: {sid}")
|
||||
await sio.emit('all', serializer.encode(dqueue.get()), to=sid)
|
||||
await sio.emit('subscriptions_all', serializer.encode([s.to_public_dict() for s in submgr.list_all()]), to=sid)
|
||||
if config.PUBLIC_MODE:
|
||||
await sio.emit('all', serializer.encode(([], [])), to=sid)
|
||||
else:
|
||||
await sio.emit('all', serializer.encode(dqueue.get()), to=sid)
|
||||
if not config.PUBLIC_MODE:
|
||||
await sio.emit('subscriptions_all', serializer.encode([s.to_public_dict() for s in submgr.list_all()]), to=sid)
|
||||
await sio.emit('configuration', serializer.encode(config.frontend_safe()), to=sid)
|
||||
if config.CUSTOM_DIRS:
|
||||
await sio.emit('custom_dirs', serializer.encode(get_custom_dirs()), to=sid)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"metube": {
|
||||
"mytube": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"outputPath": {
|
||||
"base": "dist/metube"
|
||||
"base": "dist/mytube"
|
||||
},
|
||||
"index": "src/index.html",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
|
|
@ -72,10 +72,10 @@
|
|||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "metube:build:production"
|
||||
"buildTarget": "mytube:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "metube:build:development",
|
||||
"buildTarget": "mytube:build:development",
|
||||
"proxyConfig": "proxy.conf.json"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "metube",
|
||||
"name": "mytube",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<nav class="navbar navbar-expand-md navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand d-flex align-items-center" href="#">
|
||||
<img src="assets/icons/android-chrome-192x192.png" alt="MeTube Logo" height="32" class="me-2">
|
||||
MeTube
|
||||
<img src="assets/icons/android-chrome-192x192.png" alt="MyTube Logo" height="32" class="me-2">
|
||||
MyTube
|
||||
</a>
|
||||
<div class="download-metrics">
|
||||
@if (activeDownloads > 0) {
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
</button>
|
||||
<button class="btn btn-outline-secondary btn-lg px-3" type="button"
|
||||
(click)="addSubscription()"
|
||||
[disabled]="downloads.loading">
|
||||
[disabled]="downloads.loading || isPublicMode()">
|
||||
Subscribe
|
||||
</button>
|
||||
}
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
class="form-control form-control-lg"
|
||||
placeholder="Enter video, channel, or playlist URL"
|
||||
placeholder="Enter one or multiple links (separate with spaces, commas, or newlines)"
|
||||
[(ngModel)]="addUrl"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
[disabled]="addInProgress || subscribeInProgress || downloads.loading">
|
||||
|
|
@ -150,7 +150,7 @@
|
|||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
class="form-control form-control-lg"
|
||||
placeholder="Enter video, channel, or playlist URL"
|
||||
placeholder="Enter one or multiple links (separate with spaces, commas, or newlines)"
|
||||
[(ngModel)]="addUrl"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
[disabled]="addInProgress || subscribeInProgress || downloads.loading">
|
||||
|
|
@ -893,6 +893,7 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
@if (!isPublicMode()) {
|
||||
<div class="metube-section-header">Subscriptions</div>
|
||||
<div class="px-2 py-3 border-bottom">
|
||||
@if (checkingAllSubscriptions) {
|
||||
|
|
@ -1041,6 +1042,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</main><!-- /.container -->
|
||||
|
||||
<footer class="footer navbar-dark bg-dark py-3 mt-5">
|
||||
|
|
@ -1053,7 +1055,7 @@
|
|||
</div>
|
||||
<div class="version-separator"></div>
|
||||
<div class="version-item">
|
||||
<span class="version-label">MeTube</span>
|
||||
<span class="version-label">MyTube</span>
|
||||
<span class="version-value">{{metubeVersion}}</span>
|
||||
</div>
|
||||
<div class="version-separator"></div>
|
||||
|
|
@ -1066,7 +1068,7 @@
|
|||
@if (ytDlpOptionsUpdateTime) {
|
||||
<div class="version-separator"></div>
|
||||
}
|
||||
<a href="https://github.com/alexta69/metube" target="_blank" class="github-link">
|
||||
<a href="https://github.com/TonyBlur/mytube" target="_blank" class="github-link">
|
||||
<fa-icon [icon]="faGithub" />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,51 +1,126 @@
|
|||
:host
|
||||
--mt-radius-sm: 0.5rem
|
||||
--mt-radius-md: 0.85rem
|
||||
--mt-radius-lg: 1.1rem
|
||||
--mt-shadow-sm: 0 2px 8px rgba(15, 23, 42, 0.06)
|
||||
--mt-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.08)
|
||||
--mt-shadow-lg: 0 14px 40px rgba(15, 23, 42, 0.12)
|
||||
--mt-surface: color-mix(in srgb, var(--bs-body-bg) 92%, #fff)
|
||||
--mt-surface-soft: color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-tertiary-bg))
|
||||
display: flex
|
||||
flex-direction: column
|
||||
min-height: 100vh
|
||||
|
||||
main
|
||||
flex-grow: 1
|
||||
|
||||
.navbar
|
||||
backdrop-filter: blur(8px)
|
||||
background: color-mix(in srgb, var(--bs-dark) 92%, #000)
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08)
|
||||
|
||||
.navbar-brand
|
||||
font-weight: 700
|
||||
letter-spacing: 0.02em
|
||||
|
||||
.add-url-box
|
||||
max-width: 960px
|
||||
margin: 4rem auto
|
||||
max-width: 1024px
|
||||
margin: 2.5rem auto
|
||||
|
||||
.card
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color) 75%, transparent)
|
||||
border-radius: var(--mt-radius-lg)
|
||||
background: var(--mt-surface)
|
||||
box-shadow: var(--mt-shadow-md)
|
||||
|
||||
.input-group-lg > .form-control,
|
||||
.input-group-lg > .form-select,
|
||||
.form-control-lg,
|
||||
.form-select-lg,
|
||||
.btn-lg
|
||||
border-radius: var(--mt-radius-md)
|
||||
|
||||
.form-control,
|
||||
.form-select,
|
||||
.btn
|
||||
transition: all .18s ease
|
||||
|
||||
.form-control:focus,
|
||||
.form-select:focus
|
||||
border-color: color-mix(in srgb, var(--bs-primary) 55%, #fff)
|
||||
box-shadow: 0 0 0 .22rem color-mix(in srgb, var(--bs-primary) 20%, transparent)
|
||||
|
||||
.btn-primary
|
||||
box-shadow: 0 6px 16px color-mix(in srgb, var(--bs-primary) 28%, transparent)
|
||||
|
||||
.btn-primary:hover
|
||||
transform: translateY(-1px)
|
||||
|
||||
.metube-section-header
|
||||
font-size: 1.8rem
|
||||
font-weight: 300
|
||||
position: relative
|
||||
background: var(--bs-secondary-bg)
|
||||
padding: 0.5rem 0
|
||||
margin-top: 3.5rem
|
||||
font-size: 1.2rem
|
||||
font-weight: 650
|
||||
letter-spacing: 0.01em
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
gap: .55rem
|
||||
margin-top: 2rem
|
||||
margin-bottom: .5rem
|
||||
padding: .35rem .75rem
|
||||
border-radius: var(--mt-radius-md)
|
||||
color: var(--bs-body-color)
|
||||
background: var(--mt-surface-soft)
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color) 72%, transparent)
|
||||
|
||||
.metube-section-header:before
|
||||
content: ""
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: -9999px
|
||||
right: 0
|
||||
border-left: 9999px solid var(--bs-secondary-bg)
|
||||
box-shadow: 9999px 0 0 var(--bs-secondary-bg)
|
||||
content: ""
|
||||
width: .45rem
|
||||
height: .45rem
|
||||
border-radius: 50%
|
||||
background: var(--bs-primary)
|
||||
|
||||
button:hover
|
||||
text-decoration: none
|
||||
.table
|
||||
border-radius: var(--mt-radius-md)
|
||||
overflow: hidden
|
||||
box-shadow: var(--mt-shadow-sm)
|
||||
|
||||
th
|
||||
border-top: 0
|
||||
border-bottom-width: 3px !important
|
||||
vertical-align: middle !important
|
||||
white-space: nowrap
|
||||
border-top: 0
|
||||
border-bottom-width: 2px !important
|
||||
vertical-align: middle !important
|
||||
white-space: nowrap
|
||||
font-size: .82rem
|
||||
text-transform: uppercase
|
||||
letter-spacing: .04em
|
||||
color: var(--bs-secondary-color)
|
||||
|
||||
td
|
||||
vertical-align: middle
|
||||
vertical-align: middle
|
||||
|
||||
tbody tr
|
||||
transition: background-color .15s ease
|
||||
|
||||
tbody tr:hover
|
||||
background: color-mix(in srgb, var(--bs-primary-bg-subtle) 35%, transparent)
|
||||
|
||||
.disabled
|
||||
opacity: 0.5
|
||||
pointer-events: none
|
||||
opacity: 0.5
|
||||
pointer-events: none
|
||||
|
||||
.form-switch
|
||||
input
|
||||
margin-top: 5px
|
||||
|
||||
.download-progressbar
|
||||
width: 12rem
|
||||
margin-left: auto
|
||||
width: 12rem
|
||||
margin-left: auto
|
||||
|
||||
.modal.fade.show
|
||||
background-color: rgba(0, 0, 0, 0.5)
|
||||
background-color: rgba(0, 0, 0, 0.45)
|
||||
|
||||
.modal-content
|
||||
border: 0
|
||||
border-radius: var(--mt-radius-lg)
|
||||
box-shadow: var(--mt-shadow-lg)
|
||||
|
||||
.modal-header
|
||||
border-bottom: 1px solid var(--bs-border-color)
|
||||
|
|
@ -53,6 +128,7 @@ td
|
|||
.modal-body
|
||||
textarea.form-control
|
||||
resize: vertical
|
||||
min-height: 9rem
|
||||
|
||||
.add-url
|
||||
display: inline-flex
|
||||
|
|
@ -69,24 +145,17 @@ td
|
|||
.add-cancel-btn
|
||||
min-width: 3.25rem
|
||||
|
||||
:host
|
||||
display: flex
|
||||
flex-direction: column
|
||||
min-height: 100vh
|
||||
|
||||
main
|
||||
flex-grow: 1
|
||||
|
||||
.footer
|
||||
width: 100%
|
||||
padding: 10px 0
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0.2), rgba(0,0,0,0.1))
|
||||
padding: 14px 0
|
||||
border-top: 1px solid rgba(255,255,255,.08)
|
||||
background: color-mix(in srgb, var(--bs-dark) 94%, #000)
|
||||
|
||||
.footer-content
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
gap: 20px
|
||||
gap: 18px
|
||||
color: #fff
|
||||
font-size: 0.9rem
|
||||
|
||||
|
|
@ -96,23 +165,23 @@ main
|
|||
gap: 8px
|
||||
|
||||
.version-label
|
||||
font-size: 0.75rem
|
||||
font-size: 0.72rem
|
||||
text-transform: uppercase
|
||||
letter-spacing: 0.5px
|
||||
opacity: 0.7
|
||||
letter-spacing: 0.08em
|
||||
opacity: 0.72
|
||||
|
||||
.version-value
|
||||
font-family: monospace
|
||||
font-size: 0.85rem
|
||||
padding: 2px 6px
|
||||
background: rgba(255,255,255,0.1)
|
||||
border-radius: 4px
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace
|
||||
font-size: 0.84rem
|
||||
padding: 2px 8px
|
||||
background: rgba(255,255,255,0.08)
|
||||
border-radius: var(--mt-radius-sm)
|
||||
|
||||
.version-separator
|
||||
width: 1px
|
||||
height: 16px
|
||||
background: rgba(255,255,255,0.2)
|
||||
margin: 0 4px
|
||||
background: rgba(255,255,255,0.18)
|
||||
margin: 0 2px
|
||||
|
||||
.github-link
|
||||
display: flex
|
||||
|
|
@ -121,18 +190,15 @@ main
|
|||
color: #fff
|
||||
text-decoration: none
|
||||
font-size: 0.85rem
|
||||
padding: 2px 8px
|
||||
border-radius: 4px
|
||||
padding: 3px 9px
|
||||
border-radius: var(--mt-radius-sm)
|
||||
transition: background-color 0.2s ease
|
||||
|
||||
&:hover
|
||||
background: rgba(255,255,255,0.1)
|
||||
background: rgba(255,255,255,0.12)
|
||||
color: #fff
|
||||
text-decoration: none
|
||||
|
||||
i
|
||||
font-size: 1rem
|
||||
|
||||
.download-metrics
|
||||
display: flex
|
||||
align-items: center
|
||||
|
|
@ -144,13 +210,7 @@ main
|
|||
align-items: center
|
||||
gap: 6px
|
||||
font-size: 0.9rem
|
||||
color: #adb5bd
|
||||
|
||||
fa-icon
|
||||
font-size: 1rem
|
||||
|
||||
span
|
||||
white-space: nowrap
|
||||
color: #c4cad1
|
||||
|
||||
.cookie-btn
|
||||
flex: 1 1 auto
|
||||
|
|
@ -158,29 +218,8 @@ main
|
|||
border-color: var(--bs-border-color)
|
||||
color: var(--bs-emphasis-color)
|
||||
|
||||
&:hover
|
||||
background-color: var(--bs-tertiary-bg)
|
||||
border-color: var(--bs-secondary)
|
||||
color: var(--bs-emphasis-color)
|
||||
|
||||
&.disabled
|
||||
opacity: 0.65
|
||||
pointer-events: none
|
||||
|
||||
.cookie-active-btn
|
||||
flex: 1 1 auto
|
||||
background-color: var(--bs-success-bg-subtle)
|
||||
border-color: var(--bs-success-border-subtle)
|
||||
color: var(--bs-success-text-emphasis)
|
||||
|
||||
&:hover
|
||||
background-color: var(--bs-success-bg-subtle)
|
||||
border-color: var(--bs-success)
|
||||
color: var(--bs-success-text-emphasis)
|
||||
|
||||
&.disabled
|
||||
opacity: 0.65
|
||||
pointer-events: none
|
||||
|
||||
.settings-section-label
|
||||
font-size: 0.8rem
|
||||
|
|
@ -191,9 +230,6 @@ main
|
|||
margin-top: 1.75rem
|
||||
margin-bottom: 0.75rem
|
||||
|
||||
&:first-child
|
||||
margin-top: 0
|
||||
|
||||
.action-group-label
|
||||
font-size: 0.7rem
|
||||
text-transform: uppercase
|
||||
|
|
@ -205,6 +241,3 @@ main
|
|||
font-size: 0.8rem
|
||||
margin-top: 0.35rem
|
||||
color: var(--bs-secondary-color)
|
||||
|
||||
&.active
|
||||
color: var(--bs-success-text-emphasis)
|
||||
|
|
|
|||
|
|
@ -375,6 +375,10 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
|||
return this.downloads.configuration['ALLOW_YTDL_OPTIONS_OVERRIDES'] === true;
|
||||
}
|
||||
|
||||
isPublicMode() {
|
||||
return this.downloads.configuration['PUBLIC_MODE'] === true;
|
||||
}
|
||||
|
||||
allowCustomDir(tag: string) {
|
||||
if (this.downloads.configuration['CREATE_CUSTOM_DIRS']) {
|
||||
return tag;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MeTube</title>
|
||||
<title>MyTube</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/icons/favicon-32x32.png">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "MeTube",
|
||||
"short_name": "MeTube",
|
||||
"name": "MyTube",
|
||||
"short_name": "MyTube",
|
||||
"theme_color": "#212529",
|
||||
"background_color": "#fafafa",
|
||||
"display": "standalone",
|
||||
|
|
|
|||
Loading…
Reference in a new issue