diff --git a/.gitignore b/.gitignore index b6e4761..c51108e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,129 +1,3 @@ -# 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/ -pip-wheel-metadata/ -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/ - -# 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 -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.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 - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__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/ +/tmp/* +__pycache__ +*.pyc \ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..b795cd9 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,8 @@ +1.1.0 + - Using classes for organization (@gustavo-bordin) + - Pausing/Playing with the same button (@gustavo-bordin) + - Caching the musics directory (@gustavo-bordin) + - Adjusted paths to work in unix-based systems (@gustavo-bordin) + +1.0.0 + - First working version (@Jhonatan-de-Souza) \ No newline at end of file diff --git a/Images/pillar.jpg b/Images/pillar.jpg deleted file mode 100644 index 8396d0c..0000000 Binary files a/Images/pillar.jpg and /dev/null differ diff --git a/Images/play_button.png b/Images/play_button.png deleted file mode 100644 index 21de3a4..0000000 Binary files a/Images/play_button.png and /dev/null differ diff --git a/README.md b/README.md index e7ce7a0..7d005ae 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PySimpleGUI Music Player Um player de música feito 100% em python e PySimpleGUI -![Music Player](https://i.ibb.co/v38GPKX/Screenshot-5.png) +![Music Player](https://i.ibb.co/jWX52DD/Screenshot-from-2021-06-05-20-04-20.png) # Como usar(How to use) diff --git a/app.py b/app.py index 8846bd8..12124ea 100644 --- a/app.py +++ b/app.py @@ -1,110 +1,31 @@ import PySimpleGUI as sg -import os -from utils.music_utilities import get_files_inside_directory_not_recursive, play_sound, is_sound_playing, pause_sounds, stop_sounds, unpause -sg.theme('Reddit') -song_title_column = [ - [sg.Text(text='Press play..', justification='center', background_color='black', - text_color='white', size=(200, 0), font='Tahoma', key='song_name')] -] +from utils.interface_utilities import InterfaceUtilities +from utils.music_events import MusicEvents -player_info = [ - [sg.Text('PySimpleGUI Player - By youtube.com/devaprender', - background_color='black', text_color='white', font=('Tahoma', 7))] -] -currently_playing = [ - [sg.Text(background_color='black', size=(200, 0), text_color='white', - font=('Tahoma', 10), key='currently_playing')] -] +class Core(MusicEvents): + def __init__(cls): + sg.theme('Reddit') + interface_utilities = InterfaceUtilities() + cls.window = interface_utilities.create_window() + super().__init__(cls.window) + cls._start() -GO_BACK_IMAGE_PATH = 'Images\\back.png' -GO_FORWARD_IMAGE_PATH = 'Images\\next.png' -PLAY_SONG_IMAGE_PATH = 'Images\\play_button.png' -PAUSE_SONG_IMAGE_PATH = 'Images\\pause.png' -ALBUM_COVER_IMAGE_PATH = 'Images\\pylot.png' + def _start(cls): + while True: + event, _ = cls.window.read() -main = [ - [sg.Canvas(background_color='black', size=(480, 20), pad=None)], - [sg.Column(layout=player_info, justification='c', - element_justification='c', background_color='black')], - [ - sg.Canvas(background_color='black', size=(40, 350), pad=None), - sg.Image(filename=ALBUM_COVER_IMAGE_PATH, - size=(350, 350), pad=None), - sg.Canvas(background_color='black', size=(40, 350), pad=None) - ], - [sg.Canvas(background_color='black', size=(480, 10), pad=None)], - [sg.Column(song_title_column, background_color='black', - justification='c', element_justification='c')], - [sg.Text('_'*80, background_color='black', text_color='white')], - [ - sg.Canvas(background_color='black', size=(99, 200), pad=(0, 0)), - sg.Image(pad=(10, 0), filename=GO_BACK_IMAGE_PATH, enable_events=True, - size=(35, 44), key='previous', background_color='black'), - sg.Image(filename=PLAY_SONG_IMAGE_PATH, - size=(64, 64), pad=(10, 0), enable_events=True, key='play', background_color='black'), - sg.Image(filename=PAUSE_SONG_IMAGE_PATH, - size=(58, 58), pad=(10, 0), enable_events=True, key='pause', background_color='black'), - sg.Image(filename=GO_FORWARD_IMAGE_PATH, enable_events=True, - size=(35, 44), pad=(10, 0), key='next', background_color='black'), - ], - [sg.Column(layout=currently_playing, justification='c', - element_justification='c', background_color='black', pad=None)] + if event == sg.WIN_CLOSED: + break + elif event == 'switch_state': + cls.switch_state() + + elif event == 'next': + cls.next() -] -window = sg.Window('Spotify', layout=main, size=( - 480, 730), background_color='black', finalize=True, grab_anywhere=True, resizable=False,) + elif event == 'previous': + cls.previous() -directory = sg.popup_get_folder('Select Music Directory') - -songs_in_directory = get_files_inside_directory_not_recursive(directory) -song_count = len(songs_in_directory) -current_song_index = 0 - - -def update_song_display(): - window['song_name'].update(os.path.basename( - songs_in_directory[current_song_index])) - window['currently_playing'].update( - f'Playing: {os.path.basename(songs_in_directory[current_song_index])}') - - -while True: - event, values = window.read() - if event == sg.WIN_CLOSED: - break - elif event == 'play': - if is_sound_playing(): - pass - if is_sound_playing() == False: - play_sound(songs_in_directory[current_song_index]) - update_song_display() - - elif event == 'pause': - if is_sound_playing(): - pause_sounds() - else: - unpause() - pass - - elif event == 'next': - if current_song_index + 1 < song_count: - stop_sounds() - current_song_index += 1 - play_sound(songs_in_directory[current_song_index]) - update_song_display() - - else: - print('Reached last song') - pass - - elif event == 'previous': - if current_song_index + 1 <= song_count and current_song_index > 0: - stop_sounds() - current_song_index -= 1 - play_sound(songs_in_directory[current_song_index]) - update_song_display() - else: - print('Reached first song') +Core() \ No newline at end of file diff --git a/Images/back.png b/images/back.png similarity index 100% rename from Images/back.png rename to images/back.png diff --git a/Images/next.png b/images/next.png similarity index 100% rename from Images/next.png rename to images/next.png diff --git a/Images/pause.png b/images/pause.png similarity index 100% rename from Images/pause.png rename to images/pause.png diff --git a/Images/pillar.png b/images/pillar.png similarity index 100% rename from Images/pillar.png rename to images/pillar.png diff --git a/images/play.png b/images/play.png new file mode 100644 index 0000000..3797f92 Binary files /dev/null and b/images/play.png differ diff --git a/Images/pylot.png b/images/pylot.png similarity index 100% rename from Images/pylot.png rename to images/pylot.png diff --git a/Images/war of ages.png b/images/war of ages.png similarity index 100% rename from Images/war of ages.png rename to images/war of ages.png diff --git a/Music/1. Oh Boy.wav b/music/1. Oh Boy.wav similarity index 100% rename from Music/1. Oh Boy.wav rename to music/1. Oh Boy.wav diff --git a/Music/13. Nova.wav b/music/13. Nova.wav similarity index 100% rename from Music/13. Nova.wav rename to music/13. Nova.wav diff --git a/Music/4. Ghost Drip.wav b/music/4. Ghost Drip.wav similarity index 100% rename from Music/4. Ghost Drip.wav rename to music/4. Ghost Drip.wav diff --git a/Music/5. Hackerman.wav b/music/5. Hackerman.wav similarity index 100% rename from Music/5. Hackerman.wav rename to music/5. Hackerman.wav diff --git a/Music/7. Keanu Sleeves.wav b/music/7. Keanu Sleeves.wav similarity index 100% rename from Music/7. Keanu Sleeves.wav rename to music/7. Keanu Sleeves.wav diff --git a/utils/cache.py b/utils/cache.py new file mode 100644 index 0000000..5d98ed5 --- /dev/null +++ b/utils/cache.py @@ -0,0 +1,16 @@ +class Cache: + USER_CACHE_DIR = "tmp/user.txt" + + def save(cls, key, value): + with open(cls.USER_CACHE_DIR, 'w') as cache: + cache.write(f"{key}:{value}") + + def read(cls, key): + try: + with open(cls.USER_CACHE_DIR, 'r') as cache: + for line in cache.readlines(): + if key in line: + value = line.split(':')[1] + return value + except: + return None \ No newline at end of file diff --git a/utils/interface_events.py b/utils/interface_events.py new file mode 100644 index 0000000..d2fedc2 --- /dev/null +++ b/utils/interface_events.py @@ -0,0 +1,20 @@ +from utils.interface_utilities import InterfaceUtilities + + +class InterfaceEvents(InterfaceUtilities): + def __init__(self, window): + self.window = window + + def music_has_changed(self, new_name): + self.window['music_name'].update(new_name) + self.window['currently_playing'].update(f'Playing: {new_name}') + + def pressed_switch_button(self, new_state): + switch_button = self.window['switch_state'] + + if new_state == 'PAUSE': + switch_button.update(filename=self.PLAY_MUSIC_IMAGE_PATH) + else: + switch_button.update(filename=self.PAUSE_MUSIC_IMAGE_PATH) + + \ No newline at end of file diff --git a/utils/interface_utilities.py b/utils/interface_utilities.py new file mode 100644 index 0000000..6fec88d --- /dev/null +++ b/utils/interface_utilities.py @@ -0,0 +1,104 @@ +import os + +import PySimpleGUI as sg + + +class InterfaceUtilities: + GO_BACK_IMAGE_PATH = os.path.join('images', 'back.png') + GO_FORWARD_IMAGE_PATH = os.path.join('images', 'next.png') + PLAY_MUSIC_IMAGE_PATH = os.path.join('images', 'play.png') + PAUSE_MUSIC_IMAGE_PATH = os.path.join('images', 'pause.png') + album_cover_image_path = os.path.join('images', 'pylot.png') + + def _create_main_layout(cls): + music_title = [ + [sg.Text(text='Press play...', + justification='center', + background_color='black', + text_color='white', + size=(200, 0), + font='Tahoma', + key='music_name')] + ] + + author_info = [ + [sg.Text(text='PySimpleGUI Player - By youtube.com/devaprender', + background_color='black', + text_color='white', + font=('Tahoma', 7))] + ] + + currently_playing = [ + [sg.Text(background_color='black', + size=(200, 0), + text_color='white', + font=('Tahoma', 10), + key='currently_playing')] + ] + + main_layout = [ + [sg.Canvas(background_color='black', size=(480, 20))], + [sg.Column(layout=author_info, + justification='c', + element_justification='c', + background_color='black')], + + [ + sg.Canvas(background_color='black', size=(40, 350)), + sg.Image(filename=cls.album_cover_image_path, + size=(350, 350), + key="cover"), + sg.Canvas(background_color='black', size=(40, 350)) + ], + + [sg.Canvas(background_color='black', size=(480, 10))], + [sg.Column(layout=music_title, + background_color='black', + justification='c', + element_justification='c')], + [sg.Text(text= '_' * 80, background_color='black', text_color='white')], + + [ + sg.Canvas(background_color='black', size=(99, 200), pad=(10, 0)), + + sg.Image(pad=(20, 0), + filename=cls.GO_BACK_IMAGE_PATH, + enable_events=True, + size=(35, 44), + key='previous', + background_color='black'), + + sg.Image(filename=cls.PLAY_MUSIC_IMAGE_PATH, + size=(35, 44), + pad=(20, 0), + enable_events=True, + key='switch_state', + background_color='black'), + + sg.Image(filename=cls.GO_FORWARD_IMAGE_PATH, + enable_events=True, + size=(35, 44), + pad=(20, 0), + key='next', + background_color='black'), + ], + + [sg.Column(layout=currently_playing, + justification='c', + element_justification='c', + background_color='black')] + ] + + return main_layout + + def create_window(cls): + main_layout = cls._create_main_layout() + window = sg.Window('Spotify', + layout=main_layout, + size=(480, 730), + background_color='black', + finalize=True, + grab_anywhere=True, + resizable=False) + + return window \ No newline at end of file diff --git a/utils/music_events.py b/utils/music_events.py new file mode 100644 index 0000000..64fca07 --- /dev/null +++ b/utils/music_events.py @@ -0,0 +1,64 @@ +from pygame import mixer + +from utils.interface_events import InterfaceEvents +from utils.music_utilities import MusicUtilities + + +class MusicEvents(MusicUtilities): + def __init__(cls, window): + cls.interface_events = InterfaceEvents(window) + super().__init__() + + def _play(cls): + cls.interface_events.pressed_switch_button("PLAY") + cls.interface_events.music_has_changed(cls.current_music_name) + mixer.music.load(cls.current_music) + mixer.music.play() + + def _stop(cls): + mixer.music.stop() + + def _pause(cls): + cls.is_paused = True + mixer.music.pause() + + def _unpause(cls): + cls.is_paused = False + mixer.music.unpause() + + def next(cls): + cls._stop() + + if cls.current_playing_index + 1 < cls.musics_count: + cls.current_playing_index += 1 + else: + print('Reached last music, starting again') + cls.current_playing_index = 0 + + cls._play() + + def previous(cls): + cls._stop() + + if cls.current_playing_index - 1 < 0: + print('Reached first music, playing the last one') + cls.current_playing_index = cls.musics_count - 1 + else: + cls.current_playing_index -= 1 + + cls._play() + + def switch_state(cls): + if cls.is_playing == True: + cls.interface_events.pressed_switch_button("PAUSE") + cls._pause() + + elif cls.is_paused == True: + cls.interface_events.pressed_switch_button("PLAY") + cls._unpause() + + else: + cls._play() + + + \ No newline at end of file diff --git a/utils/music_utilities.py b/utils/music_utilities.py index b8f9254..c1686d1 100644 --- a/utils/music_utilities.py +++ b/utils/music_utilities.py @@ -1,38 +1,50 @@ -import os -from pygame import mixer -mixer.init() - - -def get_files_inside_directory_not_recursive(directory): - directories = [] - for (root, dirs, files) in os.walk(directory): - for file in files: - directories.append(root + os.sep + file) - - return directories - - -def play_sound(sound_path): - mixer.music.load(sound_path) - mixer.music.play() - - -def stop_sounds(): - mixer.music.stop() - - -def pause_sounds(): - mixer.music.pause() - - -def unpause(): - mixer.music.unpause() - - -def is_sound_playing(): - if mixer.music.get_busy() == True: - return True - return False - - - +import os + +import PySimpleGUI as sg +from pygame import mixer + +from utils.cache import Cache + +class MusicUtilities: + def __init__(cls): + mixer.init() + cls.musics = [] + cls.musics_count = 0 + cls.current_playing_index = 0 + cls.is_paused = False + cls.musics_dir_key = "md" + cls._load_music_directories() + + def _get_musics_inside_directory(cls, directory): + directories = [] + + for (root, _, files) in os.walk(directory): + for file in files: + directories.append(root + os.sep + file) + + return directories + + def _load_music_directories(cls): + cache = Cache() + directory = cache.read(cls.musics_dir_key) + + if directory == None: + directory = sg.popup_get_folder('Select Music Directory') + cache.save(cls.musics_dir_key, directory) + + cls.musics = cls._get_musics_inside_directory(directory) + cls.musics_count = len(cls.musics) + + @property + def current_music(cls): + return cls.musics[cls.current_playing_index] + + @property + def current_music_name(cls): + return os.path.basename(cls.current_music) + + @property + def is_playing(cls): + if mixer.music.get_busy() == True: + return True + return False \ No newline at end of file