Skip to content
Snippets Groups Projects
Commit 41f5249f authored by Ghazi Triki's avatar Ghazi Triki
Browse files

Updated SVG conversion to simplify complex files.

parent e9e21288
No related branches found
No related tags found
No related merge requests found
package org.bigbluebutton.presentation.handlers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AddNamespaceToSvgHanlder extends AbstractCommandHandler {
private static Logger log = LoggerFactory.getLogger(AddNamespaceToSvgHanlder.class);
}
package org.bigbluebutton.presentation.handlers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Png2SvgConvertionHandler extends AbstractCommandHandler {
private static Logger log = LoggerFactory.getLogger(Png2SvgConvertionHandler.class);
}
package org.bigbluebutton.presentation.handlers;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SvgConversionHandler extends AbstractCommandHandler {
private static Logger log = LoggerFactory.getLogger(SvgConversionHandler.class);
private static String PATH_OUTPUT = "<path";
private static String IMAGE_TAG_OUTPUT = "<image";
private static String PATH_PATTERN = "\\d+\\s" + PATH_OUTPUT;
private static String IMAGE_TAG_PATTERN = "\\d+\\s" + IMAGE_TAG_OUTPUT;
/**
*
* @return The number of <path/> tags in the generated SVG
*/
public int numberOfPaths() {
if (stdoutContains(PATH_OUTPUT)) {
try {
String out = stdoutBuilder.toString();
Pattern r = Pattern.compile(PATH_PATTERN);
Matcher m = r.matcher(out);
m.find();
return Integer.parseInt(m.group(0).replace(PATH_OUTPUT, "").trim());
} catch (Exception e) {
return 0;
}
}
return 0;
}
/**
*
* @return The number of <image/> tags in the generated SVG.
*/
public int numberOfImageTags() {
if (stdoutContains(IMAGE_TAG_OUTPUT)) {
try {
String out = stdoutBuilder.toString();
Pattern r = Pattern.compile(IMAGE_TAG_PATTERN);
Matcher m = r.matcher(out);
m.find();
return Integer.parseInt(m.group(0).replace(IMAGE_TAG_OUTPUT, "").trim());
} catch (Exception e) {
return 0;
}
}
return 0;
}
}
package org.bigbluebutton.presentation.imp;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.bigbluebutton.presentation.SupportedFileTypes;
import org.bigbluebutton.presentation.SvgImageCreator;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.bigbluebutton.presentation.handlers.AddNamespaceToSvgHanlder;
import org.bigbluebutton.presentation.handlers.Pdf2PngPageConverterHandler;
import org.bigbluebutton.presentation.handlers.Png2SvgConvertionHandler;
import org.bigbluebutton.presentation.handlers.SvgConversionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SvgImageCreatorImp implements SvgImageCreator {
private static Logger log = LoggerFactory.getLogger(SvgImageCreatorImp.class);
private String IMAGEMAGICK_DIR;
@Override
public boolean createSvgImages(UploadedPresentation pres) {
boolean success = false;
File svgImagesPresentationDir = determineSvgImagesDirectory(
pres.getUploadedFile());
if (!svgImagesPresentationDir.exists())
svgImagesPresentationDir.mkdir();
import com.google.gson.Gson;
import com.zaxxer.nuprocess.NuProcess;
import com.zaxxer.nuprocess.NuProcessBuilder;
cleanDirectory(svgImagesPresentationDir);
public class SvgImageCreatorImp implements SvgImageCreator {
private static Logger log = LoggerFactory.getLogger(SvgImageCreatorImp.class);
private long imageTagThreshold;
private long pathsThreshold;
private String convTimeout = "7s";
private int WAIT_FOR_SEC = 7;
@Override
public boolean createSvgImages(UploadedPresentation pres) {
boolean success = false;
File svgImagesPresentationDir = determineSvgImagesDirectory(pres.getUploadedFile());
if (!svgImagesPresentationDir.exists())
svgImagesPresentationDir.mkdir();
try {
FileUtils.cleanDirectory(svgImagesPresentationDir);
success = generateSvgImages(svgImagesPresentationDir, pres);
} catch (Exception e) {
log.warn("Interrupted Exception while generating images.");
success = false;
}
try {
success = generateSvgImages(svgImagesPresentationDir, pres);
} catch (InterruptedException e) {
log.warn("Interrupted Exception while generating images.");
success = false;
return success;
}
return success;
}
private boolean generateSvgImages(File imagePresentationDir,
UploadedPresentation pres) throws InterruptedException {
String source = pres.getUploadedFile().getAbsolutePath();
String dest;
String COMMAND = "";
boolean done = true;
if (SupportedFileTypes.isImageFile(pres.getFileType())) {
dest = imagePresentationDir.getAbsolutePath() + File.separator
+ "slide1.pdf";
COMMAND = IMAGEMAGICK_DIR + File.separator + "convert " + source + " "
+ dest;
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
source = imagePresentationDir.getAbsolutePath() + File.separator
+ "slide1.pdf";
dest = imagePresentationDir.getAbsolutePath() + File.separator
+ "slide1.svg";
COMMAND = "pdftocairo -rx 300 -ry 300 -svg -q -f 1 -l 1 " + source + " "
+ dest;
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
} else {
for (int i = 1; i <= pres.getNumberOfPages(); i++) {
File destsvg = new File(imagePresentationDir.getAbsolutePath()
+ File.separatorChar + "slide" + i + ".svg");
COMMAND = "pdftocairo -rx 300 -ry 300 -svg -q -f " + i + " -l " + i
+ " " + File.separatorChar + source + " "
+ destsvg.getAbsolutePath();
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
if (!done) {
break;
private boolean generateSvgImages(File imagePresentationDir, UploadedPresentation pres)
throws InterruptedException {
String source = pres.getUploadedFile().getAbsolutePath();
String dest;
int numSlides;
boolean done = true;
if (SupportedFileTypes.isImageFile(pres.getFileType())) {
numSlides = 1;
dest = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.pdf";
NuProcessBuilder convertImgToSvg = new NuProcessBuilder(Arrays.asList("convert " + source + " " + dest));
NuProcess process = convertImgToSvg.start();
try {
process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
done = true;
} catch (InterruptedException e) {
done = false;
log.error(e.getMessage());
}
source = imagePresentationDir.getAbsolutePath() + File.separator + "slide1.pdf";
} else {
numSlides = pres.getNumberOfPages();
}
}
}
for (int i = 1; i <= numSlides; i++) {
int numPages = 0; // total numbers of this SVG
File destsvg = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + i + ".svg");
NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", i, source, destsvg.getAbsolutePath(),
true);
SvgConversionHandler pHandler = new SvgConversionHandler();
convertPdfToSvg.setProcessListener(pHandler);
NuProcess process = convertPdfToSvg.start();
try {
process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
done = true;
} catch (InterruptedException e) {
log.error(e.getMessage());
}
if (!done) {
break;
}
if (destsvg.length() == 0 || pHandler.numberOfImageTags() > imageTagThreshold
|| pHandler.numberOfPaths() > pathsThreshold) {
// We need t delete the destination file as we are starting a
// new conversion process
if (destsvg.exists()) {
destsvg.delete();
}
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", i);
logData.put("convertSuccess", pHandler.isCommandSuccessful());
logData.put("fileExists", destsvg.exists());
logData.put("numberOfImages", pHandler.numberOfImageTags());
logData.put("numberOfPaths", pHandler.numberOfPaths());
logData.put("message", "Potential problem with generated SVG");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn("-- analytics -- {}", logStr);
File tempPng = null;
String basePresentationame = FilenameUtils.getBaseName(pres.getName());
try {
tempPng = File.createTempFile(basePresentationame + "-" + i, ".png");
} catch (IOException ioException) {
// We should never fall into this if the server is correctly
// configured
logData = new HashMap<String, Object>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("message", "Unable to create temporary files");
gson = new Gson();
logStr = gson.toJson(logData);
log.error("-- analytics -- {}", logStr);
}
// Step 1: Convert a PDF page to PNG using a raw pdftocairo
NuProcessBuilder convertPdfToPng = createConversionProcess("-png", i, source,
tempPng.getAbsolutePath().substring(0, tempPng.getAbsolutePath().lastIndexOf('.')), false);
Pdf2PngPageConverterHandler pngHandler = new Pdf2PngPageConverterHandler();
convertPdfToPng.setProcessListener(pngHandler);
NuProcess pngProcess = convertPdfToPng.start();
try {
pngProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error(e.getMessage());
}
// Step 2: Convert a PNG image to SVG
NuProcessBuilder convertPngToSvg = new NuProcessBuilder(Arrays.asList("timeout", convTimeout, "convert",
tempPng.getAbsolutePath(), destsvg.getAbsolutePath()));
Png2SvgConvertionHandler svgHandler = new Png2SvgConvertionHandler();
convertPngToSvg.setProcessListener(svgHandler);
NuProcess svgProcess = convertPngToSvg.start();
try {
svgProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error(e.getMessage());
}
done = svgHandler.isCommandSuccessful();
// Delete the temporary PNG after finishing the image conversion
tempPng.delete();
// Step 3: Add SVG namespace to the destionation file
// Check : https://phabricator.wikimedia.org/T43174
NuProcessBuilder addNameSpaceToSVG = new NuProcessBuilder(Arrays.asList("timeout", convTimeout,
"/bin/sh", "-c",
"sed -i "
+ "'4s|>| xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.2\">|' "
+ destsvg.getAbsolutePath()));
AddNamespaceToSvgHanlder namespaceHandler = new AddNamespaceToSvgHanlder();
addNameSpaceToSVG.setProcessListener(namespaceHandler);
NuProcess namespaceProcess = addNameSpaceToSVG.start();
try {
namespaceProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error(e.getMessage());
}
}
if (done) {
return true;
}
}
if (done) {
return true;
}
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("message", "Failed to create svg images.");
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("message", "Failed to create svg images.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn("-- analytics -- " + logStr);
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn("-- analytics -- " + logStr);
return false;
}
return false;
}
private File determineSvgImagesDirectory(File presentationFile) {
return new File(presentationFile.getParent() + File.separatorChar + "svgs");
}
private NuProcessBuilder createConversionProcess(String format, int page, String source, String destFile,
boolean analyze) {
String rawCommand = "pdftocairo -r " + (analyze ? " 300 " : " 150 ") + format + (analyze ? "" : " -singlefile")
+ " -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", convTimeout, "/bin/sh", "-c", rawCommand));
}
private void cleanDirectory(File directory) {
File[] files = directory.listFiles();
for (int i = 0; i < files.length; i++) {
files[i].delete();
private File determineSvgImagesDirectory(File presentationFile) {
return new File(presentationFile.getParent() + File.separatorChar + "svgs");
}
}
public void setImageMagickDir(String imageMagickDir) {
IMAGEMAGICK_DIR = imageMagickDir;
}
public void setImageTagThreshold(long threshold) {
imageTagThreshold = threshold;
}
public void setPathsThreshold(long threshold) {
pathsThreshold = threshold;
}
}
......@@ -73,7 +73,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<bean id="textFileCreator" class="org.bigbluebutton.presentation.imp.TextFileCreatorImp"/>
<bean id="svgImageCreator" class="org.bigbluebutton.presentation.imp.SvgImageCreatorImp">
<property name="imageMagickDir" value="${imageMagickDir}"/>
<property name="imageTagThreshold" value="${imageTagThreshold}"/>
<property name="pathsThreshold" value="${placementsThreshold}"/>
</bean>
<bean id="generatedSlidesInfoHelper" class="org.bigbluebutton.presentation.GeneratedSlidesInfoHelperImp"/>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment