Compare commits

...

10 Commits

Author SHA1 Message Date
Masahiko AMANO
d1462470b1 Some funny little change
:)
2022-02-09 19:12:17 +03:00
Masahiko AMANO
afa417d251 A little optimization for table encoding 2021-12-14 13:13:26 +03:00
Masahiko AMANO
52c6c95050 Update README.md 2021-12-11 00:30:00 +03:00
Masahiko AMANO
309521897b Rearranged files
So, now it all works standalone without any dependencies on external resources.
2021-12-11 00:29:21 +03:00
Masahiko AMANO
f63cb6cb59 Minor fixes 2021-12-10 00:45:29 +03:00
Masahiko AMANO
0ed8d1482b Some minor changes 2021-12-09 16:16:33 +03:00
Masahiko AMANO
06e53e7972 Change table encoding to SHICHIRO 2021-12-09 16:15:47 +03:00
Masahiko AMANO
01ff16fddd Update README.md 2021-12-08 16:01:31 +03:00
Masahiko AMANO
fef8f4ec00 Just fixed it all
Back to this project after over a half-year
2021-12-08 15:58:48 +03:00
31d5b1c0f2 Rearranged files and beautified code 2021-05-03 00:45:54 +03:00
14 changed files with 404 additions and 329 deletions

View File

@ -2,15 +2,21 @@
## What's up?
Hey! This is a simple web-service called ![HuffPress](https://img.shields.io/badge/Huff-Press-orange.svg). What it does? Well, actually it *huffpresses* files... I mean, compresses files using the [Huffman compression](https://en.wikipedia.org/wiki/Huffman_coding "Read about it on Wikipedia").
Hey! This is a simple web-service called __HuffPress__. What it does? Well, actually it _huffpresses_ files... I mean, compresses files using the [Huffman compression](https://en.wikipedia.org/wiki/Huffman_coding 'Read about it on Wikipedia').
Also it uses a bit modified [SHICHIRO coding](https://github.com/H1K0/SHICHIRO 'View on GitHub') for compressing and embedding Huffman table.
## What do I need?
- Any web browser
- ![PHP 7+](https://img.shields.io/badge/PHP-7+-blueviolet.svg)
- ![Python 3+](https://img.shields.io/badge/Python-3+-blue.svg)
- `click` library
- ![Python 3.7+](https://img.shields.io/badge/Python-3+-blue.svg)
- `click` python library
## So, how do I use it?
I think it's too easy to explain the usage of this service, but anyway here's [a video guide](https://yadi.sk/i/aiy78bDaoKqoJQ "Watch!").
I think the usage is too easy to explain it, but anyway here's [a video guide](https://yadi.sk/i/aiy78bDaoKqoJQ 'Watch!').
---
<p align=center><i>&copy; Masahiko AMANO a.k.a. H1K0, 2020-2021</i></p>

7
css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
@import url('https://fonts.googleapis.com/css2?family=Epilogue&family=Secular+One&display=swap');
html,body{
html,
body {
width: 100%;
min-height: 100vh;
margin: 0;
@ -15,7 +16,7 @@ body{
flex-direction: column;
justify-content: center;
align-items: center;
background-image: url(bg.webp);
background-image: url(../images/bg.webp);
background-size: cover;
}
header {
@ -37,10 +38,10 @@ main{
display: none;
margin-bottom: 2vw;
background-color: #fff8;
box-shadow: 0 0 .5vw black;
box-shadow: 0 0 0.5vw black;
border-radius: 1.5vw;
min-height: 9vw;
transition: .3s;
transition: 0.3s;
overflow: hidden;
}
main:hover {
@ -55,11 +56,11 @@ h2{
flex-direction: column;
justify-content: center;
align-items: center;
background-color: hsla(270,80%,80%,.6);
background-color: hsla(270, 80%, 80%, 0.6);
border-bottom: 0;
font-family: Secular One;
font-size: 3vw;
text-shadow: 2.5px 2px .5px white;
text-shadow: 2.5px 2px 0.5px white;
cursor: pointer;
}
form {
@ -83,7 +84,9 @@ form{
align-items: center;
z-index: 10;
}
.process,.complete,.error{
.process,
.complete,
.error {
display: none;
padding: 2vw 5vw;
background-color: white;
@ -97,13 +100,13 @@ form{
}
h3 {
margin: 0;
margin-top: .5vw;
margin-top: 0.5vw;
font-family: Secular One;
font-size: 3vw;
}
.status {
margin: 0;
margin-top: .5vw;
margin-top: 0.5vw;
font-family: monospace;
font-size: 1.5vw;
}

View File

@ -2,6 +2,8 @@ from os.path import dirname,basename,join,abspath as path
from time import time
from datetime import datetime as dt
import click
from urllib.parse import quote as url
class Log:
def __init__(self, path, name):
@ -20,7 +22,9 @@ class Log:
' | '
f'{msg}\n')
log = Log(join(dirname(__file__), 'hfm.log'), 'hfm-py')
output = ''
def huffman(data):
@ -30,7 +34,8 @@ def huffman(data):
units[c] += 1
else:
units[c] = 1
units,codes=sorted([([u],units[u]) for u in units],key=lambda u:u[1]),dict.fromkeys(units.keys(),'')
codes = dict.fromkeys(units.keys(), '')
units = sorted([([u], units[u]) for u in units], key=lambda u: u[1])
while units: # creating Haffman table
if len(units) > 2:
@ -51,78 +56,79 @@ def huffman(data):
return codes
def tbl(table):
def shichiro_encode(table):
table = ';'.join([f'{k};{table[k]}' for k in table]).split(';')
byts=[]
bits = ''
for i in range(len(table)):
if i % 2:
num=table[i]
code = table[i]
bits += bin(len(code))[2:].rjust(8,'0')
bits += code
else:
num=bin(int(table[i]))[2:]
while len(num)>7:
byts.append(int('1'+num[:7],2))
num=num[7:]
byts.append(int(num,2))
byts.append(8-len(num))
return byts
bits += bin(int(table[i]))[2:].rjust(8, '0')
continue
return bits
def detbl(byts):
def shichiro_decode(byts):
bits=''.join(byts)
dec = []
table = {}
stack = ''
i = 0
while i<len(byts):
if byts[i][0]=='1':
stack+=byts[i][1:]
else:
stack+=byts[i][int(byts[i+1],2):]
dec.append(stack[:])
stack=''
i+=1
i+=1
c = 0
while i < len(bits) and len(bits) - i >= 8:
if c % 2 == 0:
dec.append(bits[i:i+8])
i += 8
c += 1
continue
bitlen = int(bits[i:i+8], 2)
i += 8
dec.append(bits[i:i+bitlen])
i += bitlen
c += 1
for i in range(0, len(dec), 2):
table[dec[i + 1]] = int(dec[i], 2)
return table
def compress_file(filename):
global log, output
log.log(f"Loading '{filename}'...")
with open(filename, 'rb') as file: # get data
data = list(map(int, file.read()))
log.log(f'Original size: {len(data)} bytes.')
log.log('Creating Huffman table...')
hf = huffman(data)
table=tbl(hf)
table = shichiro_encode(hf)
log.log('Embedding Huffman table...')
out=[]
ln=bin(len(table))[2:] #embed the table
while len(ln)>7:
out.append(int('1'+ln[:7],2))
ln=ln[7:]
out+=[int(ln,2),8-len(ln)]+table
log.log(f'Huffman table size: {len(out)} bytes.')
tablen = bin(len(table))[2:] # embed the table
bits = ''
bitlen = bin(len(tablen))[2:]
while len(bitlen) > 7:
bits += '1' + bitlen[:7]
bitlen = bitlen[7:]
bits += bitlen.rjust(8, '0') + bin(len(bitlen))[2:].rjust(8, '0') + tablen + table
log.log(f'Huffman table size: {len(bits)} bits.')
log.log('Compressing...')
stack=''
for i in range(len(data)): # encode to Haffman
stack+=hf[data[i]]
while len(stack)>=8:
out.append(int(stack[:8],2))
stack=stack[8:]
out+=[int(stack.ljust(8,'0'),2),len(stack)]
bits += hf[data[i]]
out = []
for i in range(0, len(bits), 8):
out.append(int(bits[i:i+8].ljust(8, '0'), 2))
out.append(len(bits) % 8)
log.log(f'Compressed size: {len(out)} bytes.')
log.log(f"Saving to '{filename}.hfm'...")
with open(f'{filename}.hfm', 'wb') as file: # save Haffman code
file.write(bytes(out))
log.log('SUCCESSFULLY COMPRESSED')
print(f'"origSize":{len(data)},')
print(f'"compSize":{len(out)},')
output += f'"origSize":{len(data)},'
output += f'"compSize":{len(out)},'
def decompress_file(filename):
global log, output
log.log(f"Loading '{filename}'...")
with open(filename, 'rb') as file: # get data
data = [bin(byte)[2:].rjust(8, '0') for byte in file.read()]
@ -130,19 +136,21 @@ def decompress_file(filename):
data[-2] = data[-2][:int(data[-1], 2)]
del data[-1]
log.log('Extracting Huffman table...')
ln='' #extract the table
bitlen = '' # extract the table
i = 0
while 1:
if data[i][0] == '1':
ln+=data[i][1:]
bitlen += data[i][1:]
else:
ln+=data[i][int(data[i+1],2):]
bitlen += data[i][int(data[i + 1], 2):]
break
i += 1
del data[:i + 2]
table=detbl(data[:int(ln,2)])
del data[:int(ln,2)]
data = ''.join(data)
bitlen = int(bitlen, 2)
tablen = int(data[:bitlen], 2)
table = shichiro_decode(data[bitlen:tablen+bitlen])
data = data[bitlen+tablen:]
stack = ''
out = []
log.log('Decompressing...')
@ -151,39 +159,45 @@ def decompress_file(filename):
if stack in table:
out.append(int(table[stack]))
stack = ''
if filename[-4:] == '.hfm':
filename = filename[:-4]
log.log(f"Saving to '{filename}'...")
with open(f'{filename}', 'wb') as file: # save decoded data
file.write(bytes(out))
log.log(f'SUCCESSFULLY DECOMPRESSED')
print(f'"compSize":{os},')
print(f'"origSize":{len(out)},')
output += f'"compSize":{os},'
output += f'"origSize":{len(out)},'
@click.command(options_metavar='[-c / -d]')
@click.argument('files', nargs=-1, metavar='<file [file [...]]>')
@click.option('-c/-d', 'comp', default=True, help='Compress/decompress mode selectors.')
def CLI(files, comp):
log.log(f'hfm {"-c"*comp}{"-d"*(not comp)} {" ".join(files)}')
global log, output
log.log(f'hfm {"-c" * comp}{"-d" * (not comp)} "' + "\" \"".join(files) + '"')
for file in files:
print('{')
output += '{'
stime = time()
if comp:
try:
compress_file(path(file))
wtime = time() - stime
print('"status":true,')
print(f'"time":{round(wtime,3)},')
print(f'"dlink":"./files/{basename(file)+".hfm"}"')
output += '"status":true,'
output += f'"time":{round(wtime, 3)},'
output += f'"dlink":"./files/{url(basename(file)) + ".hfm"}"'
except Exception as e:
output += f'"status":false'
else:
try:
decompress_file(path(file))
wtime = time() - stime
print('"status":true,')
print(f'"time":{round(wtime,3)},')
print(f'"dlink":"./files/{basename(file)[:-4]}"')
output += '"status":true,'
output += f'"time":{round(wtime, 3)},'
output += f'"dlink":"./files/{url(basename(file)[:-4])}"'
except Exception as e:
print(f'"status":false')
print('}')
output += f'"status":false'
output += '}'
print(output)
if __name__ == '__main__':

View File

@ -1,12 +1,17 @@
<?php
$mode = $_POST['mode'];
$file = $_FILES['file'];
if (!is_dir(dirname(__file__).'/files')){
mkdir(dirname(__file__).'/files');
if (!is_dir('./files')) {
mkdir('./files');
}
$path=dirname(__file__).'/files/'.basename($file['name']);
if (!file_exists('./files/counter.txt')){
file_put_contents('./files/counter.txt', '0');
}
$id = (int)file_get_contents('./files/counter.txt');
file_put_contents('./files/counter.txt', (string)($id+1));
$path = "./files/{$id}__".basename($file['name']);
move_uploaded_file($file['tmp_name'], $path);
$result=json_decode((string)shell_exec('python '.dirname(__file__).'/huffman.py -'.($mode=='compress'?'c':'d').' "'.$path.'"'));
$result = json_decode((string)shell_exec(dirname(__file__)."/huffman.py -".($mode == 'compress' ? 'c' : 'd')." \"{$path}\""));
header('Content-Type: application/json');
echo json_encode($result);
?>

1
images/angry_huffman Normal file

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

View File

Before

Width:  |  Height:  |  Size: 972 KiB

After

Width:  |  Height:  |  Size: 972 KiB

View File

@ -1,13 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HuffPress</title>
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<link rel="shortcut icon" href="./favicon.ico" />
<link rel="stylesheet" href="./css/bootstrap.min.css" />
<link rel="stylesheet" href="./css/style.css" />
<script src="./js/jquery-3.6.0.min.js"></script>
<script src="./js/console.image.min.js"></script>
</head>
<body>
<header>
@ -26,18 +27,20 @@
<div class="form-group">
<div class="custom-file">
<label class="custom-file-label" for="file">Choose file...</label>
<input id="file" type="file" class="custom-file-input" required>
<input id="file" type="file" class="custom-file-input" required />
<div class="invalid-feedback">Let's choose your file!</div>
<div class="valid-feedback">Ready to start!</div>
</div>
</div>
<div class="form-group">
<input id="submit" type="submit" class="btn btn-primary" value="HuffPress!">
<input id="submit" type="submit" class="btn btn-primary" value="HuffPress!" />
</div>
</form>
</main>
<div class="wrap">
<div class="process"><img src="processing.gif" alt="Processing..."></div>
<div class="process">
<img src="./images/processing.gif" alt="Processing..." />
</div>
<div class="complete">
<h3>Completed!</h3>
<p class="status"></p>
@ -48,12 +51,12 @@
</div>
<div class="error">
<h3>Error!</h3>
<p class="status">Unable to decompress this file!</p>
<p class="status">Something went wrong!</p>
<div class="btncont">
<button class="btn closebtn">Close</button>
</div>
</div>
</div>
<script src="script.js"></script>
<script src="./js/script.js"></script>
</body>
</html>

4
js/console.image.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
js/jquery-3.6.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

106
js/script.js Normal file
View File

@ -0,0 +1,106 @@
function winsize() {
if (
(!toggled && $(window).width() / $(window).height() > 90/31) ||
(toggled && $(window).width() / ($(window).height()-219) > 18/7)
) {
$('body').css('justify-content', 'flex-start');
} else {
$('body').css('justify-content', 'center');
}
};
$(window).on('load', function () {
setTimeout(() => {
$('main').slideDown(500);
}, 100);
winsize();
});
$(window).on('resize', function () {
winsize();
});
var toggled = false;
$('h2').click(function () {
var time = 300;
if (toggled) {
$('form').slideUp(time);
$('main').css('min-height', '9vw');
setTimeout(() => {
$('h2').css('border-bottom', '0');
}, time);
toggled = false;
winsize();
} else {
$('main').css('min-height', 'calc(9vw + 260px)');
$('form').slideDown(time);
$('form').css('display', 'flex');
$('h2').css('border-bottom', '1px solid black');
toggled = true;
setTimeout(() => {
winsize();
}, time);
};
});
$('form').on('submit', function submit(e) {
e.preventDefault();
$('.wrap').css('display', 'flex');
$('.process').css('display', 'block');
var form = new FormData();
form.append('mode', $('#mode').val());
$.each($('#file')[0].files, function (i, file) {
form.append('file', file);
});
$.ajax({
url: 'huffpress.php',
type: 'POST',
processData: false,
contentType: false,
dataType: 'json',
data: form,
success: function (resp) {
if (resp === null) {
$('.process').css('display', 'none');
$('.error').css('display', 'block');
} else if (resp.status) {
$('.process').css('display', 'none');
$('.complete').css('display', 'block');
if ($('#mode').val() == 'compress') {
$('.complete .status').html(
`Original size:&nbsp;&nbsp;&nbsp;${resp.origSize} B<br>Compressed size: ${resp.compSize} B<br>Time:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${resp.time} s`
);
} else {
$('.complete .status').html(
`Compressed size: ${resp.compSize} B<br>Original size:&nbsp;&nbsp;&nbsp;${resp.origSize} B<br>Time:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${resp.time} s`
);
}
$('#dlink').attr('href', resp.dlink);
} else {
$('.process').css('display', 'none');
$('.error').css('display', 'block');
};
},
});
});
$('.closebtn').click(function () {
$('.wrap').css('display', 'none');
$('.process').css('display', 'none');
$('.error').css('display', 'none');
$('.complete').css('display', 'none');
});
$(window).on('load', function() {
let msg = 'И что это мы тут забыли?\nА ну кыш отседова!\nНашлись тут кулхацкеры понимаешь!';
let style = [
'padding: 1rem;',
'font-family: monospace;',
'font-size: 18pt;',
].join('');
console.log('%c%s', style, msg);
$.ajax({
url: './images/angry_huffman',
success: function(data) {
console.image(data);
},
});
});

View File

@ -1,76 +0,0 @@
function winsize(){
if (!toggled && $(window).width()/$(window).height()>(90/31) ||
toggled && $(window).width()/($(window).height()-219)>(18/7)){
$('body').css('justify-content','flex-start');
} else {
$('body').css('justify-content','center');
}
};
window.onload=function(){
setTimeout(()=>{$('main').slideDown(500)},100);
winsize();
};
$(window).on('resize',function(){
winsize();
});
var toggled=false;
$('h2').click(function(){
var time=300;
if (toggled) {
$('form').slideUp(time);
$('main').css('min-height','9vw');
setTimeout(()=>{$('h2').css('border-bottom','0')},time);
toggled=false;
winsize();
} else {
$('main').css('min-height','calc(9vw + 260px)');
$('form').slideDown(time);
$('form').css('display','flex');
$('h2').css('border-bottom','1px solid black');
toggled=true;
setTimeout(()=>{winsize()},time);
}
});
$('form').on('submit',function submit(e){
e.preventDefault();
$('.wrap').css('display','flex');
$('.process').css('display','block');
var form=new FormData();
form.append('mode',$('#mode').val());
$.each($('#file')[0].files, function(i, file) {
form.append('file', file);
});
$.ajax({
url: 'huffpress.php',
type: 'POST',
processData: false,
contentType: false,
dataType: 'json',
data: form,
success: function(resp){
console.log(resp);
if (resp.status){
$('.process').css('display','none');
$('.complete').css('display','block');
if ($('#mode').val()=='compress'){
$('.complete .status').html(`Original size:&nbsp;&nbsp;&nbsp;${resp.origSize} B<br>Compressed size: ${resp.compSize} B<br>Time:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${resp.time} s`);
} else {
$('.complete .status').html(`Compressed size: ${resp.compSize} B<br>Original size:&nbsp;&nbsp;&nbsp;${resp.origSize} B<br>Time:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;${resp.time} s`);
}
$('#dlink').attr('href',resp.dlink);
} else {
$('.process').css('display','none');
$('.error').css('display','block');
}
}
});
});
$('.closebtn').click(function(){
$('.wrap').css('display','none');
$('.process').css('display','none');
$('.error').css('display','none');
$('.complete').css('display','none');
});