├── .gitignore ├── README.md ├── music_player └── music_player.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | venv/ 4 | tags 5 | 6 | # Created by https://www.gitignore.io/api/pydev,django,python,pycharm 7 | 8 | ### Django ### 9 | *.log 10 | *.pot 11 | *.pyc 12 | __pycache__/ 13 | local_settings.py 14 | db.sqlite3 15 | media 16 | 17 | ### PyCharm ### 18 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 19 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 20 | 21 | # User-specific stuff: 22 | .idea/**/workspace.xml 23 | .idea/**/tasks.xml 24 | .idea/dictionaries 25 | 26 | # Sensitive or high-churn files: 27 | .idea/**/dataSources/ 28 | .idea/**/dataSources.ids 29 | .idea/**/dataSources.xml 30 | .idea/**/dataSources.local.xml 31 | .idea/**/sqlDataSources.xml 32 | .idea/**/dynamic.xml 33 | .idea/**/uiDesigner.xml 34 | 35 | # Gradle: 36 | .idea/**/gradle.xml 37 | .idea/**/libraries 38 | 39 | # CMake 40 | cmake-build-debug/ 41 | 42 | # Mongo Explorer plugin: 43 | .idea/**/mongoSettings.xml 44 | 45 | ## File-based project format: 46 | *.iws 47 | 48 | ## Plugin-specific files: 49 | 50 | # IntelliJ 51 | /out/ 52 | 53 | # mpeltonen/sbt-idea plugin 54 | .idea_modules/ 55 | 56 | # JIRA plugin 57 | atlassian-ide-plugin.xml 58 | 59 | # Cursive Clojure plugin 60 | .idea/replstate.xml 61 | 62 | # Crashlytics plugin (for Android Studio and IntelliJ) 63 | com_crashlytics_export_strings.xml 64 | crashlytics.properties 65 | crashlytics-build.properties 66 | fabric.properties 67 | 68 | ### PyCharm Patch ### 69 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 70 | 71 | # *.iml 72 | # modules.xml 73 | # .idea/misc.xml 74 | # *.ipr 75 | 76 | # Sonarlint plugin 77 | .idea/sonarlint 78 | 79 | ### pydev ### 80 | .pydevproject 81 | 82 | ### Python ### 83 | # Byte-compiled / optimized / DLL files 84 | *.py[cod] 85 | *$py.class 86 | 87 | # C extensions 88 | *.so 89 | 90 | # Distribution / packaging 91 | .Python 92 | env/ 93 | build/ 94 | develop-eggs/ 95 | dist/ 96 | downloads/ 97 | eggs/ 98 | .eggs/ 99 | lib/ 100 | lib64/ 101 | parts/ 102 | sdist/ 103 | var/ 104 | wheels/ 105 | *.egg-info/ 106 | .installed.cfg 107 | *.egg 108 | 109 | # PyInstaller 110 | # Usually these files are written by a python script from a template 111 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 112 | *.manifest 113 | *.spec 114 | 115 | # Installer logs 116 | pip-log.txt 117 | pip-delete-this-directory.txt 118 | 119 | # Unit test / coverage reports 120 | htmlcov/ 121 | .tox/ 122 | .coverage 123 | .coverage.* 124 | .cache 125 | nosetests.xml 126 | coverage.xml 127 | *,cover 128 | .hypothesis/ 129 | 130 | # Translations 131 | *.mo 132 | 133 | # Django stuff: 134 | 135 | # Flask stuff: 136 | instance/ 137 | .webassets-cache 138 | 139 | # Scrapy stuff: 140 | .scrapy 141 | 142 | # Sphinx documentation 143 | docs/_build/ 144 | 145 | # PyBuilder 146 | target/ 147 | 148 | # Jupyter Notebook 149 | .ipynb_checkpoints 150 | 151 | # pyenv 152 | .python-version 153 | 154 | # celery beat schedule file 155 | celerybeat-schedule 156 | 157 | # SageMath parsed files 158 | *.sage.py 159 | 160 | # dotenv 161 | .env 162 | 163 | # virtualenv 164 | .venv 165 | venv/ 166 | ENV/ 167 | 168 | # Spyder project settings 169 | .spyderproject 170 | .spyproject 171 | 172 | # Rope project settings 173 | .ropeproject 174 | 175 | # mkdocs documentation 176 | /site 177 | .idea 178 | exam/exam5.sqlite3 179 | TwitterApp/twitterdb.sqlite3 180 | 181 | # End of https://www.gitignore.io/api/pydev,django,python,pycharm 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GUI mp3 player 2 | 3 | This GUI program is a mp3 music player made by means of Tkinter, Pygame and Mutagen. 4 | If you want to use this program install modules specified in requirements.txt. 5 | To run start music_player.py with python. 6 | 7 | Help: 8 | * to add songs to playlist click 'add to list' button and choose mp3 files to add (you can choose multiple files) 9 | * to play click 'play' button 10 | * to start/stop music click 'toggle' button 11 | * to play next/previous song click 'next'/'previous' button 12 | 13 | After current song ends, next song on the list will be played. 14 | -------------------------------------------------------------------------------- /music_player/music_player.py: -------------------------------------------------------------------------------- 1 | from mutagen.easyid3 import EasyID3 2 | import pygame 3 | from tkinter.filedialog import * 4 | from tkinter import * 5 | 6 | pygame.init() 7 | 8 | 9 | class FrameApp(Frame): 10 | def __init__(self,master): 11 | super(FrameApp, self).__init__(master) 12 | 13 | self.grid() 14 | self.paused = False 15 | self.playlist = list() 16 | self.actual_song = 0 17 | 18 | self.b1 = Button(self, text="play", command=self.play_music, width=20) 19 | self.b1.grid(row=1, column=0) 20 | 21 | self.b2 = Button(self, text="previous", command=self.previous_song, 22 | width=20) 23 | self.b2.grid(row=2, column=0) 24 | 25 | self.b3 = Button(self, text="toggle", command=self.toggle, width=20) 26 | self.b3.grid(row=3, column=0) 27 | 28 | self.b4 = Button(self, text="next", command=self.next_song, width=20) 29 | self.b4.grid(row=4, column=0) 30 | 31 | self.b5 = Button(self, text="add to list", command=self.add_to_list, 32 | width=20) 33 | self.b5.grid(row=5, column=0) 34 | 35 | self.label1 = Label(self) 36 | self.label1.grid(row=6, column=0) 37 | 38 | self.output = Text(self, wrap=WORD, width=50) 39 | self.output.grid(row=8, column=0) 40 | 41 | # set event to not predefined value in pygame 42 | self.SONG_END = pygame.USEREVENT + 1 43 | 44 | # TODO: Make progressbar, delete songs from playlist, amplify volume 45 | 46 | def add_to_list(self): 47 | """ 48 | Opens window to browse data on disk and adds selected songs to play list 49 | :return: None 50 | """ 51 | directory = askopenfilenames() 52 | # appends song directory on disk to playlist in memory 53 | for song_dir in directory: 54 | print(song_dir) 55 | self.playlist.append(song_dir) 56 | self.output.delete(0.0, END) 57 | 58 | for key, item in enumerate(self.playlist): 59 | # appends song to textbox 60 | song = EasyID3(item) 61 | song_data = (str(key + 1) + ' : ' + song['title'][0] + ' - ' 62 | + song['artist'][0]) 63 | self.output.insert(END, song_data + '\n') 64 | 65 | def song_data(self): 66 | """ 67 | Makes string of current playing song data over the text box 68 | :return: string - current song data 69 | """ 70 | song = EasyID3(self.playlist[self.actual_song]) 71 | song_data = "Now playing: Nr:" + str(self.actual_song + 1) + " " + \ 72 | str(song['title']) + " - " + str(song['artist']) 73 | return song_data 74 | 75 | def play_music(self): 76 | """ 77 | Loads current song, plays it, sets event on song finish 78 | :return: None 79 | """ 80 | directory = self.playlist[self.actual_song] 81 | pygame.mixer.music.load(directory) 82 | pygame.mixer.music.play(1, 0.0) 83 | pygame.mixer.music.set_endevent(self.SONG_END) 84 | self.paused = False 85 | self.label1['text'] = self.song_data() 86 | 87 | def check_music(self): 88 | """ 89 | Listens to END_MUSIC event and triggers next song to play if current 90 | song has finished 91 | :return: None 92 | """ 93 | for event in pygame.event.get(): 94 | if event.type == self.SONG_END: 95 | self.next_song() 96 | 97 | def toggle(self): 98 | """ 99 | Toggles current song 100 | :return: None 101 | """ 102 | if self.paused: 103 | pygame.mixer.music.unpause() 104 | self.paused = False 105 | elif not self.paused: 106 | pygame.mixer.music.pause() 107 | self.paused = True 108 | 109 | def get_next_song(self): 110 | """ 111 | Gets next song number on playlist 112 | :return: int - next song number 113 | """ 114 | if self.actual_song + 2 <= len(self.playlist): 115 | return self.actual_song + 1 116 | else: 117 | return 0 118 | 119 | def next_song(self): 120 | """ 121 | Plays next song 122 | :return: None 123 | """ 124 | self.actual_song = self.get_next_song() 125 | self.play_music() 126 | 127 | def get_previous_song(self): 128 | """ 129 | Gets previous song number on playlist and returns it 130 | :return: int - prevoius song number on playlist 131 | """ 132 | if self.actual_song - 1 >= 0: 133 | return self.actual_song - 1 134 | else: 135 | return len(self.playlist) - 1 136 | 137 | def previous_song(self): 138 | """ 139 | Plays prevoius song 140 | :return: 141 | """ 142 | self.actual_song = self.get_previous_song() 143 | self.play_music() 144 | 145 | 146 | root = Tk() 147 | root.geometry("350x500") 148 | app = FrameApp(root) 149 | 150 | while True: 151 | # runs mainloop of program 152 | app.check_music() 153 | app.update() 154 | 155 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mutagen==1.38 2 | pygame==1.9.3 3 | --------------------------------------------------------------------------------