commit a228f94dd0d6daa10b5b31d1ff8cde443a254ceb Author: H1K0 Date: Sat Oct 10 22:40:27 2020 +0300 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be32f77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/files +*.log \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd52688 --- /dev/null +++ b/README.md @@ -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!"). diff --git a/bg.webp b/bg.webp new file mode 100644 index 0000000..172612c Binary files /dev/null and b/bg.webp differ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..71aaf2b Binary files /dev/null and b/favicon.ico differ diff --git a/huffman.py b/huffman.py new file mode 100644 index 0000000..90e6818 --- /dev/null +++ b/huffman.py @@ -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 i7: + 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='') +@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() \ No newline at end of file diff --git a/huffpress.php b/huffpress.php new file mode 100644 index 0000000..fa56c99 --- /dev/null +++ b/huffpress.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..cd6e4e7 --- /dev/null +++ b/index.html @@ -0,0 +1,59 @@ + + + + + + HuffPress + + + + + + +
+

HuffPress

+
+
+

Compress your files with the Huffman compression!

+
+
+ + +
+
+
+ + +
Let's choose your file!
+
Ready to start!
+
+
+
+ +
+
+
+
+
Processing...
+
+

Completed!

+

+
+ Download + +
+
+
+

Error!

+

Unable to decompress this file!

+
+ +
+
+
+ + + \ No newline at end of file diff --git a/processing.gif b/processing.gif new file mode 100644 index 0000000..d15dcdb Binary files /dev/null and b/processing.gif differ diff --git a/script.js b/script.js new file mode 100644 index 0000000..cfa541b --- /dev/null +++ b/script.js @@ -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:   ${resp.origSize} B
Compressed size: ${resp.compSize} B
Time:            ${resp.time} s`); + } else { + $('.complete .status').html(`Compressed size: ${resp.compSize} B
Original size:   ${resp.origSize} B
Time:            ${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'); +}); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..96fdef3 --- /dev/null +++ b/style.css @@ -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; +} \ No newline at end of file