26 |
27 | ## Key Features
28 |
29 | - **4K Video Downloads**: Download YouTube videos in ultra HD quality
30 | - **YouTube to MP3 Converter**: Convert YouTube videos to high-quality 320kbps MP3 audio
31 | - **YouTube Shorts**: Save Shorts videos with one click
32 | - **No Ads, No Extra Software**: Clean and straightforward downloading experience
33 | - **Cross-Platform**: Works on Windows, macOS, and Linux with Chrome, Edge, Firefox, Opera, Brave, and Vivaldi
34 | - **Private Video Support**: Download private videos and member-only content you have access to
35 |
36 | ## Screenshots
37 |
38 |
39 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ## Installation
51 |
52 | ### Chrome, Brave, Opera, and Vivaldi
53 | For detailed installation instructions on Chromium-based browsers, visit our [official website](https://tubly.download/install.html).
54 |
55 | ### Microsoft Edge
56 | 1. Visit our [Edge Add-ons page](https://microsoftedge.microsoft.com/addons/detail/npolimekdjdhijlfikfghaipaijbbobj)
57 | 2. Click "Get" to add the extension
58 | 3. Follow the prompts to complete installation
59 |
60 | ### Firefox
61 | 1. Download our [Firefox add-on xpi package](https://tubly.download/firefox/tubly_downloader_v1.5.0.xpi)
62 | 2. Firefox will automatically start the installation process
63 | 3. Follow the prompts to complete installation
64 |
65 | ### Updating
66 | > Edge & Firefox users get updates automatically.
67 | 1. For Chrome users, download the new version from our website
68 | 2. Follow the same installation steps with the new file
69 | 3. Your settings will be preserved automatically
70 |
71 | ## FAQ
72 |
73 | ### How do I download YouTube videos in 4K quality?
74 | Simply navigate to any YouTube video, and click the download button that appears below the video player. You'll be presented with multiple quality options, including 4K (2160p) and even 8K if available.
75 |
76 | ### Can I download YouTube Shorts videos?
77 | Yes! Our Chrome extension is specially designed to detect and download YouTube Shorts. When viewing Shorts content, you'll see our download button appear, allowing you to save these short-form videos directly to your device.
78 |
79 | ### How to convert YouTube videos to MP3?
80 | Our extension extracts audio directly from videos without requiring any additional software. After installing, you'll see an "Audio" option when clicking the download button to save in high-quality 320kbps MP3 format.
81 |
82 |
--------------------------------------------------------------------------------
/src/popup.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', () => {
2 | const noVideoSection = document.getElementById('no-video');
3 | const videoInfoSection = document.getElementById('video-info');
4 | const videoThumbnail = document.getElementById('video-thumbnail');
5 | const videoTitle = document.getElementById('video-title');
6 | const videoDuration = document.getElementById('video-duration');
7 | const channelName = document.getElementById('channel-name');
8 | const videoQualities = document.getElementById('video-qualities');
9 | const audioQualities = document.getElementById('audio-qualities');
10 | const progressContainer = document.getElementById('download-progress');
11 | const progressBar = document.getElementById('progress');
12 | const progressText = document.getElementById('progress-text');
13 | const openYouTubeBtn = document.getElementById('open-youtube');
14 | const settingsBtn = document.getElementById('settings-btn');
15 |
16 | // Current video information
17 | let currentVideo = null;
18 |
19 | // Check if we're on a YouTube video page
20 | chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
21 | const currentTab = tabs[0];
22 | const url = currentTab.url;
23 |
24 | if (isYouTubeVideoUrl(url)) {
25 | // We're on a YouTube video page
26 | noVideoSection.classList.add('hidden');
27 | videoInfoSection.classList.remove('hidden');
28 |
29 | // Get video information
30 | chrome.tabs.sendMessage(currentTab.id, {action: 'getVideoInfo'}, (response) => {
31 | if (response && response.videoInfo) {
32 | displayVideoInfo(response.videoInfo);
33 | } else {
34 | showError('Could not retrieve video information');
35 | }
36 | });
37 | } else {
38 | // Not on a YouTube video page
39 | noVideoSection.classList.remove('hidden');
40 | videoInfoSection.classList.add('hidden');
41 | }
42 | });
43 |
44 | // Handle "Go to YouTube" button click
45 | openYouTubeBtn.addEventListener('click', () => {
46 | chrome.tabs.create({url: 'https://www.youtube.com'});
47 | });
48 |
49 | // Handle settings button click
50 | settingsBtn.addEventListener('click', () => {
51 | chrome.runtime.openOptionsPage();
52 | });
53 |
54 | // Display video information
55 | function displayVideoInfo(videoInfo) {
56 | currentVideo = videoInfo;
57 |
58 | videoThumbnail.src = videoInfo.thumbnail;
59 | videoTitle.textContent = videoInfo.title;
60 | videoDuration.textContent = formatDuration(videoInfo.duration);
61 | channelName.textContent = videoInfo.channelName;
62 |
63 | // Display video quality options
64 | videoQualities.innerHTML = '';
65 | videoInfo.videoQualities.forEach(quality => {
66 | const qualityBtn = createQualityButton(quality, 'video');
67 | videoQualities.appendChild(qualityBtn);
68 | });
69 |
70 | // Display audio quality options
71 | audioQualities.innerHTML = '';
72 | videoInfo.audioQualities.forEach(quality => {
73 | const qualityBtn = createQualityButton(quality, 'audio');
74 | audioQualities.appendChild(qualityBtn);
75 | });
76 | }
77 |
78 | // Create quality selection button
79 | function createQualityButton(quality, type) {
80 | const button = document.createElement('button');
81 | button.classList.add('quality-btn');
82 | button.setAttribute('data-quality', quality.id);
83 | button.setAttribute('data-type', type);
84 |
85 | if (type === 'video') {
86 | button.textContent = `${quality.label} (${quality.fileSize})`;
87 | } else {
88 | button.textContent = `${quality.label} MP3 (${quality.fileSize})`;
89 | }
90 |
91 | button.addEventListener('click', () => {
92 | initiateDownload(quality, type);
93 | });
94 |
95 | return button;
96 | }
97 |
98 | // Initiate download process
99 | function initiateDownload(quality, type) {
100 | progressContainer.classList.remove('hidden');
101 | progressText.textContent = 'Preparing download...';
102 | progressBar.style.width = '0%';
103 |
104 | chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
105 | const currentTab = tabs[0];
106 |
107 | chrome.tabs.sendMessage(
108 | currentTab.id,
109 | {
110 | action: 'downloadVideo',
111 | qualityId: quality.id,
112 | type: type,
113 | videoId: currentVideo.id
114 | },
115 | (response) => {
116 | if (response && response.success) {
117 | updateDownloadProgress(0);
118 |
119 | // Simulate download progress
120 | const downloadInterval = setInterval(() => {
121 | const currentWidth = parseInt(progressBar.style.width, 10) || 0;
122 | if (currentWidth >= 100) {
123 | clearInterval(downloadInterval);
124 | progressText.textContent = 'Download complete!';
125 | setTimeout(() => {
126 | progressContainer.classList.add('hidden');
127 | }, 2000);
128 | } else {
129 | updateDownloadProgress(currentWidth + 5);
130 | }
131 | }, 300);
132 | } else {
133 | showError('Failed to start download');
134 | }
135 | }
136 | );
137 | });
138 | }
139 |
140 | // Update download progress
141 | function updateDownloadProgress(percent) {
142 | progressBar.style.width = `${percent}%`;
143 | if (percent < 100) {
144 | progressText.textContent = `Downloading: ${percent}%`;
145 | }
146 | }
147 |
148 | // Show error message
149 | function showError(message) {
150 | progressContainer.classList.remove('hidden');
151 | progressText.textContent = message;
152 | progressBar.style.width = '100%';
153 | progressBar.style.backgroundColor = '#f44336';
154 | }
155 |
156 | // Check if URL is a YouTube video
157 | function isYouTubeVideoUrl(url) {
158 | return /^(https?:\/\/)?(www\.)?(youtube\.com\/watch|youtu\.be\/|youtube\.com\/shorts)/.test(url);
159 | }
160 |
161 | // Format duration from seconds to MM:SS
162 | function formatDuration(seconds) {
163 | const minutes = Math.floor(seconds / 60);
164 | const remainingSeconds = Math.floor(seconds % 60);
165 | return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
166 | }
167 | });
168 |
--------------------------------------------------------------------------------