Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Название видео вместо id #5

Open
ORanGeOfficial opened this issue Sep 6, 2023 · 6 comments
Open

Название видео вместо id #5

ORanGeOfficial opened this issue Sep 6, 2023 · 6 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@ORanGeOfficial
Copy link

Возможно ли сделать так, чтобы имя скачиваемого файла состояло из названия видео, а не его id? Также было бы неплохо добавить приписку с выбранным уровнем качества.

Или подскажите, пожалуйста, что для этого нужно изменить в коде расширения? Хотя бы просто чтобы название видео было в имени файла.

@JustKappaMan
Copy link
Owner

@ORanGeOfficial приветствую.

Хотел сделать генерацию названия файла в формате ИмяВидео_Качество ещё в первой версии. Выяснилось что так просто это сделать не выйдет. Поэтому забил. У тега a есть аттрибут download, который задаёт имя файла. Но в данном случае он бесполезен. Объясняю в чём суть.

Файл отдаётся с левого адреса. Не vk.com, а vkvd89.mycdn.me например. При этом в HTTP заголовке, приходящем с сервера, задан параметр Content-Disposition, задающий имя файла -- id видео.

Насколько помню, аттрибут download у тега a игнорируется если:

  1. С сервера отдаётся параметр Content-Disposition
  2. Не соблюдается same-origin-policy (файл не лежит на vk.com в нашем случае)

В нашем случае актуально и то, и другое. Погляжу, конечно, в свободное время, может можно что-нибудь придумать :)

@JustKappaMan JustKappaMan self-assigned this Sep 6, 2023
@JustKappaMan JustKappaMan added enhancement New feature or request help wanted Extra attention is needed labels Sep 6, 2023
@alekseyvlivanov
Copy link

Сразу под вашими ссылками стоит имя ролика - возможно ли воспользоваться именно им + id + разрешение для генерации имени файла?
yt-dlp также умеет считывать название видео, сейчас специально это проверил. Возможно, можно глянуть их экстрактор (хоть там и питон).

@ORanGeOfficial
Copy link
Author

Сразу под вашими ссылками стоит имя ролика - возможно ли воспользоваться именно им + id + разрешение для генерации имени файла? yt-dlp также умеет считывать название видео, сейчас специально это проверил. Возможно, можно глянуть их экстрактор (хоть там и питон).

Получить имя то не проблема.
title=document.getElementById('mv_title');
Непонятно как обойти перезапись аттрибута download

Я пока просто для ускорения работы сделал такой небезопасный костыль, который при попытке скачать, копирует в буфер обмена название + качество. Как дизайнеру, мне можно творить такую дичь :)
aTag.onclick = function(){navigator.clipboard.writeText(title.innerHTML+'_'+quality)};

@mef0

This comment was marked as outdated.

@mef0
Copy link

mef0 commented Jun 16, 2024

@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;
  }
})();

@mef0
Copy link

mef0 commented Jun 23, 2024

Осознал что метод 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;
  }

})();

@JustKappaMan JustKappaMan removed their assignment Nov 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants