|
14 | 14 | import os
|
15 | 15 | import re
|
16 | 16 | import shutil
|
17 |
| -import socket |
18 | 17 | import sys
|
19 | 18 | import tempfile
|
20 | 19 | import threading
|
21 | 20 | import time
|
22 |
| -import warnings |
23 | 21 | import xml.dom.minidom
|
24 | 22 | from typing import AnyStr, List, Match
|
25 | 23 |
|
26 | 24 | import aiohttp
|
27 | 25 | import requests
|
28 |
| -import ffmpeg |
29 |
| -import gevent.monkey |
30 |
| -gevent.monkey.patch_all(ssl=False,thread=False) |
31 | 26 | from aiohttp_socks import ProxyConnector
|
32 | 27 | from bs4 import BeautifulSoup
|
33 | 28 | from mutagen.mp4 import MP4, MP4StreamInfoError
|
34 | 29 | from requests.adapters import HTTPAdapter
|
35 | 30 | from requests.utils import add_dict_to_cookiejar
|
36 | 31 | from urllib3.util import Retry
|
37 |
| -from tqdm import tqdm, TqdmWarning |
38 | 32 |
|
| 33 | +from ffmpeg_dl import FfmpegDL, FfmpegDLException |
39 | 34 |
|
40 | 35 | __version__ = "1.16"
|
41 | 36 | __author__ = "Alex Aplin"
|
@@ -1250,80 +1245,24 @@ def download_video_part(session: requests.Session, start, end, filename: AnyStr,
|
1250 | 1245 | update_multithread_progress(len(block))
|
1251 | 1246 |
|
1252 | 1247 |
|
1253 |
| -def capture_ffmpeg_progress(filename, sock, handler): |
1254 |
| - """Capture and send ffmpeg events to the provided handler.""" |
1255 |
| - |
1256 |
| - connection, client_address = sock.accept() |
1257 |
| - data = b"" |
1258 |
| - try: |
1259 |
| - while True: |
1260 |
| - more_data = connection.recv(16) |
1261 |
| - if not more_data: |
1262 |
| - break |
1263 |
| - data += more_data |
1264 |
| - lines = data.split(b"\n") |
1265 |
| - for line in lines[:-1]: |
1266 |
| - line = line.decode() |
1267 |
| - parts = line.split("=") |
1268 |
| - key = parts[0] if len(parts) > 0 else None |
1269 |
| - value = parts[1] if len(parts) > 1 else None |
1270 |
| - handler(key, value) |
1271 |
| - data = lines[-1] |
1272 |
| - finally: |
1273 |
| - connection.close() |
1274 |
| - |
1275 |
| - |
1276 |
| -@contextlib.contextmanager |
1277 |
| -def watch_ffmpeg_progress(handler): |
1278 |
| - """Spawn a socket to capture ffmpeg events.""" |
1279 |
| - |
1280 |
| - with get_temp_dir() as temp_dir: |
1281 |
| - socket_filename = os.path.join(temp_dir, "sock") |
1282 |
| - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) |
1283 |
| - with contextlib.closing(sock): |
1284 |
| - sock.bind(socket_filename) |
1285 |
| - sock.listen(1) |
1286 |
| - child = gevent.spawn(capture_ffmpeg_progress, socket_filename, sock, handler) |
1287 |
| - try: |
1288 |
| - yield socket_filename |
1289 |
| - except: |
1290 |
| - gevent.kill(child) |
1291 |
| - raise |
1292 |
| - |
1293 |
| - |
1294 |
| -@contextlib.contextmanager |
1295 |
| -def show_ffmpeg_progress(duration: float): |
1296 |
| - """Render a tqdm progress bar relative to a specified stream duration.""" |
1297 |
| - |
1298 |
| - warnings.filterwarnings("ignore", category=TqdmWarning) |
1299 |
| - with tqdm(total=round(duration, 2)) as progress_bar: |
1300 |
| - def handler(key, value): |
1301 |
| - if key == "out_time_ms": |
1302 |
| - time = round(float(value) / 1000000., 2) |
1303 |
| - progress_bar.update(time - progress_bar.n) |
1304 |
| - elif key == "progress" and value == "end": |
1305 |
| - progress_bar.update(progress_bar.total - progress_bar.n) |
1306 |
| - with watch_ffmpeg_progress(handler) as socket_filename: |
1307 |
| - yield socket_filename |
1308 |
| - |
1309 |
| - |
1310 |
| -def perform_ffmpeg_dl(filename: AnyStr, duration: float, streams: List): |
| 1248 | +def perform_ffmpeg_dl(video_id: AnyStr, filename: AnyStr, duration: float, streams: List): |
1311 | 1249 | """Send video and/or audio stream to ffmpeg for download."""
|
1312 | 1250 |
|
1313 |
| - inputs = [] |
1314 | 1251 | try:
|
1315 |
| - with show_ffmpeg_progress(duration) as socket_filename: |
1316 |
| - for stream in streams: |
1317 |
| - input = ffmpeg.input(stream, protocol_whitelist="https,http,tls,tcp,file,crypto", allowed_extensions="ALL") |
1318 |
| - inputs.append(input) |
1319 |
| - output = ffmpeg.output(*inputs, filename, vcodec="copy", acodec="copy") |
1320 |
| - output = output.global_args("-progress", "unix://{}".format(socket_filename), "-y") |
1321 |
| - output.run(capture_stdout=True, capture_stderr=True) |
1322 |
| - return True |
1323 |
| - except ffmpeg.Error as error: |
1324 |
| - stderr = error.stderr.decode("utf8") |
1325 |
| - actual_error = stderr.splitlines()[-1] |
1326 |
| - raise FormatNotAvailableException(f"ffmpeg exited with an error: {actual_error}") |
| 1252 | + video_download = FfmpegDL(streams=streams, |
| 1253 | + input_kwargs={ |
| 1254 | + "protocol_whitelist": "https,http,tls,tcp,file,crypto", |
| 1255 | + "allowed_extensions": "ALL", |
| 1256 | + }, |
| 1257 | + output_path=filename, |
| 1258 | + output_kwargs={ |
| 1259 | + "vcodec": "copy", |
| 1260 | + "acodec": "copy", |
| 1261 | + }) |
| 1262 | + video_download.convert(name=video_id, duration=duration) |
| 1263 | + return True |
| 1264 | + except FfmpegDLException as error: |
| 1265 | + raise FormatNotAvailableException(f"ffmpeg failed to download the video or audio stream with the following error: \"{error}\"") |
1327 | 1266 | except Exception:
|
1328 | 1267 | raise FormatNotAvailableException("Failed to download video or audio stream")
|
1329 | 1268 |
|
@@ -1366,7 +1305,7 @@ def download_video_media(session: requests.Session, filename: AnyStr, template_p
|
1366 | 1305 | generic_dl_request(session, key_url, key_path, binary=True)
|
1367 | 1306 | rewrite_file(m3u8_path, key_url, key_path)
|
1368 | 1307 | m3u8_streams.append(m3u8_path)
|
1369 |
| - continue_code = perform_ffmpeg_dl(filename, float(template_params["duration"]), m3u8_streams) |
| 1308 | + continue_code = perform_ffmpeg_dl(template_params["id"], filename, float(template_params["duration"]), m3u8_streams) |
1370 | 1309 | os.rename(filename, complete_filename)
|
1371 | 1310 | return continue_code
|
1372 | 1311 |
|
|
0 commit comments