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