From 823492346a13146f248b0a1f5bfe62bf65ad69cf Mon Sep 17 00:00:00 2001 From: Minidu Thiranjaya <74867152+MiniduTH@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:20:26 +0530 Subject: [PATCH] Add initial project files for MTH Video Manifest Capture extension --- README.md | 107 ++++++++++++++++++++++ background.js | 75 +++++++++++++++ icon128.png | Bin 0 -> 13351 bytes icon16.png | Bin 0 -> 1134 bytes icon48.png | Bin 0 -> 3158 bytes manifest.json | 25 +++++ popup.css | 193 ++++++++++++++++++++++++++++++++++++++ popup.html | 73 +++++++++++++++ popup.js | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 722 insertions(+) create mode 100644 README.md create mode 100644 background.js create mode 100644 icon128.png create mode 100644 icon16.png create mode 100644 icon48.png create mode 100644 manifest.json create mode 100644 popup.css create mode 100644 popup.html create mode 100644 popup.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..10f6a6c --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# MTH Video Manifest Capture + +A Chrome extension that captures video manifest URLs (e.g., HLS `.m3u8`, MPEG-DASH `.mpd`, and SharePoint DASH manifests) from web pages, processes them, and generates FFmpeg and yt-dlp commands for downloading. It also includes a simple UI for customization and a button to download or open the manifest URL directly. + +## Overview + +**MTH Video Manifest Capture** is designed to assist users in capturing and processing video streaming manifests, particularly for SharePoint, HLS, and DASH formats. The extension provides a user-friendly popup interface to view cleaned manifest URLs, select quality and format options, set custom filenames, and copy commands for FFmpeg and yt-dlp to download the video content. It also includes a **"Download Manifest"** button to open the cleaned manifest URL in a new tab or initiate a basic download. + +This extension is built using Chrome's **Manifest V3**, ensuring modern security and performance standards. + +## Features + +- **Captures video manifest URLs** (`.m3u8`, `.mpd`, and SharePoint `/transform/videomanifest` with `format=dash`). +- **Cleans URLs** by removing unnecessary parameters (e.g., after `format=dash`). +- **Generates customizable FFmpeg and yt-dlp commands** with options for quality (Best, 1080p, 720p, 480p) and format (MP4, MKV, TS). +- **Allows custom output filenames** or uses a timestamp-based default (e.g., `video_YYYYMMDD_HHMMSS`). +- **"Download Manifest" button** to open the cleaned manifest URL in a new tab or initiate a basic download of the manifest file. +- **Toggle buttons** to expand/collapse long URLs and commands for better readability. +- **Copy buttons** for FFmpeg and yt-dlp commands to easily paste into a terminal. + +## Installation + +### Prerequisites + +- Google Chrome or Chromium browser (**Manifest V3 compatible, version 88 or later**). +- FFmpeg and yt-dlp installed on your system (**optional, for running the generated commands**). + +### Steps + +1. **Clone or Download the Repository** + ```bash + git clone https://github.com/MiniduTH/Sharepoint-Downloader.git + ``` + Or download the ZIP file and extract it. + +2. **Load the Extension in Chrome** + - Open Chrome and navigate to `chrome://extensions/`. + - Enable **"Developer mode"** in the top-right corner. + - Click **"Load unpacked"** and select the `Sharepoint-Downloader` folder. + +3. **Install Dependencies** *(optional, for using generated commands)* + - **FFmpeg:** Install via your package manager (e.g., `sudo apt install ffmpeg` on Ubuntu) or download from [ffmpeg.org](https://ffmpeg.org/). + - **yt-dlp:** Install via `pip install yt-dlp` or download from [yt-dlp.org](https://yt-dlp.org/). + +## Usage + +1. **Open a Web Page with Video Manifests** + - Visit a website streaming video content (e.g., SharePoint, HLS, or DASH streams). + +2. **Activate the Extension** + - Click the **"MTH Video Manifest Capture"** icon in the Chrome toolbar. + +3. **Capture Manifests** + - The extension automatically detects and captures video manifest URLs (`.m3u8`, `.mpd`, or SharePoint DASH manifests). + - The cleaned URL, FFmpeg command, and yt-dlp command will appear in the popup. + +4. **Customize Options** + - Use the dropdowns to select video quality (**Best, 1080p, 720p, 480p**) and format (**MP4, MKV, TS**). + - Enter a custom output filename, or leave it blank to use the timestamp-based default (e.g., `video_20250305_143022.mp4`). + +5. **Copy Commands** + - Click **"Copy FFmpeg"** or **"Copy yt-dlp"** to copy the respective commands to your clipboard. + - Paste the commands into a terminal to download the video using FFmpeg or yt-dlp. + +6. **Download Manifest** *(optional)* + - Click **"Download Manifest"** to open the cleaned manifest URL in a new tab or download the manifest file (**note:** this downloads the manifest, not the full video; further processing is required using FFmpeg or yt-dlp). + +## Screenshots + +_Screenshot of the popup interface showing captured manifest, commands, and options._ + +![Screenshot](https://i.ibb.co/wZmMQ04P/Screenshot-2025-03-05-151334.png) + +## Known Limitations + +- **"Download Manifest" only saves the manifest file** (e.g., `.m3u8` or `.mpd`), not the full video. You need tools like **FFmpeg or yt-dlp** to process it. +- **Authenticated manifest URLs** (e.g., SharePoint temp auth tokens) may require user login or additional configuration. +- **Direct video downloads** without external tools are **not supported** due to browser security restrictions. + +## Contributing + +Contributions are welcome! Please follow these steps: + +1. **Fork the repository**. +2. **Create a new branch** for your feature or bug fix: + ```bash + git checkout -b feature/your-feature-name + ``` +3. **Make your changes and commit them**: + ```bash + git commit -m "Add your commit message here" + ``` +4. **Push to the branch**: + ```bash + git push origin feature/your-feature-name + ``` +5. **Submit a pull request** to the main branch. + +## License + +This project is licensed under the **MIT License**. See the `LICENSE` file for details. + +## Contact + +- **Author:** Minidu Weerasinghe +- **GitHub:** [MiniduTH](https://github.com/MiniduTH) +- **LinkedIn:** [linkedin.com/in/minidu0th](https://linkedin.com/in/minidu0th) diff --git a/background.js b/background.js new file mode 100644 index 0000000..76606ce --- /dev/null +++ b/background.js @@ -0,0 +1,75 @@ +console.log('Background script loaded'); + +chrome.webRequest.onBeforeRequest.addListener( + (details) => { + try { + const originalUrl = details.url; + if ( + (originalUrl.includes('/transform/videomanifest') && originalUrl.includes('format=dash')) || + originalUrl.endsWith('.m3u8') || + originalUrl.endsWith('.mpd') + ) { + console.log('Video manifest detected:', originalUrl); + + let cleanedUrl = originalUrl; + let type = 'unknown'; + if (originalUrl.includes('/transform/videomanifest') && originalUrl.includes('format=dash')) { + const index = originalUrl.indexOf('format=dash'); + cleanedUrl = originalUrl.substring(0, index + 'format=dash'.length); + type = 'dash'; + } else if (originalUrl.endsWith('.mpd')) { + const index = originalUrl.indexOf('.mpd') + '.mpd'.length; + cleanedUrl = originalUrl.substring(0, index); + type = 'dash'; + } else if (originalUrl.endsWith('.m3u8')) { + const index = originalUrl.indexOf('.m3u8') + '.m3u8'.length; + cleanedUrl = originalUrl.substring(0, index); + type = 'hls'; + } + + const manifestData = { + originalUrl: originalUrl, + cleanedUrl: cleanedUrl, + type: type + }; + chrome.storage.local.set({ lastManifest: manifestData }, (result) => { + if (chrome.runtime.lastError) { + console.error('Error saving manifest:', chrome.runtime.lastError.message); + } else { + console.log('Manifest saved:', manifestData); + } + }); + + chrome.runtime.sendMessage({ type: 'manifestDetected', data: manifestData }, (response) => { + if (chrome.runtime.lastError) { + console.error('Error sending message:', chrome.runtime.lastError.message); + } + }); + } + } catch (error) { + console.error('Error in webRequest listener:', error); + } + }, + { urls: [""] }, + ["requestBody"] +); + +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + console.log('Message received:', message); + if (message.type === 'getLastManifest') { + try { + chrome.storage.local.get('lastManifest', (result) => { + if (chrome.runtime.lastError) { + console.error('Error getting manifest:', chrome.runtime.lastError.message); + sendResponse({ data: null }); + } else { + sendResponse({ data: result.lastManifest || null }); + } + }); + } catch (error) { + console.error('Error in message listener:', error); + sendResponse({ data: null }); + } + return true; // Keep the channel open for async response + } +}); \ No newline at end of file diff --git a/icon128.png b/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..fe0ee9b177153cf18fbb3aa1424010c927d664cf GIT binary patch literal 13351 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 MOQ?g~`LghT0B(XS&Hw-a literal 0 HcmV?d00001 diff --git a/icon16.png b/icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..e286466ff6f5546d6568107098180791a1aad7b2 GIT binary patch literal 1134 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 qpl@8*g}f5ei}AqospOIpip$ra`k+|~! literal 0 HcmV?d00001 diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..d6cdee1 --- /dev/null +++ b/manifest.json @@ -0,0 +1,25 @@ +{ + "manifest_version": 3, + "name": "MTH Video Manifest Capture", + "version": "1.0", + "description": "Captures video manifest URLs.", + "permissions": [ + "webRequest", + "activeTab", + "storage" + ], + "host_permissions": [ + "" + ], + "background": { + "service_worker": "background.js" + }, + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + } + } +} \ No newline at end of file diff --git a/popup.css b/popup.css new file mode 100644 index 0000000..689fe9e --- /dev/null +++ b/popup.css @@ -0,0 +1,193 @@ +body { + width: 350px; + padding: 0; + margin: 0; + font-family: 'Arial', sans-serif; + background-color: #f8f9fa; + color: #333; + box-sizing: border-box; +} + +.container { + padding: 15px; +} + +.title { + margin: 0 0 15px; + font-size: 20px; + font-weight: 600; + color: #1a73e8; + text-align: center; +} + +.btn { + display: block; + padding: 10px; + border: none; + border-radius: 4px; + font-size: 14px; + cursor: pointer; + transition: background-color 0.2s ease, transform 0.1s ease; +} + +.btn.primary { + width: 100%; + background-color: #1a73e8; + color: white; +} + +.btn.primary:hover { + background-color: #1557b0; +} + +.btn.primary:active { + background-color: #0d47a1; + transform: scale(0.98); +} + +.btn.secondary { + background-color: #4caf50; + color: white; +} + +.btn.secondary:hover { + background-color: #388e3c; +} + +.btn.secondary:active { + background-color: #2e7d32; + transform: scale(0.98); +} + +.btn.small { + width: auto; + padding: 6px 12px; + margin-top: 5px; + font-size: 12px; +} + +.btn.toggle { + width: 100%; + background-color: #f5f5f5; + color: #1a73e8; + margin-top: 5px; + border: 1px solid #ddd; +} + +.btn.toggle:hover { + background-color: #e8f0fe; +} + +.btn.toggle:active { + background-color: #d9e6ff; + transform: scale(0.98); +} + +.card { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 15px; + margin-top: 10px; +} + +.card h2 { + margin: 0 0 10px; + font-size: 16px; + color: #555; + border-bottom: 1px solid #eee; + padding-bottom: 10px; +} + +.manifest-section { + margin-bottom: 15px; +} + +.manifest-section label { + display: block; + margin-bottom: 5px; + font-weight: 500; + color: #666; + font-size: 14px; +} + +.text-container { + position: relative; + margin-bottom: 5px; +} + +.text { + margin: 0; + padding: 8px; + background-color: #f5f5f5; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 12px; + line-height: 1.4; + word-wrap: break-word; + max-height: 100px; + overflow-y: hidden; + white-space: nowrap; + text-overflow: ellipsis; + transition: max-height 0.3s ease, white-space 0.3s ease; +} + +.text.expanded { + max-height: 300px; + overflow-y: auto; + white-space: normal; + text-overflow: clip; +} + +.options-section { + margin: 15px 0; + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; +} + +.options-section label { + font-size: 14px; + color: #555; + margin-right: 5px; +} + +.select, .input { + padding: 6px; + font-size: 13px; + border: 1px solid #ddd; + border-radius: 4px; + background-color: #fff; + cursor: pointer; +} + +.input { + width: 150px; + cursor: text; +} + +.select:focus, .input:focus { + outline: none; + border-color: #1a73e8; + box-shadow: 0 0 0 2px #e8f0fe; +} + +@media (max-width: 350px) { + body { + width: 100%; + } + .container { + padding: 10px; + } + .card { + padding: 10px; + } + .input { + width: 120px; + } + .btn.small { + width: 100%; + padding: 8px; + } +} \ No newline at end of file diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..6179278 --- /dev/null +++ b/popup.html @@ -0,0 +1,73 @@ + + + + Video Manifest Capture + + + + + +
+

Video Manifest Capture

+ + + +
+

Manifest Details

+
+ +
+

No manifest captured yet.

+
+ +
+ +
+ + + + + + + + +
+ +
+ +
+

FFmpeg command will appear here.

+
+ + +
+ +
+ +
+

yt-dlp command will appear here.

+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..9020e54 --- /dev/null +++ b/popup.js @@ -0,0 +1,249 @@ +document.addEventListener('DOMContentLoaded', () => { + // Get DOM elements with null checks + const manifestUrlDiv = document.getElementById('manifestUrl'); + const ffmpegCommandDiv = document.getElementById('ffmpegCommand'); + const ytdlpCommandDiv = document.getElementById('ytdlpCommand'); + const refreshButton = document.getElementById('refresh'); + const copyFffmpegBtn = document.getElementById('copyFffmpegBtn'); // Fixed typo + const copyYtdlpBtn = document.getElementById('copyYtdlpBtn'); + const qualitySelect = document.getElementById('quality'); + const formatSelect = document.getElementById('format'); + const filenameInput = document.getElementById('filename'); + const toggleUrlBtn = document.getElementById('toggleUrl'); + const toggleFffmpegBtn = document.getElementById('toggleFffmpeg'); // Fixed typo + const toggleYtdlpBtn = document.getElementById('toggleYtdlp'); + + // Check if all elements exist + if (!manifestUrlDiv || !ffmpegCommandDiv || !ytdlpCommandDiv || !refreshButton || !copyFffmpegBtn || + !copyYtdlpBtn || !qualitySelect || !formatSelect || !filenameInput || !toggleUrlBtn || + !toggleFffmpegBtn || !toggleYtdlpBtn) { + console.error('One or more DOM elements not found in popup.html'); + return; + } + + let currentManifestData = null; + + // Function to generate a timestamp-based filename + function generateTimestampFilename() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + return `video_${year}${month}${day}_${hours}${minutes}${seconds}`; + } + + // Function to generate FFmpeg command + function generateFffmpegCommand(cleanedUrl, type, quality, format) { + const userFilename = filenameInput.value.trim(); + const filename = userFilename || `${generateTimestampFilename()}.${format}`; + return `ffmpeg -i "${cleanedUrl}" -c copy ${filename}`; + } + + // Function to generate yt-dlp command + function generateYtdlpCommand(cleanedUrl, quality, format) { + const userFilename = filenameInput.value.trim(); + const filename = userFilename || `${generateTimestampFilename()}.${format}`; + let qualityParam = ''; + if (quality !== 'best') { + qualityParam = `--recode-video ${format} -f bestvideo[height<=?${quality.replace('p', '')}]+bestaudio/best[height<=?${quality.replace('p', '')}]`; + } else { + qualityParam = `--recode-video ${format} -f bestvideo+bestaudio/best`; + } + return `yt-dlp "${cleanedUrl}" ${qualityParam} -o "${filename}"`; + } + + function updateUI(data) { + if (data) { + currentManifestData = data; + if (manifestUrlDiv) manifestUrlDiv.textContent = data.cleanedUrl; // Add null check + const ffmpegCommand = generateFffmpegCommand( + data.cleanedUrl, + data.type, + qualitySelect.value, + formatSelect.value + ); + const ytdlpCommand = generateYtdlpCommand( + data.cleanedUrl, + qualitySelect.value, + formatSelect.value + ); + if (ffmpegCommandDiv) ffmpegCommandDiv.textContent = ffmpegCommand; // Add null check + if (ytdlpCommandDiv) ytdlpCommandDiv.textContent = ytdlpCommand; // Add null check + if (filenameInput && !filenameInput.value.trim()) { + filenameInput.placeholder = generateTimestampFilename(); + } + } else { + if (manifestUrlDiv) manifestUrlDiv.textContent = 'No manifest captured yet.'; + if (ffmpegCommandDiv) ffmpegCommandDiv.textContent = 'FFmpeg command will appear here.'; + if (ytdlpCommandDiv) ytdlpCommandDiv.textContent = 'yt-dlp command will appear here.'; + if (filenameInput) { + filenameInput.value = ''; + filenameInput.placeholder = 'video_YYYYMMDD_HHMMSS'; + } + } + // Reset toggle states with null checks + if (manifestUrlDiv) manifestUrlDiv.classList.remove('expanded'); + if (ffmpegCommandDiv) ffmpegCommandDiv.classList.remove('expanded'); + if (ytdlpCommandDiv) ytdlpCommandDiv.classList.remove('expanded'); + if (toggleUrlBtn) toggleUrlBtn.textContent = 'Expand'; + if (toggleFffmpegBtn) toggleFffmpegBtn.textContent = 'Expand'; + if (toggleYtdlpBtn) toggleYtdlpBtn.textContent = 'Expand'; + } + + function fetchLastManifest() { + try { + chrome.runtime.sendMessage({ type: 'getLastManifest' }, (response) => { + if (chrome.runtime.lastError) { + console.error('Runtime error:', chrome.runtime.lastError.message); + updateUI(null); + return; + } + updateUI(response.data); + }); + } catch (error) { + console.error('Error sending message:', error); + updateUI(null); + } + } + + // Initial fetch + fetchLastManifest(); + + // Listen for new manifests + chrome.runtime.onMessage.addListener((message) => { + if (message.type === 'manifestDetected') { + updateUI(message.data); + } + }); + + // Add event listeners with null checks + if (refreshButton) { + refreshButton.addEventListener('click', fetchLastManifest); + } + + // Update commands on quality/format/filename change with null checks + if (qualitySelect) { + qualitySelect.addEventListener('change', () => { + if (currentManifestData) { + const ffmpegCommand = generateFffmpegCommand( + currentManifestData.cleanedUrl, + currentManifestData.type, + qualitySelect.value, + formatSelect.value + ); + const ytdlpCommand = generateYtdlpCommand( + currentManifestData.cleanedUrl, + qualitySelect.value, + formatSelect.value + ); + if (ffmpegCommandDiv) ffmpegCommandDiv.textContent = ffmpegCommand; + if (ytdlpCommandDiv) ytdlpCommandDiv.textContent = ytdlpCommand; + } + }); + } + + if (formatSelect) { + formatSelect.addEventListener('change', () => { + if (currentManifestData) { + const ffmpegCommand = generateFffmpegCommand( + currentManifestData.cleanedUrl, + currentManifestData.type, + qualitySelect.value, + formatSelect.value + ); + const ytdlpCommand = generateYtdlpCommand( + currentManifestData.cleanedUrl, + qualitySelect.value, + formatSelect.value + ); + if (ffmpegCommandDiv) ffmpegCommandDiv.textContent = ffmpegCommand; + if (ytdlpCommandDiv) ytdlpCommandDiv.textContent = ytdlpCommand; + } + }); + } + + if (filenameInput) { + filenameInput.addEventListener('change', () => { + if (currentManifestData) { + const ffmpegCommand = generateFffmpegCommand( + currentManifestData.cleanedUrl, + currentManifestData.type, + qualitySelect.value, + formatSelect.value + ); + const ytdlpCommand = generateYtdlpCommand( + currentManifestData.cleanedUrl, + qualitySelect.value, + formatSelect.value + ); + if (ffmpegCommandDiv) ffmpegCommandDiv.textContent = ffmpegCommand; + if (ytdlpCommandDiv) ytdlpCommandDiv.textContent = ytdlpCommand; + } + }); + } + + // Copy FFmpeg button with null check + if (copyFffmpegBtn) { + copyFffmpegBtn.addEventListener('click', () => { + if (ffmpegCommandDiv) { + const commandText = ffmpegCommandDiv.textContent; + navigator.clipboard.writeText(commandText).then(() => { + copyFffmpegBtn.textContent = 'Copied!'; + setTimeout(() => copyFffmpegBtn.textContent = 'Copy FFmpeg', 2000); + }).catch((err) => { + console.error('Failed to copy FFmpeg:', err); + copyFffmpegBtn.textContent = 'Copy Failed'; + }); + } + }); + } + + // Copy yt-dlp button with null check + if (copyYtdlpBtn) { + copyYtdlpBtn.addEventListener('click', () => { + if (ytdlpCommandDiv) { + const commandText = ytdlpCommandDiv.textContent; + navigator.clipboard.writeText(commandText).then(() => { + copyYtdlpBtn.textContent = 'Copied!'; + setTimeout(() => copyYtdlpBtn.textContent = 'Copy yt-dlp', 2000); + }).catch((err) => { + console.error('Failed to copy yt-dlp:', err); + copyYtdlpBtn.textContent = 'Copy Failed'; + }); + } + }); + } + + // Toggle URL visibility with null check + if (toggleUrlBtn) { + toggleUrlBtn.addEventListener('click', () => { + if (manifestUrlDiv) { + manifestUrlDiv.classList.toggle('expanded'); + toggleUrlBtn.textContent = manifestUrlDiv.classList.contains('expanded') ? 'Collapse' : 'Expand'; + } + }); + } + + // Toggle FFmpeg visibility with null check + if (toggleFffmpegBtn) { + toggleFffmpegBtn.addEventListener('click', () => { + if (ffmpegCommandDiv) { + ffmpegCommandDiv.classList.toggle('expanded'); + toggleFffmpegBtn.textContent = ffmpegCommandDiv.classList.contains('expanded') ? 'Collapse' : 'Expand'; + } + }); + } + + // Toggle yt-dlp visibility with null check + if (toggleYtdlpBtn) { + toggleYtdlpBtn.addEventListener('click', () => { + if (ytdlpCommandDiv) { + ytdlpCommandDiv.classList.toggle('expanded'); + toggleYtdlpBtn.textContent = ytdlpCommandDiv.classList.contains('expanded') ? 'Collapse' : 'Expand'; + } + }); + } +}); \ No newline at end of file