|
from pathlib import Path |
|
import subprocess |
|
from re import compile, findall, sub |
|
from time import sleep |
|
from typing import Literal |
|
from urllib.parse import quote_plus |
|
|
|
import uuid |
|
import threading |
|
|
|
from flask import Flask, render_template, request, jsonify, send_file |
|
from httpx import Client |
|
|
|
from yt_dlp import YoutubeDL |
|
|
|
|
|
def extract_youtube_id(url: str) -> list[str] | str | None: |
|
youtube_regex = r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=|shorts/)?([^&=%\?]{11})' |
|
youtube_url_format = compile(youtube_regex) |
|
urls = findall(youtube_url_format, url) |
|
if len(urls) > 0: |
|
return urls[0][-1] |
|
else: |
|
return None |
|
|
|
|
|
def download_yt_audio_ytdlp(youtube_id: str) -> Path: |
|
ydl_opts = { |
|
'format': 'bestaudio/best', |
|
'outtmpl': '%(title)s.%(ext)s', |
|
} |
|
with YoutubeDL(ydl_opts) as ydl: |
|
info_dict = ydl.extract_info(f'https://www.youtube.com/watch?v={youtube_id}', download=True) |
|
file_name = Path(ydl.prepare_filename(info_dict)) |
|
print('yt_dlp ok') |
|
return file_name |
|
|
|
|
|
def download_yt_audio_api(youtube_id: str) -> Path: |
|
encoded_youtube_url = quote_plus(f'https://www.youtube.com/watch?v={youtube_id}') |
|
api_response_url = f'https://ab.cococococ.com/ajax/download.php?copyright=0&format=webm&url={encoded_youtube_url}&api=dfcb6d76f2f6a9894gjkege8a4ab232222' |
|
|
|
with Client(verify=False, timeout=90, follow_redirects=True) as client: |
|
response = client.get(api_response_url) |
|
response.raise_for_status() |
|
data = response.json() |
|
file_name = sub(r'[^A-Za-zА-Яа-я_]', '', data['info']['title'].replace(' ', '_')) |
|
video_id = data.get('id') |
|
if not video_id: |
|
raise ValueError("ошибка на стороне апи!") |
|
|
|
progress_url = f'https://p.oceansaver.in/ajax/progress.php?id={video_id}' |
|
while True: |
|
response = client.get(progress_url) |
|
response.raise_for_status() |
|
data = response.json() |
|
download_url = data.get('download_url') |
|
if download_url: |
|
break |
|
sleep(1) |
|
|
|
response = client.get(download_url) |
|
response.raise_for_status() |
|
path = Path('./') |
|
file_name = path / f'{file_name}.webm' |
|
with open(file_name, 'wb') as f: |
|
f.write(response.content) |
|
print('===========================================') |
|
print(' API API APIAPI API API API API API API') |
|
print('===========================================') |
|
return file_name |
|
|
|
|
|
def download_yt_audio(youtube_id: str) -> Path | None: |
|
try: |
|
return download_yt_audio_ytdlp(youtube_id) |
|
except Exception as e: |
|
print(e) |
|
return download_yt_audio_api(youtube_id) |
|
|
|
|
|
def audio_to_wav(input_file: Path) -> Path | None: |
|
|
|
|
|
wav_file = f'{input_file.stem}.wav' |
|
ffmpeg_command = ["./ffmpeg", "-i", input_file, "-ac", "1", wav_file] |
|
subprocess.run(ffmpeg_command, check=True) |
|
input_file.unlink(missing_ok=True) |
|
return Path(wav_file) |
|
|
|
|
|
def wav_to_opus(wav_file: Path, del_input: bool = True) -> Path: |
|
|
|
|
|
opus_file = f'{wav_file.stem}.opus' |
|
opusenc_command = [ |
|
"./opusenc", |
|
"--bitrate", "18", |
|
"--vbr", |
|
"--speech", |
|
"--comp", "10", |
|
"--framesize", "60", |
|
"--downmix-mono", |
|
wav_file, opus_file |
|
] |
|
subprocess.run(opusenc_command, check=True) |
|
if del_input: |
|
wav_file.unlink(missing_ok=True) |
|
return Path(opus_file) |
|
|
|
|
|
def wav_to_usac(wav_file: Path, del_input: bool = True) -> Path: |
|
|
|
|
|
exhale_file = f'{wav_file.stem}.m4a' |
|
exhale_command = ["./exhale", "0", wav_file, exhale_file] |
|
subprocess.run(exhale_command, check=True) |
|
if del_input: |
|
wav_file.unlink(missing_ok=True) |
|
return Path(exhale_file) |
|
|
|
|
|
def download_and_encode(youtube_url: str, codec: Literal['opus', 'usac', 'both'] = 'both') -> list[Path]: |
|
temp_wav = audio_to_wav(download_yt_audio(extract_youtube_id(youtube_url))) |
|
if codec == 'opus': |
|
return [wav_to_opus(temp_wav)] |
|
elif codec == 'usac': |
|
return [wav_to_usac(temp_wav)] |
|
else: |
|
return [wav_to_opus(temp_wav, del_input=False), wav_to_usac(temp_wav)] |
|
|
|
|
|
app = Flask(__name__) |
|
|
|
|
|
tasks = {} |
|
|
|
@app.route('/') |
|
def index(): |
|
return render_template('index.html') |
|
|
|
@app.route('/download', methods=['POST']) |
|
def download(): |
|
youtube_url = request.form['youtube_url'] |
|
codec = request.form['codec'] |
|
task_id = str(uuid.uuid4()) |
|
|
|
def process_task(): |
|
tasks[task_id] = {'status': 'скачивание с ютуба...'} |
|
audio_file = download_yt_audio(extract_youtube_id(youtube_url)) |
|
tasks[task_id]['status'] = 'предварительная конвертация...' |
|
wav_file = audio_to_wav(audio_file) |
|
tasks[task_id]['status'] = 'сжатие...' |
|
encoded_files = download_and_encode(youtube_url, codec) |
|
tasks[task_id]['status'] = 'готово' |
|
tasks[task_id]['files'] = [str(f) for f in encoded_files] |
|
|
|
threading.Thread(target=process_task).start() |
|
return jsonify({'task_id': task_id}) |
|
|
|
@app.route('/download_status/<task_id>') |
|
def download_status(task_id): |
|
task = tasks.get(task_id) |
|
if task: |
|
return jsonify(task) |
|
else: |
|
return jsonify({'error': 'Task not found'}), 404 |
|
|
|
|
|
@app.route('/play/<path:filename>') |
|
def play(filename): |
|
return send_file(filename, as_attachment=True) |
|
|
|
|
|
if __name__ == '__main__': |
|
app.run(host='0.0.0.0', port=7860) |
|
|