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