From bb814381c5e86a3820dcea8c288fe0a6fc6bdcc9 Mon Sep 17 00:00:00 2001 From: Francois Pelletier Date: Sun, 12 Feb 2023 01:04:11 -0500 Subject: [PATCH] =?UTF-8?q?ajout=20de=20cr=C3=A9ation=20de=20formats=20vid?= =?UTF-8?q?=C3=A9o=20mp4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 97 ++++++++++++++++++---- requirements.txt | 3 +- styles/blanc/format_parameters.json | 37 +++++++-- styles/jevalideca/format_parameters.json | 37 +++++++-- styles/lcm/format_parameters.json | 14 ++++ styles/missioncyber/format_parameters.json | 37 +++++++-- test_main.http | 60 ++++++++++++- 7 files changed, 240 insertions(+), 45 deletions(-) diff --git a/main.py b/main.py index e1d1955..c7567d5 100644 --- a/main.py +++ b/main.py @@ -12,6 +12,7 @@ import os from wand.image import Image from wand.color import Color import shutil +import cv2 class DocumentSpecs(BaseModel): @@ -28,6 +29,8 @@ class DocumentSpecs(BaseModel): margin: int vmargin: int extension: str + fps: int + stilltime: int class FormatParameters(BaseModel): @@ -41,6 +44,8 @@ class FormatParameters(BaseModel): margin: int vmargin: int extension: str + fps: int + stilltime: int class Styles(BaseModel): @@ -78,6 +83,48 @@ def convert_pdf(filename, filetype, output_path, resolution=300): 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() @@ -121,7 +168,6 @@ async def generer(specs: DocumentSpecs): 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) - output_file = f"./out/{specs.style}-{specs.format}-{datef}-output.pdf" filters = ['latex-emoji.lua', 'centered.lua'] pdoc_args = [ f'--include-in-header={header_file}', @@ -132,13 +178,15 @@ async def generer(specs: DocumentSpecs): 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: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()) @@ -149,32 +197,45 @@ async def generer(specs: DocumentSpecs): extra_args=pdoc_args, filters=filters, cworkdir=os.getcwd(), - outputfile=output_file + outputfile=pdf_file_path ) except RuntimeError as rerr: logging.exception(rerr) except OSError as oerr: logging.exception(oerr) - if specs.extension in ["png", "jpg"]: - zip_filename = os.path.join("out", os.path.splitext(os.path.basename(output_file))[0]) - png_output_dir = "./out/png_output" - if not os.path.exists(png_output_dir): - os.mkdir(png_output_dir) + 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(output_file, - specs.extension, - png_output_dir, + convert_pdf(pdf_file_path, + conversion_extension, + images_path, resolution=300) - shutil.make_archive(base_name=zip_filename, - format='zip', - root_dir=png_output_dir) - shutil.rmtree(png_output_dir) + 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(zip_filename + ".zip") + return FileResponse(f"{filename}.{output_extension}") elif specs.extension == "pdf": - return FileResponse(output_file) + return FileResponse(pdf_file_path) else: return 0 diff --git a/requirements.txt b/requirements.txt index ecd337e..1877d5c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,4 +20,5 @@ watchfiles~=0.18.1 httpx~=0.23.1 setuptools~=67.2.0 pypandoc~=1.10 -Wand~=0.6.10 \ No newline at end of file +Wand~=0.6.10 +opencv-python~=4.7.0.68 \ No newline at end of file diff --git a/styles/blanc/format_parameters.json b/styles/blanc/format_parameters.json index fbf27d5..a6de3f3 100644 --- a/styles/blanc/format_parameters.json +++ b/styles/blanc/format_parameters.json @@ -6,8 +6,11 @@ "fontsize": 12, "paperwidth": 2480, "paperheight": 3507, + "ratio": 100, "margin": 248, - "vmargin": 350, + "vmargin": 525, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "lettre": { @@ -17,8 +20,11 @@ "fontsize": 12, "paperwidth": 2550, "paperheight": 3300, + "ratio": 100, "margin": 255, - "vmargin": 330, + "vmargin": 495, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "linkedin": { @@ -28,8 +34,11 @@ "fontsize": 16, "paperwidth": 1200, "paperheight": 1200, - "margin": 120, - "vmargin": 120, + "ratio": 100, + "margin": 180, + "vmargin": 180, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "instagram": { @@ -39,8 +48,11 @@ "fontsize": 16, "paperwidth": 1080, "paperheight": 1920, + "ratio": 100, "margin": 108, - "vmargin": 192, + "vmargin": 244, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "pinterest": { @@ -50,8 +62,11 @@ "fontsize": 16, "paperwidth": 735, "paperheight": 1102, + "ratio": 100, "margin": 75, - "vmargin": 110, + "vmargin": 165, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "slide43": { @@ -61,8 +76,11 @@ "fontsize": 16, "paperwidth": 2560, "paperheight": 1920, + "ratio": 100, "margin": 256, - "vmargin": 192, + "vmargin": 288, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "slide169": { @@ -72,8 +90,11 @@ "fontsize": 16, "paperwidth": 2560, "paperheight": 1440, + "ratio": 100, "margin": 256, - "vmargin": 144, + "vmargin": 216, + "fps": 15, + "stilltime": 2, "extension": "pdf" } } diff --git a/styles/jevalideca/format_parameters.json b/styles/jevalideca/format_parameters.json index fbf27d5..a6de3f3 100644 --- a/styles/jevalideca/format_parameters.json +++ b/styles/jevalideca/format_parameters.json @@ -6,8 +6,11 @@ "fontsize": 12, "paperwidth": 2480, "paperheight": 3507, + "ratio": 100, "margin": 248, - "vmargin": 350, + "vmargin": 525, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "lettre": { @@ -17,8 +20,11 @@ "fontsize": 12, "paperwidth": 2550, "paperheight": 3300, + "ratio": 100, "margin": 255, - "vmargin": 330, + "vmargin": 495, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "linkedin": { @@ -28,8 +34,11 @@ "fontsize": 16, "paperwidth": 1200, "paperheight": 1200, - "margin": 120, - "vmargin": 120, + "ratio": 100, + "margin": 180, + "vmargin": 180, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "instagram": { @@ -39,8 +48,11 @@ "fontsize": 16, "paperwidth": 1080, "paperheight": 1920, + "ratio": 100, "margin": 108, - "vmargin": 192, + "vmargin": 244, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "pinterest": { @@ -50,8 +62,11 @@ "fontsize": 16, "paperwidth": 735, "paperheight": 1102, + "ratio": 100, "margin": 75, - "vmargin": 110, + "vmargin": 165, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "slide43": { @@ -61,8 +76,11 @@ "fontsize": 16, "paperwidth": 2560, "paperheight": 1920, + "ratio": 100, "margin": 256, - "vmargin": 192, + "vmargin": 288, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "slide169": { @@ -72,8 +90,11 @@ "fontsize": 16, "paperwidth": 2560, "paperheight": 1440, + "ratio": 100, "margin": 256, - "vmargin": 144, + "vmargin": 216, + "fps": 15, + "stilltime": 2, "extension": "pdf" } } diff --git a/styles/lcm/format_parameters.json b/styles/lcm/format_parameters.json index 41acc47..a6de3f3 100644 --- a/styles/lcm/format_parameters.json +++ b/styles/lcm/format_parameters.json @@ -9,6 +9,8 @@ "ratio": 100, "margin": 248, "vmargin": 525, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "lettre": { @@ -21,6 +23,8 @@ "ratio": 100, "margin": 255, "vmargin": 495, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "linkedin": { @@ -33,6 +37,8 @@ "ratio": 100, "margin": 180, "vmargin": 180, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "instagram": { @@ -45,6 +51,8 @@ "ratio": 100, "margin": 108, "vmargin": 244, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "pinterest": { @@ -57,6 +65,8 @@ "ratio": 100, "margin": 75, "vmargin": 165, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "slide43": { @@ -69,6 +79,8 @@ "ratio": 100, "margin": 256, "vmargin": 288, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "slide169": { @@ -81,6 +93,8 @@ "ratio": 100, "margin": 256, "vmargin": 216, + "fps": 15, + "stilltime": 2, "extension": "pdf" } } diff --git a/styles/missioncyber/format_parameters.json b/styles/missioncyber/format_parameters.json index fbf27d5..a6de3f3 100644 --- a/styles/missioncyber/format_parameters.json +++ b/styles/missioncyber/format_parameters.json @@ -6,8 +6,11 @@ "fontsize": 12, "paperwidth": 2480, "paperheight": 3507, + "ratio": 100, "margin": 248, - "vmargin": 350, + "vmargin": 525, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "lettre": { @@ -17,8 +20,11 @@ "fontsize": 12, "paperwidth": 2550, "paperheight": 3300, + "ratio": 100, "margin": 255, - "vmargin": 330, + "vmargin": 495, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "linkedin": { @@ -28,8 +34,11 @@ "fontsize": 16, "paperwidth": 1200, "paperheight": 1200, - "margin": 120, - "vmargin": 120, + "ratio": 100, + "margin": 180, + "vmargin": 180, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "instagram": { @@ -39,8 +48,11 @@ "fontsize": 16, "paperwidth": 1080, "paperheight": 1920, + "ratio": 100, "margin": 108, - "vmargin": 192, + "vmargin": 244, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "pinterest": { @@ -50,8 +62,11 @@ "fontsize": 16, "paperwidth": 735, "paperheight": 1102, + "ratio": 100, "margin": 75, - "vmargin": 110, + "vmargin": 165, + "fps": 15, + "stilltime": 2, "extension": "jpg" }, "slide43": { @@ -61,8 +76,11 @@ "fontsize": 16, "paperwidth": 2560, "paperheight": 1920, + "ratio": 100, "margin": 256, - "vmargin": 192, + "vmargin": 288, + "fps": 15, + "stilltime": 2, "extension": "pdf" }, "slide169": { @@ -72,8 +90,11 @@ "fontsize": 16, "paperwidth": 2560, "paperheight": 1440, + "ratio": 100, "margin": 256, - "vmargin": 144, + "vmargin": 216, + "fps": 15, + "stilltime": 2, "extension": "pdf" } } diff --git a/test_main.http b/test_main.http index 28a3f25..ab92d6c 100644 --- a/test_main.http +++ b/test_main.http @@ -17,11 +17,37 @@ Accept: application/zip "ratio": 100, "margin": 180, "vmargin": 180, + "fps": 15, + "stilltime": 2, "extension": "jpg" } ### +GET http://127.0.0.1:8000/generer/ +Content-Type: application/json +Accept: application/zip + +{ + "format": "instagram", + "style": "jevalideca", + "linkcolor": "blue", + "tocdepth": 2, + "pdfengine": "lualatex", + "content": "# Ceci est un titre\n## Ceci est un sous-titre\n\nCeci est un paragraphe\n\n## Ceci est un autre sous-titre\n\n> Ceci est du code\n\nCeci est un emoji :heart_eyes:\n\n::: {.center}\nCeci est centré\n:::", + "fontsize": 14, + "paperwidth": 1080, + "paperheight": 1920, + "ratio": 100, + "margin": 180, + "vmargin": 180, + "fps": 15, + "stilltime": 2, + "extension": "mp4" +} + +### + GET http://127.0.0.1:8000/generer/ Content-Type: application/json Accept: application/pdf @@ -39,6 +65,8 @@ Accept: application/pdf "ratio": 100, "margin": 90, "vmargin": 90, + "fps": 15, + "stilltime": 2, "extension": "pdf" } @@ -60,8 +88,10 @@ Accept: application/pdf "paperheight": 1080, "ratio": 100, "margin": 180, - "extension": "jpg", - "vmargin": 300 + "vmargin": 300, + "fps": 15, + "stilltime": 2, + "extension": "jpg" } ### @@ -83,11 +113,37 @@ Accept: application/pdf "ratio": 100, "margin": 256, "vmargin": 216, + "fps": 15, + "stilltime": 2, "extension": "jpg" } ### +GET http://127.0.0.1:8000/generer/ +Content-Type: application/json +Accept: application/pdf + +{ + "format": "slide169", + "style": "lcm", + "linkcolor": "blue", + "tocdepth": 2, + "pdfengine": "lualatex", + "content": "# Ceci est un titre\n## Ceci est un sous-titre\n\nCeci est un paragraphe\n\n## Ceci est un autre sous-titre\n\n> Ceci est du code\n\nCeci est un emoji :heart_eyes:\n\n::: {.center}\nCeci est centré\n:::", + "fontsize": 14, + "paperwidth": 2560, + "paperheight": 1440, + "ratio": 100, + "margin": 256, + "vmargin": 216, + "fps": 15, + "stilltime": 2, + "extension": "mp4" +} + +### + GET http://127.0.0.1:8000/format_parameters/jevalideca/slide169/ Content-Type: application/json Accept: application/pdf \ No newline at end of file