Open ORanGeOfficial opened 1 year ago
@ORanGeOfficial приветствую.
Хотел сделать генерацию названия файла в формате ИмяВидео_Качество
ещё в первой версии. Выяснилось что так просто это сделать не выйдет. Поэтому забил. У тега a
есть аттрибут download
, который задаёт имя файла. Но в данном случае он бесполезен. Объясняю в чём суть.
Файл отдаётся с левого адреса. Не vk.com
, а vkvd89.mycdn.me
например. При этом в HTTP заголовке, приходящем с сервера, задан параметр Content-Disposition
, задающий имя файла -- id видео.
Насколько помню, аттрибут download
у тега a
игнорируется если:
Content-Disposition
В нашем случае актуально и то, и другое. Погляжу, конечно, в свободное время, может можно что-нибудь придумать :)
Сразу под вашими ссылками стоит имя ролика - возможно ли воспользоваться именно им + id + разрешение для генерации имени файла? yt-dlp также умеет считывать название видео, сейчас специально это проверил. Возможно, можно глянуть их экстрактор (хоть там и питон).
Сразу под вашими ссылками стоит имя ролика - возможно ли воспользоваться именно им + id + разрешение для генерации имени файла? yt-dlp также умеет считывать название видео, сейчас специально это проверил. Возможно, можно глянуть их экстрактор (хоть там и питон).
Получить имя то не проблема.
title=document.getElementById('mv_title');
Непонятно как обойти перезапись аттрибута download
Я пока просто для ускорения работы сделал такой небезопасный костыль, который при попытке скачать, копирует в буфер обмена название + качество. Как дизайнеру, мне можно творить такую дичь :)
aTag.onclick = function(){navigator.clipboard.writeText(title.innerHTML+'_'+quality)};
@JustKappaMan Попробуйте :). У меня этой правкой заголовок подсовывается
Код создаст панель скачивания, где каждый элемент будет вести на функцию скачивания файла через fetch(). При клике на элемент панели будет осуществляться попытка скачать видео с использованием Blob и задавать ему имя файла в соответствии с указанным заголовком видео.
function createDownloadPanel() {
const supportedWindow = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
const videoSources = {
'144p': supportedWindow.mvcur.player.vars.url144,
'240p': supportedWindow.mvcur.player.vars.url240,
'360p': supportedWindow.mvcur.player.vars.url360,
'480p': supportedWindow.mvcur.player.vars.url480,
'720p': supportedWindow.mvcur.player.vars.url720,
'1080p': supportedWindow.mvcur.player.vars.url1080,
'1440p': supportedWindow.mvcur.player.vars.url1440,
'2160p': supportedWindow.mvcur.player.vars.url2160,
};
const videoTitle = document.querySelector('.mv_title') ? document.querySelector('.mv_title').innerText : 'video';
const fileName = videoTitle.replace(/[|&;$%@"<>()+©,]/g, '').replace(/\s+/g, ' ');
const label = document.createElement('span');
label.innerText = 'Скачать:';
label.style.marginRight = '2px';
const panel = document.createElement('div');
panel.id = 'vkVideoDownloaderPanel';
panel.appendChild(label);
for (const [quality, url] of Object.entries(videoSources)) {
if (typeof url !== 'undefined') {
const aTag = document.createElement('a');
aTag.href = '#';
aTag.innerText = quality;
aTag.style.margin = '0 2px';
// Добавляем обработчик клика
aTag.addEventListener('click', function(e) {
e.preventDefault(); // Предотвращаем переход по ссылке
fetch(url).then(response => {
if (response.ok) return response.blob();
throw new Error('Network response was not ok.');
}).then(blob => {
// Создаем временную ссылку для скачивания
const tempUrl = window.URL.createObjectURL(blob);
const tempLink = document.createElement('a');
tempLink.href = tempUrl;
tempLink.download = fileName + '.mp4'; // Задаем имя файла
// tempLink.download = fileName + '_' + quality + '.mp4 // если нужно добавлять качество в конец **
document.body.appendChild(tempLink); // Добавляем ссылку в DOM
tempLink.click(); // Программно кликаем по ссылке для скачивания
document.body.removeChild(tempLink); // Удаляем ссылку из DOM
window.URL.revokeObjectURL(tempUrl); // Освобождаем URL
}).catch(error => console.error('Fetch error:', error));
});
panel.appendChild(aTag);
}
}
return panel;
}
Добавил в pull request, на примере с monkey
@JustKappaMan Название видео вместо id для версии 1.1.9 (monkeys / srcipts / desktop.js)
// ==UserScript==
// @name VK-Video-Downloader-desktop
// @namespace https://github.com/JustKappaMan
// @version 1.1.9 **(+ filename)**
// @description Скачивайте видео с сайта «ВКонтакте» в желаемом качестве
// @author Kirill "JustKappaMan" Volozhanin
// @match https://vk.com/*
// @run-at document-idle
// @icon https://raw.githubusercontent.com/JustKappaMan/VK-Video-Downloader/main/monkeys/icons/icon128.png
// @homepageURL https://github.com/JustKappaMan/VK-Video-Downloader
// @downloadURL https://raw.githubusercontent.com/JustKappaMan/VK-Video-Downloader/main/monkeys/scripts/desktop.js
// @updateURL https://raw.githubusercontent.com/JustKappaMan/VK-Video-Downloader/main/monkeys/scripts/desktop.js
// @grant none
// ==/UserScript==
(function () {
'use strict';
let lastUrl = location.href;
let checkerHasBeenCalled = false;
let showPanelHasBeenCalled = false;
new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
checkerHasBeenCalled = false;
showPanelHasBeenCalled = false;
const old_panel = document.querySelector('#vkVideoDownloaderPanel');
if (old_panel !== null) {
old_panel.remove();
}
}
if (
(/z=(?:video|clip)/.test(location.search) || /^\/(?:video|clip)[^\/s]+$/.test(location.pathname)) &&
!checkerHasBeenCalled
) {
checkerHasBeenCalled = true;
const checker = setInterval(() => {
if (!showPanelHasBeenCalled && document.querySelector('#video_player video')) {
showPanelHasBeenCalled = true;
clearInterval(checker);
document.body.appendChild(createDownloadPanel());
} else if (!showPanelHasBeenCalled && document.querySelector('#video_player iframe')) {
showPanelHasBeenCalled = true;
clearInterval(checker);
document.body.appendChild(createErrorPanel());
}
}, 500);
}
}).observe(document.body, { subtree: true, childList: true });
function createDownloadPanel() {
const supportedWindow = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
const videoSources = {
'144p': supportedWindow.mvcur.player.vars.url144,
'240p': supportedWindow.mvcur.player.vars.url240,
'360p': supportedWindow.mvcur.player.vars.url360,
'480p': supportedWindow.mvcur.player.vars.url480,
'720p': supportedWindow.mvcur.player.vars.url720,
'1080p': supportedWindow.mvcur.player.vars.url1080,
'1440p': supportedWindow.mvcur.player.vars.url1440,
'2160p': supportedWindow.mvcur.player.vars.url2160,
};
const label = document.createElement('span');
label.innerText = 'Скачать:';
label.style.marginRight = '2px';
const panel = document.createElement('div');
panel.id = 'vkVideoDownloaderPanel';
panel.style.position = 'fixed';
panel.style.left = '16px';
panel.style.bottom = '16px';
panel.style.zIndex = '2147483647';
panel.style.padding = '4px';
panel.style.color = '#fff';
panel.style.backgroundColor = '#07f';
panel.style.border = '1px solid #fff';
panel.appendChild(label);
for (const [quality, url] of Object.entries(videoSources)) {
if (typeof url !== 'undefined') {
const aTag = document.createElement('a');
aTag.href = '#';
aTag.innerText = quality;
aTag.style.margin = '0 2px';
aTag.style.color = '#fff';
// Название видео из мета тега + удаление лишних символов
const videoTitleMeta = document.querySelector('meta[property="og:title"]');
const videoTitle = videoTitleMeta ? videoTitleMeta.getAttribute('content') : 'video';
const fileName = videoTitle.replace(/[|&;$%@"<>()+©,]/g, '').replace(/\s+/g, ' ');
aTag.addEventListener('click', function(e) {
e.preventDefault();
fetch(url).then(response => {
if (response.ok) return response.blob();
throw new Error('Network response was not ok.');
}).then(blob => {
const tempUrl = window.URL.createObjectURL(blob);
const tempLink = document.createElement('a');
tempLink.href = tempUrl;
tempLink.download = fileName + '_' + quality + '.mp4'; // Добавляем имя файла и качество
//tempLink.download = fileName + '.mp4'; // Добавляем имя файла
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
window.URL.revokeObjectURL(tempUrl);
}).catch(error => console.error('Fetch error:', error));
});
panel.appendChild(aTag);
}
}
return panel;
}
function createErrorPanel() {
const label = document.createElement('span');
label.innerText = 'Видео со стороннего сайта. Воспользуйтесь инструментами для скачивания с него.';
const panel = document.createElement('div');
panel.id = 'vkVideoDownloaderPanel';
panel.style.position = 'fixed';
panel.style.left = '16px';
panel.style.bottom = '16px';
panel.style.zIndex = '2147483647';
panel.style.padding = '4px';
panel.style.color = '#fff';
panel.style.backgroundColor = '#07f';
panel.style.border = '1px solid #fff';
panel.appendChild(label);
return panel;
}
})();
Осознал что метод blob нормально работает только для видео небольшого размера.
Нашел способ быстро подсовывать название видео вместо id только при запуске сервера для промежуточного скачивания файла.
Если кому интересно, рабочий пример на python:
Сервер
from flask import Flask, request, Response
import requests
from urllib.parse import unquote
import logging
import re
import base64
app = Flask(__name__)
logging.basicConfig(level=logging.DEBUG)
# Функция для очистки имени файла от специальных символов
def clean_filename(filename):
filename = re.sub(r'[|&;$%@"<>()+©,]', '', filename) # Удаление специальных символов
filename = re.sub(r's+', ' ', filename) # Замена нескольких пробелов на один
return filename
@app.route('/download')
def download():
file_url = request.args.get('url')
filename = request.args.get('filename')
if not file_url or not filename:
return 'Missing url or filename parameter', 400
try:
# Декодируем URL
file_url = unquote(file_url)
logging.debug(f'Decoded URL: {file_url}')
# Очистка имени файла
filename = clean_filename(filename)
# Закодируем имя файла в base64
filename_base64 = base64.b64encode(filename.encode('utf-8')).decode('utf-8')
# Добавим заголовки
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(file_url, headers=headers, stream=True)
logging.debug(f'Response status code: {response.status_code}')
response.raise_for_status()
# Создаем генератор для стриминга файла
def generate():
for chunk in response.iter_content(chunk_size=8192):
if chunk:
yield chunk
content_disposition = f'attachment; filename="=?UTF-8?B?{filename_base64}?="'
return Response(generate(), headers={
'Content-Disposition': content_disposition,
'Content-Type': response.headers['Content-Type'],
})
except requests.RequestException as e:
logging.error(f'Error downloading the file: {e}')
return f'Error downloading the file: {e}', 500
if __name__ == '__main__':
app.run(port=3000)
И monkey
// ==UserScript==
// @name VK-Video-Downloader-desktop
// @namespace https://github.com/JustKappaMan
// @version 1.1.9 **(+ filename)**
// @description Скачивайте видео с сайта «ВКонтакте» в желаемом качестве
// @author Kirill "JustKappaMan" Volozhanin
// @match https://vk.com/*
// @run-at document-idle
// @icon https://raw.githubusercontent.com/JustKappaMan/VK-Video-Downloader/main/monkeys/icons/icon128.png
// @homepageURL https://github.com/JustKappaMan/VK-Video-Downloader
// @downloadURL https://raw.githubusercontent.com/JustKappaMan/VK-Video-Downloader/main/monkeys/scripts/desktop.js
// @updateURL https://raw.githubusercontent.com/JustKappaMan/VK-Video-Downloader/main/monkeys/scripts/desktop.js
// @grant none
// ==/UserScript==
(function () {
'use strict';
let lastUrl = location.href;
let checkerHasBeenCalled = false;
let showPanelHasBeenCalled = false;
new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
checkerHasBeenCalled = false;
showPanelHasBeenCalled = false;
const oldPanel = document.querySelector('#vkVideoDownloaderPanel');
if (oldPanel !== null) {
oldPanel.remove();
}
}
if (
(/z=(?:video|clip)/.test(location.search) || /^\/(?:video|clip)[^\/s]+$/.test(location.pathname)) &&
!checkerHasBeenCalled
) {
checkerHasBeenCalled = true;
const checker = setInterval(() => {
if (!showPanelHasBeenCalled && document.querySelector('#video_player video')) {
showPanelHasBeenCalled = true;
clearInterval(checker);
document.body.appendChild(createDownloadPanel());
} else if (!showPanelHasBeenCalled && document.querySelector('#video_player iframe')) {
showPanelHasBeenCalled = true;
clearInterval(checker);
document.body.appendChild(createErrorPanel());
}
}, 500);
}
}).observe(document.body, { subtree: true, childList: true });
function createDownloadPanel() {
const supportedWindow = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
const videoSources = {
'144p': supportedWindow.mvcur.player.vars.url144,
'240p': supportedWindow.mvcur.player.vars.url240,
'360p': supportedWindow.mvcur.player.vars.url360,
'480p': supportedWindow.mvcur.player.vars.url480,
'720p': supportedWindow.mvcur.player.vars.url720,
'1080p': supportedWindow.mvcur.player.vars.url1080,
'1440p': supportedWindow.mvcur.player.vars.url1440,
'2160p': supportedWindow.mvcur.player.vars.url2160,
};
const label = document.createElement('span');
label.innerText = 'Скачать:';
label.style.marginRight = '2px';
const panel = document.createElement('div');
panel.id = 'vkVideoDownloaderPanel';
panel.style.position = 'fixed';
panel.style.left = '16px';
panel.style.bottom = '16px';
panel.style.zIndex = '2147483647';
panel.style.padding = '4px';
panel.style.color = '#fff';
panel.style.backgroundColor = '#07f';
panel.style.border = '1px solid #fff';
panel.appendChild(label);
for (const [quality, url] of Object.entries(videoSources)) {
if (url) {
const aTag = document.createElement('a');
aTag.href = '#';
aTag.innerText = quality;
aTag.style.margin = '0 2px';
aTag.style.color = '#fff';
aTag.addEventListener('click', function (event) {
event.preventDefault();
const videoTitle = getVideoTitle() + '-' + quality + '.mp4';
fetchAndDownload(url, videoTitle);
});
panel.appendChild(aTag);
}
}
return panel;
}
function createErrorPanel() {
const label = document.createElement('span');
label.innerText = 'Видео со стороннего сайта. Воспользуйтесь инструментами для скачивания с него.';
const panel = document.createElement('div');
panel.id = 'vkVideoDownloaderPanel';
panel.style.position = 'fixed';
panel.style.left = '16px';
panel.style.bottom = '16px';
panel.style.zIndex = '2147483647';
panel.style.padding = '4px';
panel.style.color = '#fff';
panel.style.backgroundColor = '#07f';
panel.style.border = '1px солидный #fff';
panel.appendChild(label);
return panel;
}
function getVideoTitle() {
const videoTitleDiv = document.querySelector('#mv_min_title');
if (videoTitleDiv) {
return videoTitleDiv.innerText;
} else {
const videoTitleMeta = document.querySelector('meta[property="og:title"]'); // после обновления страницы название видео можно взять из og:tittle
return videoTitleMeta ? videoTitleMeta.getAttribute('content') : null;
}
}
function fetchAndDownload(url, filename) {
const serverUrl = 'http://localhost:3000/download?url=' + encodeURIComponent(url) + '&filename=' + encodeURIComponent(filename);
// Создание временного элемента <a>
const a = document.createElement('a');
a.href = serverUrl;
document.body.appendChild(a);
// Триггерим событие click для открытия диалогового окна сохранения
a.click();
// Удаление временного элемента
setTimeout(() => {
document.body.removeChild(a);
}, 100);
}
// не пригодилось
function getVideoId() {
const metaTag = document.querySelector('meta[property="og:image"]');
if (metaTag) {
const imgUrl = metaTag.getAttribute('content');
const idMatched = imgUrl.match(/id=(\d+)/);
return idMatched ? idMatched[1] : null;
}
return null;
}
})();
Возможно ли сделать так, чтобы имя скачиваемого файла состояло из названия видео, а не его id? Также было бы неплохо добавить приписку с выбранным уровнем качества.
Или подскажите, пожалуйста, что для этого нужно изменить в коде расширения? Хотя бы просто чтобы название видео было в имени файла.