diff --git a/poetry.lock b/poetry.lock index 97e1759..41055d2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -360,6 +360,17 @@ python-dotenv = ">=0.21.0" toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] +[[package]] +name = "pydub" +version = "0.25.1" +description = "Manipulate audio with an simple and easy high level interface" +optional = false +python-versions = "*" +files = [ + {file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"}, + {file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"}, +] + [[package]] name = "pygame" version = "2.5.2" @@ -428,17 +439,16 @@ files = [ [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] @@ -750,4 +760,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "fa2d0875e4934b7e12675cec0d7781e12b1f0c938a8d3407193ad6179e1ef20a" +content-hash = "1270371bc043d87d38fc0a032dbdbc75a93e7413f053d02f16eff11e6587d04d" diff --git a/pyproject.toml b/pyproject.toml index 37f7bdd..538de36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,10 @@ version = "0.5.3" description = "CLI app to manage, search, and play collections of mp3 tracks." authors = ["Kinuax "] license = "MIT" -readme = "README.rst" -homepage = "https://github.com/kinuax/rolabesti" -repository = "https://github.com/kinuax/rolabesti" -documentation = "https://github.com/kinuax/rolabesti" +readme = "README.md" +homepage = "https://github.com/kinuax/rolabesti/" +repository = "https://github.com/kinuax/rolabesti/" +documentation = "https://github.com/kinuax/rolabesti/" keywords = ["mp3", "id3", "vlc", "mongo"] classifiers = [ "Development Status :: 4 - Beta", @@ -34,6 +34,7 @@ mutagen = "1.47.0" platformdirs = "4.2.0" pydantic = "2.7.0" pydantic-settings = "2.2.1" +pydub = "0.25.1" pygame = "2.5.2" pymongo = "4.6.2" tinydb = "4.8.0" diff --git a/rolabesti/views/tracklist.py b/rolabesti/views/tracklist.py index f68b8fd..b6b8f21 100644 --- a/rolabesti/views/tracklist.py +++ b/rolabesti/views/tracklist.py @@ -5,7 +5,7 @@ import typer -from .utils import play_audio +from .utils import play_mp3 from rolabesti.models import FIELD_FILTERS, Track from rolabesti.logger import Logger from rolabesti.utils import length_to_string @@ -37,7 +37,7 @@ def play(self, cli: bool, overlap_length: int) -> None: if cli: for i, track in enumerate(self.tracks): self.logger.log(f"[green]playing --> [/green]{track}") - play_audio(track.path) + play_mp3(track.path, i % 2) # Adjust waiting_length. Filter out last track and too short tracks. if i < self.count - 1 and overlap_length < track.length: diff --git a/rolabesti/views/utils.py b/rolabesti/views/utils.py index 72475dc..af336f3 100644 --- a/rolabesti/views/utils.py +++ b/rolabesti/views/utils.py @@ -1,6 +1,10 @@ +import tempfile from contextlib import contextmanager, redirect_stdout from pathlib import Path +from pydub import AudioSegment + + try: from wurlitzer import pipes except ModuleNotFoundError: @@ -15,10 +19,17 @@ def pipes(): import pygame -def play_audio(trackpath: Path) -> None: - """Play the audio file located at trackpath.""" +def play_mp3(trackpath: Path, channel: int) -> None: + """Play the track located at trackpath on the given channel.""" pygame.mixer.init() + channel = pygame.mixer.Channel(channel) # Avoid messages from C libraries. with pipes(): - pygame.mixer.music.load(trackpath) - pygame.mixer.music.play() + try: + sound = pygame.mixer.Sound(trackpath) + except pygame.error: + # Convert mp3 to wav if "Unrecognized audio format" error. + with tempfile.TemporaryFile() as wav_file: + AudioSegment.from_mp3(trackpath).export(wav_file, format="wav") + sound = pygame.mixer.Sound(wav_file) + channel.play(sound)