Initial commit

This commit is contained in:
Masahiko AMANO 2020-10-10 22:40:27 +03:00
commit a228f94dd0
11 changed files with 476 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/files
*.log

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# HuffPress
## 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").
## 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
## 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!").

BIN
bg.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

190
huffman.py Normal file
View File

@ -0,0 +1,190 @@
from os.path import dirname,basename,join,abspath as path
from time import time
from datetime import datetime as dt
import click
class Log:
def __init__(self,path,name):
self.path=path
self.name=name
def log(self,msg,name=None):
if name is None:
name=self.name
now=dt.today()
with open(self.path,'a',encoding='utf-8') as file:
file.write(f'{now.year}-{str(now.month).rjust(2,"0")}-{str(now.day).rjust(2,"0")} '
f'{str(now.hour).rjust(2,"0")}:{str(now.minute).rjust(2,"0")}:{str(now.second).rjust(2,"0")},{str(now.microsecond)[:3]}'
' | '
f'{name}'
' | '
f'{msg}\n')
log=Log(join(dirname(__file__),'hfm.log'),'hfm-py')
def huffman(data):
units={} #getting element-wise info
for c in data:
if c in units:
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(),'')
while units: #creating Haffman table
if len(units)>2:
b=int(units[0][1]+units[1][1]>units[1][1]+units[2][1])
else:
b=0
for c in units[b][0]:
codes[c]='0'+codes[c]
for c in units[1+b][0]:
codes[c]='1'+codes[c]
units[2*b]=units[b][0]+units[1+b][0],units[b][1]+units[1+b][1]
if len(units)>2:
del units[1]
units.sort(key=lambda u:u[1])
else:
del units
break
return codes
def tbl(table):
table=';'.join([f'{k};{table[k]}' for k in table]).split(';')
byts=[]
for i in range(len(table)):
if i%2:
num=table[i]
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
def detbl(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
for i in range(0,len(dec),2):
table[dec[i+1]]=int(dec[i],2)
return table
def compress_file(filename):
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)
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.')
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"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)},')
def decompress_file(filename):
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()]
os=len(data)
data[-2]=data[-2][:int(data[-1],2)]
del data[-1]
log.log('Extracting Huffman table...')
ln='' #extract the table
i=0
while 1:
if data[i][0]=='1':
ln+=data[i][1:]
else:
ln+=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)
stack=''
out=[]
log.log('Decompressing...')
for c in data: #decode Haffman
stack+=c
if stack in table:
out.append(int(table[stack]))
stack=''
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)},')
@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)}')
for file in files:
print('{')
stime=time()
if comp:
compress_file(path(file))
wtime=time()-stime
print('"status":true,')
print(f'"time":{round(wtime,3)},')
print(f'"dlink":"./files/{basename(file)+".hfm"}"')
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]}"')
except Exception as e:
print(f'"status":false')
print('}')
if __name__=='__main__':
CLI()

12
huffpress.php Normal file
View File

@ -0,0 +1,12 @@
<?php
$mode=$_POST['mode'];
$file=$_FILES['file'];
if (!is_dir(dirname(__file__).'/files')){
mkdir(dirname(__file__).'/files');
}
$path=dirname(__file__).'/files/'.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.'"'));
header('Content-Type: application/json');
echo json_encode($result);
?>

59
index.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<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>
</head>
<body>
<header>
<h1>HuffPress</h1>
</header>
<main>
<h2>Compress your files with the Huffman compression!</h2>
<form id="form" class="was-validated" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="mode">Select mode</label>
<select id="mode" class="form-control" id="mode">
<option value="compress">Compress</option>
<option value="decompress">Decompress</option>
</select>
</div>
<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>
<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!">
</div>
</form>
</main>
<div class="wrap">
<div class="process"><img src="processing.gif" alt="Processing..."></div>
<div class="complete">
<h3>Completed!</h3>
<p class="status"></p>
<div class="btncont">
<a id="dlink" class="btn btn-primary" href="" download>Download</a>
<button class="btn closebtn">Close</button>
</div>
</div>
<div class="error">
<h3>Error!</h3>
<p class="status">Unable to decompress this file!</p>
<div class="btncont">
<button class="btn closebtn">Close</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

BIN
processing.gif Normal file

Binary file not shown.

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');
});

119
style.css Normal file
View File

@ -0,0 +1,119 @@
@import url('https://fonts.googleapis.com/css2?family=Epilogue&family=Secular+One&display=swap');
html,body{
width: 100%;
min-height: 100vh;
margin: 0;
padding: 0;
}
body{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-image: url(bg.webp);
background-size: cover;
}
header{
margin: 0;
margin-top: 2vw;
padding: 0;
text-align: center;
}
h1{
margin: 0;
padding: 0;
color: white;
font-family: Epilogue;
font-size: 14vw;
text-shadow: 0 0 1vw black;
cursor: default;
}
main{
display: none;
margin-bottom: 2vw;
background-color: #fff8;
box-shadow: 0 0 .5vw black;
border-radius: 1.5vw;
min-height: 9vw;
transition: .3s;
overflow: hidden;
}
main:hover{
background-color: #fff;
box-shadow: 0 0 1vw black;
}
h2{
margin: 0;
padding: 0 2vw;
height: 9vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: hsla(270,80%,80%,.6);
border-bottom: 0;
font-family: Secular One;
font-size: 3vw;
text-shadow: 2.5px 2px .5px white;
cursor: pointer;
}
form{
display: flex;
flex-direction: column;
justify-content: center;
display: none;
margin: 0;
padding: 2vw 2vw;
box-sizing: border-box;
}
.wrap{
display: none;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #0008;
justify-content: center;
align-items: center;
z-index: 10;
}
.process,.complete,.error{
display: none;
padding: 2vw 5vw;
background-color: white;
box-shadow: 0 0 2vw black;
border-radius: 1.5vw;
font-size: 5vw;
cursor: default;
}
.process img{
width: 15vw;
}
h3{
margin: 0;
margin-top: .5vw;
font-family: Secular One;
font-size: 3vw;
}
.status{
margin: 0;
margin-top: .5vw;
font-family: monospace;
font-size: 1.5vw;
}
.btncont{
margin: 0;
padding: 0;
}
.btncont *{
font-size: 2vw;
}
#dlink{
margin-right: 1vw;
}