Merge pull request #5338 from ritzalam/generate-png-images
- generate png files for uploaded presentation
This commit is contained in:
commit
bc4ed416de
23
bbb-common-web/src/main/java/org/bigbluebutton/presentation/PngCreator.java
Executable file
23
bbb-common-web/src/main/java/org/bigbluebutton/presentation/PngCreator.java
Executable 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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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']
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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 = {
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user