Initial commit
This commit is contained in:
commit
e0abc48efd
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
*.swp
|
||||
venv/*
|
||||
*.pyc
|
||||
__pycache__/*
|
||||
__pycache__
|
||||
*.wpr
|
||||
*.log
|
||||
.idea/
|
76
caff_previewer_wrapper/app.py
Normal file
76
caff_previewer_wrapper/app.py
Normal file
@ -0,0 +1,76 @@
|
||||
import tempfile
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
from flask import Flask, current_app
|
||||
|
||||
from config import Config
|
||||
|
||||
from utils import write_file_to_fd_while_calculating_md5, create_md5_sum_for_file
|
||||
from converter import convert_caff_to_tga, convert_tga_to_png
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
|
||||
|
||||
def do_everything(workdir: str):
|
||||
# Recieve file
|
||||
uploaded_caff_fd, uploaded_caff_path = tempfile.mkstemp(suffix='.caff', dir=workdir)
|
||||
|
||||
uploaded_caff_md5sum = write_file_to_fd_while_calculating_md5(uploaded_caff_fd) # Throws overflow error
|
||||
|
||||
# Convert CAFF to TGA
|
||||
converted_tga_fd, converted_tga_path = tempfile.mkstemp(suffix='.tga', dir=workdir)
|
||||
os.close(converted_tga_fd)
|
||||
|
||||
convert_caff_to_tga(uploaded_caff_path, converted_tga_path)
|
||||
|
||||
if not os.path.isfile(converted_tga_path):
|
||||
raise FileNotFoundError("Conversion output is missing")
|
||||
|
||||
# Convert TGA to PNG
|
||||
converted_png_fd, converted_png_path = tempfile.mkstemp(suffix='.png', dir=workdir)
|
||||
os.close(converted_png_fd)
|
||||
|
||||
convert_tga_to_png(converted_tga_path, converted_png_path)
|
||||
|
||||
if not os.path.isfile(converted_tga_path):
|
||||
raise FileNotFoundError("Conversion output is missing")
|
||||
|
||||
converted_png_md5sum = create_md5_sum_for_file(converted_png_path)
|
||||
|
||||
# Send back converted file
|
||||
converted_png_handle = open(converted_png_path, 'rb')
|
||||
|
||||
def stream_and_remove_file():
|
||||
# This really is some black magic here
|
||||
# When flask transmits the file ...
|
||||
yield from converted_png_handle # <- It transmits from file handle
|
||||
# After it's done the rest of this function will be called, so it cleans up after itself
|
||||
converted_png_handle.close()
|
||||
shutil.rmtree(workdir)
|
||||
|
||||
return current_app.response_class(
|
||||
stream_and_remove_file(),
|
||||
headers={
|
||||
'X-request-checksum': uploaded_caff_md5sum,
|
||||
'X-response-checksum': converted_png_md5sum,
|
||||
'Content-type': 'image/png'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route('/preview', methods=['POST'])
|
||||
def perform_conversion():
|
||||
workdir = tempfile.mkdtemp(prefix='caff')
|
||||
try:
|
||||
response = do_everything(workdir) # normally this would clean up workdir
|
||||
except:
|
||||
shutil.rmtree(workdir) # but sometimes it must be done externally
|
||||
raise
|
||||
|
||||
return response
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
9
caff_previewer_wrapper/config.py
Normal file
9
caff_previewer_wrapper/config.py
Normal file
@ -0,0 +1,9 @@
|
||||
import os
|
||||
|
||||
|
||||
class Config:
|
||||
CAFF_PREVIEWER_BINARY = os.environ.get('CAFF_PREVIEWER_BINARY', '/usr/local/bin/caff_previewer')
|
||||
IMAGEMAGICK_CONVERT_BINARY = os.environ.get('IMAGEMAGICK_CONVERT_BINARY', '/usr/bin/convert')
|
||||
CONVERSION_TIMEOUT = int(os.environ.get('IMAGEMAGICK_CONVERT_BINARY', 30))
|
||||
RECIEVE_CHUNKSIZE = int(os.environ.get('RECIEVE_CHUNKSIZE', 2048))
|
||||
MAX_RECIEVE_SIZE = int(os.environ.get('MAX_RECIEVE_SIZE', 536870912)) # 512 MB
|
41
caff_previewer_wrapper/converter.py
Normal file
41
caff_previewer_wrapper/converter.py
Normal file
@ -0,0 +1,41 @@
|
||||
import subprocess
|
||||
from flask import current_app
|
||||
import werkzeug.exceptions
|
||||
|
||||
|
||||
def run_abstract_converter(converter: str, source: str, destination: str) -> int:
|
||||
"""
|
||||
Just runs a binary and gives it two arguments
|
||||
:param converter: the converter binary to run
|
||||
:param source: source file
|
||||
:param destination: destination file
|
||||
:returns: exitcode of the converter
|
||||
"""
|
||||
completed_process = subprocess.run([converter, source, destination],
|
||||
timeout=current_app.config['CONVERSION_TIMEOUT'], env={})
|
||||
|
||||
return completed_process.returncode
|
||||
|
||||
def convert_caff_to_tga(source: str, destination: str):
|
||||
"""
|
||||
This function uses caff_previewer to convert a CAFF file into a TGA file
|
||||
:param source: path of the source TGA file (must exists)
|
||||
:param destination: path of the destination TGA file (will be created)
|
||||
"""
|
||||
INTERNAL_ERROR_CODES = [0x01, 0x03, 0x04, 0x05, 0x32, 0x51]
|
||||
ret = run_abstract_converter(current_app.config['CAFF_PREVIEWER_BINARY'], source, destination)
|
||||
if ret in INTERNAL_ERROR_CODES:
|
||||
raise RuntimeError(f"Caff Previewer returned an unexpected error code: {ret}")
|
||||
elif ret != 0:
|
||||
raise werkzeug.exceptions.BadRequest("CAFF format violation")
|
||||
|
||||
|
||||
def convert_tga_to_png(source: str, destination: str):
|
||||
"""
|
||||
This function uses ImageMagick to convert a TGA file into a PNG file
|
||||
:param source: path of the source TGA file (must exists)
|
||||
:param destination: path of the destination TGA file (will be created)
|
||||
"""
|
||||
ret = run_abstract_converter(current_app.config['IMAGEMAGICK_CONVERT_BINARY'], source, destination)
|
||||
if ret != 0:
|
||||
raise RuntimeError(f"Image magick convert returned an unexpected error code: {ret}")
|
37
caff_previewer_wrapper/utils.py
Normal file
37
caff_previewer_wrapper/utils.py
Normal file
@ -0,0 +1,37 @@
|
||||
from flask import request, current_app
|
||||
import werkzeug.exceptions
|
||||
import hashlib
|
||||
|
||||
|
||||
def write_file_to_fd_while_calculating_md5(fd: int) -> str:
|
||||
chunksize = current_app.config['RECIEVE_CHUNKSIZE']
|
||||
m = hashlib.md5()
|
||||
|
||||
total_recieved = 0
|
||||
|
||||
# Begin recieving the file
|
||||
with open(fd, "bw") as f:
|
||||
|
||||
while True: # This is where uploading happens
|
||||
chunk = request.stream.read(chunksize)
|
||||
if len(chunk) == 0:
|
||||
break
|
||||
|
||||
total_recieved += len(chunk)
|
||||
if total_recieved > current_app.config['MAX_RECIEVE_SIZE']:
|
||||
raise werkzeug.exceptions.RequestEntityTooLarge()
|
||||
|
||||
m.update(chunk)
|
||||
f.write(chunk)
|
||||
|
||||
return m.hexdigest()
|
||||
|
||||
|
||||
def create_md5_sum_for_file(fname):
|
||||
m = hashlib.md5()
|
||||
|
||||
with open(fname, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
m.update(chunk)
|
||||
|
||||
return m.hexdigest()
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Flask~=1.1.2
|
||||
gunicorn~=20.0.4
|
Loading…
Reference in New Issue
Block a user