Skip to content

Instantly share code, notes, and snippets.

@Far-Se
Created November 1, 2025 06:21
Show Gist options
  • Save Far-Se/c2d8e825e20ded62daa38f43150dacf2 to your computer and use it in GitHub Desktop.
Save Far-Se/c2d8e825e20ded62daa38f43150dacf2 to your computer and use it in GitHub Desktop.
Youtube yt-dlp command generator.
// ==UserScript==
// @name yt-dlp Command Generator
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Generate yt-dlp commands with quality selection and metadata options
// @match https://www.youtube.com/*
// @grant GM_registerMenuCommand
// @grant GM_setClipboard
// ==/UserScript==
(function() {
'use strict';
// Fix for TrustedHTML error
if (window.trustedTypes && window.trustedTypes.createPolicy) {
window.trustedTypes.createPolicy('default', {
createHTML: (string) => string
});
}
// Register menu command
GM_registerMenuCommand('Download', showDownloadPopup,"d");
function parseMusicTitle(title) {
// Remove common video type suffixes
const suffixes = [
/\s*\(Official\s+Video\)/gi,
/\s*\(Official\s+Music\s+Video\)/gi,
/\s*\(Official\s+Audio\)/gi,
/\s*\[Official\s+Video\]/gi,
/\s*\[Official\s+Audio\]/gi,
/\s*\(Lyric\s+Video\)/gi,
/\s*\[Lyric\s+Video\]/gi,
/\s*\(Audio\)/gi,
/\s*\[Audio\]/gi,
/\s*\(Visualizer\)/gi,
/\s*\[Visualizer\]/gi,
/\s*\(Music\s+Video\)/gi,
/\s*\[Music\s+Video\]/gi,
/\s*\(Official\)/gi,
/\s*\[Official\]/gi
];
let cleanTitle = title;
suffixes.forEach(suffix => {
cleanTitle = cleanTitle.replace(suffix, '');
});
// Check for delimiters: —, -, //
const delimiters = ['—', ' - ', ' // ', '//'];
for (let delimiter of delimiters) {
if (cleanTitle.includes(delimiter)) {
const parts = cleanTitle.split(delimiter);
if (parts.length >= 2) {
// Take the second part (song name) and trim it
let songName = parts[1].trim();
// Remove any remaining suffixes from the song name
suffixes.forEach(suffix => {
songName = songName.replace(suffix, '');
});
return songName.trim();
}
}
}
// If no delimiter found, return cleaned title
return cleanTitle.trim();
}
function showDownloadPopup() {
if(!window.location.pathname.includes('watch'))return;
// Get video data from YouTube's player response
const videoData = window.ytInitialPlayerResponse?.videoDetails;
if (!videoData) {
alert('Could not retrieve video information.\n Refresh the page first!');
return;
}
const videoId = videoData.videoId;
const title = videoData.title;
const author = videoData.author;
const duration = formatDuration(videoData.lengthSeconds);
const thumbnail = `https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`;
// Parse music title to extract song name
const songTitle = parseMusicTitle(title);
// Create popup overlay
const overlay = document.createElement('div');
overlay.id = 'ytdlp-popup-overlay';
overlay.innerHTML = `
<div id="ytdlp-popup">
<div id="ytdlp-header">
<h2>Download Video</h2>
<button id="ytdlp-close">×</button>
</div>
<div id="ytdlp-video-info">
<img src="${thumbnail}" alt="Thumbnail" id="ytdlp-thumbnail">
<div id="ytdlp-details">
<div id="ytdlp-title">${escapeHtml(title)}</div>
<div id="ytdlp-author">${escapeHtml(author)}</div>
<div id="ytdlp-duration">Duration: ${duration}</div>
</div>
</div>
<div id="ytdlp-metadata-section">
<h3>Metadata Options (for MP3/MP4)</h3>
<div class="ytdlp-metadata-inputs">
<div class="ytdlp-input-group">
<label>Artist:</label>
<input type="text" id="ytdlp-artist" placeholder="Artist name" value="${escapeHtml(author)}">
</div>
<div class="ytdlp-input-group">
<label>Title:</label>
<input type="text" id="ytdlp-song-title" placeholder="Song title" value="${escapeHtml(songTitle)}">
</div>
<div class="ytdlp-checkbox-group">
<input type="checkbox" id="ytdlp-embed-thumbnail" checked>
<label for="ytdlp-embed-thumbnail">Embed Thumbnail</label>
</div>
<div class="ytdlp-checkbox-group">
<input type="checkbox" id="ytdlp-embed-metadata" checked>
<label for="ytdlp-embed-metadata">Embed Metadata</label>
</div>
</div>
</div>
<div id="ytdlp-tables-container">
<div id="ytdlp-quality-section">
<h3>Video Quality Options</h3>
<table id="ytdlp-quality-table">
<thead>
<tr>
<th>Quality</th>
<th>Format</th>
<th>Action</th>
</tr>
</thead>
<tbody>
${generateQualityRows(videoId)}
</tbody>
</table>
</div>
<div id="ytdlp-audio-section">
<h3>Audio Only Options</h3>
<table id="ytdlp-audio-table">
<thead>
<tr>
<th>Format</th>
<th>Quality</th>
<th>Action</th>
</tr>
</thead>
<tbody>
${generateAudioRows(videoId)}
</tbody>
</table>
</div>
</div>
<div id="ytdlp-metadata-section">
<h3>Copy yt-dlp command then open a command prompt and paste it</h3>
</div>
</div>
`;
document.body.appendChild(overlay);
addStyles();
attachEventListeners(overlay, videoId);
}
function generateQualityRows(videoId) {
const qualities = [
{ label: 'Best Available', format: 'bestvideo+bestaudio/best', desc: 'MP4' },
{ label: '2160p (4K)', format: 'bestvideo[height<=2160]+bestaudio/best[height<=2160]', desc: 'MP4' },
{ label: '1440p (2K)', format: 'bestvideo[height<=1440]+bestaudio/best[height<=1440]', desc: 'MP4' },
{ label: '1080p (FHD)', format: 'bestvideo[height<=1080]+bestaudio/best[height<=1080]', desc: 'MP4' },
{ label: '720p (HD)', format: 'bestvideo[height<=720]+bestaudio/best[height<=720]', desc: 'MP4' },
{ label: '480p', format: 'bestvideo[height<=480]+bestaudio/best[height<=480]', desc: 'MP4' },
{ label: '360p', format: 'bestvideo[height<=360]+bestaudio/best[height<=360]', desc: 'MP4' },
{ label: 'Video Only (Best)', format: 'bestvideo', desc: 'No Audio' }
];
return qualities.map(q => `
<tr>
<td>${q.label}</td>
<td>${q.desc}</td>
<td>
<button class="ytdlp-copy-btn" data-format="${q.format}" data-type="video">
Copy
</button>
</td>
</tr>
`).join('');
}
function generateAudioRows(videoId) {
const audioFormats = [
{ label: 'M4A (Best Quality)', format: 'bestaudio', ext: 'm4a', quality: '0' },
{ label: 'OPUS (Best Quality)', format: 'bestaudio[ext=opus]', ext: 'opus', quality: '0' },
{ label: 'Audio Only (Best)', format: 'bestaudio', ext: 'best', quality: '0' },
{ label: 'MP3 (320kbps)', format: 'bestaudio', ext: 'mp3', quality: '320K' },
{ label: 'MP3 (256kbps)', format: 'bestaudio', ext: 'mp3', quality: '256K' },
{ label: 'MP3 (192kbps)', format: 'bestaudio', ext: 'mp3', quality: '192K' },
];
return audioFormats.map(a => `
<tr>
<td>${a.label}</td>
<td>${a.ext.toUpperCase()}</td>
<td>
<button class="ytdlp-copy-btn" data-format="${a.format}" data-type="audio" data-ext="${a.ext}" data-quality="${a.quality}">
Copy
</button>
</td>
</tr>
`).join('');
}
function attachEventListeners(overlay, videoId) {
// Close button
overlay.querySelector('#ytdlp-close').addEventListener('click', () => {
overlay.remove();
});
// Click outside to close
overlay.addEventListener('click', (e) => {
if (e.target.id === 'ytdlp-popup-overlay') {
overlay.remove();
}
});
// Copy buttons
overlay.querySelectorAll('.ytdlp-copy-btn').forEach(btn => {
btn.addEventListener('click', function() {
const format = this.dataset.format;
const type = this.dataset.type;
const videoUrl = window.location.href;
let command = 'yt-dlp ';
// Add metadata options
const embedThumbnail = document.getElementById('ytdlp-embed-thumbnail').checked;
const embedMetadata = document.getElementById('ytdlp-embed-metadata').checked;
const artist = document.getElementById('ytdlp-artist').value;
const songTitle = document.getElementById('ytdlp-song-title').value;
if (type === 'audio') {
const ext = this.dataset.ext;
const quality = this.dataset.quality;
command += '-f ' + format + ' ';
if (ext !== 'best') {
command += '-x --audio-format ' + ext + ' ';
if (quality !== '0') {
command += '--audio-quality ' + quality + ' ';
}
}
if (embedThumbnail) {
command += '--embed-thumbnail ';
}
if (embedMetadata) {
command += '--embed-metadata ';
if (artist) {
command += `--parse-metadata "title:%(artist)s" --parse-metadata "${artist}:%(artist)s" `;
}
if (songTitle) {
command += `--parse-metadata "${songTitle}:%(title)s" `;
}
}
command += '--add-metadata ';
} else {
command += '-f "' + format + '" ';
if (embedMetadata && format.includes('bestvideo+bestaudio')) {
command += '--embed-metadata ';
if (artist) {
command += `--parse-metadata "${artist}:%(artist)s" `;
}
if (songTitle) {
command += `--parse-metadata "${songTitle}:%(title)s" `;
}
}
command += '--merge-output-format mp4 ';
}
command += '"' + videoUrl + '" -o "%(title)s.%(ext)s"';
// Copy to clipboard
GM_setClipboard(command);
// Show feedback
const originalText = this.textContent;
this.textContent = 'Copied!';
this.style.backgroundColor = '#065fd4';
setTimeout(() => {
this.textContent = originalText;
this.style.backgroundColor = '';
}, 2000);
});
});
}
function formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
if (hours > 0) {
return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
return `${minutes}:${String(secs).padStart(2, '0')}`;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function addStyles() {
if (document.getElementById('ytdlp-styles')) return;
const style = document.createElement('style');
style.id = 'ytdlp-styles';
style.textContent = `
#ytdlp-popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
font-family: "YouTube Sans", "Roboto", Arial, sans-serif;
}
#ytdlp-popup {
background: #212121;
border-radius: 12px;
width: 90%;
max-width: 1200px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
color: #fff;
}
#ytdlp-popup::-webkit-scrollbar {
width: 8px;
}
#ytdlp-popup::-webkit-scrollbar-track {
background: #181818;
}
#ytdlp-popup::-webkit-scrollbar-thumb {
background: #404040;
border-radius: 4px;
}
#ytdlp-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
border-bottom: 1px solid #303030;
}
#ytdlp-header h2 {
margin: 0;
font-size: 20px;
font-weight: 500;
}
#ytdlp-close {
background: none;
border: none;
color: #fff;
font-size: 32px;
cursor: pointer;
padding: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.2s;
}
#ytdlp-close:hover {
background: #3f3f3f;
}
#ytdlp-video-info {
display: flex;
gap: 16px;
padding: 24px;
background: #181818;
border-bottom: 1px solid #303030;
}
#ytdlp-thumbnail {
width: 240px;
height: 135px;
object-fit: cover;
border-radius: 8px;
flex-shrink: 0;
}
#ytdlp-details {
display: flex;
flex-direction: column;
gap: 8px;
}
#ytdlp-title {
font-size: 16px;
font-weight: 500;
line-height: 1.4;
}
#ytdlp-author {
color: #aaa;
font-size: 14px;
}
#ytdlp-duration {
color: #aaa;
font-size: 14px;
}
#ytdlp-metadata-section {
padding: 24px;
border-bottom: 1px solid #303030;
}
#ytdlp-metadata-section h3 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 500;
}
.ytdlp-metadata-inputs {
display: grid;
gap: 16px;
}
.ytdlp-input-group {
display: grid;
gap: 8px;
}
.ytdlp-input-group label {
font-size: 14px;
color: #aaa;
}
.ytdlp-input-group input[type="text"] {
background: #181818;
border: 1px solid #303030;
color: #fff;
padding: 10px 12px;
border-radius: 4px;
font-size: 14px;
font-family: inherit;
}
.ytdlp-input-group input[type="text"]:focus {
outline: none;
border-color: #3ea6ff;
}
.ytdlp-checkbox-group {
display: flex;
align-items: center;
gap: 8px;
}
.ytdlp-checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.ytdlp-checkbox-group label {
font-size: 14px;
cursor: pointer;
}
#ytdlp-tables-container {
display: flex;
gap: 24px;
padding: 24px;
flex-wrap: wrap;
}
#ytdlp-quality-section,
#ytdlp-audio-section {
flex: 1;
min-width: 400px;
}
#ytdlp-quality-section h3,
#ytdlp-audio-section h3 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 500;
}
#ytdlp-quality-table,
#ytdlp-audio-table {
width: 100%;
border-collapse: collapse;
}
#ytdlp-quality-table th,
#ytdlp-audio-table th {
text-align: left;
padding: 12px;
background: #181818;
font-weight: 500;
font-size: 14px;
border-bottom: 1px solid #303030;
}
#ytdlp-quality-table td,
#ytdlp-audio-table td {
padding: 12px;
border-bottom: 1px solid #303030;
font-size: 14px;
}
#ytdlp-quality-table tr:hover,
#ytdlp-audio-table tr:hover {
background: #282828;
}
.ytdlp-copy-btn {
background: #3f3f3f;
color: #fff;
border: none;
padding: 8px 16px;
border-radius: 18px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background 0.2s;
white-space: nowrap;
}
.ytdlp-copy-btn:hover {
background: #4f4f4f;
}
.ytdlp-copy-btn:active {
background: #065fd4;
}
@media (max-width: 1024px) {
#ytdlp-tables-container {
flex-direction: column;
}
#ytdlp-quality-section,
#ytdlp-audio-section {
min-width: 100%;
}
}
@media (max-width: 768px) {
#ytdlp-video-info {
flex-direction: column;
}
#ytdlp-thumbnail {
width: 100%;
height: auto;
}
#ytdlp-popup {
width: 95%;
}
}
`;
document.head.appendChild(style);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment