-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathencoder.py
More file actions
executable file
·141 lines (116 loc) · 4.03 KB
/
Copy pathencoder.py
File metadata and controls
executable file
·141 lines (116 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/env python
import sys
import struct
import binascii
import array
import base64
import gmpy
import numpy as np
import matplotlib.pyplot as plt
plt.rcdefaults()
def get_base(symbol):
# TODO: Return the preferred base dependign on how many symbols they are and their
# frequency. Requires some math.
return 2
def get_size_shift(shift_value):
return 1 + int(shift_value/8)
def ffs(x):
"""Returns the index, counting from 0, of the
least significant set bit in `x`.
"""
return (x & -x).bit_length()-1
def encode_charlist(file_string, base):
# get character frequency
freq = {}
for char in file_string:
if char not in freq:
freq[char] = 1
else:
freq[char] += 1
# sort characters by frequency (sort keys by value)
freq_sorted = {k: v for k, v in sorted(
freq.items(), key=lambda x: x[1], reverse=True)}
# encode
charlist = []
last = ''
for i, char in enumerate(freq_sorted):
# shift amount has to start at 1 not zero
print('assign %d with %s and %d' %
(1 << i, char, freq_sorted[char]))
# Change frequency to encoded value now it's sorted
# (encoded value, size in bytes of encoded value)
freq_sorted[char] = (1 << i, get_size_shift(i))
charlist.append(char)
last = char
charlist.append(last)
return (charlist, freq_sorted)
def compress(charlist, freq_sorted):
encoded = bytearray(0)
carry = np.uint8(0)
idx = 0
for i, char in enumerate(file_string):
(encoded_value, size_bytes) = freq_sorted[char]
carry = np.uint8((1 << 7 - idx) | (carry))
idx += gmpy.scan1(encoded_value) + 1
# overflow
while idx >= 8:
idx -= 8
encoded.append(carry)
carry = np.uint8(0)
# todo: make last char work by putting padding idx in header
encoded.append(carry)
header = bytearray()
# for bt in charlist:
# if bt not in range(0, 256):
# print(hex(bt))
header.extend(map(ord, charlist))
header.append(np.uint8(idx))
return (header, encoded, carry)
# FILE SPEC:
# |##############################################################################|
# | Header | Data |
# | Character List | Duplicate last character (END) | Final IDX (1 byte) | Data |
# |##############################################################################|
#
# Character List
# List of symbols in order of occurence. First (most frequent) symbol gets mapped to
# 0x1, second 0x10, third 0x100 and so fourth.
# Duplicate last character
# The final character is duplicated to signal the end of the character list.
# Final IDX
# The file ending may not align on a (byte) bondary. It could end a few bits earlier, so
# the
# final index tells us where it ends.
# All the rest in the file is data
if __name__ == "__main__":
try:
file_in = sys.argv[1]
file_out = sys.argv[2]
except:
print("Usage: encoder.py <INPUT FILE> <OUTPUT FILE>")
print(f"Opening: {file_in} for compression")
try:
f = open(file_in, "r")
file_string = f.read()
f.close()
except:
print("Could not open file: {file_in}")
input_size = len(file_string.encode('utf-8'))
(charlist, freq_sorted) = encode_charlist(file_string, 2)
(header, encoded, carry) = compress(charlist, freq_sorted)
print('Compressed file\nHeader:\n%s\nData:\n%s' %
(header, binascii.hexlify(encoded)))
file = header + encoded
f = open(file_out, 'wb')
f.write(file)
f.close()
output_size = len(file)
print('Compression ratio: %f' % ((input_size/output_size)))
objects = ('Input File', 'Output File')
y_pos = np.arange(len(objects))
results = (input_size, output_size)
plt.bar(y_pos, results, align='center', alpha=0.5)
plt.xticks(y_pos, objects)
plt.ylabel('Size (bytes)')
plt.title('File size (input vs output file)')
plt.show()