mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-14 22:43:57 +00:00
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:
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
|
||||
|
||||
+47
-148
@@ -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);
|
||||
}
|
||||
}
|
||||
+74
-66
@@ -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,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/
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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>
|
||||
Executable → Regular
+39
-24
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user