Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,742 changes: 1,450 additions & 1,292 deletions poetry.lock

Large diffs are not rendered by default.

105 changes: 57 additions & 48 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,71 +1,83 @@
[tool.poetry]
[project]
name = "streamrip"
version = "2.2.0"
version = "2.3.0"
description = "A fast, all-in-one music downloader for Qobuz, Deezer, Tidal, and SoundCloud"
authors = ["nathom <nathanthomas707@gmail.com>"]
authors = [{ name = "nathom", email = "nathanthomas707@gmail.com" }]
maintainers = [{ name = "Stensel8" }]
license = "GPL-3.0-only"
readme = "README.md"
homepage = "https://github.com/nathom/streamrip"
repository = "https://github.com/nathom/streamrip"
include = ["src/config.toml"]
requires-python = ">=3.10,<4.0"
keywords = ["hi-res", "free", "music", "download"]
classifiers = [
"License :: OSI Approved :: GNU General Public License (GPL)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
packages = [{ include = "streamrip" }]
dependencies = [
"mutagen>=1.47,<2",
"tomlkit>=0.13,<1",
"pathvalidate>=3.2,<4",
"simple-term-menu>=1.6,<2; sys_platform != 'win32'",
"pick>=2.4,<3; sys_platform == 'win32'",
"windows-curses>=2.4,<3; sys_platform == 'win32'",
"Pillow>=11,<13",
"deezer-py>=1.3.7,<2",
"pycryptodomex>=3.20,<4",
"m3u8>=6,<7",
"aiofiles>=24.1",
"aiohttp>=3.11,<4",
"aiolimiter>=1.2,<2",
"rich>=13.9",
"click>=8.1,<9",
"click-help-colors>=0.9.4,<0.10",
"requests>=2.32,<3",
]

[project.optional-dependencies]
# Use certifi's CA bundle instead of the system certificates.
ssl = ["certifi>=2024.7.4"]
# Capture the Qobuz user_auth_token from a real browser login.
qobuz-login = ["playwright>=1.49"]

[tool.poetry.scripts]
[project.scripts]
rip = "streamrip.rip:rip"

[tool.poetry.dependencies]
python = ">=3.10 <4.0"
mutagen = "^1.45.1"
tomlkit = "^0.7.2"
pathvalidate = "^2.4.1"
simple-term-menu = { version = "^1.2.1", platform = 'darwin|linux' }
pick = { version = "^2", platform = 'win32|cygwin' }
windows-curses = { version = "^2.2.0", platform = 'win32|cygwin' }
Pillow = ">=9,<11"
deezer-py = "1.3.6"
pycryptodomex = "^3.10.1"
appdirs = "^1.4.4"
m3u8 = "^0.9.0"
aiofiles = "^0.7"
aiohttp = "^3.9"
aiodns = "^3.0.0"
aiolimiter = "^1.1.0"
rich = "^13.6.0"
click-help-colors = "^0.9.2"
certifi = { version = "^2025.1.31", optional = true }
[project.urls]
Homepage = "https://github.com/Stensel8/streamrip"
Repository = "https://github.com/Stensel8/streamrip"
"Bug Reports" = "https://github.com/Stensel8/streamrip/issues"
Upstream = "https://github.com/nathom/streamrip"

[tool.poetry.urls]
"Bug Reports" = "https://github.com/nathom/streamrip/issues"
[tool.poetry]
packages = [{ include = "streamrip" }]

[tool.poetry.dev-dependencies]
types-click = "^7.1.2"
types-Pillow = "^8.3.1"
ruff = "^0.1"
black = "^24"
isort = "^5.9.3"
flake8 = "^3.9.2"
setuptools = "^67.4.0"
pytest = "^7.4"
pytest-mock = "^3.11.1"
pytest-asyncio = "^0.21.1"
[tool.poetry.group.dev.dependencies]
ruff = ">=0.8"
pytest = ">=8.3"
pytest-mock = ">=3.14"
pytest-asyncio = ">=0.25"
certifi = ">=2024.7.4"

[tool.pytest.ini_options]
minversion = "6.0"
minversion = "8.0"
addopts = "-ra -q"
testpaths = ["tests"]
log_level = "DEBUG"
asyncio_mode = 'auto'
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
log_cli = true

[build-system]
requires = ["poetry-core>=1.0.0"]
requires = ["poetry-core>=2.0.0,<3.0.0"]
Comment on lines 74 to +75
build-backend = "poetry.core.masonry.api"

[tool.ruff]
target-version = "py310"

[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
select = ["E4", "E7", "E9", "F", "I", "ASYNC", "N", "RUF", "ERA001"]
Expand All @@ -90,6 +102,3 @@ skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"

[tool.poetry.extras]
ssl = ["certifi"]
2 changes: 1 addition & 1 deletion streamrip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from .config import Config

__all__ = ["Config", "converter", "db", "exceptions", "media", "metadata"]
__version__ = "2.2.0"
__version__ = "2.3.0"
4 changes: 3 additions & 1 deletion streamrip/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ async def get_session(

# Get connector kwargs based on SSL verification setting
connector_kwargs = get_aiohttp_connector_kwargs(verify_ssl=verify_ssl)
connector = aiohttp.TCPConnector(**connector_kwargs)
connector = aiohttp.TCPConnector(
**connector_kwargs, resolver=aiohttp.ThreadedResolver()
)

return aiohttp.ClientSession(
headers={"User-Agent": DEFAULT_USER_AGENT} | headers,
Expand Down
4 changes: 3 additions & 1 deletion streamrip/client/qobuz.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ async def __aenter__(self):

# For the spoofer, always use SSL verification
connector_kwargs = get_aiohttp_connector_kwargs(verify_ssl=True)
connector = aiohttp.TCPConnector(**connector_kwargs)
connector = aiohttp.TCPConnector(
**connector_kwargs, resolver=aiohttp.ThreadedResolver()
)
Comment on lines +133 to +135

self.session = aiohttp.ClientSession(connector=connector)
return self
Expand Down
2 changes: 1 addition & 1 deletion streamrip/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
APP_DIR = click.get_app_dir("streamrip")
os.makedirs(APP_DIR, exist_ok=True)
DEFAULT_CONFIG_PATH = os.path.join(APP_DIR, "config.toml")
CURRENT_CONFIG_VERSION = "2.2.0"
CURRENT_CONFIG_VERSION = "2.3.0"


class OutdatedConfigError(Exception):
Expand Down
2 changes: 1 addition & 1 deletion streamrip/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,6 @@ max_search_results = 100

[misc]
# Metadata to identify this config file. Do not change.
version = "2.2.0"
version = "2.3.0"
# Print a message if a new version of streamrip is available
check_for_updates = true
4 changes: 3 additions & 1 deletion streamrip/media/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,9 @@ async def fetch(session: aiohttp.ClientSession, url, **kwargs):
# Create new session so we're not bound by rate limit
verify_ssl = getattr(self.config.session.downloads, "verify_ssl", True)
connector_kwargs = get_aiohttp_connector_kwargs(verify_ssl=verify_ssl)
connector = aiohttp.TCPConnector(**connector_kwargs)
connector = aiohttp.TCPConnector(
**connector_kwargs, resolver=aiohttp.ThreadedResolver()
)

async with aiohttp.ClientSession(connector=connector) as session:
page = await fetch(session, playlist_url)
Expand Down
4 changes: 3 additions & 1 deletion streamrip/rip/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,9 @@ async def latest_streamrip_version(verify_ssl: bool = True) -> tuple[str, str |
"""
# Create connector with appropriate SSL settings
connector_kwargs = get_aiohttp_connector_kwargs(verify_ssl=verify_ssl)
connector = aiohttp.TCPConnector(**connector_kwargs)
connector = aiohttp.TCPConnector(
**connector_kwargs, resolver=aiohttp.ThreadedResolver()
)

async with aiohttp.ClientSession(connector=connector) as s:
async with s.get("https://pypi.org/pypi/streamrip/json") as resp:
Expand Down
6 changes: 4 additions & 2 deletions streamrip/rip/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@

logger = logging.getLogger("streamrip")

if platform.system() == "Windows":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# Windows used to be forced onto the SelectorEventLoop because aiodns needed it,
# but that loop cannot run subprocesses, so every ffmpeg conversion failed there.
# aiodns is no longer used (connectors use aiohttp's ThreadedResolver), so the
# default ProactorEventLoop is kept.


class Main:
Expand Down
20 changes: 0 additions & 20 deletions streamrip/rip/user_paths.py

This file was deleted.

4 changes: 2 additions & 2 deletions streamrip/utils/ssl_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ def get_aiohttp_connector_kwargs(verify_ssl=True):
Dictionary of kwargs to pass to aiohttp.TCPConnector
"""
if not verify_ssl:
return {"verify_ssl": False}
return {"ssl": False}

if HAS_CERTIFI:
ssl_context = create_ssl_context(verify=True)
return {"ssl": ssl_context}
else:
return {"verify_ssl": True}
return {"ssl": True}


def print_ssl_error_help():
Expand Down
2 changes: 1 addition & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def test_config_file_update():
assert toml["cli"]["text_output"] is True # type: ignore
assert toml["cli"]["progress_bars"] is True # type: ignore
assert toml["cli"]["max_search_results"] == 100 # type: ignore
assert toml["misc"]["version"] == "2.2.0" # type: ignore
assert toml["misc"]["version"] == "2.3.0" # type: ignore
assert "YouTubeVideos" in str(toml["youtube"]["video_downloads_folder"])
# type: ignore
os.remove("tests/test_config_old2.toml")
Expand Down
2 changes: 1 addition & 1 deletion tests/test_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,5 @@ max_search_results = 100

[misc]
# Metadata to identify this config file. Do not change.
version = "2.2.0"
version = "2.3.0"
check_for_updates = true
8 changes: 4 additions & 4 deletions tests/test_ssl_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def test_get_aiohttp_connector_kwargs_without_verification():
kwargs = get_aiohttp_connector_kwargs(verify_ssl=False)

# It should return kwargs with verify_ssl=False
assert kwargs == {"verify_ssl": False}
assert kwargs == {"ssl": False}


def test_client_get_session_supports_verify_ssl():
Expand Down Expand Up @@ -139,7 +139,7 @@ async def test_client_get_session_creates_connector():
patch("aiohttp.ClientSession") as mock_client_session,
patch("aiohttp.TCPConnector") as mock_connector,
):
mock_get_kwargs.return_value = {"verify_ssl": False}
mock_get_kwargs.return_value = {"ssl": False}
mock_connector.return_value = MagicMock()
mock_client_session.return_value = AsyncMock()

Expand Down Expand Up @@ -183,7 +183,7 @@ async def test_latest_streamrip_version_creates_session():
patch("aiohttp.ClientSession") as mock_client_session,
patch("aiohttp.TCPConnector") as mock_connector,
):
mock_get_kwargs.return_value = {"verify_ssl": False}
mock_get_kwargs.return_value = {"ssl": False}
mock_connector.return_value = MagicMock()

# Setup mock responses for API calls
Expand Down Expand Up @@ -274,7 +274,7 @@ async def test_lastfm_playlist_session_creation(mock_client_session):
with patch(
"streamrip.utils.ssl_utils.get_aiohttp_connector_kwargs"
) as mock_get_kwargs:
mock_get_kwargs.return_value = {"verify_ssl": False}
mock_get_kwargs.return_value = {"ssl": False}

# Try to parse the playlist
with pytest.raises(Exception):
Expand Down