From 18ca54544051175eb44ffd077ba761fb56d1380e Mon Sep 17 00:00:00 2001 From: Stan Date: Sat, 29 Jun 2024 01:01:51 -0500 Subject: [PATCH] main push to repo --- .dockerignore | 27 + .vscode/launch.json | 19 + .vscode/settings.json | 3 + .vscode/tasks.json | 26 + Dockerfile | 23 + Fbrowser.py | 273 ++ MidPlay.py | 255 ++ ScanOrg.py | 213 ++ __pycache__/MidPlay.cpython-310.pyc | Bin 0 -> 8113 bytes __pycache__/ScanOrg.cpython-310.pyc | Bin 0 -> 7706 bytes compression.py | 94 + docker-compose.debug.yml | 11 + docker-compose.yml | 8 + extraction.py | 74 + paq-8l_intel.exe | Bin 0 -> 155648 bytes paq7asm-x86_64.asm | 102 + paq7asm.asm | 140 ++ paq7asmsse.asm | 93 + paq8l.cpp | 3575 +++++++++++++++++++++++++++ paq8l.exe | Bin 0 -> 37376 bytes paq9a.cpp | 1222 +++++++++ paq9a.exe | Bin 0 -> 18432 bytes paqtest.py | 178 ++ pypaqtest.paq | 0 readme.txt | 43 + requirements.txt | 6 + stanzip.py | 152 ++ test.png | Bin 0 -> 2396 bytes test.py | 168 ++ test_Fbrowser.py | 37 + testmidi.py | 159 ++ 31 files changed, 6901 insertions(+) create mode 100755 .dockerignore create mode 100755 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100755 .vscode/tasks.json create mode 100755 Dockerfile create mode 100755 Fbrowser.py create mode 100755 MidPlay.py create mode 100755 ScanOrg.py create mode 100755 __pycache__/MidPlay.cpython-310.pyc create mode 100755 __pycache__/ScanOrg.cpython-310.pyc create mode 100755 compression.py create mode 100755 docker-compose.debug.yml create mode 100755 docker-compose.yml create mode 100755 extraction.py create mode 100755 paq-8l_intel.exe create mode 100755 paq7asm-x86_64.asm create mode 100755 paq7asm.asm create mode 100755 paq7asmsse.asm create mode 100755 paq8l.cpp create mode 100755 paq8l.exe create mode 100755 paq9a.cpp create mode 100755 paq9a.exe create mode 100755 paqtest.py create mode 100755 pypaqtest.paq create mode 100755 readme.txt create mode 100755 requirements.txt create mode 100755 stanzip.py create mode 100755 test.png create mode 100755 test.py create mode 100755 test_Fbrowser.py create mode 100644 testmidi.py diff --git a/.dockerignore b/.dockerignore new file mode 100755 index 0000000..0b1e1e7 --- /dev/null +++ b/.dockerignore @@ -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 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100755 index 0000000..f3d8430 --- /dev/null +++ b/.vscode/launch.json @@ -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" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..39c0c4f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "github.gitAuthentication": false +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100755 index 0000000..37fc95f --- /dev/null +++ b/.vscode/tasks.json @@ -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" + } + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..109f18f --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/Fbrowser.py b/Fbrowser.py new file mode 100755 index 0000000..0f87ef5 --- /dev/null +++ b/Fbrowser.py @@ -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_()) \ No newline at end of file diff --git a/MidPlay.py b/MidPlay.py new file mode 100755 index 0000000..19f8bd5 --- /dev/null +++ b/MidPlay.py @@ -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_() \ No newline at end of file diff --git a/ScanOrg.py b/ScanOrg.py new file mode 100755 index 0000000..1a48c33 --- /dev/null +++ b/ScanOrg.py @@ -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) + + + \ No newline at end of file diff --git a/__pycache__/MidPlay.cpython-310.pyc b/__pycache__/MidPlay.cpython-310.pyc new file mode 100755 index 0000000000000000000000000000000000000000..00219d16f5bf6e4b327da5d49ddb28ac4864d72f GIT binary patch literal 8113 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 YCu{^v21f}Le_Ay9AKNtk%lh*F0CAy&>i_@% literal 0 HcmV?d00001 diff --git a/__pycache__/ScanOrg.cpython-310.pyc b/__pycache__/ScanOrg.cpython-310.pyc new file mode 100755 index 0000000000000000000000000000000000000000..be7279af674ed4ca72127b1e2b32a9923183aca7 GIT binary patch literal 7706 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 literal 0 HcmV?d00001 diff --git a/compression.py b/compression.py new file mode 100755 index 0000000..e228356 --- /dev/null +++ b/compression.py @@ -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) diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml new file mode 100755 index 0000000..bf8c4d0 --- /dev/null +++ b/docker-compose.debug.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100755 index 0000000..a84be70 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3.4' + +services: + fbrowser: + image: fbrowser + build: + context: . + dockerfile: ./Dockerfile diff --git a/extraction.py b/extraction.py new file mode 100755 index 0000000..70e509d --- /dev/null +++ b/extraction.py @@ -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() diff --git a/paq-8l_intel.exe b/paq-8l_intel.exe new file mode 100755 index 0000000000000000000000000000000000000000..f6f3684c22e90be4b139fccc4437fdd1c884755c GIT binary patch literal 155648 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%0D} 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 literal 0 HcmV?d00001 diff --git a/paq7asm-x86_64.asm b/paq7asm-x86_64.asm new file mode 100755 index 0000000..a0754a6 --- /dev/null +++ b/paq7asm-x86_64.asm @@ -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 + diff --git a/paq7asm.asm b/paq7asm.asm new file mode 100755 index 0000000..82d55a7 --- /dev/null +++ b/paq7asm.asm @@ -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 + diff --git a/paq7asmsse.asm b/paq7asmsse.asm new file mode 100755 index 0000000..98ff613 --- /dev/null +++ b/paq7asmsse.asm @@ -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 + + diff --git a/paq8l.cpp b/paq8l.cpp new file mode 100755 index 0000000..3f69df8 --- /dev/null +++ b/paq8l.cpp @@ -0,0 +1,3575 @@ +/* paq8l file compressor/archiver. Release by Matt Mahoney, Mar. 8, 2007. + Updated Apr. 15, 2007 (no change to paq8l.exe). + + Copyright (C) 2006 Matt Mahoney, Serge Osnach, Alexander Ratushnyak, + Bill Pettis, Przemyslaw Skibinski, Matthew Fite, wowtiger, Andrew Paterson, + + + LICENSE + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details at + Visit . + +To install and use in Windows: + +- To install, put paq8l.exe or a shortcut to it on your desktop. +- To compress a file or folder, drop it on the paq8l icon. +- To decompress, drop a .paq8l file on the icon. + +A .paq8l extension is added for compression, removed for decompression. +The output will go in the same folder as the input. + +While paq8l is working, a command window will appear and report +progress. When it is done you can close the window by pressing +ENTER or clicking [X]. + + +COMMAND LINE INTERFACE + +- To install, put paq8l.exe somewhere in your PATH. +- To compress: paq8l [-N] file1 [file2...] +- To decompress: paq8l [-d] file1.paq8l [dir2] +- To view contents: more < file1.paq8l + +The compressed output file is named by adding ".paq8l" extension to +the first named file (file1.paq8l). Each file that exists will be +added to the archive and its name will be stored without a path. +The option -N specifies a compression level ranging from -0 +(fastest) to -9 (smallest). The default is -5. If there is +no option and only one file, then the program will pause when +finished until you press the ENTER key (to support drag and drop). +If file1.paq8l exists then it is overwritten. + +If the first named file ends in ".paq8l" then it is assumed to be +an archive and the files within are extracted to the same directory +as the archive unless a different directory (dir2) is specified. +The -d option forces extraction even if there is not a ".paq8l" +extension. If any output file already exists, then it is compared +with the archive content and the first byte that differs is reported. +No files are overwritten or deleted. If there is only one argument +(no -d or dir2) then the program will pause when finished until +you press ENTER. + +For compression, if any named file is actually a directory, then all +files and subdirectories are compressed, preserving the directory +structure, except that empty directories are not stored, and file +attributes (timestamps, permissions, etc.) are not preserved. +During extraction, directories are created as needed. For example: + + paq8l -4 c:\tmp\foo bar + +compresses foo and bar (if they exist) to c:\tmp\foo.paq8l at level 4. + + paq8l -d c:\tmp\foo.paq8l . + +extracts foo and compares bar in the current directory. If foo and bar +are directories then their contents are extracted/compared. + +There are no commands to update an existing archive or to extract +part of an archive. Files and archives larger than 2GB are not +supported (but might work on 64-bit machines, not tested). +File names with nonprintable characters are not supported (spaces +are OK). + + +TO COMPILE + +There are 2 files: paq8l.cpp (C++) and paq7asm.asm (NASM/YASM). +paq7asm.asm is the same as in paq7 and paq8x. paq8l.cpp recognizes the +following compiler options: + + -DWINDOWS (to compile in Windows) + -DUNIX (to compile in Unix, Linux, Solairs, MacOS/Darwin, etc) + -DNOASM (to replace paq7asm.asm with equivalent C++) + -DDEFAULT_OPTION=N (to change the default compression level from 5 to N). + +If you compile without -DWINDOWS or -DUNIX, you can still compress files, +but you cannot compress directories or create them during extraction. +You can extract directories if you manually create the empty directories +first. + +Use -DEFAULT_OPTION=N to change the default compression level to support +drag and drop on machines with less than 256 MB of memory. Use +-DDEFAULT_OPTION=4 for 128 MB, 3 for 64 MB, 2 for 32 MB, etc. + +Use -DNOASM for non x86-32 machines, or older than a Pentium-MMX (about +1997), or if you don't have NASM or YASM to assemble paq7asm.asm. The +program will still work but it will be slower. For NASM in Windows, +use the options "--prefix _" and either "-f win32" or "-f obj" depending +on your C++ compiler. In Linux, use "-f elf". + +Recommended compiler commands and optimizations: + + MINGW g++: + nasm paq7asm.asm -f win32 --prefix _ + g++ paq8l.cpp -DWINDOWS -O2 -Os -s -march=pentiumpro -fomit-frame-pointer -o paq8l.exe paq7asm.obj + + Borland: + nasm paq7asm.asm -f obj --prefix _ + bcc32 -DWINDOWS -O -w-8027 paq8l.cpp paq7asm.obj + + Mars: + nasm paq7asm.asm -f obj --prefix _ + dmc -DWINDOWS -Ae -O paq8l.cpp paq7asm.obj + + UNIX/Linux (PC): + nasm -f elf paq7asm.asm + g++ paq8l.cpp -DUNIX -O2 -Os -s -march=pentiumpro -fomit-frame-pointer -o paq8l paq7asm.o + + Non PC (e.g. PowerPC under MacOS X) + g++ paq8l.cpp -O2 -DUNIX -DNOASM -s -o paq8l + +MinGW produces faster executables than Borland or Mars, but Intel 9 +is about 4% faster than MinGW). + + +ARCHIVE FILE FORMAT + +An archive has the following format. It is intended to be both +human and machine readable. The header ends with CTRL-Z (Windows EOF) +so that the binary compressed data is not displayed on the screen. + + paq8l -N CR LF + size TAB filename CR LF + size TAB filename CR LF + ... + CTRL-Z + compressed binary data + +-N is the option (-0 to -9), even if a default was used. +Plain file names are stored without a path. Files in compressed +directories are stored with path relative to the compressed directory +(using UNIX style forward slashes "/"). For example, given these files: + + 123 C:\dir1\file1.txt + 456 C:\dir2\file2.txt + +Then + + paq8l archive \dir1\file1.txt \dir2 + +will create archive.paq8l with the header: + + paq8l -5 + 123 file1.txt + 456 dir2/file2.txt + +The command: + + paq8l archive.paq8l C:\dir3 + +will create the files: + + C:\dir3\file1.txt + C:\dir3\dir2\file2.txt + +Decompression will fail if the first 7 bytes are not "paq8l -". Sizes +are stored as decimal numbers. CR, LF, TAB, CTRL-Z are ASCII codes +13, 10, 9, 26 respectively. + + +ARITHMETIC CODING + +The binary data is arithmetic coded as the shortest base 256 fixed point +number x = SUM_i x_i 256^-1-i such that p(= 16. + + The primaty output is t_i := stretch(sm(n0,n1,h)), where sm(.) is + a stationary map with K = 1/256, initiaized to + sm(n0,n1,h) = (n1+(1/64))/(n+2/64). Four additional inputs are also + be computed to improve compression slightly: + + p1_i = sm(n0,n1,h) + p0_i = 1 - p1_i + t_i := stretch(p_1) + t_i+1 := K1 (p1_i - p0_i) + t_i+2 := K2 stretch(p1) if n0 = 0, -K2 stretch(p1) if n1 = 0, else 0 + t_i+3 := K3 (-p0_i if n1 = 0, p1_i if n0 = 0, else 0) + t_i+4 := K3 (-p0_i if n0 = 0, p1_i if n1 = 0, else 0) + + where K1..K4 are ad-hoc constants. + + h is updated as follows: + If n < 4, append y_j to h. + Else if n <= 16, set h := y_j. + Else h = 0. + + The update rule is biased toward newer data in a way that allows + n0 or n1, but not both, to grow large by discarding counts of the + opposite bit. Large counts are incremented probabilistically. + Specifically, when y_j = 0 then the update rule is: + + n0 := n0 + 1, n < 29 + n0 + 1 with probability 2^(27-n0)/2 else n0, 29 <= n0 < 41 + n0, n = 41. + n1 := n1, n1 <= 5 + round(8/3 lg n1), if n1 > 5 + + swapping (n0,n1) when y_j = 1. + + Furthermore, to allow an 8 bit representation for (n0,n1,h), states + exceeding the following values of n0 or n1 are replaced with the + state with the closest ratio n0:n1 obtained by decrementing the + smaller count: (41,0,h), (40,1,h), (12,2,h), (5,3,h), (4,4,h), + (3,5,h), (2,12,h), (1,40,h), (0,41,h). For example: + (12,2,1) 0-> (7,1,0) because there is no state (13,2,0). + +- Match Model. The state is (c,b), initially (0,0), where c is 1 if + the context was previously seen, else 0, and b is the next bit in + this context. The prediction is: + + t_i := (2b - 1)Kc log(m + 1) + + where m is the length of the context. The update rule is c := 1, + b := y_j. A match model can be implemented efficiently by storing + input in a buffer and storing pointers into the buffer into a hash + table indexed by context. Then c is indicated by a hash table entry + and b can be retrieved from the buffer. + + +CONTEXTS + +High compression is achieved by combining a large number of contexts. +Most (not all) contexts start on a byte boundary and end on the bit +immediately preceding the predicted bit. The contexts below are +modeled with both a run map and a nonstationary map unless indicated. + +- Order n. The last n bytes, up to about 16. For general purpose data. + Most of the compression occurs here for orders up to about 6. + An order 0 context includes only the 0-7 bits of the partially coded + byte and the number of these bits (255 possible values). + +- Sparse. Usually 1 or 2 of the last 8 bytes preceding the byte containing + the predicted bit, e.g (2), (3),..., (8), (1,3), (1,4), (1,5), (1,6), + (2,3), (2,4), (3,6), (4,8). The ordinary order 1 and 2 context, (1) + or (1,2) are included above. Useful for binary data. + +- Text. Contexts consists of whole words (a-z, converted to lower case + and skipping other values). Contexts may be sparse, e.g (0,2) meaning + the current (partially coded) word and the second word preceding the + current one. Useful contexts are (0), (0,1), (0,1,2), (0,2), (0,3), + (0,4). The preceding byte may or may not be included as context in the + current word. + +- Formatted text. The column number (determined by the position of + the last linefeed) is combined with other contexts: the charater to + the left and the character above it. + +- Fixed record length. The record length is determined by searching for + byte sequences with a uniform stride length. Once this is found, then + the record length is combined with the context of the bytes immediately + preceding it and the corresponding byte locations in the previous + one or two records (as with formatted text). + +- Context gap. The distance to the previous occurrence of the order 1 + or order 2 context is combined with other low order (1-2) contexts. + +- FAX. For 2-level bitmapped images. Contexts are the surrounding + pixels already seen. Image width is assumed to be 1728 bits (as + in calgary/pic). + +- Image. For uncompressed 24-bit color BMP and TIFF images. Contexts + are the high order bits of the surrounding pixels and linear + combinations of those pixels, including other color planes. The + image width is detected from the file header. When an image is + detected, other models are turned off to improve speed. + +- JPEG. Files are further compressed by partially uncompressing back + to the DCT coefficients to provide context for the next Huffman code. + Only baseline DCT-Huffman coded files are modeled. (This ia about + 90% of images, the others are usually progresssive coded). JPEG images + embedded in other files (quite common) are detected by headers. The + baseline JPEG coding process is: + - Convert to grayscale and 2 chroma colorspace. + - Sometimes downsample the chroma images 2:1 or 4:1 in X and/or Y. + - Divide each of the 3 images into 8x8 blocks. + - Convert using 2-D discrete cosine transform (DCT) to 64 12-bit signed + coefficients. + - Quantize the coefficients by integer division (lossy). + - Split the image into horizontal slices coded independently, separated + by restart codes. + - Scan each block starting with the DC (0,0) coefficient in zigzag order + to the (7,7) coefficient, interleaving the 3 color components in + order to scan the whole image left to right starting at the top. + - Subtract the previous DC component from the current in each color. + - Code the coefficients using RS codes, where R is a run of R zeros (0-15) + and S indicates 0-11 bits of a signed value to follow. (There is a + special RS code (EOB) to indicate the rest of the 64 coefficients are 0). + - Huffman code the RS symbol, followed by S literal bits. + The most useful contexts are the current partially coded Huffman code + (including S following bits) combined with the coefficient position + (0-63), color (0-2), and last few RS codes. + +- Match. When a context match of 400 bytes or longer is detected, + the next bit of the match is predicted and other models are turned + off to improve speed. + +- Exe. When a x86 file (.exe, .obj, .dll) is detected, sparse contexts + with gaps of 1-12 selecting only the prefix, opcode, and the bits + of the modR/M byte that are relevant to parsing are selected. + This model is turned off otherwise. + +- Indirect. The history of the last 1-3 bytes in the context of the + last 1-2 bytes is combined with this 1-2 byte context. + +- DMC. A bitwise n-th order context is built from a state machine using + DMC, described in http://plg.uwaterloo.ca/~ftp/dmc/dmc.c + The effect is to extend a single context, one bit at a time and predict + the next bit based on the history in this context. The model here differs + in that two predictors are used. One is a pair of counts as in the original + DMC. The second predictor is a bit history state mapped adaptively to + a probability as as in a Nonstationary Map. + +ARCHITECTURE + +The context models are mixed by several of several hundred neural networks +selected by a low-order context. The outputs of these networks are +combined using a second neural network, then fed through several stages of +adaptive probability maps (APM) before arithmetic coding. + +For images, only one neural network is used and its context is fixed. + +An APM is a stationary map combining a context and an input probability. +The input probability is stretched and divided into 32 segments to +combine with other contexts. The output is interpolated between two +adjacent quantized values of stretch(p1). There are 2 APM stages in series: + + p1 := (p1 + 3 APM(order 0, p1)) / 4. + p1 := (APM(order 1, p1) + 2 APM(order 2, p1) + APM(order 3, p1)) / 4. + +PREPROCESSING + +paq8l uses preprocessing transforms on certain data types to improve +compression. To improve reliability, the decoding transform is +tested during compression to ensure that the input file can be +restored. If the decoder output is not identical to the input file +due to a bug, then the transform is abandoned and the data is compressed +without a transform so that it will still decompress correctly. + +The input is split into blocks with the format +where is 1 byte (0 = no transform), is the size +of the data after decoding, which may be different than the size of . +Blocks do not span file boundaries, and have a maximum size of 4MB to +2GB depending on compression level. Large files are split into blocks +of this size. The preprocessor has 3 parts: + +- Detector. Splits the input into smaller blocks depending on data type. + +- Coder. Input is a block to be compressed. Output is a temporary + file. The coder determines whether a transform is to be applied + based on file type, and if so, which one. A coder may use lots + of resources (memory, time) and make multiple passes through the + input file. The file type is stored (as one byte) during compression. + +- Decoder. Performs the inverse transform of the coder. It uses few + resorces (fast, low memory) and runs in a single pass (stream oriented). + It takes input either from a file or the arithmetic decoder. Each call + to the decoder returns a single decoded byte. + +The following transforms are used: + +- EXE: CALL (0xE8) and JMP (0xE9) address operands are converted from + relative to absolute address. The transform is to replace the sequence + E8/E9 xx xx xx 00/FF by adding file offset modulo 2^25 (signed range, + little-endian format). Data to transform is identified by trying the + transform and applying a crude compression test: testing whether the + byte following the E8/E8 (LSB of the address) occurred more recently + in the transformed data than the original and within 4KB 4 times in + a row. The block ends when this does not happen for 4KB. + +- JPEG: detected by SOI and SOF and ending with EOI or any nondecodable + data. No transform is applied. The purpose is to separate images + embedded in execuables to block the EXE transform, and for a future + place to insert a transform. + + +IMPLEMENTATION + +Hash tables are designed to minimize cache misses, which consume most +of the CPU time. + +Most of the memory is used by the nonstationary context models. +Contexts are represented by 32 bits, possibly a hash. These are +mapped to a bit history, represented by 1 byte. The hash table is +organized into 64-byte buckets on cache line boundaries. Each bucket +contains 7 x 7 bit histories, 7 16-bit checksums, and a 2 element LRU +queue packed into one byte. Each 7 byte element represents 7 histories +for a context ending on a 3-bit boundary plus 0-2 more bits. One +element (for bits 0-1, which have 4 unused bytes) also contains a run model +consisting of the last byte seen and a count (as 1 byte each). + +Run models use 4 byte hash elements consisting of a 2 byte checksum, a +repeat count (0-255) and the byte value. The count also serves as +a priority. + +Stationary models are most appropriate for small contexts, so the +context is used as a direct table lookup without hashing. + +The match model maintains a pointer to the last match until a mismatching +bit is found. At the start of the next byte, the hash table is referenced +to find another match. The hash table of pointers is updated after each +whole byte. There is no checksum. Collisions are detected by comparing +the current and matched context in a rotating buffer. + +The inner loops of the neural network prediction (1) and training (2) +algorithms are implemented in MMX assembler, which computes 4 elements +at a time. Using assembler is 8 times faster than C++ for this code +and 1/3 faster overall. (However I found that SSE2 code on an AMD-64, +which computes 8 elements at a time, is not any faster). + + +DIFFERENCES FROM PAQ7 + +An .exe model and filter are added. Context maps are improved using 16-bit +checksums to reduce collisions. The state table uses probabilistic updates +for large counts, more states that remember the last bit, and decreased +discounting of the opposite count. It is implemented as a fixed table. +There are also many minor changes. + +DIFFERENCES FROM PAQ8A + +The user interface supports directory compression and drag and drop. +The preprocessor segments the input into blocks and uses more robust +EXE detection. An indirect context model was added. There is no +dictionary preprocesor like PAQ8B/C/D/E. + +DIFFERENCES FROM PAQ8F + +Different models, usually from paq8hp*. Also changed rate from 8 to 7. A bug +in Array was fixed that caused the program to silently crash upon exit. + +DIFFERENCES FROM PAQ8J + +1) Slightly improved sparse model. +2) Added new family of sparse contexts. Each byte mapped to 3-bit value, where +different values corresponds to different byte classes. For example, input +byte 0x00 transformed into 0, all bytes that less then 16 -- into 5, all +punctuation marks (ispunct(c)!=0) -- into 2 etc. Then this flags from 11 +previous bytes combined into 32-bit pseudo-context. + +All this improvements gives only 62 byte on BOOK1, but on binaries archive size +reduced on 1-2%. + +DIFFERENCES FROM PAQ8JA + +Introduced distance model. Distance model uses distance to last occurence +of some anchor char ( 0x00, space, newline, 0xff ), combined with previous +charactes as context. This slightly improves compression of files with +variable-width record data. + +DIFFERENCES FROM PAQ8JB + +Restored recordModel(), broken in paq8hp*. Slightly tuned indirectModel(). + +DIFFERENCES FROM PAQ8JC + +Changed the APMs in the Predictor. Up to a 0.2% improvement for some files. + +DIFFERENCES FROM PAQ8JD + +Added DMCModel. Removed some redundant models from SparseModel and other +minor tuneups. Changes introduced in PAQ8K were not carried over. + +PAQ8L v.2 + +Changed Mixer::p() to p() to fix a compiler error in Linux +(patched by Indrek Kruusa, Apr. 15, 2007). + +*/ + +#define PROGNAME "paq8l" // Please change this if you change the program. + +#include +#include +#include +#include +#include +#include +#define NDEBUG // remove for debugging (turns on Array bound checks) +#include + +#ifdef UNIX +#include +#include +#include +#include +#endif + +#ifdef WINDOWS +#include +#endif + +#ifndef DEFAULT_OPTION +#define DEFAULT_OPTION 5 +#endif + +// 8, 16, 32 bit unsigned types (adjust as appropriate) +typedef unsigned char U8; +typedef unsigned short U16; +typedef unsigned int U32; + +// min, max functions +#ifndef WINDOWS +inline int min(int a, int b) {return a='A'&&c1<='Z') c1+='a'-'A'; + int c2=*b; + if (c2>='A'&&c2<='Z') c2+='a'-'A'; + if (c1!=c2) return 0; + ++a; + ++b; + } + return *a==*b; +} + +//////////////////////// Program Checker ///////////////////// + +// Track time and memory used +class ProgramChecker { + int memused; // bytes allocated by Array now + int maxmem; // most bytes allocated ever + clock_t start_time; // in ticks +public: + void alloc(int n) { // report memory allocated, may be negative + memused+=n; + if (memused>maxmem) maxmem=memused; + } + ProgramChecker(): memused(0), maxmem(0) { + start_time=clock(); + assert(sizeof(U8)==1); + assert(sizeof(U16)==2); + assert(sizeof(U32)==4); + assert(sizeof(short)==2); + assert(sizeof(int)==4); + } + void print() const { // print time and memory used + printf("Time %1.2f sec, used %d bytes of memory\n", + double(clock()-start_time)/CLOCKS_PER_SEC, maxmem); + } +} programChecker; + +//////////////////////////// Array //////////////////////////// + +// Array a(n); creates n elements of T initialized to 0 bits. +// Constructors for T are not called. +// Indexing is bounds checked if assertions are on. +// a.size() returns n. +// a.resize(n) changes size to n, padding with 0 bits or truncating. +// a.push_back(x) appends x and increases size by 1, reserving up to size*2. +// a.pop_back() decreases size by 1, does not free memory. +// Copy and assignment are not supported. +// Memory is aligned on a ALIGN byte boundary (power of 2), default is none. + +template class Array { +private: + int n; // user size + int reserved; // actual size + char *ptr; // allocated memory, zeroed + T* data; // start of n elements of aligned data + void create(int i); // create with size i +public: + explicit Array(int i=0) {create(i);} + ~Array(); + T& operator[](int i) { +#ifndef NDEBUG + if (i<0 || i>=n) fprintf(stderr, "%d out of bounds %d\n", i, n), quit(); +#endif + return data[i]; + } + const T& operator[](int i) const { +#ifndef NDEBUG + if (i<0 || i>=n) fprintf(stderr, "%d out of bounds %d\n", i, n), quit(); +#endif + return data[i]; + } + int size() const {return n;} + void resize(int i); // change size to i + void pop_back() {if (n>0) --n;} // decrement size + void push_back(const T& x); // increment size, append x +private: + Array(const Array&); // no copy or assignment + Array& operator=(const Array&); +}; + +template void Array::resize(int i) { + if (i<=reserved) { + n=i; + return; + } + char *saveptr=ptr; + T *savedata=data; + int saven=n; + create(i); + if (saveptr) { + if (savedata) { + memcpy(data, savedata, sizeof(T)*min(i, saven)); + programChecker.alloc(-ALIGN-n*sizeof(T)); + } + free(saveptr); + } +} + +template void Array::create(int i) { + n=reserved=i; + if (i<=0) { + data=0; + ptr=0; + return; + } + const int sz=ALIGN+n*sizeof(T); + programChecker.alloc(sz); + ptr = (char*)calloc(sz, 1); + if (!ptr) quit("Out of memory"); + data = (ALIGN ? (T*)(ptr+ALIGN-(((long)ptr)&(ALIGN-1))) : (T*)ptr); + assert((char*)data>=ptr && (char*)data<=ptr+ALIGN); +} + +template Array::~Array() { + programChecker.alloc(-ALIGN-n*sizeof(T)); + free(ptr); +} + +template void Array::push_back(const T& x) { + if (n==reserved) { + int saven=n; + resize(max(1, n*2)); + n=saven; + } + data[n++]=x; +} + +/////////////////////////// String ///////////////////////////// + +// A tiny subset of std::string +// size() includes NUL terminator. + +class String: public Array { +public: + const char* c_str() const {return &(*this)[0];} + void operator=(const char* s) { + resize(strlen(s)+1); + strcpy(&(*this)[0], s); + } + void operator+=(const char* s) { + assert(s); + pop_back(); + while (*s) push_back(*s++); + push_back(0); + } + String(const char* s=""): Array(1) { + (*this)+=s; + } +}; + + +//////////////////////////// rnd /////////////////////////////// + +// 32-bit pseudo random number generator +class Random{ + Array table; + int i; +public: + Random(): table(64) { + table[0]=123456789; + table[1]=987654321; + for(int j=0; j<62; j++) table[j+2]=table[j+1]*11+table[j]*23/16; + i=0; + } + U32 operator()() { + return ++i, table[i&63]=table[i-24&63]^table[i-55&63]; + } +} rnd; + +////////////////////////////// Buf ///////////////////////////// + +// Buf(n) buf; creates an array of n bytes (must be a power of 2). +// buf[i] returns a reference to the i'th byte with wrap (no out of bounds). +// buf(i) returns i'th byte back from pos (i > 0) +// buf.size() returns n. + +int pos; // Number of input bytes in buf (not wrapped) + +class Buf { + Array b; +public: + Buf(int i=0): b(i) {} + void setsize(int i) { + if (!i) return; + assert(i>0 && (i&(i-1))==0); + b.resize(i); + } + U8& operator[](int i) { + return b[i&b.size()-1]; + } + int operator()(int i) const { + assert(i>0); + return b[pos-i&b.size()-1]; + } + int size() const { + return b.size(); + } +}; + +/////////////////////// Global context ///////////////////////// + +int level=DEFAULT_OPTION; // Compression level 0 to 9 +#define MEM (0x10000< t; +public: + int operator()(U16 x) const {return t[x];} + Ilog(); +} ilog; + +// Compute lookup table by numerical integration of 1/x +Ilog::Ilog(): t(65536) { + U32 x=14155776; + for (int i=2; i<65536; ++i) { + x+=774541002/(i*2-1); // numerator is 2^29/ln 2 + t[i]=x>>24; + } +} + +// llog(x) accepts 32 bits +inline int llog(U32 x) { + if (x>=0x1000000) + return 256+ilog(x>>16); + else if (x>=0x10000) + return 128+ilog(x>>8); + else + return ilog(x); +} + +///////////////////////// state table //////////////////////// + +// State table: +// nex(state, 0) = next state if bit y is 0, 0 <= state < 256 +// nex(state, 1) = next state if bit y is 1 +// nex(state, 2) = number of zeros in bit history represented by state +// nex(state, 3) = number of ones represented +// +// States represent a bit history within some context. +// State 0 is the starting state (no bits seen). +// States 1-30 represent all possible sequences of 1-4 bits. +// States 31-252 represent a pair of counts, (n0,n1), the number +// of 0 and 1 bits respectively. If n0+n1 < 16 then there are +// two states for each pair, depending on if a 0 or 1 was the last +// bit seen. +// If n0 and n1 are too large, then there is no state to represent this +// pair, so another state with about the same ratio of n0/n1 is substituted. +// Also, when a bit is observed and the count of the opposite bit is large, +// then part of this count is discarded to favor newer data over old. + +#if 1 // change to #if 0 to generate this table at run time (4% slower) +static const U8 State_table[256][4]={ + { 1, 2, 0, 0},{ 3, 5, 1, 0},{ 4, 6, 0, 1},{ 7, 10, 2, 0}, // 0-3 + { 8, 12, 1, 1},{ 9, 13, 1, 1},{ 11, 14, 0, 2},{ 15, 19, 3, 0}, // 4-7 + { 16, 23, 2, 1},{ 17, 24, 2, 1},{ 18, 25, 2, 1},{ 20, 27, 1, 2}, // 8-11 + { 21, 28, 1, 2},{ 22, 29, 1, 2},{ 26, 30, 0, 3},{ 31, 33, 4, 0}, // 12-15 + { 32, 35, 3, 1},{ 32, 35, 3, 1},{ 32, 35, 3, 1},{ 32, 35, 3, 1}, // 16-19 + { 34, 37, 2, 2},{ 34, 37, 2, 2},{ 34, 37, 2, 2},{ 34, 37, 2, 2}, // 20-23 + { 34, 37, 2, 2},{ 34, 37, 2, 2},{ 36, 39, 1, 3},{ 36, 39, 1, 3}, // 24-27 + { 36, 39, 1, 3},{ 36, 39, 1, 3},{ 38, 40, 0, 4},{ 41, 43, 5, 0}, // 28-31 + { 42, 45, 4, 1},{ 42, 45, 4, 1},{ 44, 47, 3, 2},{ 44, 47, 3, 2}, // 32-35 + { 46, 49, 2, 3},{ 46, 49, 2, 3},{ 48, 51, 1, 4},{ 48, 51, 1, 4}, // 36-39 + { 50, 52, 0, 5},{ 53, 43, 6, 0},{ 54, 57, 5, 1},{ 54, 57, 5, 1}, // 40-43 + { 56, 59, 4, 2},{ 56, 59, 4, 2},{ 58, 61, 3, 3},{ 58, 61, 3, 3}, // 44-47 + { 60, 63, 2, 4},{ 60, 63, 2, 4},{ 62, 65, 1, 5},{ 62, 65, 1, 5}, // 48-51 + { 50, 66, 0, 6},{ 67, 55, 7, 0},{ 68, 57, 6, 1},{ 68, 57, 6, 1}, // 52-55 + { 70, 73, 5, 2},{ 70, 73, 5, 2},{ 72, 75, 4, 3},{ 72, 75, 4, 3}, // 56-59 + { 74, 77, 3, 4},{ 74, 77, 3, 4},{ 76, 79, 2, 5},{ 76, 79, 2, 5}, // 60-63 + { 62, 81, 1, 6},{ 62, 81, 1, 6},{ 64, 82, 0, 7},{ 83, 69, 8, 0}, // 64-67 + { 84, 71, 7, 1},{ 84, 71, 7, 1},{ 86, 73, 6, 2},{ 86, 73, 6, 2}, // 68-71 + { 44, 59, 5, 3},{ 44, 59, 5, 3},{ 58, 61, 4, 4},{ 58, 61, 4, 4}, // 72-75 + { 60, 49, 3, 5},{ 60, 49, 3, 5},{ 76, 89, 2, 6},{ 76, 89, 2, 6}, // 76-79 + { 78, 91, 1, 7},{ 78, 91, 1, 7},{ 80, 92, 0, 8},{ 93, 69, 9, 0}, // 80-83 + { 94, 87, 8, 1},{ 94, 87, 8, 1},{ 96, 45, 7, 2},{ 96, 45, 7, 2}, // 84-87 + { 48, 99, 2, 7},{ 48, 99, 2, 7},{ 88,101, 1, 8},{ 88,101, 1, 8}, // 88-91 + { 80,102, 0, 9},{103, 69,10, 0},{104, 87, 9, 1},{104, 87, 9, 1}, // 92-95 + {106, 57, 8, 2},{106, 57, 8, 2},{ 62,109, 2, 8},{ 62,109, 2, 8}, // 96-99 + { 88,111, 1, 9},{ 88,111, 1, 9},{ 80,112, 0,10},{113, 85,11, 0}, // 100-103 + {114, 87,10, 1},{114, 87,10, 1},{116, 57, 9, 2},{116, 57, 9, 2}, // 104-107 + { 62,119, 2, 9},{ 62,119, 2, 9},{ 88,121, 1,10},{ 88,121, 1,10}, // 108-111 + { 90,122, 0,11},{123, 85,12, 0},{124, 97,11, 1},{124, 97,11, 1}, // 112-115 + {126, 57,10, 2},{126, 57,10, 2},{ 62,129, 2,10},{ 62,129, 2,10}, // 116-119 + { 98,131, 1,11},{ 98,131, 1,11},{ 90,132, 0,12},{133, 85,13, 0}, // 120-123 + {134, 97,12, 1},{134, 97,12, 1},{136, 57,11, 2},{136, 57,11, 2}, // 124-127 + { 62,139, 2,11},{ 62,139, 2,11},{ 98,141, 1,12},{ 98,141, 1,12}, // 128-131 + { 90,142, 0,13},{143, 95,14, 0},{144, 97,13, 1},{144, 97,13, 1}, // 132-135 + { 68, 57,12, 2},{ 68, 57,12, 2},{ 62, 81, 2,12},{ 62, 81, 2,12}, // 136-139 + { 98,147, 1,13},{ 98,147, 1,13},{100,148, 0,14},{149, 95,15, 0}, // 140-143 + {150,107,14, 1},{150,107,14, 1},{108,151, 1,14},{108,151, 1,14}, // 144-147 + {100,152, 0,15},{153, 95,16, 0},{154,107,15, 1},{108,155, 1,15}, // 148-151 + {100,156, 0,16},{157, 95,17, 0},{158,107,16, 1},{108,159, 1,16}, // 152-155 + {100,160, 0,17},{161,105,18, 0},{162,107,17, 1},{108,163, 1,17}, // 156-159 + {110,164, 0,18},{165,105,19, 0},{166,117,18, 1},{118,167, 1,18}, // 160-163 + {110,168, 0,19},{169,105,20, 0},{170,117,19, 1},{118,171, 1,19}, // 164-167 + {110,172, 0,20},{173,105,21, 0},{174,117,20, 1},{118,175, 1,20}, // 168-171 + {110,176, 0,21},{177,105,22, 0},{178,117,21, 1},{118,179, 1,21}, // 172-175 + {110,180, 0,22},{181,115,23, 0},{182,117,22, 1},{118,183, 1,22}, // 176-179 + {120,184, 0,23},{185,115,24, 0},{186,127,23, 1},{128,187, 1,23}, // 180-183 + {120,188, 0,24},{189,115,25, 0},{190,127,24, 1},{128,191, 1,24}, // 184-187 + {120,192, 0,25},{193,115,26, 0},{194,127,25, 1},{128,195, 1,25}, // 188-191 + {120,196, 0,26},{197,115,27, 0},{198,127,26, 1},{128,199, 1,26}, // 192-195 + {120,200, 0,27},{201,115,28, 0},{202,127,27, 1},{128,203, 1,27}, // 196-199 + {120,204, 0,28},{205,115,29, 0},{206,127,28, 1},{128,207, 1,28}, // 200-203 + {120,208, 0,29},{209,125,30, 0},{210,127,29, 1},{128,211, 1,29}, // 204-207 + {130,212, 0,30},{213,125,31, 0},{214,137,30, 1},{138,215, 1,30}, // 208-211 + {130,216, 0,31},{217,125,32, 0},{218,137,31, 1},{138,219, 1,31}, // 212-215 + {130,220, 0,32},{221,125,33, 0},{222,137,32, 1},{138,223, 1,32}, // 216-219 + {130,224, 0,33},{225,125,34, 0},{226,137,33, 1},{138,227, 1,33}, // 220-223 + {130,228, 0,34},{229,125,35, 0},{230,137,34, 1},{138,231, 1,34}, // 224-227 + {130,232, 0,35},{233,125,36, 0},{234,137,35, 1},{138,235, 1,35}, // 228-231 + {130,236, 0,36},{237,125,37, 0},{238,137,36, 1},{138,239, 1,36}, // 232-235 + {130,240, 0,37},{241,125,38, 0},{242,137,37, 1},{138,243, 1,37}, // 236-239 + {130,244, 0,38},{245,135,39, 0},{246,137,38, 1},{138,247, 1,38}, // 240-243 + {140,248, 0,39},{249,135,40, 0},{250, 69,39, 1},{ 80,251, 1,39}, // 244-247 + {140,252, 0,40},{249,135,41, 0},{250, 69,40, 1},{ 80,251, 1,40}, // 248-251 + {140,252, 0,41}}; // 252, 253-255 are reserved + +#define nex(state,sel) State_table[state][sel] + +// The code used to generate the above table at run time (4% slower). +// To print the table, uncomment the 4 lines of print statements below. +// In this code x,y = n0,n1 is the number of 0,1 bits represented by a state. +#else + +class StateTable { + Array ns; // state*4 -> next state if 0, if 1, n0, n1 + enum {B=5, N=64}; // sizes of b, t + static const int b[B]; // x -> max y, y -> max x + static U8 t[N][N][2]; // x,y -> state number, number of states + int num_states(int x, int y); // compute t[x][y][1] + void discount(int& x); // set new value of x after 1 or y after 0 + void next_state(int& x, int& y, int b); // new (x,y) after bit b +public: + int operator()(int state, int sel) {return ns[state*4+sel];} + StateTable(); +} nex; + +const int StateTable::b[B]={42,41,13,6,5}; // x -> max y, y -> max x +U8 StateTable::t[N][N][2]; + +int StateTable::num_states(int x, int y) { + if (x=N || y>=N || y>=B || x>=b[y]) return 0; + + // States 0-30 are a history of the last 0-4 bits + if (x+y<=4) { // x+y choose x = (x+y)!/x!y! + int r=1; + for (int i=x+1; i<=x+y; ++i) r*=i; + for (int i=2; i<=y; ++i) r/=i; + return r; + } + + // States 31-255 represent a 0,1 count and possibly the last bit + // if the state is reachable by either a 0 or 1. + else + return 1+(y>0 && x+y<16); +} + +// New value of count x if the opposite bit is observed +void StateTable::discount(int& x) { + if (x>2) x=ilog(x)/6-1; +} + +// compute next x,y (0 to N) given input b (0 or 1) +void StateTable::next_state(int& x, int& y, int b) { + if (x next if 0, next if 1, x, y +StateTable::StateTable(): ns(1024) { + + // Assign states + int state=0; + for (int i=0; i<256; ++i) { + for (int y=0; y<=i; ++y) { + int x=i-y; + int n=num_states(x, y); + if (n) { + t[x][y][0]=state; + t[x][y][1]=n; + state+=n; + } + } + } + + // Print/generate next state table + state=0; + for (int i=0; i0) ns1+=t[x-1][y+1][1]; + ns[state*4]=ns0; + ns[state*4+1]=ns1; + ns[state*4+2]=x; + ns[state*4+3]=y; + } + else if (t[x][y][1]) { + next_state(x0, y0, 0); + next_state(x1, y1, 1); + ns[state*4]=ns0=t[x0][y0][0]; + ns[state*4+1]=ns1=t[x1][y1][0]+(t[x1][y1][1]>1); + ns[state*4+2]=x; + ns[state*4+3]=y; + } + // uncomment to print table above +// printf("{%3d,%3d,%2d,%2d},", ns[state*4], ns[state*4+1], +// ns[state*4+2], ns[state*4+3]); +// if (state%4==3) printf(" // %d-%d\n ", state-3, state); + assert(state>=0 && state<256); + assert(t[x][y][1]>0); + assert(t[x][y][0]<=state); + assert(t[x][y][0]+t[x][y][1]>state); + assert(t[x][y][1]<=6); + assert(t[x0][y0][1]>0); + assert(t[x1][y1][1]>0); + assert(ns0-t[x0][y0][0]=0); + assert(ns1-t[x1][y1][0]=0); + ++state; + } + } + } +// printf("%d states\n", state); exit(0); // uncomment to print table above +} + +#endif + +///////////////////////////// Squash ////////////////////////////// + +// return p = 1/(1 + exp(-d)), d scaled by 8 bits, p scaled by 12 bits +int squash(int d) { + static const int t[33]={ + 1,2,3,6,10,16,27,45,73,120,194,310,488,747,1101, + 1546,2047,2549,2994,3348,3607,3785,3901,3975,4022, + 4050,4068,4079,4085,4089,4092,4093,4094}; + if (d>2047) return 4095; + if (d<-2047) return 0; + int w=d&127; + d=(d>>7)+16; + return (t[d]*(128-w)+t[(d+1)]*w+64) >> 7; +} + +//////////////////////////// Stretch /////////////////////////////// + +// Inverse of squash. d = ln(p/(1-p)), d scaled by 8 bits, p by 12 bits. +// d has range -2047 to 2047 representing -8 to 8. p has range 0 to 4095. + +class Stretch { + Array t; +public: + Stretch(); + int operator()(int p) const { + assert(p>=0 && p<4096); + return t[p]; + } +} stretch; + +Stretch::Stretch(): t(4096) { + int pi=0; + for (int x=-2047; x<=2047; ++x) { // invert squash() + int i=squash(x); + for (int j=pi; j<=i; ++j) + t[j]=x; + pi=i+1; + } + t[4095]=2047; +} + +//////////////////////////// Mixer ///////////////////////////// + +// Mixer m(N, M, S=1, w=0) combines models using M neural networks with +// N inputs each, of which up to S may be selected. If S > 1 then +// the outputs of these neural networks are combined using another +// neural network (with parameters S, 1, 1). If S = 1 then the +// output is direct. The weights are initially w (+-32K). +// It is used as follows: +// m.update() trains the network where the expected output is the +// last bit (in the global variable y). +// m.add(stretch(p)) inputs prediction from one of N models. The +// prediction should be positive to predict a 1 bit, negative for 0, +// nominally +-256 to +-2K. The maximum allowed value is +-32K but +// using such large values may cause overflow if N is large. +// m.set(cxt, range) selects cxt as one of 'range' neural networks to +// use. 0 <= cxt < range. Should be called up to S times such +// that the total of the ranges is <= M. +// m.p() returns the output prediction that the next bit is 1 as a +// 12 bit number (0 to 4095). + +// dot_product returns dot product t*w of n elements. n is rounded +// up to a multiple of 8. Result is scaled down by 8 bits. +#ifdef NOASM // no assembly language +int dot_product(short *t, short *w, int n) { + int sum=0; + n=(n+7)&-8; + for (int i=0; i> 8; + return sum; +} +#else // The NASM version uses MMX and is about 8 times faster. +extern "C" int dot_product(short *t, short *w, int n); // in NASM +#endif + +// Train neural network weights w[n] given inputs t[n] and err. +// w[i] += t[i]*err, i=0..n-1. t, w, err are signed 16 bits (+- 32K). +// err is scaled 16 bits (representing +- 1/2). w[i] is clamped to +- 32K +// and rounded. n is rounded up to a multiple of 8. +#ifdef NOASM +void train(short *t, short *w, int n, int err) { + n=(n+7)&-8; + for (int i=0; i>16)+1>>1); + if (wt<-32768) wt=-32768; + if (wt>32767) wt=32767; + w[i]=wt; + } +} +#else +extern "C" void train(short *t, short *w, int n, int err); // in NASM +#endif + +class Mixer { + const int N, M, S; // max inputs, max contexts, max context sets + Array tx; // N inputs from add() + Array wx; // N*M weights + Array cxt; // S contexts + int ncxt; // number of contexts (0 to S) + int base; // offset of next context + int nx; // Number of inputs in tx, 0 to N + Array pr; // last result (scaled 12 bits) + Mixer* mp; // points to a Mixer to combine results +public: + Mixer(int n, int m, int s=1, int w=0); + + // Adjust weights to minimize coding cost of last prediction + void update() { + for (int i=0; i=-32768 && err<32768); + train(&tx[0], &wx[cxt[i]*N], nx, err); + } + nx=base=ncxt=0; + } + + // Input x (call up to N times) + void add(int x) { + assert(nx=0); + assert(ncxt=0); + assert(base+cxupdate(); + for (int i=0; i>5); + mp->add(stretch(pr[i])); + } + mp->set(0, 1); + return mp->p(); + } + else { // S=1 context + return pr[0]=squash(dot_product(&tx[0], &wx[0], nx)>>8); + } + } + ~Mixer(); +}; + +Mixer::~Mixer() { + delete mp; +} + + +Mixer::Mixer(int n, int m, int s, int w): + N((n+7)&-8), M(m), S(s), tx(N), wx(N*M), + cxt(S), ncxt(0), base(0), nx(0), pr(S), mp(0) { + assert(n>0 && N>0 && (N&7)==0 && M>0); + for (int i=0; i1) mp=new Mixer(S, 1, 1, 0x7fff); +} + +//////////////////////////// APM ////////////////////////////// + +// APM maps a probability and a context into a new probability +// that bit y will next be 1. After each guess it updates +// its state to improve future guesses. Methods: +// +// APM a(N) creates with N contexts, uses 66*N bytes memory. +// a.p(pr, cx, rate=7) returned adjusted probability in context cx (0 to +// N-1). rate determines the learning rate (smaller = faster, default 7). +// Probabilities are scaled 12 bits (0-4095). + +class APM { + int index; // last p, context + const int N; // number of contexts + Array t; // [N][33]: p, context -> p +public: + APM(int n); + int p(int pr=2048, int cxt=0, int rate=7) { + assert(pr>=0 && pr<4096 && cxt>=0 && cxt0 && rate<32); + pr=stretch(pr); + int g=(y<<16)+(y<> rate; + t[index+1] += g-t[index+1] >> rate; + const int w=pr&127; // interpolation weight (33 points) + index=(pr+2048>>7)+cxt*33; + return t[index]*(128-w)+t[index+1]*w >> 11; + } +}; + +// maps p, cxt -> p initially +APM::APM(int n): index(0), N(n), t(n*33) { + for (int i=0; i probability * 4096 +class StateMap { +protected: + int cxt; // context + Array t; // 256 states -> probability * 64K +public: + StateMap(); + int p(int cx) { + assert(cx>=0 && cx> 8; + return t[cxt=cx] >> 4; + } +}; + +StateMap::StateMap(): cxt(0), t(256) { + for (int i=0; i<256; ++i) { + int n0=nex(i,2); + int n1=nex(i,3); + if (n0==0) n1*=64; + if (n1==0) n0*=64; + t[i] = 65536*(n1+1)/(n0+n1+2); + } +} + +//////////////////////////// hash ////////////////////////////// + +// Hash 2-5 ints. +inline U32 hash(U32 a, U32 b, U32 c=0xffffffff, U32 d=0xffffffff, + U32 e=0xffffffff) { + U32 h=a*200002979u+b*30005491u+c*50004239u+d*70004807u+e*110002499u; + return h^h>>9^a>>2^b>>3^c>>4^d>>5^e>>6; +} + +///////////////////////////// BH //////////////////////////////// + +// A BH maps a 32 bit hash to an array of B bytes (checksum and B-2 values) +// +// BH bh(N); creates N element table with B bytes each. +// N must be a power of 2. The first byte of each element is +// reserved for a checksum to detect collisions. The remaining +// B-1 bytes are values, prioritized by the first value. This +// byte is 0 to mark an unused element. +// +// bh[i] returns a pointer to the i'th element, such that +// bh[i][0] is a checksum of i, bh[i][1] is the priority, and +// bh[i][2..B-1] are other values (0-255). +// The low lg(n) bits as an index into the table. +// If a collision is detected, up to M nearby locations in the same +// cache line are tested and the first matching checksum or +// empty element is returned. +// If no match or empty element is found, then the lowest priority +// element is replaced. + +// 2 byte checksum with LRU replacement (except last 2 by priority) +template class BH { + enum {M=8}; // search limit + Array t; // elements + U32 n; // size-1 +public: + BH(int i): t(i*B), n(i-1) { + assert(B>=2 && i>0 && (i&(i-1))==0); // size a power of 2? + } + U8* operator[](U32 i); +}; + +template +inline U8* BH::operator[](U32 i) { + int chk=(i>>16^i)&0xffff; + i=i*M&n; + U8 *p; + U16 *cp; + int j; + for (j=0; j2 && t[(i+j)*B+2]>t[(i+j-1)*B+2]) --j; + } + else memcpy(tmp, cp, B); + memmove(&t[(i+1)*B], &t[i*B], j*B); + memcpy(&t[i*B], tmp, B); + return &t[i*B+1]; +} + +/////////////////////////// ContextMap ///////////////////////// +// +// A ContextMap maps contexts to a bit histories and makes predictions +// to a Mixer. Methods common to all classes: +// +// ContextMap cm(M, C); creates using about M bytes of memory (a power +// of 2) for C contexts. +// cm.set(cx); sets the next context to cx, called up to C times +// cx is an arbitrary 32 bit value that identifies the context. +// It should be called before predicting the first bit of each byte. +// cm.mix(m) updates Mixer m with the next prediction. Returns 1 +// if context cx is found, else 0. Then it extends all the contexts with +// global bit y. It should be called for every bit: +// +// if (bpos==0) +// for (int i=0; i= 1. Context need not be hashed. + +// Predict to mixer m from bit history state s, using sm to map s to +// a probability. +inline int mix2(Mixer& m, int s, StateMap& sm) { + int p1=sm.p(s); + int n0=-!nex(s,2); + int n1=-!nex(s,3); + int st=stretch(p1)>>2; + m.add(st); + p1>>=4; + int p0=255-p1; + m.add(p1-p0); + m.add(st*(n1-n0)); + m.add((p1&n0)-(p0&n1)); + m.add((p1&n1)-(p0&n0)); + return s>0; +} + +// A RunContextMap maps a context into the next byte and a repeat +// count up to M. Size should be a power of 2. Memory usage is 3M/4. +class RunContextMap { + BH<4> t; + U8* cp; +public: + RunContextMap(int m): t(m/4) {cp=t[0]+1;} + void set(U32 cx) { // update count + if (cp[0]==0 || cp[1]!=buf(1)) cp[0]=1, cp[1]=buf(1); + else if (cp[0]<255) ++cp[0]; + cp=t[cx]+1; + } + int p() { // predict next bit + if (cp[1]+256>>8-bpos==c0) + return ((cp[1]>>7-bpos&1)*2-1)*ilog(cp[0]+1)*8; + else + return 0; + } + int mix(Mixer& m) { // return run length + m.add(p()); + return cp[0]!=0; + } +}; + +// Context is looked up directly. m=size is power of 2 in bytes. +// Context should be < m/512. High bits are discarded. +class SmallStationaryContextMap { + Array t; + int cxt; + U16 *cp; +public: + SmallStationaryContextMap(int m): t(m/2), cxt(0) { + assert((m/2&m/2-1)==0); // power of 2? + for (int i=0; i> rate; + cp=&t[cxt+c0]; + m.add(stretch(*cp>>4)); + } +}; + +// Context map for large contexts. Most modeling uses this type of context +// map. It includes a built in RunContextMap to predict the last byte seen +// in the same context, and also bit-level contexts that map to a bit +// history state. +// +// Bit histories are stored in a hash table. The table is organized into +// 64-byte buckets alinged on cache page boundaries. Each bucket contains +// a hash chain of 7 elements, plus a 2 element queue (packed into 1 byte) +// of the last 2 elements accessed for LRU replacement. Each element has +// a 2 byte checksum for detecting collisions, and an array of 7 bit history +// states indexed by the last 0 to 2 bits of context. The buckets are indexed +// by a context ending after 0, 2, or 5 bits of the current byte. Thus, each +// byte modeled results in 3 main memory accesses per context, with all other +// accesses to cache. +// +// On bits 0, 2 and 5, the context is updated and a new bucket is selected. +// The most recently accessed element is tried first, by comparing the +// 16 bit checksum, then the 7 elements are searched linearly. If no match +// is found, then the element with the lowest priority among the 5 elements +// not in the LRU queue is replaced. After a replacement, the queue is +// emptied (so that consecutive misses favor a LFU replacement policy). +// In all cases, the found/replaced element is put in the front of the queue. +// +// The priority is the state number of the first element (the one with 0 +// additional bits of context). The states are sorted by increasing n0+n1 +// (number of bits seen), implementing a LFU replacement policy. +// +// When the context ends on a byte boundary (bit 0), only 3 of the 7 bit +// history states are used. The remaining 4 bytes implement a run model +// as follows: where is the last byte +// seen, possibly repeated. is a 7 bit count and a 1 bit +// flag (represented by count * 2 + d). If d=0 then = 1..127 is the +// number of repeats of and no other bytes have been seen. If d is 1 then +// other byte values have been seen in this context prior to the last +// copies of . +// +// As an optimization, the last two hash elements of each byte (representing +// contexts with 2-7 bits) are not updated until a context is seen for +// a second time. This is indicated by = <1,0> (2). After update, +// is updated to <2,0> or <1,1> (4 or 3). + +class ContextMap { + const int C; // max number of contexts + class E { // hash element, 64 bytes + U16 chk[7]; // byte context checksums + U8 last; // last 2 accesses (0-6) in low, high nibble + public: + U8 bh[7][7]; // byte context, 3-bit context -> bit history state + // bh[][0] = 1st bit, bh[][1,2] = 2nd bit, bh[][3..6] = 3rd bit + // bh[][0] is also a replacement priority, 0 = empty + U8* get(U16 chk); // Find element (0-6) matching checksum. + // If not found, insert or replace lowest priority (not last). + }; + Array t; // bit histories for bits 0-1, 2-4, 5-7 + // For 0-1, also contains a run count in bh[][4] and value in bh[][5] + // and pending update count in bh[7] + Array cp; // C pointers to current bit history + Array cp0; // First element of 7 element array containing cp[i] + Array cxt; // C whole byte contexts (hashes) + Array runp; // C [0..3] = count, value, unused, unused + StateMap *sm; // C maps of state -> p + int cn; // Next context to set by set() + void update(U32 cx, int c); // train model that context cx predicts c + int mix1(Mixer& m, int cc, int bp, int c1, int y1); + // mix() with global context passed as arguments to improve speed. +public: + ContextMap(int m, int c=1); // m = memory in bytes, a power of 2, C = c + ~ContextMap(); + void set(U32 cx, int next=-1); // set next whole byte context to cx + // if next is 0 then set order does not matter + int mix(Mixer& m) {return mix1(m, c0, bpos, buf(1), y);} +}; + +// Find or create hash element matching checksum ch +inline U8* ContextMap::E::get(U16 ch) { + if (chk[last&15]==ch) return &bh[last&15][0]; + int b=0xffff, bi=0; + for (int i=0; i<7; ++i) { + if (chk[i]==ch) return last=last<<4|i, &bh[i][0]; + int pri=bh[i][0]; + if ((last&15)!=i && last>>4!=i && pri>6), cp(c), cp0(c), + cxt(c), runp(c), cn(0) { + assert(m>=64 && (m&m-1)==0); // power of 2? + assert(sizeof(E)==64); + sm=new StateMap[C]; + for (int i=0; i=0 && i>16; + cxt[i]=cx*123456791+i; +} + +// Update the model with bit y1, and predict next bit to mixer m. +// Context: cc=c0, bp=bpos, c1=buf(1), y1=y. +int ContextMap::mix1(Mixer& m, int cc, int bp, int c1, int y1) { + + // Update model with y + int result=0; + for (int i=0; i=&t[0].bh[0][0] && cp[i]<=&t[t.size()-1].bh[6][6]); + assert((long(cp[i])&63)>=15); + int ns=nex(*cp[i], y1); + if (ns>=204 && rnd() << (452-ns>>3)) ns-=4; // probabilistic increment + *cp[i]=ns; + } + + // Update context pointers + if (bpos>1 && runp[i][0]==0) + cp[i]=0; + else if (bpos==1||bpos==3||bpos==6) + cp[i]=cp0[i]+1+(cc&1); + else if (bpos==4||bpos==7) + cp[i]=cp0[i]+3+(cc&3); + else { + cp0[i]=cp[i]=t[cxt[i]+cc&t.size()-1].get(cxt[i]>>16); + + // Update pending bit histories for bits 2-7 + if (bpos==0) { + if (cp0[i][3]==2) { + const int c=cp0[i][4]+256; + U8 *p=t[cxt[i]+(c>>6)&t.size()-1].get(cxt[i]>>16); + p[0]=1+((c>>5)&1); + p[1+((c>>5)&1)]=1+((c>>4)&1); + p[3+((c>>4)&3)]=1+((c>>3)&1); + p=t[cxt[i]+(c>>3)&t.size()-1].get(cxt[i]>>16); + p[0]=1+((c>>2)&1); + p[1+((c>>2)&1)]=1+((c>>1)&1); + p[3+((c>>1)&3)]=1+(c&1); + cp0[i][6]=0; + } + // Update run count of previous context + if (runp[i][0]==0) // new context + runp[i][0]=2, runp[i][1]=c1; + else if (runp[i][1]!=c1) // different byte in context + runp[i][0]=1, runp[i][1]=c1; + else if (runp[i][0]<254) // same byte in context + runp[i][0]+=2; + else if (runp[i][0]==255) + runp[i][0]=128; + runp[i]=cp0[i]+3; + } + } + + // predict from last byte in context + int rc=runp[i][0]; // count*2, +1 if 2 different bytes seen + if (runp[i][1]+256>>8-bp==cc) { + int b=(runp[i][1]>>7-bp&1)*2-1; // predicted bit + for 1, - for 0 + int c=ilog(rc+1)<<2+(~rc&1); + m.add(b*c); + } + else + m.add(0); + + // predict from bit context + result+=mix2(m, cp[i] ? *cp[i] : 0, sm[i]); + } + if (bp==7) cn=0; + return result; +} + +//////////////////////////// Models ////////////////////////////// + +// All of the models below take a Mixer as a parameter and write +// predictions to it. + +//////////////////////////// matchModel /////////////////////////// + +// matchModel() finds the longest matching context and returns its length + +int matchModel(Mixer& m) { + const int MAXLEN=65534; // longest allowed match + 1 + static Array t(MEM); // hash table of pointers to contexts + static int h=0; // hash of last 7 bytes + static int ptr=0; // points to next byte of match if any + static int len=0; // length of match, or 0 if no match + static int result=0; + + static SmallStationaryContextMap scm1(0x20000); + + if (!bpos) { + h=h*997*8+buf(1)+1&t.size()-1; // update context hash + if (len) ++len, ++ptr; + else { // find match + ptr=t[h]; + if (ptr && pos-ptr0 && !(result&0xfff)) printf("pos=%d len=%d ptr=%d\n", pos, len, ptr); + scm1.set(pos); + } + + // predict + if (len>MAXLEN) len=MAXLEN; + int sgn; + if (len && buf(1)==buf[ptr-1] && c0==buf[ptr]+256>>8-bpos) { + if (buf[ptr]>>7-bpos&1) sgn=1; + else sgn=-1; + } + else sgn=len=0; + m.add(sgn*4*ilog(len)); + m.add(sgn*64*min(len, 32)); + scm1.mix(m); + return result; +} + +//////////////////////////// picModel ////////////////////////// + +// Model a 1728 by 2376 2-color CCITT bitmap image, left to right scan, +// MSB first (216 bytes per row, 513216 bytes total). Insert predictions +// into m. + +void picModel(Mixer& m) { + static U32 r0, r1, r2, r3; // last 4 rows, bit 8 is over current pixel + static Array t(0x10200); // model: cxt -> state + const int N=3; // number of contexts + static int cxt[N]; // contexts + static StateMap sm[N]; + + // update the model + for (int i=0; i>(7-bpos))&1); + r2+=r2+((buf(431)>>(7-bpos))&1); + r3+=r3+((buf(647)>>(7-bpos))&1); + cxt[0]=r0&0x7|r1>>4&0x38|r2>>3&0xc0; + cxt[1]=0x100+(r0&1|r1>>4&0x3e|r2>>2&0x40|r3>>1&0x80); + cxt[2]=0x200+(r0&0x3f^r1&0x3ffe^r2<<2&0x7f00^r3<<5&0xf800); + + // predict + for (int i=0; i='A' && c<='Z') + c+='a'-'A'; + if (c>='a' && c<='z' || c>=128) { + word0=word0*263*32+c; + text0=text0*997*16+c; + } + else if (word0) { + word5=word4*23; + word4=word3*19; + word3=word2*17; + word2=word1*13; + word1=word0*11; + word0=0; + } + if (c==10) nl1=nl, nl=pos-1; + int col=min(255, pos-nl), above=buf[nl1+col]; // text column context + U32 h=word0*271+buf(1); + + cm.set(h); + cm.set(word0); + cm.set(h+word1); + cm.set(word0+word1*31); + cm.set(h+word1+word2*29); + cm.set(text0&0xffffff); + cm.set(text0&0xfffff); + + cm.set(h+word2); + cm.set(h+word3); + cm.set(h+word4); + cm.set(h+word5); + cm.set(buf(1)|buf(3)<<8|buf(5)<<16); + cm.set(buf(2)|buf(4)<<8|buf(6)<<16); + + cm.set(h+word1+word3); + cm.set(h+word2+word3); + + // Text column models + cm.set(col<<16|buf(1)<<8|above); + cm.set(buf(1)<<8|above); + cm.set(col<<8|buf(1)); + cm.set(col); + } + cm.mix(m); +} + +//////////////////////////// recordModel /////////////////////// + +// Model 2-D data with fixed record length. Also order 1-2 models +// that include the distance to the last match. + +void recordModel(Mixer& m) { + static int cpos1[256] , cpos2[256], cpos3[256], cpos4[256]; + static int wpos1[0x10000]; // buf(1..2) -> last position + static int rlen=2, rlen1=3, rlen2=4; // run length and 2 candidates + static int rcount1=0, rcount2=0; // candidate counts + static ContextMap cm(32768, 3), cn(32768/2, 3), co(32768*2, 3), cp(MEM, 3); + + // Find record length + if (!bpos) { + int w=c4&0xffff, c=w&255, d=w>>8; +#if 1 + int r=pos-cpos1[c]; + if (r>1 && r==cpos1[c]-cpos2[c] + && r==cpos2[c]-cpos3[c] && r==cpos3[c]-cpos4[c] + && (r>15 || (c==buf(r*5+1)) && c==buf(r*6+1))) { + if (r==rlen1) ++rcount1; + else if (r==rlen2) ++rcount2; + else if (rcount1>rcount2) rlen2=r, rcount2=1; + else rlen1=r, rcount1=1; + } + if (rcount1>15 && rlen!=rlen1) rlen=rlen1, rcount1=rcount2=0; + if (rcount2>15 && rlen!=rlen2) rlen=rlen2, rcount1=rcount2=0; + + // Set 2 dimensional contexts + assert(rlen>0); +#endif + cm.set(c<<8| (min(255, pos-cpos1[c])/4) ); + cm.set(w<<9| llog(pos-wpos1[w])>>2); + + cm.set(rlen|buf(rlen)<<10|buf(rlen*2)<<18); + cn.set(w|rlen<<8); + cn.set(d|rlen<<16); + cn.set(c|rlen<<8); + + co.set(buf(1)<<8|min(255, pos-cpos1[buf(1)])); + co.set(buf(1)<<17|buf(2)<<9|llog(pos-wpos1[w])>>2); + int col=pos%rlen; + co.set(buf(1)<<8|buf(rlen)); + + //cp.set(w*16); + //cp.set(d*32); + //cp.set(c*64); + cp.set(rlen|buf(rlen)<<10|col<<18); + cp.set(rlen|buf(1)<<10|col<<18); + cp.set(col|rlen<<12); + + // update last context positions + cpos4[c]=cpos3[c]; + cpos3[c]=cpos2[c]; + cpos2[c]=cpos1[c]; + cpos1[c]=pos; + wpos1[w]=pos; + } + cm.mix(m); + cn.mix(m); + co.mix(m); + cp.mix(m); +} + + +//////////////////////////// sparseModel /////////////////////// + +// Model order 1-2 contexts with gaps. + +void sparseModel(Mixer& m, int seenbefore, int howmany) { + static ContextMap cm(MEM*2, 48); + static int mask = 0; + + if (bpos==0) { + + cm.set( c4&0x00f0f0f0); + cm.set((c4&0xf0f0f0f0)+1); + cm.set((c4&0x00f8f8f8)+2); + cm.set((c4&0xf8f8f8f8)+3); + cm.set((c4&0x00e0e0e0)+4); + cm.set((c4&0xe0e0e0e0)+5); + cm.set((c4&0x00f0f0ff)+6); + + cm.set(seenbefore); + cm.set(howmany); + cm.set(c4&0x00ff00ff); + cm.set(c4&0xff0000ff); + cm.set(buf(1)|buf(5)<<8); + cm.set(buf(1)|buf(6)<<8); + cm.set(buf(3)|buf(6)<<8); + cm.set(buf(4)|buf(8)<<8); + + for (int i=1; i<8; ++i) { + cm.set((buf(i+1)<<8)|buf(i+2)); + cm.set((buf(i+1)<<8)|buf(i+3)); + cm.set(seenbefore|buf(i)<<8); + } + + int fl = 0; + if( c4&0xff != 0 ){ + if( isalpha( c4&0xff ) ) fl = 1; + else if( ispunct( c4&0xff ) ) fl = 2; + else if( isspace( c4&0xff ) ) fl = 3; + else if( c4&0xff == 0xff ) fl = 4; + else if( c4&0xff < 16 ) fl = 5; + else if( c4&0xff < 64 ) fl = 6; + else fl = 7; + } + mask = (mask<<3)|fl; + cm.set(mask); + cm.set(mask<<8|buf(1)); + cm.set(mask<<17|buf(2)<<8|buf(3)); + cm.set(mask&0x1ff|((c4&0xf0f0f0f0)<<9)); + } + cm.mix(m); +} + +//////////////////////////// distanceModel /////////////////////// + +// Model for modelling distances between symbols + +void distanceModel(Mixer& m) { + static ContextMap cr(MEM, 3); + if( bpos == 0 ){ + static int pos00=0,pos20=0,posnl=0; + int c=c4&0xff; + if(c==0x00)pos00=pos; + if(c==0x20)pos20=pos; + if(c==0xff||c=='\r'||c=='\n')posnl=pos; + cr.set(min(pos-pos00,255)|(c<<8)); + cr.set(min(pos-pos20,255)|(c<<8)); + cr.set(min(pos-posnl,255)|(c<<8)+234567); + } + cr.mix(m); +} + +//////////////////////////// bmpModel ///////////////////////////////// + +// Model a 24-bit color uncompressed .bmp or .tif file. Return +// width in pixels if an image file is detected, else 0. + +// 32-bit little endian number at buf(i)..buf(i-3) +inline U32 i4(int i) { + assert(i>3); + return buf(i)+256*buf(i-1)+65536*buf(i-2)+16777216*buf(i-3); +} + +// 16-bit +inline int i2(int i) { + assert(i>1); + return buf(i)+256*buf(i-1); +} + +// Square buf(i) +inline int sqrbuf(int i) { + assert(i>0); + return buf(i)*buf(i); +} + +int bmpModel(Mixer& m) { + static int w=0; // width of image in bytes (pixels * 3) + static int eoi=0; // end of image + static U32 tiff=0; // offset of tif header + const int SC=0x20000; + static SmallStationaryContextMap scm1(SC), scm2(SC), + scm3(SC), scm4(SC), scm5(SC), scm6(SC*2); + static ContextMap cm(MEM*4, 8); + + // Detect .bmp file header (24 bit color, not compressed) + if (!bpos && buf(54)=='B' && buf(53)=='M' + && i4(44)==54 && i4(40)==40 && i4(24)==0) { + w=(i4(36)+3&-4)*3; // image width + const int height=i4(32); + eoi=pos; + if (w<0x30000 && height<0x10000) { + eoi=pos+w*height; // image size in bytes + printf("BMP %dx%d ", w/3, height); + } + else + eoi=pos; + } + + // Detect .tif file header (24 bit color, not compressed). + // Parsing is crude, won't work with weird formats. + if (!bpos) { + if (c4==0x49492a00) tiff=pos; // Intel format only + if (pos-tiff==4 && c4!=0x08000000) tiff=0; // 8=normal offset to directory + if (tiff && pos-tiff==200) { // most of directory should be read by now + int dirsize=i2(pos-tiff-4); // number of 12-byte directory entries + w=0; + int bpp=0, compression=0, width=0, height=0; + for (int i=tiff+6; i0; i+=12) { + int tag=i2(pos-i); // 256=width, 257==height, 259: 1=no compression + // 277=3 samples/pixel + int tagfmt=i2(pos-i-2); // 3=short, 4=long + int taglen=i4(pos-i-4); // number of elements in tagval + int tagval=i4(pos-i-8); // 1 long, 1-2 short, or points to array + if ((tagfmt==3||tagfmt==4) && taglen==1) { + if (tag==256) width=tagval; + if (tag==257) height=tagval; + if (tag==259) compression=tagval; // 1 = no compression + if (tag==277) bpp=tagval; // should be 3 + } + } + if (width>0 && height>0 && width*height>50 && compression==1 + && (bpp==1||bpp==3)) + eoi=tiff+width*height*bpp, w=width*bpp; + if (eoi>pos) + printf("TIFF %dx%dx%d ", width, height, bpp); + else + tiff=w=0; + } + } + if (pos>eoi) return w=0; + + // Select nearby pixels as context + if (!bpos) { + assert(w>3); + int color=pos%3; + int mean=buf(3)+buf(w-3)+buf(w)+buf(w+3); + const int var=sqrbuf(3)+sqrbuf(w-3)+sqrbuf(w)+sqrbuf(w+3)-mean*mean/4>>2; + mean>>=2; + const int logvar=ilog(var); + int i=0; + cm.set(hash(++i, buf(3)>>2, buf(w)>>2, color)); + cm.set(hash(++i, buf(3)>>2, buf(1)>>2, color)); + cm.set(hash(++i, buf(3)>>2, buf(2)>>2, color)); + cm.set(hash(++i, buf(w)>>2, buf(1)>>2, color)); + cm.set(hash(++i, buf(w)>>2, buf(2)>>2, color)); + cm.set(hash(++i, buf(3)+buf(w)>>1, color)); + cm.set(hash(++i, buf(3)+buf(w)>>3, buf(1)>>5, buf(2)>>5, color)); + cm.set(hash(++i, mean, logvar>>5, color)); + scm1.set(buf(3)+buf(w)>>1); + scm2.set(buf(3)+buf(w)-buf(w+3)>>1); + scm3.set(buf(3)*2-buf(6)>>1); + scm4.set(buf(w)*2-buf(w*2)>>1); + scm5.set(buf(3)+buf(w)-buf(w-3)>>1); + scm6.set(mean>>1|logvar<<1&0x180); + } + + // Predict next bit + scm1.mix(m); + scm2.mix(m); + scm3.mix(m); + scm4.mix(m); + scm5.mix(m); + scm6.mix(m); + cm.mix(m); + return w; +} + +//////////////////////////// jpegModel ///////////////////////// + +// Model JPEG. Return 1 if a JPEG file is detected or else 0. +// Only the baseline and 8 bit extended Huffman coded DCT modes are +// supported. The model partially decodes the JPEG image to provide +// context for the Huffman coded symbols. + +// Print a JPEG segment at buf[p...] for debugging +void dump(const char* msg, int p) { + printf("%s:", msg); + int len=buf[p+2]*256+buf[p+3]; + for (int i=0; i ht(8); // pointers to Huffman table headers + static int htsize=0; // number of pointers in ht + + // Huffman decode state + static U32 huffcode=0; // Current Huffman code including extra bits + static int huffbits=0; // Number of valid bits in huffcode + static int huffsize=0; // Number of bits without extra bits + static int rs=-1; // Decoded huffcode without extra bits. It represents + // 2 packed 4-bit numbers, r=run of zeros, s=number of extra bits for + // first nonzero code. huffcode is complete when rs >= 0. + // rs is -1 prior to decoding incomplete huffcode. + static int mcupos=0; // position in MCU (0-639). The low 6 bits mark + // the coefficient in zigzag scan order (0=DC, 1-63=AC). The high + // bits mark the block within the MCU, used to select Huffman tables. + + // Decoding tables + static Array huf(128); // Tc*64+Th*16+m -> min, max, val + static int mcusize=0; // number of coefficients in an MCU + static int linesize=0; // width of image in MCU + static int hufsel[2][10]; // DC/AC, mcupos/64 -> huf decode table + static Array hbuf(2048); // Tc*1024+Th*256+hufcode -> RS + + // Image state + static Array color(10); // block -> component (0-3) + static Array pred(4); // component -> last DC value + static int dc=0; // DC value of the current block + static int width=0; // Image width in MCU + static int row=0, column=0; // in MCU (column 0 to width-1) + static Buf cbuf(0x20000); // Rotating buffer of coefficients, coded as: + // DC: level shifted absolute value, low 4 bits discarded, i.e. + // [-1023...1024] -> [0...255]. + // AC: as an RS code: a run of R (0-15) zeros followed by an S (0-15) + // bit number, or 00 for end of block (in zigzag order). + // However if R=0, then the format is ssss11xx where ssss is S, + // xx is the first 2 extra bits, and the last 2 bits are 1 (since + // this never occurs in a valid RS code). + static int cpos=0; // position in cbuf + static U32 huff1=0, huff2=0, huff3=0, huff4=0; // hashes of last codes + static int rs1, rs2, rs3, rs4; // last 4 RS codes + static int ssum=0, ssum1=0, ssum2=0, ssum3=0, ssum4=0; + // sum of S in RS codes in block and last 4 values + + // Be sure to quit on a byte boundary + if (!bpos) next_jpeg=jpeg>1; + if (bpos && !jpeg) return next_jpeg; + if (!bpos && app>0) --app; + if (app>0) return next_jpeg; + if (!bpos) { + + // Parse. Baseline DCT-Huffman JPEG syntax is: + // SOI APPx... misc... SOF0 DHT... SOS data EOI + // SOI (= FF D8) start of image. + // APPx (= FF Ex) len ... where len is always a 2 byte big-endian length + // including the length itself but not the 2 byte preceding code. + // Application data is ignored. There may be more than one APPx. + // misc codes are DQT, DNL, DRI, COM (ignored). + // SOF0 (= FF C0) len 08 height width Nf [C HV Tq]... + // where len, height, width (in pixels) are 2 bytes, Nf is the repeat + // count (1 byte) of [C HV Tq], where C is a component identifier + // (color, 0-3), HV is the horizontal and vertical dimensions + // of the MCU (high, low bits, packed), and Tq is the quantization + // table ID (not used). An MCU (minimum compression unit) consists + // of 64*H*V DCT coefficients for each color. + // DHT (= FF C4) len [TcTh L1...L16 V1,1..V1,L1 ... V16,1..V16,L16]... + // defines Huffman table Th (1-4) for Tc (0=DC (first coefficient) + // 1=AC (next 63 coefficients)). L1..L16 are the number of codes + // of length 1-16 (in ascending order) and Vx,y are the 8-bit values. + // A V code of RS means a run of R (0-15) zeros followed by S (0-15) + // additional bits to specify the next nonzero value, negative if + // the first additional bit is 0 (e.g. code x63 followed by the + // 3 bits 1,0,1 specify 7 coefficients: 0, 0, 0, 0, 0, 0, 5. + // Code 00 means end of block (remainder of 63 AC coefficients is 0). + // SOS (= FF DA) len Ns [Cs TdTa]... 0 3F 00 + // Start of scan. TdTa specifies DC/AC Huffman tables (0-3, packed + // into one byte) for component Cs matching C in SOF0, repeated + // Ns (1-4) times. + // EOI (= FF D9) is end of image. + // Huffman coded data is between SOI and EOI. Codes may be embedded: + // RST0-RST7 (= FF D0 to FF D7) mark the start of an independently + // compressed region. + // DNL (= FF DC) 04 00 height + // might appear at the end of the scan (ignored). + // FF 00 is interpreted as FF (to distinguish from RSTx, DNL, EOI). + + // Detect JPEG (SOI, APPx) + if (!jpeg && buf(4)==FF && buf(3)==SOI && buf(2)==FF && buf(1)>>4==0xe) { + jpeg=1; + app=sos=sof=htsize=data=mcusize=linesize=0; + huffcode=huffbits=huffsize=mcupos=cpos=0, rs=-1; + memset(&huf[0], 0, huf.size()*sizeof(HUF)); + memset(&pred[0], 0, pred.size()*sizeof(int)); + } + + // Detect end of JPEG when data contains a marker other than RSTx + // or byte stuff (00). + if (jpeg && data && buf(2)==FF && buf(1) && (buf(1)&0xf8)!=RST0) { + jassert(buf(1)==EOI); + jpeg=0; + } + if (!jpeg) return next_jpeg; + + // Detect APPx or COM field + if (!data && !app && buf(4)==FF && (buf(3)>>4==0xe || buf(3)==COM)) + app=buf(2)*256+buf(1)+2; + + // Save pointers to sof, ht, sos, data, + if (buf(5)==FF && buf(4)==SOS) { + int len=buf(3)*256+buf(2); + if (len==6+2*buf(1) && buf(1) && buf(1)<=4) // buf(1) is Ns + sos=pos-5, data=sos+len+2, jpeg=2; + } + if (buf(4)==FF && buf(3)==DHT && htsize<8) ht[htsize++]=pos-4; + if (buf(4)==FF && buf(3)==SOF0) sof=pos-4; + + // Restart + if (buf(2)==FF && (buf(1)&0xf8)==RST0) { + huffcode=huffbits=huffsize=mcupos=0, rs=-1; + memset(&pred[0], 0, pred.size()*sizeof(int)); + } + } + + { + // Build Huffman tables + // huf[Tc][Th][m] = min, max+1 codes of length m, pointer to byte values + if (pos==data && bpos==1) { + jassert(htsize>0); + for (int i=0; i>4, th=buf[p]&15; + if (tc>=2 || th>=4) break; + jassert(tc>=0 && tc<2 && th>=0 && th<4); + HUF* h=&huf[tc*64+th*16]; // [tc][th][0]; + int val=p+17; // pointer to values + int hval=tc*1024+th*256; // pointer to RS values in hbuf + for (int j=0; j<256; ++j) // copy RS codes + hbuf[hval+j]=buf[val+j]; + int code=0; + for (int j=0; j<16; ++j) { + h[j].min=code; + h[j].max=code+=buf[p+j+1]; + h[j].val=hval; + val+=buf[p+j+1]; + hval+=buf[p+j+1]; + code*=2; + } + p=val; + jassert(hval>=0 && hval<2048); + } + jassert(p==end); + } + huffcode=huffbits=huffsize=0, rs=-1; + + // Build Huffman table selection table (indexed by mcupos). + // Get image width. + if (!sof && sos) return next_jpeg; + int ns=buf[sos+4]; + int nf=buf[sof+9]; + jassert(ns<=4 && nf<=4); + mcusize=0; // blocks per MCU + int hmax=0; // MCU horizontal dimension + for (int i=0; i>4>hmax) hmax=hv>>4; + hv=(hv&15)*(hv>>4); // number of blocks in component C + jassert(hv>=1 && hv+mcusize<=10); + while (hv) { + jassert(mcusize<10); + hufsel[0][mcusize]=buf[sos+2*i+6]>>4&15; + hufsel[1][mcusize]=buf[sos+2*i+6]&15; + jassert (hufsel[0][mcusize]<4 && hufsel[1][mcusize]<4); + color[mcusize]=i; + --hv; + ++mcusize; + } + } + } + } + jassert(hmax>=1 && hmax<=10); + width=buf[sof+7]*256+buf[sof+8]; // in pixels + int height=buf[sof+5]*256+buf[sof+6]; + printf("JPEG %dx%d ", width, height); + width=(width-1)/(hmax*8)+1; // in MCU + jassert(width>0); + mcusize*=64; // coefficients per MCU + row=column=0; + } + } + + + // Decode Huffman + { + if (mcusize && buf(1+(!bpos))!=FF) { // skip stuffed byte + jassert(huffbits<=32); + huffcode+=huffcode+y; + ++huffbits; + if (rs<0) { + jassert(huffbits>=1 && huffbits<=16); + const int ac=(mcupos&63)>0; + jassert(mcupos>=0 && (mcupos>>6)<10); + jassert(ac==0 || ac==1); + const int sel=hufsel[ac][mcupos>>6]; + jassert(sel>=0 && sel<4); + const int i=huffbits-1; + jassert(i>=0 && i<16); + const HUF *h=&huf[ac*64+sel*16]; // [ac][sel]; + jassert(h[i].min<=h[i].max && h[i].val<2048 && huffbits>0); + if (huffcode=h[i].min); + int k=h[i].val+huffcode-h[i].min; + jassert(k>=0 && k<2048); + rs=hbuf[k]; + huffsize=huffbits; + } + } + if (rs>=0) { + if (huffsize+(rs&15)==huffbits) { // done decoding + huff4=huff3; + huff3=huff2; + huff2=huff1; + huff1=hash(huffcode, huffbits); + rs4=rs3; + rs3=rs2; + rs2=rs1; + rs1=rs; + int x=0; // decoded extra bits + if (mcupos&63) { // AC + if (rs==0) { // EOB + mcupos=mcupos+63&-64; + jassert(mcupos>=0 && mcupos<=mcusize && mcupos<=640); + while (cpos&63) cbuf[cpos++]=0; + } + else { // rs = r zeros + s extra bits for the next nonzero value + // If first extra bit is 0 then value is negative. + jassert((rs&15)<=10); + const int r=rs>>4; + const int s=rs&15; + jassert(mcupos>>6==mcupos+r>>6); + mcupos+=r+1; + x=huffcode&(1<>s-1)) x-=(1<=1; --i) cbuf[cpos++]=i<<4|s; + cbuf[cpos++]=s<<4|huffcode<<2>>s&3|12; + ssum+=s; + } + } + else { // DC: rs = 0S, s<12 + jassert(rs<12); + ++mcupos; + x=huffcode&(1<>rs-1)) x-=(1<=0 && mcupos>>6<10); + const int comp=color[mcupos>>6]; + jassert(comp>=0 && comp<4); + dc=pred[comp]+=x; + jassert((cpos&63)==0); + cbuf[cpos++]=dc+1023>>3; + ssum4=ssum3; + ssum3=ssum2; + ssum2=ssum1; + ssum1=ssum; + ssum=rs; + } + jassert(mcupos>=0 && mcupos<=mcusize); + if (mcupos>=mcusize) { + mcupos=0; + if (++column==width) column=0, ++row; + } + huffcode=huffsize=huffbits=0, rs=-1; + } + } + } + } + + // Estimate next bit probability + if (!jpeg || !data) return next_jpeg; + + // Context model + const int N=19; // size of t, number of contexts + static BH<9> t(MEM); // context hash -> bit history + // As a cache optimization, the context does not include the last 1-2 + // bits of huffcode if the length (huffbits) is not a multiple of 3. + // The 7 mapped values are for context+{"", 0, 00, 01, 1, 10, 11}. + static Array cxt(N); // context hashes + static Array cp(N); // context pointers + static StateMap sm[N]; + static Mixer m1(32, 800, 4); + static APM a1(1024), a2(0x10000); + const static U8 zzu[64]={ // zigzag coef -> u,v + 0,1,0,0,1,2,3,2,1,0,0,1,2,3,4,5,4,3,2,1,0,0,1,2,3,4,5,6,7,6,5,4, + 3,2,1,0,1,2,3,4,5,6,7,7,6,5,4,3,2,3,4,5,6,7,7,6,5,4,5,6,7,7,6,7}; + const static U8 zzv[64]={ + 0,0,1,2,1,0,0,1,2,3,4,3,2,1,0,0,1,2,3,4,5,6,5,4,3,2,1,0,0,1,2,3, + 4,5,6,7,7,6,5,4,3,2,1,2,3,4,5,6,7,7,6,5,4,3,4,5,6,7,7,6,5,6,7,7}; + + + // Update model + if (cp[N-1]) { + for (int i=0; i>6]; + const int coef=(mcupos&63)|comp<<6; + const int hc=huffcode|1<2 || huffbits==0) hbcount=0; + jassert(coef>=0 && coef<256); + const int zu=zzu[mcupos&63], zv=zzv[mcupos&63]; + if (hbcount==0) { + const int mpos=mcupos>>4|!(mcupos&-64)<<7; + int n=0; + cxt[0]=hash(++n, hc, mcupos>>2, min(3, mcupos&63)); + cxt[1]=hash(++n, hc, mpos>>4, cbuf[cpos-mcusize]); + cxt[2]=hash(++n, hc, mpos>>4, cbuf[cpos-width*mcusize]); + cxt[3]=hash(++n, hc, ilog(ssum3), coef); + cxt[4]=hash(++n, hc, coef, column>>3); + cxt[5]=hash(++n, hc, coef, column>>1); + cxt[6]=hash(++n, hc, rs1, mpos); + cxt[7]=hash(++n, hc, rs1, rs2); + cxt[8]=hash(++n, hc, rs1, rs2, rs3); + cxt[9]=hash(++n, hc, ssum>>4, mcupos); + cxt[10]=hash(++n, hc, mpos, cbuf[cpos-1]); + cxt[11]=hash(++n, hc, dc); + cxt[12]=hash(++n, hc, rs1, coef); + cxt[13]=hash(++n, hc, rs1, rs2, coef); + cxt[14]=hash(++n, hc, mcupos>>3, ssum3>>3); + cxt[15]=hash(++n, hc, huff1); + cxt[16]=hash(++n, hc, coef, huff1); + cxt[17]=hash(++n, hc, zu, comp); + cxt[18]=hash(++n, hc, zv, comp); + } + + // Predict next bit + m1.add(128); + assert(hbcount<=2); + for (int i=0; i4))); + } + cm.mix(m); +} + +//////////////////////////// indirectModel ///////////////////// + +// The context is a byte string history that occurs within a +// 1 or 2 byte context. + +void indirectModel(Mixer& m) { + static ContextMap cm(MEM, 6); + static U32 t1[256]; + static U16 t2[0x10000]; + + if (!bpos) { + U32 d=c4&0xffff, c=d&255; + U32& r1=t1[d>>8]; + r1=r1<<8|c; + U16& r2=t2[c4>>8&0xffff]; + r2=r2<<8|c; + U32 t=c|t1[c]<<8; + cm.set(t&0xffff); + cm.set(t&0xffffff); + cm.set(t); + cm.set(t&0xff00); + t=d|t2[d]<<16; + cm.set(t&0xffffff); + cm.set(t); + + } + cm.mix(m); +} + +//////////////////////////// dmcModel ////////////////////////// + +// Model using DMC. The bitwise context is represented by a state graph, +// initilaized to a bytewise order 1 model as in +// http://plg.uwaterloo.ca/~ftp/dmc/dmc.c but with the following difference: +// - It uses integer arithmetic. +// - The threshold for cloning a state increases as memory is used up. +// - Each state maintains both a 0,1 count and a bit history (as in a +// context model). The 0,1 count is best for stationary data, and the +// bit history for nonstationary data. The bit history is mapped to +// a probability adaptively using a StateMap. The two computed probabilities +// are combined. +// - When memory is used up the state graph is reinitialized to a bytewise +// order 1 context as in the original DMC. However, the bit histories +// are not cleared. + +struct DMCNode { // 12 bytes + unsigned int nx[2]; // next pointers + U8 state; // bit history + unsigned int c0:12, c1:12; // counts * 256 +}; + +void dmcModel(Mixer& m) { + static int top=0, curr=0; // allocated, current node + static Array t(MEM*2); // state graph + static StateMap sm; + static int threshold=256; + + // clone next state + if (top>0 && top=threshold*2 && nn-n>=threshold*3) { + int r=n*4096/nn; + assert(r>=0 && r<=4096); + t[next].c0 -= t[top].c0 = t[next].c0*r>>12; + t[next].c1 -= t[top].c1 = t[next].c1*r>>12; + t[top].nx[0]=t[next].nx[0]; + t[top].nx[1]=t[next].nx[1]; + t[top].state=t[next].state; + t[curr].nx[y]=top; + ++top; + if (top==MEM*2) threshold=512; + if (top==MEM*3) threshold=768; + } + } + + // Initialize to a bytewise order 1 model at startup or when flushing memory + if (top==t.size() && bpos==1) top=0; + if (top==0) { + assert(t.size()>=65536); + for (int i=0; i<256; ++i) { + for (int j=0; j<256; ++j) { + if (i<127) { + t[j*256+i].nx[0]=j*256+i*2+1; + t[j*256+i].nx[1]=j*256+i*2+2; + } + else { + t[j*256+i].nx[0]=(i-127)*256; + t[j*256+i].nx[1]=(i+1)*256; + } + t[j*256+i].c0=128; + t[j*256+i].c1=128; + } + } + top=65536; + curr=0; + threshold=256; + } + + // update count, state + if (y) { + if (t[curr].c1<3800) t[curr].c1+=256; + } + else if (t[curr].c0<3800) t[curr].c0+=256; + t[curr].state=nex(t[curr].state, y); + curr=t[curr].nx[y]; + + // predict + const int pr1=sm.p(t[curr].state); + const int n1=t[curr].c1; + const int n0=t[curr].c0; + const int pr2=(n1+5)*4096/(n0+n1+10); + m.add(stretch(pr1)); + m.add(stretch(pr2)); +} + +//////////////////////////// contextModel ////////////////////// + +typedef enum {DEFAULT, JPEG, EXE, TEXT} Filetype; + +// This combines all the context models with a Mixer. + +int contextModel2() { + static ContextMap cm(MEM*32, 9); + static RunContextMap rcm7(MEM), rcm9(MEM), rcm10(MEM); + static Mixer m(800, 3088, 7, 128); + static U32 cxt[16]; // order 0-11 contexts + static Filetype filetype=DEFAULT; + static int size=0; // bytes remaining in block +// static const char* typenames[4]={"", "jpeg ", "exe ", "text "}; + + // Parse filetype and size + if (bpos==0) { + --size; + if (size==-1) filetype=(Filetype)buf(1); + if (size==-5) { + size=buf(4)<<24|buf(3)<<16|buf(2)<<8|buf(1); +// if (filetype<=3) printf("(%s%d)", typenames[filetype], size); + if (filetype==EXE) size+=8; + } + } + + m.update(); + m.add(256); + + // Test for special file types + int isjpeg=jpegModel(m); // 1 if JPEG is detected, else 0 + int ismatch=ilog(matchModel(m)); // Length of longest matching context + int isbmp=bmpModel(m); // Image width (bytes) if BMP or TIFF detected, or 0 + + if (isjpeg) { + m.set(1, 8); + m.set(c0, 256); + m.set(buf(1), 256); + return m.p(); + } + else if (isbmp>0) { + static int col=0; + if (++col>=24) col=0; + m.set(2, 8); + m.set(col, 24); + m.set(buf(isbmp)+buf(3)>>4, 32); + m.set(c0, 256); + return m.p(); + } + + + // Normal model + if (bpos==0) { + for (int i=15; i>0; --i) // update order 0-11 context hashes + cxt[i]=cxt[i-1]*257+(c4&255)+1; + for (int i=0; i<7; ++i) + cm.set(cxt[i]); + rcm7.set(cxt[7]); + cm.set(cxt[8]); + rcm9.set(cxt[10]); + rcm10.set(cxt[12]); + cm.set(cxt[14]); + } + int order=cm.mix(m); + + rcm7.mix(m); + rcm9.mix(m); + rcm10.mix(m); + + if (level>=4) { + sparseModel(m,ismatch,order); + distanceModel(m); + picModel(m); + recordModel(m); + wordModel(m); + indirectModel(m); + dmcModel(m); + if (filetype==EXE) exeModel(m); + } + + + + order = order-2; + if(order<0) order=0; + + U32 c1=buf(1), c2=buf(2), c3=buf(3), c; + + m.set(c1+8, 264); + m.set(c0, 256); + m.set(order+8*(c4>>5&7)+64*(c1==c2)+128*(filetype==EXE), 256); + m.set(c2, 256); + m.set(c3, 256); + m.set(ismatch, 256); + + if(bpos) + { + c=c0<<(8-bpos); if(bpos==1)c+=c3/2; + c=(min(bpos,5))*256+c1/32+8*(c2/32)+(c&192); + } + else c=c3/128+(c4>>31)*2+4*(c2/64)+(c1&240); + m.set(c, 1536); + int pr=m.p(); + return pr; +} + + +//////////////////////////// Predictor ///////////////////////// + +// A Predictor estimates the probability that the next bit of +// uncompressed data is 1. Methods: +// p() returns P(1) as a 12 bit number (0-4095). +// update(y) trains the predictor with the actual bit (0 or 1). + +class Predictor { + int pr; // next prediction +public: + Predictor(); + int p() const {assert(pr>=0 && pr<4096); return pr;} + void update(); +}; + +Predictor::Predictor(): pr(2048) {} + +void Predictor::update() { + static APM a(256), a1(0x10000), a2(0x10000), a3(0x10000), + a4(0x10000), a5(0x10000), a6(0x10000); + + // Update global context: pos, bpos, c0, c4, buf + c0+=c0+y; + if (c0>=256) { + buf[pos++]=c0; + c4=(c4<<8)+c0-256; + c0=1; + } + bpos=(bpos+1)&7; + + // Filter the context model with APMs + int pr0=contextModel2(); + + pr=a.p(pr0, c0); + + int pr1=a1.p(pr0, c0+256*buf(1)); + int pr2=a2.p(pr0, c0^hash(buf(1), buf(2))&0xffff); + int pr3=a3.p(pr0, c0^hash(buf(1), buf(2), buf(3))&0xffff); + pr0=pr0+pr1+pr2+pr3+2>>2; + + pr1=a4.p(pr, c0+256*buf(1)); + pr2=a5.p(pr, c0^hash(buf(1), buf(2))&0xffff); + pr3=a6.p(pr, c0^hash(buf(1), buf(2), buf(3))&0xffff); + pr=pr+pr1+pr2+pr3+2>>2; + + pr=pr+pr0+1>>1; +} + +//////////////////////////// Encoder //////////////////////////// + +// An Encoder does arithmetic encoding. Methods: +// Encoder(COMPRESS, f) creates encoder for compression to archive f, which +// must be open past any header for writing in binary mode. +// Encoder(DECOMPRESS, f) creates encoder for decompression from archive f, +// which must be open past any header for reading in binary mode. +// code(i) in COMPRESS mode compresses bit i (0 or 1) to file f. +// code() in DECOMPRESS mode returns the next decompressed bit from file f. +// Global y is set to the last bit coded or decoded by code(). +// compress(c) in COMPRESS mode compresses one byte. +// decompress() in DECOMPRESS mode decompresses and returns one byte. +// flush() should be called exactly once after compression is done and +// before closing f. It does nothing in DECOMPRESS mode. +// size() returns current length of archive +// setFile(f) sets alternate source to FILE* f for decompress() in COMPRESS +// mode (for testing transforms). +// If level (global) is 0, then data is stored without arithmetic coding. + +typedef enum {COMPRESS, DECOMPRESS} Mode; +class Encoder { +private: + Predictor predictor; + const Mode mode; // Compress or decompress? + FILE* archive; // Compressed data file + U32 x1, x2; // Range, initially [0, 1), scaled by 2^32 + U32 x; // Decompress mode: last 4 input bytes of archive + FILE *alt; // decompress() source in COMPRESS mode + + // Compress bit y or return decompressed bit + int code(int i=0) { + int p=predictor.p(); + assert(p>=0 && p<4096); + p+=p<2048; + U32 xmid=x1 + (x2-x1>>12)*p + ((x2-x1&0xfff)*p>>12); + assert(xmid>=x1 && xmid>24, archive); + x1<<=8; + x2=(x2<<8)+255; + if (mode==DECOMPRESS) x=(x<<8)+(getc(archive)&255); // EOF is OK + } + return y; + } + +public: + Encoder(Mode m, FILE* f); + Mode getMode() const {return mode;} + long size() const {return ftell(archive);} // length of archive so far + void flush(); // call this when compression is finished + void setFile(FILE* f) {alt=f;} + + // Compress one byte + void compress(int c) { + assert(mode==COMPRESS); + if (level==0) + putc(c, archive); + else + for (int i=7; i>=0; --i) + code((c>>i)&1); + } + + // Decompress and return one byte + int decompress() { + if (mode==COMPRESS) { + assert(alt); + return getc(alt); + } + else if (level==0) + return getc(archive); + else { + int c=0; + for (int i=0; i<8; ++i) + c+=c+code(); + return c; + } + } +}; + +Encoder::Encoder(Mode m, FILE* f): + mode(m), archive(f), x1(0), x2(0xffffffff), x(0), alt(0) { + if (level>0 && mode==DECOMPRESS) { // x = first 4 bytes of archive + for (int i=0; i<4; ++i) + x=(x<<8)+(getc(archive)&255); + } +} + +void Encoder::flush() { + if (mode==COMPRESS && level>0) + putc(x1>>24, archive); // Flush first unequal byte of range +} + +/////////////////////////// Filters ///////////////////////////////// +// +// Before compression, data is encoded in blocks with the following format: +// +// +// +// Type is 1 byte (type Filetype): DEFAULT=0, JPEG, EXE +// Size is 4 bytes in big-endian format. +// Encoded-data decodes to bytes. The encoded size might be +// different. Encoded data is designed to be more compressible. +// +// void encode(FILE* in, FILE* out, int n); +// +// Reads n bytes of in (open in "rb" mode) and encodes one or +// more blocks to temporary file out (open in "wb+" mode). +// The file pointer of in is advanced n bytes. The file pointer of +// out is positioned after the last byte written. +// +// en.setFile(FILE* out); +// int decode(Encoder& en); +// +// Decodes and returns one byte. Input is from en.decompress(), which +// reads from out if in COMPRESS mode. During compression, n calls +// to decode() must exactly match n bytes of in, or else it is compressed +// as type 0 without encoding. +// +// Filetype detect(FILE* in, int n, Filetype type); +// +// Reads n bytes of in, and detects when the type changes to +// something else. If it does, then the file pointer is repositioned +// to the start of the change and the new type is returned. If the type +// does not change, then it repositions the file pointer n bytes ahead +// and returns the old type. +// +// For each type X there are the following 2 functions: +// +// void encode_X(FILE* in, FILE* out, int n, ...); +// +// encodes n bytes from in to out. +// +// int decode_X(Encoder& en); +// +// decodes one byte from en and returns it. decode() and decode_X() +// maintain state information using static variables. + +// Detect EXE or JPEG data +Filetype detect(FILE* in, int n, Filetype type) { + U32 buf1=0, buf0=0; // last 8 bytes + long start=ftell(in); + + // For EXE detection + Array abspos(256), // CALL/JMP abs. addr. low byte -> last offset + relpos(256); // CALL/JMP relative addr. low byte -> last offset + int e8e9count=0; // number of consecutive CALL/JMPs + int e8e9pos=0; // offset of first CALL or JMP instruction + int e8e9last=0; // offset of most recent CALL or JMP + + // For JPEG detection + int soi=0, sof=0, sos=0; // position where found + + for (int i=0; i>24; + buf0=buf0<<8|c; + + // Detect JPEG by code SOI APPx (FF D8 FF Ex) followed by + // SOF0 (FF C0 xx xx 08) and SOS (FF DA) within a reasonable distance. + // Detect end by any code other than RST0-RST7 (FF D9-D7) or + // a byte stuff (FF 00). + + if (i>=3 && (buf0&0xfffffff0)==0xffd8ffe0) soi=i; + if (soi && i-soi<0x10000 && (buf1&0xff)==0xff + && (buf0&0xff0000ff)==0xc0000008) + sof=i; + if (soi && sof && sof>soi && i-soi<0x10000 && i-sof<0x1000 + && (buf0&0xffff)==0xffda) { + sos=i; + if (type!=JPEG) return fseek(in, start+soi-3, SEEK_SET), JPEG; + } + if (type==JPEG && sos && i>sos && (buf0&0xff00)==0xff00 + && (buf0&0xff)!=0 && (buf0&0xf8)!=0xd0) + return DEFAULT; + + // Detect EXE if the low order byte (little-endian) XX is more + // recently seen (and within 4K) if a relative to absolute address + // conversion is done in the context CALL/JMP (E8/E9) XX xx xx 00/FF + // 4 times in a row. Detect end of EXE at the last + // place this happens when it does not happen for 64KB. + + if ((buf1&0xfe)==0xe8 && (buf0+1&0xfe)==0) { + int r=buf0>>24; // relative address low 8 bits + int a=(buf0>>24)+i&0xff; // absolute address low 8 bits + int rdist=i-relpos[r]; + int adist=i-abspos[a]; + if (adist5) { + e8e9last=i; + ++e8e9count; + if (e8e9pos==0 || e8e9pos>abspos[a]) e8e9pos=abspos[a]; + } + else e8e9count=0; + if (type!=EXE && e8e9count>=4 && e8e9pos>5) + return fseek(in, start+e8e9pos-5, SEEK_SET), EXE; + abspos[a]=i; + relpos[r]=i; + } + if (type==EXE && i-e8e9last>0x1000) + return fseek(in, start+e8e9last, SEEK_SET), DEFAULT; + } + return type; +} + +// Default encoding as self +void encode_default(FILE* in, FILE* out, int len) { + while (len--) putc(getc(in), out); +} + +int decode_default(Encoder& en) { + return en.decompress(); +} + +// JPEG encode as self. The purpose is to shield jpegs from exe transform. +void encode_jpeg(FILE* in, FILE* out, int len) { + while (len--) putc(getc(in), out); +} + +int decode_jpeg(Encoder& en) { + return en.decompress(); +} + +// EXE transform: ... +// Encoded-size is 4 bytes, MSB first. +// begin is the offset of the start of the input file, 4 bytes, MSB first. +// Each block applies the e8e9 transform to strings falling entirely +// within the block starting from the end and working backwards. +// The 5 byte pattern is E8/E9 xx xx xx 00/FF (x86 CALL/JMP xxxxxxxx) +// where xxxxxxxx is a relative address LSB first. The address is +// converted to an absolute address by adding the offset mod 2^25 +// (in range +-2^24). + +void encode_exe(FILE* in, FILE* out, int len, int begin) { + const int BLOCK=0x10000; + Array blk(BLOCK); + fprintf(out, "%c%c%c%c", len>>24, len>>16, len>>8, len); // size, MSB first + fprintf(out, "%c%c%c%c", begin>>24, begin>>16, begin>>8, begin); + + // Transform + for (int offset=0; offset=4; --i) { + if ((blk[i-4]==0xe8||blk[i-4]==0xe9) && (blk[i]==0||blk[i]==0xff)) { + int a=(blk[i-3]|blk[i-2]<<8|blk[i-1]<<16|blk[i]<<24)+offset+begin+i+1; + a<<=7; + a>>=7; + blk[i]=a>>24; + blk[i-1]=a>>16; + blk[i-2]=a>>8; + blk[i-3]=a; + } + } + fwrite(&blk[0], 1, bytesRead, out); + } +} + +int decode_exe(Encoder& en) { + const int BLOCK=0x10000; // block size + static int offset=0, q=0; // decode state: file offset, queue size + static int size=0; // where to stop coding + static int begin=0; // offset in file + static U8 c[5]; // queue of last 5 bytes, c[0] at front + + // Read size from first 4 bytes, MSB first + while (offset==size && q==0) { + offset=0; + size=en.decompress()<<24; + size|=en.decompress()<<16; + size|=en.decompress()<<8; + size|=en.decompress(); + begin=en.decompress()<<24; + begin|=en.decompress()<<16; + begin|=en.decompress()<<8; + begin|=en.decompress(); + } + + // Fill queue + while (offset subtract location from x + if (q==5 && (c[4]==0xe8||c[4]==0xe9) && (c[0]==0||c[0]==0xff) + && ((offset-1^offset-5)&-BLOCK)==0) { // not crossing block boundary + int a=(c[3]|c[2]<<8|c[1]<<16|c[0]<<24)-offset-begin; + a<<=7; + a>>=7; + c[3]=a; + c[2]=a>>8; + c[1]=a>>16; + c[0]=a>>24; + } + + // return oldest byte in queue + assert(q>0 && q<=5); + return c[--q]; +} + + + +// Split n bytes into blocks by type. For each block, output +// and call encode_X to convert to type X. +void encode(FILE* in, FILE* out, int n) { + Filetype type=DEFAULT; + long begin=ftell(in); + while (n>0) { + Filetype nextType=detect(in, n, type); + long end=ftell(in); + fseek(in, begin, SEEK_SET); + int len=int(end-begin); + if (len>0) { + fprintf(out, "%c%c%c%c%c", type, len>>24, len>>16, len>>8, len); + switch(type) { + case JPEG: encode_jpeg(in, out, len); break; + case EXE: encode_exe(in, out, len, begin); break; + default: encode_default(in, out, len); break; + } + } + n-=len; + type=nextType; + begin=end; + } +} + +// Decode ... +int decode(Encoder& en) { + static Filetype type=DEFAULT; + static int len=0; + while (len==0) { + type=(Filetype)en.decompress(); + len=en.decompress()<<24; + len|=en.decompress()<<16; + len|=en.decompress()<<8; + len|=en.decompress(); + if (len<0) len=1; + } + --len; + switch (type) { + case JPEG: return decode_jpeg(en); + case EXE: return decode_exe(en); + default: return decode_default(en); + } +} + +//////////////////// Compress, Decompress //////////////////////////// + +// Print progress: n is the number of bytes compressed or decompressed +void printStatus(int n) { + if (n>0 && !(n&0x0fff)) + printf("%12d\b\b\b\b\b\b\b\b\b\b\b\b", n), fflush(stdout); +} + +// Compress a file +void compress(const char* filename, long filesize, Encoder& en) { + assert(en.getMode()==COMPRESS); + assert(filename && filename[0]); + FILE *f=fopen(filename, "rb"); + if (!f) perror(filename), quit(); + long start=en.size(); + printf("%s %ld -> ", filename, filesize); + + // Transform and test in blocks + const int BLOCK=MEM*64; + for (int i=0; filesize>0; i+=BLOCK) { + int size=BLOCK; + if (size>filesize) size=filesize; + FILE* tmp=tmpfile(); + if (!tmp) perror("tmpfile"), quit(); + long savepos=ftell(f); + encode(f, tmp, size); + + // Test transform + rewind(tmp); + en.setFile(tmp); + fseek(f, savepos, SEEK_SET); + long j; + int c1=0, c2=0; + for (j=0; j>24); + en.compress(size>>16); + en.compress(size>>8); + en.compress(size); + fseek(f, savepos, SEEK_SET); + for (int j=0; j ", filename, filesize); + bool found=false; // mismatch? + for (int i=0; i ", filename, filesize); + for (int i=0; i ", filename, filesize); + for (int i=0; i=s.size()) s.resize(len*2+1); + if (c!='\r') s[len++]=c; + } + if (len>=s.size()) s.resize(len+1); + s[len]=0; + if (c==EOF || c==26) + return 0; + else + return s.c_str(); +} + +// int expand(String& archive, String& s, const char* fname, int base) { +// Given file name fname, print its length and base name (beginning +// at fname+base) to archive in format "%ld\t%s\r\n" and append the +// full name (including path) to String s in format "%s\n". If fname +// is a directory then substitute all of its regular files and recursively +// expand any subdirectories. Base initially points to the first +// character after the last / in fname, but in subdirectories includes +// the path from the topmost directory. Return the number of files +// whose names are appended to s and archive. + +// Same as expand() except fname is an ordinary file +int putsize(String& archive, String& s, const char* fname, int base) { + int result=0; + FILE *f=fopen(fname, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + long len=ftell(f); + if (len>=0) { + static char blk[24]; + sprintf(blk, "%ld\t", len); + archive+=blk; + archive+=(fname+base); + archive+="\r\n"; + s+=fname; + s+="\n"; + ++result; + } + fclose(f); + } + return result; +} + +#ifdef WINDOWS + +int expand(String& archive, String& s, const char* fname, int base) { + int result=0; + DWORD attr=GetFileAttributes(fname); + if ((attr != 0xFFFFFFFF) && (attr & FILE_ATTRIBUTE_DIRECTORY)) { + WIN32_FIND_DATA ffd; + String fdir(fname); + fdir+="/*"; + HANDLE h=FindFirstFile(fdir.c_str(), &ffd); + while (h!=INVALID_HANDLE_VALUE) { + if (!equals(ffd.cFileName, ".") && !equals(ffd.cFileName, "..")) { + String d(fname); + d+="/"; + d+=ffd.cFileName; + result+=expand(archive, s, d.c_str(), base); + } + if (FindNextFile(h, &ffd)!=TRUE) break; + } + FindClose(h); + } + else // ordinary file + result=putsize(archive, s, fname, base); + return result; +} + +#else +#ifdef UNIX + +int expand(String& archive, String& s, const char* fname, int base) { + int result=0; + struct stat sb; + if (stat(fname, &sb)<0) return 0; + + // If a regular file and readable, get file size + if (sb.st_mode & S_IFREG && sb.st_mode & 0400) + result+=putsize(archive, s, fname, base); + + // If a directory with read and execute permission, traverse it + else if (sb.st_mode & S_IFDIR && sb.st_mode & 0400 && sb.st_mode & 0100) { + DIR *dirp=opendir(fname); + if (!dirp) { + perror("opendir"); + return result; + } + dirent *dp; + while(errno=0, (dp=readdir(dirp))!=0) { + if (!equals(dp->d_name, ".") && !equals(dp->d_name, "..")) { + String d(fname); + d+="/"; + d+=dp->d_name; + result+=expand(archive, s, d.c_str(), base); + } + } + if (errno) perror("readdir"); + closedir(dirp); + } + else printf("%s is not a readable file or directory\n", fname); + return result; +} + +#else // Not WINDOWS or UNIX, ignore directories + +int expand(String& archive, String& s, const char* fname, int base) { + return putsize(archive, s, fname, base); +} + +#endif +#endif + + +// To compress to file1.paq8l: paq8l [-n] file1 [file2...] +// To decompress: paq8l file1.paq8l [output_dir] +int main(int argc, char** argv) { + bool pause=argc<=2; // Pause when done? + try { + + // Get option + bool doExtract=false; // -d option + if (argc>1 && argv[1][0]=='-' && argv[1][1] && !argv[1][2]) { + if (argv[1][1]>='0' && argv[1][1]<='9') + level=argv[1][1]-'0'; + else if (argv[1][1]=='d') + doExtract=true; + else + quit("Valid options are -0 through -9 or -d\n"); + --argc; + ++argv; + pause=false; + } + + // Print help message + if (argc<2) { + printf(PROGNAME " archiver (C) 2006, Matt Mahoney et al.\n" + "Free under GPL, http://www.gnu.org/licenses/gpl.txt\n\n" +#ifdef WINDOWS + "To compress or extract, drop a file or folder on the " + PROGNAME " icon.\n" + "The output will be put in the same folder as the input.\n" + "\n" + "Or from a command window: " +#endif + "To compress:\n" + " " PROGNAME " -level file (compresses to file." PROGNAME ")\n" + " " PROGNAME " -level archive files... (creates archive." PROGNAME ")\n" + " " PROGNAME " file (level -%d, pause when done)\n" + "level: -0 = store, -1 -2 -3 = faster (uses 35, 48, 59 MB)\n" + "-4 -5 -6 -7 -8 = smaller (uses 133, 233, 435, 837, 1643 MB)\n" +#if defined(WINDOWS) || defined (UNIX) + "You may also compress directories.\n" +#endif + "\n" + "To extract or compare:\n" + " " PROGNAME " -d dir1/archive." PROGNAME " (extract to dir1)\n" + " " PROGNAME " -d dir1/archive." PROGNAME " dir2 (extract to dir2)\n" + " " PROGNAME " archive." PROGNAME " (extract, pause when done)\n" + "\n" + "To view contents: more < archive." PROGNAME "\n" + "\n", + DEFAULT_OPTION); + quit(); + } + + FILE* archive=0; // compressed file + int files=0; // number of files to compress/decompress + Array fname(1); // file names (resized to files) + Array fsize(1); // file lengths (resized to files) + + // Compress or decompress? Get archive name + Mode mode=COMPRESS; + String archiveName(argv[1]); + { + const int prognamesize=strlen(PROGNAME); + const int arg1size=strlen(argv[1]); + if (arg1size>prognamesize+1 && argv[1][arg1size-prognamesize-1]=='.' + && equals(PROGNAME, argv[1]+arg1size-prognamesize)) { + mode=DECOMPRESS; + } + else if (doExtract) + mode=DECOMPRESS; + else { + archiveName+="."; + archiveName+=PROGNAME; + } + } + + // Compress: write archive header, get file names and sizes + String filenames; + if (mode==COMPRESS) { + + // Expand filenames to read later. Write their base names and sizes + // to archive. + String header_string; + for (int i=1; i0 && name[len-1]=='/') // remove trailing / + name[--len]=0; + int base=len-1; + while (base>=0 && name[base]!='/') --base; // find last / + ++base; + if (base==0 && len>=2 && name[1]==':') base=2; // chop "C:" + int expanded=expand(header_string, filenames, name.c_str(), base); + if (!expanded && (i>1||argc==2)) + printf("%s: not found, skipping...\n", name.c_str()); + files+=expanded; + } + + // If archive doesn't exist and there is at least one file to compress + // then create the archive header. + if (files<1) quit("Nothing to compress\n"); +// archive=fopen(archiveName.c_str(), "rb"); +// if (archive) +// printf("%s already exists\n", archiveName.c_str()), quit(); + archive=fopen(archiveName.c_str(), "wb+"); + if (!archive) perror(archiveName.c_str()), quit(); + fprintf(archive, PROGNAME " -%d\r\n%s\x1A", + level, header_string.c_str()); + printf("Creating archive %s with %d file(s)...\n", + archiveName.c_str(), files); + + // Fill fname[files], fsize[files] with input filenames and sizes + fname.resize(files); + fsize.resize(files); + char *p=&filenames[0]; + rewind(archive); + getline(archive); + for (int i=0; i=0); + fname[i]=p; + while (*p!='\n') ++p; + assert(p-filenames.c_str()9) level=DEFAULT_OPTION; + + // Fill fname[files], fsize[files] with output file names and sizes + while (getline(archive)) ++files; // count files + printf("Extracting %d file(s) from %s -%d\n", files, + archiveName.c_str(), level); + long header_size=ftell(archive); + filenames.resize(header_size+4); // copy of header + rewind(archive); + fread(&filenames[0], 1, header_size, archive); + fname.resize(files); + fsize.resize(files); + char* p=&filenames[0]; + while (*p && *p!='\r') ++p; // skip first line + ++p; + for (int i=0; i=0 && level<=9); + buf.setsize(MEM*8); + + // Compress or decompress files + assert(fname.size()==files); + assert(fsize.size()==files); + long total_size=0; // sum of file sizes + for (int i=0; i %ld\n", total_size, en.size()); + } + + // Decompress files to dir2: paq8l -d dir1/archive.paq8l dir2 + // If there is no dir2, then extract to dir1 + // If there is no dir1, then extract to . + else { + assert(argc>=2); + String dir(argc>2?argv[2]:argv[1]); + if (argc==2) { // chop "/archive.paq8l" + int i; + for (i=dir.size()-2; i>=0; --i) { + if (dir[i]=='/' || dir[i]=='\\') { + dir[i]=0; + break; + } + if (i==1 && dir[i]==':') { // leave "C:" + dir[i+1]=0; + break; + } + } + if (i==-1) dir="."; // "/" not found + } + dir=dir.c_str(); + if (dir[0] && (dir.size()!=3 || dir[1]!=':')) dir+="/"; + for (int i=0; i5$mrRt=5VLG$39Pnt`6L2>!8gX?S{SN=)j$B>aJW$;l}U{QiUZ zRAw?hE*Vep*@I6?i8^Q`AtA2$zoDs40DvbD47ky^{FnP5m;zwHNFc}x!2c^_{|-6~ z&@T=L09dfU@?ZL&?Ea$%{ZHP&zwCeV|1aSG+W%ww3;(GL0KosR{8#zE`u`>Wwf-kw z!~byqzv8`A8u7m;{9D`qlh=Rh`M-e$08IV$Gx@KM{}=zK{?fnn|K&&iA3xLo`u(T4 z|6HU0`26RX{|m?fK)b0C(G>7EC_w@Nf;aC{Z+^cBh;V|M8SCfZsDIO4>w>x7X9AJ)PR+L&QIiALaE0}`A1wRLqmQc`= zIhca`9%LWKGdfxyOw{#LjapFp8fb8^c@G7D04D3)rVy(5IwXtN2!Zk*>GDw_vq?(nc->lTTCI>iT!7afydifZfvh|Rg}0ML=H;Rdci=`)oiHIR!K=K zRAOs%=ejSIgM*B#g>mE`t!!=AgtBLragVqqaHrebPd4m+R+n=imVoX|PN&8ljxzs5 z$(EDiCQHTv%YpPrO%m0CO}xWuDJiW!j&KH0<{4qulh11ifmhUNtAPZ|McKJ#T4ADT z9zz>5#eXwen+Dh&mI{Ou0r8NHG)(%g?<2JGQx7pCBZ zqQ)O0L)I<;!w~aCj_2@HLjexux#Ka;11X!|$9S&jbp;*`*-7ePKuofwT%1*MH$<%6 z0Oo5(ixhJ$qxHpsYu{5uO3~T+oLV#X=#yXLovK`Xzti!*Nbv5BDv!19vs*OHTn4A2`(x6yiBEL$ttF?fgPE#8gMXg z`-MafxjJ%SL$rN=cW4O>$WcM_sB`RDOZL?6s~d%)q}{H0W{{KY&%1d6(~ydmXTbUc zZC+1*2-d-aQ+7^2V>v%#iccIBs&?D4g*ocnP&U|+n$aVhEYGsiltkfsE+0!0Y@y+` z+GZVMU{Z$0$_l)LDTaVD)ml$;3p6)AD&{JI1Bu+;A|TiY4+fEi7G|-@QBe>Wck@fw35{`RPb22tg9f2gilOzRTlCEo3NG9;O0sDTIRYu%eb4+DfEI7%3e+U3wqhDL>66*X>lQoxRI zeaV8iO!;mgXm&2_dlCv$qFJ0O>k1TF>5;A~wE=AkBpI5(%0DX+Xqq(Z&sI8&)p1%< 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 Cl;n^A literal 0 HcmV?d00001 diff --git a/paq9a.cpp b/paq9a.cpp new file mode 100755 index 0000000..58552af --- /dev/null +++ b/paq9a.cpp @@ -0,0 +1,1222 @@ +/* paq9a archiver, Dec. 31, 2007 (C) 2007, Matt Mahoney + + LICENSE + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details at + Visit . + +paq9a is an experimental file compressor and archiver. Usage: + + paq9a {a|x|l} archive [[-opt] files...]... + +Commands: + + a = create archive and compress named files. + x = extract from archive. + l = list contents. + +Archives are "solid". You can only create new archives. You cannot +modify existing archives. File names are stored and extracted exactly as +named when the archive is created, but you have the option to rename them +during extraction. Files are never clobbered. + +The "a" command creates a new archive and adds the named files. +Wildcards are permitted if compiled with g++. Options +and filenames may be in any order. Options apply only to filenames +after the option, and override previous options. Options are: + + -s = store without compression. + -c = compress (default). + -1 through -9 selects memory level from 18 MB to 1.5 GB Default is -7 + using 405 MB. The memory option must be set before the first file. + Decompression requires the same amount of memory. + +For example: + + paq9a a foo.paq9a a.txt -3 -s b.txt -c c.txt tmp/d.txt /tmp/e.txt + +creates the archive foo.paq9a with 5 files. The file b.txt is +stored without compression. The other 4 files are compressed +at memory level 3. Extraction requires the same memory as compression. + +If any named file does not exist, then it is omitted from the archive +with a warning and the remaining files are added. An existing +archive cannot be overwritten. There must be at least one filename on +the command line. + +The "x" command extracts the archive contents, creating files exactly +as named when the archive was created. Files cannot be overwritten. +If a file already exists or cannot be created, then it is skipped. +For example, "tmp/d.txt" would be skipped if either the current +directory does not have a subdirectory tmp, or tmp is write +protected, or tmp/d.txt already exists. + +If "x" is followed by one or more file names, then the output files +are renamed in the order they were added to the archive and any remaining +contents are extracted without renaming. For example: + + paq9a x foo.paq9a x.txt y.txt + +would extract a.txt to x.txt and b.txt to y.txt, then extract c.txt, +tmp/d.txt and /tmp/e.txt. If the command line has more filenames than +the archive then the extra arguments are ignored. Options are not +allowed. + +The "l" (letter l) command lists the contents. Any extra arguments +are ignored. + +Any other command, or no command, displays a help message. + + +ARCHIVE FORMAT + + "lPq" 1 mem [filename {'\0' mode usize csize contents}...]... + +The first 4 bytes are "lPq\x01" (1 is the version number). + +mem is a digit '1' through '9', where '9' uses the most memory (1.5 GB). + +A file is stored as one or more blocks. The filename is stored +only in the first block as a NUL terminated string. Subsequent +blocks start with a 0. + +The mode is 's' if the block is stored and 'c' if compressed. + +usize = uncompressed size as a 4 byte big-endian number (MSB first). + +csize = compressed size as a 4 byte big-endian number. + +The contents is copied from the file itself if mode is 's' or the +compressed contents otherwise. Its length is exactly csize bytes. + + +COMPRESSED FORMAT + +Files are preprocessed with LZP and then compressed with a context +mixing compressor and arithmetic coded one bit at a time. Model +contents are maintained across files. + +The LZP stage predicts the next byte by matching the current context +(order 12 or higher) to a rotating buffer. If a match is found +then the next byte after the match is predicted. If the next byte +matches the prediction, then a 1 bit is coded and the context is extended. +Otherwise a 0 is coded followed by 8 bits of the actual byte in MSB to +LSB order. + +A 1 bit is modeled using the match length as context, then refined +in 3 stages using sucessively longer contexts. The predictions are +adjusted by 2 input neurons selected by a context hash with the second +input fixed. + +If the LZP prediction is missed, then the literal is coded using a chain +of predicions which are mixed using neurons, where one input is the +previous prediction and the second input is the prediction given the +current context. The current context is mapped to an 8 bit state +representing the bit history, the sequence of bits previously observed +in that context. The bit history is used both to select the neuron +and is mapped to a prediction that provides the second input. In addition, +if the known bits of the current byte match the LZP incorrectly predicted +byte, then this fact is used to select one of 2 sets of neurons (512 total). + +The contexts, in order, are sparse order-1 with gaps of 3, 2, and 1 +byte, then orders 1 through 6, then word orders 0 and 1, where a word +is a sequenece of case insensitive letters (useful for compressing text). +Contexts longer than 1 are hashed. Order-n contexts consist of a hash +of the last n bytes plus the 0 to 7 known bits of the current byte. +The order 6 context and the word order 0 and 1 contexts also include +the LZP predicted byte. + +All mixing is in the logistic or "stretched" domain: stretch(p) = ln(p/(1-p)), +then "squashed" by the inverse function: squash(p) = 1/(1 + exp(-p)) before +arithmetic coding. A 2 input neuron has 2 weights (w0 and w1) +selected by context. Given inputs x0 and x1 (2 predictions, or one +prediction and a constant), the output prediction is computed: +p = w0*x0 + w1*x1. If the actual bit is y, then the weights are updated +to minimize its coding cost: + + error = y - squash(p) + w0 += x0 * error * L + w1 += x1 * error * L + +where L is the learning rate, normally 1/256, but increased by a factor +of 4 an 2 for the first 2 training cycles (using the 2 low bits +of w0 as a counter). In the implementation, p is represented by a fixed +point number with a 12 bit fractional part in the linear domain (0..4095) +and 8 bits in the logistic domain (-2047..2047 representing -8..8). +Weights are scaled by 24 bits. Both weights are initialized to 1/2, +expecting 2 probabilities, weighted equally). However, when one input +(x0) is fixed, its weight (w0) is initialized to 0. + +A bit history represents the sequence of 0 and 1 bits observed in a given +context. An 8 bit state represents all possible sequences up to 4 bits +long. Longer sequences are represented by a count of 0 and 1 bits, plus +an indicator of the most recent bit. If counts grow too large, then the +next state represents a pair of smaller counts with about the same ratio. +The state table is the same as used in PAQ8 (all versions) and LPAQ1. + +A state is mapped to a prediction by using a table. A table entry +contains 2 values, p, initialized to 1/2, and n, initialized to 0. +The output prediciton is p (in the linear domain, not stretched). +If the actual bit is y, then the entry is updated: + + error = y - p + p += error/(n + 1.5) + if n < limit then n += 1 + +In practice, p is scaled by 22 bits, and n is 10 bits, packed into +one 32 bit integer. The limit is 255. + +Every 4 bits, contexts are mapped to arrays of 15 states using a +hash table. The first element is the bit history for the current +context ending on a half byte boundary, followed by all possible +contexts formed by appending up to 3 more bits. + +A hash table accepts a 32 bit context, which must be a hash if +longer than 4 bytes. The input is further hashed and divided into +an index (depending on the table size, a power of 2), and an 8 bit +checksum which is stored in the table and used to detect collisions +(not perfectly). A lookup tests 3 adjacent locations within a single +64 byte cache line, and if a matching checksum is not found, then the +entry with the smallest value in the first data element is replaced +(LFU replacement policy). This element represents a bit history +for a context ending on a half byte boundary. The states are ordered +so that larger values represent larger total bit counts, which +estimates the likelihood of future use. The initial state is 0. + +Memory is allocated from MEM = pow(2, opt+22) bytes, where opt is 1 through +9 (user selected). Of this, MEM/2 is for the hash table for storing literal +context states, MEM/8 for the rotating LZP buffer, and MEM/8 for a +hash table of pointers into the buffer, plus 12 MB for miscellaneous data. +Total memory usage is 0.75*MEM + 12 MB. + + +ARITHMETIC CODING + +The arithmetic coder codes a bit with probability p using log2(1/p) bits. +Given input string y, the output is a binary fraction x such that +P(< y) <= x < P(<= y) where P(< y) means the total probability of all inputs +lexicographically less than y and P(<= y) = P(< y) + P(y). Note that one +can always find x with length at most log2(P(y)) + 1 bits. + +x can be computed efficiently by maintaining a range, low <= x < high +(initially 0..1) and expressing P(y) as a product of predictions: +P(y) = P(y1) P(y2|y1) P(y3|y1y2) P(y4|y1y2y3) ... P(yn|y1y2...yn-1) +where the term P(yi|y0y1...yi-1) means the probability that yi is 1 +given the context y1...yi-1, the previous i-1 bits of y. For each +prediction p, the range is split in proportion to the probabilities +of 0 and 1, then updated by taking the half corresponding to the actual +bit y as the new range, i.e. + + mid = low + (high - low) * p(y = 1) + if y = 0 then (low, high) := (mid, high) + if y = 1 then (low, high) := (low, mid) + +As low and high approach each other, the high order bits of x become +known (because they are the same throughout the range) and can be +output immediately. + +For decoding, the range is split as before and the range is updated +to the half containing x. The corresponding bit y is used to update +the model. Thus, the model has the same knowledge for coding and +decoding. + +*/ + +#include +#include +#include +#include +#include +#define NDEBUG // remove for debugging +#include + +int allocated=0; // Total memory allocated by alloc() + +// Create an array p of n elements of type T +template void alloc(T*&p, int n) { + p=(T*)calloc(n, sizeof(T)); + if (!p) printf("Out of memory\n"), exit(1); + allocated+=n*sizeof(T); +} + +// 8, 16, 32 bit unsigned types (adjust as appropriate) +typedef unsigned char U8; +typedef unsigned short U16; +typedef unsigned int U32; + +///////////////////////////// Squash ////////////////////////////// + +// return p = 1/(1 + exp(-d)), d scaled by 8 bits, p scaled by 12 bits +class Squash { + short tab[4096]; +public: + Squash(); + int operator()(int d) { + d+=2048; + if (d<0) return 0; + else if (d>4095) return 4095; + else return tab[d]; + } +} squash; + +Squash::Squash() { + static const int t[33]={ + 1,2,3,6,10,16,27,45,73,120,194,310,488,747,1101, + 1546,2047,2549,2994,3348,3607,3785,3901,3975,4022, + 4050,4068,4079,4085,4089,4092,4093,4094}; + for (int i=-2048; i<2048; ++i) { + int w=i&127; + int d=(i>>7)+16; + tab[i+2048]=(t[d]*(128-w)+t[(d+1)]*w+64) >> 7; + } +} + +//////////////////////////// Stretch /////////////////////////////// + +// Inverse of squash. stretch(d) returns ln(p/(1-p)), d scaled by 8 bits, +// p by 12 bits. d has range -2047 to 2047 representing -8 to 8. +// p has range 0 to 4095 representing 0 to 1. + +class Stretch { + short t[4096]; +public: + Stretch(); + int operator()(int p) const { + assert(p>=0 && p<4096); + return t[p]; + } +} stretch; + +Stretch::Stretch() { + int pi=0; + for (int x=-2047; x<=2047; ++x) { // invert squash() + int i=squash(x); + for (int j=pi; j<=i; ++j) + t[j]=x; + pi=i+1; + } + t[4095]=2047; +} + +///////////////////////////// ilog ////////////////////////////// + +// ilog(x) = round(log2(x) * 16), 0 <= x < 64K +class Ilog { + U8* t; +public: + int operator()(U16 x) const {return t[x];} + Ilog(); +} ilog; + +// Compute lookup table by numerical integration of 1/x +Ilog::Ilog() { + alloc(t, 65536); + U32 x=14155776; + for (int i=2; i<65536; ++i) { + x+=774541002/(i*2-1); // numerator is 2^29/ln 2 + t[i]=x>>24; + } +} + +// llog(x) accepts 32 bits +inline int llog(U32 x) { + if (x>=0x1000000) + return 256+ilog(x>>16); + else if (x>=0x10000) + return 128+ilog(x>>8); + else + return ilog(x); +} + +///////////////////////// state table //////////////////////// + +// State table: +// nex(state, 0) = next state if bit y is 0, 0 <= state < 256 +// nex(state, 1) = next state if bit y is 1 +// +// States represent a bit history within some context. +// State 0 is the starting state (no bits seen). +// States 1-30 represent all possible sequences of 1-4 bits. +// States 31-252 represent a pair of counts, (n0,n1), the number +// of 0 and 1 bits respectively. If n0+n1 < 16 then there are +// two states for each pair, depending on if a 0 or 1 was the last +// bit seen. +// If n0 and n1 are too large, then there is no state to represent this +// pair, so another state with about the same ratio of n0/n1 is substituted. +// Also, when a bit is observed and the count of the opposite bit is large, +// then part of this count is discarded to favor newer data over old. + +static const U8 State_table[256][2]={ +{ 1, 2},{ 3, 5},{ 4, 6},{ 7, 10},{ 8, 12},{ 9, 13},{ 11, 14}, // 0 +{ 15, 19},{ 16, 23},{ 17, 24},{ 18, 25},{ 20, 27},{ 21, 28},{ 22, 29}, // 7 +{ 26, 30},{ 31, 33},{ 32, 35},{ 32, 35},{ 32, 35},{ 32, 35},{ 34, 37}, // 14 +{ 34, 37},{ 34, 37},{ 34, 37},{ 34, 37},{ 34, 37},{ 36, 39},{ 36, 39}, // 21 +{ 36, 39},{ 36, 39},{ 38, 40},{ 41, 43},{ 42, 45},{ 42, 45},{ 44, 47}, // 28 +{ 44, 47},{ 46, 49},{ 46, 49},{ 48, 51},{ 48, 51},{ 50, 52},{ 53, 43}, // 35 +{ 54, 57},{ 54, 57},{ 56, 59},{ 56, 59},{ 58, 61},{ 58, 61},{ 60, 63}, // 42 +{ 60, 63},{ 62, 65},{ 62, 65},{ 50, 66},{ 67, 55},{ 68, 57},{ 68, 57}, // 49 +{ 70, 73},{ 70, 73},{ 72, 75},{ 72, 75},{ 74, 77},{ 74, 77},{ 76, 79}, // 56 +{ 76, 79},{ 62, 81},{ 62, 81},{ 64, 82},{ 83, 69},{ 84, 71},{ 84, 71}, // 63 +{ 86, 73},{ 86, 73},{ 44, 59},{ 44, 59},{ 58, 61},{ 58, 61},{ 60, 49}, // 70 +{ 60, 49},{ 76, 89},{ 76, 89},{ 78, 91},{ 78, 91},{ 80, 92},{ 93, 69}, // 77 +{ 94, 87},{ 94, 87},{ 96, 45},{ 96, 45},{ 48, 99},{ 48, 99},{ 88,101}, // 84 +{ 88,101},{ 80,102},{103, 69},{104, 87},{104, 87},{106, 57},{106, 57}, // 91 +{ 62,109},{ 62,109},{ 88,111},{ 88,111},{ 80,112},{113, 85},{114, 87}, // 98 +{114, 87},{116, 57},{116, 57},{ 62,119},{ 62,119},{ 88,121},{ 88,121}, // 105 +{ 90,122},{123, 85},{124, 97},{124, 97},{126, 57},{126, 57},{ 62,129}, // 112 +{ 62,129},{ 98,131},{ 98,131},{ 90,132},{133, 85},{134, 97},{134, 97}, // 119 +{136, 57},{136, 57},{ 62,139},{ 62,139},{ 98,141},{ 98,141},{ 90,142}, // 126 +{143, 95},{144, 97},{144, 97},{ 68, 57},{ 68, 57},{ 62, 81},{ 62, 81}, // 133 +{ 98,147},{ 98,147},{100,148},{149, 95},{150,107},{150,107},{108,151}, // 140 +{108,151},{100,152},{153, 95},{154,107},{108,155},{100,156},{157, 95}, // 147 +{158,107},{108,159},{100,160},{161,105},{162,107},{108,163},{110,164}, // 154 +{165,105},{166,117},{118,167},{110,168},{169,105},{170,117},{118,171}, // 161 +{110,172},{173,105},{174,117},{118,175},{110,176},{177,105},{178,117}, // 168 +{118,179},{110,180},{181,115},{182,117},{118,183},{120,184},{185,115}, // 175 +{186,127},{128,187},{120,188},{189,115},{190,127},{128,191},{120,192}, // 182 +{193,115},{194,127},{128,195},{120,196},{197,115},{198,127},{128,199}, // 189 +{120,200},{201,115},{202,127},{128,203},{120,204},{205,115},{206,127}, // 196 +{128,207},{120,208},{209,125},{210,127},{128,211},{130,212},{213,125}, // 203 +{214,137},{138,215},{130,216},{217,125},{218,137},{138,219},{130,220}, // 210 +{221,125},{222,137},{138,223},{130,224},{225,125},{226,137},{138,227}, // 217 +{130,228},{229,125},{230,137},{138,231},{130,232},{233,125},{234,137}, // 224 +{138,235},{130,236},{237,125},{238,137},{138,239},{130,240},{241,125}, // 231 +{242,137},{138,243},{130,244},{245,135},{246,137},{138,247},{140,248}, // 238 +{249,135},{250, 69},{ 80,251},{140,252},{249,135},{250, 69},{ 80,251}, // 245 +{140,252},{ 0, 0},{ 0, 0},{ 0, 0}}; // 252 +#define nex(state,sel) State_table[state][sel] + +//////////////////////////// StateMap ////////////////////////// + +// A StateMap maps a context to a probability. Methods: +// +// Statemap sm(n) creates a StateMap with n contexts using 4*n bytes memory. +// sm.p(cx, limit) converts state cx (0..n-1) to a probability (0..4095) +// that the next updated bit y=1. +// limit (1..1023, default 255) is the maximum count for computing a +// prediction. Larger values are better for stationary sources. +// sm.update(y) updates the model with actual bit y (0..1). + +class StateMap { +protected: + const int N; // Number of contexts + int cxt; // Context of last prediction + U32 *t; // cxt -> prediction in high 22 bits, count in low 10 bits + static int dt[1024]; // i -> 16K/(i+3) +public: + StateMap(int n=256); + + // update bit y (0..1) + void update(int y, int limit=255) { + assert(cxt>=0 && cxt>10; // count, prediction + if (n>3)*dt[n]&0xfffffc00; + } + + // predict next bit in context cx + int p(int cx) { + assert(cx>=0 && cx>20; + } +}; + +int StateMap::dt[1024]={0}; + +StateMap::StateMap(int n): N(n), cxt(0) { + alloc(t, N); + for (int i=0; i=0 && cx>16)+(x2=p2)*(wt[cxt+1]>>16)+128>>8; + } + void update(int y) { + assert(y==0 || y==1); + int err=((y<<12)-squash(pr)); + if ((wt[cxt]&3)<3) + err*=4-(++wt[cxt]&3); + err=err+8>>4; + wt[cxt]+=x1*err&-4; + wt[cxt+1]+=x2*err; + } +}; + +Mix::Mix(int n): N(n), x1(0), x2(0), cxt(0), pr(0) { + alloc(wt, n*2); + for (int i=0; i h(n) - create using n bytes n and B must be +// powers of 2 with n >= B*4, and B >= 2. +// h[i] returns array [1..B-1] of bytes indexed by i, creating and +// replacing another element if needed. Element 0 is the +// checksum and should not be modified. + +template +class HashTable { + U8* t; // table: 1 element = B bytes: checksum priority data data + const U32 N; // size in bytes +public: + HashTable(int n); + ~HashTable(); + U8* operator[](U32 i); +}; + +template +HashTable::HashTable(int n): t(0), N(n) { + assert(B>=2 && (B&B-1)==0); + assert(N>=B*4 && (N&N-1)==0); + alloc(t, N+B*4+64); + t+=64-int(((long)t)&63); // align on cache line boundary +} + +template +inline U8* HashTable::operator[](U32 i) { + i*=123456791; + i=i<<16|i>>16; + i*=234567891; + int chk=i>>24; + i=i*B&N-B; + if (t[i]==chk) return t+i; + if (t[i^B]==chk) return t+(i^B); + if (t[i^B*2]==chk) return t+(i^B*2); + if (t[i+1]>t[i+1^B] || t[i+1]>t[i+1^B*2]) i^=B; + if (t[i+1]>t[i+1^B^B*2]) i^=B^B*2; + memset(t+i, 0, B); + t[i]=chk; + return t+i; +} + +template +HashTable::~HashTable() { + int c=0, c0=0; + for (U32 i=0; i %1.4f%% full, %1.4f%% utilized of %d KiB\n", + B, 100.0*c0*B/N, 100.0*c/N, N>>10); +} + +////////////////////////// LZP ///////////////////////// + +U32 MEM=1<<29; // Global memory limit, 1 << 22+(memory option) + +// LZP predicts the next byte and maintains context. Methods: +// c() returns the predicted byte for the next update, or -1 if none. +// p() returns the 12 bit probability (0..4095) that c() is next. +// update(ch) updates the model with actual byte ch (0..255). +// c(i) returns the i'th prior byte of context, i > 0. +// c4() returns the order 4 context, shifted into the LSB. +// c8() returns a hash of the order 8 context, shifted 4 bits into LSB. +// word0, word1 are hashes of the current and previous word (a-z). + +class LZP { +private: + const int N, H; // buf, t sizes + enum {MINLEN=12}; // minimum match length + U8* buf; // Rotating buffer of size N + U32* t; // hash table of pointers in high 24 bits, state in low 8 bits + int match; // start of match + int len; // length of match + int pos; // position of next ch to write to buf + U32 h; // context hash + U32 h1; // hash of last 8 byte updates, shifting 4 bits to MSB + U32 h2; // last 4 updates, shifting 8 bits to MSB + StateMap sm1; // len+offset -> p + APM a1, a2, a3; // p, context -> p + int literals, matches; // statistics +public: + U32 word0, word1; // hashes of last 2 words (case insensitive a-z) + LZP(); + ~LZP(); + int c(); // predicted char + int c(int i);// context + int c4() {return h2;} // order 4 context, c(1) in LSB + int c8() {return h1;} // hashed order 8 context + int p(); // probability that next char is c() * 4096 + void update(int ch); // update model with actual char ch +}; + +// Initialize +LZP::LZP(): N(MEM/8), H(MEM/32), + match(-1), len(0), pos(0), h(0), h1(0), h2(0), + sm1(0x200), a1(0x10000), a2(0x40000), a3(0x100000), + literals(0), matches(0), word0(0), word1(0) { + assert(MEM>0); + assert(H>0); + alloc(buf, N); + alloc(t, H); +} + +// Print statistics +LZP::~LZP() { + int c=0; + for (int i=0; i>8, pos>10); + printf("LZP %d literals, %d matches (%1.4f%% matched)\n", + literals, matches, + literals+matches>0?100.0*matches/(literals+matches):0.0); +} + +// Predicted next byte, or -1 for no prediction +inline int LZP::c() { + return len>=MINLEN ? buf[match&N-1] : -1; +} + +// Return i'th byte of context (i > 0) +inline int LZP::c(int i) { + assert(i>0); + return buf[pos-i&N-1]; +} + +// Return prediction that c() will be the next byte (0..4095) +int LZP::p() { + if (len28) cxt=28+(len>=32)+(len>=64)+(len>=128); + int pc=c(); + int pr=sm1.p(cxt); + pr=stretch(pr); + pr=a1.pp(2048, pr*2, h2*256+pc&0xffff)*3+pr>>2; + pr=a2.pp(2048, pr*2, h1*(11<<6)+pc&0x3ffff)*3+pr>>2; + pr=a3.pp(2048, pr*2, h1*(7<<4)+pc&0xfffff)*3+pr>>2; + pr=squash(pr); + return pr; +} + +// Update model with predicted byte ch (0..255) +void LZP::update(int ch) { + int y=c()==ch; // 1 if prediction of ch was right, else 0 + h1=h1*(3<<4)+ch+1; // update context hashes + h2=h2<<8|ch; + h=h*(5<<2)+ch+1&H-1; + if (len>=MINLEN) { + sm1.update(y); + a1.update(y); + a2.update(y); + a3.update(y); + } + if (isalpha(ch)) + word0=word0*(29<<2)+tolower(ch); + else if (word0) + word1=word0, word0=0; + buf[pos&N-1]=ch; // update buf + ++pos; + if (y) { // extend match + ++len; + ++match; + ++matches; + } + else { // find new match, try order 6 context first + ++literals; + y=0; + len=1; + match=t[h]; + if (!((match^pos)&N-1)) --match; + while (len<=128 && buf[match-len&N-1]==buf[pos-len&N-1]) ++len; + --len; + } + t[h]=pos; +} + +LZP* lzp=0; + +//////////////////////////// Predictor ///////////////////////// + +// A Predictor estimates the probability that the next bit of +// uncompressed data is 1. Methods: +// Predictor() creates. +// p() returns P(1) as a 12 bit number (0-4095). +// update(y) trains the predictor with the actual bit (0 or 1). + +class Predictor { + enum {N=11}; // number of contexts + int c0; // last 0-7 bits with leading 1, 0 before LZP flag + int nibble; // last 0-3 bits with leading 1 (1..15) + int bcount; // number of bits in c0 (0..7) + HashTable<16> t; // context -> state + StateMap sm[N]; // state -> prediction + U8* cp[N]; // i -> state array of bit histories for i'th context + U8* sp[N]; // i -> pointer to bit history for i'th context + Mix m[N-1]; // combines 2 predictions given a context + APM a1, a2, a3; // adjusts a prediction given a context + U8* t2; // order 1 contexts -> state + +public: + Predictor(); + int p(); + void update(int y); +}; + +// Initialize +Predictor::Predictor(): + c0(0), nibble(1), bcount(0), t(MEM/2), + a1(0x10000), a2(0x10000), a3(0x10000) { + alloc(t2, 0x40000); + for (int i=0; i=0 && bcount<8); + assert(c0>=0 && c0<256); + assert(nibble>=1 && nibble<=15); + if (c0==0) + c0=1-y; + else { + *sp[0]=nex(*sp[0], y); + sm[0].update(y); + for (int i=1; i=16) nibble=1; + a1.update(y); + a2.update(y); + a3.update(y); + } +} + +// Predict next bit +int Predictor::p() { + assert(lzp); + if (c0==0) + return lzp->p(); + else { + + // Set context pointers + int pc=lzp->c(); // mispredicted byte + int r=pc+256>>8-bcount==c0; // c0 consistent with mispredicted byte? + U32 c4=lzp->c4(); // last 4 whole context bytes, shifted into LSB + U32 c8=(lzp->c8()<<4)-1; // hash of last 7 bytes with 4 trailing 1 bits + if ((bcount&3)==0) { // nibble boundary? Update context pointers + pc&=-r; + U32 c4p=c4<<8; + if (bcount==0) { // byte boundary? Update order-1 context pointers + cp[0]=t2+(c4>>16&0xff00); + cp[1]=t2+(c4>>8 &0xff00)+0x10000; + cp[2]=t2+(c4 &0xff00)+0x20000; + cp[3]=t2+(c4<<8 &0xff00)+0x30000; + } + cp[4]=t[(c4p&0xffff00)-c0]; + cp[5]=t[(c4p&0xffffff00)*3+c0]; + cp[6]=t[c4*7+c0]; + cp[7]=t[(c8*5&0xfffffc)+c0]; + cp[8]=t[(c8*11&0xffffff0)+c0+pc*13]; + cp[9]=t[lzp->word0*5+c0+pc*17]; + cp[10]=t[lzp->word1*7+lzp->word0*11+c0+pc*37]; + } + + // Mix predictions + r<<=8; + sp[0]=&cp[0][c0]; + int pr=stretch(sm[0].p(*sp[0])); + for (int i=1; i>2; + } + pr=a1.pp(512, pr*2, c0+pc*256&0xffff)*3+pr>>2; // Adjust prediction + pr=a2.pp(512, pr*2, c4<<8&0xff00|c0)*3+pr>>2; + pr=a3.pp(512, pr*2, c4*3+c0&0xffff)*3+pr>>2; + return squash(pr); + } +} + +Predictor* predictor=0; + +/////////////////////////// get4, put4 ////////////////////////// + +// Read/write a 4 byte big-endian number +int get4(FILE* in) { + int r=getc(in); + r=r*256+getc(in); + r=r*256+getc(in); + r=r*256+getc(in); + return r; +} + +void put4(U32 c, FILE* out) { + fprintf(out, "%c%c%c%c", c>>24, c>>16, c>>8, c); +} + +//////////////////////////// Encoder //////////////////////////// + +// An Encoder arithmetic codes in blocks of size BUFSIZE. Methods: +// Encoder(COMPRESS, f) creates encoder for compression to archive f, which +// must be open past any header for writing in binary mode. +// Encoder(DECOMPRESS, f) creates encoder for decompression from archive f, +// which must be open past any header for reading in binary mode. +// code(i) in COMPRESS mode compresses bit i (0 or 1) to file f. +// code() in DECOMPRESS mode returns the next decompressed bit from file f. +// count() should be called after each byte is compressed. +// flush() should be called after compression is done. It is also called +// automatically when a block is written. + +typedef enum {COMPRESS, DECOMPRESS} Mode; +class Encoder { +private: + const Mode mode; // Compress or decompress? + FILE* archive; // Compressed data file + U32 x1, x2; // Range, initially [0, 1), scaled by 2^32 + U32 x; // Decompress mode: last 4 input bytes of archive + enum {BUFSIZE=0x20000}; + static unsigned char* buf; // Compression output buffer, size BUFSIZE + int usize, csize; // Buffered uncompressed and compressed sizes + double usum, csum; // Total of usize, csize + +public: + Encoder(Mode m, FILE* f); + void flush(); // call this when compression is finished + + // Compress bit y or return decompressed bit + int code(int y=0) { + assert(predictor); + int p=predictor->p(); + assert(p>=0 && p<4096); + p+=p<2048; + U32 xmid=x1 + (x2-x1>>12)*p + ((x2-x1&0xfff)*p>>12); + assert(xmid>=x1 && xmidupdate(y); + while (((x1^x2)&0xff000000)==0) { // pass equal leading bytes of range + if (mode==COMPRESS) buf[csize++]=x2>>24; + x1<<=8; + x2=(x2<<8)+255; + if (mode==DECOMPRESS) x=(x<<8)+getc(archive); + } + return y; + } + + // Count one byte + void count() { + assert(mode==COMPRESS); + ++usize; + if (csize>BUFSIZE-256) + flush(); + } +}; +unsigned char* Encoder::buf=0; + +// Create in mode m (COMPRESS or DECOMPRESS) with f opened as the archive. +Encoder::Encoder(Mode m, FILE* f): + mode(m), archive(f), x1(0), x2(0xffffffff), x(0), + usize(0), csize(0), usum(0), csum(0) { + if (mode==DECOMPRESS) { // x = first 4 bytes of archive + for (int i=0; i<4; ++i) + x=(x<<8)+(getc(archive)&255); + csize=4; + } + else if (!buf) + alloc(buf, BUFSIZE); +} + +// Write a compressed block and reinitialize the encoder. The format is: +// uncompressed size (usize, 4 byte, MSB first) +// compressed size (csize, 4 bytes, MSB first) +// compressed data (csize bytes) +void Encoder::flush() { + if (mode==COMPRESS) { + buf[csize++]=x1>>24; + buf[csize++]=255; + buf[csize++]=255; + buf[csize++]=255; + putc(0, archive); + putc('c', archive); + put4(usize, archive); + put4(csize, archive); + fwrite(buf, 1, csize, archive); + usum+=usize; + csum+=csize+10; + printf("%15.0f -> %15.0f" + "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", + usum, csum); + x1=x=usize=csize=0; + x2=0xffffffff; + } +} + +/////////////////////////// paq9a //////////////////////////////// + +// Compress or decompress from in to out, depending on whether mode +// is COMPRESS or DECOMPRESS. A byte c is encoded as a 1 bit if it +// is predicted by LZP, otherwise a 0 followed by 8 bits from MSB to LSB. +void paq9a(FILE* in, FILE* out, Mode mode) { + if (!lzp && !predictor) { + lzp=new LZP; + predictor=new Predictor; + printf("%8d KiB\b\b\b\b\b\b\b\b\b\b\b\b", allocated>>10); + } + if (mode==COMPRESS) { + Encoder e(COMPRESS, out); + int c; + while ((c=getc(in))!=EOF) { + int cp=lzp->c(); + if (c==cp) + e.code(1); + else + for (int i=8; i>=0; --i) + e.code(c>>i&1); + e.count(); + lzp->update(c); + } + e.flush(); + } + else { // DECOMPRESS + int usize=get4(in); + get4(in); // csize + Encoder e(DECOMPRESS, in); + while (usize--) { + int c=lzp->c(); + if (e.code()==0) { + c=1; + while (c<256) c+=c+e.code(); + c&=255; + } + if (out) putc(c, out); + lzp->update(c); + } + } +} + + +///////////////////////////// store /////////////////////////// + +// Store a file in blocks as: {'\0' mode usize csize contents}... +void store(FILE* in, FILE* out) { + assert(in); + assert(out); + + // Store in blocks + const int BLOCKSIZE=0x100000; + static char* buf=0; + if (!buf) alloc(buf, BLOCKSIZE); + bool first=true; + while (true) { + int n=fread(buf, 1, BLOCKSIZE, in); + if (!first && n<=0) break; + fprintf(out, "%c%c", 0, 's'); + put4(n, out); // usize + put4(n, out); // csize + fwrite(buf, 1, n, out); + first=false; + } + + // Close file + fclose(in); +} + +// Write usize == csize bytes of an uncompressed block from in to out +void unstore(FILE* in, FILE* out) { + assert(in); + int usize=get4(in); + int csize=get4(in); + if (usize!=csize) + printf("Bad archive format: usize=%d csize=%d\n", usize, csize); + static char* buf=0; + const int BUFSIZE=0x1000; + if (!buf) alloc(buf, BUFSIZE); + while (csize>0) { + usize=csize; + if (usize>BUFSIZE) usize=BUFSIZE; + if (int(fread(buf, 1, usize, in))!=usize) + printf("Unexpected end of archive\n"), exit(1); + if (out) fwrite(buf, 1, usize, out); + csize-=usize; + } +} + +//////////////////////// Archiving functions //////////////////////// + +const int MAXNAMELEN=1023; // max filename length + +// Return true if the first 4 bytes of in are a valid archive +bool check_archive(FILE* in) { + return getc(in)=='p' && getc(in)=='Q' && getc(in)=='9' && getc(in)==1; +} + +// Open archive and check for valid archive header, exit if bad. +// Set MEM to memory option '1' through '9' +FILE* open_archive(const char* filename) { + FILE* in=fopen(filename, "rb"); + if (!in) + printf("Cannot find archive %s\n", filename), exit(1); + if (!check_archive(in) || (MEM=getc(in))<'1' || MEM>'9') { + fclose(in); + printf("%s: Not a paq9a archive\n", filename); + exit(1); + } + return in; +} + +// Compress filename to out. option is 'c' to compress or 's' to store. +void compress(const char* filename, FILE* out, int option) { + + // Open input file + FILE* in=fopen(filename, "rb"); + if (!in) { + printf("File not found: %s\n", filename); + return; + } + fprintf(out, "%s", filename); + printf("%-40s ", filename); + + // Compress depending on option + if (option=='s') + store(in, out); + else if (option=='c') + paq9a(in, out, COMPRESS); + printf("\n"); +} + +// List archive contents +void list(const char* archive) { + double usum=0, csum=0; // uncompressed and compressed size per file + double utotal=0, ctotal=4; // total size in archive + static char filename[MAXNAMELEN+1]; + int mode=0; + + FILE* in=open_archive(archive); + printf("\npaq9a -%c\n", MEM); + while (true) { + + // Get filename, mode + int c=getc(in); + if (c==EOF) break; + if (c) { // start of new file? Print previous file + if (mode) + printf("%10.0f -> %10.0f %c %s\n", usum, csum, mode, filename); + int len=0; + filename[len++]=c; + while ((c=getc(in))!=EOF && c) + if (lenBUFSIZE) + csize-=fread(buf, 1, BUFSIZE, in); + fread(buf, 1, csize, in); + } + printf("%10.0f -> %10.0f %c %s\n", usum, csum, mode, filename); + utotal+=usum; + ctotal+=csum; + printf("%10.0f -> %10.0f total\n", utotal, ctotal); + fclose(in); +} + +// Extract files given command line arguments +// Input format is: [filename {'\0' mode usize csize contents}...]... +void extract(int argc, char** argv) { + assert(argc>2); + assert(argv[1][0]=='x'); + static char filename[MAXNAMELEN+1]; // filename from archive + + // Open archive + FILE* in=open_archive(argv[2]); + MEM=1<<22+MEM-'0'; + + // Extract files + argc-=3; + argv+=3; + FILE* out=0; + while (true) { // for each block + + // Get filename + int c; + for (int i=0;; ++i) { + c=getc(in); + if (c==EOF) break; + if (i0) fn=argv[0], --argc, ++argv; + if (out) fclose(out); + out=fopen(fn, "rb"); + if (out) { + printf("\nCannot overwrite file, skipping: %s ", fn); + fclose(out); + out=0; + } + else { + out=fopen(fn, "wb"); + if (!out) printf("\nCannot create file: %s ", fn); + } + if (out) { + if (fn==filename) printf("\n%s ", filename); + else printf("\n%s -> %s ", filename, fn); + } + } + + // Extract block + int mode=getc(in); + if (mode=='s') + unstore(in, out); + else if (mode=='c') + paq9a(in, out, DECOMPRESS); + else + printf("\nUnsupported compression mode %c %d at %ld\n", + mode, mode, ftell(in)), exit(1); + } + printf("\n"); + if (out) fclose(out); +} + +// Command line is: paq9a {a|x|l} archive [[-option] files...]... +int main(int argc, char** argv) { + clock_t start=clock(); + + // Check command line arguments + if (argc<3 || argv[1][1] || (argv[1][0]!='a' && argv[1][0]!='x' + && argv[1][0]!='l') || (argv[1][0]=='a' && argc<4) || argv[2][0]=='-') + { + printf("paq9a archiver (C) 2007, Matt Mahoney\n" + "Free software under GPL, http://www.gnu.org/copyleft/gpl.html\n" + "\n" + "To create archive: paq9a a archive [-1..-9] [[-s|-c] files...]...\n" + " -1..-9 = use 18 to 1585 MiB memory (default -7 = 408 MiB)\n" + " -s = store, -c = compress (default)\n" + "To extract files: paq9a x archive [files...]\n" + "To list contents: paq9a l archive\n"); + exit(1); + } + + // Create archive + if (argv[1][0]=='a') { + int option = 'c'; // -c or -s + FILE* out=fopen(argv[2], "rb"); + if (out) printf("Cannot overwrite archive %s\n", argv[2]), exit(1); + out=fopen(argv[2], "wb"); + if (!out) printf("Cannot create archive %s\n", argv[2]), exit(1); + fprintf(out, "pQ9%c", 1); + int i=3; + if (argc>3 && argv[3][0]=='-' && argv[3][1]>='1' && argv[3][1]<='9' + && argv[3][2]==0) { + putc(argv[3][1], out); + MEM=1<<22+argv[3][1]-'0'; + ++i; + } + else + putc('7', out); + for (; i %ld in %1.2f sec\n", ftell(out), + double(clock()-start)/CLOCKS_PER_SEC); + } + + // List archive contents + else if (argv[1][0]=='l') + list(argv[2]); + + // Extract from archive + else if (argv[1][0]=='x') { + extract(argc, argv); + printf("%1.2f sec\n", double(clock()-start)/CLOCKS_PER_SEC); + } + + // Report statistics + delete predictor; + delete lzp; + printf("Used %d KiB memory\n", allocated>>10); + return 0; +} diff --git a/paq9a.exe b/paq9a.exe new file mode 100755 index 0000000000000000000000000000000000000000..ee938e2972eea3ec43882a8ff74749b645d25862 GIT binary patch literal 18432 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 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() \ No newline at end of file diff --git a/pypaqtest.paq b/pypaqtest.paq new file mode 100755 index 0000000..e69de29 diff --git a/readme.txt b/readme.txt new file mode 100755 index 0000000..147e927 --- /dev/null +++ b/readme.txt @@ -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. + diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000..2b5abb2 --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/stanzip.py b/stanzip.py new file mode 100755 index 0000000..fcdd5e9 --- /dev/null +++ b/stanzip.py @@ -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() \ No newline at end of file diff --git a/test.png b/test.png new file mode 100755 index 0000000000000000000000000000000000000000..8722e47213f249d39d568626335c2b1699cbf0fa GIT binary patch literal 2396 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&A1{5*9c;^X_vMh0pC<)F_D=AMbN@eg( zEGfvzFUiSFQYcF;D$dN$GuE@vGuBbaC@Co@w$j(ng)7j@FG|-x{wyLGXb5M4M`SSr z1Aih2Gp?{-p2@(#anjSpF{EP7+iQl53<^943=Zsn{*b9pjjO8c{23hv2BUNDfjaLQ zH83z3vI#IK9AjW&I3nS|&>+Fg!61;#z{rrqqrl+M!^pzWVK}O2Gzdmh!DvPpEel4A j!_gXHv{IyNyFgE^l6}we<(ra#?G*-3S3j3^P6 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() diff --git a/test_Fbrowser.py b/test_Fbrowser.py new file mode 100755 index 0000000..75f7a78 --- /dev/null +++ b/test_Fbrowser.py @@ -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() \ No newline at end of file diff --git a/testmidi.py b/testmidi.py new file mode 100644 index 0000000..7365413 --- /dev/null +++ b/testmidi.py @@ -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_()) \ No newline at end of file