From b6e0a571d5e34ca194f54f3bfa6d6fbd1e857dc4 Mon Sep 17 00:00:00 2001 From: Gustavo Trott <gustavo@trott.com.br> Date: Thu, 22 Apr 2021 21:57:55 -0300 Subject: [PATCH] Implements forceRasterizeSlides (force convert presentation to png before svg) and pngWidthRasterizedSlides (force png width (px)) --- .../presentation/imp/SvgImageCreatorImp.java | 101 +++++++++++------- .../grails-app/conf/bigbluebutton.properties | 17 +++ .../grails-app/conf/spring/doc-conversion.xml | 2 + 3 files changed, 83 insertions(+), 37 deletions(-) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java index 01659895df..50e654e902 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SvgImageCreatorImp.java @@ -31,6 +31,8 @@ public class SvgImageCreatorImp implements SvgImageCreator { private long pathsThreshold; private int convPdfToSvgTimeout = 60; private int svgResolutionPpi = 300; + private boolean forceRasterizeSlides = false; + private int pngWidthRasterizedSlides = 2048; private String BLANK_SVG; @Override @@ -93,30 +95,37 @@ public class SvgImageCreatorImp implements SvgImageCreator { File destsvg = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + page + ".svg"); - NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", page, source, destsvg.getAbsolutePath(), + SvgConversionHandler pHandler = new SvgConversionHandler(); + + if(this.forceRasterizeSlides == false) { + NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", page, source, destsvg.getAbsolutePath(), true); - SvgConversionHandler pHandler = new SvgConversionHandler(); - convertPdfToSvg.setProcessListener(pHandler); + convertPdfToSvg.setProcessListener(pHandler); - NuProcess process = convertPdfToSvg.start(); - try { - process.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS); - done = true; - } catch (InterruptedException e) { - log.error("Interrupted Exception while generating SVG slides {}", pres.getName(), e); - } + NuProcess process = convertPdfToSvg.start(); + try { + process.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS); + done = true; + } catch (InterruptedException e) { + log.error("Interrupted Exception while generating SVG slides {}", pres.getName(), e); + } - if(pHandler.isCommandTimeout()) { - log.error("Command execution (convertPdfToSvg) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page); - } + if(pHandler.isCommandTimeout()) { + log.error("Command execution (convertPdfToSvg) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page); + } - if (!done) { - return done; + if (!done) { + return done; + } } - if (destsvg.length() == 0 || pHandler.numberOfImageTags() > imageTagThreshold - || pHandler.numberOfPaths() > pathsThreshold) { + + if (destsvg.length() == 0 || + pHandler.numberOfImageTags() > imageTagThreshold || + pHandler.numberOfPaths() > pathsThreshold || + this.forceRasterizeSlides) { + // We need t delete the destination file as we are starting a // new conversion process if (destsvg.exists()) { @@ -125,21 +134,24 @@ public class SvgImageCreatorImp implements SvgImageCreator { done = false; - Map<String, Object> logData = new HashMap<String, Object>(); - logData.put("meetingId", pres.getMeetingId()); - logData.put("presId", pres.getId()); - logData.put("filename", pres.getName()); - logData.put("page", page); - logData.put("convertSuccess", pHandler.isCommandSuccessful()); - logData.put("fileExists", destsvg.exists()); - logData.put("numberOfImages", pHandler.numberOfImageTags()); - logData.put("numberOfPaths", pHandler.numberOfPaths()); - logData.put("logCode", "potential_problem_with_svg"); - logData.put("message", "Potential problem with generated SVG"); - Gson gson = new Gson(); - String logStr = gson.toJson(logData); - - log.warn(" --analytics-- data={}", logStr); + if(!this.forceRasterizeSlides) { + Map<String, Object> logData = new HashMap<String, Object>(); + logData.put("meetingId", pres.getMeetingId()); + logData.put("presId", pres.getId()); + logData.put("filename", pres.getName()); + logData.put("page", page); + logData.put("convertSuccess", pHandler.isCommandSuccessful()); + logData.put("fileExists", destsvg.exists()); + logData.put("numberOfImages", pHandler.numberOfImageTags()); + logData.put("numberOfPaths", pHandler.numberOfPaths()); + logData.put("logCode", "potential_problem_with_svg"); + logData.put("message", "Potential problem with generated SVG"); + Gson gson = new Gson(); + String logStr = gson.toJson(logData); + + log.warn(" --analytics-- data={}", logStr); + } + File tempPng = null; String basePresentationame = UUID.randomUUID().toString(); @@ -148,14 +160,15 @@ public class SvgImageCreatorImp implements SvgImageCreator { } catch (IOException ioException) { // We should never fall into this if the server is correctly // configured + Map<String, Object> logData = new HashMap<String, Object>(); logData = new HashMap<String, Object>(); logData.put("meetingId", pres.getMeetingId()); logData.put("presId", pres.getId()); logData.put("filename", pres.getName()); logData.put("logCode", "problem_with_creating_svg"); logData.put("message", "Unable to create temporary files"); - gson = new Gson(); - logStr = gson.toJson(logData); + Gson gson = new Gson(); + String logStr = gson.toJson(logData); log.error(" --analytics-- data={}", logStr, ioException); } @@ -178,7 +191,6 @@ public class SvgImageCreatorImp implements SvgImageCreator { if(tempPng.length() > 0) { // Step 2: Convert a PNG image to SVG - NuProcessBuilder convertPngToSvg = new NuProcessBuilder(Arrays.asList("timeout", convPdfToSvgTimeout + "s", "convert", tempPng.getAbsolutePath(), destsvg.getAbsolutePath())); @@ -254,12 +266,19 @@ public class SvgImageCreatorImp implements SvgImageCreator { private NuProcessBuilder createConversionProcess(String format, int page, String source, String destFile, boolean analyze) { - String rawCommand = "pdftocairo -r " + this.svgResolutionPpi + " " + format + (analyze ? "" : " -singlefile") - + " -q -f " + String.valueOf(page) + " -l " + String.valueOf(page) + " " + source + " " + destFile; + String rawCommand = "pdftocairo -r " + this.svgResolutionPpi + " " + format + (analyze ? "" : " -singlefile"); + + //Resize png resolution to avoid too large files + if(format.equals("-png") && this.pngWidthRasterizedSlides != 0) { + rawCommand += " -scale-to-x " + this.pngWidthRasterizedSlides + " -scale-to-y -1 "; + } + + rawCommand += " -q -f " + String.valueOf(page) + " -l " + String.valueOf(page) + " " + source + " " + destFile; if (analyze) { rawCommand += " && cat " + destFile; rawCommand += " | egrep 'data:image/png;base64|<path' | sed 's/ / /g' | cut -d' ' -f 1 | sort | uniq -cw 2"; } + return new NuProcessBuilder(Arrays.asList("timeout", convPdfToSvgTimeout + "s", "/bin/sh", "-c", rawCommand)); } @@ -314,4 +333,12 @@ public class SvgImageCreatorImp implements SvgImageCreator { public void setSvgResolutionPpi(int svgResolutionPpi) { this.svgResolutionPpi = svgResolutionPpi; } + + public void setForceRasterizeSlides(boolean forceRasterizeSlides) { + this.forceRasterizeSlides = forceRasterizeSlides; + } + + public void setPngWidthRasterizedSlides(int pngWidthRasterizedSlides) { + this.pngWidthRasterizedSlides = pngWidthRasterizedSlides; + } } diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index d0a309d8ae..2a12b773a6 100755 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -108,6 +108,23 @@ svgConversionTimeout=60 #------------------------------------ svgPresentationResolutionPpi=300 +#------------------------------------ +# Force conversion of slides to PNG before converting to SVG +## This will solve problems like reported in issue #8835 +## Disabled by default as it can affect the quality in zoom +#------------------------------------ +forceRasterizeSlides=false + +#------------------------------------ +# Presentation will be resized to this width (in pixels) when rasterizing (converting to PNG) +## Applied in these situations: +## a) the source can't be converted directly to SVG ; +## b) option "forceRasterizeSlides" is defined as true ; +## To disable this constraint (and keep source resolution) define this property as 0. +#------------------------------------ +pngWidthRasterizedSlides=2048 + + #------------------------------------ # Timeout(secs) to wait for conversion script execution #------------------------------------ diff --git a/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml b/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml index 1120fe7dbb..7f4f9da4cb 100755 --- a/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml +++ b/bigbluebutton-web/grails-app/conf/spring/doc-conversion.xml @@ -88,6 +88,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="blankSvg" value="${BLANK_SVG}"/> <property name="convPdfToSvgTimeout" value="${svgConversionTimeout}"/> <property name="svgResolutionPpi" value="${svgPresentationResolutionPpi}"/> + <property name="forceRasterizeSlides" value="${forceRasterizeSlides}"/> + <property name="pngWidthRasterizedSlides" value="${pngWidthRasterizedSlides}"/> </bean> <bean id="generatedSlidesInfoHelper" class="org.bigbluebutton.presentation.GeneratedSlidesInfoHelperImp"/> -- GitLab