main push to repo
This commit is contained in:
commit
18ca545440
27
.dockerignore
Executable file
27
.dockerignore
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
**/__pycache__
|
||||||
|
**/.venv
|
||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
19
.vscode/launch.json
vendored
Executable file
19
.vscode/launch.json
vendored
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Docker: Python - General",
|
||||||
|
"type": "docker",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "docker-run: debug",
|
||||||
|
"python": {
|
||||||
|
"pathMappings": [
|
||||||
|
{
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "/app"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"projectType": "general"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"github.gitAuthentication": false
|
||||||
|
}
|
||||||
26
.vscode/tasks.json
vendored
Executable file
26
.vscode/tasks.json
vendored
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "docker-build",
|
||||||
|
"label": "docker-build",
|
||||||
|
"platform": "python",
|
||||||
|
"dockerBuild": {
|
||||||
|
"tag": "fbrowser:latest",
|
||||||
|
"dockerfile": "${workspaceFolder}/Dockerfile",
|
||||||
|
"context": "${workspaceFolder}",
|
||||||
|
"pull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-run",
|
||||||
|
"label": "docker-run: debug",
|
||||||
|
"dependsOn": [
|
||||||
|
"docker-build"
|
||||||
|
],
|
||||||
|
"python": {
|
||||||
|
"file": "Fbrowser.py"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
Dockerfile
Executable file
23
Dockerfile
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
# For more information, please refer to https://aka.ms/vscode-docker-python
|
||||||
|
FROM python:3-slim
|
||||||
|
|
||||||
|
# Keeps Python from generating .pyc files in the container
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
||||||
|
# Turns off buffering for easier container logging
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Install pip requirements
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN python -m pip install -r requirements.txt
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
# Creates a non-root user with an explicit UID and adds permission to access the /app folder
|
||||||
|
# For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers
|
||||||
|
RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# During debugging, this entry point will be overridden. For more information, please refer to https://aka.ms/vscode-docker-python-debug
|
||||||
|
CMD ["python", "Fbrowser.py"]
|
||||||
273
Fbrowser.py
Executable file
273
Fbrowser.py
Executable file
@ -0,0 +1,273 @@
|
|||||||
|
# Path: Fbrowser.py
|
||||||
|
# Sample Music Browser & Ogranizer: Main.py
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Sample Music Browser Main Class
|
||||||
|
class SampleMusicBrowser(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.organizer = organizer()
|
||||||
|
self.extractor = extractor()
|
||||||
|
self.file_model = QStandardItemModel()
|
||||||
|
self.player = QMediaPlayer()
|
||||||
|
self.playlist = QMediaPlaylist()
|
||||||
|
self.player.setPlaylist(self.playlist)
|
||||||
|
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}")
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
label = QLabel('Sample Music Browser')
|
||||||
|
buttons_layout = QHBoxLayout()
|
||||||
|
layout.addWidget(label)
|
||||||
|
|
||||||
|
|
||||||
|
#self.midi_player = MidPlay()
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
self.player.setVolume(50)
|
||||||
|
volume_slider = QSlider(Qt.Horizontal)
|
||||||
|
volume_slider.setRange(0, 100)
|
||||||
|
volume_slider.setValue(50)
|
||||||
|
volume_slider.valueChanged.connect(self.player.setVolume)
|
||||||
|
layout.addWidget(volume_slider)
|
||||||
|
self.playlist.currentIndexChanged.connect(self.playlist_current_index_changed)
|
||||||
|
self.playlist.currentMediaChanged.connect(self.playlist_current_media_changed)
|
||||||
|
self.playlist.mediaInserted.connect(self.playlist_media_inserted)
|
||||||
|
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)))
|
||||||
|
self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(path)))
|
||||||
|
|
||||||
|
def populate_file_tree(self, path):
|
||||||
|
try:
|
||||||
|
self.tree_model.setRootPath(path)
|
||||||
|
self.file_tree.setModel(self.tree_model)
|
||||||
|
self.directory_model = DirectoryFilterProxyModel()
|
||||||
|
self.directory_model.setSourceModel(self.tree_model)
|
||||||
|
self.file_tree.setModel(self.directory_model)
|
||||||
|
self.file_tree.setRootIndex(self.directory_model.mapFromSource(self.tree_model.index(path)))
|
||||||
|
self.list_model = QFileSystemModel()
|
||||||
|
self.list_model.setRootPath(path)
|
||||||
|
self.file_filter_model = FileFilterProxyModel()
|
||||||
|
self.file_filter_model.setSourceModel(self.list_model)
|
||||||
|
self.folder_contents_view.setModel(self.file_filter_model)
|
||||||
|
self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(path)))
|
||||||
|
self.current_dir_label.setText(path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error Populating File Tree: {e}")
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
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()
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
def player_state_changed(self, state):
|
||||||
|
if state == QMediaPlayer.StoppedState:
|
||||||
|
self.playlist.setCurrentIndex(0)
|
||||||
|
|
||||||
|
def player_position_changed(self, position):
|
||||||
|
pass
|
||||||
|
def player_duration_changed(self, duration):
|
||||||
|
pass
|
||||||
|
def playlist_current_index_changed(self, index):
|
||||||
|
pass
|
||||||
|
def playlist_current_media_changed(self, media):
|
||||||
|
pass
|
||||||
|
def playlist_media_inserted(self, start, end):
|
||||||
|
pass
|
||||||
|
def playlist_media_removed(self, start, end):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def change_directory(self, index):
|
||||||
|
index = self.directory_model.mapToSource(index)
|
||||||
|
try:
|
||||||
|
file_path = self.tree_model.filePath(index)
|
||||||
|
self.list_model.setRootPath(file_path)
|
||||||
|
self.current_dir_label.setText(file_path)
|
||||||
|
self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(file_path)))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error Changing Dirs.: {e}")
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
|
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_())
|
||||||
255
MidPlay.py
Executable file
255
MidPlay.py
Executable file
@ -0,0 +1,255 @@
|
|||||||
|
#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 fluidsynth
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from PyQt5.QtWidgets import (QApplication, QLabel, QListWidget, QFileDialog, QMessageBox, QWidget, QPushButton, QHBoxLayout,
|
||||||
|
QVBoxLayout,
|
||||||
|
QProgressBar,
|
||||||
|
QSlider) # structured for readability and to avoid long lines and it annoys my friend XD
|
||||||
|
from PyQt5.QtCore import QTimer, Qt
|
||||||
|
import threading
|
||||||
|
import cProfile # profiler remove for production
|
||||||
|
|
||||||
|
pygame.mixer.init()
|
||||||
|
pygame.init()
|
||||||
|
|
||||||
|
class MidPlayGUI(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.player = MidPlay()
|
||||||
|
self.current_midi_label = QLabel()
|
||||||
|
self.playlist_widget = QListWidget()
|
||||||
|
self.setWindowTitle("MidPlay - Midi Player")
|
||||||
|
self.init_ui()
|
||||||
|
self.timer = QTimer()
|
||||||
|
self.timer.timeout.connect(self.handle_song_end)
|
||||||
|
self.timer.start(1000)
|
||||||
|
|
||||||
|
def set_volume(self, value):
|
||||||
|
volume = value / 100
|
||||||
|
pygame.mixer.music.set_volume(volume)
|
||||||
|
|
||||||
|
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()
|
||||||
|
progress = current_time / total_time * 100
|
||||||
|
self.progress_bar.setValue(int(progress))
|
||||||
|
|
||||||
|
def handle_song_end(self):
|
||||||
|
if self.player.playing and not pygame.mixer.music.get_busy():
|
||||||
|
self.player.next_song()
|
||||||
|
if self.player.playlist:
|
||||||
|
self.player.current_index %= len(self.player.playlist)
|
||||||
|
filepath = self.player.playlist[self.player.current_index]
|
||||||
|
filename = os.path.basename(filepath)
|
||||||
|
self.current_midi_label.setText(f"Current MIDI: {filename}")
|
||||||
|
self.update_progress()
|
||||||
|
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
#label = QLabel("MidPlay - Midi player")
|
||||||
|
#label.setStyleSheet("font-size: 20px; font-weight: bold;")
|
||||||
|
|
||||||
|
self.progress_bar = QProgressBar()
|
||||||
|
self.volume_slider = QSlider(Qt.Horizontal)
|
||||||
|
self.volume_slider.setMinimum(0)
|
||||||
|
self.volume_slider.setMaximum(100)
|
||||||
|
self.volume_slider.setValue(100)
|
||||||
|
self.volume_slider.valueChanged.connect(self.set_volume)
|
||||||
|
self.current_midi_label.setText("Current MIDI: None")
|
||||||
|
|
||||||
|
|
||||||
|
self.playlist_widget.itemDoubleClicked.connect(self.play_selected_song)
|
||||||
|
pygame.mixer.music.set_endevent(pygame.USEREVENT)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
play_button = QPushButton("Play")
|
||||||
|
play_button.clicked.connect(self.player.play_midi)
|
||||||
|
|
||||||
|
pause_button = QPushButton("Pause")
|
||||||
|
pause_button.clicked.connect(self.player.pause)
|
||||||
|
|
||||||
|
stop_button = QPushButton("Stop")
|
||||||
|
stop_button.clicked.connect(self.player.stop)
|
||||||
|
|
||||||
|
next_button = QPushButton("Next")
|
||||||
|
next_button.clicked.connect(self.player.next_song)
|
||||||
|
|
||||||
|
back_button = QPushButton("Back")
|
||||||
|
back_button.clicked.connect(self.previous_song)
|
||||||
|
|
||||||
|
add_button = QPushButton("Add to Playlist")
|
||||||
|
add_button.clicked.connect(self.load_midi_file)
|
||||||
|
|
||||||
|
add_folder_button = QPushButton("Add Folder to Playlist")
|
||||||
|
add_folder_button.clicked.connect(self.load_folder)
|
||||||
|
|
||||||
|
clear_button = QPushButton("Clear Playlist")
|
||||||
|
clear_button.clicked.connect(self.clear_playlist)
|
||||||
|
|
||||||
|
# Window layout
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.current_midi_label)
|
||||||
|
layout.addWidget(self.playlist_widget)
|
||||||
|
layout.addWidget(self.progress_bar)
|
||||||
|
layout.addWidget(self.volume_slider)
|
||||||
|
layout.addWidget(play_button)
|
||||||
|
layout.addWidget(pause_button)
|
||||||
|
layout.addWidget(stop_button)
|
||||||
|
layout.addWidget(next_button)
|
||||||
|
layout.addWidget(back_button)
|
||||||
|
layout.addWidget(add_button)
|
||||||
|
layout.addWidget(add_folder_button)
|
||||||
|
layout.addWidget(clear_button)
|
||||||
|
|
||||||
|
progress_volume_layout = QHBoxLayout()
|
||||||
|
progress_volume_layout.addWidget(self.progress_bar)
|
||||||
|
progress_volume_layout.addWidget(self.volume_slider)
|
||||||
|
layout.addLayout(progress_volume_layout)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Event handlers
|
||||||
|
def play_selected_song(self, item):
|
||||||
|
index = self.playlist_widget.row(item)
|
||||||
|
self.current_index = index
|
||||||
|
filepath = self.player.playlist[self.current_index]
|
||||||
|
self.player.load_midi(filepath)
|
||||||
|
self.player.play_midi()
|
||||||
|
pygame.mixer.music.set_endevent(pygame.USEREVENT)
|
||||||
|
filename = os.path.basename(filepath)
|
||||||
|
self.current_midi_label.setText(f"Current MIDI: {filename}")
|
||||||
|
|
||||||
|
def load_midi_file(self):
|
||||||
|
filepath, _ = QFileDialog.getOpenFileName(self, "Select MIDI File", filter="MIDI files (*.mid *.midi)")
|
||||||
|
if filepath:
|
||||||
|
filename = os.path.basename(filepath)
|
||||||
|
self.player.load_midi(filepath)
|
||||||
|
self.current_midi_label.setText(f"Current MIDI: {filename}")
|
||||||
|
self.player.play_midi()
|
||||||
|
self.playlist_widget.addItem(filename)
|
||||||
|
self.player.add_to_playlist(filepath)
|
||||||
|
|
||||||
|
def load_folder(self):
|
||||||
|
folder = QFileDialog.getExistingDirectory(self, "Select Folder")
|
||||||
|
if folder:
|
||||||
|
for file in os.listdir(folder):
|
||||||
|
if file.endswith((".midi", ".mid")):
|
||||||
|
filepath = os.path.join(folder, file)
|
||||||
|
self.playlist_widget.addItem(file)
|
||||||
|
self.player.add_to_playlist(filepath) # Only add to playlist, don't load immediately!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
#probably should be in the MidPlay class
|
||||||
|
|
||||||
|
|
||||||
|
def previous_song(self):
|
||||||
|
if self.player.playlist:
|
||||||
|
filepath = self.player.playlist[self.current_index]
|
||||||
|
filename = os.path.basename(filepath)
|
||||||
|
self.player.current_index = (self.player.current_index - 1) % len(self.player.playlist)
|
||||||
|
self.current_midi_label.setText(f"Current MIDI: {filename}")
|
||||||
|
self.player.play_midi()
|
||||||
|
|
||||||
|
def clear_playlist(self):
|
||||||
|
self.player.clear_playlist()
|
||||||
|
self.playlist_widget.clear()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
confirmation = QMessageBox.question(self, "Exit Confirmation", "Are you sure you want to exit?", QMessageBox.Yes | QMessageBox.No)
|
||||||
|
if confirmation == QMessageBox.Yes:
|
||||||
|
pygame.mixer.quit()
|
||||||
|
pygame.quit()
|
||||||
|
event.accept()
|
||||||
|
else:
|
||||||
|
event.ignore()
|
||||||
|
|
||||||
|
class MidPlay:
|
||||||
|
"""The Heart of Midi Playback"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.playlist = []
|
||||||
|
self.current_midi = None
|
||||||
|
self.playing = False
|
||||||
|
self.current_index = 0
|
||||||
|
|
||||||
|
def load_midi(self, filepath: str) -> None:
|
||||||
|
def load():
|
||||||
|
try:
|
||||||
|
self.current_midi = pretty_midi.PrettyMIDI(filepath)
|
||||||
|
pygame.mixer.music.load(filepath)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading MIDI: {e}")
|
||||||
|
threading.Thread(target=load).start()
|
||||||
|
|
||||||
|
def add_to_playlist(self, filepath: str) -> None:
|
||||||
|
self.playlist.append(filepath)
|
||||||
|
|
||||||
|
def clear_playlist(self) -> None:
|
||||||
|
self.playlist = []
|
||||||
|
|
||||||
|
def play_midi(self) -> None:
|
||||||
|
def play():
|
||||||
|
if self.current_midi:
|
||||||
|
self.current_midi.instruments[0].synthesize()
|
||||||
|
pygame.mixer.music.play()
|
||||||
|
self.playing = True
|
||||||
|
pygame.mixer.music.set_endevent(pygame.USEREVENT)
|
||||||
|
else:
|
||||||
|
print("No MIDI file loaded")
|
||||||
|
threading.Thread(target=play).start()
|
||||||
|
|
||||||
|
def pause(self) -> None:
|
||||||
|
pygame.mixer.music.pause()
|
||||||
|
self.playing = False
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
pygame.mixer.music.stop()
|
||||||
|
self.playing = False
|
||||||
|
|
||||||
|
|
||||||
|
def next_song(self) -> None:
|
||||||
|
#print("Debug: next_song() called", self.playlist) debug line
|
||||||
|
if self.playlist:
|
||||||
|
self.current_index = (self.current_index + 1) % len(self.playlist)
|
||||||
|
filepath = self.playlist[self.current_index]
|
||||||
|
|
||||||
|
# If a new MIDI was loaded before the last one ended, respect that as the new playlist start
|
||||||
|
if self.current_midi and self.playing:
|
||||||
|
# print("Debug: New MIDI loaded before last one ended") # debug line
|
||||||
|
self.load_midi(filepath)
|
||||||
|
self.play_midi()
|
||||||
|
# print("Debug: Filepath:", filepath) # debug line
|
||||||
|
# print("Debug: Current MIDI:", self.current_midi) # debug line
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QApplication([])
|
||||||
|
player_gui = MidPlayGUI()
|
||||||
|
player_gui.show()
|
||||||
|
running = True
|
||||||
|
while True:
|
||||||
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.USEREVENT:
|
||||||
|
player_gui.player.next_song()
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
running = False
|
||||||
|
break
|
||||||
|
app.exec_()
|
||||||
213
ScanOrg.py
Executable file
213
ScanOrg.py
Executable file
@ -0,0 +1,213 @@
|
|||||||
|
#Path: ScanOrg.py
|
||||||
|
# Description: A class to scan and organize music files
|
||||||
|
|
||||||
|
import concurrent.futures
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import zipfile
|
||||||
|
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
|
||||||
|
|
||||||
|
# Directory Filter Proxy Model
|
||||||
|
class DirectoryFilterProxyModel(QSortFilterProxyModel):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setFilterCaseSensitivity(Qt.CaseInsensitive)
|
||||||
|
self.setFilterKeyColumn(0)
|
||||||
|
def filterAcceptsRow(self, source_row, source_parent):
|
||||||
|
index = self.sourceModel().index(source_row, 0, source_parent)
|
||||||
|
return self.sourceModel().isDir(index)
|
||||||
|
|
||||||
|
# File Filter Proxy Model
|
||||||
|
class FileFilterProxyModel(QSortFilterProxyModel):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setFilterCaseSensitivity(Qt.CaseInsensitive)
|
||||||
|
self.setFilterKeyColumn(0)
|
||||||
|
self.allowed_extensions = ['.zip', '.mp3', '.wav', '.flac', '.mid', '.midi', '.aiff', '.aif', '.aifc', '.au', '.snd', '.wv', '.wma', '.m4a']
|
||||||
|
|
||||||
|
def filterAcceptsRow(self, source_row, source_parent):
|
||||||
|
index = self.sourceModel().index(source_row, 0, source_parent)
|
||||||
|
if self.sourceModel().isDir(index):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return self.sourceModel().fileName(index).endswith(tuple(self.allowed_extensions))
|
||||||
|
|
||||||
|
# File Scan and Organize
|
||||||
|
class file_scanner:
|
||||||
|
def __init__(self):
|
||||||
|
self.file_list = []
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
def scan(self, path):
|
||||||
|
def background_scan(self, path):
|
||||||
|
if path in self.cache:
|
||||||
|
return self.cache[path]
|
||||||
|
|
||||||
|
file_list = []
|
||||||
|
dirs_queue = queue.Queue()
|
||||||
|
dirs_queue.put(path)
|
||||||
|
|
||||||
|
while not dirs_queue.empty():
|
||||||
|
current_path = dirs_queue.get()
|
||||||
|
try:
|
||||||
|
for root, dirs, files in os.walk(current_path):
|
||||||
|
for dir in dirs:
|
||||||
|
dirs_queue.put(os.path.join(root, dir))
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(('.mp3', '.wav', '.flac', '.mid', '.midi', '.aiff', '.aif', '.aifc', '.au', '.snd', '.wv', '.wma', '.m4a')):
|
||||||
|
file_list.append(os.path.join(root, file))
|
||||||
|
self.cache[current_path] = file_list
|
||||||
|
except (IOError, PermissionError, FileNotFoundError, OSError) as e:
|
||||||
|
print(f"Error Scanning Files: {e}")
|
||||||
|
|
||||||
|
return file_list
|
||||||
|
|
||||||
|
file_list = []
|
||||||
|
thread = threading.Thread(target=background_scan, args=(path, file_list))
|
||||||
|
thread.start()
|
||||||
|
return file_list
|
||||||
|
|
||||||
|
def get_file_list(self):
|
||||||
|
return self.file_list
|
||||||
|
|
||||||
|
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:
|
||||||
|
index = file_filter_model.mapToSource(index)
|
||||||
|
file_path = list_model.filePath(index)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if file_path.endswith(".zip"):
|
||||||
|
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:
|
||||||
|
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}")
|
||||||
|
|
||||||
|
class organizer:
|
||||||
|
global metadata_queue
|
||||||
|
metadata_queue = queue.Queue()
|
||||||
|
def __init__(self):
|
||||||
|
self.file_list = []
|
||||||
|
self.artist_list = []
|
||||||
|
self.album_list = []
|
||||||
|
self.genre_list = []
|
||||||
|
self.year_list = []
|
||||||
|
self.file_scanner = file_scanner()
|
||||||
|
self.file_info_cache = {}
|
||||||
|
|
||||||
|
def scan(self, path):
|
||||||
|
if path in self.file_scanner.cache:
|
||||||
|
self.file_list = self.file_scanner.cache[path]
|
||||||
|
else:
|
||||||
|
self.file_list = self.file_scanner.scan(path)
|
||||||
|
|
||||||
|
def get_file_list(self):
|
||||||
|
return self.file_list
|
||||||
|
def clear_file_list(self):
|
||||||
|
self.file_list = []
|
||||||
|
def get_artist_list(self):
|
||||||
|
return self.artist_list
|
||||||
|
def get_album_list(self):
|
||||||
|
return self.album_list
|
||||||
|
def get_genre_list(self):
|
||||||
|
return self.genre_list
|
||||||
|
def get_year_list(self):
|
||||||
|
return self.year_list
|
||||||
|
def clear_artist_list(self):
|
||||||
|
self.artist_list = []
|
||||||
|
def clear_album_list(self):
|
||||||
|
self.album_list = []
|
||||||
|
def clear_genre_list(self):
|
||||||
|
self.genre_list = []
|
||||||
|
def clear_year_list(self):
|
||||||
|
self.year_list = []
|
||||||
|
|
||||||
|
def organize(self):
|
||||||
|
results_queue = queue.Queue()
|
||||||
|
metadata = pyqtSignal(dict)
|
||||||
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
futures = []
|
||||||
|
for file in self.file_list:
|
||||||
|
futures.append(executor.submit(self.get_file_info, file, results_queue))
|
||||||
|
|
||||||
|
for future in concurrent.futures.as_completed(futures):
|
||||||
|
try:
|
||||||
|
metadata = future.result()
|
||||||
|
if metadata['artist'] not in self.artist_list:
|
||||||
|
self.artist_list.append(metadata['artist'])
|
||||||
|
if metadata['album'] not in self.album_list:
|
||||||
|
self.album_list.append(metadata['album'])
|
||||||
|
if metadata['genre'] not in self.genre_list:
|
||||||
|
self.genre_list.append(metadata['genre'])
|
||||||
|
if metadata['year'] not in self.year_list:
|
||||||
|
self.year_list.append(metadata['year'])
|
||||||
|
except mutagen.mp3.HeaderNotFoundError:
|
||||||
|
print('Error: ' + file)
|
||||||
|
continue
|
||||||
|
while not metadata_queue.put(metadata):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_file_info(self, file, results_queue):
|
||||||
|
try:
|
||||||
|
audio = mutagen.File(file)
|
||||||
|
artist = audio['artist'][0]
|
||||||
|
album = audio['album'][0]
|
||||||
|
genre = audio['genre'][0]
|
||||||
|
year = audio['date'][0]
|
||||||
|
if artist not in self.artist_list:
|
||||||
|
self.artist_list.append(artist)
|
||||||
|
if album not in self.album_list:
|
||||||
|
self.album_list.append(album)
|
||||||
|
if genre not in self.genre_list:
|
||||||
|
self.genre_list.append(genre)
|
||||||
|
if year not in self.year_list:
|
||||||
|
self.year_list.append(year)
|
||||||
|
metadata = {
|
||||||
|
'artist': artist,
|
||||||
|
'album': album,
|
||||||
|
'genre': genre,
|
||||||
|
'year': year
|
||||||
|
}
|
||||||
|
self.metadata_extracted.emit(metadata)
|
||||||
|
except Exception as e:
|
||||||
|
results_queue.put(None)
|
||||||
|
print('Error: ' + file)
|
||||||
|
if os.path.splitext(file)[1] == ('.mp3', '.wav', '.flac', '.m4a', '.wma', 'mid', '.midi'):
|
||||||
|
self.organize_audio()
|
||||||
|
audio = mutagen.File(file)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BIN
__pycache__/MidPlay.cpython-310.pyc
Executable file
BIN
__pycache__/MidPlay.cpython-310.pyc
Executable file
Binary file not shown.
BIN
__pycache__/ScanOrg.cpython-310.pyc
Executable file
BIN
__pycache__/ScanOrg.cpython-310.pyc
Executable file
Binary file not shown.
94
compression.py
Executable file
94
compression.py
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
# imports
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import rarfile
|
||||||
|
import py7zr
|
||||||
|
import shutil
|
||||||
|
import tarfile
|
||||||
|
import argparse
|
||||||
|
import tqdm
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
from multiprocessing import pool
|
||||||
|
|
||||||
|
# File Compressor
|
||||||
|
class Compressor:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _compress_folder(self, source_path, archive_file, archive_format):
|
||||||
|
for root, _, files in os.walk(source_path):
|
||||||
|
for file in files:
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
archive_path = os.path.relpath(file_path, source_path)
|
||||||
|
self._compress_file(file_path, archive_file, archive_path, archive_format)
|
||||||
|
|
||||||
|
def _compress_file(self, file_path, archive_file, archive_path, archive_format):
|
||||||
|
|
||||||
|
if archive_format == "zip":
|
||||||
|
with open(file_path, 'rb') as file:
|
||||||
|
for chunk in iter(lambda: file.read(1024 * 1024), b''):
|
||||||
|
archive_file.writestr(archive_path, chunk)
|
||||||
|
|
||||||
|
elif archive_format == "tar":
|
||||||
|
archive_file.add(file_path, arcname=archive_path)
|
||||||
|
|
||||||
|
elif archive_format == "7z":
|
||||||
|
archive_file.write(file_path, archive_path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported archive format: {archive_format}")
|
||||||
|
|
||||||
|
def compress(self, source_path, archive_name, archive_format="zip"):
|
||||||
|
pbar = tqdm.tqdm(total=100, unit="B", unit_scale=True, desc="Compressing")
|
||||||
|
supported_formats = ["zip", "tar", "7z"]
|
||||||
|
|
||||||
|
if archive_format not in supported_formats:
|
||||||
|
raise ValueError(f"Unsupported archive format: {archive_format}")
|
||||||
|
archive_path = os.path.join(os.path.dirname(source_path), f"{archive_name}.{archive_format}")
|
||||||
|
|
||||||
|
# Check if source path exists
|
||||||
|
if not os.path.exists(source_path):
|
||||||
|
print(f"Source path does not exist: {source_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if archive path already exists
|
||||||
|
if os.path.exists(archive_path):
|
||||||
|
print(f"Archive path already exists: {archive_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Open archive file based on format
|
||||||
|
if archive_format == "zip":
|
||||||
|
archive_file = zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED)
|
||||||
|
elif archive_format == "tar":
|
||||||
|
archive_file = tarfile.open(archive_path, mode="w")
|
||||||
|
elif archive_format == "7z":
|
||||||
|
archive_file = py7zr.SevenZipFile(archive_path, mode="w")
|
||||||
|
|
||||||
|
# Compress the source path
|
||||||
|
try:
|
||||||
|
|
||||||
|
if os.path.isdir(source_path):
|
||||||
|
self._compress_folder(source_path, archive_file, archive_format)
|
||||||
|
pbar.update(1)
|
||||||
|
else:
|
||||||
|
if os.path.isfile(source_path):
|
||||||
|
self._compress_file(source_path, archive_file, "", archive_format)
|
||||||
|
pbar.update(1)
|
||||||
|
else:
|
||||||
|
print(f"Source path is not a file or directory: {source_path}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Compressed to: {archive_path} error:{e}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
archive_file.close() # Ensure closing the archive file
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Compress files")
|
||||||
|
parser.add_argument("source", help="Path to the file or folder to compress")
|
||||||
|
parser.add_argument("archive_name", help="Name for the compressed archive")
|
||||||
|
parser.add_argument("-f", "--format", choices=["zip", "tar", "7z"], default="zip", help="Archive format")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
compressor = Compressor()
|
||||||
|
compressor.compress(args.source, args.archive_name, args.format)
|
||||||
11
docker-compose.debug.yml
Executable file
11
docker-compose.debug.yml
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
fbrowser:
|
||||||
|
image: fbrowser
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
command: ["sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 Fbrowser.py "]
|
||||||
|
ports:
|
||||||
|
- 5678:5678
|
||||||
8
docker-compose.yml
Executable file
8
docker-compose.yml
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
fbrowser:
|
||||||
|
image: fbrowser
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
74
extraction.py
Executable file
74
extraction.py
Executable file
@ -0,0 +1,74 @@
|
|||||||
|
# extractor.py
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import rarfile
|
||||||
|
import py7zr
|
||||||
|
import argparse
|
||||||
|
from tqdm import tqdm
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
class Extractor:
|
||||||
|
|
||||||
|
def zipviewer(self, source, destination):
|
||||||
|
print(f"checking if {source} exists")
|
||||||
|
if not os.path.exists(source):
|
||||||
|
print(f"Error: Archive file not found: {source}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
print(f"checking if {destination} exists")
|
||||||
|
if not os.path.exists(destination):
|
||||||
|
print(f"{destination} does not exist, creating {destination}")
|
||||||
|
os.makedirs(destination)
|
||||||
|
print(f"{destination} created")
|
||||||
|
else:
|
||||||
|
print(f"{destination} exists")
|
||||||
|
|
||||||
|
print(f"checking if {source} is a valid archive file")
|
||||||
|
if source.endswith(".zip"):
|
||||||
|
print(f"Extracting all files from {source} to {destination}")
|
||||||
|
with zipfile.ZipFile(source, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(destination)
|
||||||
|
print(f"Extracted all files from {source} to {destination}")
|
||||||
|
|
||||||
|
elif source.endswith(".rar, .tar.gz, .tar.bz2, .tar.xz, .tar.zst"):
|
||||||
|
with rarfile.RarFile(source, 'r') as rar_ref:
|
||||||
|
rar_ref.extractall(destination)
|
||||||
|
print(f"Extracted all files from {source} to {destination}")
|
||||||
|
|
||||||
|
elif source.endswith(".7z"):
|
||||||
|
with py7zr.SevenZipFile(source, 'r') as sevenzip_ref:
|
||||||
|
sevenzip_ref.extractall(destination)
|
||||||
|
print(f"Extracted all files from {source} to {destination}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Unsupported file format: {source}")
|
||||||
|
|
||||||
|
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:
|
||||||
|
print(f"Extraction Error: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Welcome to the Archive Extractor!")
|
||||||
|
parser = argparse.ArgumentParser(description="Compress or extract files")
|
||||||
|
subparsers = parser.add_subparsers(title="Command", dest="command")
|
||||||
|
|
||||||
|
# Subparser for extraction
|
||||||
|
extract_parser = subparsers.add_parser("extract")
|
||||||
|
extract_parser.add_argument("source", help="Path to the archive file")
|
||||||
|
extract_parser.add_argument("destination", help="Extraction directory")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "extract":
|
||||||
|
print(f"Extracting {args.source} to {args.destination}")
|
||||||
|
extractor = Extractor()
|
||||||
|
extractor.zipviewer(args.source, args.destination)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
paq-8l_intel.exe
Executable file
BIN
paq-8l_intel.exe
Executable file
Binary file not shown.
102
paq7asm-x86_64.asm
Executable file
102
paq7asm-x86_64.asm
Executable file
@ -0,0 +1,102 @@
|
|||||||
|
; YASM x86-64 assembly language code for PAQ7/8 ver. 2, Jan 18, 2007
|
||||||
|
;
|
||||||
|
; (C) 2005-2007, Matt Mahoney, Matthew Fite.
|
||||||
|
; This is free software under GPL, http://www.gnu.org/licenses/gpl.txt
|
||||||
|
;
|
||||||
|
; This code was tested on an Athlon-64 under Ubuntu Linux 2.6.15.27.amd64-generic
|
||||||
|
; with paq8f and paq8jd. It should work with any PAQ version since paq7,
|
||||||
|
; because all versions use the same paq7asm.asm code for 32 bit Windows/Linux
|
||||||
|
; versions. To compile e.g. paq8jd in Linux:
|
||||||
|
;
|
||||||
|
; yasm paq7asm-x86_64.asm -f elf -m amd64
|
||||||
|
; g++ -O3 -s -fomit-frame-pointer -DUNIX paq8jd.cpp paq7asm-x86_64.o -o paq8jd
|
||||||
|
;
|
||||||
|
; This code has not been tested in Windows. (You would need XP Professional
|
||||||
|
; 64 bit edition and a 64 bit compiler).
|
||||||
|
|
||||||
|
section .text
|
||||||
|
|
||||||
|
BITS 64
|
||||||
|
|
||||||
|
; Vector product a*b of n signed words, returning signed dword scaled
|
||||||
|
; down by 8 bits. n is rounded up to a multiple of 8.
|
||||||
|
|
||||||
|
global dot_product ; (short* a, short* b, int n)
|
||||||
|
align 16
|
||||||
|
dot_product:
|
||||||
|
mov rcx, rdx ; n
|
||||||
|
mov rax, rdi ; a
|
||||||
|
mov rdx, rsi ; b
|
||||||
|
add rcx, 7 ; n rounding up
|
||||||
|
and rcx, -8
|
||||||
|
jz .done
|
||||||
|
sub rax, 16
|
||||||
|
sub rdx, 16
|
||||||
|
pxor xmm0, xmm0 ; sum = 0
|
||||||
|
.loop: ; each loop sums 4 products
|
||||||
|
movdqa xmm1, [rax+rcx*2] ; put parital sums of vector product in xmm1
|
||||||
|
pmaddwd xmm1, [rdx+rcx*2]
|
||||||
|
psrad xmm1, 8
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
sub rcx, 8
|
||||||
|
ja .loop
|
||||||
|
movdqa xmm1, xmm0 ; add 4 parts of xmm0 and return in eax
|
||||||
|
psrldq xmm1, 8
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
movdqa xmm1, xmm0
|
||||||
|
psrldq xmm1, 4
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
movd rax, xmm0
|
||||||
|
.done
|
||||||
|
ret
|
||||||
|
|
||||||
|
; Train n neural network weights w[n] on inputs t[n] and err.
|
||||||
|
; w[i] += (t[i]*err*2 >> 16)+1 >> 1 bounded to +- 32K.
|
||||||
|
; n is rounded up to a multiple of 8.
|
||||||
|
|
||||||
|
;1st arg rdi -> *t
|
||||||
|
;2nd arg rsi -> *w
|
||||||
|
;3rd arg rdx -> n
|
||||||
|
;4th arg rcx -> err (signed 16 bits)
|
||||||
|
|
||||||
|
global train ; (short* t, short* w, int n, int err)
|
||||||
|
BITS 64
|
||||||
|
align 16
|
||||||
|
train:
|
||||||
|
mov rax, rcx ; err
|
||||||
|
and rax, 0xffff ; put 8 copies of err in xmm0
|
||||||
|
movd xmm0, rax
|
||||||
|
movd xmm1, rax
|
||||||
|
pslldq xmm1, 2
|
||||||
|
por xmm0, xmm1
|
||||||
|
movdqa xmm1, xmm0
|
||||||
|
pslldq xmm1, 4
|
||||||
|
por xmm0, xmm1
|
||||||
|
movdqa xmm1, xmm0
|
||||||
|
pslldq xmm1, 8
|
||||||
|
por xmm0, xmm1;
|
||||||
|
pcmpeqb xmm1, xmm1 ; 8 copies of 1 in xmm1
|
||||||
|
psrlw xmm1, 15
|
||||||
|
mov rcx, rdx ; n
|
||||||
|
mov rax, rdi ; t
|
||||||
|
mov rdx, rsi ; w
|
||||||
|
add rcx, 7 ; n/8 rounding up
|
||||||
|
and rcx, -8
|
||||||
|
sub rax, 16
|
||||||
|
sub rdx, 16
|
||||||
|
jz .done
|
||||||
|
align 16
|
||||||
|
.loop: ; each iteration adjusts 8 weights
|
||||||
|
movdqa xmm2, [rdx+rcx*2] ; w[i]
|
||||||
|
movdqa xmm3, [rax+rcx*2] ; t[i]
|
||||||
|
paddsw xmm3, xmm3 ; t[i]*2
|
||||||
|
pmulhw xmm3, xmm0 ; t[i]*err*2 >> 16
|
||||||
|
paddsw xmm3, xmm1 ; (t[i]*err*2 >> 16)+1
|
||||||
|
psraw xmm3, 1 ; (t[i]*err*2 >> 16)+1 >> 1
|
||||||
|
paddsw xmm2, xmm3 ; w[i] + xmm3
|
||||||
|
movdqa [rdx+rcx*2], xmm2
|
||||||
|
sub rcx, 8
|
||||||
|
ja .loop
|
||||||
|
.done:
|
||||||
|
ret
|
||||||
|
|
||||||
140
paq7asm.asm
Executable file
140
paq7asm.asm
Executable file
@ -0,0 +1,140 @@
|
|||||||
|
; NASM assembly language code for PAQ7.
|
||||||
|
; (C) 2005, Matt Mahoney.
|
||||||
|
; This is free software under GPL, http://www.gnu.org/licenses/gpl.txt
|
||||||
|
;
|
||||||
|
; MINGW g++: nasm paq7asm.asm -f win32 --prefix _
|
||||||
|
; DJGPP g++: nasm paq7asm.asm -f coff --prefix _
|
||||||
|
; Borland, Mars: nasm paq7asm.asm -f obj --prefix _
|
||||||
|
; Linux: nasm paq7asm.asm -f elf
|
||||||
|
;
|
||||||
|
; For other Windows compilers try -f win32 or -f obj. Some old versions
|
||||||
|
; of Linux should use -f aout instead of -f elf.
|
||||||
|
;
|
||||||
|
; This code will only work on a Pentium-MMX or higher. It doesn't
|
||||||
|
; use extended (Katmai/SSE) instructions. It won't work
|
||||||
|
; in 64-bit mode.
|
||||||
|
|
||||||
|
section .text use32 class=CODE
|
||||||
|
|
||||||
|
; Reset after MMX
|
||||||
|
global do_emms
|
||||||
|
do_emms:
|
||||||
|
emms
|
||||||
|
ret
|
||||||
|
|
||||||
|
; Vector product a*b of n signed words, returning signed dword scaled
|
||||||
|
; down by 8 bits. n is rounded up to a multiple of 8.
|
||||||
|
|
||||||
|
global dot_product ; (short* a, short* b, int n)
|
||||||
|
align 16
|
||||||
|
dot_product:
|
||||||
|
mov eax, [esp+4] ; a
|
||||||
|
mov edx, [esp+8] ; b
|
||||||
|
mov ecx, [esp+12] ; n
|
||||||
|
add ecx, 7 ; n rounding up
|
||||||
|
and ecx, -8
|
||||||
|
jz .done
|
||||||
|
sub eax, 8
|
||||||
|
sub edx, 8
|
||||||
|
pxor mm0, mm0 ; sum = 0
|
||||||
|
.loop: ; each loop sums 4 products
|
||||||
|
movq mm1, [eax+ecx*2] ; put halves of vector product in mm0
|
||||||
|
pmaddwd mm1, [edx+ecx*2]
|
||||||
|
movq mm2, [eax+ecx*2-8]
|
||||||
|
pmaddwd mm2, [edx+ecx*2-8]
|
||||||
|
psrad mm1, 8
|
||||||
|
psrad mm2, 8
|
||||||
|
paddd mm0, mm1
|
||||||
|
paddd mm0, mm2
|
||||||
|
sub ecx, 8
|
||||||
|
ja .loop
|
||||||
|
movq mm1, mm0 ; add 2 halves of mm0 and return in eax
|
||||||
|
psrlq mm1, 32
|
||||||
|
paddd mm0, mm1
|
||||||
|
movd eax, mm0
|
||||||
|
emms
|
||||||
|
.done
|
||||||
|
ret
|
||||||
|
|
||||||
|
; This should work on a Pentium 4 or higher in 32-bit mode,
|
||||||
|
; but it isn't much faster than the MMX version so I don't use it.
|
||||||
|
|
||||||
|
global dot_product_sse2 ; (short* a, short* b, int n)
|
||||||
|
align 16
|
||||||
|
dot_product_sse2:
|
||||||
|
mov eax, [esp+4] ; a
|
||||||
|
mov edx, [esp+8] ; b
|
||||||
|
mov ecx, [esp+12] ; n
|
||||||
|
add ecx, 7 ; n rounding up
|
||||||
|
and ecx, -8
|
||||||
|
jz .done
|
||||||
|
sub eax, 16
|
||||||
|
sub edx, 16
|
||||||
|
pxor xmm0, xmm0 ; sum = 0
|
||||||
|
.loop: ; each loop sums 4 products
|
||||||
|
movdqa xmm1, [eax+ecx*2] ; put parital sums of vector product in xmm0
|
||||||
|
pmaddwd xmm1, [edx+ecx*2]
|
||||||
|
psrad xmm1, 8
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
sub ecx, 8
|
||||||
|
ja .loop
|
||||||
|
movdqa xmm1, xmm0 ; add 4 parts of xmm0 and return in eax
|
||||||
|
psrldq xmm1, 8
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
movdqa xmm1, xmm0
|
||||||
|
psrldq xmm1, 4
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
movd eax, xmm0
|
||||||
|
.done
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
; Train n neural network weights w[n] on inputs t[n] and err.
|
||||||
|
; w[i] += t[i]*err*2+1 >> 17 bounded to +- 32K.
|
||||||
|
; n is rounded up to a multiple of 8.
|
||||||
|
|
||||||
|
global train ; (short* t, short* w, int n, int err)
|
||||||
|
align 16
|
||||||
|
train:
|
||||||
|
mov eax, [esp+16] ; err
|
||||||
|
and eax, 0xffff ; put 4 copies of err in mm0
|
||||||
|
movd mm0, eax
|
||||||
|
movd mm1, eax
|
||||||
|
psllq mm1, 16
|
||||||
|
por mm0, mm1
|
||||||
|
movq mm1, mm0
|
||||||
|
psllq mm1, 32
|
||||||
|
por mm0, mm1
|
||||||
|
pcmpeqb mm1, mm1 ; 4 copies of 1 in mm1
|
||||||
|
psrlw mm1, 15
|
||||||
|
mov eax, [esp+4] ; t
|
||||||
|
mov edx, [esp+8] ; w
|
||||||
|
mov ecx, [esp+12] ; n
|
||||||
|
add ecx, 7 ; n/8 rounding up
|
||||||
|
and ecx, -8
|
||||||
|
sub eax, 8
|
||||||
|
sub edx, 8
|
||||||
|
jz .done
|
||||||
|
.loop: ; each iteration adjusts 8 weights
|
||||||
|
movq mm2, [edx+ecx*2] ; w[i]
|
||||||
|
movq mm3, [eax+ecx*2] ; t[i]
|
||||||
|
movq mm4, [edx+ecx*2-8] ; w[i]
|
||||||
|
movq mm5, [eax+ecx*2-8] ; t[i]
|
||||||
|
paddsw mm3, mm3
|
||||||
|
paddsw mm5, mm5
|
||||||
|
pmulhw mm3, mm0
|
||||||
|
pmulhw mm5, mm0
|
||||||
|
paddsw mm3, mm1
|
||||||
|
paddsw mm5, mm1
|
||||||
|
psraw mm3, 1
|
||||||
|
psraw mm5, 1
|
||||||
|
paddsw mm2, mm3
|
||||||
|
paddsw mm4, mm5
|
||||||
|
movq [edx+ecx*2], mm2
|
||||||
|
movq [edx+ecx*2-8], mm4
|
||||||
|
sub ecx, 8
|
||||||
|
ja .loop
|
||||||
|
.done:
|
||||||
|
emms
|
||||||
|
ret
|
||||||
|
|
||||||
93
paq7asmsse.asm
Executable file
93
paq7asmsse.asm
Executable file
@ -0,0 +1,93 @@
|
|||||||
|
; NASM assembly language code for PAQ7.
|
||||||
|
; (C) 2005, Matt Mahoney.
|
||||||
|
; train - written by wowtiger, Jan. 30, 2007
|
||||||
|
;
|
||||||
|
; This is free software under GPL, http://www.gnu.org/licenses/gpl.txt
|
||||||
|
;
|
||||||
|
; This code is a replacement for paq7asm.asm for newer processors
|
||||||
|
; supporting SSE2 instructions. It is about 1% faster than the
|
||||||
|
; equivalent MMX code. It can be linked with any version of paq7*
|
||||||
|
; or paq8*. Assemble as below, then link following the instructions
|
||||||
|
; in the C++ source code, replacing paq7asm.obj with paq7asmsse.obj.
|
||||||
|
; No C++ code changes are needed.
|
||||||
|
;
|
||||||
|
; MINGW g++: nasm paq7asmsse.asm -f win32 --prefix _
|
||||||
|
; DJGPP g++: nasm paq7asmsse.asm -f coff --prefix _
|
||||||
|
; Borland, Mars: nasm paq7asmsse.asm -f obj --prefix _
|
||||||
|
; Linux: nasm paq7asmsse.asm -f elf
|
||||||
|
;
|
||||||
|
|
||||||
|
section .text use32 class=CODE
|
||||||
|
|
||||||
|
; Vector product a*b of n signed words, returning signed dword scaled
|
||||||
|
; down by 8 bits. n is rounded up to a multiple of 8.
|
||||||
|
|
||||||
|
global dot_product ; (short* a, short* b, int n)
|
||||||
|
align 16
|
||||||
|
dot_product:
|
||||||
|
mov eax, [esp+4] ; a
|
||||||
|
mov edx, [esp+8] ; b
|
||||||
|
mov ecx, [esp+12] ; n
|
||||||
|
add ecx, 7 ; n rounding up
|
||||||
|
and ecx, -8
|
||||||
|
jz .done
|
||||||
|
sub eax, 16
|
||||||
|
sub edx, 16
|
||||||
|
pxor xmm0, xmm0 ; sum = 0
|
||||||
|
.loop: ; each loop sums 4 products
|
||||||
|
movdqa xmm1, [eax+ecx*2] ; put parital sums of vector product in xmm0
|
||||||
|
pmaddwd xmm1, [edx+ecx*2]
|
||||||
|
psrad xmm1, 8
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
sub ecx, 8
|
||||||
|
ja .loop
|
||||||
|
movdqa xmm1, xmm0 ; add 4 parts of xmm0 and return in eax
|
||||||
|
psrldq xmm1, 8
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
movdqa xmm1, xmm0
|
||||||
|
psrldq xmm1, 4
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
movd eax, xmm0
|
||||||
|
.done
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
; Train n neural network weights w[n] on inputs t[n] and err.
|
||||||
|
; w[i] += t[i]*err*2+1 >> 17 bounded to +- 32K.
|
||||||
|
; n is rounded up to a multiple of 8.
|
||||||
|
|
||||||
|
; Train for SSE2
|
||||||
|
; Use this code to get some performance...
|
||||||
|
|
||||||
|
global train ; (short* t, short* w, int n, int err)
|
||||||
|
align 16
|
||||||
|
train:
|
||||||
|
mov eax, [esp+4] ; t
|
||||||
|
mov edx, [esp+8] ; w
|
||||||
|
mov ecx, [esp+12] ; n
|
||||||
|
add ecx, 7 ; n/8 rounding up
|
||||||
|
and ecx, -8
|
||||||
|
jz .done
|
||||||
|
sub eax, 16
|
||||||
|
sub edx, 16
|
||||||
|
movd xmm0, [esp+16]
|
||||||
|
pshuflw xmm0,xmm0,0
|
||||||
|
punpcklqdq xmm0,xmm0
|
||||||
|
.loop: ; each iteration adjusts 8 weights
|
||||||
|
movdqa xmm3, [eax+ecx*2] ; t[i]
|
||||||
|
movdqa xmm2, [edx+ecx*2] ; w[i]
|
||||||
|
paddsw xmm3, xmm3 ; t[i]*2
|
||||||
|
pmulhw xmm3, xmm0 ; t[i]*err*2 >> 16
|
||||||
|
paddsw xmm3, [_mask] ; (t[i]*err*2 >> 16)+1
|
||||||
|
psraw xmm3, 1 ; (t[i]*err*2 >> 16)+1 >> 1
|
||||||
|
paddsw xmm2, xmm3 ; w[i] + xmm3
|
||||||
|
movdqa [edx+ecx*2], xmm2
|
||||||
|
sub ecx, 8
|
||||||
|
ja .loop
|
||||||
|
.done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
align 16
|
||||||
|
_mask dd 10001h,10001h,10001h,10001h ; 8 copies of 1 in xmm1
|
||||||
|
|
||||||
|
|
||||||
178
paqtest.py
Executable file
178
paqtest.py
Executable file
@ -0,0 +1,178 @@
|
|||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
from multiprocessing import Pool
|
||||||
|
from sys import platform
|
||||||
|
|
||||||
|
|
||||||
|
def set_archive_filename(output: str, paq8l_version: str) -> str:
|
||||||
|
basename, ext = os.path.splitext(output)
|
||||||
|
if ext == 'paq8l{}'.format(paq8l_version):
|
||||||
|
return output
|
||||||
|
if ext == 'paq8l':
|
||||||
|
return output + paq8l_version
|
||||||
|
else:
|
||||||
|
return output + '.paq8l' + paq8l_version
|
||||||
|
|
||||||
|
|
||||||
|
def compress_file(file: str, output: str, exe_filename: str, compression_arg: str, paq8l_version: str) -> None:
|
||||||
|
output = set_archive_filename(output, paq8l_version)
|
||||||
|
if platform == "win32":
|
||||||
|
cmd = [exe_filename, compression_arg, file, output]
|
||||||
|
else:
|
||||||
|
cmd = "{} {} \"{}\" \"{}\"".format(exe_filename, compression_arg, file, output)
|
||||||
|
print(cmd)
|
||||||
|
subprocess.run(cmd, shell=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_archive(input_location: str, archive: str, exe_filename: str, paq8l_version: str) -> None:
|
||||||
|
archive = set_archive_filename(archive, paq8l_version)
|
||||||
|
if platform == "win32":
|
||||||
|
cmd = [exe_filename, '-t', archive]
|
||||||
|
else:
|
||||||
|
cmd = "{} -t \"{}\" \"{}\"".format(exe_filename, archive, input_location)
|
||||||
|
print(cmd)
|
||||||
|
subprocess.run(cmd, shell=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_text_file(filelist: list, input_location: str, filename: str) -> str:
|
||||||
|
if filelist:
|
||||||
|
filelist_path = os.path.join(input_location, filename + '.txt')
|
||||||
|
print("Writing filelist.txt")
|
||||||
|
txt_file = open(filelist_path, 'w')
|
||||||
|
txt_file.write('\n')
|
||||||
|
for file in filelist:
|
||||||
|
if not os.path.isdir(file):
|
||||||
|
txt_file.write(file + '\n')
|
||||||
|
txt_file.close()
|
||||||
|
return '@' + filelist_path
|
||||||
|
else:
|
||||||
|
return input_location
|
||||||
|
|
||||||
|
def compression_args(args: argparse) -> str:
|
||||||
|
if not args.level:
|
||||||
|
level = '9'
|
||||||
|
else:
|
||||||
|
level = args.level
|
||||||
|
|
||||||
|
|
||||||
|
def get_output_location(args: argparse) -> str:
|
||||||
|
if not args.output:
|
||||||
|
output_location = args.input
|
||||||
|
else:
|
||||||
|
output_location = args.output
|
||||||
|
return output_location
|
||||||
|
|
||||||
|
def parse_action(args: argparse) -> tuple:
|
||||||
|
action = "compress"
|
||||||
|
action_finished = "Compression"
|
||||||
|
if args.test and not args.test_only:
|
||||||
|
action += " and test"
|
||||||
|
action_finished += " and testing"
|
||||||
|
if args.test_only:
|
||||||
|
action = "test"
|
||||||
|
action_finished = "Testing"
|
||||||
|
return action, action_finished
|
||||||
|
|
||||||
|
|
||||||
|
def single_threaded_compression(args: argparse, input_location: str, output_location: str, filename: str,
|
||||||
|
exe_filename: str, paq8l_version: str, compression_args: str) -> None:
|
||||||
|
filelist = []
|
||||||
|
action, _ = parse_action(args)
|
||||||
|
if os.path.isdir(input_location):
|
||||||
|
print("Listing files to {}".format(action))
|
||||||
|
for dir_, _, files in os.walk(input_location):
|
||||||
|
for fileName in sorted(files):
|
||||||
|
rel_file = os.path.join(fileName)
|
||||||
|
filelist.append(rel_file)
|
||||||
|
print(rel_file)
|
||||||
|
single_file = False
|
||||||
|
else:
|
||||||
|
print("file to {}".format(action), filename)
|
||||||
|
single_file = True
|
||||||
|
|
||||||
|
if (filelist or single_file) and not args.test_only:
|
||||||
|
filename = create_text_file(filelist, input_location, filename)
|
||||||
|
print("\nStarting compression...\n")
|
||||||
|
compress_file(filename, output_location, exe_filename, compression_args)
|
||||||
|
if args.test or args.test_only:
|
||||||
|
print("\nVerifying archive...\n")
|
||||||
|
test_archive(input_location, output_location, exe_filename)
|
||||||
|
|
||||||
|
|
||||||
|
def multithreaded_compression(args: argparse, input_location: str, output_location: str, filename: str,
|
||||||
|
exe_filename: str, compression_args: str) -> None:
|
||||||
|
if os.path.isdir(input_location):
|
||||||
|
print("Compressing each file separately")
|
||||||
|
pool = Pool()
|
||||||
|
for file in sorted(os.listdir(input_location)):
|
||||||
|
file_path = os.path.join(input_location, file)
|
||||||
|
pool.apply_async(compress_file, (file_path, file_path, exe_filename, compression_args))
|
||||||
|
pool.close()
|
||||||
|
pool.join()
|
||||||
|
else:
|
||||||
|
print("file to compress:", filename)
|
||||||
|
print("\nStarting compression...\n")
|
||||||
|
compress_file(input_location, output_location, exe_filename, compression_args)
|
||||||
|
if args.test or args.test_only:
|
||||||
|
print("\nVerifying archive is not yet implemented for multi-threaded individual file compression...\n")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description='This script will generate a filelist file which will be used by '
|
||||||
|
'paq8l_v207 for compressing. It is also used for testing if you '
|
||||||
|
'use the -t or -to argument')
|
||||||
|
required = parser.add_argument_group('required arguments')
|
||||||
|
optional = parser.add_argument_group('optional arguments')
|
||||||
|
required.add_argument('-i', '--input', help="Input file or folder to compress. REQUIRED", required=True)
|
||||||
|
optional.add_argument('-v', '--version', help='Version of paq8l to use. Example: 207. Default is 207',
|
||||||
|
required=False, default='207')
|
||||||
|
optional.add_argument('-l', '--level', help="Compression level and switches. Example: 9a to compress using level 9 "
|
||||||
|
"and with the 'Adaptive learning rate' switch. Default is 9",
|
||||||
|
required=False, default='9')
|
||||||
|
optional.add_argument('-o', '--output', help="Output file to use. If not used, the archive will be saved at the "
|
||||||
|
"root of the parent folder where the file/folder to compress is "
|
||||||
|
"located. Do not provide extension", required=False, default=None)
|
||||||
|
optional.add_argument('-t', '--test', help="Optional flag to test the archive after compressing it. It is "
|
||||||
|
"recommended to use this option. Default is not to test",
|
||||||
|
required=False, action='store_true')
|
||||||
|
optional.add_argument('-to', '--test-only', help="Skip compression and just test the archive.",
|
||||||
|
required=False, action='store_true')
|
||||||
|
optional.add_argument('-r', '--remove', help="Deletes the filelist text file. Not recommended unless you plan not "
|
||||||
|
"to test the archive later. Default is not to remove", required=False,
|
||||||
|
default=False, action='store_true')
|
||||||
|
optional.add_argument('-mt', '--multithread', help="Compresses each file on a separate thread. This creates "
|
||||||
|
"individual archives with just one file", required=False,
|
||||||
|
default=False, action='store_true')
|
||||||
|
optional.add_argument('-n', '--nativecpu', help="Use the native CPU version. "
|
||||||
|
"These versions usually ends with _nativecpu and may provide "
|
||||||
|
"performane improvements on your machine over the generic version",
|
||||||
|
required=False,
|
||||||
|
default=False, action='store_true')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Variables:
|
||||||
|
exe_filename = "/home/stan/Documents/Dev/Fbroswer/paq8l"
|
||||||
|
compression_args = '-' + args.level
|
||||||
|
input_location = args.input
|
||||||
|
output_location = get_output_location(args)
|
||||||
|
filename = os.path.basename(input_location)
|
||||||
|
|
||||||
|
# Compression
|
||||||
|
if not args.multithread:
|
||||||
|
single_threaded_compression(args, input_location, output_location, filename,
|
||||||
|
exe_filename, compression_args)
|
||||||
|
else:
|
||||||
|
multithreaded_compression(args, input_location, output_location, filename,
|
||||||
|
exe_filename)
|
||||||
|
|
||||||
|
# Remove file list if not in multithreaded mode.
|
||||||
|
if args.remove and not args.multithread:
|
||||||
|
print("\nRemoving the filelist file")
|
||||||
|
os.remove(os.path.join(input_location, filename + '.txt'))
|
||||||
|
_, action_finished = parse_action(args)
|
||||||
|
print("\n{} finished!".format(action_finished))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
0
pypaqtest.paq
Executable file
0
pypaqtest.paq
Executable file
43
readme.txt
Executable file
43
readme.txt
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
paq8l is an open source (GPL) file compressor and archiver.
|
||||||
|
Last update Mar. 18, 2007 by Matt Mahoney.
|
||||||
|
|
||||||
|
Contents of paq8l.zip:
|
||||||
|
|
||||||
|
readme.txt - this file
|
||||||
|
paq8l.exe - Win32 (MinGW g++) executable for Pentium MMX and higher
|
||||||
|
paq-8l_intel.exe - Faster Win32 executable (compiled by Johan de Bock with Intel C++ from http://uclc.info )
|
||||||
|
paq8l - Linux executable (by Giorgio Tani, Mar. 18, 2007)
|
||||||
|
|
||||||
|
paq8l.cpp - C++ source code for all versions (Mar. 8, 2007)
|
||||||
|
paq7asm.asm - NASM/YASM assembler code for Pentium MMX or higher
|
||||||
|
paq7asmsse.asm - NASM/YASM for Pentium 4 (SSE2) or higher in 32 bit mode
|
||||||
|
paq7asm-x86_64.asm - YASM for x86-64 bit processors (tested in 64 bit Linux)
|
||||||
|
|
||||||
|
paq8l can be compiled for other processors without the assembler
|
||||||
|
code using the -DNOASM option (but it will run slower).
|
||||||
|
The assembler code is the same for all paq7/8 versions.
|
||||||
|
|
||||||
|
paq8l was written by Matt Mahoney (as paq8f) with improvements by
|
||||||
|
Bill Pettis (based on improvements by Alexander Ratushnyak and
|
||||||
|
Przemyslaw Skibinski in the paq8hp* series) and Serge Osnach (additional
|
||||||
|
models), and Andrew Paterson (Borland port). The assembler code was ported
|
||||||
|
to 64 bit by Matthew Fite and 32 bit SSE2 by wowtiger.
|
||||||
|
|
||||||
|
Other contributors to the PAQ project: Berto Destasio (tuning earlier
|
||||||
|
models for better compression), Johan de Bock (benchmarking, compiling
|
||||||
|
fast exectuables), David A. Scott (arithmetic coder improvements),
|
||||||
|
Fabio Buffoni (speed optimizations), Jason Schmidt (compression
|
||||||
|
improvements), Rudi Cilibrasi (text modeling), and Pavel L. Holoborodko
|
||||||
|
(PGM image modeling), and Jari Aalto (licensing/distribution).
|
||||||
|
|
||||||
|
This work would not be possible without the benchmarking efforts of
|
||||||
|
Marcus Hutter (Hutter prize), Werner Bergmans (maximumcompression.com)
|
||||||
|
Johan de Bock (UCLC), Berto Destasio (Emilcont benchmark), Stephan Busch
|
||||||
|
(Squeeze Chart), Leonid A. Broukhis (Calgary Corpus Challenge),
|
||||||
|
and Black Fox.
|
||||||
|
|
||||||
|
A similar (but rewritten) context mixing algorithm is used in
|
||||||
|
WinRK 3.0.3 (pwcm mode) by Malcolm Taylor. Modified versions of
|
||||||
|
PAQ (faster but less compression) are used in UDA and WinUDA by dwing,
|
||||||
|
and in xml-wrt by Przemyslaw Skibinski.
|
||||||
|
|
||||||
6
requirements.txt
Executable file
6
requirements.txt
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
# 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
|
||||||
152
stanzip.py
Executable file
152
stanzip.py
Executable file
@ -0,0 +1,152 @@
|
|||||||
|
# stanzip.py
|
||||||
|
# Description: Can Compress and Extract files using Various libraries more compression methods are going to be added
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import rarfile
|
||||||
|
import py7zr
|
||||||
|
import shutil
|
||||||
|
import argparse
|
||||||
|
import tqdm
|
||||||
|
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
# File Extractor
|
||||||
|
class Extractor:
|
||||||
|
|
||||||
|
def zipviewer(self, source, destination):
|
||||||
|
|
||||||
|
if not os.path.exists(source):
|
||||||
|
print(f"Error: Archive file not found: {source}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
pbar = tqdm.tqdm(total=100, desc="Extracting Archive file")
|
||||||
|
|
||||||
|
if not os.path.exists(destination):
|
||||||
|
os.makedirs(destination)
|
||||||
|
pbar.update(1)
|
||||||
|
|
||||||
|
if source.endswith(".zip"):
|
||||||
|
with zipfile.ZipFile(source, 'r') as zip_ref:
|
||||||
|
with tqdm.tqdm(total=len(zipfile.ZipFile(source).namelist()), desc="Extracting ZIP files") as pbar:
|
||||||
|
for filename in zip_ref.namelist():
|
||||||
|
zip_ref.extract(filename, destination)
|
||||||
|
pbar.update(1)
|
||||||
|
print(f"Extracted all files from {source} to {destination}")
|
||||||
|
|
||||||
|
elif source.endswith(".rar, .tar.gz, .tar.bz2, .tar.xz, .tar.zst"):
|
||||||
|
with rarfile.RarFile(source, 'r') as rar_ref:
|
||||||
|
with tqdm.tqdm(total=len(rar_ref.namelist()), desc="Extracting RAR files") as pbar:
|
||||||
|
for filename in rar_ref.namelist():
|
||||||
|
rar_ref.extractall(filename, destination)
|
||||||
|
pbar.update(1)
|
||||||
|
print(f"Extracted all files from {source} to {destination}")
|
||||||
|
|
||||||
|
elif source.endswith(".7z"):
|
||||||
|
with py7zr.SevenZipFile(source, 'r') as sevenzip_ref:
|
||||||
|
with tqdm.tqdm(total=len(sevenzip_ref.namelist()), desc="Extracting 7z files") as pbar:
|
||||||
|
for filename in sevenzip_ref.namelist():
|
||||||
|
sevenzip_ref.extractall(filename, destination)
|
||||||
|
pbar.update(1)
|
||||||
|
print(f"Extracted all files from {source} to {destination}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Unsupported file format: {source}")
|
||||||
|
|
||||||
|
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:
|
||||||
|
print(f"Extraction Error: {e}")
|
||||||
|
|
||||||
|
# File Compressor
|
||||||
|
class Compressor:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _compress_folder(self, source_path, zip_file):
|
||||||
|
for root, _, files in os.walk(source_path):
|
||||||
|
for file in files:
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
archive_path = os.path.relpath(file_path, source_path)
|
||||||
|
self._compress_file(file_path, zip_file, archive_path)
|
||||||
|
|
||||||
|
def _compress_file(self, file_path, zip_file, archive_path=None):
|
||||||
|
if not archive_path:
|
||||||
|
archive_path = os.path.basename(file_path)
|
||||||
|
|
||||||
|
if archive_path.endswith(".zip"):
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(file_path, 'rb') as file:
|
||||||
|
for chunk in iter(lambda: file.read(1024 * 1024), b''):
|
||||||
|
zip_file.writestr(archive_path, chunk)
|
||||||
|
|
||||||
|
def compress(self, source_path, archive_name, archive_format="zip"):
|
||||||
|
|
||||||
|
if archive_format != "zip":
|
||||||
|
raise ValueError(f"Unsupported archive format: {archive_format}")
|
||||||
|
|
||||||
|
archive_path = os.path.join(os.path.dirname(source_path), f"{archive_name}.{archive_format}")
|
||||||
|
|
||||||
|
# Check if source path exists
|
||||||
|
if not os.path.exists(source_path):
|
||||||
|
print(f"Source path does not exist: {source_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Compress the source path
|
||||||
|
with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||||
|
if os.path.isdir(source_path):
|
||||||
|
print(f"Compressing folder: {source_path}")
|
||||||
|
self._compress_folder(source_path, zip_file)
|
||||||
|
else:
|
||||||
|
print(f"Compressing file: {source_path}")
|
||||||
|
self._compress_file(source_path, zip_file)
|
||||||
|
|
||||||
|
print(f"Compressed to: {archive_path}")
|
||||||
|
|
||||||
|
if os.path.isdir(source_path):
|
||||||
|
file_list = []
|
||||||
|
for root, _, files in os.walk(source_path):
|
||||||
|
for file in files:
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
file_list.append(file_path)
|
||||||
|
|
||||||
|
# Use thread pool
|
||||||
|
with ThreadPoolExecutor(max_workers=4) as executor:
|
||||||
|
for file_path in file_list:
|
||||||
|
executor.submit(self._compress_file, file_path, zip_file)
|
||||||
|
executor.shutdown(wait=True)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Compress or extract files")
|
||||||
|
subparsers = parser.add_subparsers(title="Command", dest="command")
|
||||||
|
|
||||||
|
# Subparser for extraction
|
||||||
|
extract_parser = subparsers.add_parser("extract")
|
||||||
|
extract_parser.add_argument("source", help="Path to the archive file")
|
||||||
|
extract_parser.add_argument("destination", help="Extraction directory")
|
||||||
|
|
||||||
|
# Subparser for compression
|
||||||
|
compress_parser = subparsers.add_parser("compress")
|
||||||
|
compress_parser.add_argument("source", help="Path to the file or folder to compress")
|
||||||
|
compress_parser.add_argument("archive_name", help="Name for the compressed archive")
|
||||||
|
compress_parser.add_argument("-f", "--format", choices=["zip"], default="zip", help="Archive format (default: zip)")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "extract":
|
||||||
|
extractor = Extractor()
|
||||||
|
extractor.zipviewer(args.source, args.destination)
|
||||||
|
if args.command == "compress":
|
||||||
|
compressor = Compressor()
|
||||||
|
compressor.compress(args.source, args.archive_name, args.format)
|
||||||
|
else:
|
||||||
|
print("Invalid command. Use 'extract' or 'compress'")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
test.png
Executable file
BIN
test.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
168
test.py
Executable file
168
test.py
Executable file
@ -0,0 +1,168 @@
|
|||||||
|
import pretty_midi
|
||||||
|
import random
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, filedialog
|
||||||
|
import pygame
|
||||||
|
import pypianoroll # type: ignore
|
||||||
|
from icecream import ic # type: ignore
|
||||||
|
|
||||||
|
class midgen:
|
||||||
|
|
||||||
|
def __init__(self, status_label: ttk.Label):
|
||||||
|
self.status_label = status_label
|
||||||
|
self.scales = self.scales()
|
||||||
|
|
||||||
|
def scales(self):
|
||||||
|
scales = {
|
||||||
|
"Major": [0, 2, 4, 5, 7, 9, 11],
|
||||||
|
"Minor": [0, 2, 3, 5, 7, 8, 10],
|
||||||
|
"Pentatonic": [0, 2, 4, 7, 9],
|
||||||
|
"Blues": [0, 3, 5, 6, 7, 10],
|
||||||
|
"Whole Tone": [0, 2, 4, 6, 8, 10],
|
||||||
|
"Chromatic": [i for i in range(12)],
|
||||||
|
"Octatonic": [0, 1, 3, 4, 6, 7, 9, 10],
|
||||||
|
"Harmonic Minor": [0, 2, 3, 5, 7, 8, 11],
|
||||||
|
"Melodic Minor": [0, 2, 3, 5, 7, 9, 11],
|
||||||
|
"Dorian": [0, 2, 3, 5, 7, 9, 10],
|
||||||
|
"Phrygian": [0, 1, 3, 5, 7, 8, 10],
|
||||||
|
"Lydian": [0, 2, 4, 6, 7, 9, 11],
|
||||||
|
"Mixolydian": [0, 2, 4, 5, 7, 9, 10],
|
||||||
|
"Locrian": [0, 1, 3, 5, 6, 8, 10],
|
||||||
|
"Diminished": [0, 2, 3, 5, 6, 8, 9, 11],
|
||||||
|
"Whole Half Diminished": [0, 2, 3, 5, 6, 8, 9, 11],
|
||||||
|
"Arabian": [0, 2, 4, 5, 6, 8, 10],
|
||||||
|
"Hungarian Minor": [0, 2, 3, 6, 7, 8, 11],
|
||||||
|
"Enigmatic": [0, 1, 4, 6, 8, 10, 11],
|
||||||
|
"Neapolitan Major": [0, 1, 3, 5, 7, 9, 11],
|
||||||
|
"Neapolitan Minor": [0, 1, 3, 5, 7, 8, 11],
|
||||||
|
"Bluesy": [0, 3, 5, 6, 7, 10],
|
||||||
|
"Hawaiian": [0, 2, 3, 7, 9],
|
||||||
|
"Japanese": [0, 1, 5, 7, 8],
|
||||||
|
"Chinese": [0, 4, 6, 7, 11],
|
||||||
|
"Gypsy": [0, 2, 3, 6, 7, 8, 10],
|
||||||
|
"Hirojoshi": [0, 2, 3, 7, 8],
|
||||||
|
"In Sen": [0, 1, 5, 7, 10],
|
||||||
|
"Iwato": [0, 1, 5, 6, 10],
|
||||||
|
"Kumoi": [0, 2, 3, 7, 9],
|
||||||
|
"Pelog": [0, 1, 3, 7, 8],
|
||||||
|
"Ryukyu": [0, 4, 5, 7, 11],
|
||||||
|
"Spanish": [0, 1, 3, 4, 5, 6, 8, 10],
|
||||||
|
"Todi": [0, 1, 3, 6, 7, 8, 11],
|
||||||
|
"Yo": [0, 2, 5, 7, 9]
|
||||||
|
}
|
||||||
|
return scales
|
||||||
|
|
||||||
|
|
||||||
|
def generate_midi(self):
|
||||||
|
self.status_label.config(text='Generating MIDI...')
|
||||||
|
|
||||||
|
try:
|
||||||
|
midi = pretty_midi.PrettyMIDI()
|
||||||
|
instrument = pretty_midi.Instrument(0)
|
||||||
|
|
||||||
|
scale = random.choice(list(self.scales.keys()))
|
||||||
|
scale_notes = self.scales[scale]
|
||||||
|
ic(f"Using scale: {scale}")
|
||||||
|
ic(f"Using notes: {scale_notes}")
|
||||||
|
|
||||||
|
for start, end in zip(range(0, 100, 10), range(10, 110, 10)):
|
||||||
|
note = pretty_midi.Note(
|
||||||
|
velocity=100, pitch=random.choice(scale_notes),
|
||||||
|
start=start, end=end
|
||||||
|
)
|
||||||
|
instrument.notes.append(note)
|
||||||
|
|
||||||
|
midi.instruments.append(instrument)
|
||||||
|
|
||||||
|
filepath = filedialog.asksaveasfilename(defaultextension='.mid')
|
||||||
|
if filepath:
|
||||||
|
midi.write(filepath)
|
||||||
|
track = pypianoroll.Multitrack(filepath)
|
||||||
|
track.plot()
|
||||||
|
self.status_label.config(text='MIDI generated successfully!')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.status_label.config(text=f"Error generating MIDI: {e}")
|
||||||
|
|
||||||
|
class MidPlay:
|
||||||
|
"""A class to handle MIDI file playback."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.playlist = []
|
||||||
|
self.current_midi = None
|
||||||
|
self.playing = False
|
||||||
|
pygame.mixer.init()
|
||||||
|
|
||||||
|
def load_midi(self, filepath: str) -> None:
|
||||||
|
try:
|
||||||
|
self.current_midi = pretty_midi.PrettyMIDI(filepath)
|
||||||
|
pygame.mixer.music.load(filepath)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading MIDI: {e}")
|
||||||
|
|
||||||
|
def add_to_playlist(self, filepath: str) -> None:
|
||||||
|
"""Adds a MIDI file to the playlist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: The path to the MIDI file.
|
||||||
|
"""
|
||||||
|
self.playlist.append(filepath)
|
||||||
|
|
||||||
|
def clear_playlist(self) -> None:
|
||||||
|
"""Clears the playlist."""
|
||||||
|
self.playlist = []
|
||||||
|
|
||||||
|
def play_midi(self) -> None:
|
||||||
|
"""Starts or resumes playback of the current MIDI file."""
|
||||||
|
if self.current_midi:
|
||||||
|
self.current_midi.instruments[0].synthesize()
|
||||||
|
pygame.mixer.music.play()
|
||||||
|
self.playing = True
|
||||||
|
else:
|
||||||
|
print("No MIDI file loaded")
|
||||||
|
|
||||||
|
def pause(self) -> None:
|
||||||
|
"""Pauses playback."""
|
||||||
|
pygame.mixer.music.pause()
|
||||||
|
self.playing = False
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""Stops playback."""
|
||||||
|
pygame.mixer.music.stop()
|
||||||
|
self.playing = False
|
||||||
|
|
||||||
|
class UserInterface:
|
||||||
|
def __init__(self):
|
||||||
|
self.root = tk.Tk()
|
||||||
|
self.root.title("MIDI Generator")
|
||||||
|
self.root.geometry("400x200")
|
||||||
|
self.root.resizable(True, True)
|
||||||
|
self.status_label = ttk.Label(self.root, text="")
|
||||||
|
self.status_label.pack()
|
||||||
|
|
||||||
|
self.midi_generator = midgen(self.status_label)
|
||||||
|
self.midi_player = MidPlay()
|
||||||
|
|
||||||
|
|
||||||
|
self.filepath = None
|
||||||
|
self.midi = None
|
||||||
|
|
||||||
|
|
||||||
|
self.generate_button = ttk.Button(self.root, text="Generate MIDI", command=self.midi_generator.generate_midi)
|
||||||
|
self.generate_button.pack()
|
||||||
|
|
||||||
|
self.load_button = ttk.Button(self.root, text="Load MIDI", command=lambda: self.midi_player.load_midi(self.filepath))
|
||||||
|
self.load_button.pack()
|
||||||
|
|
||||||
|
self.play_button = ttk.Button(self.root, text="Play MIDI", command=lambda: self.midi_player.play_midi())
|
||||||
|
self.play_button.pack()
|
||||||
|
|
||||||
|
self.exit_button = ttk.Button(self.root, text="Exit", command=self.root.quit)
|
||||||
|
self.exit_button.pack()
|
||||||
|
|
||||||
|
window = tk.Tk()
|
||||||
|
window.title("MIDI Generator")
|
||||||
|
self.root.mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ui = UserInterface()
|
||||||
37
test_Fbrowser.py
Executable file
37
test_Fbrowser.py
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
from fbrowser import SampleMusicBrowser
|
||||||
|
|
||||||
|
class TestSampleMusicBrowser(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.app = QApplication([])
|
||||||
|
self.browser = SampleMusicBrowser()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.app.quit()
|
||||||
|
|
||||||
|
def test_player_error(self):
|
||||||
|
# Mock QMediaPlayer and set error code
|
||||||
|
self.browser.player.error = MagicMock(return_value=1)
|
||||||
|
self.browser.player.errorString = MagicMock(return_value="Test Error")
|
||||||
|
self.browser.player_error(1)
|
||||||
|
# Assert that the error message is printed
|
||||||
|
self.assertIn("An error occurred: Code:1 Test Error", self.browser.console_output)
|
||||||
|
|
||||||
|
def test_player_media_status_changed(self):
|
||||||
|
# Mock QMediaPlayer and set media status
|
||||||
|
self.browser.player_media_status_changed(2)
|
||||||
|
# Assert that the media status is printed
|
||||||
|
self.assertIn("Media Status: 2", self.browser.console_output)
|
||||||
|
|
||||||
|
def test_play_file(self):
|
||||||
|
# Mock QFileSystemModel and set file path
|
||||||
|
self.browser.list_model.filePath = MagicMock(return_value="/path/to/file.mp3")
|
||||||
|
# Call play_file method
|
||||||
|
self.browser.play_file(None)
|
||||||
|
# Assert that the player is playing the correct media
|
||||||
|
self.assertEqual(self.browser.playlist.media(0).canonicalUrl().toString(), "file:///path/to/file.mp3")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
159
testmidi.py
Normal file
159
testmidi.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileSystemModel, QTreeView, QLabel, QComboBox
|
||||||
|
from PyQt5.QtCore import QDir, Qt, QThread, pyqtSignal
|
||||||
|
import mido
|
||||||
|
import pygame
|
||||||
|
import numpy as np
|
||||||
|
from mingus.midi import fluidsynth
|
||||||
|
from mingus.containers import Note
|
||||||
|
import soundfile as sf
|
||||||
|
|
||||||
|
class MidiPlayerThread(QThread):
|
||||||
|
update_signal = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, file_path):
|
||||||
|
super().__init__()
|
||||||
|
self.file_path = file_path
|
||||||
|
self.playing = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
midi_file = mido.MidiFile(self.file_path)
|
||||||
|
for msg in midi_file.play():
|
||||||
|
if not self.playing:
|
||||||
|
break
|
||||||
|
if not msg.is_meta:
|
||||||
|
if msg.type == 'note_on':
|
||||||
|
fluidsynth.play_Note(Note(msg.note), msg.channel, msg.velocity)
|
||||||
|
elif msg.type == 'note_off':
|
||||||
|
fluidsynth.stop_Note(Note(msg.note), msg.channel)
|
||||||
|
elif msg.type == 'control_change':
|
||||||
|
fluidsynth.control_change(msg.channel, msg.control, msg.value)
|
||||||
|
self.update_signal.emit(f"Playing: {msg}")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.playing = False
|
||||||
|
|
||||||
|
class AudioPlayerThread(QThread):
|
||||||
|
update_signal = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, file_path):
|
||||||
|
super().__init__()
|
||||||
|
self.file_path = file_path
|
||||||
|
self.playing = True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
pygame.mixer.music.load(self.file_path)
|
||||||
|
pygame.mixer.music.play()
|
||||||
|
while pygame.mixer.music.get_busy() and self.playing:
|
||||||
|
pygame.time.Clock().tick(10)
|
||||||
|
self.update_signal.emit(f"Playing audio: {pygame.mixer.music.get_pos() / 1000:.2f} seconds")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.playing = False
|
||||||
|
pygame.mixer.music.stop()
|
||||||
|
|
||||||
|
class MidiPlayer(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("MIDI Player and Audio File Browser")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
|
||||||
|
self.central_widget = QWidget()
|
||||||
|
self.setCentralWidget(self.central_widget)
|
||||||
|
self.layout = QHBoxLayout(self.central_widget)
|
||||||
|
|
||||||
|
# File Browser
|
||||||
|
self.model = QFileSystemModel()
|
||||||
|
self.model.setRootPath(QDir.rootPath())
|
||||||
|
self.model.setNameFilters(["*.mid", "*.midi", "*.mp3", "*.wav", "*.sf2"])
|
||||||
|
self.model.setNameFilterDisables(False)
|
||||||
|
|
||||||
|
self.tree = QTreeView()
|
||||||
|
self.tree.setModel(self.model)
|
||||||
|
self.tree.setRootIndex(self.model.index(QDir.homePath()))
|
||||||
|
self.tree.setColumnWidth(0, 250)
|
||||||
|
self.tree.setAnimated(False)
|
||||||
|
self.tree.setIndentation(20)
|
||||||
|
self.tree.setSortingEnabled(True)
|
||||||
|
self.tree.setWindowTitle("File Browser")
|
||||||
|
self.tree.clicked.connect(self.on_file_clicked)
|
||||||
|
|
||||||
|
# Player controls
|
||||||
|
self.player_widget = QWidget()
|
||||||
|
self.player_layout = QVBoxLayout(self.player_widget)
|
||||||
|
|
||||||
|
self.file_label = QLabel("No file selected")
|
||||||
|
self.player_layout.addWidget(self.file_label)
|
||||||
|
|
||||||
|
self.play_button = QPushButton("Play")
|
||||||
|
self.play_button.clicked.connect(self.play_file)
|
||||||
|
self.player_layout.addWidget(self.play_button)
|
||||||
|
|
||||||
|
self.stop_button = QPushButton("Stop")
|
||||||
|
self.stop_button.clicked.connect(self.stop_file)
|
||||||
|
self.player_layout.addWidget(self.stop_button)
|
||||||
|
|
||||||
|
self.soundfont_combo = QComboBox()
|
||||||
|
self.soundfont_combo.currentIndexChanged.connect(self.change_soundfont)
|
||||||
|
self.player_layout.addWidget(self.soundfont_combo)
|
||||||
|
|
||||||
|
self.status_label = QLabel("")
|
||||||
|
self.player_layout.addWidget(self.status_label)
|
||||||
|
|
||||||
|
# Add widgets to main layout
|
||||||
|
self.layout.addWidget(self.tree)
|
||||||
|
self.layout.addWidget(self.player_widget)
|
||||||
|
|
||||||
|
# Initialize pygame mixer
|
||||||
|
pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=1024)
|
||||||
|
|
||||||
|
# Initialize FluidSynth
|
||||||
|
fluidsynth.init(sf2="/path/to/default/soundfont.sf2") # Adjust this path as needed
|
||||||
|
|
||||||
|
self.player_thread = None
|
||||||
|
|
||||||
|
def on_file_clicked(self, index):
|
||||||
|
file_path = self.model.filePath(index)
|
||||||
|
self.file_label.setText(os.path.basename(file_path))
|
||||||
|
if file_path.lower().endswith('.sf2'):
|
||||||
|
self.load_soundfont(file_path)
|
||||||
|
|
||||||
|
def load_soundfont(self, sf2_path):
|
||||||
|
try:
|
||||||
|
fluidsynth.init(sf2=sf2_path)
|
||||||
|
self.soundfont_combo.clear()
|
||||||
|
self.soundfont_combo.addItems([f"Instrument {i}" for i in range(128)]) # MIDI has 128 standard instruments
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading soundfont: {e}")
|
||||||
|
|
||||||
|
def change_soundfont(self, index):
|
||||||
|
fluidsynth.set_instrument(0, index) # Set instrument for channel 0
|
||||||
|
|
||||||
|
def play_file(self):
|
||||||
|
file_path = self.model.filePath(self.tree.currentIndex())
|
||||||
|
if file_path.lower().endswith(('.mid', '.midi')):
|
||||||
|
self.player_thread = MidiPlayerThread(file_path)
|
||||||
|
elif file_path.lower().endswith(('.mp3', '.wav')):
|
||||||
|
self.player_thread = AudioPlayerThread(file_path)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.player_thread.update_signal.connect(self.update_status)
|
||||||
|
self.player_thread.start()
|
||||||
|
|
||||||
|
def stop_file(self):
|
||||||
|
if self.player_thread and self.player_thread.isRunning():
|
||||||
|
self.player_thread.stop()
|
||||||
|
self.player_thread.wait()
|
||||||
|
pygame.mixer.stop()
|
||||||
|
fluidsynth.stop_everything()
|
||||||
|
|
||||||
|
def update_status(self, status):
|
||||||
|
self.status_label.setText(status)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
player = MidiPlayer()
|
||||||
|
player.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
Loading…
x
Reference in New Issue
Block a user