From 17b2bb1fa2488bf684c081c86ef41813a451bfc8 Mon Sep 17 00:00:00 2001 From: janis Date: Thu, 10 Mar 2022 22:50:29 +0100 Subject: [PATCH] finished base version 1.0 and as such the app is fully functional though there might still be bugs around and also some improvements will be made later on --- .idea/MusicPlayer.iml | 2 +- .idea/misc.xml | 2 +- bin/csv_parsers.py | 122 ++++++++++++++++++++++++++++++++ bin/filepathanalysis.py | 26 +++++++ bin/gui/gui.kv | 113 +++++++++++++++++++++++++++++- bin/player.py | 96 +++++++++++++++++++++++++ data/songtemp.csv | 2 + data/temp.csv | 1 + musicplayer.py | 151 +++++++++++++++++++++++++++++++++++++--- 9 files changed, 503 insertions(+), 12 deletions(-) create mode 100644 bin/csv_parsers.py create mode 100644 bin/filepathanalysis.py create mode 100644 bin/player.py create mode 100644 data/songtemp.csv create mode 100644 data/temp.csv diff --git a/.idea/MusicPlayer.iml b/.idea/MusicPlayer.iml index d0876a7..8437fe6 100644 --- a/.idea/MusicPlayer.iml +++ b/.idea/MusicPlayer.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index d1e22ec..dc9ea49 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/bin/csv_parsers.py b/bin/csv_parsers.py new file mode 100644 index 0000000..d1f593c --- /dev/null +++ b/bin/csv_parsers.py @@ -0,0 +1,122 @@ +"""@package docstring +This is a simplification of the csv module""" + +import csv + + +class CsvRead: + """This is a class that reads csv files and depending on the module selected does do different things with it""" + def __init__(self): + self.__imp = "" + self.__raw = "" + self.__raw_list = "" + + def importing(self, path): + """Returns a list of the imported csv-file, requires path, either direct system path or relative path""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__raw_list = list(self.__raw) + self.__imp.close() + return self.__raw_list + + +class CsvWrite: + """This is a class that modifies csv files""" + def __init__(self): + self.__impl = [] + self.__strpop = [] + self.__removed = [] + self.__removing = 0 + self.__change = 0 + self.__appending = 0 + self.__imp = [] + self.__raw = [] + + def rem_str(self, path, row): + """Opens the csv-file in write mode which is specified as an argument either as direct or relative path""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__impl = list(self.__raw) + self.__removed = self.__impl.pop(row + 1) + with open(path, "w") as removedata: + self.__removing = csv.writer(removedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + while len(self.__impl) > 0: + with open(path, "a") as removedata: + self.__removing = csv.writer(removedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + self.__imp.close() + removedata.close() + + + def chg_str(self, path, row, pos, new_value): + """Opens the csv-file in write mode to change a value, e.g. if a recipes is changed.""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__impl = list(self.__raw) + self.__strpop = self.__impl.pop(row) + self.__strpop.pop(pos) + self.__strpop.insert(pos, new_value) + self.__impl.insert(row, self.__strpop) + with open(path, "w") as changedata: + self.__change = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__change.writerow(self.__impl.pop(0)) + while len(self.__impl) > 0: + with open(path, "a") as changedata: + self.__removing = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + self.__imp.close() + changedata.close() + + def chg_str_rem(self, path, row, pos): + """Opens the csv-file in write mode to change a value, e.g. if a recipes is changed.""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__impl = list(self.__raw) + self.__strpop = self.__impl.pop(row) + self.__strpop.pop(pos) + self.__strpop.pop(pos) + self.__impl.insert(row, self.__strpop) + with open(path, "w") as changedata: + self.__change = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__change.writerow(self.__impl.pop(0)) + while len(self.__impl) > 0: + with open(path, "a") as changedata: + self.__removing = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + self.__imp.close() + changedata.close() + + def chg_str_add(self, path, row, new_value1, new_value2): + """Opens the csv-file in write mode to change a value, e.g. if a recipes is changed.""" + self.__imp = open(path) + self.__raw = csv.reader(self.__imp, delimiter=',') + self.__impl = list(self.__raw) + self.__strpop = self.__impl.pop(row) + self.__strpop.append(new_value1) + self.__strpop.append(new_value2) + self.__impl.insert(row, self.__strpop) + with open(path, "w") as changedata: + self.__change = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__change.writerow(self.__impl.pop(0)) + while len(self.__impl) > 0: + with open(path, "a") as changedata: + self.__removing = csv.writer(changedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__removing.writerow(self.__impl.pop(0)) + self.__imp.close() + changedata.close() + + def app_str(self, path, value): + """Opens the csv-file in append mode and writes given input. CsvWrite.app_str(path, value). + Path can be specified both as direct or relative. value is a list. Will return an error if type of value is + not a list.""" + with open(path, "a") as appenddata: + self.__appending = csv.writer(appenddata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__appending.writerow(value) + appenddata.close() + + def write_str(self, path, value): + with open(path, "w") as writedata: + self.__change = csv.writer(writedata, delimiter=',', quoting=csv.QUOTE_MINIMAL) + self.__change.writerow(value) + writedata.close() diff --git a/bin/filepathanalysis.py b/bin/filepathanalysis.py new file mode 100644 index 0000000..55c013d --- /dev/null +++ b/bin/filepathanalysis.py @@ -0,0 +1,26 @@ +import os + + +class PathAnalysis: + def __init__(self): + self.__output = [] + self.__input = [] + self.__file_extension = "" + self.__filepath = "" + self.__returns = [] + self.__names = [] + + def validsonglistcreator(self, path): + self.__input = os.listdir(path) + for self.item in self.__input: + self.__file_extension = self.item[(len(self.item) - 4):] + if self.__file_extension == ".mp3" or self.__file_extension == ".wav": + self.__filepath = str(path) + self.__filepath += f"/{str(self.item)}" + self.__names.append(self.item) + self.__output.append(self.__filepath) + else: + pass + self.__returns.append(self.__output) + self.__returns.append(self.__names) + return self.__returns diff --git a/bin/gui/gui.kv b/bin/gui/gui.kv index f37ef3b..9c78511 100644 --- a/bin/gui/gui.kv +++ b/bin/gui/gui.kv @@ -1,6 +1,11 @@ RootScreen: Home: Main: + ShowcaseS + +########### +# POPUPS +########### : title: "NOTICE!" @@ -20,6 +25,46 @@ RootScreen: on_release: root.dismiss() +: + title: "NOTICE!" + font_size: 50 + size_hint: 0.5, 0.4 + auto_dismiss: False + GridLayout: + cols:1 + Label: + text: "Path without any mp3/wav files specified" + font_size: 18 + Label: + text: "Please enter another path and try again" + font_size: 15 + Button: + text:"Ok" + on_release: + root.dismiss() + +: + title: "NOTICE!" + font_size: 50 + size_hint: 0.5, 0.4 + auto_dismiss: False + GridLayout: + cols:1 + Label: + text: "Invalid path specified" + font_size: 18 + Label: + text: "Please enter a valid path and try again" + font_size: 15 + Button: + text:"Ok" + on_release: + root.dismiss() + +########### +# SCREENS +########### + : name: "Home" md_bg_color: app.theme_cls.accent_color @@ -42,6 +87,7 @@ RootScreen: hint_text: "Path to Folder containing the Music files" pos_hint: {"x":0.2, "y":0.5} size_hint_x: 0.6 + text: "/home/janis/Downloads" Button: text: "Start" color: app.theme_cls.primary_color @@ -52,11 +98,74 @@ RootScreen: root.change_screen()
: + on_pre_enter: root.initialize() name: "Main" GridLayout: cols: 1 + GridLayout: + cols: 2 + Button: + text: "Next" + on_release: + root.nextsong() + Button: + text: "Rewind" + on_release: + root.rewindsong() Button: - text: "Back" + text: "Play" + id: pp_button on_release: - app.root.current = "Home" + root.playmusic() + GridLayout: + cols: 2 + Button: + text: "Back" + on_release: + root.go_back() + Button: + text: "Showcase" + on_release: + app.root.current = "Showcase" + root.manager.transition.direction = "left" + +: + on_pre_enter: root.screen_updater_start() + name: "Showcase" + md_bg_color: (0, 0, 0, 1) + FloatLayout: + Label: + text: "Currently Playing" + pos_hint: {"y": 0.4} + font_size: 35 + color: app.theme_cls.primary_color + MDProgressBar: + orientation: "horizontal" + value: 100 + pos_hint: {"y": 0.35} + color: app.theme_cls.primary_dark + Label: + id: current_song + text: "Currently playing Song will appear here" + pos_hint: {"y": 0.25} + font_size: 30 + color: app.theme_cls.primary_color + Label: + text: "upcoming" + font_size: 25 + color: app.theme_cls.primary_color + Label: + id: upcoming_songs + text: "Upcoming Songs will appear here" + pos_hint: {"y": -0.25} + font_size: 20 + color: app.theme_cls.primary_color + Button: + text: "back" + font_size: 10 + size_hint: 0.05, 0.05 + background_color: app.theme_cls.accent_light + on_release: + app.root.current = "Main" + root.manager.transition.direction = "right" \ No newline at end of file diff --git a/bin/player.py b/bin/player.py new file mode 100644 index 0000000..747a310 --- /dev/null +++ b/bin/player.py @@ -0,0 +1,96 @@ +import pygame.mixer as mx +import bin.csv_parsers +import copy +import bin.filepathanalysis +import pygame +import time + +pa = bin.filepathanalysis.PathAnalysis() +cvr = bin.csv_parsers.CsvRead() +cvw = bin.csv_parsers.CsvWrite() + + +class Player: + def __init__(self): + self.__running = 1 + self.event = "" + self.__recent_change = 1000000 + self.__imports = [] + self.information = [] + self.current_playing_pos = 0 + + def start_playing(self): + # initialize playing + if pygame.get_init() == True: + pass + else: + pygame.init() + self.path = cvr.importing("./data/temp.csv").pop(0) + self.__imports = pa.validsonglistcreator(self.path.pop()) + self.playlist = self.__imports.pop(0) + self.playlist_backup = copy.deepcopy(self.playlist) + self.information = self.__imports.pop(0) + mx.init() + self.current_playing = self.playlist.pop(0) + mx.music.load(self.current_playing) + mx.music.play() + mx.music.pause() + + def infoupdater(self): + self.transmission = [] + cvw.write_str("./data/songtemp.csv", [self.current_playing_pos]) + cvw.app_str("./data/songtemp.csv", self.information) + + def musicmanager(self, inst, other): + self.start_playing() + self.infoupdater() + while self.__running == 1: + if self.__recent_change < 1: + pass + else: + self.__recent_change -= 1 + # instructions from main class + if other.value == 1: + other.value = 0 + mx.music.unload() + if len(self.playlist) > 0: + pass + else: + self.playlist = copy.deepcopy(self.playlist_backup) + self.current_playing_pos = -1 + self.current_playing = self.playlist.pop(0) + self.current_playing_pos += 1 + mx.music.load(self.current_playing) + mx.music.play() + self.__recent_change = 1000000 + self.infoupdater() + + elif other.value == 2: + mx.music.rewind() + other.value = 0 + + elif other.value == 3: + self.__recent_change = 1000000 + other.value = 0 + else: + if inst.value == 1: + mx.music.unpause() + else: + mx.music.pause() + # Main event-checking part + if mx.music.get_busy() is False and inst.value == 1 and self.__recent_change == 0: + mx.music.unload() + if len(self.playlist) > 0: + pass + else: + self.playlist = copy.deepcopy(self.playlist_backup) + self.current_playing_pos = -1 + self.current_playing = self.playlist.pop(0) + self.current_playing_pos += 1 + mx.music.load(self.current_playing) + mx.music.play() + self.__recent_change = 10000000 + self.infoupdater() + else: + pass + diff --git a/data/songtemp.csv b/data/songtemp.csv new file mode 100644 index 0000000..304c865 --- /dev/null +++ b/data/songtemp.csv @@ -0,0 +1,2 @@ +0 +Metaheuristic - Freedom Trail Studio.mp3,Heal You - Freedom Trail Studio.mp3,Straw Squeak.mp3 diff --git a/data/temp.csv b/data/temp.csv new file mode 100644 index 0000000..0ace8cd --- /dev/null +++ b/data/temp.csv @@ -0,0 +1 @@ +/home/janis/Downloads diff --git a/musicplayer.py b/musicplayer.py index 187dbfb..312195a 100644 --- a/musicplayer.py +++ b/musicplayer.py @@ -1,10 +1,26 @@ -import playsound as ps +import multiprocessing import os +from kivy.core.window import Window, Config from kivy.uix.screenmanager import ScreenManager from kivymd.uix.screen import MDScreen from kivymd.app import MDApp from kivy.base import Builder from kivy.uix.popup import Popup +from kivy.clock import Clock +import bin.csv_parsers +import bin.filepathanalysis +import bin.player +import pygame +import pygame.mixer as mx +import copy +import time + + + +pl = bin.player.Player() +pa = bin.filepathanalysis.PathAnalysis() +cvr = bin.csv_parsers.CsvRead() +cvw = bin.csv_parsers.CsvWrite() ########### @@ -16,6 +32,14 @@ class PathMissingPU(Popup): pass +class PathWrongPU(Popup): + pass + + +class invalidpathPU(Popup): + pass + + ########### # SCREENS ########### @@ -24,23 +48,128 @@ class PathMissingPU(Popup): class Home(MDScreen): def change_screen(self): if self.ids.filepath.text != "": - self.manager.current = "Main" - self.manager.transition.directio = "right" + self.analyse_dir() else: self.openpathmpu() + def analyse_dir(self): + try: + self.__fcontent = os.listdir(self.ids.filepath.text) + self.__good_files = 0 + for self.item in self.__fcontent: + self.__filextension = self.item[(len(self.item) - 4):] + if self.__filextension == ".mp3" or self.__filextension == ".wav": + self.__good_files += 1 + else: + pass + # self.__good_files = 1 + if self.__good_files > 0: + cvw.write_str("./data/temp.csv", [self.ids.filepath.text]) + self.manager.current = "Main" + self.manager.transition.direction = "left" + else: + self.openpathfpu() + except: + self.ivpathpu() + def openpathmpu(self): self.pmpu = PathMissingPU() self.pmpu.open() -class Main(MDScreen): - pass + def openpathfpu(self): + self.wppu = PathWrongPU() + self.wppu.open() + def ivpathpu(self): + self.ivppu = invalidpathPU() + self.ivppu.open() + + +class Main(MDScreen): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.instructions = multiprocessing.Value('i', 0) + self.others = multiprocessing.Value('i', 0) + self.keyboard = Window.request_keyboard(None, self) + self.keyboard.bind(on_key_down=self.key_pressed) + self.quit_requests = 0 + + def key_pressed(self, keyboard, keycode, text, modifiers): + # print(keycode[1]) + self.key = keycode[1] + if self.key == "spacebar": + self.playmusic() + elif self.key == "right": + self.nextsong() + elif self.key == "left": + self.rewindsong() + else: + pass + + def initialize(self): + try: + if self.mplayer.is_alive() == True: + pass + else: + self.mplayer = multiprocessing.Process(name="player", target=pl.musicmanager, args=(self.instructions, self.others,)) + self.mplayer.start() + except: + self.mplayer = multiprocessing.Process(name="player", target=pl.musicmanager, args=(self.instructions, self.others,)) + self.mplayer.start() + + def playmusic(self): + self.others.value = 3 + if self.instructions.value == 0: + self.instructions.value = 1 + self.ids.pp_button.text = "Pause" + else: + self.instructions.value = 0 + self.ids.pp_button.text = "Play" + + def nextsong(self): + self.others.value = 1 + + def rewindsong(self): + self.others.value = 2 + + def go_back(self): + try: + self.mplayer.kill() + except: + pass + self.manager.current = "Home" + self.manager.transition.direction = "right" + +class ShowcaseS(MDScreen): + def screen_updater_start(self): + Clock.schedule_interval(self.screen_updating, 2) + + + def screen_updating(self, waste): + self.__info = cvr.importing("./data/songtemp.csv") + self.__currents_imp = self.__info.pop(0) + self.__currents = int(self.__currents_imp.pop(0)) + self.__upcoming = self.__info.pop(0) + self.__current = self.__upcoming.pop(self.__currents) + self.ids.current_song.text = self.__current[:(len(self.__current) - 4)] + if len(self.__upcoming) <= self.__currents: + self.ids.upcoming_songs.text = "No more songs in Queue" + else: + self.__upcoming2 = str(self.__upcoming.pop(self.__currents)) + self.__upcoming_output = self.__upcoming2[:(len(self.__upcoming2) - 4)] + self.__length_output = 0 + for i in range(len(self.__upcoming) - self.__currents): + if self.__length_output > 5: + pass + else: + self.__upcoming2 = str(self.__upcoming.pop(self.__currents)) + self.__upcoming_output += f"\n{self.__upcoming2[:(len(self.__upcoming2) - 4)]}" + self.__length_output += 1 + self.ids.upcoming_songs.text = self.__upcoming_output class RootScreen(ScreenManager): pass - class MusicPlayer(MDApp): def build(self): self.title = "MusicPlayer" @@ -50,6 +179,12 @@ class MusicPlayer(MDApp): return Builder.load_file("./bin/gui/gui.kv") if __name__ == "__main__": + Config.set('graphics', 'width', '800') + Config.set('graphics', 'height', '600') + Config.set('graphics', 'resizable', True) + Config.set('kivy', 'exit_on_escape', '0') + Config.set('input', 'mouse', 'mouse,multitouch_on_demand') + Config.set('graphics', 'window_state', 'normal') + Config.set('graphics', 'fullscreen', False) + Config.write() MusicPlayer().run() - -# ps.playsound("/mnt/sda3/Music/Videos/Songs/Ancient.mp3")