Compare commits

..

No commits in common. "d1462470b17712ad2da1cbe075f1209e20980559" and "a228f94dd0d6daa10b5b31d1ff8cde443a254ceb" have entirely different histories.

14 changed files with 329 additions and 404 deletions

View File

@ -2,21 +2,15 @@
## What's up? ## What's up?
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'). 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").
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? ## What do I need?
- Any web browser - Any web browser
- ![PHP 7+](https://img.shields.io/badge/PHP-7+-blueviolet.svg) - ![PHP 7+](https://img.shields.io/badge/PHP-7+-blueviolet.svg)
- ![Python 3.7+](https://img.shields.io/badge/Python-3+-blue.svg) - ![Python 3+](https://img.shields.io/badge/Python-3+-blue.svg)
- `click` python library - `click` library
## So, how do I use it? ## So, how do I use it?
I think the usage is too easy to explain it, but anyway here's [a video guide](https://yadi.sk/i/aiy78bDaoKqoJQ 'Watch!'). 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!").
---
<p align=center><i>&copy; Masahiko AMANO a.k.a. H1K0, 2020-2021</i></p>

View File

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

File diff suppressed because one or more lines are too long

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,106 +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).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

Before

Width:  |  Height:  |  Size: 972 KiB

After

Width:  |  Height:  |  Size: 972 KiB

76
script.js Normal file
View File

@ -0,0 +1,76 @@
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');
});

View File

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