diff --git a/Fbroswer.tar.gz b/Fbroswer.tar.gz new file mode 100644 index 0000000..ba23803 Binary files /dev/null and b/Fbroswer.tar.gz differ diff --git a/Fbrowser.py b/Fbrowser.py index 0f87ef5..ad6c2c5 100755 --- a/Fbrowser.py +++ b/Fbrowser.py @@ -4,18 +4,30 @@ # Importing Libraries import sys import os - from ScanOrg import organizer, file_scanner, DirectoryFilterProxyModel, FileFilterProxyModel -from stanzip import Extractor as extractor -from stanzip import Compressor as compressor -from stanzip import zipfile, py7zr, rarfile -from PyQt5.QtGui import QStandardItem , QStandardItemModel, QContextMenuEvent -from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QVBoxLayout, QMenu, QTreeView, QMessageBox, QSlider, QWidget, QFileSystemModel, QSplitter, QHBoxLayout, QFileDialog -from PyQt5.QtMultimedia import QMediaPlaylist, QMediaPlayer, QMediaContent, QAudioFormat, QAudioDeviceInfo, QAudio -from PyQt5.QtCore import QDir, QSortFilterProxyModel, Qt, QUrl -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as MPLCanvas -from matplotlib.figure import Figure +from PyQt5.QtGui import QStandardItem , QStandardItemModel +from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QTreeView, QMessageBox, QSlider, QWidget, QFileSystemModel, QSplitter, QHBoxLayout, QFileDialog +from PyQt5.QtMultimedia import QMediaPlaylist, QMediaPlayer, QMediaContent, QAudioFormat, QAudioDeviceInfo, QAudio +from PyQt5.QtCore import QDir, QSortFilterProxyModel, Qt, QUrl #QAbstractItemModel, QAbstractProxyModel, QModelIndex, QItemSelectionModel, QItemSelection, QItemSelectionRange, QItemSelectionModel, QItemSelection, QItemSelectionRange + + +""" +# Audio Format +audio_format = QAudioFormat() +audio_format.setSampleRate(44100) +audio_format.setChannelCount(2) +audio_format.setSampleSize(16) +audio_format.setCodec('audio/pcm') +audio_format.setByteOrder(QAudioFormat.LittleEndian) +audio_format.setSampleType(QAudioFormat.SignedInt) +# Audio Device Info +device_info = QAudioDeviceInfo.defaultOutputDevice() +if not device_info.isFormatSupported(audio_format): + print('Raw audio format not supported by backend, cannot play audio.') + audio_format = device_info.nearestFormat(audio_format) + +""" # Sample Music Browser Main Class @@ -23,7 +35,6 @@ class SampleMusicBrowser(QWidget): def __init__(self): super().__init__() self.organizer = organizer() - self.extractor = extractor() self.file_model = QStandardItemModel() self.player = QMediaPlayer() self.playlist = QMediaPlaylist() @@ -31,102 +42,68 @@ class SampleMusicBrowser(QWidget): self.tree_model = QFileSystemModel() self.init_ui() - #self.midi_player = MidPlay() + self.folder_contents_view.setEditTriggers(QTreeView.NoEditTriggers) self.player.error.connect(self.player_error) self.player.mediaStatusChanged.connect(self.player_media_status_changed) self.player.setAudioRole(QAudio.MusicRole) - self.layout = QHBoxLayout() - self.canvas = MPLCanvas() - self.layout.addWidget(self.canvas) - self.setLayout(self.layout) - - # Player Error Debugging + def player_error(self, error): - try: - if error == QMediaPlayer.NoError: - return - print(f"An error occurred: Code:{error} {self.player.errorString()}") - except Exception as e: - print(f"Error: {e}") + if error == QMediaPlayer.NoError: + return + print('Error: ' + self.player.errorString()) - # Media Status Changed Debugging def player_media_status_changed(self, status): if status == QMediaPlayer.NoMedia: return print('Media Status: ' + str(status)) - - def on_extract_button_clicked(self): - extraction_directory = QFileDialog.getExistingDirectory(self, "Select Extraction Directory") - if extraction_directory: - index = self.folder_contents_view.currentIndex() - if index.isValid(): - self.extractor.zipviewer(index, self.file_filter_model, self.list_model, extraction_directory) - - def show_context_menu(self, position): - menu = QMenu(self) - extract_action = menu.addAction('Extract') - extract_action.triggered.connect(self.on_extract_button_clicked) # Connect to the extraction function - menu.exec(self.folder_contents_view.mapToGlobal(position)) - def init_ui(self): - layout = QVBoxLayout() + layout = QHBoxLayout() label = QLabel('Sample Music Browser') - buttons_layout = QHBoxLayout() layout.addWidget(label) - - - #self.midi_player = MidPlay() + button = QPushButton('Exit') + button.clicked.connect(self.show_exit_popup) + layout.addWidget(button) self.file_tree = QTreeView() - self.file_tree.setHeaderHidden(True) self.file_tree.clicked.connect(self.change_directory) - - - play_button = QPushButton('Play') - play_button.clicked.connect(self.player.play) - #play_button.clicked.connect(self.midi_player.play_midi) - buttons_layout.addWidget(play_button) - - stop_button = QPushButton('Stop') - stop_button.clicked.connect(self.player.stop) - # stop_button.clicked.connect(self.midi_player.stop) - buttons_layout.addWidget(stop_button) - self.player.stateChanged.connect(self.player_state_changed) - self.player.positionChanged.connect(self.player_position_changed) - self.player.durationChanged.connect(self.player_duration_changed) - - - layout.addLayout(buttons_layout) - self.folder_contents_view = QTreeView() self.folder_contents_view.setHeaderHidden(False) self.folder_contents_view.setRootIsDecorated(False) self.folder_contents_view.setSortingEnabled(True) - splitter = QSplitter() splitter.addWidget(self.file_tree) splitter.addWidget(self.folder_contents_view) layout.addWidget(splitter) self.current_dir_label = QLabel() layout.addWidget(self.current_dir_label) - up_dir_button = QPushButton('Up Directory') up_dir_button.clicked.connect(self.go_up_directory) layout.addWidget(up_dir_button) - + back_button = QPushButton('Back') + back_button.clicked.connect(self.go_back_directory) + layout.addWidget(back_button) forward_button = QPushButton('Forward') forward_button.clicked.connect(self.go_forward_directory) layout.addWidget(forward_button) self.setLayout(layout) - self.setWindowTitle('Samples are life!') path = QFileDialog.getExistingDirectory(self, 'Select Directory') if path: self.populate_file_tree(path) - - + play_button = QPushButton('Play') + play_button.clicked.connect(self.player.play) + layout.addWidget(play_button) + pause_button = QPushButton('Pause') + pause_button.clicked.connect(self.player.pause) + layout.addWidget(pause_button) + stop_button = QPushButton('Stop') + stop_button.clicked.connect(self.player.stop) + layout.addWidget(stop_button) + self.player.stateChanged.connect(self.player_state_changed) + self.player.positionChanged.connect(self.player_position_changed) + self.player.durationChanged.connect(self.player_duration_changed) self.player.setVolume(50) volume_slider = QSlider(Qt.Horizontal) volume_slider.setRange(0, 100) @@ -139,9 +116,6 @@ class SampleMusicBrowser(QWidget): self.playlist.mediaRemoved.connect(self.playlist_media_removed) self.playlist.setPlaybackMode(QMediaPlaylist.Loop) self.folder_contents_view.doubleClicked.connect(self.play_file) - self.folder_contents_view.setContextMenuPolicy(Qt.CustomContextMenu) - self.folder_contents_view.customContextMenuRequested.connect(self.show_context_menu) - def directory_loaded(self, path): self.file_tree.setRootIndex(self.directory_model.mapFromSource(self.model.index(path))) @@ -165,45 +139,19 @@ class SampleMusicBrowser(QWidget): except Exception as e: print(f"Error Populating File Tree: {e}") - def closeEvent(self, event): + + def show_exit_popup(self): reply = QMessageBox.question(self, 'Exit', 'Are you sure you want to exit?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: - event.accept() - else: - event.ignore() + sys.exit() def play_file(self, index): - try: - index = self.file_filter_model.mapToSource(index) - file_path = self.list_model.filePath(index) - if file_path.endswith(('.zip', '.rar', '.7z')): - with zipfile.ZipFile(file_path, 'r') as zip_ref: - for filename in zip_ref.namelist(): - if filename.lower().endswith(('mp3', 'wav', 'ogg', 'flac', - 'm4a', 'wma', 'aac', 'aiff', 'alac', - 'mid', 'midi', 'mp4', 'm4a')): - audo_file = zip_ref.extract(filename) - media = QMediaContent(QUrl.fromLocalFile(audo_file)) - self.playlist.clear() - self.playlist.addMedia(media) - self.player.play() - break - if os.path.exists(audo_file): - os.remove(audo_file) - - elif file_path.endswith(('.mid', '.midi')): - #self.midi_player = MidPlay() - #fig = self.midi_player.play_midi(file_path) - self.canvas.draw() - - else: - media = QMediaContent(QUrl.fromLocalFile(file_path)) - self.playlist.clear() - self.playlist.addMedia(media) - self.player.play() - except Exception as e: - print(f"Error Playing File: {e}") + index = self.file_filter_model.mapToSource(index) + file_path = self.list_model.filePath(index) + media = QMediaContent(QUrl.fromLocalFile(file_path)) + self.playlist.addMedia(media) + self.player.play() def player_state_changed(self, state): if state == QMediaPlayer.StoppedState: @@ -235,39 +183,34 @@ class SampleMusicBrowser(QWidget): def go_up_directory(self): index = self.folder_contents_view.rootIndex() index = self.file_filter_model.mapToSource(index) - parent_index = index.parent() - if parent_index.isValid(): # Check if the parent index is valid - self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(parent_index)) - self.current_dir_label.setText(self.list_model.filePath(parent_index)) + index = self.file_model.index(index) + index = index.parent() + index = self.file_filter_model.mapFromSource(index) + self.folder_contents_view.setRootIndex(index) + self.current_dir_label.setText(self.model.filePath(index)) + + def go_back_directory(self): + index = self.folder_contents_view.rootIndex() + index = self.file_filter_model.mapToSource(index) + index = self.file_model.index(index) + index = index.parent() + index = self.file_filter_model.mapFromSource(index) + self.folder_contents_view.setRootIndex(index) + self.current_dir_label.setText(self.model.filePath(index)) def go_forward_directory(self): index = self.folder_contents_view.rootIndex() index = self.file_filter_model.mapToSource(index) - parent_index = index.parent() - if parent_index.isValid(): - self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(parent_index)) - self.current_dir_label.setText(self.list_model.filePath(parent_index)) + index = self.file_model.index(index) + index = index.parent() + index = self.file_filter_model.mapFromSource(index) + self.folder_contents_view.setRootIndex(index) + self.current_dir_label.setText(self.model.filePath(index)) + if __name__ == '__main__': - # player = MidPlay() - # file_path = list(player.select_file()) # Get the selected file path - - # viewer = MidViewer() - - # viewer.read_midi(file_path) - # viewer.view_midi() - #viewer.show() - # viewer.save('test.png') - # viewer.clear() - # viewer.close() - # print(viewer.get_midi_info(file_path)) # Use the file path - # print(viewer.get_piano_roll(file_path)) # Use the file path - # print(viewer.get_tempo(file_path)) # Use the file path - # print(viewer.get_notes(file_path)) # Use the file path app = QApplication(sys.argv) sampleMusicBrowser = SampleMusicBrowser() sampleMusicBrowser.show() - - sys.exit(app.exec_()) \ No newline at end of file diff --git a/Fbrowser.zip b/Fbrowser.zip new file mode 100755 index 0000000..a09a7bf Binary files /dev/null and b/Fbrowser.zip differ diff --git a/MidPlay.py b/MidPlay.py index 19f8bd5..b01388f 100755 --- a/MidPlay.py +++ b/MidPlay.py @@ -1,25 +1,10 @@ #Path: MidPlay.py # Description: A class to play MIDI files and a class to view MIDI files -# probably switching to a different library for midi handling -# pretty_midi is not very good for this purpose or real-time playback of midi files -"""Pretty Midi module type stubs are included but incomplete. -Pretty Midi comes with a statement to cite the following paper -when used in a research project: - -Colin Raffel and Daniel P. W. Ellis. Intuitive Analysis, -Creation and Manipulation of MIDI Data with pretty_midi. -In Proceedings of the 15th International Conference on Music -Information Retrieval Late Breaking and Demo Papers, 2014. - -colinraffel.com/publications/ismir2014intuitive.pdf - -""" import pygame # Imports -import pretty_midi +import mido import fluidsynth -import sys import os from PyQt5.QtWidgets import (QApplication, QLabel, QListWidget, QFileDialog, QMessageBox, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, @@ -29,6 +14,18 @@ from PyQt5.QtCore import QTimer, Qt import threading import cProfile # profiler remove for production +#profiler remove for production +def start_profiling(): + global pr + pr = cProfile.Profile() + pr.enable() + +def stop_profiling(): + pr.disable() + pr.dump_stats('midi_profile.out') + +# The pygame.mixer.init() call is necessary to initialize the mixer module +# before any sound can be played. The pygame.init() call is necessary maybe. pygame.mixer.init() pygame.init() @@ -51,10 +48,17 @@ class MidPlayGUI(QWidget): def update_progress(self): if self.player.current_midi: current_time = pygame.mixer.music.get_pos() / 1000 # get_pos returns time in milliseconds NOT SECONDS! - total_time = self.player.current_midi.get_end_time() + total_time = self.calculate_midi_duration(self.player.current_midi) progress = current_time / total_time * 100 self.progress_bar.setValue(int(progress)) + def calculate_midi_duration(self, midi_file): + total_duration = 0 + for track in midi_file.tracks: + track_duration = max([msg.time for msg in track]) if track else 0 + total_duration = max(total_duration, track_duration) + return total_duration + def handle_song_end(self): if self.player.playing and not pygame.mixer.music.get_busy(): self.player.next_song() @@ -194,7 +198,7 @@ class MidPlay: def load_midi(self, filepath: str) -> None: def load(): try: - self.current_midi = pretty_midi.PrettyMIDI(filepath) + self.current_midi = mido.MidiFile(filepath) pygame.mixer.music.load(filepath) except Exception as e: print(f"Error loading MIDI: {e}") @@ -240,6 +244,7 @@ class MidPlay: # print("Debug: Filepath:", filepath) # debug line # print("Debug: Current MIDI:", self.current_midi) # debug line +""" if __name__ == '__main__': app = QApplication([]) player_gui = MidPlayGUI() @@ -252,4 +257,5 @@ if __name__ == '__main__': if event.type == pygame.QUIT: running = False break - app.exec_() \ No newline at end of file + app.exec_() +""" \ No newline at end of file diff --git a/ScanOrg.py b/ScanOrg.py index 1a48c33..b19fbb3 100755 --- a/ScanOrg.py +++ b/ScanOrg.py @@ -9,13 +9,13 @@ import py7zr import rarfile import os import mutagen -from PyQt5.QtCore import Qt, QSortFilterProxyModel, QAbstractTableModel, QModelIndex, QVariant, QAbstractItemModel, QFileInfo, QDir, QMimeDatabase, QMimeData, QUrl, QItemSelectionModel, QItemSelection, QItemSelectionRange, QObject, QThread, QTimer, QEventLoop, QCoreApplication, QUrl, pyqtSignal +from PyQt6.QtCore import Qt, QSortFilterProxyModel, pyqtSignal # Directory Filter Proxy Model class DirectoryFilterProxyModel(QSortFilterProxyModel): def __init__(self): super().__init__() - self.setFilterCaseSensitivity(Qt.CaseInsensitive) + self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) self.setFilterKeyColumn(0) def filterAcceptsRow(self, source_row, source_parent): index = self.sourceModel().index(source_row, 0, source_parent) @@ -25,7 +25,7 @@ class DirectoryFilterProxyModel(QSortFilterProxyModel): class FileFilterProxyModel(QSortFilterProxyModel): def __init__(self): super().__init__() - self.setFilterCaseSensitivity(Qt.CaseInsensitive) + self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) self.setFilterKeyColumn(0) self.allowed_extensions = ['.zip', '.mp3', '.wav', '.flac', '.mid', '.midi', '.aiff', '.aif', '.aifc', '.au', '.snd', '.wv', '.wma', '.m4a'] @@ -77,44 +77,21 @@ class file_scanner: def clear_file_list(self): self.file_list = [] -class Extractor: - def zipviewer(self, index, file_filter_model, list_model, extraction_directory): - if index.isValid() and extraction_directory is not None: +class extractor: + def zipviewer(self, index, file_filter_model, list_model, extraction_directory): + if index.isValid() and extraction_directory is not None: index = file_filter_model.mapToSource(index) file_path = list_model.filePath(index) - try: - if file_path.endswith(".zip"): + if file_path.endswith(('.zip', '.rar', '.7z')): with zipfile.ZipFile(file_path, 'r') as zip_ref: - self._extract_files(zip_ref, extraction_directory) - elif file_path.endswith(".rar"): - with rarfile.RarFile(file_path, 'r') as rar_ref: - self._extract_files(rar_ref, extraction_directory) - elif file_path.endswith(".7z"): - with py7zr.SevenZipFile(file_path, 'r') as sevenzip_ref: - self._extract_files(sevenzip_ref, extraction_directory) - else: - print(f"Unsupported file format: {file_path}") - - except (zipfile.BadZipFile, zipfile.LargeZipFile) as e: - print(f"ZIP Extraction Error: {e}") - except (rarfile.RarFileException, rarfile.NotRARFile) as e: - print(f"RAR Extraction Error: {e}") - except py7zr.exceptions.SevenZipException as e: - print(f"7z Extraction Error: {e}") - except OSError as e: + for filename in zip_ref.namelist(): + destination = os.path.join(extraction_directory, filename) + zip_ref.extract(filename, extraction_directory) + except (zipfile.BadZipFile, OSError, zipfile.LargeZipFile, zipfile.LargeZipFile) as e: print(f"Extraction Error: {e}") - - def _extract_files(self, archive_ref, extraction_directory): - for filename in archive_ref.namelist(): - destination = os.path.join(extraction_directory, filename) - if os.path.isfile(destination): - print(f"File already exists: {destination}") - else: - os.makedirs(os.path.dirname(destination), exist_ok=True) - with open(destination, 'wb') as file: - file.write(archive_ref.read(filename)) - print(f"Extracted: {filename}") + return + class organizer: global metadata_queue @@ -207,7 +184,4 @@ class organizer: print('Error: ' + file) if os.path.splitext(file)[1] == ('.mp3', '.wav', '.flac', '.m4a', '.wma', 'mid', '.midi'): self.organize_audio() - audio = mutagen.File(file) - - - \ No newline at end of file + audio = mutagen.File(file) \ No newline at end of file diff --git a/ScanOrg100.py b/ScanOrg100.py new file mode 100644 index 0000000..da35a08 --- /dev/null +++ b/ScanOrg100.py @@ -0,0 +1,69 @@ +import os +from PyQt6.QtCore import QThread, pyqtSignal + +class FileScanner(QThread): + items_found = pyqtSignal(list) + scan_complete = pyqtSignal() + + def __init__(self, path): + super().__init__() + self.path = path + self.stop_requested = False + self.allowed_extensions = { + '.mid', '.midi', '.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.wma', + '.flp', '.als', '.logic', '.logicx', '.ptx', '.pts', '.cpr', '.rpp', + '.reason', '.sng', '.ardour', '.bwproject' + } + + def run(self): + self.scan_directory(self.path) + self.scan_complete.emit() + + def scan_directory(self, path): + try: + items = [] + with os.scandir(path) as entries: + for entry in entries: + if self.stop_requested: + return + if entry.is_dir(): + items.append((entry.path, True)) + elif entry.is_file() and entry.name.lower().endswith(tuple(self.allowed_extensions)): + items.append((entry.path, False)) + self.items_found.emit(items) + except PermissionError: + print(f"Permission denied: {path}") + except OSError as e: + print(f"Error accessing {path}: {e}") + + def stop(self): + self.stop_requested = True + +class Organizer: + def __init__(self): + self.file_list = [] + self.dir_list = [] + self.scanner = None + + def start_scan(self, path): + self.file_list.clear() + self.dir_list.clear() + self.scanner = FileScanner(path) + self.scanner.items_found.connect(self.add_items) + self.scanner.scan_complete.connect(self.scan_finished) + self.scanner.start() + + def add_items(self, items): + for path, is_dir in items: + if is_dir: + self.dir_list.append(path) + else: + self.file_list.append(path) + + def scan_finished(self): + print(f"Scan complete. Found {len(self.dir_list)} directories and {len(self.file_list)} files.") + + def stop_scan(self): + if self.scanner: + self.scanner.stop() + self.scanner.wait() diff --git a/maintest.py b/maintest.py new file mode 100644 index 0000000..a242673 --- /dev/null +++ b/maintest.py @@ -0,0 +1,147 @@ +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +import sys +import os +from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, + QTreeView, QLabel, QStyle, QFileDialog, QHBoxLayout, QLineEdit, QProgressBar) +from PyQt6.QtCore import Qt, QDir, QTimer +from PyQt6.QtGui import QStandardItemModel, QStandardItem +from testmid import MidPlay +from ScanOrg100 import Organizer +from timer_m import Timer_Ui + +class Fbrowser(QMainWindow): + def __init__(self): + super().__init__() + self.midplay = None + self.current_path = QDir.homePath() + self.organizer = Organizer() + self.init_ui() + self.timer = Timer_Ui() + + def init_ui(self): + self.setWindowTitle('File Browser') + central_widget = QWidget() + layout = QVBoxLayout(central_widget) + + # Address bar + address_layout = QHBoxLayout() + self.address_bar = QLineEdit(self.current_path) + self.address_bar.returnPressed.connect(self.navigate_to_address) + address_layout.addWidget(self.address_bar) + + # Navigation buttons + back_button = QPushButton('Back') + back_button.clicked.connect(self.go_back) + address_layout.addWidget(back_button) + + layout.addLayout(address_layout) + + # Add a progress bar + self.progress_bar = QProgressBar() + layout.addWidget(self.progress_bar) + + # File view + self.file_model = QStandardItemModel() + self.file_tree = QTreeView() + self.file_tree.setModel(self.file_model) + self.file_tree.doubleClicked.connect(self.on_item_double_clicked) + layout.addWidget(self.file_tree) + + # MidPlay button + open_midplay_button = QPushButton('Open MidPlay') + open_midplay_button.clicked.connect(self.open_midplay) + layout.addWidget(open_midplay_button) + + self.setCentralWidget(central_widget) + self.resize(800, 600) + + # Timer Button + self.timer_button = QPushButton('Start Timer') + self.timer_button.clicked.connect(self.open_timer) + layout.addWidget(self.timer_button) + + + self.scan_current_directory() + + def scan_current_directory(self): + self.organizer.start_scan(self.current_path) + self.progress_bar.setRange(0, 0) # Indeterminate progress + self.organizer.scanner.scan_complete.connect(self.scan_finished) + + def scan_finished(self): + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(100) + self.update_file_tree() + + def navigate_to_address(self): + new_path = self.address_bar.text() + if os.path.isdir(new_path): + self.current_path = new_path + self.scan_current_directory() + else: + self.address_bar.setText(self.current_path) + + def update_file_tree(self): + self.file_model.clear() + self.file_model.setHorizontalHeaderLabels(['Name']) + + root = self.file_model.invisibleRootItem() + + # Add directories + for path in self.organizer.dir_list: + name = os.path.basename(path) + item = QStandardItem(name) + item.setData(path, Qt.ItemDataRole.UserRole) + item.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_DirIcon)) + root.appendRow(item) + + # Add files + for path in self.organizer.file_list: + name = os.path.basename(path) + item = QStandardItem(name) + item.setData(path, Qt.ItemDataRole.UserRole) + item.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon)) + root.appendRow(item) + + self.file_tree.sortByColumn(0, Qt.SortOrder.AscendingOrder) + + def on_item_double_clicked(self, index): + item = self.file_model.itemFromIndex(index) + path = item.data(Qt.ItemDataRole.UserRole) + if os.path.isdir(path): + self.current_path = path + self.address_bar.setText(path) + self.scan_current_directory() + else: + self.play_file(path) + def playingcheck_and_stop(self): + if self.midplay.is_playing(): + self.midplay.stop() + def play_file(self, file_path): + if not self.midplay: + self.midplay = MidPlay() + #self.midplay.showUI() + self.midplay.add_to_playlist(file_path) + + def go_back(self): + parent_dir = os.path.dirname(self.current_path) + if parent_dir != self.current_path: + self.current_path = parent_dir + self.address_bar.setText(parent_dir) + self.scan_current_directory() + + def open_midplay(self): + if not self.midplay: + self.midplay = MidPlay() + self.midplay.showUI() + + def open_timer(self): + self.timer.showUI() + +if __name__ == '__main__': + app = QApplication(sys.argv) + ex = Fbrowser() + ex.show() + sys.exit(app.exec()) diff --git a/readme.txt b/readme.txt index c061d9a..1319b7e 100755 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,9 @@ +<<<<<<< HEAD FBroswer is a sample and loop organizer and browser with extra features such as the ability to sample that audio you're checking out full midi play back support with soundfont controls a timer to help balance work life and personal life a way to keep track of what you're listening to +======= +fbrowser is a sample and loops audio organizer and browser +>>>>>>> 1ee9caf82243dd45a72a97bf6c5de681139670e2 diff --git a/requirements.txt b/requirements.txt old mode 100755 new mode 100644 index 2b5abb2..9fc0bbe --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,13 @@ -# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file -py7zr -rarfile -tqdm -PyQt5 -mutagen +PyQT5 +PyQt5-tools +PyQt6 +PyQt6-tools +matplotlib +rarfile +py7zr +pygame +mido +numpy +crypto +django +pyFluidSynth \ No newline at end of file diff --git a/test.py b/test.py index 7eebc37..1ccb84d 100755 --- a/test.py +++ b/test.py @@ -1,4 +1,4 @@ -import pretty_midi +import mido as pretty_midi import random import tkinter as tk from tkinter import ttk, filedialog diff --git a/test_Fbrowser.py b/test_Fbrowser.py index 75f7a78..83a5739 100755 --- a/test_Fbrowser.py +++ b/test_Fbrowser.py @@ -1,7 +1,7 @@ import unittest from unittest.mock import MagicMock from PyQt5.QtWidgets import QApplication -from fbrowser import SampleMusicBrowser +from Fbrowser import SampleMusicBrowser class TestSampleMusicBrowser(unittest.TestCase): def setUp(self): diff --git a/testmid.py b/testmid.py new file mode 100644 index 0000000..fca7f85 --- /dev/null +++ b/testmid.py @@ -0,0 +1,136 @@ +import os +import subprocess +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QFileDialog, QSlider, QListWidget +from PyQt6.QtCore import Qt, QUrl +from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput + +DEFAULT_SOUNDFONT = '/home/stan/Documents/Dev/Tests/MidPlay/SoundFonts/FinalFantasyVI.sf2' + +class MidPlay(QWidget): + def __init__(self): + super().__init__() + self.current_file = None + self.current_soundfont = DEFAULT_SOUNDFONT + self.process = None + self.media_player = QMediaPlayer() + self.audio_output = QAudioOutput() + self.media_player.setAudioOutput(self.audio_output) + self.playlist = [] + self.current_index = 0 + self.current_volume = 50 + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout() + + self.playlist_widget = QListWidget() + self.playlist_widget.itemDoubleClicked.connect(self.play_selected) + layout.addWidget(self.playlist_widget) + + self.play_button = QPushButton("Play") + self.play_button.clicked.connect(self.play) + layout.addWidget(self.play_button) + + self.stop_button = QPushButton("Stop") + self.stop_button.clicked.connect(self.stop) + layout.addWidget(self.stop_button) + + self.next_button = QPushButton("Next") + self.next_button.clicked.connect(self.play_next) + layout.addWidget(self.next_button) + + self.volume_slider = QSlider(Qt.Orientation.Horizontal) + self.volume_slider.setRange(0, 100) + self.volume_slider.setValue(self.current_volume) + self.volume_slider.valueChanged.connect(self.set_volume) + layout.addWidget(self.volume_slider) + + self.status_label = QLabel("No file loaded") + layout.addWidget(self.status_label) + + self.setLayout(layout) + + def add_to_playlist(self, file_path): + self.playlist.append(file_path) + self.playlist_widget.addItem(os.path.basename(file_path)) + if not self.current_file: + self.current_file = file_path + self.play() + + def play_selected(self, item): + index = self.playlist_widget.row(item) + self.current_index = index + self.current_file = self.playlist[index] + self.play() + + def play(self): + if not self.current_file: + self.status_label.setText("No file selected") + return + + self.stop() # Stop any current playback + + if self.current_file.lower().endswith(('.mid', '.midi')): + self.play_midi() + else: + self.play_audio() + + def play_midi(self): + self.media_player.stop() + command = [ + "fluidsynth", + "-a", "pulseaudio", + "-g", str(self.current_volume / 50), + self.current_soundfont, + self.current_file + ] + self.process = subprocess.Popen(command) + self.status_label.setText(f"Playing MIDI: {os.path.basename(self.current_file)}") + + def play_audio(self): + if self.process: + self.process.terminate() + self.process = None + + self.media_player.setSource(QUrl.fromLocalFile(self.current_file)) + self.media_player.play() + self.status_label.setText(f"Playing Audio: {os.path.basename(self.current_file)}") + self.media_player.mediaStatusChanged.connect(self.handle_media_status_change) + + def stop(self): + if self.process: + self.process.terminate() + self.process = None + self.media_player.stop() + self.status_label.setText("Playback stopped") + + def set_volume(self, value): + self.current_volume = value + self.audio_output.setVolume(value / 50) + if self.process and self.current_file.lower().endswith(('.mid', '.midi')): + self.update_midi_volume() + + def update_midi_volume(self): + pass + + def play_next(self): + if self.playlist: + self.current_index = (self.current_index + 1) % len(self.playlist) + self.current_file = self.playlist[self.current_index] + self.play() + + def handle_media_status_change(self, status): + if status == QMediaPlayer.MediaStatus.EndOfMedia: + self.play_next() + + def closeEvent(self, event): + self.stop() + super().closeEvent(event) + + def showUI(self): + self.setWindowTitle("MidPlay") + super().show() + + def close(self): + self.stop() + super().close() diff --git a/timer_m.py b/timer_m.py new file mode 100644 index 0000000..e249fda --- /dev/null +++ b/timer_m.py @@ -0,0 +1,141 @@ +import time +import threading +import subprocess +from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QHBoxLayout, QLineEdit +from PyQt6.QtCore import QTimer, QUrl +from PyQt6.QtMultimedia import QSoundEffect + +class Timer: + def __init__(self): + self.remaining = 0 + self.running = False + self.timer_thread = None + + def set(self, hours, minutes, seconds): + self.remaining = hours * 3600 + minutes * 60 + seconds + + def start(self): + if self.remaining > 0: + self.running = True + self.timer_thread = threading.Thread(target=self._run_timer) + self.timer_thread.start() + + def _run_timer(self): + while self.running and self.remaining > 0: + time.sleep(1) + self.remaining -= 1 + if self.running: + self._alarm() + + def stop(self): + self.running = False + if self.timer_thread: + self.timer_thread.join() + + def get_remaining_time(self): + return self.remaining + + def _alarm(self): + print("Time's up!") + try: + for _ in range(3): + subprocess.run(["aplay", "/usr/share/sounds/sound-icons/glass.wav"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True) + except subprocess.CalledProcessError: + print("Error playing alarm sound") + self._fallback_alarm() + + def _fallback_alarm(self): + sound = QSoundEffect() + sound.setSource(QUrl.fromLocalFile("/usr/share/sounds/sound-icons/glass.wav")) + sound.play() + + +class Timer_Ui(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Timer") + self.width = 400 + self.height = 200 + self.timer = Timer() + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout() + self.setLayout(layout) + + self.timer_label = QLabel('Timer: Not Set') + layout.addWidget(self.timer_label) + + timer_input_layout = QHBoxLayout() + self.hours_input = QLineEdit() + self.minutes_input = QLineEdit() + self.seconds_input = QLineEdit() + timer_input_layout.addWidget(QLabel('Hours:')) + timer_input_layout.addWidget(self.hours_input) + timer_input_layout.addWidget(QLabel('Minutes:')) + timer_input_layout.addWidget(self.minutes_input) + timer_input_layout.addWidget(QLabel('Seconds:')) + timer_input_layout.addWidget(self.seconds_input) + layout.addLayout(timer_input_layout) + + self.set_timer_button = QPushButton('Set Timer') + self.set_timer_button.clicked.connect(self.set_timer) + layout.addWidget(self.set_timer_button) + + self.start_timer_button = QPushButton('Start Timer') + self.start_timer_button.clicked.connect(self.start_timer) + layout.addWidget(self.start_timer_button) + + self.stop_timer_button = QPushButton('Stop Timer') + self.stop_timer_button.clicked.connect(self.stop_timer) + layout.addWidget(self.stop_timer_button) + + self.update_timer = QTimer() + self.update_timer.timeout.connect(self.update_timer_display) + self.update_timer.start(1000) + + def set_timer(self): + try: + hours = int(self.hours_input.text() or 0) + minutes = int(self.minutes_input.text() or 0) + seconds = int(self.seconds_input.text() or 0) + self.timer.set(hours, minutes, seconds) + self.timer_label.setText(f'Timer: Set to {hours:02d}:{minutes:02d}:{seconds:02d}') + except ValueError: + self.timer_label.setText('Timer: Invalid input') + + def start_timer(self): + if self.timer.get_remaining_time() > 0: + self.timer.start() + else: + self.timer_label.setText('Timer: Not set') + + def stop_timer(self): + self.timer.stop() + self.timer_label.setText('Timer: Stopped') + + def update_timer_display(self): + if self.timer.running: + remaining = self.timer.get_remaining_time() + hours, remainder = divmod(remaining, 3600) + minutes, seconds = divmod(remainder, 60) + self.timer_label.setText(f'Timer: {hours:02d}:{minutes:02d}:{seconds:02d}') + elif self.timer.get_remaining_time() == 0 and self.timer_label.text() != 'Timer: Not Set': + self.timer_label.setText('Timer: Time\'s up!') + + def showUI(self): + self.setWindowTitle("Timer") + self.setGeometry(100, 100, self.width, self.height) + super().show() + +if __name__ == "__main__": + import sys + from PyQt6.QtWidgets import QApplication + + app = QApplication(sys.argv) + Timerui = Timer_Ui() + Timerui.showUI() + sys.exit(app.exec())