This is an automated email from the ASF dual-hosted git repository. solomax pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openmeetings.git
The following commit(s) were added to refs/heads/master by this push: new f1b65ef [OPENMEETINGS-2592] recording in interview room should work, code clean-up new 84b0783 Merge branch 'master' of github.com:apache/openmeetings f1b65ef is described below commit f1b65efd2daaf8654ca531ae8eece5a47a5abb79 Author: Maxim Solodovnik <solomax...@gmail.com> AuthorDate: Fri Mar 12 07:58:54 2021 +0700 [OPENMEETINGS-2592] recording in interview room should work, code clean-up --- .../openmeetings/core/converter/BaseConverter.java | 54 +++---- .../core/converter/ImageConverter.java | 31 ++-- .../core/converter/InterviewConverter.java | 161 ++++++++++++--------- .../core/converter/RecordingConverter.java | 2 +- .../core/converter/VideoConverter.java | 12 +- .../apache/openmeetings/core/remote/KStream.java | 128 +++++++++------- openmeetings-db/pom.xml | 1 - .../openmeetings/util/process/ProcessHelper.java | 21 ++- .../web/pages/install/InstallWizard.java | 11 +- 9 files changed, 231 insertions(+), 190 deletions(-) diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/BaseConverter.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/BaseConverter.java index 941c31d..e19df90 100644 --- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/BaseConverter.java +++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/BaseConverter.java @@ -62,7 +62,7 @@ public abstract class BaseConverter { private static final Pattern p = Pattern.compile("\\d{2,5}(x)\\d{2,5}"); public static final String EXEC_EXT = System.getProperty("os.name").toUpperCase(Locale.ROOT).indexOf("WINDOWS") < 0 ? "" : ".exe"; private static final int MINUTE_MULTIPLIER = 60 * 1000; - public static final int TIME_TO_WAIT_FOR_FRAME = 15 * MINUTE_MULTIPLIER; + public static final int TIME_TO_WAIT_FOR_FRAME = 5 * MINUTE_MULTIPLIER; public static final double HALF_STEP = 1. / 2; @Autowired @@ -140,7 +140,7 @@ public abstract class BaseConverter { } } - private String[] mergeAudioToWaves(List<File> waveFiles, File wav) throws IOException { + private List<String> mergeAudioToWaves(List<File> waveFiles, File wav) throws IOException { List<String> argv = new ArrayList<>(); argv.add(getPathToSoX()); @@ -150,7 +150,7 @@ public abstract class BaseConverter { } argv.add(wav.getCanonicalPath()); - return argv.toArray(new String[0]); + return argv; } protected void createWav(Recording r, ProcessResultList logs, File streamFolder, List<File> waveFiles, File wav, List<RecordingChunk> chunks) throws IOException { @@ -163,27 +163,25 @@ public abstract class BaseConverter { // Calculate delta at beginning double duration = diffSeconds(r.getRecordEnd(), r.getRecordStart()); - String[] cmd = new String[] { getPathToSoX(), oneSecWav, wav.getCanonicalPath(), "pad", "0", String.valueOf(duration) }; + List<String> cmd = List.of(getPathToSoX(), oneSecWav, wav.getCanonicalPath(), "pad", "0", String.valueOf(duration)); - logs.add(ProcessHelper.executeScript("generateSampleAudio", cmd)); + logs.add(ProcessHelper.exec("generateSampleAudio", cmd)); } else if (waveFiles.size() == 1) { copyFile(waveFiles.get(0), wav); } else { - String[] soxArgs = mergeAudioToWaves(waveFiles, wav); - - logs.add(ProcessHelper.executeScript("mergeAudioToWaves", soxArgs)); + logs.add(ProcessHelper.exec("mergeAudioToWaves", mergeAudioToWaves(waveFiles, wav))); } } - private String[] addSoxPad(ProcessResultList logs, String job, double length, double position, File inFile, File outFile) throws IOException { + private List<String> addSoxPad(ProcessResultList logs, String job, double length, double position, File inFile, File outFile) throws IOException { if (length < 0 || position < 0) { log.debug("::addSoxPad {} Invalid parameters: length = {}; position = {}; inFile = {}", job, length, position, inFile); } - String[] argv = new String[] { getPathToSoX(), inFile.getCanonicalPath(), outFile.getCanonicalPath(), "pad" + List<String> argv = List.of(getPathToSoX(), inFile.getCanonicalPath(), outFile.getCanonicalPath(), "pad" , String.valueOf(length < 0 ? 0 : length) - , String.valueOf(position < 0 ? 0 : position) }; + , String.valueOf(position < 0 ? 0 : position)); - logs.add(ProcessHelper.executeScript(job, argv)); + logs.add(ProcessHelper.exec(job, argv)); return argv; } @@ -192,7 +190,7 @@ public abstract class BaseConverter { log.debug("### {}:: recording id {}; stream with id {}; current status: {} ", prefix, chunk.getRecording().getId() , chunk.getId(), chunk.getStreamStatus()); File chunkFlv = getRecordingChunk(chunk.getRecording().getRoomId(), chunk.getStreamName()); - log.debug("### {}:: Flv file [{}] exists ? {}; size: {}, lastModified: {} ", prefix, chunkFlv.getPath(), chunkFlv.exists(), chunkFlv.length(), chunkFlv.lastModified()); + log.debug("### {}:: Chunk file [{}] exists ? {}; size: {}, lastModified: {} ", prefix, chunkFlv.getPath(), chunkFlv.exists(), chunkFlv.length(), chunkFlv.lastModified()); } } @@ -259,13 +257,13 @@ public abstract class BaseConverter { log.debug("FLV File Name: {} Length: {} ", inputFlvFile.getName(), inputFlvFile.length()); if (inputFlvFile.exists()) { - String[] argv = new String[] { + List<String> argv = List.of( getPathToFFMPEG(), "-y" , "-i", inputFlvFile.getCanonicalPath() , "-af", String.format("aresample=%s:min_comp=0.001:min_hard_comp=0.100000", getAudioBitrate()) - , outputWav.getCanonicalPath()}; + , outputWav.getCanonicalPath()); //there might be no audio in the stream - logs.add(ProcessHelper.executeScript("stripAudioFromFLVs", argv, true)); + logs.add(ProcessHelper.exec("stripAudioFromFLVs", argv, true)); } if (outputWav.exists() && outputWav.length() != 0) { @@ -319,13 +317,12 @@ public abstract class BaseConverter { return List.of(); } - private List<String> addMp4OutParams(Recording r, List<String> argv, String mp4path) { + private List<String> addMp4OutParams(Recording r, List<String> argv, boolean interview, String mp4path) { argv.addAll(List.of( "-c:v", "h264" // , "-crf", "24" , "-vsync", "0" , "-pix_fmt", "yuv420p" - , "-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2" , "-preset", getVideoPreset() , "-profile:v", "baseline" , "-level", "3.0" @@ -334,16 +331,19 @@ public abstract class BaseConverter { , "-ar", String.valueOf(getAudioRate()) , "-b:a", getAudioBitrate() )); + if (!interview) { + argv.addAll(List.of("-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2")); + } argv.addAll(additionalMp4OutParams(r)); argv.add(mp4path); return argv; } - protected String convertToMp4(Recording r, List<String> inArgv, ProcessResultList logs) throws IOException { + protected String convertToMp4(Recording r, List<String> inArgv, boolean interview, ProcessResultList logs) throws IOException { String mp4path = r.getFile().getCanonicalPath(); List<String> argv = new ArrayList<>(List.of(getPathToFFMPEG(), "-y")); argv.addAll(inArgv); - logs.add(ProcessHelper.executeScript("generate MP4", addMp4OutParams(r, argv, mp4path).toArray(new String[]{}))); + logs.add(ProcessHelper.exec("generate MP4", addMp4OutParams(r, argv, interview, mp4path))); return mp4path; } @@ -351,13 +351,13 @@ public abstract class BaseConverter { // Extract first Image for preview purpose // ffmpeg -i movie.mp4 -vf "thumbnail,scale=640:-1" -frames:v 1 movie.png File png = f.getFile(EXTENSION_PNG); - String[] argv = new String[] { // - getPathToFFMPEG(), "-y" // - , "-i", mp4path // - , "-vf", "thumbnail,scale=640:-1" // - , "-frames:v", "1" // - , png.getCanonicalPath() }; - logs.add(ProcessHelper.executeScript(String.format("generate preview PNG :: %s", f.getHash()), argv)); + List<String> argv = List.of( + getPathToFFMPEG(), "-y" + , "-i", mp4path + , "-vf", "thumbnail,scale=640:-1" + , "-frames:v", "1" + , png.getCanonicalPath()); + logs.add(ProcessHelper.exec("generate preview PNG :: " + f.getHash(), argv)); } /** diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/ImageConverter.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/ImageConverter.java index c327dda..a6dc993 100644 --- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/ImageConverter.java +++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/ImageConverter.java @@ -33,6 +33,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.List; import java.util.Optional; import java.util.function.DoubleConsumer; @@ -152,17 +153,16 @@ public class ImageConverter extends BaseConverter { * */ private ProcessResult convertSinglePng(File in, File out) throws IOException { - String[] argv = new String[] { getPathToConvert(), in.getCanonicalPath(), out.getCanonicalPath() }; + List<String> argv = List.of(getPathToConvert(), in.getCanonicalPath(), out.getCanonicalPath()); - return ProcessHelper.executeScript("convertSinglePng", argv); + return ProcessHelper.exec("convertSinglePng", argv); } public ProcessResult resize(File in, File out, Integer width, Integer height) throws IOException { - String[] argv = new String[] { getPathToConvert() + List<String> argv = List.of(getPathToConvert() , "-resize", (width == null ? "" : width) + (height == null ? "" : "x" + height) - , in.getCanonicalPath(), out.getCanonicalPath() - }; - return ProcessHelper.executeScript("resize", argv); + , in.getCanonicalPath(), out.getCanonicalPath()); + return ProcessHelper.exec("resize", argv); } /** @@ -176,16 +176,15 @@ public class ImageConverter extends BaseConverter { */ public ProcessResultList convertDocument(FileItem f, File pdf, ProcessResultList logs, Optional<DoubleConsumer> progress) throws IOException { log.debug("convertDocument"); - String[] argv = new String[] { - getPathToConvert() - , "-density", getDpi() - , "-define", "pdf:use-cropbox=true" - , pdf.getCanonicalPath() - , "+profile", "'*'" - , "-quality", getQuality() - , new File(pdf.getParentFile(), PAGE_TMPLT).getCanonicalPath() - }; - ProcessResult res = ProcessHelper.executeScript("convert PDF to images", argv); + List<String> argv = List.of( + getPathToConvert() + , "-density", getDpi() + , "-define", "pdf:use-cropbox=true" + , pdf.getCanonicalPath() + , "+profile", "'*'" + , "-quality", getQuality() + , new File(pdf.getParentFile(), PAGE_TMPLT).getCanonicalPath()); + ProcessResult res = ProcessHelper.exec("convert PDF to images", argv); logs.add(res); progress.ifPresent(theProgress -> theProgress.accept(1. / 4)); if (res.isOk()) { diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/InterviewConverter.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/InterviewConverter.java index 7e17d93..d74c287 100644 --- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/InterviewConverter.java +++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/InterviewConverter.java @@ -48,6 +48,24 @@ import org.springframework.stereotype.Component; @Component public class InterviewConverter extends BaseConverter implements IRecordingConverter { private static final Logger log = LoggerFactory.getLogger(InterviewConverter.class); + private static final int WIDTH = 320; + private static final int HEIGHT = 260; + private String interviewCam; + private String interviewBlank; + + private void init() throws ConversionException, IOException { + // Default Image for empty interview video pods + final File interviewCamFile = new File(OmFileHelper.getImagesDir(), "interview_webcam.png"); + if (!interviewCamFile.exists()) { + throw new ConversionException("defaultInterviewImageFile does not exist!"); + } + interviewCam = interviewCamFile.getCanonicalPath(); + final File interviewBlankFile = new File(OmFileHelper.getImagesDir(), "blank.png"); + if (!interviewBlankFile.exists()) { + throw new ConversionException("defaultInterviewImageFile does not exist!"); + } + interviewBlank = interviewBlankFile.getCanonicalPath(); + } @Override public void startConversion(Recording r) { @@ -58,13 +76,10 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve ProcessResultList logs = new ProcessResultList(); List<File> waveFiles = new ArrayList<>(); try { - // Default Image for empty interview video pods - final File interviewCamFile = new File(OmFileHelper.getImagesDir(), "interview_webcam.png"); - if (!interviewCamFile.exists()) { - throw new ConversionException("defaultInterviewImageFile does not exist!"); - } - log.debug("recording {}", r.getId()); + if (interviewCam == null) { + init(); + } if (Strings.isEmpty(r.getHash())) { r.setHash(randomUUID().toString()); } @@ -77,10 +92,6 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve File wav = new File(streamFolder, String.format("INTERVIEW_%s_FINAL_WAVE.wav", r.getId())); createWav(r, logs, streamFolder, waveFiles, wav, chunks); - final String interviewCam = interviewCamFile.getCanonicalPath(); - - final int width = 320; - final int height = 260; // Merge Audio with Video / Calculate resulting video // group by sid first to get all pods @@ -89,61 +100,21 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve , LinkedHashMap::new , Collectors.collectingAndThen(Collectors.toList(), l -> l.stream().sorted(Comparator.comparing(RecordingChunk::getStart)).collect(Collectors.toList())))); List<String> pods = new ArrayList<>(); - int numPods = pods.size(); for (Entry<String, List<RecordingChunk>> e : cunksBySid.entrySet()) { + int podIdx = pods.size(); Date pStart = r.getRecordStart(); List<PodPart> parts = new ArrayList<>(); - pStart = processParts(r.getRoomId(), e.getValue(), logs, numPods, parts, pStart); + pStart = processParts(r.getRoomId(), e.getValue(), logs, podIdx, parts, pStart); if (!parts.isEmpty()) { - String podX = new File(streamFolder, String.format("rec_%s_pod_%s.%s", r.getId(), numPods, EXTENSION_MP4)).getCanonicalPath(); + String podX = new File(streamFolder, String.format("rec_%s_pod_%s.%s", r.getId(), podIdx, EXTENSION_MP4)).getCanonicalPath(); long diff = diff(r.getRecordEnd(), pStart); PodPart.add(parts, diff); - /* create continuous pod - * ffmpeg \ - * -loop 1 -framerate 24 -t 10 -i image1.jpg \ - * -i video.mp4 \ - * -loop 1 -framerate 24 -t 10 -i image2.jpg \ - * -loop 1 -framerate 24 -t 10 -i image3.jpg \ - * -filter_complex "[0][1][2][3]concat=n=4:v=1:a=0" out.mp4 - */ - List<String> args = new ArrayList<>(); - args.add(getPathToFFMPEG()); - args.add("-y"); - StringBuilder videos = new StringBuilder(); - StringBuilder concat = new StringBuilder(); - for (int i = 0; i < parts.size(); ++i) { - PodPart p = parts.get(i); - if (p.getFile() == null) { - args.add("-loop"); - args.add("1"); - args.add("-t"); - args.add(formatMillis(p.getDuration())); - args.add("-i"); - args.add(interviewCam); - } else { - args.add("-t"); - args.add(formatMillis(p.getDuration())); - args.add("-i"); - args.add(p.getFile()); - } - videos.append('[').append(i).append(']') - .append("scale=").append(width).append(':').append(height).append(",setsar=1:1") - .append("[v").append(i).append("]; "); - concat.append("[v").append(i).append(']'); - } - args.add("-filter_complex"); - args.add(concat.insert(0, videos).append("concat=n=").append(parts.size()).append(":v=1:a=0").toString()); - args.add("-an"); - args.add(podX); - ProcessResult res = ProcessHelper.executeScript(String.format("Full video pod_%s", numPods), args.toArray(new String[0]), true); - logs.add(res); - if (res.isWarn()) { - throw new ConversionException("Fail to create pod"); - } + + createPod(podX, interviewCam, podIdx, parts, logs); pods.add(podX); - numPods = pods.size(); } } + int numPods = pods.size(); if (numPods == 0) { ProcessResult res = new ProcessResult(); res.setProcess("CheckStreamFilesExists"); @@ -155,11 +126,18 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve double ratio = Math.sqrt(numPods / Math.sqrt(2)); int w = ratio < 1 ? numPods : (int)Math.round(ratio); w = Math.max(w, (int)Math.round(1. * numPods / w)); + List<PodPart> missingParts = new ArrayList<>(); + PodPart.add(missingParts, diff(r.getRecordEnd(), r.getRecordStart())); + String missingPod = new File(streamFolder, String.format("rec_%s_pod_%s.%s", r.getId(), numPods, EXTENSION_MP4)).getCanonicalPath(); + createPod(missingPod, interviewBlank, numPods, missingParts, logs); + for (int i = numPods % w; i < w; ++i) { + pods.add(missingPod); + } - r.setWidth(w * width); - r.setHeight((numPods / w) * height); + r.setWidth(w * WIDTH); + r.setHeight((numPods / w) * HEIGHT); - String mp4path = convertToMp4(r, getFinalArgs(numPods, pods, wav, w), logs); + String mp4path = convertToMp4(r, getFinalArgs(pods, wav, w), numPods != 1, logs); finalizeRec(r, mp4path, logs); } catch (Exception err) { @@ -175,6 +153,51 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve } } + private void createPod(String podX, String image, int podIdx, List<PodPart> parts, ProcessResultList logs) throws ConversionException { + /* create continuous pod + * ffmpeg \ + * -loop 1 -framerate 24 -t 10 -i image1.jpg \ + * -i video.mp4 \ + * -loop 1 -framerate 24 -t 10 -i image2.jpg \ + * -loop 1 -framerate 24 -t 10 -i image3.jpg \ + * -filter_complex "[0][1][2][3]concat=n=4:v=1:a=0" out.mp4 + */ + List<String> args = new ArrayList<>(); + args.add(getPathToFFMPEG()); + args.add("-y"); + StringBuilder videos = new StringBuilder(); + StringBuilder concat = new StringBuilder(); + for (int i = 0; i < parts.size(); ++i) { + PodPart p = parts.get(i); + if (p.getFile() == null) { + args.add("-loop"); + args.add("1"); + args.add("-t"); + args.add(formatMillis(p.getDuration())); + args.add("-i"); + args.add(image); + } else { + args.add("-t"); + args.add(formatMillis(p.getDuration())); + args.add("-i"); + args.add(p.getFile()); + } + videos.append('[').append(i).append(']') + .append("scale=").append(WIDTH).append(':').append(HEIGHT).append(",setsar=1:1") + .append("[v").append(i).append("]; "); + concat.append("[v").append(i).append(']'); + } + args.add("-filter_complex"); + args.add(concat.insert(0, videos).append("concat=n=").append(parts.size()).append(":v=1:a=0").toString()); + args.add("-an"); + args.add(podX); + ProcessResult res = ProcessHelper.exec("Full video pod_" + podIdx, args, true); + logs.add(res); + if (res.isWarn()) { + throw new ConversionException("Fail to create pod"); + } + } + private Date processParts(Long roomId, List<RecordingChunk> chunks, ProcessResultList logs, int numPods, List<PodPart> parts, Date pStart) throws IOException { for (RecordingChunk chunk : chunks) { File chunkStream = getRecordingChunk(roomId, chunk.getStreamName()); @@ -186,12 +209,12 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve /* CHECK FILE: * ffmpeg -i rec_316_stream_567_2013_08_28_11_51_45.webm -v error -f null file.null */ - String[] args = new String[] {getPathToFFMPEG(), "-y" + List<String> args = List.of(getPathToFFMPEG(), "-y" , "-i", path , "-v", "error" , "-f", "null" - , "file.null"}; - ProcessResult res = ProcessHelper.executeScript(String.format("Check chunk pod video_%s_%s", numPods, parts.size()), args, true); + , "file.null"); + ProcessResult res = ProcessHelper.exec(String.format("Check chunk pod video_%s_%s", numPods, parts.size()), args, true); logs.add(res); if (!res.isWarn()) { long diff = diff(chunk.isAudioOnly() ? chunk.getEnd() : chunk.getStart(), pStart); @@ -205,7 +228,8 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve return pStart; } - private static List<String> getFinalArgs(int numPods, List<String> pods, File wav, int w) throws IOException { + private static List<String> getFinalArgs(List<String> pods, File wav, int w) throws IOException { + final int numPods = pods.size(); List<String> args = new ArrayList<>(); if (numPods == 1) { args.add("-i"); @@ -222,12 +246,14 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve */ StringBuilder cols = new StringBuilder(); StringBuilder rows = new StringBuilder(); + int colCount = 0; for (int i = 0, j = 0; i < numPods; ++i) { + colCount++; args.add("-i"); args.add(pods.get(i)); cols.append('[').append(i).append(":v]"); - if (i != 0 && (i + 1) % w == 0) { - cols.append("hstack=inputs=").append(w); + if (i != 0 && colCount % w == 0) { + cols.append("hstack=inputs=").append(colCount); if (j == 0 && i == numPods - 1) { cols.append("[v]"); } else { @@ -235,10 +261,11 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve } rows.append("[c").append(j).append(']'); j++; + colCount = 0; } if (i == numPods - 1) { if (j > 1) { - rows.append("vstack=inputs=").append(j).append("[v]"); + rows.append("vstack=inputs=").append(j).append("[out];[out]pad=ceil(iw/2)*2:ceil(ih/2)*2[v]"); } else { rows.setLength(0); } @@ -252,7 +279,7 @@ public class InterviewConverter extends BaseConverter implements IRecordingConve args.add("[v]"); } args.add("-map"); - args.add(String.format("%s:a", numPods)); + args.add(numPods + ":a"); args.add("-qmax"); args.add("1"); args.add("-qmin"); args.add("1"); return args; diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/RecordingConverter.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/RecordingConverter.java index f1afb49..80ccbae 100644 --- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/RecordingConverter.java +++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/RecordingConverter.java @@ -88,7 +88,7 @@ public class RecordingConverter extends BaseConverter implements IRecordingConve String mp4path = convertToMp4(r, List.of( "-itsoffset", formatMillis(diff(screenChunk.getStart(), r.getRecordStart())), "-i", inputScreenFullFlv, "-i", wav.getCanonicalPath() - ), logs); + ), false, logs); Dimension dim = getDimension(logs.getLast().getError(), null); // will return 100x100 for non-video to be able to play if (dim != null) { r.setWidth(dim.getWidth()); diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/VideoConverter.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/VideoConverter.java index 0f444ae..96be9f5 100644 --- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/VideoConverter.java +++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/VideoConverter.java @@ -62,13 +62,13 @@ public class VideoConverter extends BaseConverter { List<String> args = new ArrayList<>(List.of(getPathToFFMPEG(), "-y")); if (sf.isAudio()) { // need to add background image, it should be jpg since black on transparent will be invisible - args.addAll(List.of("-loop", "1"// - , "-framerate", "24"// + args.addAll(List.of("-loop", "1" + , "-framerate", "24" , "-i", new File(getCssImagesDir(), "audio.jpg").getCanonicalPath())); } - args.addAll(List.of("-i", input // - , "-c:v", "h264" // - , "-c:a", "aac" // + args.addAll(List.of("-i", input + , "-c:v", "h264" + , "-c:a", "aac" , "-pix_fmt", "yuv420p" , "-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2" )); @@ -76,7 +76,7 @@ public class VideoConverter extends BaseConverter { args.add("-shortest"); } args.add(mp4.getCanonicalPath()); - ProcessResult res = ProcessHelper.executeScript("convert to MP4 :: " + f.getHash(), args.toArray(new String[0])); + ProcessResult res = ProcessHelper.exec("convert to MP4 :: " + f.getHash(), args); logs.add(res); progress.ifPresent(theProgress -> theProgress.accept(STEP)); if (sameExt && tmp != null) { diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KStream.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KStream.java index aaef0ca..56f2a58 100644 --- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KStream.java +++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/remote/KStream.java @@ -339,7 +339,10 @@ public class KStream extends AbstractStream implements ISipCallbacks { setTags(recorder, uid); recorder.addRecordingListener(evt -> chunkId = kRoom.getChunkDao().start(kRoom.getRecordingId(), type, chunkUid, sid)); - recorder.addStoppedListener(evt -> kRoom.getChunkDao().stop(chunkId)); + recorder.addStoppedListener(evt -> { + kRoom.getChunkDao().stop(chunkId); + chunkId = null; + }); switch (profile) { case WEBM: outgoingMedia.connect(recorder, MediaType.AUDIO); @@ -367,8 +370,7 @@ public class KStream extends AbstractStream implements ISipCallbacks { } public void stopRecord() { - releaseRecorder(true); - chunkId = null; + stopRecorder(true, () -> {}); } public void remove(final Client c) { @@ -423,77 +425,93 @@ public class KStream extends AbstractStream implements ISipCallbacks { public void release(boolean remove) { if (outgoingMedia != null) { releaseListeners(); - releaseRecorder(false); - releaseRtp(); - outgoingMedia.release(new Continuation<Void>() { - @Override - public void onSuccess(Void result) throws Exception { - log.trace("PARTICIPANT {}: Released successfully", KStream.this.uid); - } + stopRecorder(false, () -> { + releaseRtp(); + outgoingMedia.release(new Continuation<Void>() { + @Override + public void onSuccess(Void result) throws Exception { + log.trace("PARTICIPANT {}: Released successfully", KStream.this.uid); + } - @Override - public void onError(Throwable cause) throws Exception { - log.warn("PARTICIPANT {}: Could not release", KStream.this.uid, cause); - } - }); - pipeline.release(new Continuation<Void>() { - @Override - public void onSuccess(Void result) throws Exception { - log.trace("PARTICIPANT {}: Released Pipeline", KStream.this.uid); - } + @Override + public void onError(Throwable cause) throws Exception { + log.warn("PARTICIPANT {}: Could not release", KStream.this.uid, cause); + } + }); + pipeline.release(new Continuation<Void>() { + @Override + public void onSuccess(Void result) throws Exception { + log.trace("PARTICIPANT {}: Released Pipeline", KStream.this.uid); + } - @Override - public void onError(Throwable cause) throws Exception { - log.warn("PARTICIPANT {}: Could not release Pipeline", KStream.this.uid, cause); - } + @Override + public void onError(Throwable cause) throws Exception { + log.warn("PARTICIPANT {}: Could not release Pipeline", KStream.this.uid, cause); + } + }); + outgoingMedia = null; + doRemove(remove); }); - outgoingMedia = null; + } else { + doRemove(remove); } + } + + private void doRemove(boolean remove) { if (remove) { kHandler.getStreamProcessor().release(this, false); } } - private void releaseRecorder(boolean wait) { - if (recorder != null) { - if (wait) { - recorder.stopAndWait(); - } else { - recorder.stop(new Continuation<Void>() { - @Override - public void onSuccess(Void result) throws Exception { - log.trace("PARTICIPANT {}: Recording stopped", KStream.this.uid); - } + private void releaseRecorder(Runnable then) { + outgoingMedia.disconnect(recorder, new Continuation<Void>() { + @Override + public void onSuccess(Void result) throws Exception { + log.trace("PARTICIPANT {}: Recorder disconnected successfully", KStream.this.uid); + } - @Override - public void onError(Throwable cause) throws Exception { - log.warn("PARTICIPANT {}: Could not stop recording", KStream.this.uid, cause); - } - }); + @Override + public void onError(Throwable cause) throws Exception { + log.warn("PARTICIPANT {}: Could not disconnect recorder", KStream.this.uid, cause); + } + }); + recorder.release(new Continuation<Void>() { + @Override + public void onSuccess(Void result) throws Exception { + log.trace("PARTICIPANT {}: Recorder released successfully", KStream.this.uid); } - outgoingMedia.disconnect(recorder, new Continuation<Void>() { - @Override - public void onSuccess(Void result) throws Exception { - log.trace("PARTICIPANT {}: Recorder disconnected successfully", KStream.this.uid); - } - @Override - public void onError(Throwable cause) throws Exception { - log.warn("PARTICIPANT {}: Could not disconnect recorder", KStream.this.uid, cause); - } - }); - recorder.release(new Continuation<Void>() { + @Override + public void onError(Throwable cause) throws Exception { + log.warn("PARTICIPANT {}: Could not release recorder", KStream.this.uid, cause); + } + }); + recorder = null; + then.run(); + } + + private void stopRecorder(boolean wait, Runnable then) { + if (recorder != null) { + final Continuation<Void> stop = new Continuation<>() { @Override public void onSuccess(Void result) throws Exception { - log.trace("PARTICIPANT {}: Recorder released successfully", KStream.this.uid); + log.trace("PARTICIPANT {}: Recording stopped", KStream.this.uid); + releaseRecorder(then); } @Override public void onError(Throwable cause) throws Exception { - log.warn("PARTICIPANT {}: Could not release recorder", KStream.this.uid, cause); + log.warn("PARTICIPANT {}: Could not stop recording", KStream.this.uid, cause); + releaseRecorder(then); } - }); - recorder = null; + }; + if (wait) { + recorder.stopAndWait(stop); + } else { + recorder.stop(stop); + } + } else { + then.run(); } } diff --git a/openmeetings-db/pom.xml b/openmeetings-db/pom.xml index d64fccb..af04d2f 100644 --- a/openmeetings-db/pom.xml +++ b/openmeetings-db/pom.xml @@ -60,7 +60,6 @@ <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> - <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> diff --git a/openmeetings-util/src/main/java/org/apache/openmeetings/util/process/ProcessHelper.java b/openmeetings-util/src/main/java/org/apache/openmeetings/util/process/ProcessHelper.java index 262b60d..4c0f596 100644 --- a/openmeetings-util/src/main/java/org/apache/openmeetings/util/process/ProcessHelper.java +++ b/openmeetings-util/src/main/java/org/apache/openmeetings/util/process/ProcessHelper.java @@ -26,6 +26,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -71,15 +72,11 @@ public class ProcessHelper { private ProcessHelper() {} - private static String getCommand(String[] argv) { - StringBuilder tString = new StringBuilder(); - for (int i = 0; i < argv.length; i++) { - tString.append(argv[i]).append(" "); - } - return tString.toString(); + private static String getCommand(List<String> argv) { + return String.join(" ", argv); } - private static void debugCommandStart(String desc, String[] argv) { + private static void debugCommandStart(String desc, List<String> argv) { if (log.isDebugEnabled()) { log.debug("START {} ################# ", desc); log.debug(getCommand(argv)); @@ -92,15 +89,15 @@ public class ProcessHelper { } } - public static ProcessResult executeScript(String process, String[] argv) { - return executeScript(process, argv, false); + public static ProcessResult exec(String process, List<String> argv) { + return exec(process, argv, false); } - public static ProcessResult executeScript(String process, String[] argv, boolean optional) { - return executeScript(process, argv, Map.of(), optional); + public static ProcessResult exec(String process, List<String> argv, boolean optional) { + return exec(process, argv, Map.of(), optional); } - private static ProcessResult executeScript(String process, String[] argv, Map<? extends String, ? extends String> env, boolean optional) { + private static ProcessResult exec(String process, List<String> argv, Map<? extends String, ? extends String> env, boolean optional) { ProcessResult res = new ProcessResult() .setProcess(process) .setOptional(optional); diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/install/InstallWizard.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/install/InstallWizard.java index 0f8f322..fb52961 100644 --- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/install/InstallWizard.java +++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/install/InstallWizard.java @@ -618,8 +618,9 @@ public class InstallWizard extends BootstrapWizard { private void reportSuccess(TextField<String> path) { path.success(path.getLabel().getObject() + " - " + getString("54")); } - private boolean checkToolPath(TextField<String> path, String[] args) { - ProcessResult result = ProcessHelper.executeScript(path.getInputName() + " path:: '" + path.getValue() + "'", args); + + private boolean checkToolPath(TextField<String> path, List<String> args) { + ProcessResult result = ProcessHelper.exec(path.getInputName() + " path:: '" + path.getValue() + "'", args); if (!result.isOk()) { path.error(result.getError().replaceAll(REGEX, "")); } else { @@ -629,15 +630,15 @@ public class InstallWizard extends BootstrapWizard { } private boolean checkMagicPath() { - return checkToolPath(imageMagicPath, new String[] {getToolPath(imageMagicPath.getValue(), "convert" + EXEC_EXT), OPT_VERSION}); + return checkToolPath(imageMagicPath, List.of(getToolPath(imageMagicPath.getValue(), "convert" + EXEC_EXT), OPT_VERSION)); } private boolean checkFfmpegPath() { - return checkToolPath(ffmpegPath, new String[] {getToolPath(ffmpegPath.getValue(), "ffmpeg" + EXEC_EXT), OPT_VERSION}); + return checkToolPath(ffmpegPath, List.of(getToolPath(ffmpegPath.getValue(), "ffmpeg" + EXEC_EXT), OPT_VERSION)); } private boolean checkSoxPath() { - return checkToolPath(soxPath, new String[] {getToolPath(soxPath.getValue(), "sox" + EXEC_EXT), "--version"}); + return checkToolPath(soxPath, List.of(getToolPath(soxPath.getValue(), "sox" + EXEC_EXT), "--version")); } private boolean checkOfficePath() {