Merge pull request #5338 from ritzalam/generate-png-images

- generate png files for uploaded presentation
This commit is contained in:
Chad Pilkey 2018-04-13 15:03:55 -04:00 committed by GitHub
commit bc4ed416de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 270 additions and 11 deletions

View File

@ -0,0 +1,23 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
* <p>
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
* <p>
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
* <p>
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* <p>
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*/
package org.bigbluebutton.presentation;
public interface PngCreator {
public boolean createPng(UploadedPresentation pres);
}

View File

@ -33,15 +33,8 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.bigbluebutton.presentation.ConversionMessageConstants;
import org.bigbluebutton.presentation.ConversionUpdateMessage;
import org.bigbluebutton.presentation.*;
import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
import org.bigbluebutton.presentation.PageConverter;
import org.bigbluebutton.presentation.PdfToSwfSlide;
import org.bigbluebutton.presentation.SvgImageCreator;
import org.bigbluebutton.presentation.TextFileCreator;
import org.bigbluebutton.presentation.ThumbnailCreator;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.bigbluebutton.presentation.messages.DocPageCountExceeded;
import org.bigbluebutton.presentation.messages.DocPageCountFailed;
import org.slf4j.Logger;
@ -57,12 +50,16 @@ public class PdfToSwfSlidesGenerationService {
private PageConverter pdfToSwfConverter;
private ExecutorService executor;
private ThumbnailCreator thumbnailCreator;
private PngCreator pngCreator;
private TextFileCreator textFileCreator;
private SvgImageCreator svgImageCreator;
private long MAX_CONVERSION_TIME = 5 * 60 * 1000;
private String BLANK_SLIDE;
private int MAX_SWF_FILE_SIZE;
private boolean svgImagesRequired;
private boolean generatePngs;
private final long CONVERSION_TIMEOUT = 20000000000L; // 20s
public PdfToSwfSlidesGenerationService(int numConversionThreads) {
@ -81,6 +78,11 @@ public class PdfToSwfSlidesGenerationService {
createSvgImages(pres);
}
// only create PNG images if the configuration requires it
if (generatePngs) {
createPngImages(pres);
}
notifier.sendConversionCompletedMessage(pres);
}
}
@ -161,6 +163,10 @@ public class PdfToSwfSlidesGenerationService {
svgImageCreator.createSvgImages(pres);
}
private void createPngImages(UploadedPresentation pres) {
pngCreator.createPng(pres);
}
private void convertPdfToSwf(UploadedPresentation pres) {
int numPages = pres.getNumberOfPages();
List<PdfToSwfSlide> slides = setupSlides(pres, numPages);
@ -318,14 +324,22 @@ public class PdfToSwfSlidesGenerationService {
this.MAX_SWF_FILE_SIZE = size;
}
public void setSvgImagesRequired(boolean svg) {
this.svgImagesRequired = svg;
public void setGeneratePngs(boolean generatePngs) {
this.generatePngs = generatePngs;
}
public void setSvgImagesRequired(boolean svg) {
this.svgImagesRequired = svg;
}
public void setThumbnailCreator(ThumbnailCreator thumbnailCreator) {
this.thumbnailCreator = thumbnailCreator;
}
public void setPngCreator(PngCreator pngCreator) {
this.pngCreator = pngCreator;
}
public void setTextFileCreator(TextFileCreator textFileCreator) {
this.textFileCreator = textFileCreator;
}

View File

@ -0,0 +1,173 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
* <p>
* Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below).
* <p>
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
* <p>
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* <p>
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*/
package org.bigbluebutton.presentation.imp;
import com.google.gson.Gson;
import org.apache.commons.io.FileUtils;
import org.bigbluebutton.presentation.PngCreator;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PngCreatorImp implements PngCreator {
private static Logger log = LoggerFactory.getLogger(PngCreatorImp.class);
private static final Pattern PAGE_NUMBER_PATTERN = Pattern.compile("(.+-png)-([0-9]+)(.png)");
private String BLANK_PNG;
private int slideWidth = 800;
private static String TEMP_PNG_NAME = "temp-png";
public boolean createPng(UploadedPresentation pres) {
boolean success = false;
File pngDir = determinePngDirectory(pres.getUploadedFile());
if (!pngDir.exists())
pngDir.mkdir();
cleanDirectory(pngDir);
try {
success = generatePngs(pngDir, pres);
} catch (InterruptedException e) {
log.warn("Interrupted Exception while generating png.");
success = false;
}
// Create blank thumbnails for pages that failed to generate a thumbnail.
createBlankPngs(pngDir, pres.getNumberOfPages());
renamePng(pngDir);
return success;
}
private boolean generatePngs(File pngsDir, UploadedPresentation pres)
throws InterruptedException {
String source = pres.getUploadedFile().getAbsolutePath();
String dest;
String COMMAND = "";
dest = pngsDir.getAbsolutePath() + File.separator + TEMP_PNG_NAME;
COMMAND = "pdftocairo -png -scale-to " + slideWidth + " " + source + " " + dest;
boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000);
if (done) {
return true;
} else {
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 png.");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn("-- analytics -- " + logStr);
}
return false;
}
private File determinePngDirectory(File presentationFile) {
return new File(presentationFile.getParent() + File.separatorChar + "pngs");
}
private void renamePng(File dir) {
/*
* If more than 1 file, filename like 'temp-png-X.png' else filename is
* 'temp-png.png'
*/
if (dir.list().length > 1) {
File[] files = dir.listFiles();
Matcher matcher;
for (int i = 0; i < files.length; i++) {
matcher = PAGE_NUMBER_PATTERN.matcher(files[i].getAbsolutePath());
if (matcher.matches()) {
// Path should be something like
// 'c:/temp/bigluebutton/presname/pngs/temp-png-1.png'
// Extract the page number. There should be 4 matches.
// 0. c:/temp/bigluebutton/presname/pngs/temp-png-1.png
// 1. c:/temp/bigluebutton/presname/pngs/temp-png
// 2. 1 ---> what we are interested in
// 3. .png
// We are interested in the second match.
int pageNum = Integer.valueOf(matcher.group(2).trim()).intValue();
String newFilename = "slide-" + (pageNum) + ".png";
File renamedFile = new File(
dir.getAbsolutePath() + File.separator + newFilename);
files[i].renameTo(renamedFile);
}
}
} else if (dir.list().length == 1) {
File oldFilename = new File(
dir.getAbsolutePath() + File.separator + dir.list()[0]);
String newFilename = "slide-1.png";
File renamedFile = new File(
oldFilename.getParent() + File.separator + newFilename);
oldFilename.renameTo(renamedFile);
}
}
private void createBlankPngs(File pngsDir, int pageCount) {
File[] pngs = pngsDir.listFiles();
if (pngs.length != pageCount) {
for (int i = 0; i < pageCount; i++) {
File png = new File(pngsDir.getAbsolutePath() + File.separator + TEMP_PNG_NAME + "-" + i + ".png");
if (!png.exists()) {
log.info("Copying blank png for slide " + i);
copyBlankPng(png);
}
}
}
}
private void copyBlankPng(File png) {
try {
FileUtils.copyFile(new File(BLANK_PNG), png);
} catch (IOException e) {
log.error("IOException while copying blank thumbnail.");
}
}
private void cleanDirectory(File directory) {
File[] files = directory.listFiles();
for (int i = 0; i < files.length; i++) {
files[i].delete();
}
}
public void setBlankPng(String blankPng) {
BLANK_PNG = blankPng;
}
public void setSlideWidth(int width) {
slideWidth = width;
}
}

View File

@ -29,6 +29,10 @@ class UrlMappings {
action = [GET:'showThumbnail']
}
"/presentation/$conference/$room/$presentation_name/png/$id"(controller:"presentation") {
action = [GET:'showPng']
}
"/presentation/$conference/$room/$presentation_name/svgs"(controller:"presentation") {
action = [GET:'numberOfSvgs']
}

View File

@ -50,6 +50,7 @@ presCheckExec=/usr/share/prescheck/prescheck.sh
BLANK_SLIDE=/var/bigbluebutton/blank/blank-slide.swf
BLANK_PRESENTATION=/var/bigbluebutton/blank/blank-presentation.pdf
BLANK_THUMBNAIL=/var/bigbluebutton/blank/blank-thumb.png
BLANK_PNG=/var/bigbluebutton/blank/blank-png.png
#----------------------------------------------------
# Number of minutes the conversion should take. If it takes
@ -88,6 +89,12 @@ numConversionThreads=2
# to be used in the HTML5 client
svgImagesRequired=false
#----------------------------------------------------
# Additional conversion of the presentation slides to PNG
# to be used in the IOS mobile client
generatePngs=true
pngSlideWidth=1200
# Default number of digits for voice conference users joining through the PSTN.
defaultNumDigitsForTelVoice=5

View File

@ -70,6 +70,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="blankThumbnail" value="${BLANK_THUMBNAIL}"/>
</bean>
<bean id="pngCreator" class="org.bigbluebutton.presentation.imp.PngCreatorImp">
<property name="blankPng" value="${BLANK_PNG}"/>
<property name="slideWidth" value="${pngSlideWidth}"/>
</bean>
<bean id="textFileCreator" class="org.bigbluebutton.presentation.imp.TextFileCreatorImp"/>
<bean id="svgImageCreator" class="org.bigbluebutton.presentation.imp.SvgImageCreatorImp">
@ -84,6 +89,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="counterService" ref="pageCounterService"/>
<property name="pageConverter" ref="pdf2SwfPageConverter"/>
<property name="thumbnailCreator" ref="thumbCreator"/>
<property name="pngCreator" ref="pngCreator"/>
<property name="textFileCreator" ref="textFileCreator"/>
<property name="svgImageCreator" ref="svgImageCreator"/>
<property name="blankSlide" value="${BLANK_SLIDE}"/>
@ -91,6 +97,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="maxConversionTime" value="${maxConversionTime}"/>
<property name="swfSlidesGenerationProgressNotifier" ref="swfSlidesGenerationProgressNotifier"/>
<property name="svgImagesRequired" value="${svgImagesRequired}"/>
<property name="generatePngs" value="${generatePngs}"/>
</bean>
<bean id="imageToSwfSlidesGenerationService"

View File

@ -224,6 +224,29 @@ class PresentationController {
return null;
}
def showPng = {
def presentationName = params.presentation_name
def conf = params.conference
def rm = params.room
def png = params.id
InputStream is = null;
try {
def pres = presentationService.showPng(conf, rm, presentationName, png)
if (pres.exists()) {
def bytes = pres.readBytes()
response.addHeader("Cache-Control", "no-cache")
response.contentType = 'image'
response.outputStream << bytes;
}
} catch (IOException e) {
log.error("Error reading file.\n" + e.getMessage());
}
return null;
}
def showTextfile = {
def presentationName = params.presentation_name
@ -340,7 +363,7 @@ class PresentationController {
}
}
}
}
}
}
def numberOfSvgs = {

View File

@ -109,6 +109,14 @@ class PresentationService {
new File(thumbFile)
}
def showPng = {conf, room, presentationName, page ->
def pngFile = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar +
"pngs" + File.separatorChar + "slide-${page}.png"
log.debug "showing $pngFile"
new File(pngFile)
}
def showTextfile = {conf, room, presentationName, textfile ->
def txt = roomDirectory(conf, room).absolutePath + File.separatorChar + presentationName + File.separatorChar +
"textfiles" + File.separatorChar + "slide-${textfile}.txt"