music_player_flet
/
music_player.py
482 строки · 18.9 Кб
1import flet as ft2import os3import shutil4from tinytag import TinyTag5from math import pi6import asyncio7
8class MusicPlayer:9def __init__(self, page):10self.page = page11self.index = 012self.state = ""13self.audio_controls = []14self.tracks_list = [15"sounds/outfoxing.mp3",16"sounds/param_viper.mp3",17"sounds/track_drums.mp3",18]19self.volume = 0.520self.sounds_folder = 'sounds'21if not os.path.exists(self.sounds_folder):22os.makedirs(self.sounds_folder)23
24# Определение стилей25self.styles = {26"default": {27"bgcolor": ft.colors.BLUE_700,28"text_color": ft.colors.BLUE_500,29"button_color": ft.colors.BLUE_400,30"volume_icon": ft.icons.VOLUME_UP,31"volume_slider": ft.colors.BLUE_40032},33"dark": {34"bgcolor": ft.colors.GREY_700,35"text_color": ft.colors.GREY_500,36"button_color": ft.colors.GREY_400,37"volume_icon": ft.icons.VOLUME_UP,38"volume_slider": ft.colors.GREY_40039},40"light": {41"bgcolor": ft.colors.GREEN_700,42"text_color": ft.colors.GREEN_500,43"button_color": ft.colors.GREEN_400,44"volume_icon": ft.icons.VOLUME_UP,45"volume_slider": ft.colors.GREEN_40046},47"sunset_dreams": {48"bgcolor": ft.colors.AMBER_500,49"text_color": ft.colors.AMBER_500,50"button_color": ft.colors.ORANGE_300,51"volume_icon": ft.icons.VOLUME_UP,52"volume_slider": ft.colors.ORANGE_30053},54"ocean_breeze": {55"bgcolor": ft.colors.LIGHT_BLUE_500,56"text_color": ft.colors.LIGHT_BLUE_500,57"button_color": ft.colors.TEAL_400,58"volume_icon": ft.icons.VOLUME_UP,59"volume_slider": ft.colors.TEAL_40060},61"midnight_blue": {62"bgcolor": ft.colors.BLACK,63"text_color": ft.colors.BLACK,64"button_color": ft.colors.BLUE_300,65"volume_icon": ft.icons.VOLUME_UP,66"volume_slider": ft.colors.BLUE_30067},68"forest_green": {69"bgcolor": ft.colors.GREEN_500,70"text_color": ft.colors.GREEN_500,71"button_color": ft.colors.LIGHT_GREEN_300,72"volume_icon": ft.icons.VOLUME_UP,73"volume_slider": ft.colors.LIGHT_GREEN_30074},75"retro_vibes": {76"bgcolor": ft.colors.BROWN_900,77"text_color": ft.colors.ORANGE_200,78"button_color": ft.colors.BROWN_700,79"volume_icon": ft.icons.VOLUME_UP,80"volume_slider": ft.colors.ORANGE_20081},82"neon_lights": {83"bgcolor": ft.colors.BLACK,84"text_color": ft.colors.CYAN_ACCENT_700,85"button_color": ft.colors.GREEN_ACCENT_400,86"volume_icon": ft.icons.VOLUME_UP,87"volume_slider": ft.colors.CYAN_ACCENT_70088},89"pastel_harmony": {90"bgcolor": ft.colors.WHITE,91"text_color": ft.colors.PINK_400,92"button_color": ft.colors.LIGHT_GREEN_300,93"volume_icon": ft.icons.VOLUME_UP,94"volume_slider": ft.colors.LIGHT_GREEN_30095},96"mountain_hike": {97"bgcolor": ft.colors.GREY_900,98"text_color": ft.colors.BROWN_400,99"button_color": ft.colors.BROWN_600,100"volume_icon": ft.icons.VOLUME_UP,101"volume_slider": ft.colors.BROWN_600102},103"vibrant_citrus": {104"bgcolor": ft.colors.YELLOW_500,105"text_color": ft.colors.BLACK,106"button_color": ft.colors.ORANGE_400,107"volume_icon": ft.icons.VOLUME_UP,108"volume_slider": ft.colors.ORANGE_400109}110}111self.current_style = "default"112
113def change_style(self, e):114styles_list = list(self.styles.keys())115current_index = styles_list.index(self.current_style)116self.current_style = styles_list[(current_index + 1) % len(styles_list)]117self.update_style()118self.page.update()119
120def update_style(self):121style = self.styles[self.current_style]122self.page.bgcolor = style["bgcolor"] # Установка цвета фона страницы123self.current_time.color = style["text_color"]124self.remaining_time.color = style["text_color"]125self.track_name.color = style["text_color"]126self.track_artist.color = style["text_color"]127self.progress_bar.bgcolor = style["button_color"]128self.volume_icon.name = style["volume_icon"]129self.volume_slider.active_color = style["volume_slider"]130
131def pick_files_result(self, e: ft.FilePickerResultEvent):132if e.files:133for f in e.files:134try:135src = f.path.replace("\\", "/")136filename = os.path.basename(src)137dest = os.path.join(self.sounds_folder, filename)138shutil.copy(src, dest)139self.tracks_list.append(dest)140
141audio1 = ft.Audio(142src=dest,143autoplay=False,144volume=self.volume,145balance=0,146on_position_changed=self.progress_change,147on_state_changed=self.check_state,148)149self.audio_controls.append(audio1)150self.page.overlay.append(audio1)151
152audio_info = TinyTag.get(dest)153self.track_name.value = audio_info.title if audio_info.title else "Неизвестное название"154self.track_artist.value = audio_info.artist if audio_info.artist else "Неизвестный исполнитель"155
156except Exception as ex:157print(f"Ошибка при обработке файла {f.path}: {ex}")158
159self.update_track_list()160self.page.update()161else:162print("Отмена")163
164def check_state(self, e):165if e.data == "completed":166self.next_track(None)167self.page.update()168
169def play_track(self, e):170if not self.audio_controls:171print("Нет доступных треков для воспроизведения")172return173
174if self.state == "":175self.state = "playing"176self.btn_play.icon = ft.icons.PAUSE_CIRCLE177self.audio_controls[self.index].play()178elif self.state == "playing":179self.state = "paused"180self.btn_play.icon = ft.icons.PLAY_CIRCLE181self.audio_controls[self.index].pause()182else:183self.state = "playing"184self.btn_play.icon = ft.icons.PAUSE_CIRCLE185self.audio_controls[self.index].resume()186self.page.update()187
188def new_track(self):189self.disc_image.rotate.angle += pi * 2190audio = TinyTag.get(self.audio_controls[self.index].src)191self.track_name.value = audio.title if audio.title else "Неизвестное название"192self.track_artist.value = audio.artist if audio.artist else "Неизвестный исполнитель"193self.current_time.value = "0:0"194self.remaining_time.value = self.converter_time(audio.duration * 1000)195self.progress_track.value = 0.0196
197for audio_control in self.audio_controls:198audio_control.pause()199
200if self.state == "playing":201self.audio_controls[self.index].volume = self.volume202self.audio_controls[self.index].play()203
204self.page.update()205
206def next_track(self, e):207if not self.audio_controls:208print("Нет доступных треков для воспроизведения")209return210
211self.audio_controls[self.index].pause()212self.index = (self.index + 1) % len(self.audio_controls)213self.new_track()214self.page.update()215
216def previous_track(self, e):217if not self.audio_controls:218print("Нет доступных треков для воспроизведения")219return220
221self.audio_controls[self.index].pause()222self.index = (self.index - 1) % len(self.audio_controls)223self.new_track()224self.page.update()225
226def seek_forward(self, e):227if not self.audio_controls:228print("Нет доступных треков для воспроизведения")229return230
231current_position = self.audio_controls[self.index].position232new_position = current_position + 10000233if new_position > self.audio_controls[self.index].duration * 1000:234new_position = self.audio_controls[self.index].duration * 1000235print(f"Seeking forward: Current Position: {current_position}, New Position: {new_position}")236self.audio_controls[self.index].position = new_position237self.progress_change(ft.Event(data=new_position))238self.page.update()239
240def seek_backward(self, e):241if not self.audio_controls:242print("Нет доступных треков для воспроизведения")243return244
245current_position = self.audio_controls[self.index].position246new_position = current_position - 10000247if new_position < 0:248new_position = 0249print(f"Seeking backward: Current Position: {current_position}, New Position: {new_position}")250self.audio_controls[self.index].position = new_position251self.progress_change(ft.Event(data=new_position))252self.page.update()253
254def volume_change(self, e):255if not self.audio_controls:256print("Нет доступных треков для воспроизведения")257return258
259v = e.control.value260self.audio_controls[self.index].volume = 0.01 * v261self.volume = 0.01 * v262if v == 0:263self.volume_icon.name = ft.icons.VOLUME_OFF264elif 0 < v <= 50:265self.volume_icon.name = ft.icons.VOLUME_DOWN266else:267self.volume_icon.name = ft.icons.VOLUME_UP268self.page.update()269
270@staticmethod271def converter_time(millis):272millis = int(millis)273seconds = (millis // 1000) % 60274minutes = (millis // (1000 * 60)) % 60275return f"{int(minutes)}:{int(seconds):02d}"276
277def progress_change(self, e):278if not self.audio_controls:279print("Нет доступных треков для воспроизведения")280return281
282audio = TinyTag.get(self.audio_controls[self.index].src)283self.current_time.value = self.converter_time(e.data)284self.remaining_time.value = self.converter_time((audio.duration * 1000) - int(e.data))285self.progress_track.value = float(e.data) / (audio.duration * 1000)286self.page.update()287
288def update_track_list(self):289# Обновите элемент интерфейса списка треков, если у вас есть такой элемент290pass291
292def on_progress_bar_click(self, e):293if not self.audio_controls:294print("Нет доступных треков для воспроизведения")295return296
297print(f"Click event data: {e.data}")298if e.data and 'x' in e.data:299click_position = e.data['x']300print(f"Progress bar clicked at: {click_position}")301
302progress_width = self.progress_bar.width303print(f"Progress bar width: {progress_width}")304
305if 0 <= click_position <= progress_width:306new_position = (click_position / progress_width) * (self.audio_controls[self.index].duration * 1000)307print(f"Setting new position: {new_position}")308self.audio_controls[self.index].position = new_position309self.progress_change(ft.Event(data=new_position))310self.page.update()311
312def build_ui(self):313self.pick_files_dialog = ft.FilePicker(on_result=self.pick_files_result)314self.upload_button = ft.IconButton(315icon=ft.icons.UPLOAD_FILE,316icon_size=35,317on_click=lambda _: self.pick_files_dialog.pick_files(318allow_multiple=True,319file_type=ft.FilePickerFileType.AUDIO320)321)322self.style_button = ft.IconButton(323icon=ft.icons.STYLE,324icon_size=35,325on_click=self.change_style326)327
328for track in self.tracks_list:329audio = ft.Audio(330src=track,331autoplay=False,332volume=self.volume,333balance=0,334on_position_changed=self.progress_change,335on_state_changed=self.check_state,336)337self.audio_controls.append(audio)338self.page.overlay.append(audio)339
340audio_init = TinyTag.get(self.audio_controls[self.index].src)341
342self.current_time = ft.Text(value="0:0")343self.remaining_time = ft.Text(value=self.converter_time(audio_init.duration * 1000))344self.progress_bar = ft.Container(345width=400,346height=8,347bgcolor=self.styles[self.current_style]["button_color"],348on_click=self.on_progress_bar_click349)350self.progress_track = ft.Container(351width=400,352height=8,353bgcolor=ft.colors.BLUE,354)355self.track_name = ft.Text(value=audio_init.title if audio_init.title else "Неизвестное название")356self.track_artist = ft.Text(value=audio_init.artist if audio_init.artist else "Неизвестный исполнитель")357self.btn_play = ft.IconButton(icon=ft.icons.PLAY_CIRCLE, icon_size=35, on_click=self.play_track)358self.volume_icon = ft.Icon(name=self.styles[self.current_style]["volume_icon"])359
360# Установите значение слайдера громкости в соответствии с текущей громкостью361self.volume_slider = ft.Slider(362width=150,363active_color=self.styles[self.current_style]["volume_slider"],364min=0,365max=100,366divisions=100,367value=self.volume * 100,368label="{value}",369on_change=self.volume_change,370)371
372self.disc_image = ft.Image(373src="assets/album.png",374width=180,375height=180,376fit=ft.ImageFit.CONTAIN,377rotate=ft.transform.Rotate(0, alignment=ft.alignment.center),378animate_rotation=ft.animation.Animation(300, ft.AnimationCurve.LINEAR),379)380
381main_content = ft.Card(382content=ft.Container(383content=ft.Row(384[385ft.Container(width=80, height=300),386ft.Column(387[388ft.ListTile(389leading=ft.Icon(ft.icons.MUSIC_NOTE_ROUNDED),390title=self.track_name,391subtitle=self.track_artist,392),393ft.Row(394[self.current_time, self.progress_bar, self.remaining_time],395alignment=ft.MainAxisAlignment.END,396),397ft.Row(398[399ft.IconButton(400icon=ft.icons.SKIP_PREVIOUS,401icon_size=35,402on_click=self.previous_track,403),404self.btn_play,405ft.IconButton(406icon=ft.icons.SKIP_NEXT,407icon_size=35,408on_click=self.next_track,409),410self.volume_icon,411self.volume_slider, # Используйте слайдер громкости412self.upload_button,413self.style_button,414],415alignment=ft.MainAxisAlignment.END,416),417]418),419]420),421),422right=-10,423width=620,424color=ft.colors.ON_PRIMARY,425height=180,426)427
428stack = ft.Stack(429controls=[430main_content,431self.disc_image,432self.pick_files_dialog,433],434width=700,435height=300,436)437
438self.page.add(stack)439
440async def monitor_new_tracks(player: MusicPlayer):441while True:442files_in_folder = os.listdir(player.sounds_folder)443if files_in_folder:444for filename in files_in_folder:445if not filename.endswith(".mp3"):446continue447full_path = os.path.join(player.sounds_folder, filename)448if full_path not in player.tracks_list:449player.tracks_list.append(full_path)450audio1 = ft.Audio(451src=full_path,452autoplay=False,453volume=player.volume,454balance=0,455on_position_changed=player.progress_change,456on_state_changed=player.check_state,457)458player.audio_controls.append(audio1)459player.page.overlay.append(audio1)460player.update_track_list()461player.page.update()462await asyncio.sleep(3)463
464async def main(page: ft.Page):465page.title = "Музыкальный плеер"466page.vertical_alignment = ft.MainAxisAlignment.CENTER467page.horizontal_alignment = ft.CrossAxisAlignment.CENTER468page.window.height = 300469page.window.width = 800470page.window.min_width = 800471page.window.min_height = 300472
473player = MusicPlayer(page)474player.build_ui()475
476asyncio.create_task(monitor_new_tracks(player))477
478ft.app(target=main)479
480'''
481
482'''