From 69841953921d63eef8d63e7c39da203a802d5606 Mon Sep 17 00:00:00 2001 From: Francois Pelletier Date: Sat, 11 Feb 2023 20:35:32 -0500 Subject: [PATCH 1/5] =?UTF-8?q?ajout=20d'un=20param=C3=A8tre=20de=20ratio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 12 ++++++++---- requirements.txt | 9 +++------ styles/lcm/format_parameters.json | 7 +++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/main.py b/main.py index 03a7fbf..da3fb6e 100644 --- a/main.py +++ b/main.py @@ -24,6 +24,7 @@ class DocumentSpecs(BaseModel): fontsize: int paperwidth: int paperheight: int + ratio: int margin: int vmargin: int extension: str @@ -36,6 +37,7 @@ class FormatParameters(BaseModel): fontsize: int paperwidth: int paperheight: int + ratio: int margin: int vmargin: int extension: str @@ -130,10 +132,12 @@ 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={specs.paperwidth / 300}in', - '-V', f'geometry:paperheight={specs.paperheight / 300}in', - '-V', f'geometry:margin={specs.margin / 300}in', - '-V', f'geometry:vmargin={specs.vmargin / 300}in' + '-V', f'geometry:paperwidth={round(specs.paperwidth * specs.ratio / 100,-1) / 300}in', + '-V', f'geometry:paperheight={round(specs.paperwidth * 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' ] try: logging.info("Dossier courant = " + os.getcwd()) diff --git a/requirements.txt b/requirements.txt index 8ff15f4..ecd337e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,23 @@ h11~=0.14.0 -pip~=21.3.1 -wheel~=0.37.1 PyYAML~=6.0 anyio~=3.6.2 sniffio~=1.3.0 click~=8.1.3 httpcore~=0.16.3 idna~=3.4 -rfc3986~=1.5.0 certifi~=2022.12.7 pandoc~=2.3 ply~=3.11 plumbum~=1.8.0 uvloop~=0.17.0 -fastapi~=0.88.0 -starlette~=0.22.0 +fastapi~=0.91.0 +starlette~=0.24.0 pydantic~=1.10.2 websockets~=10.4 uvicorn~=0.20.0 httptools~=0.5.0 watchfiles~=0.18.1 httpx~=0.23.1 -setuptools~=60.2.0 +setuptools~=67.2.0 pypandoc~=1.10 Wand~=0.6.10 \ No newline at end of file diff --git a/styles/lcm/format_parameters.json b/styles/lcm/format_parameters.json index fbf27d5..362e38c 100644 --- a/styles/lcm/format_parameters.json +++ b/styles/lcm/format_parameters.json @@ -6,6 +6,7 @@ "fontsize": 12, "paperwidth": 2480, "paperheight": 3507, + "ratio": 100, "margin": 248, "vmargin": 350, "extension": "pdf" @@ -17,6 +18,7 @@ "fontsize": 12, "paperwidth": 2550, "paperheight": 3300, + "ratio": 100, "margin": 255, "vmargin": 330, "extension": "pdf" @@ -28,6 +30,7 @@ "fontsize": 16, "paperwidth": 1200, "paperheight": 1200, + "ratio": 100, "margin": 120, "vmargin": 120, "extension": "jpg" @@ -39,6 +42,7 @@ "fontsize": 16, "paperwidth": 1080, "paperheight": 1920, + "ratio": 100, "margin": 108, "vmargin": 192, "extension": "jpg" @@ -50,6 +54,7 @@ "fontsize": 16, "paperwidth": 735, "paperheight": 1102, + "ratio": 100, "margin": 75, "vmargin": 110, "extension": "jpg" @@ -61,6 +66,7 @@ "fontsize": 16, "paperwidth": 2560, "paperheight": 1920, + "ratio": 100, "margin": 256, "vmargin": 192, "extension": "pdf" @@ -72,6 +78,7 @@ "fontsize": 16, "paperwidth": 2560, "paperheight": 1440, + "ratio": 100, "margin": 256, "vmargin": 144, "extension": "pdf" From e2c54f4d84587ad40d86211511eaf0780f1c5b14 Mon Sep 17 00:00:00 2001 From: Francois Pelletier Date: Sat, 11 Feb 2023 22:04:35 -0500 Subject: [PATCH 2/5] =?UTF-8?q?ajout=20du=20filtre=20pour=20les=20emojis?= =?UTF-8?q?=20et=20changement=20des=20marges=20par=20d=C3=A9faut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- latex-emoji.lua | 248 ++++++++++++++++++++++++++++++ main.py | 17 +- styles/lcm/format_parameters.json | 16 +- test_main.http | 23 ++- 4 files changed, 282 insertions(+), 22 deletions(-) create mode 100644 latex-emoji.lua diff --git a/latex-emoji.lua b/latex-emoji.lua new file mode 100644 index 0000000..7db9f32 --- /dev/null +++ b/latex-emoji.lua @@ -0,0 +1,248 @@ +-- latex-emoji.lua +-- +-- @copyright 2020 Takayuki YATO (aka. "ZR") +-- GitHub: https://github.com/zr-tex8r +-- Twitter: @zr_tex8r +-- This program is distributed under the MIT License. +-- +local filter_name = 'latex-emoji' +---------------------------------------- helpers + +--- Show debug log? +local show_log = true +--- The default emoji font +local default_emojifont = 'TwemojiMozilla.ttf' + +--- Use bxcoloremoji package? +local bxcoloremoji = false +--- The emoji font to use +local emojifont, emojifontoptions = nil +--- All used codepoints +local ucs_used = {} +--- The number of emoji text spans. +local text_count = 0 + +local utils = require 'pandoc.utils' +local concat, insert, pack, unpack = + table.concat, table.insert, table.pack, table.unpack + +--- Shows a debug log. +local function log(fmt, ...) + if not show_log then return end + io.stderr:write(filter_name..": "..fmt:format(...).."\n") +end + +--- Aborts with an error message. +local function abort(fmt, ...) + error(filter_name..": "..fmt:format(...)) +end + +--- Returns the Pandoc-or-ordinary type of v. +-- @return A string that says type name. +local function pantype(v) + local t = type(v) + return (t == 'table') and v.t or t +end + +--- Makes a comma-separated value string. +-- @return A string. +local function clist(...) + local t, u = pack(...), {} + for i = 1, t.n do + local v = (t[i] == nil) and '' or tostring(t[i]) + if v ~= '' then insert(u, v) end + end + return concat(u, ',') +end + +--- Makes the sorted sequence of all keys of a given table. +-- @return A sequence of strings. +local function keys(t) + local u = {} + for k in pairs(t) do insert(u, k) end + table.sort(u) + return u +end + +--- Converts a singleton sequence to its element. +-- @return The sole element of v if v is a singleton; +-- v if v is not a table; otherwise an error is issued. +local function tosingle(v, l) + if type(v) ~= 'table' then return v end + if #v == 1 then return tosingle(v[1], l) end + abort("multiple values given: %s", l) +end + +--- Converts a value to a singleton sequence. +-- @return The empty table if v is nil; v if v is a table; +-- otherwise the singleton of v. +local function toseq(v) + if v == nil then return {} + elseif type(v) == 'table' then return v + else return {v} + end +end + +--- Converts MetaInlines values inside a MetaValue to strings. +-- @return The converted value. (v is not modified.) +local function tostring_meta(v, l) + if type(v) ~= 'table' then return v end + if v.t == 'MetaList' or v.t == nil then + local r = {} + for k, e in pairs(v) do r[k] = tostring_meta(e, l) end + return r + elseif v.t == 'MetaInlines' then + return utils.stringify(v) + else abort("cannot stringify: %s", v.t, l) + end +end + +--- Gets the source to go into the header. +-- @return LaTeX source string +local function get_header() + if not bxcoloremoji or not next(ucs_used) then + return nil + end + return ([[ +\usepackage[%s]{bxcoloremoji} +\newcommand*{\panEmoji}{\coloremoji} +]]):format(clist(emojifont, unpack(emojifontoptions))) +end + +--- Gets the source to go into the head of body. +-- @return LaTeX source string +local function get_prologue() + if bxcoloremoji or not next(ucs_used) then + return nil + end + local fname = emojifont or default_emojifont + local fopts = clist('Renderer=HarfBuzz', unpack(emojifontoptions)); + local ucs = keys(ucs_used) + for i = 1, #ucs do + ucs[i] = ('"%X'):format(ucs[i]) + end + local dcrsrc = concat(ucs, ',\n') + return ([[ +\makeatletter +\ifnum0\ifdefined\directlua\directlua{ + if ("\luaescapestring{\luatexbanner}"):match("LuaHBTeX") then tex.write("1") end + }\fi>\z@ %% LuaHBTeX is ok + \setfontface\p@emoji@font{%s}[%s] +\else + \@latex@error{You must install a new TeX system (TeX Live 2020)\MessageBreak + and then use 'lualatex' engine to print emoji} + {The compilation will be aborted.} + \let\p@emoji@font\relax +\fi +\ifdefined\ltjdefcharrange +\ltjdefcharrange{208}{ +%s} +\ltjsetparameter{jacharrange={-208}} +\fi +\newcommand*{\panEmoji}[1]{{\p@emoji@font#1}} +\makeatother +]]):format(fname, fopts, dcrsrc) +end + +--- For debug. +local function inspect(v) + local t = type(v) + if t == 'userdata' or t == 'function' or t == 'nil' then return t + elseif t == 'table' then + local u, tag = {}, (v.t or 'table') + if tag == 'Str' then return tag..'{'..v.text..'}' end + for i = 1, #v do u[i] = inspect(v[i]) end + return tag..'{'..concat(u, ';')..'}' + else return tostring(v) + end +end + +---------------------------------------- phase 'readmeta' + +--- For Meta elements. +local function readmeta_Meta (meta) + -- bxcoloremoji + if meta.bxcoloremoji == nil then + bxcoloremoji = false + elseif type(meta.bxcoloremoji) == 'boolean' then + bxcoloremoji = meta.bxcoloremoji + else + abort("not a boolean value: bxcoloremoji") + end + log('bxcoloremoji = %s', bxcoloremoji) + -- emojifont + emojifont = tostring_meta(meta.emojifont, "emojifont") + emojifont = tosingle(emojifont, "emojifont") + log('emojifont = %s', emojifont) + -- emojifontoptions + emojifontoptions = tostring_meta(meta.emojifontoptions, "emojifontoptions") + emojifontoptions = toseq(emojifontoptions) + for i in ipairs(emojifontoptions) do + emojifontoptions[i] = tosingle(emojifontoptions[i], "emojifontoptions element") + log('emojifontoptions = %s', emojifontoptions[i]) + end +end + +---------------------------------------- phase 'mainproc' + +--- For Span element. +local function mainproc_Span(span) + if span.classes:includes('emoji', 1) then + text_count = text_count + 1 + local str = utils.stringify(span.content) + for p, uc in utf8.codes(str) do + if not ucs_used[uc] and uc >= 0x100 then + log("emoji character: U+%04X", uc) + ucs_used[uc] = true + end + end + insert(span.content, 1, pandoc.RawInline('latex', [[\panEmoji{]])) + insert(span.content, pandoc.RawInline('latex', [[}]])) + return span.content + end +end + +--- For Meta elements. +local function mainproc_Meta(meta) + local src = get_header() + if src then + local headers = meta['header-includes'] + if headers == nil then + headers = pandoc.MetaList({}) + elseif pantype(headers) == 'MetaList' then + abort("unexpected metavalue type: header-includes") + end + insert(headers, pandoc.MetaBlocks{pandoc.RawBlock('latex', src)}) + meta['header-includes'] = headers + log("header successfully appended") + return meta + end +end + +--- For the whole document. +local function mainproc_Pandoc(doc) + log("number of emoji spans: %s", text_count) + local src = get_prologue() + if src then + insert(doc.blocks, 1, pandoc.RawBlock('latex', src)) + log("prologue successfully inserted") + return doc + end +end + +---------------------------------------- the filter +if FORMAT == 'latex' then + return { + {-- phase 'readmeta' + Meta = readmeta_Meta; + }; + {-- phase 'mainproc' + Span = mainproc_Span; + Meta = mainproc_Meta; + Pandoc = mainproc_Pandoc; + }; + } +else + log("format '%s' in not supported", FORMAT) +end +---------------------------------------- done \ No newline at end of file diff --git a/main.py b/main.py index da3fb6e..4a9fd2a 100644 --- a/main.py +++ b/main.py @@ -122,7 +122,7 @@ async def generer(specs: DocumentSpecs): datef = datetime.datetime.now().strftime("%m-%d-%Y") os.makedirs("out", exist_ok=True) output_file = f"./out/{specs.style}-{specs.format}-{datef}-output.pdf" - filters = [] + filters = ['latex-emoji.lua'] pdoc_args = [ f'--include-in-header={header_file}', f'--include-after-body={cover_file}', @@ -144,7 +144,7 @@ async def generer(specs: DocumentSpecs): result = pypandoc.convert_text(source=specs.content, to='pdf', - format='markdown+implicit_figures+smart', + format='markdown+implicit_figures+smart+emoji', encoding='utf-8', extra_args=pdoc_args, filters=filters, @@ -157,13 +157,18 @@ async def generer(specs: DocumentSpecs): except OSError as oerr: logging.exception(oerr) if specs.extension in ["png", "jpg"]: - zip_filename = os.path.splitext(os.path.basename(output_file))[0] - png_output_dir = "./png_output" + 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) try: - convert_pdf(output_file, specs.extension, png_output_dir, resolution=300) - shutil.make_archive(zip_filename, 'zip', png_output_dir) + convert_pdf(output_file, + specs.extension, + png_output_dir, + resolution=300) + shutil.make_archive(base_name=zip_filename, + format='zip', + root_dir=png_output_dir) shutil.rmtree(png_output_dir) except Exception as e: logging.exception(e) diff --git a/styles/lcm/format_parameters.json b/styles/lcm/format_parameters.json index 362e38c..41acc47 100644 --- a/styles/lcm/format_parameters.json +++ b/styles/lcm/format_parameters.json @@ -8,7 +8,7 @@ "paperheight": 3507, "ratio": 100, "margin": 248, - "vmargin": 350, + "vmargin": 525, "extension": "pdf" }, "lettre": { @@ -20,7 +20,7 @@ "paperheight": 3300, "ratio": 100, "margin": 255, - "vmargin": 330, + "vmargin": 495, "extension": "pdf" }, "linkedin": { @@ -31,8 +31,8 @@ "paperwidth": 1200, "paperheight": 1200, "ratio": 100, - "margin": 120, - "vmargin": 120, + "margin": 180, + "vmargin": 180, "extension": "jpg" }, "instagram": { @@ -44,7 +44,7 @@ "paperheight": 1920, "ratio": 100, "margin": 108, - "vmargin": 192, + "vmargin": 244, "extension": "jpg" }, "pinterest": { @@ -56,7 +56,7 @@ "paperheight": 1102, "ratio": 100, "margin": 75, - "vmargin": 110, + "vmargin": 165, "extension": "jpg" }, "slide43": { @@ -68,7 +68,7 @@ "paperheight": 1920, "ratio": 100, "margin": 256, - "vmargin": 192, + "vmargin": 288, "extension": "pdf" }, "slide169": { @@ -80,7 +80,7 @@ "paperheight": 1440, "ratio": 100, "margin": 256, - "vmargin": 144, + "vmargin": 216, "extension": "pdf" } } diff --git a/test_main.http b/test_main.http index 2d397aa..9994685 100644 --- a/test_main.http +++ b/test_main.http @@ -10,11 +10,13 @@ Accept: application/zip "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", + "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:", "fontsize": 14, "paperwidth": 1080, "paperheight": 1920, + "ratio": 100, "margin": 180, + "vmargin": 180, "extension": "jpg" } @@ -30,11 +32,13 @@ Accept: application/pdf "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", + "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:", "fontsize": 14, "paperwidth": 1080, "paperheight": 1080, + "ratio": 100, "margin": 90, + "vmargin": 90, "extension": "pdf" } @@ -50,10 +54,11 @@ Accept: application/pdf "linkcolor": "blue", "tocdepth": 2, "pdfengine": "lualatex", - "content": "# Comment améliorer ma sécurité informatique\n\n20 défis gratuits de 2 minutes chacun !", + "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:", "fontsize": 20, "paperwidth": 1080, "paperheight": 1080, + "ratio": 100, "margin": 180, "extension": "jpg", "vmargin": 300 @@ -67,15 +72,17 @@ Accept: application/pdf { "format": "slide169", - "style": "jevalideca", + "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", + "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:", "fontsize": 14, - "paperwidth": 1920, - "paperheight": 1080, - "margin": 90, + "paperwidth": 2560, + "paperheight": 1440, + "ratio": 100, + "margin": 256, + "vmargin": 216, "extension": "jpg" } From 46554aadd07059872fcd5d92a9ecaf6e3fe0eb33 Mon Sep 17 00:00:00 2001 From: Francois Pelletier Date: Sat, 11 Feb 2023 22:52:39 -0500 Subject: [PATCH 3/5] ajout du filtre pour centrer + correction bogue sur la largeur --- centered.lua | 29 +++++++++++++++++++++++++++++ main.py | 6 +++--- test_main.http | 8 ++++---- 3 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 centered.lua diff --git a/centered.lua b/centered.lua new file mode 100644 index 0000000..ac3a39a --- /dev/null +++ b/centered.lua @@ -0,0 +1,29 @@ +-- centered.lua +-- Copyright (c) bpj on GitHub +-- https://github.com/jgm/pandoc/issues/719#issuecomment-922019826 +-- GPL-2+ license + +local center_for = { + latex = { + pre = pandoc.RawBlock('latex', '\\begin{center}'), + post = pandoc.RawBlock('latex', '\\end{center}'), + }, + -- add more as needed... +} + +function Div (div) + if div.classes:includes('center') then + if center_for[FORMAT] then + local rv = {} + if center_for[FORMAT].pre then + rv[#rv+1] = center_for[FORMAT].pre + end + rv[#rv+1] = div + if center_for[FORMAT].post then + rv[#rv+1] = center_for[FORMAT].post + end + return rv + end + end + return nil +end \ No newline at end of file diff --git a/main.py b/main.py index 4a9fd2a..f5c17f3 100644 --- a/main.py +++ b/main.py @@ -119,10 +119,10 @@ async def get_format_parameters(style: str, format: str): 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("%m-%d-%Y") + 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'] + filters = ['latex-emoji.lua', 'centered.lua'] pdoc_args = [ f'--include-in-header={header_file}', f'--include-after-body={cover_file}', @@ -133,7 +133,7 @@ async def generer(specs: DocumentSpecs): '-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.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', diff --git a/test_main.http b/test_main.http index 9994685..28a3f25 100644 --- a/test_main.http +++ b/test_main.http @@ -10,7 +10,7 @@ Accept: application/zip "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:", + "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, @@ -32,7 +32,7 @@ Accept: application/pdf "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:", + "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": 1080, @@ -54,7 +54,7 @@ Accept: application/pdf "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:", + "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": 20, "paperwidth": 1080, "paperheight": 1080, @@ -76,7 +76,7 @@ Accept: application/pdf "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:", + "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, From 4f6f49e31323c76ee942c08bd6586f72385f42fe Mon Sep 17 00:00:00 2001 From: Francois Pelletier Date: Sat, 11 Feb 2023 23:29:09 -0500 Subject: [PATCH 4/5] =?UTF-8?q?les=20noms=20de=20fichiers=20sont=20num?= =?UTF-8?q?=C3=A9rot=C3=A9s=20avec=20du=20padding=20maintenant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index f5c17f3..e1d1955 100644 --- a/main.py +++ b/main.py @@ -72,7 +72,7 @@ def convert_pdf(filename, filetype, output_path, resolution=300): img.alpha_channel = 'remove' image_filename = os.path.splitext(os.path.basename(filename))[0] - image_filename = f'{image_filename}-{i}.{filetype}' + image_filename = f'{image_filename}-{i:03}.{filetype}' image_filename = os.path.join(output_path, image_filename) img.save(filename=image_filename) From bb814381c5e86a3820dcea8c288fe0a6fc6bdcc9 Mon Sep 17 00:00:00 2001 From: Francois Pelletier Date: Sun, 12 Feb 2023 01:04:11 -0500 Subject: [PATCH 5/5] =?UTF-8?q?ajout=20de=20cr=C3=A9ation=20de=20formats?= =?UTF-8?q?=20vid=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