Compare commits
3 Commits
404deff6e4
...
323d6c0499
Author | SHA1 | Date |
---|---|---|
|
323d6c0499 | |
|
bacf927d4b | |
|
27453f505e |
|
@ -0,0 +1,175 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
|
@ -4,11 +4,13 @@ version = "0.1.0"
|
|||
description = "music utilies"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "publicmatt", email = "git@publicmatt.com" }]
|
||||
requires-python = ">=3.13"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"click>=8.1.8",
|
||||
"joblib>=1.4.2",
|
||||
"python-dotenv>=1.0.1",
|
||||
"mpv>=1.0.7",
|
||||
"mutagen>=1.47.0",
|
||||
"requests>=2.32.3",
|
||||
"yt-dlp>=2025.1.15",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
asyncio
|
||||
shazamio
|
||||
click
|
||||
python_dotenv
|
||||
tqdm
|
||||
joblib
|
||||
mutagen
|
|
@ -1,2 +0,0 @@
|
|||
def main() -> None:
|
||||
print("Hello from music!")
|
|
@ -1,9 +1,10 @@
|
|||
from enum import Enum
|
||||
import click
|
||||
import subprocess
|
||||
# from .shazam import rename
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from click.exceptions import ClickException
|
||||
import click
|
||||
|
||||
from .save import yt
|
||||
from .stream import Stream, local, play
|
||||
|
||||
|
||||
@click.group()
|
||||
|
@ -11,43 +12,40 @@ def cli():
|
|||
pass
|
||||
|
||||
|
||||
class Stream(str, Enum):
|
||||
KEXP = "kexp"
|
||||
KUGS = "kugs"
|
||||
CLIS = "clis"
|
||||
|
||||
|
||||
@cli.command("stream")
|
||||
@click.option(
|
||||
"-s", "--stream", "stream", type=click.Choice(Stream), default=Stream.KEXP
|
||||
@click.argument(
|
||||
"stream", type=click.Choice([e.value for e in Stream]), default=Stream.KEXP
|
||||
)
|
||||
def play_stream(stream):
|
||||
"""Play the KEXP stream using mpv."""
|
||||
match stream:
|
||||
case Stream.KEXP:
|
||||
url = "https://kexp-mp3-128.streamguys1.com/kexp128.mp3"
|
||||
case Stream.KUGS:
|
||||
url = "https://peridot.streamguys1.com:7175/kugs-mp3""
|
||||
case Stream.CLIS:
|
||||
url = "https://stream2.statsradio.com:8012/stream?=&&___cb=759135934160766"
|
||||
case _:
|
||||
raise ClickException(f"unrecognized stream: {stream}")
|
||||
subprocess.run(["mpv", url])
|
||||
@click.option(
|
||||
"-t",
|
||||
"--timestamp",
|
||||
"t",
|
||||
type=click.DateTime(formats=["%Y-%m-%d %H:%M"]),
|
||||
required=False,
|
||||
)
|
||||
def _stream(stream, t: Optional[datetime]):
|
||||
"""play streams."""
|
||||
|
||||
if t is not None:
|
||||
time = t.astimezone(tz=timezone.utc)
|
||||
play(stream, time)
|
||||
|
||||
|
||||
@cli.command("random")
|
||||
def play_random():
|
||||
"""todo."""
|
||||
raise NotImplementedError("todo")
|
||||
# fd -t file -e mp3 -p -a "fall|winter|spring|summer" $HOME/Music/ | mpv --playlist=- --shuffle --no-video
|
||||
def _random():
|
||||
"""play randomly from local music."""
|
||||
local()
|
||||
|
||||
@cli.command("save")
|
||||
@click.option( "-u", "--url", "url", required=True)
|
||||
def save_song(url):
|
||||
"""todo."""
|
||||
raise NotImplementedError("todo")
|
||||
|
||||
# FILENAME=$(yt-dlp {{url}} -x --audio-format mp3 | tee /dev/tty | grep 'ExtractAudio' | cut -d ' ' -f 3-)
|
||||
# $HOME/.local/lib/shazam/bin/shazam.py rename --song "$FILENAME"
|
||||
@cli.command("yt")
|
||||
@click.argument("url", required=True)
|
||||
def _save(url):
|
||||
"""
|
||||
download a youtube song from URL to current quarter dir.
|
||||
|
||||
URL: youtube url to download
|
||||
"""
|
||||
yt(url)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
||||
def quarter_year(for_date: Optional[datetime] = None) -> Tuple[str, int]:
|
||||
if for_date is None:
|
||||
for_date = datetime.now()
|
||||
year = for_date.year
|
||||
month = for_date.month
|
||||
if month in [12, 1, 2]:
|
||||
quarter = "winter"
|
||||
elif month in [3, 4, 5]:
|
||||
quarter = "spring"
|
||||
elif month in [6, 7, 8]:
|
||||
quarter = "summer"
|
||||
else:
|
||||
quarter = "fall"
|
||||
return quarter, year
|
|
@ -0,0 +1,21 @@
|
|||
from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
from music.dates import quarter_year
|
||||
|
||||
|
||||
def seasons() -> Generator[Path, None, None]:
|
||||
prefix = ("fall_", "winter_", "spring_", "summer_")
|
||||
music_dir = Path().home() / "Music"
|
||||
for d in music_dir.iterdir():
|
||||
if not d.is_dir():
|
||||
continue
|
||||
if not d.name.startswith(prefix):
|
||||
continue
|
||||
yield d
|
||||
yield from []
|
||||
|
||||
|
||||
def current_quarter() -> Path:
|
||||
quarter, year = quarter_year()
|
||||
return Path().home() / "Music" / f"{quarter}_{year}"
|
|
@ -0,0 +1,48 @@
|
|||
from mutagen.easyid3 import EasyID3
|
||||
import yt_dlp
|
||||
from .dates import quarter_year
|
||||
from .paths import current_quarter
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def yt(url):
|
||||
ydl_opts = {
|
||||
"format": "mp3/bestaudio/best",
|
||||
# ℹ️ See help(yt_dlp.postprocessor) for a list of available Postprocessors and their arguments
|
||||
"postprocessors": [
|
||||
{ # Extract audio using ffmpeg
|
||||
"key": "FFmpegExtractAudio",
|
||||
"preferredcodec": "mp3",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
os.chdir(Path.home() / "Downloads")
|
||||
# Download the audio and get the filename
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
info_dict = ydl.extract_info(url, download=True)
|
||||
_ = ydl.prepare_filename(info_dict)
|
||||
if info_dict is None:
|
||||
raise ValueError("error downloading")
|
||||
filename = info_dict["requested_downloads"][0]["filepath"]
|
||||
|
||||
track = info_dict.get("track")
|
||||
artist = info_dict.get("artist")
|
||||
|
||||
parent = current_quarter()
|
||||
if not parent.exists():
|
||||
parent.mkdir(parents=True)
|
||||
if track is not None and artist is not None:
|
||||
name = f"{artist} - {track}.mp3"
|
||||
new_filepath = parent / name
|
||||
os.rename(filename, new_filepath)
|
||||
tags = EasyID3(new_filepath)
|
||||
tags["title"] = track
|
||||
tags["artist"] = artist
|
||||
tags.save()
|
||||
|
||||
else:
|
||||
new_filepath = parent / filename
|
||||
os.rename(filename, new_filepath)
|
|
@ -1,137 +0,0 @@
|
|||
#! /home/user/.venv/bin/python
|
||||
|
||||
import asyncio
|
||||
from shazamio import Shazam, Serialize
|
||||
from pathlib import Path
|
||||
import click
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
from tqdm import tqdm
|
||||
from typing import Optional
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from joblib import Memory
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
base_path = Path(__file__).parent.parent
|
||||
memory = Memory(base_path / "./.cache", verbose=0)
|
||||
|
||||
|
||||
class Match(BaseModel):
|
||||
old: Path
|
||||
new: Optional[Path] = Field(default=None)
|
||||
title: Optional[str] = Field(default=None)
|
||||
artist: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option(
|
||||
"-s",
|
||||
"--song",
|
||||
required=True,
|
||||
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
||||
)
|
||||
def rename(song: Path):
|
||||
change = get_match(song)
|
||||
if change and change.new is not None:
|
||||
prompt_rename(change)
|
||||
|
||||
|
||||
def songs(root: Path):
|
||||
seasons = ["winter", "spring", "summer", "fall"]
|
||||
pattern = "{}_*/*.mp3"
|
||||
matches = []
|
||||
for season in seasons:
|
||||
for file_path in root.rglob(pattern.format(season)):
|
||||
matches.append(file_path)
|
||||
|
||||
def small(f: Path, max_bytes: int = 10_000_000):
|
||||
return f.stat().st_size < max_bytes
|
||||
|
||||
return filter(small, matches)
|
||||
|
||||
|
||||
def needs_rename(song: Match):
|
||||
if song.new is None:
|
||||
print(f"\nno match: {song.old.name}\n")
|
||||
return False
|
||||
if song.old.name == song.new.name:
|
||||
print(f"\nname correct: {song.old.name}\n")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("main")
|
||||
def main():
|
||||
root = Path(os.getenv("MUSIC_PATH", "~/Music"))
|
||||
if not root.exists():
|
||||
raise ValueError(f"{str(root)} does not exist")
|
||||
|
||||
renames = []
|
||||
for song in tqdm(songs(root), position=0, leave=True):
|
||||
rename = get_match(song)
|
||||
if rename and rename.new is not None:
|
||||
renames.append(rename)
|
||||
|
||||
for song in filter(needs_rename, renames):
|
||||
prompt_rename(song)
|
||||
|
||||
sys.exit(127)
|
||||
|
||||
|
||||
def prompt_rename(song: Match):
|
||||
old = song.old
|
||||
answer = input(
|
||||
f'\nrename: "{old.name}"\nto: "{song.new.name}"?\n[y[es]/n[o]/m[anual]] > '
|
||||
)
|
||||
if answer in ["n", "N"]:
|
||||
print(f"\nskipping: {old.name}\n")
|
||||
return
|
||||
if answer in ["m"]:
|
||||
manual = input("\nnew name: ")
|
||||
new = old.parent / manual
|
||||
assert (
|
||||
new.suffix == old.suffix
|
||||
), f"filetype changed: {old.suffix} -> {new.suffix}"
|
||||
else:
|
||||
new = song.new
|
||||
try:
|
||||
old.rename(new)
|
||||
from mutagen.easyid3 import EasyID3
|
||||
|
||||
tags = EasyID3(new)
|
||||
tags["title"] = song.title
|
||||
tags["artist"] = song.artist
|
||||
tags.save()
|
||||
except FileNotFoundError as e:
|
||||
print(f"error renaming: {e}")
|
||||
print(f'old: "{old}"\nnew: "{new}"')
|
||||
return
|
||||
|
||||
|
||||
@memory.cache
|
||||
def get_match(original: Path) -> Match:
|
||||
loop = asyncio.get_event_loop()
|
||||
shazam = Shazam()
|
||||
try:
|
||||
out = loop.run_until_complete(shazam.recognize(str(original)))
|
||||
serialized = Serialize.track(out["track"])
|
||||
rename = f"{serialized.title} - {serialized.subtitle}{original.suffix}"
|
||||
new = Path(original.parent) / rename
|
||||
return Match(
|
||||
old=original, new=new, title=serialized.title, artist=serialized.subtitle
|
||||
)
|
||||
except:
|
||||
return Match(old=original, new=None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
env = Path(__file__).parent.parent / ".env"
|
||||
if not load_dotenv(env):
|
||||
raise ValueError(".env not found: {env}")
|
||||
cli()
|
|
@ -0,0 +1,67 @@
|
|||
import subprocess
|
||||
from enum import Enum
|
||||
from .paths import seasons
|
||||
import random
|
||||
import requests
|
||||
|
||||
# import mpv
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
|
||||
class Stream(str, Enum):
|
||||
KEXP = "kexp"
|
||||
KUGS = "kugs"
|
||||
CLIS = "clis"
|
||||
|
||||
|
||||
def play(stream, t=None):
|
||||
"""Play streams using mpv."""
|
||||
match stream:
|
||||
case Stream.KEXP:
|
||||
default = "https://kexp-mp3-128.streamguys1.com/kexp128.mp3"
|
||||
if t:
|
||||
tz = t.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
print(f"getting archive: {t}")
|
||||
check_url = f"https://api.kexp.org/get_streaming_url/?bitrate=128×tamp={tz}&location=1"
|
||||
response = requests.get(check_url)
|
||||
if response.status_code == 200:
|
||||
url = response.json().get("sg-url")
|
||||
if url is None:
|
||||
print(f"error getting archive: {t}")
|
||||
url = default
|
||||
else:
|
||||
url = default
|
||||
case Stream.KUGS:
|
||||
url = "https://peridot.streamguys1.com:7175/kugs-mp3"
|
||||
case Stream.CLIS:
|
||||
url = "https://stream2.statsradio.com:8012/stream?=&&___cb=759135934160766"
|
||||
case _:
|
||||
raise ValueError(f"unrecognized stream: {stream}")
|
||||
subprocess.run(["mpv", url])
|
||||
|
||||
|
||||
def local():
|
||||
"""play random local songs."""
|
||||
songs = []
|
||||
for d in seasons():
|
||||
for f in d.iterdir():
|
||||
if not f.suffix == ".mp3":
|
||||
continue
|
||||
songs.append(f)
|
||||
random.shuffle(songs)
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as tmp:
|
||||
for song in songs:
|
||||
tmp.write(f"{song}\n".encode())
|
||||
playlist = tmp.name
|
||||
# fd -t file -e mp3 -p -a "fall|winter|spring|summer" $HOME/Music/ | mpv --playlist=- --shuffle --no-video
|
||||
|
||||
try:
|
||||
subprocess.run(["mpv", f"--playlist={playlist}", "--shuffle", "--no-video"])
|
||||
# process = subprocess.Popen(
|
||||
# ["mpv", f"--playlist={playlist}", "--shuffle", "--no-video"],
|
||||
# )
|
||||
|
||||
# process.wait()
|
||||
finally:
|
||||
os.remove(playlist)
|
122
uv.lock
122
uv.lock
|
@ -1,12 +1,60 @@
|
|||
version = 1
|
||||
requires-python = ">=3.13"
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.13'",
|
||||
"python_full_version < '3.13'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.1.31"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
|
||||
wheels = [
|
||||
|
@ -23,12 +71,21 @@ wheels = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "joblib"
|
||||
version = "1.4.2"
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpv"
|
||||
version = "1.0.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/70/a9/fe30517ebfdc6a9ed5f1d03dfdf4901d078126620d6b600f0bdcc5aa665d/mpv-1.0.7.tar.gz", hash = "sha256:ae17d56176e05e4d046aa28a0732a478c0d58603e878e8da6d82b6c145ae1d82", size = 47052 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/3f/d835556e34804cd0078507ed0f8a550f15d2861b875656193dd3451b720b/mpv-1.0.7-py3-none-any.whl", hash = "sha256:520fb134c18185b69c7fce4aa3514f14371028022d92eb193818e9fefb1e9fe8", size = 45257 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -37,22 +94,59 @@ version = "0.1.0"
|
|||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "joblib" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "mpv" },
|
||||
{ name = "mutagen" },
|
||||
{ name = "requests" },
|
||||
{ name = "yt-dlp" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.1.8" },
|
||||
{ name = "joblib", specifier = ">=1.4.2" },
|
||||
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
||||
{ name = "mpv", specifier = ">=1.0.7" },
|
||||
{ name = "mutagen", specifier = ">=1.47.0" },
|
||||
{ name = "requests", specifier = ">=2.32.3" },
|
||||
{ name = "yt-dlp", specifier = ">=2025.1.15" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
name = "mutagen"
|
||||
version = "1.47.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/e6/64bc71b74eef4b68e61eb921dcf72dabd9e4ec4af1e11891bbd312ccbb77/mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99", size = 1274186 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/7a/620f945b96be1f6ee357d211d5bf74ab1b7fe72a9f1525aafbfe3aee6875/mutagen-1.47.0-py3-none-any.whl", hash = "sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719", size = 194391 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yt-dlp"
|
||||
version = "2025.1.15"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/e9/61fc5947d35e9fcdd8efca88a5a2de53946a4aaf4e6c3e23f7b1b721b4c8/yt_dlp-2025.1.15.tar.gz", hash = "sha256:e8ec515d49bb62704915d13a22ee6fe03a5658d651e4e64574e3a17ee01f6e3b", size = 2916472 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/97/73eadf12412173dc518897a2715693e6caa73d6550ddf0b7c47c6f1e7703/yt_dlp-2025.1.15-py3-none-any.whl", hash = "sha256:b8666b88e23c3fa5ee1e80920f4a9dfac7c405504a447214c0cf3d0c386edcfc", size = 3177849 },
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue