diff --git a/.github/workflows/submodule-check.yml b/.github/workflows/submodule-check.yml
new file mode 100644
index 0000000..4acbe7d
--- /dev/null
+++ b/.github/workflows/submodule-check.yml
@@ -0,0 +1,62 @@
+name: Submodule Cleanliness Check
+
+on:
+ pull_request:
+ branches: [ main, master ]
+ push:
+ branches: [ main, master ]
+
+jobs:
+ verify-submodules:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository with submodules
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+
+ - name: Initialize/Update submodules
+ run: |
+ git submodule sync --recursive
+ git submodule update --init --recursive --jobs 4
+
+ - name: Ensure submodule status has no anomalies
+ run: |
+ set -euo pipefail
+ anomalies=0
+
+ echo "Checking git submodule status output"
+ status_output=$(git submodule status --recursive || true)
+ echo "$status_output"
+
+ # If any line starts with '-', submodule is not initialized
+ if echo "$status_output" | grep -E '^-' >/dev/null; then
+ echo "ERROR: Found uninitialized submodules (lines starting with '-')" >&2
+ anomalies=1
+ fi
+
+ # If any line starts with '+', submodule commit is different than recorded
+ if echo "$status_output" | grep -E '^\+' >/dev/null; then
+ echo "ERROR: Found submodules not at recorded commit (lines starting with '+')" >&2
+ anomalies=1
+ fi
+
+ # If any line contains 'dirty', submodule has local changes
+ if echo "$status_output" | grep -E 'dirty' >/dev/null; then
+ echo "ERROR: Found dirty submodule working trees" >&2
+ anomalies=1
+ fi
+
+ # Also ensure working tree under submodules is clean
+ if ! git diff --submodule=diff --quiet; then
+ echo "ERROR: Detected differences in submodules (working tree not clean)" >&2
+ anomalies=1
+ fi
+
+ if [ "$anomalies" -ne 0 ]; then
+ echo "Submodule anomalies detected; failing the job." >&2
+ exit 1
+ fi
+
+ echo "Submodule status clean."
diff --git a/.gitignore b/.gitignore
index 988fb45..46a967c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,5 @@
-<<<<<<< HEAD
build/bin
-node_modules
TODO.md
+node_modules/
+
-=======
-fyne-cross
-TODO.md
->>>>>>> 5c9da659bc26ab9faf682fb549dc7e0b599a7169
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..bd6cab8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+# Contributing
+
+## Submodules policy
+
+This repository includes a `frontend/` submodule. To maintain reproducible builds and CI integrity:
+
+- Never commit a dirty submodule state. Ensure `git submodule status` shows no `dirty` markers.
+- Update submodules deterministically using:
+ - `git submodule sync --recursive`
+ - `git submodule update --init --recursive --checkout`
+- When you change the `frontend/` code, commit and push inside the submodule first, then update the pointer in this repo:
+ 1. `cd frontend`
+ 2. Work and commit: `git add -A && git commit -m "feat: ..." && git push`
+ 3. `cd .. && git add frontend && git commit -m "chore(frontend): bump submodule" && git push`
+
+CI will block merges that contain uninitialized, mismatched, or dirty submodule pointers (see `.github/workflows/submodule-check.yml`).
+
+## Development quickstart
+
+- Clone with submodules: `git clone --recurse-submodules https://github.com/Aswanidev-vs/Predator.git`
+- Or initialize after clone: `git submodule update --init --recursive`
+- Go toolchain: see `go.mod` (`go 1.25`). Build/dev commands:
+ - Dev: `wails dev`
+ - Build: `wails build`
diff --git a/README.md b/README.md
index bd408b2..9ab3fd7 100644
--- a/README.md
+++ b/README.md
@@ -1,179 +1,174 @@
# Predator
-
+
-A simple desktop app for grabbing videos and audio from YouTube and Instagram. Built with Go and Wails (web tech frontend), using yt-dlp under the hood to do the heavy lifting.
+
-## What it does
+
+
-- **Download videos** from YouTube in any resolution from 144p up to 4K
-- **Grab audio only** in MP3, M4A, Opus, or WAV format
-- **Works with Instagram too** - reels, posts, stories, TV videos
-- **Playlist support** - Download entire YouTube playlists or pick specific videos
-- **Shows file sizes** before you download so you know what you're getting into
-- **Live progress tracking** - Watch speed, ETA, and percentage in real-time
-- **Cancel anytime** - Change your mind? Hit cancel and it stops cleanly
-- **Picks up where it left off** - Failed downloads auto-retry up to 3 times
-- **Duplicate detection** - Warns if you already downloaded something
-- **Choose where files go** - Set your download folder once, it remembers
-- **Keeps history** - See what you've downloaded, filter by video/audio, open files directly
-- **Dark and light themes** - Toggle with Ctrl+T
+A simple desktop app for grabbing videos and audio from YouTube and Instagram. Built with Go and Wails (web tech frontend), using yt-dlp under the hood to do the heavy lifting.
-## Getting started
+## Table of Contents
-You'll need **Go 1.24 or newer** installed.
+- [Features](#features)
+- [Getting Started](#getting-started)
+- [How to Use](#how-to-use)
+- [Keyboard Shortcuts](#keyboard-shortcuts)
+- [Technical Details](#technical-details)
+- [Requirements](#requirements)
+- [Troubleshooting](#troubleshooting)
+- [Contributing](#contributing)
+- [License](#license)
-```bash
-git clone https://github.com/Aswanidev-vs/Predator.git
-cd Predator
-go mod download
-```
+## Features
-# Predator - YouTube Downloader
-
-
-
-A modern, cross-platform desktop application for downloading videos and audio from YouTube .
-It is built using Go and the Fyne GUI framework, with yt-dlp as the download engine.
+### Video Downloads
+- **Multiple resolutions** - From 144p up to 4K (2160p)
+- **Best quality option** - Automatically picks the highest available
+- **Smart codec handling** - Uses H.264 video + AAC audio for maximum compatibility
+- **WebM support** - Available with VP9/AV1 + Opus for better quality
-The project focuses on simplicity, transparency, and responsive user experience while keeping the implementation clean and maintainable.
+### Audio Downloads
+- **Multiple formats** - MP3, M4A, Opus, or WAV
+- **High quality output** - Uses best available source and converts to your chosen format
+- **MP3**: V0 quality (~245kbps)
+- **M4A**: AAC 192kbps
+- **WAV**: 16-bit PCM 44.1kHz stereo
-## Features
+### Platform Support
+- **YouTube** - Videos, shorts, live streams
+- **Instagram** - Reels, posts, stories, TV videos
-- **Video Download**: Download YouTube videos in various resolutions (144p to 2160p)
-- **Audio Extraction**: Extract audio from videos in multiple formats (MP3, M4A, Opus, WAV)
-- **Real-time Metadata**: Fetches video information dynamically, showing available resolutions with file sizes
-- **Progress Tracking**: Live download progress ETA information
-- **Cancellable Downloads**: Stop downloads at any time
-- **Custom Output Directory**: Select and manage your download location
-- **Cross-platform**: Works on Windows, macOS, and Linux
+### User Experience
+- **Playlist support** - Download entire playlists or select specific videos
+- **File size preview** - See file sizes before downloading
+- **Live progress** - Real-time speed, ETA, and percentage tracking
+- **Cancel anytime** - Clean cancellation with automatic temp file cleanup
+- **Auto-retry** - Failed downloads retry up to 3 times automatically
+- **Duplicate detection** - Warns if content was already downloaded
+- **Custom download folder** - Set once, remembers forever
+- **Download history** - View past downloads, filter by type, open file location
-## Requirements
-- **Go**: 1.24.0 or higher
-- **FFmpeg**: Required for audio extraction and video merging (automatically handled by yt-dlp)
-- **yt-dlp**: Automatically installed on first run
+## Getting Started
-## Installation
+You'll need **Go 1.24 or newer** installed.
-### Clone the Repository
```bash
+# Clone the repository
git clone https://github.com/Aswanidev-vs/Predator.git
cd Predator
+
+# Install dependencies
+go mod download
```
-### Install Dependencies
+### Running the App
+
+**Development mode:**
```bash
-go mod download
+wails dev
```
-### Build and Run
+**Build for production:**
```bash
-go run main.go
+wails build
```
-### Build for Distribution
-The project includes cross-platform build configuration in the `fyne-cross` directory:
+**First time setup:** The app will ask to download yt-dlp and ffmpeg automatically. Just say yes - it handles everything and caches them locally. No need to install anything else manually.
-```bash
-# For Windows
-go install fyne.io/tools/cmd/fyne-cross@latest
-fyne-cross windows
+## How to Use
-# For macOS
-fyne-cross darwin
+1. **Open the app** - It'll show the main download tab
+2. **Set your download folder** - Click "Change Download Location" (or it'll use the current folder)
+3. **Paste a URL** - YouTube or Instagram links both work
+4. **Pick your format:**
+ - For video: Choose resolution (144p to 4K, or "best")
+ - For audio: Pick format (MP3, M4A, Opus, WAV)
+5. **Hit download** - Watch the progress bar do its thing
-# For Linux
-fyne-cross linux
-```
-## Usage
+**Pro tips:**
+- Playlists show a modal where you can select specific videos to download
+- Click the folder icon in history to open where a file is saved
+- The app auto-updates yt-dlp to the latest version on each download
-1. **Launch the Application**: Run the executable or `go run main.go`
-2. **Set Download Location**: Click "Change Download Location" to select where videos will be saved
-3. **Paste YouTube URL**: Paste a YouTube video link into the URL field
-4. **Select Download Type**:
- - **Video**: Choose a resolution and video will be downloaded with best audio merged
- - **Audio**: Choose an audio format (MP3, M4A, Opus, WAV)
-5. **Click Download**: Start the download process
-6. **Monitor Progress**: Watch real-time progress, speed, and ETA
-7. **Cancel if Needed**: Click Cancel button to stop an ongoing download
+## Technical Details
-## Architecture
+### Video Format
+- **Container:** MP4 (with WebM option)
+- **Video codec:** H.264 (avc1) for maximum device compatibility
+- **Audio codec:** AAC (m4a) for best audio quality
+- **Fallback handling:** If preferred codec isn't available, automatically falls back to next best option
-### Main Components
+### Audio Format
+- Uses best available source (webm/opus or m4a/aac) and converts to your chosen format using ffmpeg
-- **UI Layer**: Built with [Fyne](https://fyne.io/) - a cross-platform GUI framework
-- **Download Engine**: Powered by [go-ytdlp](https://github.com/lrstanley/go-ytdlp) - a Go wrapper for yt-dlp
-- **Async Operations**: Uses goroutines for non-blocking UI interactions
-- **Progress Tracking**: Real-time progress callbacks with ETA calculations
+### File Locations
+- **Downloads:** User-selected folder or `PREDATOR_OUTPUT_DIR` environment variable
+- **History:** `~/.predator/history.json` (keeps last 100 items)
+- **Settings:** `~/.predator/settings.json`
+- **Dependencies:** `~/.cache/yt-dlp/` (yt-dlp, ffmpeg, ffprobe)
-### Key Functions
+### Performance
+- **Concurrent downloads:** Max 3 at a time to not overwhelm your connection
+- **Progress updates:** Every 200ms for smooth progress bars
+- **Speed smoothing:** Uses exponential moving average for stable speed display
-- `main()`: Initializes the UI and event handlers
-- `fetchVideoInfo()`: Dynamically fetches video metadata and available resolutions
-- `formatBytes()`: Converts byte sizes to human-readable format
-- `formatETA()`: Converts duration to HH:MM:SS format
+### Cleanup
+- Cancelled downloads automatically clean up `.part` and temp files
+- Failed downloads after max retries also trigger cleanup
-## Project Structure
+## Requirements
-```
-Predator/
-├── main.go # Main application code
-├── go.mod # Go module definition
-├── go.sum # Go module checksums
-├── logov4.png # Application icon
-├── asset/ # Icon and logo assets
-├── fyne-cross/ # Cross-platform build configuration
-├── sample.txt # Code sample reference
-└── samplev*.txt # Additional code samples
-```
+- Go 1.24+
+- Internet connection
+- yt-dlp and ffmpeg (auto-installed on first run)
-## Dependencies
+## Troubleshooting
-- **fyne.io/fyne/v2**: Cross-platform GUI framework
-- **github.com/lrstanley/go-ytdlp**: Go wrapper for yt-dlp
+### "ffmpeg not found" error
+The app will prompt you to download ffmpeg on first run. If you skip it, you can:
+1. Install ffmpeg manually from [ffmpeg.org](https://ffmpeg.org)
+2. Or delete `~/.cache/yt-dlp/` and restart the app - it'll ask again
+### Slow downloads
+- Check your internet connection
+- YouTube might be throttling (try a different resolution)
+- Max 3 concurrent downloads - close other apps using bandwidth
-## Performance
+### Download fails with "format not available"
+- Try a lower resolution
+- The video might be region-locked or unavailable
+- Try using "best" option for auto-selection of best available
-- **Lazy Metadata Loading**: Video info is only fetched when a valid URL is entered
-- **Debounced Input**: 600ms debounce on URL input to prevent excessive API calls
-- **Atomic Operations**: Thread-safe state management for concurrent operations
-- **Streaming Download**: Supports live progress updates during download
+### "Permission denied" errors
+- Check if your download folder is writable
+- Try a different download location
-## Disclaimer
+## Contributing
-**IMPORTANT LEGAL NOTICE**
+Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines.
-This application is provided for educational and personal use only. Users are solely responsible for compliance with all applicable laws and regulations in their jurisdiction.
+## Legal Stuff
-### Legal Considerations
+**This tool is for educational and personal use only.**
-- **Copyright Compliance**: Users must respect copyright laws and YouTube's Terms of Service. Downloading copyrighted content without proper authorization may be illegal in your jurisdiction.
-- **Terms of Service**: By using this application, you acknowledge that you are bound by YouTube's Terms of Service and agree not to violate them.
-- **Personal Use Only**: This tool is intended for downloading content you own or have permission to download.
-- **Liability**: The authors and contributors of Predator are not responsible for:
- - Any copyright infringement or violations of third-party rights
- - Misuse of downloaded content
- - Any legal consequences arising from use of this application
- - Data loss or corruption
- - Any other damages resulting from use of this software
+- Respect copyright laws and YouTube's Terms of Service
+- Only download content you own or have permission to download
+- Don't use this to redistribute copyrighted material
+- The authors aren't responsible for how you use this tool
-### Responsible Use
+Basically: be cool, don't pirate stuff you shouldn't.
-Users should:
-- Only download content they have permission to download
-- Respect content creators' rights and intellectual property
-- Use downloaded content in compliance with local laws
-- Not distribute copyrighted content without proper licensing
-- Be aware that many YouTube videos are protected by copyright
+## License
-### No Warranty
+MIT License - see [LICENSE](LICENSE) file.
-This application is provided "as-is" without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, or non-infringement.
+---
-The authors assume no responsibility for any illegal activities or misuse of this tool. By using Predator, you accept all risks and responsibilities associated with your downloads.
+Built with [Wails](https://wails.io/) + Go + vanilla JS. No React, no bloat.
diff --git a/app.go b/app.go
index acf0d9b..0ed011a 100644
--- a/app.go
+++ b/app.go
@@ -412,78 +412,159 @@ func extractResolution(resolution string) string {
/* -------------------- Format Builders -------------------- */
+// buildVideoFormatString builds format string for mp4 container with proper codec handling
+// Uses separate video+audio selection to ensure reliable merging
func buildVideoFormatString(cleanRes string, preferH264 bool) string {
- // Build format string that handles all codecs (h264, vp9, av1)
- // CRITICAL: Always use m4a audio for mp4 container compatibility
- // webm/opus audio causes ffmpeg merge failures with mp4 video
+ // Build format string that prioritizes h264+aac for mp4 container compatibility
+ // Use separate selection (bv*+ba*) to ensure proper download and merging
if cleanRes == "best" {
- // Try h264 first, then vp9, then av1
- // Audio: STRICTLY m4a/aac only to ensure mp4 compatibility
- return "bestvideo[vcodec^=avc1]+bestaudio[ext=m4a]/" +
- "bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/" +
- "bestvideo[vcodec^=vp9]+bestaudio[ext=m4a]/" +
- "bestvideo[vcodec^=vp9]+bestaudio[acodec^=mp4a]/" +
- "bestvideo[vcodec^=av01]+bestaudio[ext=m4a]/" +
- "bestvideo[vcodec^=av01]+bestaudio[acodec^=mp4a]/" +
- "bestvideo+bestaudio[ext=m4a]/" +
- "bestvideo+bestaudio[acodec^=mp4a]/" +
- "bestvideo+bestaudio/best"
+ // Best quality - prioritize h264 with aac/m4a for reliable mp4 merging
+ return "bestvideo[vcodec^=avc1]+bestaudio[ext=m4a]/bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/" +
+ "bestvideo[vcodec^=avc1]+bestaudio/" +
+ "bestvideo[vcodec^=vp9]+bestaudio[ext=m4a]/bestvideo[vcodec^=vp9]+bestaudio[acodec^=mp4a]/" +
+ "bestvideo[vcodec^=vp9]+bestaudio/" +
+ "bestvideo[vcodec^=av01]+bestaudio[ext=m4a]/bestvideo[vcodec^=av01]+bestaudio[acodec^=mp4a]/" +
+ "bestvideo[vcodec^=av01]+bestaudio/" +
+ "bestvideo+bestaudio[ext=m4a]/bestvideo+bestaudio[acodec^=mp4a]/bestvideo+bestaudio/best"
}
resNum, _ := strconv.Atoi(cleanRes)
if resNum >= 1440 {
- // High resolutions - try exact height first, then fall back to <=
- // STRICTLY m4a audio only to prevent ffmpeg merge failures
+ // High resolutions (1440p+) - prioritize h264 with exact or close height match
return fmt.Sprintf(
- // Try exact height match first with m4a audio only
+ // h264 with m4a audio - best for mp4 merging
"bestvideo[vcodec^=avc1][height=%s]+bestaudio[ext=m4a]/"+
"bestvideo[vcodec^=avc1][height=%s]+bestaudio[acodec^=mp4a]/"+
+ "bestvideo[vcodec^=avc1][height=%s]+bestaudio/"+
+ // h264 with <= height fallback
+ "bestvideo[vcodec^=avc1][height<=%s]+bestaudio[ext=m4a]/"+
+ "bestvideo[vcodec^=avc1][height<=%s]+bestaudio[acodec^=mp4a]/"+
+ "bestvideo[vcodec^=avc1][height<=%s]+bestaudio/"+
+ // vp9 fallback
"bestvideo[vcodec^=vp9][height=%s]+bestaudio[ext=m4a]/"+
"bestvideo[vcodec^=vp9][height=%s]+bestaudio[acodec^=mp4a]/"+
+ "bestvideo[vcodec^=vp9][height=%s]+bestaudio/"+
+ // av1 fallback
"bestvideo[vcodec^=av01][height=%s]+bestaudio[ext=m4a]/"+
"bestvideo[vcodec^=av01][height=%s]+bestaudio[acodec^=mp4a]/"+
- // Fall back to <= if exact match not available - still m4a only
- "bestvideo[vcodec^=avc1][height<=%s]+bestaudio[ext=m4a]/"+
- "bestvideo[vcodec^=avc1][height<=%s]+bestaudio[acodec^=mp4a]/"+
- "bestvideo[vcodec^=vp9][height<=%s]+bestaudio[ext=m4a]/"+
- "bestvideo[vcodec^=vp9][height<=%s]+bestaudio[acodec^=mp4a]/"+
- "bestvideo[vcodec^=av01][height<=%s]+bestaudio[ext=m4a]/"+
- "bestvideo[vcodec^=av01][height<=%s]+bestaudio[acodec^=mp4a]/"+
- // Final fallbacks - still prefer m4a
+ "bestvideo[vcodec^=av01][height=%s]+bestaudio/"+
+ // Any video codec fallback
"bestvideo[height<=%s]+bestaudio[ext=m4a]/"+
"bestvideo[height<=%s]+bestaudio[acodec^=mp4a]/"+
+ "bestvideo[height<=%s]+bestaudio/"+
"best[height<=%s]",
cleanRes, cleanRes, cleanRes, cleanRes, cleanRes, cleanRes,
cleanRes, cleanRes, cleanRes, cleanRes, cleanRes, cleanRes,
- cleanRes, cleanRes, cleanRes,
+ cleanRes, cleanRes, cleanRes, cleanRes,
)
}
- // Standard resolutions - try exact height first, then fall back
- // STRICTLY m4a audio only to prevent ffmpeg merge failures
+ // Standard resolutions - prioritize h264 with m4a audio
return fmt.Sprintf(
- // Try exact height match first with m4a audio only
+ // h264 with m4a audio - best for mp4 merging
"bestvideo[vcodec^=avc1][height=%s]+bestaudio[ext=m4a]/"+
"bestvideo[vcodec^=avc1][height=%s]+bestaudio[acodec^=mp4a]/"+
+ "bestvideo[vcodec^=avc1][height=%s]+bestaudio/"+
+ // h264 with <= height fallback
+ "bestvideo[vcodec^=avc1][height<=%s]+bestaudio[ext=m4a]/"+
+ "bestvideo[vcodec^=avc1][height<=%s]+bestaudio[acodec^=mp4a]/"+
+ "bestvideo[vcodec^=avc1][height<=%s]+bestaudio/"+
+ // vp9 fallback
"bestvideo[vcodec^=vp9][height=%s]+bestaudio[ext=m4a]/"+
"bestvideo[vcodec^=vp9][height=%s]+bestaudio[acodec^=mp4a]/"+
+ "bestvideo[vcodec^=vp9][height=%s]+bestaudio/"+
+ // av1 fallback
"bestvideo[vcodec^=av01][height=%s]+bestaudio[ext=m4a]/"+
"bestvideo[vcodec^=av01][height=%s]+bestaudio[acodec^=mp4a]/"+
- // Fall back to <= if exact match not available - still m4a only
- "bestvideo[vcodec^=avc1][height<=%s]+bestaudio[ext=m4a]/"+
- "bestvideo[vcodec^=avc1][height<=%s]+bestaudio[acodec^=mp4a]/"+
- "bestvideo[vcodec^=vp9][height<=%s]+bestaudio[ext=m4a]/"+
- "bestvideo[vcodec^=vp9][height<=%s]+bestaudio[acodec^=mp4a]/"+
- "bestvideo[vcodec^=av01][height<=%s]+bestaudio[ext=m4a]/"+
- "bestvideo[vcodec^=av01][height<=%s]+bestaudio[acodec^=mp4a]/"+
- // Final fallbacks - still prefer m4a
+ "bestvideo[vcodec^=av01][height=%s]+bestaudio/"+
+ // Any video codec fallback
"bestvideo[height<=%s]+bestaudio[ext=m4a]/"+
"bestvideo[height<=%s]+bestaudio[acodec^=mp4a]/"+
+ "bestvideo[height<=%s]+bestaudio/"+
+ "best[height<=%s]",
+ cleanRes, cleanRes, cleanRes, cleanRes, cleanRes, cleanRes,
+ cleanRes, cleanRes, cleanRes, cleanRes, cleanRes, cleanRes,
+ cleanRes, cleanRes, cleanRes, cleanRes,
+ )
+}
+
+// buildWebmFormatString builds format string for webm container with VP9/AV1 + opus
+// This ensures best quality webm output with modern codecs
+func buildWebmFormatString(cleanRes string) string {
+ // For webm, we want VP9 or AV1 video with opus audio
+ // This provides better quality than older vp8/opus combinations
+
+ if cleanRes == "best" {
+ // Best quality webm - prioritize av01 (AV1) then vp9
+ return "bestvideo[vcodec^=av01]+bestaudio[acodec^=opus]/" +
+ "bestvideo[vcodec^=av01]+bestaudio[ext=webm]/" +
+ "bestvideo[vcodec^=av01]+bestaudio/" +
+ "bestvideo[vcodec^=vp9]+bestaudio[acodec^=opus]/" +
+ "bestvideo[vcodec^=vp9]+bestaudio[ext=webm]/" +
+ "bestvideo[vcodec^=vp9]+bestaudio/" +
+ "bestvideo[vcodec^=vp8]+bestaudio[acodec^=opus]/" +
+ "bestvideo[vcodec^=vp8]+bestaudio/" +
+ "bestvideo+bestaudio[acodec^=opus]/bestvideo+bestaudio/best"
+ }
+
+ resNum, _ := strconv.Atoi(cleanRes)
+ if resNum >= 1440 {
+ // High resolutions - prioritize av01 then vp9
+ return fmt.Sprintf(
+ // AV1 with opus - best quality
+ "bestvideo[vcodec^=av01][height=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[vcodec^=av01][height=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[vcodec^=av01][height=%s]+bestaudio/"+
+ // AV1 <= height fallback
+ "bestvideo[vcodec^=av01][height<=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[vcodec^=av01][height<=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[vcodec^=av01][height<=%s]+bestaudio/"+
+ // VP9 with opus
+ "bestvideo[vcodec^=vp9][height=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[vcodec^=vp9][height=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[vcodec^=vp9][height=%s]+bestaudio/"+
+ // VP9 <= height fallback
+ "bestvideo[vcodec^=vp9][height<=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[vcodec^=vp9][height<=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[vcodec^=vp9][height<=%s]+bestaudio/"+
+ // Any codec fallback
+ "bestvideo[height<=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[height<=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[height<=%s]+bestaudio/"+
+ "best[height<=%s]",
+ cleanRes, cleanRes, cleanRes, cleanRes, cleanRes, cleanRes,
+ cleanRes, cleanRes, cleanRes, cleanRes, cleanRes, cleanRes,
+ cleanRes, cleanRes, cleanRes, cleanRes,
+ )
+ }
+
+ // Standard resolutions - prioritize av01 then vp9 with opus
+ return fmt.Sprintf(
+ // AV1 with opus
+ "bestvideo[vcodec^=av01][height=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[vcodec^=av01][height=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[vcodec^=av01][height=%s]+bestaudio/"+
+ // AV1 <= height fallback
+ "bestvideo[vcodec^=av01][height<=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[vcodec^=av01][height<=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[vcodec^=av01][height<=%s]+bestaudio/"+
+ // VP9 with opus
+ "bestvideo[vcodec^=vp9][height=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[vcodec^=vp9][height=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[vcodec^=vp9][height=%s]+bestaudio/"+
+ // VP9 <= height fallback
+ "bestvideo[vcodec^=vp9][height<=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[vcodec^=vp9][height<=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[vcodec^=vp9][height<=%s]+bestaudio/"+
+ // Any codec fallback
+ "bestvideo[height<=%s]+bestaudio[acodec^=opus]/"+
+ "bestvideo[height<=%s]+bestaudio[ext=webm]/"+
+ "bestvideo[height<=%s]+bestaudio/"+
"best[height<=%s]",
cleanRes, cleanRes, cleanRes, cleanRes, cleanRes, cleanRes,
cleanRes, cleanRes, cleanRes, cleanRes, cleanRes, cleanRes,
- cleanRes, cleanRes, cleanRes,
+ cleanRes, cleanRes, cleanRes, cleanRes,
)
}