import datetime import logging from fastapi import FastAPI from fastapi.responses import FileResponse from pydantic import BaseModel from typing import List import pypandoc import json from fastapi.testclient import TestClient import os from wand.image import Image from wand.color import Color import shutil import cv2 class DocumentSpecs(BaseModel): format: str style: str linkcolor: str tocdepth: int pdfengine: str content: str fontsize: int paperwidth: int paperheight: int ratio: int margin: int vmargin: int extension: str fps: int stilltime: int class FormatParameters(BaseModel): linkcolor: str tocdepth: int pdfengine: str fontsize: int paperwidth: int paperheight: int ratio: int margin: int vmargin: int extension: str fps: int stilltime: int class Styles(BaseModel): styles: List[str] class Formats(BaseModel): formats: List[str] class App(BaseModel): app: str def convert_pdf(filename, filetype, output_path, resolution=300): """ Convert a PDF into images. All the pages will give a single png file with format: {pdf_filename}-{page_number}.png The function removes the alpha channel from the image and replace it with a white background. """ all_pages = Image(filename=filename, resolution=resolution) for i, page in enumerate(all_pages.sequence): with Image(page) as img: img.format = filetype img.background_color = Color('white') img.alpha_channel = 'remove' image_filename = os.path.splitext(os.path.basename(filename))[0] image_filename = f'{image_filename}-{i:03}.{filetype}' image_filename = os.path.join(output_path, image_filename) img.save(filename=image_filename) def convert_video(images_path, output_path, width, height, fps, stilltime): """ Convert images in output_path into a mp4 file usine OpenCV. :param images_path: :param output_path: :param images_path: :param width: :param height: :return: """ # define a frame array frame_array = [] # list all files in images_path files = [f for f in os.listdir(images_path) if os.path.isfile(os.path.join(images_path, f))] # sort the files files.sort() # create a video writer object for i in range(len(files)): file = os.path.join(images_path, files[i]) logging.log(logging.INFO, f'Converting {file} to mp4') img = cv2.imread(file) for j in range(fps*stilltime): frame_array.append(img) fourcc = cv2.VideoWriter_fourcc(*'mp4v') video_writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) for i in range(len(frame_array)): # writing to a image array video_writer.write(frame_array[i]) video_writer.release() logging.log(logging.INFO, f'Finished converting {output_path}') app = FastAPI() @app.get("/") async def get_root(): app = App(app='fabriquedoc') return app # function to list only directories inside given directory def list_dir(path): return [f for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))] @app.get("/styles/") async def get_styles(): styles = Styles(styles=list_dir("./styles")) return styles @app.get("/formats/{style}/") async def get_formats(style: str): formats = Formats(formats=list_dir(f"./styles/{style}/")) return formats @app.get("/format_parameters/{style}/{format}/") async def get_format_parameters(style: str, format: str): # open styles/format_parameters.json as a dictionary with open(f"./styles/{style}/format_parameters.json", "r") as f: format_data = json.load(f).get(format) logging.log(logging.INFO, str(format_data)) # load data from format_data into the FormatParameters object parameters = FormatParameters(**format_data) return parameters @app.get("/generer/") async def generer(specs: DocumentSpecs): header_file = f'{os.getcwd()}/styles/{specs.style}/{specs.format}/header.tex' cover_file = f'{os.getcwd()}/styles/{specs.style}/{specs.format}/cover.tex' datef = datetime.datetime.now().strftime("%Y-%m-%d") os.makedirs("out", exist_ok=True) filters = ['latex-emoji.lua', 'centered.lua'] pdoc_args = [ f'--include-in-header={header_file}', f'--include-after-body={cover_file}', '--listings', '--dpi=300', f'--toc-depth={specs.tocdepth}', f'--pdf-engine={specs.pdfengine}', '-V', f'linkcolor={specs.linkcolor}', '-V', f'fontsize={specs.fontsize}pt', '-V', f'geometry:paperwidth={round(specs.paperwidth * specs.ratio / 100, -1) / 300}in', '-V', f'geometry:paperheight={round(specs.paperheight * specs.ratio / 100, -1) / 300}in', '-V', f'geometry:left={specs.margin / 300}in', '-V', f'geometry:right={specs.margin / 300}in', '-V', f'geometry:top={specs.vmargin / 300}in', '-V', f'geometry:bottom={specs.vmargin / 300}in' ] pdf_file_path = f"./out/{specs.style}-{specs.format}-{datef}-output.pdf" images_path = f"./out/{specs.style}-{specs.format}-{datef}-images" try: logging.info("Dossier courant = " + os.getcwd()) result = pypandoc.convert_text(source=specs.content, to='pdf', format='markdown+implicit_figures+smart+emoji', encoding='utf-8', extra_args=pdoc_args, filters=filters, cworkdir=os.getcwd(), outputfile=pdf_file_path ) except RuntimeError as rerr: logging.exception(rerr) except OSError as oerr: logging.exception(oerr) if specs.extension in ["png", "jpg", "mp4"]: filename = os.path.join("out", os.path.splitext(os.path.basename(pdf_file_path))[0]) if not os.path.exists(images_path): os.mkdir(images_path) conversion_extension = specs.extension output_extension = specs.extension if specs.extension in ["mp4"]: conversion_extension = "jpg" try: convert_pdf(pdf_file_path, conversion_extension, images_path, resolution=300) if specs.extension in ["png", "jpg"]: shutil.make_archive(base_name=filename, format='zip', root_dir=images_path) output_extension = "zip" shutil.rmtree(images_path) if specs.extension in ["mp4"]: output_extension = "mp4" convert_video(images_path=images_path, output_path=f"{filename}.{output_extension}", width=specs.paperwidth, height=specs.paperheight, fps=specs.fps, stilltime=specs.stilltime) except Exception as e: logging.exception(e) return FileResponse(f"{filename}.{output_extension}") elif specs.extension == "pdf": return FileResponse(pdf_file_path) else: return 0 client = TestClient(app) def test_getroot(): response = client.get("/") assert response.status_code == 200