diff --git a/README.md b/README.md
index 60704b6..b328964 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,16 @@ CLI to download videos from https://xvideos.com
+
+
+## Features
+
+-[X] Download a single video (requires the URL of the video playback page)
+-[X] Download all videos in the favorites (requires the URL of the favorite page)
+-[X] Download all videos uploaded by the user (requires the URL of the user's homepage)
+-[X] Download all videos published by the channel (requires the URL of the channel homepage)
+-[X] Segmented high-speed download, breakpoint download, progress and status display
+
## Usage
- Install xvideos-dl
@@ -30,20 +40,29 @@ pip install -U xvideos-dl
xvideos-dl --help
```
-- Download video
+- Download single / favorites / uploaded / published videos in one command:
```bash
-xvideos-dl https://www.xvideos.com/video37177493/asian_webcam_2_camsex4u.life
+xvideos-dl https://www.xvideos.com/video37177493/asian_webcam_2_camsex4u.life https://www.xvideos.com/favorite/71879935/_ https://www.xvideos.com/profiles/mypornstation https://www.xvideos.com/channels/av69tv
```
## Release History
+### 1.1.0
+
+New Features:
+
+- Download all videos uploaded by users.
+- Download all videos posted by the channel.
+- Download single, playlist, user uploaded and channel posted videos in one command.
+- Optimize download status display.
+
### 1.0.1
-New features:
+New Features:
-- Download videos from playlist
-- Show download speed
+- Download videos from playlist.
+- Show download speed.
### 1.0.0
diff --git a/README_CN.md b/README_CN.md
new file mode 100644
index 0000000..8be2bd5
--- /dev/null
+++ b/README_CN.md
@@ -0,0 +1,73 @@
+# xvideos-dl
+
+
+
+[![Build status](https://github.com/lonsty/xvideos-dl/workflows/build/badge.svg?branch=master&event=push)](https://github.com/lonsty/xvideos-dl/actions?query=workflow%3Abuild)
+[![Python Version](https://img.shields.io/pypi/pyversions/xvideos-dl.svg)](https://pypi.org/project/xvideos-dl/)
+[![Dependencies Status](https://img.shields.io/badge/dependencies-up%20to%20date-brightgreen.svg)](https://github.com/lonsty/xvideos-dl/pulls?utf8=%E2%9C%93&q=is%3Apr%20author%3Aapp%2Fdependabot)
+
+[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
+[![Security: bandit](https://img.shields.io/badge/security-bandit-green.svg)](https://github.com/PyCQA/bandit)
+[![Pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/lonsty/xvideos-dl/blob/master/.pre-commit-config.yaml)
+[![Semantic Versions](https://img.shields.io/badge/%F0%9F%9A%80-semantic%20versions-informational.svg)](https://github.com/lonsty/xvideos-dl/releases)
+[![License](https://img.shields.io/github/license/lonsty/xvideos-dl)](https://github.com/lonsty/xvideos-dl/blob/master/LICENSE)
+
+https://xvideos.com H 片命令行下载工具
+
+
+
+## 工具特点
+
+- [X] 下载单个视频(需视频播放页的 URL)
+- [X] 下载收藏夹中的所有视频(需收藏夹页的 URL)
+- [X] 下载用户上传的所有视频(需用户首页的 URL)
+- [X] 下载频道发布的所有视频(需频道首页的 URL)
+- [X] 分段高速下载,断点下载,进度与状态显示
+
+## 使用说明
+
+### 安装工具 xvideos-dl
+
+```bash
+pip install -U xvideos-dl
+```
+
+### 获取工具使用帮助
+
+```bash
+xvideos-dl --help
+```
+
+### 下载视频
+
+*注意:首此使用时,程序会出现提示语要求输入 Cookie,用个人账号登录 https://xvideos.com 获取 Cookie 粘贴到终端,即可继续运行。Cookie 可以长期使用。*
+
+只需一行命令,下载单个 / 收藏夹 / 用户上传 / 频道发布 的所有视频
+
+```bash
+xvideos-dl https://www.xvideos.com/video37177493/asian_webcam_2_camsex4u.life https://www.xvideos.com/favorite/71879935/_ https://www.xvideos.com/profiles/mypornstation https://www.xvideos.com/channels/av69tv
+```
+
+![示例](demo_1.PNG)
+
+## Release History
+
+### 1.1.0
+
+New Features:
+
+- Download all videos uploaded by users.
+- Download all videos posted by the channel.
+- Download single, playlist, user uploaded and channel posted videos in one command.
+- Optimize download status display.
+
+### 1.0.1
+
+New Features:
+
+- Download all videos in playlists.
+- Show download speed.
+
+### 1.0.0
+
+Initial release on PyPY.
diff --git a/demo_1.PNG b/demo_1.PNG
new file mode 100644
index 0000000..1ecce42
Binary files /dev/null and b/demo_1.PNG differ
diff --git a/poetry.lock b/poetry.lock
index db262c5..0d1d003 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -56,6 +56,21 @@ PyYAML = ">=5.3.1"
six = ">=1.10.0"
stevedore = ">=1.20.0"
+[[package]]
+name = "beautifulsoup4"
+version = "4.9.3"
+description = "Screen-scraping library"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""}
+
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
[[package]]
name = "black"
version = "20.8b1"
@@ -437,7 +452,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm
[[package]]
name = "pyupgrade"
-version = "2.10.0"
+version = "2.10.1"
description = "A tool to automatically upgrade syntax for newer versions."
category = "dev"
optional = false
@@ -543,6 +558,14 @@ category = "dev"
optional = false
python-versions = "*"
+[[package]]
+name = "soupsieve"
+version = "2.2"
+description = "A modern CSS selector implementation for Beautiful Soup."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
[[package]]
name = "stevedore"
version = "3.3.0"
@@ -608,20 +631,20 @@ python-versions = "*"
[[package]]
name = "urllib3"
-version = "1.26.3"
+version = "1.26.4"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
-brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+brotli = ["brotlipy (>=0.6.0)"]
[[package]]
name = "virtualenv"
-version = "20.4.2"
+version = "20.4.3"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
@@ -661,7 +684,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
-content-hash = "cd5784c574938e4efe848f1add7f9a3d62e0eb7854ed8a5b6ef9d22d9d33de82"
+content-hash = "64bef34a8fd6a1622bd647b96e3796db239fcb4e9042afe983e541a6580b2735"
[metadata.files]
appdirs = [
@@ -684,6 +707,11 @@ bandit = [
{file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"},
{file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"},
]
+beautifulsoup4 = [
+ {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"},
+ {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"},
+ {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"},
+]
black = [
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
]
@@ -857,8 +885,8 @@ pytest = [
{file = "pytest-6.2.2.tar.gz", hash = "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9"},
]
pyupgrade = [
- {file = "pyupgrade-2.10.0-py2.py3-none-any.whl", hash = "sha256:b26a00db6e2d745fe5a949e1fd02c5286c3999edaf804f746c69d559c8f8b365"},
- {file = "pyupgrade-2.10.0.tar.gz", hash = "sha256:601427033f280d50b5b102fed1013b96f91244777772114aeb7e191762cd6050"},
+ {file = "pyupgrade-2.10.1-py2.py3-none-any.whl", hash = "sha256:3c9902865055af0211a312180fbbf7858bbd6cc51e414c0085b8b759b5bab0ed"},
+ {file = "pyupgrade-2.10.1.tar.gz", hash = "sha256:6ecf88976084de9bac0041fb74b0319e1866b70fd110189937563e19186aa48c"},
]
pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
@@ -962,6 +990,10 @@ snowballstemmer = [
{file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"},
{file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"},
]
+soupsieve = [
+ {file = "soupsieve-2.2-py3-none-any.whl", hash = "sha256:d3a5ea5b350423f47d07639f74475afedad48cf41c0ad7a82ca13a3928af34f6"},
+ {file = "soupsieve-2.2.tar.gz", hash = "sha256:407fa1e8eb3458d1b5614df51d9651a1180ea5fedf07feb46e45d7e25e6d6cdd"},
+]
stevedore = [
{file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"},
{file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"},
@@ -1016,12 +1048,12 @@ typing-extensions = [
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
]
urllib3 = [
- {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"},
- {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"},
+ {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"},
+ {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"},
]
virtualenv = [
- {file = "virtualenv-20.4.2-py2.py3-none-any.whl", hash = "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"},
- {file = "virtualenv-20.4.2.tar.gz", hash = "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d"},
+ {file = "virtualenv-20.4.3-py2.py3-none-any.whl", hash = "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c"},
+ {file = "virtualenv-20.4.3.tar.gz", hash = "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107"},
]
wrapt = [
{file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"},
diff --git a/pyproject.toml b/pyproject.toml
index a1f5e94..9ffb61b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "xvideos-dl"
-version = "1.0.1"
+version = "1.1.0"
description = "CLI to download videos from https://xvideos.com"
readme = "README.md"
authors = [
@@ -36,6 +36,7 @@ python = "^3.7"
importlib_metadata = {version = "^1.6.0", python = "<3.8"}
typer = {extras = ["all"], version = "^0.3.2"}
rich = "^9.8.2"
+beautifulsoup4 = "^4.9.0"
cursor = "^1.3.4"
requests = "^2.25.0"
diff --git a/xvideos_dl/__main__.py b/xvideos_dl/__main__.py
index 8de4d22..6a264a0 100644
--- a/xvideos_dl/__main__.py
+++ b/xvideos_dl/__main__.py
@@ -7,7 +7,16 @@
from cursor import HiddenCursor
from rich.console import Console
from xvideos_dl import __version__
-from xvideos_dl.xvideos_dl import download, get_videos_by_playlist_id, get_videos_from_play_page, parse_playlist_id
+from xvideos_dl.xvideos_dl import (
+ Process,
+ download,
+ get_videos_by_playlist_id,
+ get_videos_from_play_page,
+ get_videos_from_user_page,
+ parse_playlist_id,
+)
+
+from . import constant as c
app = typer.Typer(
name="xvideos-dl",
@@ -24,18 +33,19 @@ def version_callback(value: bool):
raise typer.Exit()
-@app.command(name="")
+@app.command(name="CLI to download videos from https://xvideos.com")
def main(
- url: List[str] = typer.Argument(..., help="URL of the video web page."),
- playlist: bool = typer.Option(False, "-p", "--playlist", help="Download videos from playlist web page."),
+ urls: List[str] = typer.Argument(..., help="URL of the video web page."),
dest: str = typer.Option(
"./xvideos",
"-d",
"--destination",
help="Destination to save the downloaded videos.",
),
+ max: int = typer.Option(None, "-n", "--maximum", help="Maximum videos to download."),
+ reversed: bool = typer.Option(False, "-r", "--reversed", help="Download videos in reverse order."),
low: bool = typer.Option(False, "-l", "--low-definition", help="Download low definition videos."),
- overwrite: bool = typer.Option(False, "-O", "--overwrite", help="Overwrite the exist video files."),
+ overwrite: bool = typer.Option(False, "-o", "--overwrite", help="Overwrite the exist video files."),
version: bool = typer.Option(
None,
"-v",
@@ -46,19 +56,35 @@ def main(
),
):
"""CLI to download videos from https://xvideos.com"""
- videos = []
- if playlist:
- pids = [parse_playlist_id(u) for u in url]
- for pid in pids:
- vs = get_videos_by_playlist_id(pid)
- videos.extend(vs)
- else:
- for page_url in url:
- videos.append(get_videos_from_play_page(page_url))
+ videos_to_download = []
+ for url in urls:
+ if "/profiles/" in url:
+ videos = []
+ videos = get_videos_from_user_page(url, "0", c.USER_UPLOAD_API, videos)
+ videos_to_download.extend(videos)
+ elif "/channels/" in url:
+ videos = []
+ videos = get_videos_from_user_page(url, "0", c.CHANNEL_API, videos)
+ videos_to_download.extend(videos)
+ elif "/favorite/" in url:
+ pid = parse_playlist_id(url)
+ videos = get_videos_by_playlist_id(pid)
+ videos_to_download.extend(videos)
+ else:
+ video = get_videos_from_play_page(url)
+ videos_to_download.append(video)
+
+ if reversed:
+ videos_to_download = videos_to_download[::-1]
+ if max:
+ videos_to_download = videos_to_download[:max]
- for video in videos:
+ total = len(videos_to_download)
+ for idx, video in enumerate(videos_to_download):
try:
with HiddenCursor():
+ process = Process(idx + 1, total)
+ console.print(f"Downloading: [cyan]{process.status()}[/]")
download(video, dest, low, overwrite)
except Exception as e:
console.print(f"[red]{e}[/]")
diff --git a/xvideos_dl/constant.py b/xvideos_dl/constant.py
index f97662c..d5dccee 100644
--- a/xvideos_dl/constant.py
+++ b/xvideos_dl/constant.py
@@ -1,7 +1,40 @@
APP_NAME = "xvideos-dl"
FAVORITE_API = "https://www.xvideos.com/api/playlists/alpha"
+USER_UPLOAD_API = "https://www.xvideos.com/profiles/{u}/activity/{aid}"
PLAYLIST_API = "https://www.xvideos.com/api/playlists/list/{pid}"
+CHANNEL_API = "https://www.xvideos.com/channels/{u}/activity/straight/{aid}"
VIDEO_API = "https://www.xvideos.com/video-download/{vid}/"
-TIMEOUT = 10 # seconds
+TIMEOUT = 15 # seconds
FRAGMENT_SIZE = 1024 ** 2 * 16 # 16MB
CHUNK_SIZE = 1024 * 4 # 4KB
+
+SMILE_EMOJIS = [
+ "😁",
+ "😀",
+ "😂",
+ "🤣",
+ "😃",
+ "😄",
+ "😅",
+ "😆",
+ "😉",
+ "😊",
+ "😋",
+ "😎",
+ "😍",
+ "😘",
+ "😗",
+ "😙",
+ "😚",
+ "🙂",
+ "🤗",
+ "🤔",
+ "😐",
+ "😑",
+ "😏",
+ "😛",
+ "😜",
+ "😝",
+ "🤤",
+ # "☯",
+]
diff --git a/xvideos_dl/xvideos_dl.py b/xvideos_dl/xvideos_dl.py
index 7d78f33..5abc823 100644
--- a/xvideos_dl/xvideos_dl.py
+++ b/xvideos_dl/xvideos_dl.py
@@ -1,12 +1,16 @@
from typing import Any, Dict, List
import html
+import random
import re
import time
from collections import namedtuple
+from dataclasses import dataclass
+from functools import wraps
from pathlib import Path
-from requests import Session
+from bs4 import BeautifulSoup
+from requests import Response, Session
from requests.cookies import cookiejar_from_dict
from rich.console import Console
@@ -14,7 +18,46 @@
console = Console()
session = Session()
-Video = namedtuple("Video", "vid vname pname")
+Video = namedtuple("Video", "vid vname pname uname")
+
+
+@dataclass
+class Process:
+ now: int = 1
+ total: int = 1
+
+ def status(self):
+ if self.total > 1:
+ return f"{self.now}/{self.total} ({self.now / self.total * 100:.0f}%)"
+ # return "☯"
+ return "⚓"
+
+
+def retry(exceptions=Exception, tries=3, delay=1, backoff=2):
+ def deco_retry(f):
+ @wraps(f)
+ def f_retry(*args, **kwargs):
+ mtries, mdelay = tries, delay
+ while mtries > 1:
+ try:
+ return f(*args, **kwargs)
+ except exceptions as e:
+ console.print(f"[red]{e}, Retrying in {mdelay} seconds...[/]")
+ time.sleep(mdelay)
+ mtries -= 1
+ mdelay *= backoff
+ return f(*args, **kwargs)
+
+ return f_retry
+
+ return deco_retry
+
+
+@retry()
+def session_request(method: str, url: str, **kwargs) -> Response:
+ resp = session.request(method, url, **kwargs)
+ resp.raise_for_status()
+ return resp
def parse_cookies(cookie: str) -> Dict[str, str]:
@@ -27,7 +70,7 @@ def parse_cookies(cookie: str) -> Dict[str, str]:
def save_cookie(cookie: str) -> None:
cache_dir = Path.home() / f".{c.APP_NAME}"
- cache_dir.mkdir(exist_ok=True)
+ cache_dir.mkdir(parents=True, exist_ok=True)
with open(cache_dir / "cookie", "w") as f:
f.write(cookie)
@@ -49,15 +92,19 @@ def find_from_string(pattern: str, string: str) -> str:
def parse_video_id(index: str) -> str:
- return find_from_string(r"(?<=video)\d+(?=/)", index)
+ return find_from_string(r"(?<=video)\d+(?=/)", index.strip())
def parse_video_name(index: str) -> str:
- return find_from_string(r"(?<=\d/).+(?=[/])*", index)
+ return find_from_string(r"(?<=\d/).+(?=[/])*", index.strip())
+
+
+def parse_username(index: str) -> str:
+ return find_from_string(r"(?<=profiles/|channels/)\w*(?=/*)", index.strip())
def parse_playlist_id(index: str) -> str:
- return find_from_string(r"(?<=/favorite/)\d+(?=/)", index)
+ return find_from_string(r"(?<=/favorite/)\d+(?=/)", index.strip())
def safe_filename(filename: str) -> str:
@@ -65,7 +112,7 @@ def safe_filename(filename: str) -> str:
def get_video_full_name(index: str) -> str:
- resp = session.get(index, timeout=c.TIMEOUT)
+ resp = session_request("GET", index, timeout=c.TIMEOUT)
resp.raise_for_status()
title_tab = re.search(r'(?<=)', resp.text)
if title_tab:
@@ -79,7 +126,7 @@ def request_with_cookie(method: str, url: str, return_when: str) -> Dict[str, An
while 1:
session.cookies = cookiejar_from_dict(cookies)
- resp = session.request(method, url, timeout=c.TIMEOUT)
+ resp = session_request(method, url, timeout=c.TIMEOUT)
resp.raise_for_status()
data = resp.json()
keys = return_when.split(".")
@@ -112,7 +159,25 @@ def get_video_url(vid: str, low: bool = False) -> str:
def get_videos_from_play_page(page_url: str) -> Video:
vid = parse_video_id(page_url)
vname = get_video_full_name(page_url) or parse_video_name(page_url)
- return Video(vid=vid, vname=vname, pname="")
+ return Video(vid=vid, vname=vname, pname="", uname="")
+
+
+def get_videos_from_user_page(page_url: str, aid: str, base_api: str, videos: List[Video]) -> List[Video]:
+ username = parse_username(page_url)
+ start_page = base_api.format(u=username, aid=aid)
+ resp = session_request("POST", start_page, timeout=c.TIMEOUT)
+ resp.raise_for_status()
+ next_aid = find_from_string(r"\d+", resp.text.splitlines()[0])
+
+ bs = BeautifulSoup(resp.text, "html.parser")
+ blocks = bs.find_all("div", class_="thumb-block")
+ for block in blocks:
+ vid = block.attrs.get("data-id")
+ vname = block.find("p", class_="title").find("a").attrs.get("title")
+ videos.append(Video(vid=vid, vname=vname, pname="", uname=username))
+ if int(next_aid) > 0:
+ return get_videos_from_user_page(page_url, next_aid, base_api, videos)
+ return videos
def get_videos_by_playlist_id(pid: str) -> List[Video]:
@@ -122,7 +187,7 @@ def get_videos_by_playlist_id(pid: str) -> List[Video]:
videos_info = data.get("list", {}).get("videos")
videos = []
for v in videos_info:
- videos.append(Video(vid=v.get("id"), vname=v.get("tf"), pname=playlist_name))
+ videos.append(Video(vid=v.get("id"), vname=v.get("tf"), pname=playlist_name, uname=""))
return videos
@@ -130,13 +195,13 @@ def get_videos_by_playlist_id(pid: str) -> List[Video]:
def download(video: Video, dest: str, low: bool, overwrite: bool) -> None:
url = get_video_url(video.vid, low)
- save_dir = Path(dest) / video.pname
- save_dir.mkdir(exist_ok=True)
+ save_dir = Path(dest) / (video.pname or video.uname)
+ save_dir.mkdir(parents=True, exist_ok=True)
save_name = save_dir / f"{video.vname}(#{video.vid}).mp4"
- head = session.head(url, stream=True)
+ head = session_request("HEAD", url, stream=True)
size = int(head.headers["Content-Length"].strip())
- console.print(f"Video ID : [cyan]{video.vid}[/]")
+ console.print(f"Video ID : [white]{video.vid}[/]")
console.print(f"Video Name : [yellow]{video.vname}[/]")
console.print(f"Video Link : [underline]{url}[/]")
console.print(f"Video Size : [white]{size / 1024 ** 2:.2f}[/] MB")
@@ -145,7 +210,7 @@ def download(video: Video, dest: str, low: bool, overwrite: bool) -> None:
done = 0
if save_name.is_file():
if overwrite:
- save_name.unlink(missing_ok=True)
+ save_name.unlink()
else:
done = save_name.stat().st_size
@@ -154,6 +219,7 @@ def download(video: Video, dest: str, low: bool, overwrite: bool) -> None:
show_process_bar = True
print()
+ emoji = random.choice(c.SMILE_EMOJIS)
while done < size:
time_start = time.time()
start = done
@@ -162,7 +228,7 @@ def download(video: Video, dest: str, low: bool, overwrite: bool) -> None:
done = size
end = done
headers = {"Range": f"bytes={start}-{end - 1}"}
- resp = session.get(url, stream=True, headers=headers, timeout=c.TIMEOUT)
+ resp = session_request("GET", url, stream=True, headers=headers, timeout=c.TIMEOUT)
with open(save_name, "ab") as f:
write = start
@@ -172,7 +238,9 @@ def download(video: Video, dest: str, low: bool, overwrite: bool) -> None:
write += c.CHUNK_SIZE
percent_done = int(min(write, size) / size * 1000) / 10
bar_done = int(percent_done * 0.6)
- console.print(f"|{'█' * bar_done}{' ' * (60 - bar_done)}| [green]{percent_done:5.1f}%[/]", end="\r")
+ console.print(
+ f"{emoji} |{'█' * bar_done}{' ' * (60 - bar_done)}| [green]{percent_done:5.1f}%[/]", end="\r"
+ )
# Download speed
speed = (end - start) / (time.time() - time_start)
if speed < 1024: # 1KB
@@ -182,7 +250,7 @@ def download(video: Video, dest: str, low: bool, overwrite: bool) -> None:
else:
speed_text = f"{speed / 1048576:7.2f}MB/s"
console.print(
- f"|{'█' * bar_done}{' ' * (60 - bar_done)}| [green]{percent_done:5.1f}% {speed_text}[/]", end="\r"
+ f"{emoji} |{'█' * bar_done}{' ' * (60 - bar_done)}| [green]{percent_done:5.1f}% {speed_text}[/]", end="\r"
)
if show_process_bar: