Merge branch 'master' into permission-vertical

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java
This commit is contained in:
Steffen Schäfer committed 2018-09-26 11:03:03 +02:00
commit e8e329ca77
14 files changed
+377 -246

No files matched your search

@@ -158,6 +158,6 @@
id="org.mp4parser.isoparser"
download-size="0"
install-size="0"
version="1.9.31"/>
version="1.9.37"/>
</feature>
@@ -57,7 +57,7 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.competitorimport,
com.sap.sailing.expeditionconnector,
com.sap.sailing.expeditionconnector.common,
org.mp4parser.isoparser;bundle-version="1.9.31",
org.mp4parser.isoparser;bundle-version="1.9.37",
com.sap.sse.datamining.ui
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
Bundle-ActivationPolicy: lazy
@@ -1,16 +1,13 @@
package com.sap.sailing.gwt.ui.server;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
@@ -23,29 +20,21 @@ import java.nio.file.Files;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.shiro.SecurityUtils;
import org.mp4parser.AbstractBoxParser;
import org.mp4parser.IsoFile;
import org.mp4parser.boxes.UserBox;
import org.mp4parser.boxes.iso14496.part12.MediaDataBox;
import org.mp4parser.boxes.iso14496.part12.MovieBox;
import org.mp4parser.boxes.iso14496.part12.MovieHeaderBox;
import org.mp4parser.tools.Path;
import org.mp4parser.PropertyBoxParserImpl;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.google.gwt.thirdparty.json.JSONArray;
import com.google.gwt.thirdparty.json.JSONException;
import com.google.gwt.thirdparty.json.JSONObject;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
@@ -54,6 +43,8 @@ import com.sap.sailing.domain.common.dto.VideoMetadataDTO;
import com.sap.sailing.domain.common.media.MediaTrack;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.gwt.ui.client.MediaService;
import com.sap.sailing.media.mp4.MP4MediaParser;
import com.sap.sailing.media.mp4.MP4ParserFakeFile;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
@@ -70,7 +61,6 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
private ServiceTracker<RacingEventService, RacingEventService> racingEventServiceTracker;
private static final int REQUIRED_SIZE_IN_BYTES = 10000000;
private static final long serialVersionUID = -8917349579281305977L;
public MediaServiceImpl() {
@@ -157,14 +147,14 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
URL input = new URL(url);
// check size and do rangerequests if possible
long fileSize = determineFileSize(input);
if (fileSize > 2 * REQUIRED_SIZE_IN_BYTES) {
if (fileSize > 2 * MP4MediaParser.REQUIRED_SIZE_IN_BYTES) {
response = checkMetadataByPartialDownloads(input, fileSize);
} else {
response = checkMetadataByFullFileDownload(input);
}
} catch (Exception e) {
logger.log(Level.WARNING, "Error in video analysis ", e);
response = new VideoMetadataDTO(false, null, false, null, e.getMessage());
response = new VideoMetadataDTO(false, null, false, null, "");
}
return response;
}
@@ -197,10 +187,10 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
private VideoMetadataDTO checkMetadataByPartialDownloads(URL input, long fileSize)
throws IOException, ProtocolException {
byte[] start = new byte[REQUIRED_SIZE_IN_BYTES];
byte[] end = new byte[REQUIRED_SIZE_IN_BYTES];
downloadPartOfFile(input, start, "bytes=0-" + REQUIRED_SIZE_IN_BYTES);
downloadPartOfFile(input, end, "bytes=" + (fileSize - REQUIRED_SIZE_IN_BYTES) + "-");
byte[] start = new byte[MP4MediaParser.REQUIRED_SIZE_IN_BYTES];
byte[] end = new byte[MP4MediaParser.REQUIRED_SIZE_IN_BYTES];
downloadPartOfFile(input, start, "bytes=0-" + MP4MediaParser.REQUIRED_SIZE_IN_BYTES);
downloadPartOfFile(input, end, "bytes=" + (fileSize - MP4MediaParser.REQUIRED_SIZE_IN_BYTES) + "-");
long skipped = fileSize - start.length - end.length;
return checkMetadata(start, end, skipped);
}
@@ -211,9 +201,10 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
connection.setConnectTimeout(METADATA_CONNECTION_TIMEOUT);
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", range);
try (InputStream inStream = connection.getInputStream()) {
DataInputStream dataInStream = new DataInputStream(inStream);
try (final InputStream inStream = connection.getInputStream()) {
try (final DataInputStream dataInStream = new DataInputStream(inStream)) {
dataInStream.readFully(store);
}
} finally {
connection.disconnect();
}
@@ -222,145 +213,52 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
private VideoMetadataDTO checkMetadataByFullFileDownload(URL input)
throws ParserConfigurationException, SAXException, IOException {
final File tmp = File.createTempFile("upload", "metadataCheck");
VideoMetadataDTO result;
try {
final ReadableByteChannel rbc = Channels.newChannel(input.openStream());
try (FileOutputStream fos = new FileOutputStream(tmp)) {
try (final ReadableByteChannel rbc = Channels.newChannel(input.openStream())) {
try (final FileOutputStream fos = new FileOutputStream(tmp)) {
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
try (IsoFile isof = new IsoFile(tmp)) {
final boolean canDownload = true;
final Date recordStartedTimer = determineRecordingStart(isof);
final Duration duration = determineDuration(isof);
final boolean spherical = determine360(isof);
removeTempFiles(isof);
return new VideoMetadataDTO(canDownload, duration, spherical, recordStartedTimer, "");
try (final MP4ParserFakeFile inputFile = new MP4ParserFakeFile(tmp)) {
Files.delete(tmp.toPath());
result = checkMetadata(inputFile);
} catch (Exception e) {
result = new VideoMetadataDTO(true, null, false, null, e.getMessage());
}
} catch (Exception e) {
result = new VideoMetadataDTO(false, null, false, null, e.getMessage());
}
}
} finally {
Files.delete(tmp.toPath());
// delete the file if not already deleted in main path
tmp.delete();
}
return result;
}
@Override
public VideoMetadataDTO checkMetadata(byte[] start, byte[] end, Long skipped) {
File tmp = null;
boolean spherical = false;
Duration duration = null;
Date recordStartedTimer = null;
String message = "";
try {
tmp = createFileFromData(start, end, skipped);
try (IsoFile isof = new IsoFile(tmp)) {
try {
recordStartedTimer = determineRecordingStart(isof);
spherical = determine360(isof);
duration = determineDuration(isof);
} finally {
removeTempFiles(isof);
}
}
VideoMetadataDTO result;
try (MP4ParserFakeFile input = new MP4ParserFakeFile(start, end, skipped)) {
result = checkMetadata(input);
} catch (Exception e) {
logger.log(Level.WARNING, "Error in video analysis ", e);
message = e.getMessage();
} finally {
if (tmp != null) {
try {
Files.delete(tmp.toPath());
} catch (IOException e) {
logger.log(Level.SEVERE, "Could not delete tmp mp4 file", e);
result = new VideoMetadataDTO(true, null, false, null, e.getMessage());
}
return result;
}
}
return new VideoMetadataDTO(true, duration, spherical, recordStartedTimer, message);
}
/**
* Some boxes (we don't actually need to read) create internal tempfiles, we delete them here, as the VM could run
* quite long
*/
private void removeTempFiles(IsoFile isof) {
List<MediaDataBox> boxesWithTempFiles = isof.getBoxes(MediaDataBox.class, true);
for (MediaDataBox box : boxesWithTempFiles) {
try {
Field field = box.getClass().getDeclaredField("dataFile");
field.setAccessible(true);
File data = (File) field.get(box);
Files.delete(data.toPath());
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException
| IOException e) {
logger.log(Level.WARNING, "Could not delete mp4 temp files", e);
}
private VideoMetadataDTO checkMetadata(MP4ParserFakeFile input)
throws ParserConfigurationException, SAXException, IOException {
AbstractBoxParser boxParserImpl = new PropertyBoxParserImpl();
boxParserImpl.skippingBoxes(new String[] { "mdat" });
try (IsoFile isof = new IsoFile(input, boxParserImpl)) {
Date recordStartedTimer = MP4MediaParser.determineRecordingStart(isof);
boolean spherical = MP4MediaParser.determine360(isof);
Duration duration = new MillisecondsDurationImpl(MP4MediaParser.determineDurationInMillis(isof));
return new VideoMetadataDTO(true, duration, spherical, recordStartedTimer, "");
}
}
/**
* Creates a dummy mp4 file, only containing the start and the end, the middle is filled with 0 so that all file
* internal pointers from start to end are working
*/
private File createFileFromData(byte[] start, byte[] end, Long skipped) throws IOException, FileNotFoundException {
File tmp;
tmp = File.createTempFile("upload", "metadataCheck");
try (FileOutputStream fw = new FileOutputStream(tmp)) {
fw.write(start);
while (skipped > 0) {
// ensure that no absurd amount of memory is required, and that more then 4gb files can be analysed
if (skipped > 10000000) {
byte[] dummy = new byte[10000000];
fw.write(dummy);
skipped = skipped - 10000000;
} else {
byte[] dummy = new byte[skipped.intValue()];
fw.write(dummy);
skipped = skipped - skipped.intValue();
}
}
fw.write(end);
}
return tmp;
}
private boolean determine360(IsoFile isof) throws ParserConfigurationException, SAXException, IOException {
boolean spherical = false;
UserBox uuidBox = Path.getPath(isof, "moov[0]/trak[0]/uuid");
if (uuidBox != null) {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(new ByteArrayInputStream(uuidBox.getData()));
NodeList childs = doc.getDocumentElement().getChildNodes();
for (int i = 0; i < childs.getLength(); i++) {
Node child = childs.item(i);
if (child.getNodeName().toLowerCase().contains(":spherical")) {
spherical = true;
}
}
}
return spherical;
}
private Duration determineDuration(IsoFile isof) {
Duration duration = null;
MovieBox mbox = isof.getMovieBox();
if (mbox != null) {
MovieHeaderBox mhb = mbox.getMovieHeaderBox();
if (mhb != null) {
duration = new MillisecondsDurationImpl(mhb.getDuration()*1000 / mhb.getTimescale());
}
}
return duration;
}
private Date determineRecordingStart(IsoFile isof) {
Date creationTime = null;
MovieBox mbox = isof.getMovieBox();
if (mbox != null) {
MovieHeaderBox mhb = mbox.getMovieHeaderBox();
if (mhb != null) {
creationTime = mhb.getCreationTime();
}
}
return creationTime;
}
@Override
public MediaTrack getMediaTrackByUrl(String url) {
MediaTrack result = null;
@@ -381,6 +279,7 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
if (videoId.isEmpty()) {
message = "Empty id";
} else {
// sanitize, as we inject it into an url with our api key!
videoId = URLEncoder.encode(videoId, StandardCharsets.UTF_8.name());
try {
URL apiURL = new URL(
@@ -393,10 +292,13 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
String pageText = reader.lines().collect(Collectors.joining("\n"));
JSONObject jsonAnswer = new JSONObject(pageText);
final JSONObject item = jsonAnswer.getJSONArray("items").getJSONObject(0);
final JSONArray dataArray = jsonAnswer.getJSONArray("items");
if (dataArray.length() > 0) {
final JSONObject item = dataArray.getJSONObject(0);
message = item.getJSONObject("snippet").getString("title");
String rawDuration = item.getJSONObject("contentDetails").getString("duration");
duration = new MillisecondsDurationImpl(java.time.Duration.parse(rawDuration).toMillis());
}
canDownload = true;
} catch (JSONException e) {
message = e.getMessage();
@@ -407,9 +309,6 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
logger.log(Level.WARNING, "Error in youtube metadata call", e);
}
}
//sanitize, as we inject it into an url with our api key!
return new VideoMetadataDTO(canDownload, duration, false, null, message);
}
}
@@ -6,6 +6,7 @@ import java.util.Map;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.core.client.Duration;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.base.LatLng;
@@ -54,7 +55,6 @@ public class Swarm implements TimeListener {
private boolean swarmOffScreen = false;
private int swarmPause = 0;
private boolean swarmContinue = true;
/**
* Bounds in the map's coordinate system which may have undergone rotation and translation. See the
@@ -96,47 +96,95 @@ public class Swarm implements TimeListener {
}
public void start(final int animationIntervalMillis) {
removeBoundsChangeHandler();
fullcanvas.setCanvasSettings();
// if map is not yet loaded, wait for it
if (map.getBounds() != null) {
startWithMap(animationIntervalMillis);
} else {
removeBoundsChangeHandler();
BoundsChangeMapHandler handler = new BoundsChangeMapHandler() {
@Override
public void onEvent(BoundsChangeMapEvent event) {
if (swarmContinue) {
startWithMap(animationIntervalMillis);
}
if (loopTimer == null) {
loopTimer = new com.google.gwt.user.client.Timer() {
public void run() {
updateSwarmOneTick(animationIntervalMillis);
}
};
boundsChangeHandlers.put(handler, map.addBoundsChangeHandler(handler));
}
// the map already exists, ensure swam is started
if (map.getBounds() != null) {
startSwarmIfNecessaryAndUpdateProjection();
loopTimer.schedule(0);
}
// add handler, so the swarm updates itself without calls from the map required
BoundsChangeMapHandler handler = new BoundsChangeMapHandler() {
@Override
public void onEvent(BoundsChangeMapEvent event) {
startSwarmIfNecessaryAndUpdateProjection();
loopTimer.schedule(0);
}
};
boundsChangeHandlers.put(handler, map.addBoundsChangeHandler(handler));
}
private void startSwarmIfNecessaryAndUpdateProjection() {
if (projection == null) {
projection = new Mercator(fullcanvas, map);
}
// ensure projection fits the map
projection.calibrate();
// ensure canvas fits the map
updateBounds();
if (particles == null) {
// if this is the first start, also createParticles
createParticles();
} else {
// clear all tails, as else there will be long smears over the map
clearNextFrame = true;
}
}
private void startWithMap(int animationIntervalMillis) {
projection = new Mercator(fullcanvas, map);
projection.calibrate();
updateBounds();
if (particles == null) {
createParticles();
} else {
clearNextFrame = true;
private void updateSwarmOneTick(int animationIntervalMillis) {
double time0 = Duration.currentTimeMillis();
// upon zooming the swarm is shortly paused, to ensure no strange rendering during the css zoom
// animations
if (swarmPause > 1) {
swarmPause--;
} else if (swarmPause == 1) {
// ensure bounds and projections are up to date
startSwarmIfNecessaryAndUpdateProjection();
if (zoomChanged) {
// ensure amount of particles is updated
diffPx = new Vector(0, 0);
recycleParticles();
zoomChanged = false;
} else {
// process the offset
diffPx = fullcanvas.getDiffPx();
}
swarmPause = 0;
}
swarmContinue = true;
startLoopIfNecessary(animationIntervalMillis);
// update the swarm if it is not paused
if ((!swarmOffScreen) && (swarmPause == 0)) {
execute(diffPx);
diffPx = new Vector(0, 0);
}
double time1 = Duration.currentTimeMillis();
// wait at least 10ms for the next iteration; try to get one iteration done every
// animationIntervalMillis if possible
double timeDelta = time1 - time0;
// log("fps: "+(1000.0/timeDelta));
loopTimer.schedule((int) Math.max(10, animationIntervalMillis - timeDelta));
}
private void removeBoundsChangeHandler() {
for (HandlerRegistration reg : boundsChangeHandlers.values()) {
reg.removeHandler();
}
boundsChangeHandlers.clear();
}
public void stop() {
swarmContinue = false;
removeBoundsChangeHandler();
projection.clearCanvas();
loopTimer.cancel();
}
private Particle recycleOrCreateParticle(Particle particle) {
@@ -226,52 +274,12 @@ public class Swarm implements TimeListener {
+ visibleBoundsOfField.getNorthEast().getLatitude() / 180. * Math.PI) / 2);
}
private void startLoopIfNecessary(final int animationIntervalMillis) {
if (loopTimer == null) {
// Create animation-loop based on timer timeout
loopTimer = new com.google.gwt.user.client.Timer() {
public void run() {
Date time0 = new Date();
if (swarmPause > 1) {
swarmPause--;
} else if (swarmPause == 1) {
fullcanvas.setCanvasSettings();
projection.calibrate();
updateBounds();
if (zoomChanged) {
diffPx = new Vector(0, 0);
recycleParticles();
zoomChanged = false;
} else {
diffPx = fullcanvas.getDiffPx();
}
swarmPause = 0;
}
if ((!swarmOffScreen) && (swarmPause == 0)) {
execute(diffPx);
diffPx = new Vector(0, 0);
}
Date time1 = new Date();
if (swarmContinue) {
// wait at least 10ms for the next iteration; try to get one iteration done every
// animationIntervalMillis if possible
long timeDelta = time1.getTime() - time0.getTime();
// log("fps: "+(1000.0/timeDelta));
loopTimer.schedule((int) Math.max(10, animationIntervalMillis - timeDelta));
} else {
projection.clearCanvas();
}
}
};
loopTimer.schedule(animationIntervalMillis);
}
}
private void drawSwarm() {
Context2d ctxt = canvas.getContext2d();
// clearing neds to be done with a little over zero, thanks IE
ctxt.setGlobalAlpha(0.06);
if (clearNextFrame) {
// ctxt.setGlobalAlpha(1);
// skip this frame, as it will contain an extreme delta due to the map movement
clearNextFrame = false;
} else {
ctxt.setGlobalCompositeOperation("destination-out");
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry exported="true" kind="lib" path="lib/isoparser-1.9.31.jar"/>
<classpathentry exported="true" kind="lib" path="lib/isoparser-1.9.37.jar" sourcepath="lib/isoparser-1.9.37-sources.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="output" path="bin"/>
@@ -2,11 +2,12 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Selenium
Bundle-SymbolicName: org.mp4parser.isoparser
Bundle-Version: 1.9.31
Bundle-ClassPath: lib/isoparser-1.9.31.jar
Bundle-Version: 1.9.37
Bundle-ClassPath: .,
lib/isoparser-1.9.37.jar
Bundle-Vendor: Sebastian Annies
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Export-Package: com.sap.sailing.media,
Export-Package: com.sap.sailing.media.mp4,
org.mp4parser,
org.mp4parser.boxes,
org.mp4parser.boxes.iso14496.part12,
@@ -1,2 +1,5 @@
bin.includes = META-INF/,\
lib/isoparser-1.9.31.jar
lib/isoparser-1.9.37.jar
jars.compile.order = .
source.. = src/
output.. = bin/
+1 -1
View File
@@ -8,6 +8,6 @@
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.mp4parser.isoparser</artifactId>
<version>1.9.31</version>
<version>1.9.37</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,9 +1,7 @@
package com.sap.sailing.media;
package com.sap.sailing.media.mp4;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Date;
import javax.xml.parsers.DocumentBuilder;
@@ -20,24 +18,24 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class MediaFileParser {
public static void main(String[] args) throws MalformedURLException, ParserConfigurationException, SAXException, IOException {
new MediaFileParser().checkMetadata(new File(args[0]));
}
public void checkMetadata(File file) throws IOException, ParserConfigurationException, SAXException {
boolean spherical = false;
long durationInMillis = -1;
Date recordStartedTimer = null;
try (IsoFile isof = new IsoFile(file)) {
recordStartedTimer = determineRecordingStart(isof);
spherical = determine360(isof);
durationInMillis = determineDurationInMillis(isof);
System.out.println("Start: "+recordStartedTimer.toString()+", duration: "+durationInMillis+"ms; spherical: "+spherical);
}
}
public class MP4MediaParser {
/**
* The relevant size at the start and end of a file, that most likely will contain all metadata
*/
public static final int REQUIRED_SIZE_IN_BYTES = 10000000;
private boolean determine360(IsoFile isof) throws ParserConfigurationException, SAXException, IOException {
/**
* Determine whether or not the provided {@link IsoFile ISO file} contains any "spherical" information within the
* user defined box <code>moov[0]/trak[0]/uuid</code>.
*
* @param isof
* {@link IsoFile ISO file} to analyze
* @return <code>true</code> if spherical information are contained, <code>false</code> otherwise
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
public static boolean determine360(IsoFile isof) throws ParserConfigurationException, SAXException, IOException {
boolean spherical = false;
UserBox uuidBox = Path.getPath(isof, "moov[0]/trak[0]/uuid");
if (uuidBox != null) {
@@ -56,19 +54,36 @@ public class MediaFileParser {
return spherical;
}
private long determineDurationInMillis(IsoFile isof) {
/**
* Tries to determine the {@link Long duration} of the provided {@link IsoFile ISO file} based on its
* {@link IsoFile#getMovieBox() movie header}.
*
* @param isof
* {@link IsoFile ISO file} to analyze
* @return the determined duration in {@link Long millis}, <code>-1</code> if the duration could not be determined
*/
public static long determineDurationInMillis(IsoFile isof) {
long duration = -1;
MovieBox mbox = isof.getMovieBox();
if (mbox != null) {
MovieHeaderBox mhb = mbox.getMovieHeaderBox();
if (mhb != null) {
duration = mhb.getDuration()*1000 / mhb.getTimescale();
duration = mhb.getDuration() * 1000 / mhb.getTimescale();
}
}
return duration;
}
private Date determineRecordingStart(IsoFile isof) {
/**
* Tries to determine the {@link Date point in time} when the recording started from the provided {@link IsoFile ISO
* file} based on its {@link IsoFile#getMovieBox() movie header}.
*
* @param isof
* {@link IsoFile ISO file} to analyze
* @return the determined {@link Date point in time} when the recording started, <code>null</code> if the recording
* start could not be determined
*/
public static Date determineRecordingStart(IsoFile isof) {
Date creationTime = null;
MovieBox mbox = isof.getMovieBox();
if (mbox != null) {
@@ -0,0 +1,169 @@
package com.sap.sailing.media.mp4;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
/**
* This class pretends to be a complete larger file, while only carrying the first 10 and last 10 megabytes. It is
* specifically meant to be used for the mp4Parser and only supports the required subset for it. It extends FileChannel
* as the library uses that to skip input for skipped boxes
*/
public class MP4ParserFakeFile extends FileChannel {
private final long totalSizeOfFile;
private long currentPos = 0;
private byte[] startOfFileByteArray;
private byte[] endOfFileByteArray;
public MP4ParserFakeFile(byte[] start, byte[] end, Long skipped) {
this.startOfFileByteArray = start;
this.endOfFileByteArray = end;
this.totalSizeOfFile = start.length + end.length + skipped;
}
public MP4ParserFakeFile(File tmp) {
try (SeekableByteChannel inChannel = Files.newByteChannel(tmp.toPath())) {
totalSizeOfFile = tmp.length();
if (totalSizeOfFile < MP4MediaParser.REQUIRED_SIZE_IN_BYTES * 2) {
throw new IllegalStateException(
"The file is to small to be analysed < " + (MP4MediaParser.REQUIRED_SIZE_IN_BYTES * 2));
} else {
startOfFileByteArray = new byte[MP4MediaParser.REQUIRED_SIZE_IN_BYTES];
endOfFileByteArray = new byte[MP4MediaParser.REQUIRED_SIZE_IN_BYTES];
ByteBuffer startOfFileByteBuffer = ByteBuffer.wrap(startOfFileByteArray);
ByteBuffer endOfFileByteBuffer = ByteBuffer.wrap(endOfFileByteArray);
int startRead = inChannel.read(startOfFileByteBuffer);
if (startRead != startOfFileByteBuffer.capacity()) {
throw new IllegalArgumentException("Could not read fileStart");
}
inChannel.position(totalSizeOfFile - (MP4MediaParser.REQUIRED_SIZE_IN_BYTES + 1));
int endRead = inChannel.read(endOfFileByteBuffer);
if (endRead != endOfFileByteBuffer.capacity()) {
throw new IllegalArgumentException("Could not read fileStart");
}
}
} catch (Exception e) {
// File system error? Re-throwing, cause it cannot be handled here.
throw new RuntimeException(e);
}
}
@Override
public int read(ByteBuffer dst) throws IOException {
// Ignore boundary crossings into nulled part, as it cannot be parsed in that case.
// Only the actual movie data should be nulled.
int read = 0;
int toRead = dst.remaining();
final long startOfEndSegment = totalSizeOfFile - (endOfFileByteArray.length + 1);
if (currentPos < startOfFileByteArray.length) {
// in start buffer, read one byte
dst.put(startOfFileByteArray, (int) currentPos, toRead);
read = toRead;
} else if (currentPos < startOfEndSegment) {
throw new IllegalArgumentException(
"The nulled part should only be in the skipped boxes, else reading would be corrupt");
} else {
int remaining = (int) (totalSizeOfFile - currentPos);
int relative = endOfFileByteArray.length - remaining;
if (currentPos + toRead > totalSizeOfFile) {
toRead = (int) (totalSizeOfFile - currentPos);
}
if (toRead > 0) {
dst.put(endOfFileByteArray, relative, toRead);
read = toRead;
} else {
read = -1;
}
}
currentPos += read;
return read;
}
@Override
public int write(ByteBuffer src) throws IOException {
throw new IllegalArgumentException("MP4 files should only be read");
}
@Override
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public long position() throws IOException {
return currentPos;
}
@Override
public FileChannel position(long newPosition) throws IOException {
currentPos = newPosition;
return this;
}
@Override
public long size() throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public FileChannel truncate(long size) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public void force(boolean metaData) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public long transferTo(long position, long count, WritableByteChannel target) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public int read(ByteBuffer dst, long position) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public int write(ByteBuffer src, long position) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public FileLock lock(long position, long size, boolean shared) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
public FileLock tryLock(long position, long size, boolean shared) throws IOException {
throw new IllegalArgumentException("Not supported");
}
@Override
protected void implCloseChannel() throws IOException {
}
}
@@ -0,0 +1,36 @@
package com.sap.sailing.media.mp4;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.util.Date;
import javax.xml.parsers.ParserConfigurationException;
import org.mp4parser.AbstractBoxParser;
import org.mp4parser.IsoFile;
import org.mp4parser.PropertyBoxParserImpl;
import org.xml.sax.SAXException;
public class MediaFileParser {
public static void main(String[] args)
throws MalformedURLException, ParserConfigurationException, SAXException, IOException {
new MediaFileParser().checkMetadata(new File(args[0]));
}
public void checkMetadata(File file) throws IOException, ParserConfigurationException, SAXException {
boolean spherical = false;
long durationInMillis = -1;
Date recordStartedTimer = null;
AbstractBoxParser boxParserImpl = new PropertyBoxParserImpl();
boxParserImpl.skippingBoxes(new String[] { "mdat" });
try (IsoFile isof = new IsoFile(Files.newByteChannel(file.toPath()), boxParserImpl)) {
recordStartedTimer = MP4MediaParser.determineRecordingStart(isof);
spherical = MP4MediaParser.determine360(isof);
durationInMillis = MP4MediaParser.determineDurationInMillis(isof);
System.out.println("Start: " + recordStartedTimer.toString() + ", duration: " + durationInMillis
+ "ms; spherical: " + spherical);
}
}
}