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: # if not input_file or not input_file.is_file(): # return 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: # if not wav_file or not wav_file.is_file(): # raise ValueError('входной файл для кодирования не был получен!') 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: # if not wav_file or not wav_file.is_file(): # raise ValueError('входной файл для кодирования не был получен!') 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/') 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/') def play(filename): return send_file(filename, as_attachment=True) if __name__ == '__main__': app.run(host='0.0.0.0', port=7860)