Typecheck with mypy, lint and format with ruff (#499)

* Lint and format with ruff, check types with mypy

* Fix mypy checks

* Update CI

* Remove shebangs

* Ignore EXE rules

* Fix EOL

* Fix mypy for 3.7

* Fix CI

* Fix CI

* Ensure filelock path exists

* ci: various ci fixes (#500)

---------

Co-authored-by: Arsenii es3n1n <me@es3n.in>
This commit is contained in:
7x11x13 2024-07-09 10:51:23 -04:00 committed by GitHub
parent 399ea88eab
commit 9fd4814e1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 802 additions and 550 deletions

View File

@ -1,10 +1,45 @@
name: pypi-publish name: pypi-publish
on: on:
push: push:
branches: [ master ]
tags: tags:
- 'v*' - 'v*'
pull_request:
branches: [ master ]
jobs: jobs:
test:
strategy:
matrix:
version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install ffmpeg
run: |
sudo apt update
sudo apt install -yq --no-install-recommends ffmpeg
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.version }}
- name: Install dependencies
run: |
pip install -e .[dev]
- name: Lint
run: ruff check
- name: Format check
run: ruff format --check
- name: Type check
run: mypy
- name: Test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
run: |
pytest --exitfirst
publish: publish:
needs: test
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ __pycache__/
.env .env
.coverage* .coverage*
.idea .idea
.python-version

3
mypy.ini Normal file
View File

@ -0,0 +1,3 @@
[mypy]
packages = scdl, tests
check_untyped_defs = true

View File

@ -1,2 +1,6 @@
pytest pytest
music-tag music-tag
ruff
mypy
types-requests
types-tqdm

13
ruff.toml Normal file
View File

@ -0,0 +1,13 @@
target-version = "py37"
line-length = 100
[lint]
select = ["ALL"]
ignore = [
"C90", "D",
"S", "BLE", "FBT", "A", "EM", "FA", "G", "SLF", "PTH",
"PLR", "TRY",
"PLW2901", "ANN204",
"COM812", "ISC001",
"EXE"
]

View File

@ -1,3 +1,3 @@
# -*- encoding: utf-8 -*-
"""Python Soundcloud Music Downloader.""" """Python Soundcloud Music Downloader."""
__version__ = "v2.10.0" __version__ = "v2.10.0"

View File

@ -1,12 +1,11 @@
from base64 import b64encode from base64 import b64encode
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, Type, TypeVar, Union, Callable from functools import singledispatch
from types import MappingProxyType from typing import Optional, Union
from mutagen import FileType, flac, oggopus, id3, wave, mp3, mp4 from mutagen import FileType, flac, id3, mp3, mp4, oggopus, wave
JPEG_MIME_TYPE: str = "image/jpeg"
JPEG_MIME_TYPE: str = 'image/jpeg'
@dataclass(frozen=True) @dataclass(frozen=True)
@ -26,6 +25,11 @@ class MetadataInfo:
album_track_num: Optional[int] album_track_num: Optional[int]
@singledispatch
def assemble_metadata(file: FileType, meta: MetadataInfo) -> None: # noqa: ARG001
raise NotImplementedError
def _get_flac_pic(jpeg_data: bytes) -> flac.Picture: def _get_flac_pic(jpeg_data: bytes) -> flac.Picture:
pic = flac.Picture() pic = flac.Picture()
pic.data = jpeg_data pic.data = jpeg_data
@ -39,119 +43,113 @@ def _get_apic(jpeg_data: bytes) -> id3.APIC:
encoding=3, encoding=3,
mime=JPEG_MIME_TYPE, mime=JPEG_MIME_TYPE,
type=3, type=3,
desc='Cover', desc="Cover",
data=jpeg_data, data=jpeg_data,
) )
def _assemble_common(file: FileType, meta: MetadataInfo) -> None: def _assemble_common(file: FileType, meta: MetadataInfo) -> None:
file['artist'] = meta.artist file["artist"] = meta.artist
file['title'] = meta.title file["title"] = meta.title
if meta.genre: if meta.genre:
file['genre'] = meta.genre file["genre"] = meta.genre
if meta.link: if meta.link:
file['website'] = meta.link file["website"] = meta.link
if meta.date: if meta.date:
file['date'] = meta.date file["date"] = meta.date
if meta.album_title: if meta.album_title:
file['album'] = meta.album_title file["album"] = meta.album_title
if meta.album_author: if meta.album_author:
file['albumartist'] = meta.album_author file["albumartist"] = meta.album_author
if meta.album_track_num is not None: if meta.album_track_num is not None:
file['tracknumber'] = str(meta.album_track_num) file["tracknumber"] = str(meta.album_track_num)
def _assemble_flac(file: flac.FLAC, meta: MetadataInfo) -> None: @assemble_metadata.register(flac.FLAC)
def _(file: flac.FLAC, meta: MetadataInfo) -> None:
_assemble_common(file, meta) _assemble_common(file, meta)
if meta.description: if meta.description:
file['description'] = meta.description file["description"] = meta.description
if meta.artwork_jpeg: if meta.artwork_jpeg:
file.add_picture(_get_flac_pic(meta.artwork_jpeg)) file.add_picture(_get_flac_pic(meta.artwork_jpeg))
def _assemble_opus(file: oggopus.OggOpus, meta: MetadataInfo) -> None: @assemble_metadata.register(oggopus.OggOpus)
def _(file: oggopus.OggOpus, meta: MetadataInfo) -> None:
_assemble_common(file, meta) _assemble_common(file, meta)
if meta.description: if meta.description:
file['comment'] = meta.description file["comment"] = meta.description
if meta.artwork_jpeg: if meta.artwork_jpeg:
pic = _get_flac_pic(meta.artwork_jpeg).write() pic = _get_flac_pic(meta.artwork_jpeg).write()
file['metadata_block_picture'] = b64encode(pic).decode() file["metadata_block_picture"] = b64encode(pic).decode()
def _assemble_wav_or_mp3(file: Union[wave.WAVE, mp3.MP3], meta: MetadataInfo) -> None: @assemble_metadata.register(mp3.MP3)
file['TIT2'] = id3.TIT2(encoding=3, text=meta.title) @assemble_metadata.register(wave.WAVE)
file['TPE1'] = id3.TPE1(encoding=3, text=meta.artist) def _(file: Union[wave.WAVE, mp3.MP3], meta: MetadataInfo) -> None:
file["TIT2"] = id3.TIT2(encoding=3, text=meta.title)
file["TPE1"] = id3.TPE1(encoding=3, text=meta.artist)
if meta.description: if meta.description:
file['COMM'] = id3.COMM(encoding=3, lang='ENG', text=meta.description) file["COMM"] = id3.COMM(encoding=3, lang="ENG", text=meta.description)
if meta.genre: if meta.genre:
file['TCON'] = id3.TCON(encoding=3, text=meta.genre) file["TCON"] = id3.TCON(encoding=3, text=meta.genre)
if meta.link: if meta.link:
file['WOAS'] = id3.WOAS(url=meta.link) file["WOAS"] = id3.WOAS(url=meta.link)
if meta.date: if meta.date:
file['TDAT'] = id3.TDAT(encoding=3, text=meta.date) file["TDAT"] = id3.TDAT(encoding=3, text=meta.date)
if meta.album_title: if meta.album_title:
file['TALB'] = id3.TALB(encoding=3, text=meta.album_title) file["TALB"] = id3.TALB(encoding=3, text=meta.album_title)
if meta.album_author: if meta.album_author:
file['TPE2'] = id3.TPE2(encoding=3, text=meta.album_author) file["TPE2"] = id3.TPE2(encoding=3, text=meta.album_author)
if meta.album_track_num is not None: if meta.album_track_num is not None:
file['TRCK'] = id3.TRCK(encoding=3, text=str(meta.album_track_num)) file["TRCK"] = id3.TRCK(encoding=3, text=str(meta.album_track_num))
if meta.artwork_jpeg: if meta.artwork_jpeg:
file['APIC'] = _get_apic(meta.artwork_jpeg) file["APIC"] = _get_apic(meta.artwork_jpeg)
def _assemble_mp4(file: mp4.MP4, meta: MetadataInfo) -> None: @assemble_metadata.register(mp4.MP4)
file['\251ART'] = meta.artist def _(file: mp4.MP4, meta: MetadataInfo) -> None:
file['\251nam'] = meta.title file["\251ART"] = meta.artist
file["\251nam"] = meta.title
if meta.genre: if meta.genre:
file['\251gen'] = meta.genre file["\251gen"] = meta.genre
if meta.link: if meta.link:
file['\251cmt'] = meta.link file["\251cmt"] = meta.link
if meta.date: if meta.date:
file['\251day'] = meta.date file["\251day"] = meta.date
if meta.album_title: if meta.album_title:
file['\251alb'] = meta.album_title file["\251alb"] = meta.album_title
if meta.album_author: if meta.album_author:
file['aART'] = meta.album_author file["aART"] = meta.album_author
if meta.album_track_num is not None: if meta.album_track_num is not None:
file['trkn'] = str(meta.album_track_num) file["trkn"] = str(meta.album_track_num)
if meta.description: if meta.description:
file['desc'] = meta.description file["desc"] = meta.description
if meta.artwork_jpeg: if meta.artwork_jpeg:
file['covr'] = [mp4.MP4Cover(meta.artwork_jpeg)] file["covr"] = [mp4.MP4Cover(meta.artwork_jpeg)]
T = TypeVar('T')
METADATA_ASSEMBLERS: MappingProxyType[Type[T], Callable[[T, MetadataInfo], None]] = MappingProxyType({
flac.FLAC: _assemble_flac,
oggopus.OggOpus: _assemble_opus,
wave.WAVE: _assemble_wav_or_mp3,
mp3.MP3: _assemble_wav_or_mp3,
mp4.MP4: _assemble_mp4,
})

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,38 @@
# -*- encoding: utf-8 -*- """Copied from
"""
Copied from
https://github.com/davidfischer-ch/pytoolbox/blob/master/pytoolbox/logging.py https://github.com/davidfischer-ch/pytoolbox/blob/master/pytoolbox/logging.py
""" """
import email.message import email.message
import logging import logging
import re import re
from types import MappingProxyType
from typing import Dict, Optional
from termcolor import colored from termcolor import colored
__all__ = ('ColorizeFilter', ) __all__ = ("ColorizeFilter",)
class ColorizeFilter(logging.Filter): class ColorizeFilter(logging.Filter):
COLOR_BY_LEVEL = MappingProxyType(
{
logging.DEBUG: "blue",
logging.WARNING: "yellow",
logging.ERROR: "red",
logging.INFO: "white",
},
)
color_by_level = { def filter(self, record: logging.LogRecord) -> bool:
logging.DEBUG: 'blue',
logging.WARNING: 'yellow',
logging.ERROR: 'red',
logging.INFO: 'white'
}
def filter(self, record):
record.raw_msg = record.msg record.raw_msg = record.msg
color = self.color_by_level.get(record.levelno) color = self.COLOR_BY_LEVEL.get(record.levelno)
if color: if color:
record.msg = colored(record.msg, color) record.msg = colored(record.msg, color) # type: ignore[arg-type]
return True return True
def size_in_bytes(insize): def size_in_bytes(insize: str) -> int:
""" """Return the size in bytes from strings such as '5 mb' into 5242880.
Returns the size in bytes from strings such as '5 mb' into 5242880.
>>> size_in_bytes('1m') >>> size_in_bytes('1m')
1048576 1048576
@ -49,20 +49,20 @@ def size_in_bytes(insize):
raise ValueError('no string specified') raise ValueError('no string specified')
ValueError: no string specified ValueError: no string specified
""" """
if insize is None or insize.strip() == '': if insize is None or insize.strip() == "":
raise ValueError('no string specified') raise ValueError("no string specified")
units = { units = {
'k': 1024, "k": 1024,
'm': 1024 ** 2, "m": 1024**2,
'g': 1024 ** 3, "g": 1024**3,
't': 1024 ** 4, "t": 1024**4,
'p': 1024 ** 5, "p": 1024**5,
} }
match = re.search(r'^\s*([0-9\.]+)\s*([kmgtp])?', insize, re.I) match = re.search(r"^\s*([0-9\.]+)\s*([kmgtp])?", insize, re.IGNORECASE)
if match is None: if match is None:
raise ValueError('match not found') raise ValueError("match not found")
size, unit = match.groups() size, unit = match.groups()
@ -75,7 +75,9 @@ def size_in_bytes(insize):
return int(size) return int(size)
def parse_header(content_disposition): def parse_header(content_disposition: Optional[str]) -> Dict[str, str]:
if not content_disposition:
return {}
message = email.message.Message() message = email.message.Message()
message['content-type'] = content_disposition message["content-type"] = content_disposition
return dict(message.get_params()) return dict(message.get_params({}))

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
from os import path from os import path
from setuptools import find_packages, setup from setuptools import find_packages, setup
@ -28,10 +25,22 @@ setup(
"requests", "requests",
"tqdm", "tqdm",
"pathvalidate", "pathvalidate",
"soundcloud-v2>=1.3.10", "soundcloud-v2>=1.5.2",
"filelock>=3.0.0", "filelock>=3.0.0",
"typing_extensions; python_version < '3.11'",
], ],
extras_require={"test": ["pytest", "pytest-cov", "pytest-dotenv", "music-tag"]}, extras_require={
"dev": [
"pytest",
"pytest-cov",
"pytest-dotenv",
"music-tag",
"ruff",
"mypy",
"types-requests",
"types-tqdm",
],
},
url="https://github.com/flyingrub/scdl", url="https://github.com/flyingrub/scdl",
classifiers=[ classifiers=[
"Programming Language :: Python", "Programming Language :: Python",

0
tests/__init__.py Normal file
View File

View File

@ -5,8 +5,10 @@ from tests.utils import assert_not_track, assert_track, call_scdl_with_auth
def assert_track_playlist_1( def assert_track_playlist_1(
tmp_path: Path, playlist_folder: str = "test playlist", check_metadata: bool = True tmp_path: Path,
): playlist_folder: str = "test playlist",
check_metadata: bool = True,
) -> None:
expected_name = "1_testing - test track.mp3" expected_name = "1_testing - test track.mp3"
assert_track( assert_track(
tmp_path / playlist_folder, tmp_path / playlist_folder,
@ -19,8 +21,10 @@ def assert_track_playlist_1(
def assert_track_playlist_2( def assert_track_playlist_2(
tmp_path: Path, playlist_folder: str = "test playlist", check_metadata: bool = True tmp_path: Path,
): playlist_folder: str = "test playlist",
check_metadata: bool = True,
) -> None:
expected_name = "2_test track 2.mp3" expected_name = "2_test track 2.mp3"
assert_track( assert_track(
tmp_path / playlist_folder, tmp_path / playlist_folder,
@ -33,7 +37,7 @@ def assert_track_playlist_2(
) )
def test_playlist(tmp_path: Path): def test_playlist(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -47,7 +51,7 @@ def test_playlist(tmp_path: Path):
assert_track_playlist_2(tmp_path) assert_track_playlist_2(tmp_path)
def test_n(tmp_path: Path): def test_n(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -63,7 +67,7 @@ def test_n(tmp_path: Path):
assert_not_track(tmp_path / "test playlist", "2_testing - test track.mp3") assert_not_track(tmp_path / "test playlist", "2_testing - test track.mp3")
def test_offset(tmp_path: Path): def test_offset(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -82,7 +86,7 @@ def test_offset(tmp_path: Path):
assert_track_playlist_2(tmp_path) assert_track_playlist_2(tmp_path)
def test_no_playlist_folder(tmp_path: Path): def test_no_playlist_folder(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -99,7 +103,7 @@ def test_no_playlist_folder(tmp_path: Path):
assert_not_track(tmp_path / "test playlist", "2_test track 2.mp3") assert_not_track(tmp_path / "test playlist", "2_test track 2.mp3")
def test_no_strict_playlist(tmp_path: Path): def test_no_strict_playlist(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -114,7 +118,7 @@ def test_no_strict_playlist(tmp_path: Path):
assert_not_track(tmp_path / "test playlist", "2_test track 2.mp3") assert_not_track(tmp_path / "test playlist", "2_test track 2.mp3")
def test_strict_playlist(tmp_path: Path): def test_strict_playlist(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -130,7 +134,7 @@ def test_strict_playlist(tmp_path: Path):
assert_not_track(tmp_path / "test playlist", "2_test track 2.mp3") assert_not_track(tmp_path / "test playlist", "2_test track 2.mp3")
def test_sync(tmp_path: Path): def test_sync(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
os.makedirs("test playlist") os.makedirs("test playlist")
r = call_scdl_with_auth( r = call_scdl_with_auth(
@ -159,8 +163,6 @@ def test_sync(tmp_path: Path):
"archive.txt", "archive.txt",
) )
assert r.returncode == 0 assert r.returncode == 0
assert_not_track( assert_not_track(tmp_path / "test playlist", "Wan Bushi - Eurodance Vibes (part 1+2+3).mp3")
tmp_path / "test playlist", "Wan Bushi - Eurodance Vibes (part 1+2+3).mp3" with open("archive.txt") as f:
)
with open("archive.txt", "r") as f:
assert f.read().split() == ["1855267053", "1855318536"] assert f.read().split() == ["1855267053", "1855318536"]

View File

@ -1,11 +1,13 @@
import math
import os import os
import pytest
from pathlib import Path from pathlib import Path
import pytest
from tests.utils import assert_not_track, assert_track, call_scdl_with_auth from tests.utils import assert_not_track, assert_track, call_scdl_with_auth
def test_original_download(tmp_path: Path): def test_original_download(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -17,7 +19,7 @@ def test_original_download(tmp_path: Path):
assert_track(tmp_path, "track.wav", "copy", "saves", None) assert_track(tmp_path, "track.wav", "copy", "saves", None)
def test_original_to_stdout(tmp_path: Path): def test_original_to_stdout(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -27,12 +29,13 @@ def test_original_to_stdout(tmp_path: Path):
encoding=None, encoding=None,
) )
assert r.returncode == 0 assert r.returncode == 0
with open('track.wav', 'wb') as f: with open("track.wav", "wb") as f:
assert isinstance(r.stdout, bytes)
f.write(r.stdout) f.write(r.stdout)
assert_track(tmp_path, "track.wav", "copy", "saves", None) assert_track(tmp_path, "track.wav", "copy", "saves", None)
def test_mp3_to_stdout(tmp_path: Path): def test_mp3_to_stdout(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -44,13 +47,14 @@ def test_mp3_to_stdout(tmp_path: Path):
) )
assert r.returncode == 0 assert r.returncode == 0
with open('track.mp3', 'wb') as f: with open("track.mp3", "wb") as f:
assert isinstance(r.stdout, bytes)
f.write(r.stdout) f.write(r.stdout)
assert_track(tmp_path, "track.mp3") assert_track(tmp_path, "track.mp3")
def test_flac_to_stdout(tmp_path: Path): def test_flac_to_stdout(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -61,14 +65,15 @@ def test_flac_to_stdout(tmp_path: Path):
encoding=None, encoding=None,
) )
with open('track.flac', 'wb') as f: with open("track.flac", "wb") as f:
assert isinstance(r.stdout, bytes)
f.write(r.stdout) f.write(r.stdout)
assert r.returncode == 0 assert r.returncode == 0
assert_track(tmp_path, "track.flac", "copy", "saves", None) assert_track(tmp_path, "track.flac", "copy", "saves", None)
def test_flac(tmp_path: Path): def test_flac(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -81,7 +86,7 @@ def test_flac(tmp_path: Path):
assert_track(tmp_path, "track.flac", "copy", "saves", None) assert_track(tmp_path, "track.flac", "copy", "saves", None)
def test_m4a(tmp_path: Path): def test_m4a(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -92,8 +97,8 @@ def test_m4a(tmp_path: Path):
"--opus", "--opus",
) )
assert r.returncode == 0 assert r.returncode == 0
if (tmp_path / 'track.opus').exists(): if (tmp_path / "track.opus").exists():
pytest.skip('No go+ subscription') pytest.skip("No go+ subscription")
assert_track( assert_track(
tmp_path, tmp_path,
"track.m4a", "track.m4a",
@ -104,7 +109,7 @@ def test_m4a(tmp_path: Path):
) )
def test_opus(tmp_path: Path): def test_opus(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -118,7 +123,7 @@ def test_opus(tmp_path: Path):
assert_track(tmp_path, "track.opus") assert_track(tmp_path, "track.opus")
def test_mp3(tmp_path: Path): def test_mp3(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -131,7 +136,7 @@ def test_mp3(tmp_path: Path):
assert_track(tmp_path, "track.mp3") assert_track(tmp_path, "track.mp3")
def test_unlisted_track(tmp_path: Path): def test_unlisted_track(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -144,7 +149,7 @@ def test_unlisted_track(tmp_path: Path):
assert_track(tmp_path, "track.mp3", "test track 2") assert_track(tmp_path, "track.mp3", "test track 2")
def test_original_art(tmp_path: Path): def test_original_art(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -158,7 +163,7 @@ def test_original_art(tmp_path: Path):
assert_track(tmp_path, "track.mp3", expected_artwork_len=3409) assert_track(tmp_path, "track.mp3", expected_artwork_len=3409)
def test_original_name(tmp_path: Path): def test_original_name(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -171,7 +176,7 @@ def test_original_name(tmp_path: Path):
assert_track(tmp_path, "original.wav", check_metadata=False) assert_track(tmp_path, "original.wav", check_metadata=False)
def test_original_metadata(tmp_path: Path): def test_original_metadata(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -184,7 +189,7 @@ def test_original_metadata(tmp_path: Path):
assert_track(tmp_path, "track.wav", "og title", "og artist", "og genre", 0) assert_track(tmp_path, "track.wav", "og title", "og artist", "og genre", 0)
def test_force_metadata(tmp_path: Path): def test_force_metadata(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -207,7 +212,7 @@ def test_force_metadata(tmp_path: Path):
assert_track(tmp_path, "track.wav", "copy", "saves", None) assert_track(tmp_path, "track.wav", "copy", "saves", None)
def test_addtimestamp(tmp_path: Path): def test_addtimestamp(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -219,7 +224,7 @@ def test_addtimestamp(tmp_path: Path):
assert_track(tmp_path, "1719169486_testing - test track.mp3", check_metadata=False) assert_track(tmp_path, "1719169486_testing - test track.mp3", check_metadata=False)
def test_addtofile(tmp_path: Path): def test_addtofile(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -231,7 +236,7 @@ def test_addtofile(tmp_path: Path):
assert_track(tmp_path, "7x11x13-testing - test track 2.mp3", check_metadata=False) assert_track(tmp_path, "7x11x13-testing - test track 2.mp3", check_metadata=False)
def test_extract_artist(tmp_path: Path): def test_extract_artist(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -245,7 +250,7 @@ def test_extract_artist(tmp_path: Path):
assert_track(tmp_path, "track.mp3", "test track", "testing") assert_track(tmp_path, "track.mp3", "test track", "testing")
def test_maxsize(tmp_path: Path): def test_maxsize(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -254,10 +259,10 @@ def test_maxsize(tmp_path: Path):
"--max-size=10kb", "--max-size=10kb",
) )
assert r.returncode == 1 assert r.returncode == 1
assert "not within --min-size and --max-size bounds" in r.stderr assert "not within --min-size=0 and --max-size=10240" in r.stderr
def test_minsize(tmp_path: Path): def test_minsize(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -266,10 +271,10 @@ def test_minsize(tmp_path: Path):
"--min-size=1mb", "--min-size=1mb",
) )
assert r.returncode == 1 assert r.returncode == 1
assert "not within --min-size and --max-size bounds" in r.stderr assert f"not within --min-size={1024**2} and --max-size={math.inf}" in r.stderr
def test_only_original(tmp_path: Path): def test_only_original(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -280,7 +285,7 @@ def test_only_original(tmp_path: Path):
assert "does not have original file available" in r.stderr assert "does not have original file available" in r.stderr
def test_overwrite(tmp_path: Path): def test_overwrite(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -312,7 +317,7 @@ def test_overwrite(tmp_path: Path):
assert r.returncode == 0 assert r.returncode == 0
def test_path(tmp_path: Path): def test_path(tmp_path: Path) -> None:
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
"https://soundcloud.com/one-thousand-and-one/test-track", "https://soundcloud.com/one-thousand-and-one/test-track",
@ -326,7 +331,7 @@ def test_path(tmp_path: Path):
assert_track(tmp_path, "track.mp3", check_metadata=False) assert_track(tmp_path, "track.mp3", check_metadata=False)
def test_remove(tmp_path: Path): def test_remove(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -350,7 +355,7 @@ def test_remove(tmp_path: Path):
assert_not_track(tmp_path, "track.mp3") assert_not_track(tmp_path, "track.mp3")
def test_download_archive(tmp_path: Path): def test_download_archive(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",

View File

@ -4,11 +4,11 @@ from pathlib import Path
from tests.utils import assert_track, call_scdl_with_auth from tests.utils import assert_track, call_scdl_with_auth
def count_files(dir: Path): def count_files(folder: Path) -> int:
return len(list(dir.rglob("*"))) return len(list(folder.rglob("*")))
def test_all(tmp_path: Path): def test_all(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -22,7 +22,7 @@ def test_all(tmp_path: Path):
assert count_files(tmp_path) == 3 assert count_files(tmp_path) == 3
def test_tracks(tmp_path: Path): def test_tracks(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -36,7 +36,7 @@ def test_tracks(tmp_path: Path):
assert count_files(tmp_path) == 1 assert count_files(tmp_path) == 1
def test_likes(tmp_path: Path): def test_likes(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -46,13 +46,11 @@ def test_likes(tmp_path: Path):
"--name-format={title}", "--name-format={title}",
) )
assert r.returncode == 0 assert r.returncode == 0
assert_track( assert_track(tmp_path, "Wan Bushi - Eurodance Vibes (part 1+2+3).mp3", check_metadata=False)
tmp_path, "Wan Bushi - Eurodance Vibes (part 1+2+3).mp3", check_metadata=False
)
assert count_files(tmp_path) == 1 assert count_files(tmp_path) == 1
def test_commented(tmp_path: Path): def test_commented(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -62,13 +60,11 @@ def test_commented(tmp_path: Path):
"--name-format={title}", "--name-format={title}",
) )
assert r.returncode == 0 assert r.returncode == 0
assert_track( assert_track(tmp_path, "Wan Bushi - Eurodance Vibes (part 1+2+3).mp3", check_metadata=False)
tmp_path, "Wan Bushi - Eurodance Vibes (part 1+2+3).mp3", check_metadata=False
)
assert count_files(tmp_path) == 1 assert count_files(tmp_path) == 1
def test_playlists(tmp_path: Path): def test_playlists(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
@ -80,16 +76,15 @@ def test_playlists(tmp_path: Path):
assert count_files(tmp_path) == 3 assert count_files(tmp_path) == 3
def test_reposts(tmp_path: Path): def test_reposts(tmp_path: Path) -> None:
os.chdir(tmp_path) os.chdir(tmp_path)
r = call_scdl_with_auth( r = call_scdl_with_auth(
"-l", "-l",
"https://soundcloud.com/one-thousand-and-one", "https://soundcloud.com/one-thousand-and-one",
"-r", "-r",
"--name-format={title}", "--name-format={title}",
"--onlymp3",
) )
assert r.returncode == 0 assert r.returncode == 0
assert_track( assert_track(tmp_path, "Wan Bushi - Eurodance Vibes (part 1+2+3).mp3", check_metadata=False)
tmp_path, "Wan Bushi - Eurodance Vibes (part 1+2+3).mp3", check_metadata=False
)
assert count_files(tmp_path) == 1 assert count_files(tmp_path) == 1

View File

@ -3,22 +3,26 @@ import subprocess
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import music_tag import music_tag # type: ignore[import]
from soundcloud import SoundCloud from soundcloud import SoundCloud
client_id = SoundCloud().client_id client_id = SoundCloud().client_id
def call_scdl_with_auth(*args, encoding: Optional[str] = 'utf-8') -> subprocess.CompletedProcess[str]: def call_scdl_with_auth(
*args: str,
encoding: Optional[str] = "utf-8",
) -> subprocess.CompletedProcess:
auth_token = os.getenv("AUTH_TOKEN") auth_token = os.getenv("AUTH_TOKEN")
assert auth_token assert auth_token
args = ( args = ("scdl", *args, f"--auth-token={auth_token}", f"--client-id={client_id}")
["scdl"] return subprocess.run(
+ list(args) args,
+ [f"--auth-token={auth_token}", f"--client-id={client_id}"] capture_output=True,
encoding=encoding,
errors="ignore" if encoding is not None else None,
check=False,
) )
return subprocess.run(args, capture_output=True, encoding=encoding,
errors='ignore' if encoding is not None else None)
def assert_track( def assert_track(
@ -27,12 +31,12 @@ def assert_track(
expected_title: str = "testing - test track", expected_title: str = "testing - test track",
expected_artist: str = "7x11x13-testing", expected_artist: str = "7x11x13-testing",
expected_genre: Optional[str] = "Testing", expected_genre: Optional[str] = "Testing",
expected_artwork_len: int = 16136, expected_artwork_len: Optional[int] = 16136,
expected_album: Optional[str] = None, expected_album: Optional[str] = None,
expected_albumartist: Optional[str] = None, expected_albumartist: Optional[str] = None,
expected_tracknumber: Optional[int] = None, expected_tracknumber: Optional[int] = None,
check_metadata: bool = True, check_metadata: bool = True,
): ) -> None:
file = tmp_path / expected_name file = tmp_path / expected_name
assert file.exists() assert file.exists()
@ -55,6 +59,6 @@ def assert_track(
assert f["tracknumber"].value == expected_tracknumber assert f["tracknumber"].value == expected_tracknumber
def assert_not_track(tmp_path: Path, expected_name: str): def assert_not_track(tmp_path: Path, expected_name: str) -> None:
file = tmp_path / expected_name file = tmp_path / expected_name
assert not file.exists() assert not file.exists()