diff options
56 files changed, 2613 insertions, 855 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 96096ec..8a940d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,7 @@ if (WIN32) include_directories(${PROJECT_SOURCE_DIR}/contrib/src/inttypes) endif() include_directories(${PROJECT_SOURCE_DIR}/contrib/src/jsmn) +#include_directories(${PROJECT_SOURCE_DIR}/contrib/src/google-url) ############################################################ # General setup @@ -352,6 +353,13 @@ else() add_definitions("-DUSCXML_STATIC") endif() +# file(GLOB +# GURL_SOURCE ${PROJECT_SOURCE_DIR}/contrib/src/google-url/googleurl/src/*.cc +# GURL_SOURCE ${PROJECT_SOURCE_DIR}/contrib/src/google-url/googleurl/src/*.h) +# list(APPEND USCXML_FILES ${GURL_SOURCE}) +# list(APPEND USCXML_FILES ${PROJECT_SOURCE_DIR}/contrib/src/google-url/base/string16.cc) + + # library suffix order if (IOS) LIST(APPEND CMAKE_FIND_LIBRARY_SUFFIXES ".dylib") @@ -447,6 +455,11 @@ elseif(WIN32) list (APPEND USCXML_CORE_LIBS ${XML_LIBRARIES}) endif() +# ICU +# find_package(ICU REQUIRED) +# include_directories(${ICU_INCLUDE_DIRS}) +# list (APPEND USCXML_CORE_LIBS ${ICU_LIBRARIES}) + # CURL find_package(CURL REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) @@ -685,9 +698,13 @@ foreach( FILE ${USCXML_FILES} ) STRING(REGEX MATCH "[^\\/]*$" COMP_NAME ${PATH}) source_group("Element\\${COMP_NAME}" FILES ${FILE}) - elseif (${PATH} MATCHES ".*\\/ioprocessor\\/.*") - STRING(REGEX MATCH "[^\\/]*$" COMP_NAME ${PATH}) - source_group("IOProcessor\\${COMP_NAME}" FILES ${FILE}) + elseif (${PATH} MATCHES ".*\\/ioprocessor\\/.*") + STRING(REGEX MATCH "[^\\/]*$" COMP_NAME ${PATH}) + source_group("IOProcessor\\${COMP_NAME}" FILES ${FILE}) + + # elseif (${PATH} MATCHES ".*\\/google-url\\/.*") + # STRING(REGEX MATCH "[^\\/]*$" COMP_NAME ${PATH}) + # source_group("Interpreter\\URL" FILES ${FILE}) else () source_group(Interpreter FILES ${FILE}) diff --git a/apps/restart-on-term.sh b/apps/restart-on-term.sh new file mode 100644 index 0000000..37bb4fa --- /dev/null +++ b/apps/restart-on-term.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# see http://superuser.com/questions/223449/auto-restart-process-on-crash +PROGRAM=$1 + +RC=1 +while [ $RC -ne 0 ]; do + ./${PROGRAM} + RC=$? +done diff --git a/apps/samples/vrml/ffmpeg-server.scxml b/apps/samples/vrml/ffmpeg-server.scxml new file mode 100644 index 0000000..1fd4038 --- /dev/null +++ b/apps/samples/vrml/ffmpeg-server.scxml @@ -0,0 +1,245 @@ +<scxml datamodel="ecmascript"> + <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/dump.js" /> + + <script> +//<![CDATA[ + function clone(item) { + if (!item) { return item; } // null, undefined values check + + var types = [ Number, String, Boolean ], + result; + + // normalizing primitives if someone did new String('aaa'), or new Number('444'); + types.forEach(function(type) { + if (item instanceof type) { + result = type( item ); + } + }); + + if (typeof result == "undefined") { + if (Object.prototype.toString.call( item ) === "[object Array]") { + result = []; + item.forEach(function(child, index, array) { + result[index] = clone( child ); + }); + } else if (typeof item == "object") { + // testing that this is DOM + if (item.nodeType && typeof item.cloneNode == "function") { + var result = item.cloneNode( true ); + } else if (!item.prototype) { // check that this is a literal + if (item instanceof Date) { + result = new Date(item); + } else { + // it is an object literal + result = {}; + for (var i in item) { + result[i] = clone( item[i] ); + } + } + } else { + // depending what you would like here, + // just keep the reference, or create new object + if (false && item.constructor) { + // would not advice to do that, reason? Read below + result = new item.constructor(); + } else { + result = item; + } + } + } else { + result = item; + } + } + + return result; + } +//]]> + </script> + + <datamodel> + <data id="modelDir" /> + <data id="requests">{}</data> + </datamodel> + + <state id="start"> + <onentry> + <log expr="modelDir" /> + </onentry> + <invoke type="osgconvert" id="osgvonvert.frame"> + <param name="threads" expr="1" /> + <finalize> + <script> + requests[_event.data.context].job.renderedFrames++; + </script> + <!-- osgconverter will copy its send request for the reply so these are still available --> + <send target="#_ffmpeg" event="render.frame"> + <param name="frame" expr="_event.data.content.bmp" /> + <param name="context" expr="_event.data.context" /> + </send> + <if cond="requests[_event.data.context].job.renderedFrames >= requests[_event.data.context].job.totalFrames"> + <send target="#_ffmpeg" event="render.end"> + <param name="context" expr="_event.data.context" /> + </send> + </if> + </finalize> + </invoke> + + <invoke type="ffmpeg" id="ffmpeg"> + <finalize> + <script> +// dump(_event); + </script> + <send target="#_parent" event="render.done"> + <param name="context" expr="_event.data.context" /> + <param name="movie" expr="_event.data.movie" /> + <param name="mimetype" expr="_event.data.mimetype" /> + <param name="filename" expr="_event.data.filename" /> + </send> + <script> + // free up job in requests + requests[_event.data.context] = undefined; + </script> + + </finalize> + </invoke> + + <state id ="idle"> + <!-- <onentry> + <script> + print("Invokers:"); + dump(_invokers['ffmpeg']); + </script> + </onentry> --> + <transition event="http.post" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'movie'"> + <!-- + Something to encode just arrived. + Interpolate pose information and send each frame to the osg converter. + The finalize of the osgconverter will send the images to ffmpeg whose + finalize will trigger the response. + --> + <script> +//<![CDATA[ +// dump(_event); + var pathDelim = ':'; // we need to flatten directories - this will seperate them in filenames + + // store event + var job = {}; + job.event = _event; + job.framesPerSec = 25; + job.lengthInSec = _event.data.content.data.movieLength; + job.format = _event.data.content.data.format; + job.height = _event.data.content.data.height; + job.width = _event.data.content.data.width; + job.keyFrames = _event.data.content.data.frames; + job.frames = []; + job.totalFrames = job.lengthInSec * job.framesPerSec + job.renderedFrames = 0; + + // sanitize + if (job.height % 2) + job.height++; + if (job.width % 2) + job.width++; + + // calculate overall relative length + var totalRelLength = 0; + for (var index in job.keyFrames) { + totalRelLength += job.keyFrames[index].relFrameLength; + } + var framesToRelLength = job.totalFrames / totalRelLength; + + // create frames + for (var kfIndex = 0; job.keyFrames[kfIndex]; kfIndex++) { + var nextPose = false; + if (job.keyFrames[kfIndex + 1]) { + nextPose = job.keyFrames[kfIndex + 1].pose; + } + var keyFrame = job.keyFrames[kfIndex]; + + keyFrame.modelFile = keyFrame.imageURL.substr(keyFrame.serverURL.length + 1); + keyFrame.file = keyFrame.modelFile.substr(0, keyFrame.modelFile.indexOf('.')); + keyFrame.ext = keyFrame.modelFile.substr(keyFrame.modelFile.indexOf('.') + 1); + + keyFrame.modelFile = keyFrame.file + ".osgb"; + keyFrame.modelFile = keyFrame.modelFile.replace(/\//g, pathDelim); + keyFrame.modelFile = modelDir + '/' + keyFrame.modelFile; + + var currentPose = keyFrame.pose; + var thisFrames = Math.round(keyFrame.relFrameLength * framesToRelLength); + var startTransitionAt = Math.round((100 - keyFrame.relTransitionLength) * 0.01 * thisFrames); + var transitionFrames = thisFrames - startTransitionAt; + + //print("---------------------\n"); + //print("lengthInSec: " + job.lengthInSec + "\n"); + //print("totalFrames: " + job.totalFrames + "\n"); + //print("thisFrames for " + kfIndex + ": " + thisFrames + "\n"); + //print("startTransitionAt for " + kfIndex + ": " + startTransitionAt + "\n"); + //print("transitionFrames for " + kfIndex + ": " + transitionFrames + "\n"); + + for (var frameIndex = 0; frameIndex < thisFrames && job.frames.length <= job.totalFrames; frameIndex++) { + var frame = clone(keyFrame); + + if (frameIndex > startTransitionAt && nextPose) { + + var transitionProgress = (frameIndex - startTransitionAt) / transitionFrames; + frame.pose.pitch += (nextPose.pitch - frame.pose.pitch) * transitionProgress; + frame.pose.roll += (nextPose.roll - frame.pose.roll) * transitionProgress; + frame.pose.yaw += (nextPose.yaw - frame.pose.yaw) * transitionProgress; + frame.pose.x += (nextPose.x - frame.pose.x) * transitionProgress; + frame.pose.y += (nextPose.y - frame.pose.y) * transitionProgress; + frame.pose.z += (nextPose.z - frame.pose.z) * transitionProgress; + frame.pose.zoom += (nextPose.zoom - frame.pose.zoom) * transitionProgress; + + //print("#########################\n"); + //print("Transition at: " + frameIndex + " \ Progress: " + transitionProgress + "\n"); + //print("Interpolated pose:\n") + //dump(frame.pose); + } + + job.frames.push(frame); + } + } + requests[_event.origin] = {}; + requests[_event.origin].job = job; +//]]> + </script> + <send target="#_ffmpeg" event="render.start" idlocation="requests[_event.origin].sendId"> + <param name="context" expr="_event.origin" /> + <param name="format" expr="requests[_event.origin].job.format" /> + <param name="width" expr="requests[_event.origin].job.width" /> + <param name="height" expr="requests[_event.origin].job.height" /> + </send> + <foreach array="requests[_event.origin].job.frames" item="frame" index="index"> + <send target="#_osgvonvert.frame"> + <param name="format" expr="'bmp'" /> + <!-- param name="dest" expr="'/Users/sradomski/Desktop/ctrl/' + index + '.bmp'" / --> + <param name="source" expr="frame.modelFile" /> + <param name="pitch" expr="frame.pose.pitch" /> + <param name="roll" expr="frame.pose.roll" /> + <param name="yaw" expr="frame.pose.yaw" /> + <param name="zoom" expr="frame.pose.zoom" /> + <param name="x" expr="frame.pose.x" /> + <param name="y" expr="frame.pose.y" /> + <param name="z" expr="frame.pose.z" /> + <param name="width" expr="requests[_event.origin].job.width" /> + <param name="height" expr="requests[_event.origin].job.height" /> + <param name="autorotate" expr="frame.pose.autorotate" /> + <param name="context" expr="_event.origin" /> + </send> + </foreach> + </transition> + + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 3 && + _event.data.pathComponent[1] === 'movie' && + _event.data.pathComponent[2] === 'codecs'"> + <send target="#_parent" event="send.codecs"> + <param name="context" expr="_event.origin" /> + <param name="codecs" expr="_invokers['ffmpeg']" /> + </send> + </transition> + </state> + </state> +</scxml>
\ No newline at end of file diff --git a/apps/samples/vrml/img/close.png b/apps/samples/vrml/img/close.png Binary files differnew file mode 100644 index 0000000..7233cbe --- /dev/null +++ b/apps/samples/vrml/img/close.png diff --git a/apps/samples/vrml/img/pitchRoll-handle.png b/apps/samples/vrml/img/pitchRoll-handle.png Binary files differnew file mode 100644 index 0000000..fcff0dd --- /dev/null +++ b/apps/samples/vrml/img/pitchRoll-handle.png diff --git a/apps/samples/vrml/img/pitchRoll.pxm b/apps/samples/vrml/img/pitchRoll.pxm Binary files differnew file mode 100644 index 0000000..1dbc3e2 --- /dev/null +++ b/apps/samples/vrml/img/pitchRoll.pxm diff --git a/apps/samples/vrml/img/xy-handle.png b/apps/samples/vrml/img/xy-handle.png Binary files differnew file mode 100644 index 0000000..0edb8cc --- /dev/null +++ b/apps/samples/vrml/img/xy-handle.png diff --git a/apps/samples/vrml/img/xy.pxm b/apps/samples/vrml/img/xy.pxm Binary files differnew file mode 100644 index 0000000..5e077ef --- /dev/null +++ b/apps/samples/vrml/img/xy.pxm diff --git a/apps/samples/vrml/img/yawZoom-handle.png b/apps/samples/vrml/img/yawZoom-handle.png Binary files differnew file mode 100644 index 0000000..f6a54ee --- /dev/null +++ b/apps/samples/vrml/img/yawZoom-handle.png diff --git a/apps/samples/vrml/img/yawZoom.pxm b/apps/samples/vrml/img/yawZoom.pxm Binary files differnew file mode 100644 index 0000000..ec00b18 --- /dev/null +++ b/apps/samples/vrml/img/yawZoom.pxm diff --git a/apps/samples/vrml/stress-vrml-server.pl b/apps/samples/vrml/stress-vrml-server.pl new file mode 100755 index 0000000..4145f29 --- /dev/null +++ b/apps/samples/vrml/stress-vrml-server.pl @@ -0,0 +1,35 @@ +#!/usr/bin/perl -w + +use Math::Round; + +my $pi = 3.14159; + +# make one thousand requests with random parameters +for (my $i = 0; $i < 1000; $i++) { + my $pitch = rand(2*$pi); + my $roll = rand(2*$pi); + my $yaw = rand(2*$pi); + my $width = rand(400) + 200; + my $height = rand(400) + 200; + my $url = "http://epikur.local:8080/vrml/HARD_MP_VAL_002.png". + "?pitch=".sprintf("%.3f",$pitch). + "&roll=".sprintf("%.3f",$roll). + "&yaw=".sprintf("%.3f",$yaw). + "&width=".sprintf("%.0f",$width). + "&height=".sprintf("%.0f",$height); + print $url."\n"; + `wget '$url'`; +} + +# for (my $pitch = 0; $pitch < 2*$pi; $pitch += 0.01) { +# for (my $roll = 0; $roll < 2*$pi; $roll += 0.01) { +# for (my $yaw = 0; $yaw < 2*$pi; $yaw += 0.01) { +# my $url = "http://epikur.local:8081/vrml/HARD_MP_VAL_002.png". +# "?pitch=".sprintf("%.3f",$pitch). +# "&roll=".sprintf("%.3f",$roll). +# "&yaw=".sprintf("%.3f",$yaw); +# print $url."\n"; +# `wget '$url'`; +# } +# } +# }
\ No newline at end of file diff --git a/apps/samples/vrml/viewer.html b/apps/samples/vrml/viewer.html index 9838c33..6e139e7 100644 --- a/apps/samples/vrml/viewer.html +++ b/apps/samples/vrml/viewer.html @@ -22,7 +22,22 @@ height:200px; padding:7px; } - </style> + .tundra .dijitTooltipContainer { + background-color:rgba(200,200,200,0.5); + background:rgba(200,200,200,0.5); + } +/* .removeThumb { + background-image: url(img/close.png); + background-repeat: no-repeat; + background-size: 100%; + text-align: center; + border: 0px; + width: 20px; + height: 20px; + vertical-align: top; + margin: -3px 0px 0px -8px; + } +*/ </style> <script type="text/javascript"> // dojoConfig = { @@ -51,8 +66,8 @@ "height" : 400, "autorotate" : false }, - "serverURL": "http://smartvortex.tbm.tudelft.nl:8090/vrml", - "imageURL": "http://smartvortex.tbm.tudelft.nl:8090/vrml/hook/HARD_MP_VAL_005.png" +// "serverURL": "http://smartvortex.tbm.tudelft.nl:8090/vrml", +// "imageURL": "http://smartvortex.tbm.tudelft.nl:8090/vrml/hook/HARD_MP_VAL_005.png" }); var viewer2 = new VRMLViewer("scene2"); // var annotations = new Annotations("annotations1", { 'viewer': viewer }); diff --git a/apps/samples/vrml/viewer.js b/apps/samples/vrml/viewer.js index 4cc167c..583f06f 100644 --- a/apps/samples/vrml/viewer.js +++ b/apps/samples/vrml/viewer.js @@ -35,6 +35,9 @@ function VRMLViewer(element, params) { this.updateScene = function() { if (self.imageURL && !self.batchChanges) { self.imgElem.src = self.imageURL + urlSuffixForPose(self.pose); + // we are showing an image, activate additional controls + self.movieAddButton.domNode.style.display = ""; + self.movieDropDown.domNode.style.display = ""; } }; @@ -67,18 +70,52 @@ function VRMLViewer(element, params) { return {x: lx,y: ly}; }; + this.populateMovieCodecs = function(server, selectElem) { + self.xhr.get({ + // The URL to request + url: server, + handleAs:"json", + headers:{"X-Requested-With":null}, + load: function(result) { + for (var codec in result.video) { + if (codec !== "mpeg1video" && + codec !== "mpeg2video" && + codec !== "mpeg4" && + codec !== "h264" && + codec !== "ayuv" && + codec !== "flashsv" && + codec !== "flashsv2" && + codec !== "flv" && + codec !== "rv40" && + codec !== "theora" && + codec !== "v210" && + codec !== "v308" && + codec !== "v408" && + codec !== "v410" && + codec !== "wmv3" && + codec !== "y41p" && + codec !== "yuv4") + continue; + selectElem.options.push({ label: result.video[codec].longName, value: codec }); + if (codec === "mpeg4") + selectElem[selectElem.options.length - 1].selected = true; + } + } + }); + } + this.refreshServer = function(server) { self.serverURL = server; self.localStorage.put("vrmlServer", self.serverURL, null); - self.progressElem.appendChild(self.progress.domNode); - self.progress.start(); +// self.progressElem.appendChild(self.progress.domNode); +// self.progress.start(); self.xhr.get({ // The URL to request url: server, handleAs:"json", headers:{"X-Requested-With":null}, load: function(result) { - self.progress.stop(); +// self.progress.stop(); for (id in self.fileStore.query) { self.fileStore.remove(id); } @@ -140,7 +177,10 @@ function VRMLViewer(element, params) { "dijit/TooltipDialog", "dojo/dnd/Moveable", "dojo/ready", - "dojo/dnd/Source"], + "dojo/dnd/Source", + "dijit/form/HorizontalSlider", + "dijit/form/Select", + "dijit/form/NumberSpinner"], function(domConst, xhr, dom, @@ -157,7 +197,10 @@ function VRMLViewer(element, params) { TooltipDialog, Moveable, ready, - Source) { + Source, + HorizontalSlider, + Selector, + NumberSpinner) { ready(function() { @@ -185,7 +228,11 @@ function VRMLViewer(element, params) { </div>\ <div style="position: absolute; left: 10px; top: 10px">\ <table></tr>\ - <td class="browseDropDown" style="vertical-align: middle"></td>\ + <td class="filesDropDown" style="vertical-align: middle"></td>\ + <td>\ + <div class="movieDropDown" style="display: inline"></div>\ + <button type="button" class="movieAddButton"></button>\ + </td>\ <td align="right"><button type="button" class="resetButton"></button></td>\ <td class="dragHandler" style="vertical-align: middle; padding-top: 4px;"></td>\ </tr></table>\ @@ -193,31 +240,33 @@ function VRMLViewer(element, params) { <div style="position: absolute; right: 10px; top: 15%; height: 50%">\ <div class="zoomSlide"></div>\ </div>\ - <div style="position: absolute; right: 55%; top: 48%">\ + <div style="position: absolute; right: 50%; top: 50%">\ <div class="pitchRollHandler" style="font-size: 0.5em; background-color: rgba(255,255,255,0.5); border-radius: 5px; moz-border-radius: 5px;">\ <table>\ <tr>\ - <td><img class="pitchRollHandlerImg" src="' + self.resRoot + 'img/pitchRoll.png" width="20px" style="padding: 2px 0px 0px 4px;" /></td>\ + <td><img class="pitchRollHandlerImg" src="' + self.resRoot + 'img/pitchRoll-handle.png" height="20px" style="padding: 2px 0px 0px 4px;" /></td>\ <td><div class="pitchLabel"></div><div class="rollLabel"></div></td>\ </tr>\ </table>\ </div>\ </div>\ - <div style="position: absolute; right: 45%; top: 48%">\ - <div class="yawZoomHandler" style="font-size: 0.5em; background-color: rgba(255,255,255,0.5); border-radius: 5px; moz-border-radius: 5px;">\ - <table>\ - <tr>\ - <td><img class="yawZoomHandlerImg" src="' + self.resRoot + 'img/yawZoom.png" width="20px" style="padding: 2px 0px 0px 4px;" /></td>\ - <td><div class="yawLabel"></div><div class="zoomLabel"></div></td>\ - </tr>\ - </table>\ + <div style="position: absolute; right: 50%; top: 50%">\ + <div class="yawZoomHandler">\ + <div style="font-size: 0.5em; background-color: rgba(255,255,255,0.5); border-radius: 5px; moz-border-radius: 5px; position: absolute; left: -34px;">\ + <table>\ + <tr>\ + <td><img class="yawZoomHandlerImg" src="' + self.resRoot + 'img/yawZoom-handle.png" height="20px" style="padding: 2px 0px 0px 4px;" /></td>\ + <td><div class="yawLabel"></div><div class="zoomLabel"></div></td>\ + </tr>\ + </table>\ + </div>\ </div>\ </div>\ - <div style="position: absolute; right: 50%; top: 58%">\ + <div style="position: absolute; right: 50%; top: 50%">\ <div class="xyHandler" style="font-size: 0.5em; background-color: rgba(255,255,255,0.5); border-radius: 5px; moz-border-radius: 5px;">\ <table>\ <tr>\ - <td><img class="xyHandlerImg" src="' + self.resRoot + 'img/xy.png" width="20px" style="padding: 2px 0px 0px 4px;" /></td>\ + <td><img class="xyHandlerImg" src="' + self.resRoot + 'img/xy-handle.png" width="20px" style="padding: 2px 0px 0px 4px;" /></td>\ <td><div class="xLabel"></div><div class="yLabel"></div></td>\ </tr>\ </table>\ @@ -237,7 +286,9 @@ function VRMLViewer(element, params) { // fetch special dom nodes for content self.messageBox = dojo.query("div.messages", element)[0]; self.imgElem = dojo.query("img.model", element)[0]; - self.browseDropDownElem = dojo.query("td.browseDropDown", element)[0]; + self.filesDropDownElem = dojo.query("td.filesDropDown", element)[0]; + self.movieDropDownElem = dojo.query("div.movieDropDown", element)[0]; + self.movieAddButtonElem = dojo.query("button.movieAddButton", element)[0]; self.resetButtonElem = dojo.query("button.resetButton", element)[0]; self.progressElem = dojo.query("div.progress", element)[0]; @@ -266,10 +317,18 @@ function VRMLViewer(element, params) { var rollLabel = dojo.query(".rollLabel", mover.node)[0]; var offset = moverRelativeTo(handlerImg, self.element); + offset.x += 30; + offset.y += 20; + + self.xyHandlerElem.style.zIndex = 1; + self.yawZoomHandlerElem.style.zIndex = 1; + self.pitchRollHandlerElem.style.zIndex = 2; + // self.pose.pitch = self.pose.pitch % (2 * 3.14159); // self.pose.roll = self.pose.roll % (2 * 3.14159); self.pose.roll = offset.x / self.pose.width - 0.5; self.pose.pitch = offset.y / self.pose.height - 0.5; + self.pose.pitch *= -1; self.pose.roll *= 2 * 3.14159; self.pose.pitch *= 2 * 3.14159; self.pose.roll = Math.ceil((self.pose.roll) * 10) / 10; @@ -295,6 +354,13 @@ function VRMLViewer(element, params) { var zoomLabel = dojo.query("div.zoomLabel", mover.node)[0]; var offset = moverRelativeTo(handlerImg, self.element); + offset.x += 7; + offset.y += 9; + + self.xyHandlerElem.style.zIndex = 1; + self.yawZoomHandlerElem.style.zIndex = 2; + self.pitchRollHandlerElem.style.zIndex = 1; + // self.pose.pitch = self.pose.pitch % (2 * 3.14159); // self.pose.roll = self.pose.roll % (2 * 3.14159); self.pose.yaw = (self.pose.width - offset.x) / self.pose.width - 0.5; @@ -324,6 +390,13 @@ function VRMLViewer(element, params) { var yLabel = dojo.query("div.yLabel", mover.node)[0]; var offset = moverRelativeTo(handlerImg, self.element); + offset.x += 3; + offset.y += 13; + + self.xyHandlerElem.style.zIndex = 2; + self.yawZoomHandlerElem.style.zIndex = 1; + self.pitchRollHandlerElem.style.zIndex = 1; + self.pose.x = offset.x / self.pose.width - 0.5; self.pose.y = offset.y / self.pose.height - 0.5; self.pose.x *= 100; @@ -348,7 +421,7 @@ function VRMLViewer(element, params) { item.imageURL = self.imageURL; item.serverURL = self.serverURL; item.pose = avatarPose; - return {node: avatar, data: item, type: item.type}; + return {node: avatar, data: item, type: item.type}; } var handler = dojo.create( 'div', { innerHTML: '<img src="' + self.resRoot + 'img/drag.png" width="20px" />' }); return {node: handler, data: item, type: item.type}; @@ -375,7 +448,7 @@ function VRMLViewer(element, params) { model: self.fileTreeModel, persist: false, showRoot: false, - style: "height: 200px;", + style: "height: 300px;", onClick: function(item){ if ('url' in item) { self.imageURL = item.url; @@ -405,7 +478,7 @@ function VRMLViewer(element, params) { self.serverBox = new TextBox({ name: "Server", value: self.serverURL, - style: "width: 70%", + style: "width: 65%", onKeyDown: function(e) { var code = e.keyCode || e.which; @@ -424,14 +497,203 @@ function VRMLViewer(element, params) { } }); - self.browseDropDownContent = domConst.toDom('<div />'); - self.browseDropDownContent.appendChild(self.serverBox.domNode); - self.browseDropDownContent.appendChild(self.browseButton.domNode); - self.browseDropDownContent.appendChild(self.fileList.domNode); + self.filesDropDownContent = domConst.toDom('<div />'); + self.filesDropDownContent.appendChild(self.serverBox.domNode); + self.filesDropDownContent.appendChild(self.browseButton.domNode); + self.filesDropDownContent.appendChild(self.fileList.domNode); + + self.filesToolTip = new TooltipDialog({ content:self.filesDropDownContent, style:"max-height:320px"}); + self.filesDropDown = new DropDownButton({ label: "Files", dropDown: self.filesToolTip }); + self.filesDropDownElem.appendChild(self.filesDropDown.domNode); + + self.movieDropDownContent = domConst.toDom('<div style="overflow: auto; max-height: 420px;" />'); + + self.createMovieThumb = function(item, mode) { + if (mode == 'avatar') { + // when dragged + var avatar = dojo.create( 'div', { innerHTML: item.data }); + var avatarPose = dojo.clone(self.pose); + avatarPose.width=60; + avatarPose.height=60; + var avatarImgUrl = urlSuffixForPose(avatarPose); + avatar.innerHTML = '<img src=' + self.imageURL + avatarImgUrl + ' /> '; + item.srcEcc = "VRMLViewer"; + item.iconPoseUrl = self.imageURL + avatarImgUrl; + item.imageURL = self.imageURL; + item.serverURL = self.serverURL; + item.pose = avatarPose; + return {node: avatar, data: item, type: item.type}; + } else { + // var thumb = dojo.create( 'div', { innerHTML: item.data }); + + // when added to list + var thumb = domConst.toDom("\ + <div>\ + <table><tr><td>\ + <img class=\"movieThumb\"/>\ + <img class=\"removeThumb\" style=\"vertical-align: top; margin: -3px 0px 0px -8px; width: 20px; height: 20px;\"/>\ + </td><td align=\"left\">\ + <table><tr>\ + <td>Frame:</td><td><div class=\"relFrameLength\"/></td>\ + </tr><tr>\ + <td>Transition:</td><td><div class=\"relTransitionLength\"/></td>\ + </tr></table>\ + </td></tr></table>\ + </div>\ + "); + thumb = dojo.query("div", thumb)[0]; + + var thumbImgElem = dojo.query("img.movieThumb", thumb)[0]; + var removeImgElem = dojo.query("img.removeThumb", thumb)[0]; + var relFrameLengthElem = dojo.query("div.relFrameLength", thumb)[0]; + var relTransitionLengthElem = dojo.query("div.relTransitionLength", thumb)[0]; + + item.relFrameLengthSlider = new HorizontalSlider({ + value: 50, + title: "Relative Duration of Frame", + style: "width:150px;" + }, relFrameLengthElem); + + item.relTransitionLengthSlider = new HorizontalSlider({ + value: 80, + title: "Relative Duration of Transition", + style: "width:150px;" + }, relTransitionLengthElem); + + removeImgElem.onclick = function() { + self.addToMovieHandler.forInItems(function(obj, key, ctx) { + if (obj.data === item) { + // haha - what a mess! + self.addToMovieHandler.selectNone(); + self.addToMovieHandler.selection[key] = obj; + self.addToMovieHandler.deleteSelectedNodes(); + } + }); + } + removeImgElem.src = self.resRoot + "img/close.png"; + + var thumbPose = dojo.clone(self.pose); + thumbPose.width = self.pose.width / 10; + thumbPose.height = self.pose.height / 10; + var thumbImgUrl = urlSuffixForPose(thumbPose); + + thumbImgElem.src = self.imageURL + thumbImgUrl; + // removeImgElem.src = self.resRoot + 'img/close.png'; + + item.srcEcc = "VRMLViewer"; + item.iconPoseUrl = self.imageURL + thumbImgUrl; + item.imageURL = self.imageURL; + item.serverURL = self.serverURL; + item.pose = thumbPose; + + return {node: thumb, data: item, type: item.type}; + } + }; + + self.addToMovieHandler = new Source(self.movieDropDownContent, {copyOnly: true, creator: self.createMovieThumb}); + + self.movieFormatSelection = new Selector({ + name: "movieFormat", + options: [] + }); + self.populateMovieCodecs(self.serverURL + '/movie/codecs', self.movieFormatSelection); + + self.movieDropDownContent.appendChild(dojo.create( 'div', { + innerHTML: "Format: ", + style: "margin-right: 1em; margin-left: 0.2em; display:inline;" + })); + self.movieDropDownContent.appendChild(self.movieFormatSelection.domNode); + + self.movieDurationSpinner = new NumberSpinner({ + value: 10, + smallDelta: 1, + style: "width: 40px", + constraints: { min:0, places:0 }, + }); + self.movieDropDownContent.appendChild(self.movieDurationSpinner.domNode); + self.movieDropDownContent.appendChild(dojo.create( 'div', { + innerHTML: "sec ", + style: "margin-right: 1em; margin-left: 0.2em; display:inline;" + })); + + self.movieHeightSpinner = new NumberSpinner({ + value: 400, + smallDelta: 1, + style: "width: 60px", + constraints: { min:40, places:0 }, + }); + self.movieDropDownContent.appendChild(self.movieHeightSpinner.domNode); + self.movieDropDownContent.appendChild(dojo.create( 'div', { + innerHTML: "x", + style: "margin-right: 0.5em; margin-left: 0.5em; display:inline;" + })); + + self.movieWidthSpinner = new NumberSpinner({ + value: 600, + smallDelta: 1, + style: "width: 60px", + constraints: { min:40, places:0 }, + }); + self.movieDropDownContent.appendChild(self.movieWidthSpinner.domNode); + + self.movieCreateButton = new Button({ + label: "Create", + onClick: function(){ + var form = document.createElement("form"); + + form.setAttribute("method", "post"); + form.setAttribute("action", self.serverURL + "/movie"); + + var submitData = {}; + submitData.frames = []; + submitData.movieLength = self.movieDurationSpinner.value; + submitData.format = self.movieFormatSelection.value; + submitData.width = self.movieWidthSpinner.value; + submitData.height = self.movieHeightSpinner.value; + + self.addToMovieHandler.forInItems(function(obj, key, ctx) { + var jsonData = { + iconPoseUrl: obj.data.iconPoseUrl, + imageURL: obj.data.imageURL, + serverURL: obj.data.serverURL, + pose: obj.data.pose, + relFrameLength: obj.data.relFrameLengthSlider.value, + relTransitionLength: obj.data.relTransitionLengthSlider.value, + } + submitData.frames.push(jsonData); + }); + + var hiddenField = document.createElement("input"); + hiddenField.setAttribute("type", "hidden"); + hiddenField.setAttribute("name", "data"); + hiddenField.setAttribute("value", JSON.stringify(submitData)); + + form.appendChild(hiddenField); + + document.body.appendChild(form); + form.submit(); + document.body.removeChild(form); + } + }); + self.movieDropDownContent.appendChild(self.movieCreateButton.domNode); + + + self.movieToolTip = new TooltipDialog({ content:self.movieDropDownContent }); + self.movieDropDown = new DropDownButton({ + label: "Movie", + style: "display: none;", + dropDown: self.movieToolTip }); + self.movieDropDownElem.appendChild(self.movieDropDown.domNode); + + self.movieAddButton = new Button({ + label: "+", + style: "margin-left: -10px; display: none;", + onClick: function(){ + // we could pass item.data here to creator + self.addToMovieHandler.insertNodes(false, [ { } ]); + } + }, self.movieAddButtonElem); - self.browseToolTip = new TooltipDialog({ content:self.browseDropDownContent, style:"max-height:200px"}); - self.browseDropDown = new DropDownButton({ label: "Files", dropDown: self.browseToolTip }); - self.browseDropDownElem.appendChild(self.browseDropDown.domNode); self.resetButton = new Button({ label: "Reset", diff --git a/apps/samples/vrml/vrml-server.scxml b/apps/samples/vrml/vrml-server.scxml index 8563cb9..20d63a6 100644 --- a/apps/samples/vrml/vrml-server.scxml +++ b/apps/samples/vrml/vrml-server.scxml @@ -5,55 +5,11 @@ <script> var wrls = {}; // information of the wrl, vrml files var models = {}; // information of the osgb files - var processed = {}; // information about processed files var pathDelim = ':'; // we need to flatten directories - this will seperate them in filenames - /** - * This pattern matches the query string we use as part of generated image filenames - */ - var numPattern = '(-?[0-9]+\.?[0-9]*)'; - var formatPattern = new RegExp( - '-(' + numPattern + // pitch - '_' + numPattern + // roll - '_' + numPattern + // yaw - '_' + numPattern + // zoom - '_' + numPattern + // x - '_' + numPattern + // y - '_' + numPattern + // z - '_' + numPattern + // width - '_' + numPattern + // height - '_(off|on)' + // autorotate - ')\\.\\w+$'); // end - - /** - * Transform a file we found into a processed or model struct - */ - function fileToStruct(file) { - var struct = {}; - var formatMatch = formatPattern.exec(file.name); - // is this a processed file? - if (formatMatch && formatMatch.length == 12) { - struct.key = file.relDir.replace(/\//g, pathDelim).substr(1) + file.name.substr(0, file.name.length - formatMatch[0].length); - struct.format = formatMatch[1] + '.' + file.extension; - struct.pitch = parseFloat(formatMatch[2]); - struct.roll = parseFloat(formatMatch[3]); - struct.yaw = parseFloat(formatMatch[4]); - struct.zoom = parseFloat(formatMatch[5]); - struct.x = parseFloat(formatMatch[6]); - struct.y = parseFloat(formatMatch[7]); - struct.z = parseFloat(formatMatch[8]); - struct.width = parseFloat(formatMatch[9]); - struct.height = parseFloat(formatMatch[10]); - struct.autorotate = parseFloat(formatMatch[11]); - } else { - struct.key = file.relDir.replace(/\//g, pathDelim).substr(1) + file.strippedName; - } - return struct; - } - /** - * Transform a http request into something to look up in the processed structure + * Transform a http request into a pose */ function reqToStruct(req) { var struct = {}; @@ -80,21 +36,14 @@ struct.key += pathDelim; struct.key += struct.file; - struct.format = - struct.pitch + '_' + - struct.roll + '_' + - struct.yaw + '_' + - struct.zoom + '_' + - struct.x + '_' + - struct.y + '_' + - struct.z + '_' + - struct.width + '_' + - struct.height + '_' + - struct.autorotate + '.' + - struct.ext; return struct; } + function keyForFile(file) { + var key = file.relDir.replace(/\//g, pathDelim).substr(1) + file.strippedName; + return key; + } + function isSupportedFormat(extension) { if (extension === "gif") return true; @@ -176,38 +125,21 @@ <param name="dir" expr="_x['args']['tmp-path']" /> <param name="recurse" expr="false" /> <param name="reportExisting" expr="true" /> + <param name="suffix" expr="'osgb'" /> <!-- Called for every file we found --> <finalize> <script> - _event.fileStruct = fileToStruct(_event.data.file); - if (_event.data.file.extension === "osgb") { - // this is a binary 3D file converted from the wrls - - if (_event.name === "file.deleted") { - delete models[_event.fileStruct.key]; - print("Removed a vanished osgb file at " + _event.fileStruct.key + "\n"); - } else { - models[_event.fileStruct.key] = _event.data.file; - models[_event.fileStruct.key].group = '/' + _event.data.file.name.split(pathDelim).slice(0,-1).join('/'); - print("Inserted a new osgb file at " + _event.fileStruct.key + "\n"); - } - - } else if ('format' in _event.fileStruct) { - // this is a processed file generated for some request - - if (_event.name === "file.deleted") { - delete processed[_event.fileStruct.key][_event.fileStruct.format]; - print("Removed a vanished processed file at " + _event.fileStruct.key + "\n"); - } else { - if (!(_event.fileStruct.key in processed)) { - processed[_event.fileStruct.key] = {} - } - processed[_event.fileStruct.key][_event.fileStruct.format] = _event.data.file; - print("Inserted a new processed file at " + _event.fileStruct.key + "\n"); - } + var key = keyForFile(_event.data.file); + // this is a binary 3D file converted from the wrls + + if (_event.name === "file.deleted") { + delete models[key]; + print("Removed a vanished osgb file at " + _event.fileStruct.key + "\n"); } else { - print("Ignoring " + _event.data.file.name + "\n"); + models[key] = _event.data.file; + models[key].group = '/' + _event.data.file.name.split(pathDelim).slice(0,-1).join('/'); + print("Inserted a new osgb file at " + key + "\n"); } </script> </finalize> @@ -220,23 +152,21 @@ <param name="suffix" expr="'vrml wrl'" /> <finalize> <script> - _event.fileStruct = fileToStruct(_event.data.file); + _event.key = keyForFile(_event.data.file); if (_event.name === "file.existing" || _event.name === "file.added") { - wrls[_event.fileStruct.key] = _event.data.file; - print("Inserting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.fileStruct.key + "\n"); + wrls[_event.key] = _event.data.file; + print("Inserting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.key + "\n"); } if (_event.name === "file.deleted") { - delete wrls[_event.fileStruct.key]; - print("Deleting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.fileStruct.key + "\n"); + delete wrls[_event.key]; + print("Deleting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.key + "\n"); } </script> <if cond="models && - (!(_event.fileStruct.key in models) || - wrls[_event.fileStruct.key].mtime > models[_event.fileStruct.key].mtime) - "> + (!(_event.key in models) || wrls[_event.key].mtime > models[_event.key].mtime)"> <send target="#_osgvonvert.osgb"> <param name="source" expr="_event.data.file.path" /> - <param name="dest" expr="_x['args']['tmp-path'] + '/' + _event.fileStruct.key + '.osgb'" /> + <param name="dest" expr="_x['args']['tmp-path'] + '/' + _event.key + '.osgb'" /> </send> </if> </finalize> @@ -244,83 +174,59 @@ <!-- Start the osgconvert invoker to transform 3D files --> <invoke type="osgconvert" id="osgvonvert.osgb"> - <param name="threads" expr="4" /> - <!--finalize> + <param name="threads" expr="20" /> + <finalize> <script> - // we could put the file into the osgbs or processed here, but we rely on the directory monitors for now - print("Received " + _event.name + " regarding " + _event.data.dest + "\n"); + //dump(_event); </script> - </finalize--> + <if cond="'context' in _event.data"> + <!-- this was generated in reply to a request --> + <if cond="_event.name.endsWith('success')"> + <respond status="200" to="_event.data.context"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="_event.data.content[_event.data.format]" /> + </respond> + <else /> + <respond status="404" to="_event.data.context"> + <header name="Connection" value="close" /> + </respond> + </if> + </if> + </finalize> </invoke> + <!-- Start a nested SCXML interpreter to create movies from the images --> + <invoke type="scxml" id="scxml.ffmpeg" src="ffmpeg-server.scxml" autoforward="true"> + <param name="modelDir" expr="_x['args']['tmp-path']" /> + </invoke> + <!-- Idle here --> <state id="idle"> - <!--onentry> - <log expr="_event" /> - </onentry --> <transition event="http.get" target="idle" cond=" _event.data.pathComponent.length >= 2 && _event.data.pathComponent[_event.data.pathComponent.length - 1].indexOf('.') !== -1"> - <!-- request for a specific format - http://host/vrml/relative/path/format?query=string --> + <!-- request for a specific format http://host/vrml/relative/path/format?query=string --> <script> _event.fileStruct = reqToStruct(_event.data); - _event.dest = _x['args']['tmp-path'] + '/' + _event['fileStruct'].key + '-' + _event['fileStruct'].format; - - print("Got a request for [" + _event['fileStruct'].key + '-' + _event['fileStruct'].format + "]\n"); -// dump(_event); </script> - <if cond="_event['fileStruct'].key in models && isSupportedFormat(_event['fileStruct'].ext)"> + <if cond="_event.fileStruct.key in models && isSupportedFormat(_event['fileStruct'].ext)"> <!-- There is such a file available as osgb --> - <if cond=" - _event['fileStruct'].key in processed && - _event['fileStruct'].format in processed[_event['fileStruct'].key]"> - <script> - //print("Sending " + processed[_event['fileStruct'].key][_event['fileStruct'].format].path + "\n"); - </script> - <respond status="200" to="_event.origin"> - <header name="Connection" value="close" /> - <header name="Access-Control-Allow-Origin" value="*" /> - <content fileexpr="processed[_event['fileStruct'].key][_event['fileStruct'].format].path" /> - </respond> - <else /> - <if cond="_event.name.endsWith('postponed')"> - <!-- - A postponed event we couldn't answer - --> - <respond status="404" to="_event.origin"> - <header name="Connection" value="close" /> - </respond> - <else /> - <script> - print("Processing outfile " + _event['dest'] + " from model " + _event['file'] + "\n"); - </script> - <send target="#_osgvonvert.osgb"> - <param name="source" expr="models[_event['fileStruct'].key].path" /> - <param name="dest" expr="_event['dest']" /> - <param name="pitch" expr="_event.fileStruct.pitch" /> - <param name="roll" expr="_event.fileStruct.roll" /> - <param name="yaw" expr="_event.fileStruct.yaw" /> - <param name="zoom" expr="_event.fileStruct.zoom" /> - <param name="x" expr="_event.fileStruct.x" /> - <param name="y" expr="_event.fileStruct.y" /> - <param name="z" expr="_event.fileStruct.z" /> - <param name="width" expr="_event.fileStruct.width" /> - <param name="height" expr="_event.fileStruct.height" /> - <param name="autorotate" expr="_event.fileStruct.autorotate" /> - </send> - <!-- - Redeliver the event once the untilexpr is true. The untilexpr has to evaluate - into another valid expression that we will check again on stable configurations. - --> - <postpone - untilexpr=" - '\'' + _event['fileStruct'].key + '\' in processed && - \'' + _event['fileStruct'].format + '\'' + ' in processed[\'' + _event['fileStruct'].key + '\'] || - _event.name === \'convert.failure\' && _event.data.dest === \'' + _event['dest'] + '\'' - "/> - </if> - </if> + <send target="#_osgvonvert.osgb"> + <param name="source" expr="models[_event['fileStruct'].key].path" /> + <param name="pitch" expr="_event.fileStruct.pitch" /> + <param name="roll" expr="_event.fileStruct.roll" /> + <param name="yaw" expr="_event.fileStruct.yaw" /> + <param name="zoom" expr="_event.fileStruct.zoom" /> + <param name="x" expr="_event.fileStruct.x" /> + <param name="y" expr="_event.fileStruct.y" /> + <param name="z" expr="_event.fileStruct.z" /> + <param name="width" expr="_event.fileStruct.width" /> + <param name="height" expr="_event.fileStruct.height" /> + <param name="autorotate" expr="_event.fileStruct.autorotate" /> + <param name="context" expr="_event.origin" /> + </send> <else /> <!-- There is no such model --> <respond status="404" to="_event.origin"> @@ -346,18 +252,6 @@ <transition event="http.get" target="idle" cond=" _event.data.pathComponent.length == 2 && - _event.data.pathComponent[1] === 'processed'"> - <script>//dump(_event)</script> - <respond status="200" to="_event.origin"> - <header name="Connection" value="close" /> - <header name="Content-Type" value="application/json" /> - <header name="Access-Control-Allow-Origin" value="*" /> - <content expr="processed" /> - </respond> - </transition> - - <transition event="http.get" target="idle" cond=" - _event.data.pathComponent.length == 2 && _event.data.pathComponent[1] === 'wrls'"> <script>//dump(_event)</script> <respond status="200" to="_event.origin"> @@ -367,7 +261,7 @@ <content expr="wrls" /> </respond> </transition> - + <!-- request for topmost list of all files --> <transition event="http.get" target="idle" cond=" _event.data.pathComponent.length == 1"> @@ -382,14 +276,34 @@ <!-- XHR CORS preflight response --> <transition event="http.options" target="idle"> - <script>dump(_event);</script> + <script>//dump(_event);</script> <respond status="200" to="_event.origin"> <header name="Access-Control-Allow-Origin" value="*" /> <header name="Access-Control-Allow-Methods" value="GET, OPTIONS" /> <header name="Access-Control-Allow-Headers" value="X-Requested-With" /> </respond> </transition> - + + <transition event="render.done" target="idle"> + <script>//dump(_event);</script> + <respond status="200" to="_event.data.context"> + <header name="Connection" value="close" /> + <header name="Content-Type" valueexpr="_event.data.mimetype" /> + <header name="Content-Disposition" valueexpr="'attachment; filename=' + _event.data.filename" /> + <content expr="_event.data.movie" /> + </respond> + </transition> + + <transition event="send.codecs" target="idle"> + <script>//dump(_event);</script> + <respond status="200" to="_event.data.context"> + <header name="Connection" value="close" /> + <header name="Content-Type" valueexpr="_event.data.mimetype" /> + <header name="Content-Disposition" valueexpr="'attachment; filename=' + _event.data.filename" /> + <content expr="_event.data.codecs" /> + </respond> + </transition> + </state> </state> <state id="final" final="true" /> diff --git a/apps/samples/vrml/vrml-server.scxml.old b/apps/samples/vrml/vrml-server.scxml.old new file mode 100644 index 0000000..70a7c3a --- /dev/null +++ b/apps/samples/vrml/vrml-server.scxml.old @@ -0,0 +1,416 @@ +<scxml datamodel="ecmascript" name="vrml"> + <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/dump.js" /> + <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/string.endsWith.js" /> + <script src="http://uscxml.tk.informatik.tu-darmstadt.de/scripts/array.last.js" /> + <script> + var wrls = {}; // information of the wrl, vrml files + var models = {}; // information of the osgb files + var processed = {}; // information about processed files + + var pathDelim = ':'; // we need to flatten directories - this will seperate them in filenames + + /** + * This pattern matches the query string we use as part of generated image filenames + */ + var numPattern = '(-?[0-9]+\.?[0-9]*)'; + var formatPattern = new RegExp( + '-(' + numPattern + // pitch + '_' + numPattern + // roll + '_' + numPattern + // yaw + '_' + numPattern + // zoom + '_' + numPattern + // x + '_' + numPattern + // y + '_' + numPattern + // z + '_' + numPattern + // width + '_' + numPattern + // height + '_(off|on)' + // autorotate + ')\\.\\w+$'); // end + + /** + * Transform a file we found into a processed or model struct + */ + function fileToStruct(file) { + var struct = {}; + var formatMatch = formatPattern.exec(file.name); + // is this a processed file? + if (formatMatch && formatMatch.length == 12) { + struct.key = file.relDir.replace(/\//g, pathDelim).substr(1) + file.name.substr(0, file.name.length - formatMatch[0].length); + struct.format = formatMatch[1] + '.' + file.extension; + struct.pitch = parseFloat(formatMatch[2]); + struct.roll = parseFloat(formatMatch[3]); + struct.yaw = parseFloat(formatMatch[4]); + struct.zoom = parseFloat(formatMatch[5]); + struct.x = parseFloat(formatMatch[6]); + struct.y = parseFloat(formatMatch[7]); + struct.z = parseFloat(formatMatch[8]); + struct.width = parseFloat(formatMatch[9]); + struct.height = parseFloat(formatMatch[10]); + struct.autorotate = parseFloat(formatMatch[11]); + } else { + struct.key = file.relDir.replace(/\//g, pathDelim).substr(1) + file.strippedName; + } + return struct; + } + + /** + * Transform a http request into something to look up in the processed structure + */ + function reqToStruct(req) { + var struct = {}; + + var query = (('query' in req) ? req.query : {}); + + struct.pitch = (('pitch' in query && isNumber(query.pitch)) ? query.pitch : 0); + struct.roll = (('roll' in query && isNumber(query.roll)) ? query.roll : 0); + struct.yaw = (('yaw' in query && isNumber(query.yaw)) ? query.yaw : 0); + struct.zoom = (('zoom' in query && isNumber(query.zoom)) ? query.zoom : 1); + struct.x = (('x' in query && isNumber(query.x)) ? query.x : 0); + struct.y = (('y' in query && isNumber(query.y)) ? query.y : 0); + struct.z = (('z' in query && isNumber(query.z)) ? query.z : 0); + struct.width = (('width' in query && isNumber(query.width)) ? query.width : 640); + struct.height = (('height' in query && isNumber(query.height)) ? query.height : 480); + struct.autorotate = (('autorotate' in query && (query.autorotate === 'on' || query.autorotate === 'off')) ? query.autorotate : 'on'); + + var fileComp = req.pathComponent[req.pathComponent.length - 1]; + struct.file = fileComp.substr(0, fileComp.indexOf('.')); + struct.ext = fileComp.substr(fileComp.indexOf('.') + 1); + + struct.key = _event.data.pathComponent.slice(1, _event.data.pathComponent.length - 1).join(pathDelim); + if (struct.key.length > 0) + struct.key += pathDelim; + struct.key += struct.file; + + struct.format = + struct.pitch + '_' + + struct.roll + '_' + + struct.yaw + '_' + + struct.zoom + '_' + + struct.x + '_' + + struct.y + '_' + + struct.z + '_' + + struct.width + '_' + + struct.height + '_' + + struct.autorotate + '.' + + struct.ext; + return struct; + } + + function isSupportedFormat(extension) { + if (extension === "gif") + return true; + if (extension === "jpg") + return true; + if (extension === "jpeg") + return true; + if (extension === "png") + return true; + if (extension === "tif") + return true; + if (extension === "tiff") + return true; + if (extension === "bmp") + return true; + return false; + } + + // list all available models in a summary format for the topmost request + function overviewList() { + var struct = {}; + struct.models = {}; + for (key in models) { + var model = models[key]; + var group = models[key].group + var name = model.strippedName.split(pathDelim).last(); + var entry = assign(struct, ['models'].concat(group.substr(1).split('/')).concat(name), {}); + entry.url = + _ioprocessors['http'].location + model.relDir + model.strippedName.split(pathDelim).join('/') + '.png'; + entry.path = model.relDir + model.strippedName.split(pathDelim).join('/') + '.png'; + } + return struct; + } + + // check whether a given string represents a number + function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + + // allow to set deep keys in an object + function assign(obj, path, value) { + if (typeof path === 'string') + path = path.split('.'); + if (!(path instanceof Array)) + return undefined; + + lastKeyIndex = path.length-1; + for (var i = 0; i < lastKeyIndex; ++ i) { + key = path[i]; + if (key.length == 0) + continue; + if (!(key in obj)) + obj[key] = {} + obj = obj[key]; + } + obj[path[lastKeyIndex]] = value; + return obj[path[lastKeyIndex]]; + } + </script> + <state id="main"> + <!-- Stop processing if no vrml-path was given on command line --> + <transition target="final" cond="_x['args']['vrml-path'] == undefined || _x['args']['vrml-path'].length == 0"> + <log expr="'No --vrml-path given'" /> + </transition> + + <!-- Stop processing if no tmp-path was given on command line --> + <transition target="final" cond="_x['args']['tmp-path'] == undefined || _x['args']['tmp-path'].length == 0"> + <log expr="'No --tmp-path given'" /> + </transition> + + <!-- Stop processing if any error occurs --> + <transition target="final" event="error"> + <log expr="'An error occured:'" /> + <script>dump(_event);</script> + </transition> + + <!-- Start the directory monitor for generated files --> + <invoke type="dirmon" id="dirmon.processed"> + <param name="dir" expr="_x['args']['tmp-path']" /> + <param name="recurse" expr="false" /> + <param name="reportExisting" expr="true" /> + <!-- Called for every file we found --> + <finalize> + <script> + _event.fileStruct = fileToStruct(_event.data.file); + + if (_event.data.file.extension === "osgb") { + // this is a binary 3D file converted from the wrls + + if (_event.name === "file.deleted") { + delete models[_event.fileStruct.key]; + print("Removed a vanished osgb file at " + _event.fileStruct.key + "\n"); + } else { + models[_event.fileStruct.key] = _event.data.file; + models[_event.fileStruct.key].group = '/' + _event.data.file.name.split(pathDelim).slice(0,-1).join('/'); + print("Inserted a new osgb file at " + _event.fileStruct.key + "\n"); + } + + } else if ('format' in _event.fileStruct) { + // this is a processed file generated for some request + + if (_event.name === "file.deleted") { + delete processed[_event.fileStruct.key][_event.fileStruct.format]; + print("Removed a vanished processed file at " + _event.fileStruct.key + "\n"); + } else { + if (!(_event.fileStruct.key in processed)) { + processed[_event.fileStruct.key] = {} + } + processed[_event.fileStruct.key][_event.fileStruct.format] = _event.data.file; + print("Inserted a new processed file at " + _event.fileStruct.key + "\n"); + } + } else { + print("Ignoring " + _event.data.file.name + "\n"); + } + </script> + </finalize> + </invoke> + + <!-- Start the directory monitor for wrl files --> + <invoke type="dirmon" id="dirmon.vrml"> + <param name="dir" expr="_x['args']['vrml-path']" /> + <param name="recurse" expr="true" /> + <param name="suffix" expr="'vrml wrl'" /> + <finalize> + <script> + _event.fileStruct = fileToStruct(_event.data.file); + if (_event.name === "file.existing" || _event.name === "file.added") { + wrls[_event.fileStruct.key] = _event.data.file; + print("Inserting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.fileStruct.key + "\n"); + } + if (_event.name === "file.deleted") { + delete wrls[_event.fileStruct.key]; + print("Deleting wrl " + _event.data.file.path + " from " +_event.data.file.relDir + " at " + _event.fileStruct.key + "\n"); + } + </script> + <if cond="models && + (!(_event.fileStruct.key in models) || + wrls[_event.fileStruct.key].mtime > models[_event.fileStruct.key].mtime) + "> + <send target="#_osgvonvert.osgb"> + <param name="source" expr="_event.data.file.path" /> + <param name="dest" expr="_x['args']['tmp-path'] + '/' + _event.fileStruct.key + '.osgb'" /> + </send> + </if> + </finalize> + </invoke> + + <!-- Start the osgconvert invoker to transform 3D files --> + <invoke type="osgconvert" id="osgvonvert.osgb"> + <param name="threads" expr="4" /> + <finalize> + <script> + //dump(_event); + </script> + <!-- <file operation="write" contentexpr="_event.data.content" sandbox="off" urlexpr="_event.data.dest" /> --> + </finalize> + <!--finalize> + <script> + // we could put the file into the osgbs or processed here, but we rely on the directory monitors for now + print("Received " + _event.name + " regarding " + _event.data.dest + "\n"); + </script> + </finalize--> + </invoke> + + <!-- Start a nested SCXML interpreter to create movies from the images --> + <!-- <invoke type="scxml" id="scxml.ffmpeg" src="ffmpeg-server.scxml" autoforward="true"> + <param name="modelDir" expr="_x['args']['tmp-path']" /> + </invoke> --> + + <!-- Idle here --> + <state id="idle"> + <!--onentry> + <log expr="_event" /> + </onentry --> + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length >= 2 && + _event.data.pathComponent[_event.data.pathComponent.length - 1].indexOf('.') !== -1"> + <!-- request for a specific format + http://host/vrml/relative/path/format?query=string --> + <script> + _event.fileStruct = reqToStruct(_event.data); + _event.dest = _x['args']['tmp-path'] + '/' + _event['fileStruct'].key + '-' + _event['fileStruct'].format; + print("Got a request for [" + _event['fileStruct'].key + '-' + _event['fileStruct'].format + "]\n"); +// dump(_event); + </script> + <if cond="_event['fileStruct'].key in models && isSupportedFormat(_event['fileStruct'].ext)"> + <!-- There is such a file available as osgb --> + <if cond=" + _event['fileStruct'].key in processed && + _event['fileStruct'].format in processed[_event['fileStruct'].key]"> + <script> + //print("Sending " + processed[_event['fileStruct'].key][_event['fileStruct'].format].path + "\n"); + </script> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content fileexpr="processed[_event['fileStruct'].key][_event['fileStruct'].format].path" /> + </respond> + <else /> + <if cond="_event.name.endsWith('postponed')"> + <!-- + A postponed event we couldn't answer + --> + <respond status="404" to="_event.origin"> + <header name="Connection" value="close" /> + </respond> + <else /> + <script> + print("Processing outfile " + _event['dest'] + " from model " + _event['file'] + "\n"); + </script> + <send target="#_osgvonvert.osgb"> + <param name="source" expr="models[_event['fileStruct'].key].path" /> + <param name="dest" expr="_event['dest']" /> + <param name="pitch" expr="_event.fileStruct.pitch" /> + <param name="roll" expr="_event.fileStruct.roll" /> + <param name="yaw" expr="_event.fileStruct.yaw" /> + <param name="zoom" expr="_event.fileStruct.zoom" /> + <param name="x" expr="_event.fileStruct.x" /> + <param name="y" expr="_event.fileStruct.y" /> + <param name="z" expr="_event.fileStruct.z" /> + <param name="width" expr="_event.fileStruct.width" /> + <param name="height" expr="_event.fileStruct.height" /> + <param name="autorotate" expr="_event.fileStruct.autorotate" /> + </send> + <!-- + Redeliver the event once the untilexpr is true. The untilexpr has to evaluate + into another valid expression that we will check again on stable configurations. + --> + <postpone + untilexpr=" + '\'' + _event['fileStruct'].key + '\' in processed && + \'' + _event['fileStruct'].format + '\'' + ' in processed[\'' + _event['fileStruct'].key + '\'] || + _event.name === \'convert.failure\' && _event.data.dest === \'' + _event['dest'] + '\'' + "/> + </if> + </if> + <else /> + <!-- There is no such model --> + <respond status="404" to="_event.origin"> + <header name="Connection" value="close" /> + </respond> + </if> + </transition> + + <!-- + process request for JSON datastructures + --> + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'models'"> + <script>//dump(_event)</script> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="models" /> + </respond> + </transition> + + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'processed'"> + <script>//dump(_event)</script> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="processed" /> + </respond> + </transition> + + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 2 && + _event.data.pathComponent[1] === 'wrls'"> + <script>//dump(_event)</script> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="wrls" /> + </respond> + </transition> + + <!-- request for topmost list of all files --> + <transition event="http.get" target="idle" cond=" + _event.data.pathComponent.length == 1"> + <script>//dump(_event);</script> + <respond status="200" to="_event.origin"> + <header name="Connection" value="close" /> + <header name="Content-Type" value="application/json" /> + <header name="Access-Control-Allow-Origin" value="*" /> + <content expr="overviewList()" /> + </respond> + </transition> + + <!-- XHR CORS preflight response --> + <transition event="http.options" target="idle"> + <script>//dump(_event);</script> + <respond status="200" to="_event.origin"> + <header name="Access-Control-Allow-Origin" value="*" /> + <header name="Access-Control-Allow-Methods" value="GET, OPTIONS" /> + <header name="Access-Control-Allow-Headers" value="X-Requested-With" /> + </respond> + </transition> + + <transition event="render.done" target="idle"> + <script>dump(_event);</script> + <respond status="200" to="_event.data.context"> + <header name="Connection" value="close" /> + <header name="Content-Type" valueexpr="_event.data.mimetype" /> + <header name="Content-Disposition" valueexpr="'attachment; filename=' + _event.data.filename" /> + <content expr="_event.data.movie" /> + </respond> + </transition> + + </state> + </state> + <state id="final" final="true" /> +</scxml>
\ No newline at end of file diff --git a/apps/w3c-mmi/im/MMISessionManager.cpp b/apps/w3c-mmi/im/MMISessionManager.cpp index 83d4dea..2e68ff4 100644 --- a/apps/w3c-mmi/im/MMISessionManager.cpp +++ b/apps/w3c-mmi/im/MMISessionManager.cpp @@ -1,6 +1,7 @@ #include "MMISessionManager.h" #include <uscxml/NameSpacingParser.h> #include <uscxml/concurrency/tinythread.h> +#include <uscxml/UUID.h> #include <io/uri.hpp> #include <glog/logging.h> @@ -135,7 +136,7 @@ void MMISessionManager::received(const NewContextRequest& mmiEvent, const std::s newDOM.appendChild(newDOM.importNode(_protoInterpreter.getDocument().getDocumentElement(), true)); // instantiate new interpreter and name it after the context - std::string contextId = Interpreter::getUUID(); + std::string contextId = UUID::getUUID(); Interpreter interpreter = Interpreter::fromDOM(newDOM); interpreter.setFactory(_factory); interpreter.setName(contextId); diff --git a/contrib/cmake/FindFFMPEG.cmake b/contrib/cmake/FindFFMPEG.cmake index e01e1f9..397bcba 100644 --- a/contrib/cmake/FindFFMPEG.cmake +++ b/contrib/cmake/FindFFMPEG.cmake @@ -28,7 +28,7 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) pkg_check_modules(_FFMPEG_AVFORMAT libavformat) pkg_check_modules(_FFMPEG_AVUTIL libavutil) pkg_check_modules(_FFMPEG_SWSCALE libswscale) - pkg_check_modules(_FFMPEG_SWRESAMPLE libswresample) +# pkg_check_modules(_FFMPEG_SWRESAMPLE libswresample) endif (PKG_CONFIG_FOUND) find_path(FFMPEG_AVCODEC_INCLUDE_DIR @@ -57,10 +57,10 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) PATHS ${_FFMPEG_SWSCALE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib ) - find_library(FFMPEG_SWRESAMPLE - NAMES swresample - PATHS ${_FFMPEG_SWRESAMPLE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib - ) + # find_library(FFMPEG_SWRESAMPLE + # NAMES swresample + # PATHS ${_FFMPEG_SWRESAMPLE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib + # ) if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT) set(FFMPEG_FOUND TRUE) @@ -74,7 +74,7 @@ else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) ${FFMPEG_LIBAVFORMAT} ${FFMPEG_LIBAVUTIL} ${FFMPEG_LIBSWSCALE} - ${FFMPEG_SWRESAMPLE} +# ${FFMPEG_SWRESAMPLE} ) endif (FFMPEG_FOUND) diff --git a/contrib/cmake/FindICU.cmake b/contrib/cmake/FindICU.cmake new file mode 100644 index 0000000..c48a9a0 --- /dev/null +++ b/contrib/cmake/FindICU.cmake @@ -0,0 +1,317 @@ +# +# Taken from: +# https://github.com/julp/FindICU.cmake +# +# This module can find the International Components for Unicode (ICU) Library +# +# Requirements: +# - CMake >= 2.8.3 (for new version of find_package_handle_standard_args) +# +# The following variables will be defined for your use: +# - ICU_FOUND : were all of your specified components found (include dependencies)? +# - ICU_INCLUDE_DIRS : ICU include directory +# - ICU_LIBRARIES : ICU libraries +# - ICU_VERSION : complete version of ICU (x.y.z) +# - ICU_MAJOR_VERSION : major version of ICU +# - ICU_MINOR_VERSION : minor version of ICU +# - ICU_PATCH_VERSION : patch version of ICU +# - ICU_<COMPONENT>_FOUND : were <COMPONENT> found? (FALSE for non specified component if it is not a dependency) +# +# For windows or non standard installation, define ICU_ROOT variable to point to the root installation of ICU. Two ways: +# - run cmake with -DICU_ROOT=<PATH> +# - define an environment variable with the same name before running cmake +# With cmake-gui, before pressing "Configure": +# 1) Press "Add Entry" button +# 2) Add a new entry defined as: +# - Name: ICU_ROOT +# - Type: choose PATH in the selection list +# - Press "..." button and select the root installation of ICU +# +# Example Usage: +# +# 1. Copy this file in the root of your project source directory +# 2. Then, tell CMake to search this non-standard module in your project directory by adding to your CMakeLists.txt: +# set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}) +# 3. Finally call find_package() once, here are some examples to pick from +# +# Require ICU 4.4 or later +# find_package(ICU 4.4 REQUIRED) +# +# if(ICU_FOUND) +# include_directories(${ICU_INCLUDE_DIRS}) +# add_executable(myapp myapp.c) +# target_link_libraries(myapp ${ICU_LIBRARIES}) +# endif(ICU_FOUND) + +#============================================================================= +# Copyright (c) 2011-2013, julp +# +# Distributed under the OSI-approved BSD License +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#============================================================================= + +find_package(PkgConfig QUIET) + +########## Private ########## +if(NOT DEFINED ICU_PUBLIC_VAR_NS) + set(ICU_PUBLIC_VAR_NS "ICU") # Prefix for all ICU relative public variables +endif(NOT DEFINED ICU_PUBLIC_VAR_NS) +if(NOT DEFINED ICU_PRIVATE_VAR_NS) + set(ICU_PRIVATE_VAR_NS "_${ICU_PUBLIC_VAR_NS}") # Prefix for all ICU relative internal variables +endif(NOT DEFINED ICU_PRIVATE_VAR_NS) +if(NOT DEFINED PC_ICU_PRIVATE_VAR_NS) + set(PC_ICU_PRIVATE_VAR_NS "_PC${ICU_PRIVATE_VAR_NS}") # Prefix for all pkg-config relative internal variables +endif(NOT DEFINED PC_ICU_PRIVATE_VAR_NS) + +function(icudebug _VARNAME) + if(${ICU_PUBLIC_VAR_NS}_DEBUG) + if(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) + message("${ICU_PUBLIC_VAR_NS}_${_VARNAME} = ${${ICU_PUBLIC_VAR_NS}_${_VARNAME}}") + else(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) + message("${ICU_PUBLIC_VAR_NS}_${_VARNAME} = <UNDEFINED>") + endif(DEFINED ${ICU_PUBLIC_VAR_NS}_${_VARNAME}) + endif(${ICU_PUBLIC_VAR_NS}_DEBUG) +endfunction(icudebug) + +set(${ICU_PRIVATE_VAR_NS}_ROOT "") +if(DEFINED ENV{ICU_ROOT}) + set(${ICU_PRIVATE_VAR_NS}_ROOT "$ENV{ICU_ROOT}") +endif(DEFINED ENV{ICU_ROOT}) +if (DEFINED ICU_ROOT) + set(${ICU_PRIVATE_VAR_NS}_ROOT "${ICU_ROOT}") +endif(DEFINED ICU_ROOT) + +set(${ICU_PRIVATE_VAR_NS}_BIN_SUFFIXES ) +set(${ICU_PRIVATE_VAR_NS}_LIB_SUFFIXES ) +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + list(APPEND ${ICU_PRIVATE_VAR_NS}_BIN_SUFFIXES "bin64") + list(APPEND ${ICU_PRIVATE_VAR_NS}_LIB_SUFFIXES "lib64") +endif(CMAKE_SIZEOF_VOID_P EQUAL 8) +list(APPEND ${ICU_PRIVATE_VAR_NS}_BIN_SUFFIXES "bin") +list(APPEND ${ICU_PRIVATE_VAR_NS}_LIB_SUFFIXES "lib") + +set(${ICU_PRIVATE_VAR_NS}_COMPONENTS ) +# <icu component name> <library name 1> ... <library name N> +macro(icu_declare_component _NAME) + list(APPEND ${ICU_PRIVATE_VAR_NS}_COMPONENTS ${_NAME}) + set("${ICU_PRIVATE_VAR_NS}_COMPONENTS_${_NAME}" ${ARGN}) +endmacro(icu_declare_component) + +icu_declare_component(data icudata) +icu_declare_component(uc icuuc) # Common and Data libraries +icu_declare_component(i18n icui18n icuin) # Internationalization library +icu_declare_component(io icuio ustdio) # Stream and I/O Library +icu_declare_component(le icule) # Layout library +icu_declare_component(lx iculx) # Paragraph Layout library + +########## Public ########## +set(${ICU_PUBLIC_VAR_NS}_FOUND TRUE) +set(${ICU_PUBLIC_VAR_NS}_LIBRARIES ) +set(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS ) +set(${ICU_PUBLIC_VAR_NS}_C_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_CXX_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_CPP_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_C_SHARED_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_CXX_SHARED_FLAGS "") +set(${ICU_PUBLIC_VAR_NS}_CPP_SHARED_FLAGS "") +foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS}) + string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) + set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" FALSE) # may be done in the icu_declare_component macro +endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) + +# Check components +if(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) # uc required at least + set(${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS uc) +else(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) + list(APPEND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS uc) + list(REMOVE_DUPLICATES ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) + foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) + if(NOT DEFINED ${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) + message(FATAL_ERROR "Unknown ICU component: ${${ICU_PRIVATE_VAR_NS}_COMPONENT}") + endif(NOT DEFINED ${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) + endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) +endif(NOT ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) + +# Includes +find_path( + ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS + NAMES unicode/utypes.h utypes.h + HINTS ${${ICU_PRIVATE_VAR_NS}_ROOT} + PATH_SUFFIXES "include" + DOC "Include directories for ICU" +) + +if(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS) + ########## <part to keep synced with tests/version/CMakeLists.txt> ########## + if(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS}/unicode/uvernum.h") # ICU >= 4 + file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS}/unicode/uvernum.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) + elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS}/unicode/uversion.h") # ICU [2;4[ + file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS}/unicode/uversion.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) + elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS}/unicode/utypes.h") # ICU [1.4;2[ + file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS}/unicode/utypes.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) + elseif(EXISTS "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS}/utypes.h") # ICU 1.3 + file(READ "${${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS}/utypes.h" ${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS) + else() + message(FATAL_ERROR "ICU version header not found") + endif() + + if(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *ICU_VERSION *\"([0-9]+)\".*") # ICU 1.3 + # [1.3;1.4[ as #define ICU_VERSION "3" (no patch version, ie all 1.3.X versions will be detected as 1.3.0) + set(${ICU_PUBLIC_VAR_NS}_MAJOR_VERSION "1") + set(${ICU_PUBLIC_VAR_NS}_MINOR_VERSION "${CMAKE_MATCH_1}") + set(${ICU_PUBLIC_VAR_NS}_PATCH_VERSION "0") + elseif(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *U_ICU_VERSION_MAJOR_NUM *([0-9]+).*") + # + # Since version 4.9.1, ICU release version numbering was totaly changed, see: + # - http://site.icu-project.org/download/49 + # - http://userguide.icu-project.org/design#TOC-Version-Numbers-in-ICU + # + set(${ICU_PUBLIC_VAR_NS}_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX REPLACE ".*# *define *U_ICU_VERSION_MINOR_NUM *([0-9]+).*" "\\1" ${ICU_PUBLIC_VAR_NS}_MINOR_VERSION "${${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS}") + string(REGEX REPLACE ".*# *define *U_ICU_VERSION_PATCHLEVEL_NUM *([0-9]+).*" "\\1" ${ICU_PUBLIC_VAR_NS}_PATCH_VERSION "${${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS}") + elseif(${ICU_PRIVATE_VAR_NS}_VERSION_HEADER_CONTENTS MATCHES ".*# *define *U_ICU_VERSION *\"(([0-9]+)(\\.[0-9]+)*)\".*") # ICU [1.4;1.8[ + # [1.4;1.8[ as #define U_ICU_VERSION "1.4.1.2" but it seems that some 1.4.1(?:\.\d)? have releasing error and appears as 1.4.0 + set(${ICU_PRIVATE_VAR_NS}_FULL_VERSION "${CMAKE_MATCH_1}") # copy CMAKE_MATCH_1, no longer valid on the following if + if(${ICU_PRIVATE_VAR_NS}_FULL_VERSION MATCHES "^([0-9]+)\\.([0-9]+)$") + set(${ICU_PUBLIC_VAR_NS}_MAJOR_VERSION "${CMAKE_MATCH_1}") + set(${ICU_PUBLIC_VAR_NS}_MINOR_VERSION "${CMAKE_MATCH_2}") + set(${ICU_PUBLIC_VAR_NS}_PATCH_VERSION "0") + elseif(${ICU_PRIVATE_VAR_NS}_FULL_VERSION MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(${ICU_PUBLIC_VAR_NS}_MAJOR_VERSION "${CMAKE_MATCH_1}") + set(${ICU_PUBLIC_VAR_NS}_MINOR_VERSION "${CMAKE_MATCH_2}") + set(${ICU_PUBLIC_VAR_NS}_PATCH_VERSION "${CMAKE_MATCH_3}") + endif() + else() + message(FATAL_ERROR "failed to detect ICU version") + endif() + set(${ICU_PUBLIC_VAR_NS}_VERSION "${${ICU_PUBLIC_VAR_NS}_MAJOR_VERSION}.${${ICU_PUBLIC_VAR_NS}_MINOR_VERSION}.${${ICU_PUBLIC_VAR_NS}_PATCH_VERSION}") + ########## </part to keep synced with tests/version/CMakeLists.txt> ########## + + # Check dependencies (implies pkg-config) + if(PKG_CONFIG_FOUND) + set(${ICU_PRIVATE_VAR_NS}_COMPONENTS_DUP ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) + foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_DUP}) + pkg_check_modules(PC_ICU_PRIVATE_VAR_NS "icu-${${ICU_PRIVATE_VAR_NS}_COMPONENT}" QUIET) + + if(${PC_ICU_PRIVATE_VAR_NS}_FOUND) + foreach(${PC_ICU_PRIVATE_VAR_NS}_LIBRARY ${PC_ICU_LIBRARIES}) + string(REGEX REPLACE "^icu" "" ${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY ${${PC_ICU_PRIVATE_VAR_NS}_LIBRARY}) + list(APPEND ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS ${${PC_ICU_PRIVATE_VAR_NS}_STRIPPED_LIBRARY}) + endforeach(${PC_ICU_PRIVATE_VAR_NS}_LIBRARY) + endif(${PC_ICU_PRIVATE_VAR_NS}_FOUND) + endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) + list(REMOVE_DUPLICATES ${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS) + endif(PKG_CONFIG_FOUND) + + # Check libraries + foreach(${ICU_PRIVATE_VAR_NS}_COMPONENT ${${ICU_PUBLIC_VAR_NS}_FIND_COMPONENTS}) + set(${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES ) + set(${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES ) + foreach(${ICU_PRIVATE_VAR_NS}_BASE_NAME ${${ICU_PRIVATE_VAR_NS}_COMPONENTS_${${ICU_PRIVATE_VAR_NS}_COMPONENT}}) + list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}") + list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}d") + list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}${ICU_MAJOR_VERSION}${ICU_MINOR_VERSION}") + list(APPEND ${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES "${${ICU_PRIVATE_VAR_NS}_BASE_NAME}${ICU_MAJOR_VERSION}${ICU_MINOR_VERSION}d") + endforeach(${ICU_PRIVATE_VAR_NS}_BASE_NAME) + + find_library( + ${ICU_PRIVATE_VAR_NS}_LIB_RELEASE_${${ICU_PRIVATE_VAR_NS}_COMPONENT} + NAMES ${${ICU_PRIVATE_VAR_NS}_POSSIBLE_RELEASE_NAMES} + HINTS ${${ICU_PRIVATE_VAR_NS}_ROOT} + PATH_SUFFIXES ${_ICU_LIB_SUFFIXES} + DOC "Release libraries for ICU" + ) + find_library( + ${ICU_PRIVATE_VAR_NS}_LIB_DEBUG_${${ICU_PRIVATE_VAR_NS}_COMPONENT} + NAMES ${${ICU_PRIVATE_VAR_NS}_POSSIBLE_DEBUG_NAMES} + HINTS ${${ICU_PRIVATE_VAR_NS}_ROOT} + PATH_SUFFIXES ${_ICU_LIB_SUFFIXES} + DOC "Debug libraries for ICU" + ) + + string(TOUPPER "${${ICU_PRIVATE_VAR_NS}_COMPONENT}" ${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT) + if(NOT ${ICU_PRIVATE_VAR_NS}_LIB_RELEASE_${${ICU_PRIVATE_VAR_NS}_COMPONENT} AND NOT ${ICU_PRIVATE_VAR_NS}_LIB_DEBUG_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) # both not found + set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" FALSE) + set("${ICU_PUBLIC_VAR_NS}_FOUND" FALSE) + else(NOT ${ICU_PRIVATE_VAR_NS}_LIB_RELEASE_${${ICU_PRIVATE_VAR_NS}_COMPONENT} AND NOT ${ICU_PRIVATE_VAR_NS}_LIB_DEBUG_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) # one or both found + set("${ICU_PUBLIC_VAR_NS}_${${ICU_PRIVATE_VAR_NS}_UPPER_COMPONENT}_FOUND" TRUE) + if(NOT ${ICU_PRIVATE_VAR_NS}_LIB_RELEASE_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) # release not found => we are in debug + set(${ICU_PRIVATE_VAR_NS}_LIB_${${ICU_PRIVATE_VAR_NS}_COMPONENT} "${${ICU_PRIVATE_VAR_NS}_LIB_DEBUG_${${ICU_PRIVATE_VAR_NS}_COMPONENT}}") + elseif(NOT ${ICU_PRIVATE_VAR_NS}_LIB_DEBUG_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) # debug not found => we are in release + set(${ICU_PRIVATE_VAR_NS}_LIB_${${ICU_PRIVATE_VAR_NS}_COMPONENT} "${${ICU_PRIVATE_VAR_NS}_LIB_RELEASE_${${ICU_PRIVATE_VAR_NS}_COMPONENT}}") + else() # both found + set( + ${ICU_PRIVATE_VAR_NS}_LIB_${${ICU_PRIVATE_VAR_NS}_COMPONENT} + optimized ${${ICU_PRIVATE_VAR_NS}_LIB_RELEASE_${${ICU_PRIVATE_VAR_NS}_COMPONENT}} + debug ${${ICU_PRIVATE_VAR_NS}_LIB_DEBUG_${${ICU_PRIVATE_VAR_NS}_COMPONENT}} + ) + endif() + list(APPEND ${ICU_PUBLIC_VAR_NS}_LIBRARIES ${${ICU_PRIVATE_VAR_NS}_LIB_${${ICU_PRIVATE_VAR_NS}_COMPONENT}}) + endif(NOT ${ICU_PRIVATE_VAR_NS}_LIB_RELEASE_${${ICU_PRIVATE_VAR_NS}_COMPONENT} AND NOT ${ICU_PRIVATE_VAR_NS}_LIB_DEBUG_${${ICU_PRIVATE_VAR_NS}_COMPONENT}) + endforeach(${ICU_PRIVATE_VAR_NS}_COMPONENT) + + # Try to find out compiler flags + find_program(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE icu-config HINTS ${${ICU_PRIVATE_VAR_NS}_ROOT}) + if(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_C_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cxxflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CXX_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cppflags OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CPP_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_C_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cxxflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CXX_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE} --cppflags-dynamic OUTPUT_VARIABLE ${ICU_PUBLIC_VAR_NS}_CPP_SHARED_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE) + endif(${ICU_PUBLIC_VAR_NS}_CONFIG_EXECUTABLE) + + # Check find_package arguments + include(FindPackageHandleStandardArgs) + if(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) + find_package_handle_standard_args( + ${ICU_PUBLIC_VAR_NS} + REQUIRED_VARS ${ICU_PUBLIC_VAR_NS}_LIBRARIES ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS + VERSION_VAR ${ICU_PUBLIC_VAR_NS}_VERSION + ) + else(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) + find_package_handle_standard_args(${ICU_PUBLIC_VAR_NS} "ICU not found" ${ICU_PUBLIC_VAR_NS}_LIBRARIES ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS) + endif(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) +else(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS) + if(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) + message(FATAL_ERROR "Could not find ICU include directory") + endif(${ICU_PUBLIC_VAR_NS}_FIND_REQUIRED AND NOT ${ICU_PUBLIC_VAR_NS}_FIND_QUIETLY) +endif(${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS) + +mark_as_advanced( + ${ICU_PUBLIC_VAR_NS}_INCLUDE_DIRS + ${ICU_PUBLIC_VAR_NS}_LIBRARIES +) + +# IN (args) +icudebug("FIND_COMPONENTS") +icudebug("FIND_REQUIRED") +icudebug("FIND_QUIETLY") +icudebug("FIND_VERSION") +# OUT +# Found +icudebug("FOUND") +icudebug("UC_FOUND") +icudebug("I18N_FOUND") +icudebug("IO_FOUND") +icudebug("LE_FOUND") +icudebug("LX_FOUND") +icudebug("DATA_FOUND") +# Flags +icudebug("C_FLAGS") +icudebug("CPP_FLAGS") +icudebug("CXX_FLAGS") +icudebug("C_SHARED_FLAGS") +icudebug("CPP_SHARED_FLAGS") +icudebug("CXX_SHARED_FLAGS") +# Linking +icudebug("INCLUDE_DIRS") +icudebug("LIBRARIES") +# Version +icudebug("MAJOR_VERSION") +icudebug("MINOR_VERSION") +icudebug("PATCH_VERSION") +icudebug("VERSION")
\ No newline at end of file diff --git a/contrib/local/compress_and_upload_deps.sh b/contrib/local/compress_and_upload_deps.sh index 81df0f4..49b3949 100755 --- a/contrib/local/compress_and_upload_deps.sh +++ b/contrib/local/compress_and_upload_deps.sh @@ -31,7 +31,7 @@ cd ../prebuilt ssh ${USCXML_PREBUILT_HOST} mkdir -p ${USCXML_PREBUILT_PATH}/${VERSION} PLATFORMS=`find . -maxdepth 1 -type d -regex ./[^\.].*` -PLATFORMS="include" +PLATFORMS="windows-x86_64" for FILE in ${PLATFORMS}; do PLATFORM=`basename $FILE` if [ "$PLATFORM" != "include" ]; then diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 4e9dff0..3946bef 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -1,6 +1,7 @@ #include "uscxml/Common.h" #include "uscxml/Interpreter.h" #include "uscxml/URL.h" +#include "uscxml/UUID.h" #include "uscxml/NameSpacingParser.h" #include "uscxml/debug/SCXMLDotWriter.h" @@ -9,9 +10,6 @@ #include <DOM/Simple/DOMImplementation.hpp> #include <SAX/helpers/InputSourceResolver.hpp> -#include <boost/uuid/uuid.hpp> -#include <boost/uuid/uuid_generators.hpp> -#include <boost/uuid/uuid_io.hpp> #include <boost/lexical_cast.hpp> #include <boost/tokenizer.hpp> @@ -45,15 +43,6 @@ using namespace Arabica::DOM; std::map<std::string, boost::weak_ptr<InterpreterImpl> > Interpreter::_instances; tthread::recursive_mutex Interpreter::_instanceMutex; -boost::uuids::random_generator InterpreterImpl::uuidGen; - -std::string InterpreterImpl::getUUID() { - boost::uuids::uuid uuid = uuidGen(); - std::ostringstream os; - os << uuid; - return os.str(); -} - InterpreterImpl::InterpreterImpl() { _lastRunOnMainThread = 0; _nsURL = "*"; @@ -271,7 +260,7 @@ void InterpreterImpl::init() { _xpath.setNamespaceContext(_nsContext); if (_name.length() == 0) - _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : getUUID()); + _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : UUID::getUUID()); normalize(_scxml); @@ -293,7 +282,7 @@ void InterpreterImpl::init() { } if (_sessionId.length() == 0) - _sessionId = getUUID(); + _sessionId = UUID::getUUID(); _isInitialized = true; } @@ -347,7 +336,7 @@ void InterpreterImpl::normalize(Arabica::DOM::Element<std::string>& scxml) { Arabica::DOM::Element<std::string> stateElem = Arabica::DOM::Element<std::string>(states[i]); stateElem.setAttribute("isFirstEntry", "true"); if (!stateElem.hasAttribute("id")) { - stateElem.setAttribute("id", getUUID()); + stateElem.setAttribute("id", UUID::getUUID()); } } @@ -356,7 +345,7 @@ void InterpreterImpl::normalize(Arabica::DOM::Element<std::string>& scxml) { for (int i = 0; i < invokes.size(); i++) { Arabica::DOM::Element<std::string> invokeElem = Arabica::DOM::Element<std::string>(invokes[i]); if (!invokeElem.hasAttribute("id") && !invokeElem.hasAttribute("idlocation")) { - invokeElem.setAttribute("id", getUUID()); + invokeElem.setAttribute("id", UUID::getUUID()); } // // make sure every finalize element contained has the invoke id as an attribute // Arabica::XPath::NodeSet<std::string> finalizes = _xpath.evaluate("" + _xpathPrefix + "finalize", invokeElem).asNodeSet(); @@ -371,7 +360,7 @@ void InterpreterImpl::normalize(Arabica::DOM::Element<std::string>& scxml) { Arabica::DOM::Element<std::string> finalElem = Arabica::DOM::Element<std::string>(finals[i]); finalElem.setAttribute("isFirstEntry", "true"); if (!finalElem.hasAttribute("id")) { - finalElem.setAttribute("id", getUUID()); + finalElem.setAttribute("id", UUID::getUUID()); } } @@ -379,12 +368,12 @@ void InterpreterImpl::normalize(Arabica::DOM::Element<std::string>& scxml) { for (int i = 0; i < histories.size(); i++) { Arabica::DOM::Element<std::string> historyElem = Arabica::DOM::Element<std::string>(histories[i]); if (!historyElem.hasAttribute("id")) { - historyElem.setAttribute("id", getUUID()); + historyElem.setAttribute("id", UUID::getUUID()); } } if (!scxml.hasAttribute("id")) { - scxml.setAttribute("id", getUUID()); + scxml.setAttribute("id", UUID::getUUID()); } // create a pseudo initial and transition element @@ -570,7 +559,7 @@ void InterpreterImpl::processDOMorText(const Arabica::DOM::Node<std::string>& el } } -void InterpreterImpl::processParamChilds(const Arabica::DOM::Node<std::string>& element, std::multimap<std::string, std::string>& params) { +void InterpreterImpl::processParamChilds(const Arabica::DOM::Node<std::string>& element, std::multimap<std::string, Data>& params) { NodeSet<std::string> paramElems = filterChildElements(_xmlNSPrefix + "param", element); try { for (int i = 0; i < paramElems.size(); i++) { @@ -578,11 +567,11 @@ void InterpreterImpl::processParamChilds(const Arabica::DOM::Node<std::string>& LOG(ERROR) << "param element is missing name attribute"; continue; } - std::string paramValue; + Data paramValue; if (HAS_ATTR(paramElems[i], "expr") && _dataModel) { - paramValue = _dataModel.evalAsString(ATTR(paramElems[i], "expr")); + paramValue = _dataModel.getStringAsData(ATTR(paramElems[i], "expr")); } else if(HAS_ATTR(paramElems[i], "location") && _dataModel) { - paramValue = _dataModel.evalAsString(ATTR(paramElems[i], "location")); + paramValue = _dataModel.getStringAsData(ATTR(paramElems[i], "location")); } else { LOG(ERROR) << "param element is missing expr or location or no datamodel is specified"; continue; @@ -593,7 +582,7 @@ void InterpreterImpl::processParamChilds(const Arabica::DOM::Node<std::string>& } catch(Event e) { LOG(ERROR) << "Syntax error while processing params:" << std::endl << e << std::endl; // test 343 - std::multimap<std::string, std::string>::iterator paramIter = params.begin(); + std::multimap<std::string, Data>::iterator paramIter = params.begin(); while(paramIter != params.end()) { params.erase(paramIter++); } @@ -663,7 +652,7 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { * See 3.14 IDs for details. * */ - sendReq.sendid = ATTR(getParentState(element), "id") + "." + getUUID(); + sendReq.sendid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); if (HAS_ATTR(element, "idlocation") && _dataModel) { _dataModel.assign(ATTR(element, "idlocation"), "'" + sendReq.sendid + "'"); } else { @@ -708,9 +697,9 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { if (_dataModel) { std::vector<std::string> names = tokenizeIdRefs(ATTR(element, "namelist")); for (int i = 0; i < names.size(); i++) { - std::string namelistValue = _dataModel.evalAsString(names[i]); + Data namelistValue = _dataModel.getStringAsData(names[i]); sendReq.namelist[names[i]] = namelistValue; - sendReq.data.compound[names[i]] = Data(namelistValue, Data::VERBATIM); + sendReq.data.compound[names[i]] = namelistValue; } } else { LOG(ERROR) << "Namelist attribute at send requires datamodel to be defined"; @@ -834,7 +823,7 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) { if (HAS_ATTR(element, "id")) { invokeReq.invokeid = ATTR(element, "id"); } else { - invokeReq.invokeid = ATTR(getParentState(element), "id") + "." + getUUID(); + invokeReq.invokeid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); if (HAS_ATTR(element, "idlocation") && _dataModel) { _dataModel.assign(ATTR(element, "idlocation"), "'" + invokeReq.invokeid + "'"); } @@ -954,7 +943,7 @@ void InterpreterImpl::cancelInvoke(const Arabica::DOM::Node<std::string>& elemen } _invokers.erase(invokeId); } else { - LOG(ERROR) << "Cannot cancel invoke for id " << invokeId << ": no soch invokation"; + LOG(ERROR) << "Cannot cancel invoke for id " << invokeId << ": no such invokation"; } //receiveInternal(Event("done.invoke." + invokeId, Event::PLATFORM)); } diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index d2c9025..aadef72 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -4,7 +4,6 @@ #include "uscxml/Common.h" #include "uscxml/URL.h" -#include <boost/uuid/uuid_generators.hpp> #include <boost/algorithm/string.hpp> #include <iostream> @@ -238,7 +237,6 @@ public: static Arabica::XPath::NodeSet<std::string> filterChildElements(const std::string& tagName, const Arabica::XPath::NodeSet<std::string>& nodeSet); Arabica::DOM::Node<std::string> findLCCA(const Arabica::XPath::NodeSet<std::string>& states); Arabica::XPath::NodeSet<std::string> getProperAncestors(const Arabica::DOM::Node<std::string>& s1, const Arabica::DOM::Node<std::string>& s2); - static std::string getUUID(); protected: InterpreterImpl(); @@ -296,7 +294,7 @@ protected: std::string& text, std::string& expr); void processParamChilds(const Arabica::DOM::Node<std::string>& element, - std::multimap<std::string, std::string>& params); + std::multimap<std::string, Data>& params); void processDOMorText(const Arabica::DOM::Node<std::string>& element, Arabica::DOM::Node<std::string>& dom, std::string& text); @@ -313,8 +311,6 @@ protected: bool isInFinalState(const Arabica::DOM::Node<std::string>& state); bool parentIsScxmlState(const Arabica::DOM::Node<std::string>& state); - static boost::uuids::random_generator uuidGen; - long _lastRunOnMainThread; std::string _name; std::string _sessionId; @@ -598,9 +594,6 @@ public: Arabica::XPath::NodeSet<std::string> getProperAncestors(const Arabica::DOM::Node<std::string>& s1, const Arabica::DOM::Node<std::string>& s2) { return _impl->getProperAncestors(s1, s2); } - static std::string getUUID() { - return InterpreterImpl::getUUID(); - } boost::shared_ptr<InterpreterImpl> getImpl() { return _impl; diff --git a/src/uscxml/Message.cpp b/src/uscxml/Message.cpp index 6388f9f..ef542d7 100644 --- a/src/uscxml/Message.cpp +++ b/src/uscxml/Message.cpp @@ -193,22 +193,24 @@ Arabica::DOM::Document<std::string> SendRequest::toDocument() { Arabica::DOM::Node<std::string> payloadElem = scxmlMsg.getElementsByTagName("scxml:payload").item(0); // add parameters - std::multimap<std::string, std::string>::iterator paramIter = params.begin(); + std::multimap<std::string, Data>::iterator paramIter = params.begin(); while(paramIter != params.end()) { Arabica::DOM::Element<std::string> propertyElem = document.createElementNS("http://www.w3.org/2005/07/scxml", "scxml:property"); propertyElem.setAttribute("name", paramIter->first); - Arabica::DOM::Text<std::string> textElem = document.createTextNode(paramIter->second); + // this is simplified - Data might be more elaborate than a simple string atom + Arabica::DOM::Text<std::string> textElem = document.createTextNode(paramIter->second.atom); propertyElem.appendChild(textElem); payloadElem.appendChild(propertyElem); paramIter++; } // add namelist elements - std::map<std::string, std::string>::iterator namelistIter = namelist.begin(); + std::map<std::string, Data>::iterator namelistIter = namelist.begin(); while(namelistIter != namelist.end()) { Arabica::DOM::Element<std::string> propertyElem = document.createElementNS("http://www.w3.org/2005/07/scxml", "scxml:property"); propertyElem.setAttribute("name", namelistIter->first); - Arabica::DOM::Text<std::string> textElem = document.createTextNode(namelistIter->second); + // this is simplified - Data might be more elaborate than a simple string atom + Arabica::DOM::Text<std::string> textElem = document.createTextNode(namelistIter->second.atom); propertyElem.appendChild(textElem); payloadElem.appendChild(propertyElem); namelistIter++; @@ -294,14 +296,21 @@ Data Data::fromJSON(const std::string& jsonString) { if (t[0].end != trimmed.length()) return data; +// jsmntok_t* token = t; +// while(token->end) { +// std::cout << trimmed.substr(token->start, token->end - token->start) << std::endl; +// std::cout << "------" << std::endl; +// token++; +// } + std::list<Data*> dataStack; std::list<jsmntok_t> tokenStack; dataStack.push_back(&data); size_t currTok = 0; do { - jsmntok_t t2 = t[currTok]; - std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); +// jsmntok_t t2 = t[currTok]; +// std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); switch (t[currTok].type) { case JSMN_STRING: dataStack.back()->type = Data::VERBATIM; @@ -317,15 +326,15 @@ Data Data::fromJSON(const std::string& jsonString) { break; } - t2 = t[currTok]; - value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); +// t2 = t[currTok]; +// value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); // there are no more tokens if (t[currTok].end == 0 || tokenStack.empty()) break; // next token starts after current one => pop - if (t[currTok].end > tokenStack.back().end) { + while (t[currTok].end > tokenStack.back().end) { tokenStack.pop_back(); dataStack.pop_back(); } diff --git a/src/uscxml/Message.h b/src/uscxml/Message.h index e561c59..9ae6ea1 100644 --- a/src/uscxml/Message.h +++ b/src/uscxml/Message.h @@ -11,6 +11,7 @@ #include <DOM/io/Stream.hpp> #include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> #include <inttypes.h> #define TAGNAME(elem) ((Arabica::DOM::Element<std::string>)elem).getTagName() @@ -43,7 +44,7 @@ public: virtual ~Data() {} operator bool() const { - return (atom.length() > 0 || !compound.empty() || !array.empty()); + return (atom.length() > 0 || !compound.empty() || !array.empty() || binary); } bool hasKey(const std::string& key) const { @@ -60,11 +61,11 @@ public: Data data; return data; } - - operator std::string() { + + operator std::string() const { return atom; } - + operator std::map<std::string, Data>() { return compound; } @@ -72,7 +73,7 @@ public: operator std::list<Data>() { return array; } - + static Data fromJSON(const std::string& jsonString); static std::string toJSON(const Data& data); static Data fromXML(const std::string& xmlString); @@ -235,13 +236,57 @@ public: return ss.str(); } - std::map<std::string, std::string>& getNameList() { + std::map<std::string, Data>& getNameList() { return namelist; } - std::multimap<std::string, std::string>& getParams() { + std::multimap<std::string, Data>& getParams() { return params; } + typedef std::multimap<std::string, Data> params_t; + typedef std::map<std::string, Data> namelist_t; + + static bool getParam(params_t params, const std::string& name, Data& target) { + if (params.find(name) != params.end()) { + target = params.find(name)->second; + return true; + } + return false; + } + + static bool getParam(params_t params, const std::string& name, std::list<Data>& target) { + if (params.find(name) != params.end()) { + std::pair<params_t::iterator, params_t::iterator> rangeIter = params.equal_range(name); + while(rangeIter.first != rangeIter.second) { + target.push_back(rangeIter.first->second); + rangeIter.first++; + } + return true; + } + return false; + } + + template <typename T> static bool getParam(params_t params, const std::string& name, T& target) { + if (params.find(name) != params.end()) { + target = boost::lexical_cast<T>(params.find(name)->second.atom); + return true; + } + return false; + } + + template <typename T> static bool getParam(params_t params, const std::string& name, std::list<T>& target) { + if (params.find(name) != params.end()) { + std::pair<params_t::iterator, params_t::iterator> rangeIter = params.equal_range(name); + while(rangeIter.first != rangeIter.second) { + target.push_back(boost::lexical_cast<T>(rangeIter.first->second.atom)); + rangeIter.first++; + } + return true; + } + return false; + } + + #ifdef SWIGIMPORTED protected: #endif @@ -258,11 +303,8 @@ protected: std::string invokeid; Data data; std::string content; - std::map<std::string, std::string> namelist; - std::multimap<std::string, std::string> params; - - typedef std::multimap<std::string, std::string> params_t; - typedef std::map<std::string, std::string> namelist_t; + std::map<std::string, Data> namelist; + std::multimap<std::string, Data> params; friend std::ostream& operator<< (std::ostream& os, const Event& event); }; diff --git a/src/uscxml/URL.cpp b/src/uscxml/URL.cpp index 24a64ac..1b15dee 100644 --- a/src/uscxml/URL.cpp +++ b/src/uscxml/URL.cpp @@ -1,5 +1,6 @@ #include <glog/logging.h> #include "URL.h" +#include "UUID.h" #include "uscxml/config.h" #include <fstream> @@ -42,6 +43,7 @@ std::string URL::tmpDir() { if (tmpDir == NULL) tmpDir = "/tmp/"; +#if 0 char* tmpl = (char*)malloc(strlen(tmpDir) + 11); char* writePtr = tmpl; memcpy(writePtr, tmpDir, strlen(tmpDir)); @@ -50,6 +52,22 @@ std::string URL::tmpDir() { writePtr += 11; tmpl[writePtr - tmpl] = 0; return tmpl; +#endif + return tmpDir; +} + + +std::string URL::getTmpFilename(const std::string& suffix) { + std::string tmpFilename = tmpDir(); + if (tmpFilename.find_last_of(PATH_SEPERATOR) != tmpFilename.length() - 1) + tmpFilename += PATH_SEPERATOR; + + tmpFilename += UUID::getUUID(); + if (suffix.length() > 0) { + tmpFilename += "."; + tmpFilename += suffix; + } + return tmpFilename; } #if (!defined APPLE && !defined IOS) diff --git a/src/uscxml/URL.h b/src/uscxml/URL.h index 16c29c7..c00d24a 100644 --- a/src/uscxml/URL.h +++ b/src/uscxml/URL.h @@ -205,6 +205,7 @@ public: static void toBaseURL(URL& uri); static std::string getResourceDir(); + static std::string getTmpFilename(const std::string& suffix = ""); static URL toLocalFile(const std::string& content, const std::string& suffix) { boost::shared_ptr<URLImpl> impl = URLImpl::toLocalFile(content, suffix); diff --git a/src/uscxml/URL.mm b/src/uscxml/URL.mm index 3344601..e6dc9c5 100644 --- a/src/uscxml/URL.mm +++ b/src/uscxml/URL.mm @@ -10,19 +10,21 @@ namespace uscxml { std::string URL::getResourceDir() { + std::string path; #if HAS_AUTORELEASE_POOL @autoreleasepool { #else NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; #endif NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; - return std::string([resourcePath cStringUsingEncoding:NSUTF8StringEncoding]); + path = [resourcePath cStringUsingEncoding:NSUTF8StringEncoding]; #if HAS_AUTORELEASE_POOL } #else [pool drain]; #endif + return path; } } diff --git a/src/uscxml/UUID.cpp b/src/uscxml/UUID.cpp new file mode 100644 index 0000000..eac4316 --- /dev/null +++ b/src/uscxml/UUID.cpp @@ -0,0 +1,14 @@ +#include "UUID.h" +#include <sstream> + +namespace uscxml { +boost::uuids::random_generator UUID::uuidGen; + +std::string UUID::getUUID() { + boost::uuids::uuid uuid = uuidGen(); + std::ostringstream os; + os << uuid; + return os.str(); +} + +}
\ No newline at end of file diff --git a/src/uscxml/UUID.h b/src/uscxml/UUID.h new file mode 100644 index 0000000..4a11285 --- /dev/null +++ b/src/uscxml/UUID.h @@ -0,0 +1,20 @@ +#ifndef UUID_H_8X65R2EI +#define UUID_H_8X65R2EI + +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> +#include <ostream> + +namespace uscxml { + +class UUID { +public: + static std::string getUUID(); + static boost::uuids::random_generator uuidGen; +}; + +} + + +#endif /* end of include guard: UUID_H_8X65R2EI */ diff --git a/src/uscxml/interpreter/InterpreterDraft6.cpp b/src/uscxml/interpreter/InterpreterDraft6.cpp index e044fcf..62c61e0 100644 --- a/src/uscxml/interpreter/InterpreterDraft6.cpp +++ b/src/uscxml/interpreter/InterpreterDraft6.cpp @@ -1,6 +1,7 @@ #include "InterpreterDraft6.h" #include <glog/logging.h> +#include "uscxml/UUID.h" namespace uscxml { @@ -80,7 +81,7 @@ void InterpreterDraft6::interpret() { if (_userDefinedStartConfiguration.size() > 0) { // we emulate entering a given configuration by creating a pseudo deep history Element<std::string> initHistory = _document.createElementNS(_nsURL, "history"); - initHistory.setAttribute("id", getUUID()); + initHistory.setAttribute("id", UUID::getUUID()); initHistory.setAttribute("type", "deep"); _scxml.insertBefore(initHistory, _scxml.getFirstChild()); diff --git a/src/uscxml/interpreter/InterpreterDraft7.cpp b/src/uscxml/interpreter/InterpreterDraft7.cpp index 7f16b1f..fa578b3 100644 --- a/src/uscxml/interpreter/InterpreterDraft7.cpp +++ b/src/uscxml/interpreter/InterpreterDraft7.cpp @@ -1,6 +1,7 @@ #include "InterpreterDraft7.h" #include <glog/logging.h> +#include "uscxml/UUID.h" namespace uscxml { @@ -31,7 +32,7 @@ void InterpreterDraft7::interpret() { if (!_scxml) return; - _sessionId = getUUID(); + _sessionId = UUID::getUUID(); std::string datamodelName; if (datamodelName.length() == 0 && HAS_ATTR(_scxml, "datamodel")) diff --git a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp index 142f3ec..b2f66b4 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp @@ -223,14 +223,14 @@ void JSCDataModel::setEvent(const Event& event) { if (!eventCopy.params.empty()) { Event::params_t::iterator paramIter = eventCopy.params.begin(); while(paramIter != eventCopy.params.end()) { - eventCopy.data.compound[paramIter->first] = Data(paramIter->second, Data::VERBATIM); + eventCopy.data.compound[paramIter->first] = paramIter->second; paramIter++; } } if (!eventCopy.namelist.empty()) { Event::namelist_t::iterator nameListIter = eventCopy.namelist.begin(); while(nameListIter != eventCopy.namelist.end()) { - eventCopy.data.compound[nameListIter->first] = Data(nameListIter->second, Data::VERBATIM); + eventCopy.data.compound[nameListIter->first] = nameListIter->second; nameListIter++; } } diff --git a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp index 77efe78..0e72c67 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/v8/V8DataModel.cpp @@ -207,18 +207,19 @@ void V8DataModel::setEvent(const Event& event) { if (!eventCopy.params.empty()) { Event::params_t::iterator paramIter = eventCopy.params.begin(); while(paramIter != eventCopy.params.end()) { - eventCopy.data.compound[paramIter->first] = Data(paramIter->second, Data::VERBATIM); + eventCopy.data.compound[paramIter->first] = paramIter->second; paramIter++; } } if (!eventCopy.namelist.empty()) { Event::namelist_t::iterator nameListIter = eventCopy.namelist.begin(); while(nameListIter != eventCopy.namelist.end()) { - eventCopy.data.compound[nameListIter->first] = Data(nameListIter->second, Data::VERBATIM); + eventCopy.data.compound[nameListIter->first] = nameListIter->second; nameListIter++; } } if (eventCopy.data) { +// std::cout << Data::toJSON(eventCopy.data); eventObj->Set(v8::String::New("data"), getDataAsValue(eventCopy.data)); // set data part of _event } else { // test 343 / test 488 @@ -340,6 +341,7 @@ v8::Handle<v8::Value> V8DataModel::getDataAsValue(const Data& data) { v8::Handle<v8::Object> value = v8::Object::New(); std::map<std::string, Data>::const_iterator compoundIter = data.compound.begin(); while(compoundIter != data.compound.end()) { +// std::cout << compoundIter->first.c_str() << std::endl; value->Set(v8::String::New(compoundIter->first.c_str()), getDataAsValue(compoundIter->second)); compoundIter++; } diff --git a/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp b/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp index 07cba96..aec044e 100644 --- a/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp +++ b/src/uscxml/plugins/datamodel/prolog/swi/SWIDataModel.cpp @@ -230,6 +230,8 @@ void SWIDataModel::setEvent(const Event& event) { PlCall(dataInitStr.str().c_str()); } else if (event.content.size() > 0) { PlCall("assert", PlCompound("event", PlCompound("data", PlString(Interpreter::spaceNormalize(event.content).c_str())))); + } else if (event.data) { + LOG(ERROR) << "No support for structured data from events in prolog datamodel yet"; } // event.params diff --git a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp index cb40890..f863bb7 100644 --- a/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp +++ b/src/uscxml/plugins/datamodel/xpath/XPathDataModel.cpp @@ -139,10 +139,11 @@ void XPathDataModel::setEvent(const Event& event) { } if (event.params.size() > 0) { - std::multimap<std::string, std::string>::const_iterator paramIter = event.params.begin(); + std::multimap<std::string, Data>::const_iterator paramIter = event.params.begin(); while(paramIter != event.params.end()) { Element<std::string> eventParamElem = _doc.createElement("data"); - Text<std::string> eventParamText = _doc.createTextNode(paramIter->second); + // this is simplified - Data might be more elaborate than a simple string atom + Text<std::string> eventParamText = _doc.createTextNode(paramIter->second.atom); eventParamElem.setAttribute("id", paramIter->first); eventParamElem.appendChild(eventParamText); @@ -151,10 +152,11 @@ void XPathDataModel::setEvent(const Event& event) { } } if (event.namelist.size() > 0) { - std::map<std::string, std::string>::const_iterator namelistIter = event.namelist.begin(); + std::map<std::string, Data>::const_iterator namelistIter = event.namelist.begin(); while(namelistIter != event.namelist.end()) { Element<std::string> eventNamelistElem = _doc.createElement("data"); - Text<std::string> eventNamelistText = _doc.createTextNode(namelistIter->second); + // this is simplified - Data might be more elaborate than a simple string atom + Text<std::string> eventNamelistText = _doc.createTextNode(namelistIter->second.atom); eventNamelistElem.setAttribute("id", namelistIter->first); eventNamelistElem.appendChild(eventNamelistText); diff --git a/src/uscxml/plugins/element/file/FileElement.cpp b/src/uscxml/plugins/element/file/FileElement.cpp index e51fa70..2b48aba 100644 --- a/src/uscxml/plugins/element/file/FileElement.cpp +++ b/src/uscxml/plugins/element/file/FileElement.cpp @@ -34,7 +34,7 @@ void FileElement::enterElement(const Arabica::DOM::Node<std::string>& node) { } _givenUrl = (HAS_ATTR(node, "url") ? ATTR(node, "url") : _interpreter->getDataModel().evalAsString(ATTR(node, "urlexpr"))); - std::string sandBoxStr = (HAS_ATTR(node, "sandboxed") ? ATTR(node, "sandboxed") : "on"); + std::string sandBoxStr = (HAS_ATTR(node, "sandbox") ? ATTR(node, "sandbox") : "on"); if (boost::iequals(sandBoxStr, "off") || boost::iequals(sandBoxStr, "false") || boost::iequals(sandBoxStr, "no")) { _sandBoxed = false; } diff --git a/src/uscxml/plugins/element/respond/RespondElement.cpp b/src/uscxml/plugins/element/respond/RespondElement.cpp index 3c47b83..ae81fed 100644 --- a/src/uscxml/plugins/element/respond/RespondElement.cpp +++ b/src/uscxml/plugins/element/respond/RespondElement.cpp @@ -63,8 +63,14 @@ void RespondElement::enterElement(const Arabica::DOM::Node<std::string>& node) { if (HAS_ATTR(contents[0], "expr")) { // -- content is evaluated string from datamodel ------ if (_interpreter->getDataModel()) { try { - std::string contentValue = _interpreter->getDataModel().evalAsString(ATTR(contents[0], "expr")); - httpReply.content = contentValue; + Data contentData = _interpreter->getDataModel().getStringAsData(ATTR(contents[0], "expr")); + if (contentData.atom.length() > 0) { + httpReply.content = contentData.atom; + } else if (contentData.binary) { + httpReply.content = std::string(contentData.binary->_data, contentData.binary->_size); + } else { + httpReply.content = Data::toJSON(contentData); + } } catch (Event e) { LOG(ERROR) << "Syntax error with expr in content child of Respond element:" << std::endl << e << std::endl; return; @@ -132,20 +138,20 @@ void RespondElement::enterElement(const Arabica::DOM::Node<std::string>& node) { std::string value; if (HAS_ATTR(headers[i], "value")) { value = ATTR(headers[i], "value"); - } else if(HAS_ATTR(headers[i], "expr")) { + } else if(HAS_ATTR(headers[i], "valueexpr")) { if (_interpreter->getDataModel()) { try { - value = _interpreter->getDataModel().evalAsString(ATTR(headers[i], "expr")); + value = _interpreter->getDataModel().evalAsString(ATTR(headers[i], "valueexpr")); } catch (Event e) { - LOG(ERROR) << "Syntax error with expr in header child of Respond element:" << std::endl << e << std::endl; + LOG(ERROR) << "Syntax error with valueexpr in header child of Respond element:" << std::endl << e << std::endl; return; } } else { - LOG(ERROR) << "header element has expr attribute but no datamodel is specified."; + LOG(ERROR) << "header element has valueexpr attribute but no datamodel is specified."; return; } } else { - LOG(ERROR) << "header element has no value or expr attribute."; + LOG(ERROR) << "header element has no value or valueexpr attribute."; return; } diff --git a/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp b/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp index be6ffc7..73f44ea 100644 --- a/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp +++ b/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp @@ -72,14 +72,14 @@ void OpenALInvoker::send(const SendRequest& req) { LOG(ERROR) << "Sent event play with no src URL"; } - URL srcURL = req.params.find("src")->second; + URL srcURL = req.params.find("src")->second.atom; if (!srcURL.toAbsolute(_interpreter->getBaseURI())) { LOG(ERROR) << "src URL " << req.params.find("src")->second << " is relative with no base URI set for interpreter"; return; } _sources[req.sendid] = new OpenALSource(); - _sources[req.sendid]->loop = req.params.find("loop") != req.params.end() && boost::iequals(req.params.find("loop")->second, "true"); + _sources[req.sendid]->loop = req.params.find("loop") != req.params.end() && boost::iequals(req.params.find("loop")->second.atom, "true"); _sources[req.sendid]->file = srcURL; #ifdef LIBSNDFILE_FOUND _sources[req.sendid]->transform = new LibSoundFile(srcURL.asLocalFile(".audio")); @@ -127,7 +127,7 @@ void OpenALInvoker::send(const SendRequest& req) { LOG(WARNING) << "Cannot move source with no source given in parameters"; return; } - sourceId = req.params.find("source")->second; + sourceId = req.params.find("source")->second.atom; if (_sources.find(sourceId) == _sources.end()) { LOG(WARNING) << "Given source '" << sourceId << "' not active or not existing"; @@ -261,14 +261,14 @@ void OpenALInvoker::invoke(const InvokeRequest& req) { throw std::string("__FILE__ __LINE__ openal error opening device"); } - std::multimap<std::string, std::string>::const_iterator paramIter = req.params.begin(); + std::multimap<std::string, Data>::const_iterator paramIter = req.params.begin(); while(paramIter != req.params.end()) { if (boost::iequals(paramIter->first, "maxX")) - _maxPos[0] = strTo<float>(paramIter->second); + _maxPos[0] = strTo<float>(paramIter->second.atom); if (boost::iequals(paramIter->first, "maxY")) - _maxPos[1] = strTo<float>(paramIter->second); + _maxPos[1] = strTo<float>(paramIter->second.atom); if (boost::iequals(paramIter->first, "maxZ")) - _maxPos[2] = strTo<float>(paramIter->second); + _maxPos[2] = strTo<float>(paramIter->second.atom); paramIter++; } @@ -310,7 +310,7 @@ void OpenALInvoker::notifyOfLoop(OpenALSource* src) { returnEvent(ev); } -void OpenALInvoker::getPosFromParams(const std::multimap<std::string, std::string>& params, float* position) { +void OpenALInvoker::getPosFromParams(const std::multimap<std::string, Data>& params, float* position) { // vector explicitly given try { if (params.find("x") != params.end()) diff --git a/src/uscxml/plugins/invoker/audio/OpenALInvoker.h b/src/uscxml/plugins/invoker/audio/OpenALInvoker.h index 9b71d95..81ced9b 100644 --- a/src/uscxml/plugins/invoker/audio/OpenALInvoker.h +++ b/src/uscxml/plugins/invoker/audio/OpenALInvoker.h @@ -78,7 +78,7 @@ protected: void notifyOfLoop(OpenALSource*); float posToRadian(const std::string& pos); - void getPosFromParams(const std::multimap<std::string, std::string>& params, float* position); + void getPosFromParams(const std::multimap<std::string, Data>& params, float* position); }; diff --git a/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp index ccf65ce..e637f8b 100644 --- a/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp +++ b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.cpp @@ -1,15 +1,16 @@ #include "FFMPEGInvoker.h" #include <glog/logging.h> +#include <libavutil/imgutils.h> +#include <libavcodec/avcodec.h> +#include <fstream> #ifdef BUILD_AS_PLUGINS #include <Pluma/Connector.hpp> #endif -#define STREAM_DURATION 200.0 #define STREAM_FRAME_RATE 25 /* 25 images/s */ -#define STREAM_NB_FRAMES ((int)(STREAM_DURATION * STREAM_FRAME_RATE)) -#define STREAM_PIX_FMT AV_PIX_FMT_YUV420P /* default pix_fmt */ +#define BMP_FORMAT PIX_FMT_BGR24 namespace uscxml { @@ -29,190 +30,363 @@ FFMPEGInvoker::~FFMPEGInvoker() { boost::shared_ptr<InvokerImpl> FFMPEGInvoker::create(InterpreterImpl* interpreter) { boost::shared_ptr<FFMPEGInvoker> invoker = boost::shared_ptr<FFMPEGInvoker>(new FFMPEGInvoker()); - invoker->_interpreter = interpreter; + // Register all formats and codecs - this ought to be done just once + av_register_all(); return invoker; } Data FFMPEGInvoker::getDataModelVariables() { Data data; + + AVCodec* codec = NULL; + while((codec = av_codec_next(codec))) { + AVCodec* codecInst = avcodec_find_encoder(codec->id); + if (!codecInst) + continue; + + switch (codec->type) { + case AVMEDIA_TYPE_VIDEO: { + Data codecData; + codecData.compound["name"] = Data(codec->name, Data::VERBATIM); + codecData.compound["longName"] = Data(codec->long_name, Data::VERBATIM); + data.compound["video"].compound[codec->name] = codecData; + break; + } + case AVMEDIA_TYPE_AUDIO: { + Data codecData; + codecData.compound["name"] = Data(codec->name, Data::VERBATIM); + codecData.compound["longName"] = Data(codec->long_name, Data::VERBATIM); + data.compound["audio"].compound[codec->name] = codecData; + break; + } + default: + break; + } + } + return data; } +void FFMPEGInvoker::invoke(const InvokeRequest& req) { + int nrThreads = 1; + Event::getParam(req.params, "threads", nrThreads); + + _isRunning = true; + for (int i = 0; i < nrThreads; i++) { + _threads.insert(new tthread::thread(FFMPEGInvoker::run, this)); + } +} + void FFMPEGInvoker::send(const SendRequest& req) { - if (boost::iequals(req.name, "add")) { + SendRequest reqCopy = req; + + if (boost::iequals(req.name, "render.start")) { + // create a new encoding context + int ret; + EncodingContext* ctx = new EncodingContext(); + tthread::lock_guard<tthread::recursive_mutex> lock(ctx->mutex); + + std::string context; + Event::getParam(req.params, "context", context); + + ctx->extension = "mpeg"; + Event::getParam(req.params, "format", ctx->extension); + + Event::getParam(req.params, "width", ctx->width); + Event::getParam(req.params, "height", ctx->height); + + if (!ctx->width || !ctx->height) + return; + + ctx->filename = URL::getTmpFilename(); + + /* allocate the output media context */ + avformat_alloc_output_context2(&ctx->formatCtx, NULL, ctx->extension.c_str(), ctx->filename.c_str()); + if (!ctx->formatCtx) { + printf("Could not deduce output format from file extension: using MPEG.\n"); + avformat_alloc_output_context2(&ctx->formatCtx, NULL, "mpeg", ctx->filename.c_str()); + } + if (!ctx->formatCtx) { + return; + } + ctx->format = ctx->formatCtx->oformat; + + /* Add the audio and video streams using the default format codecs + * and initialize the codecs. */ + ctx->videoStream = NULL; + + if (ctx->format->video_codec != AV_CODEC_ID_NONE) { + ctx->videoStream = addStream(ctx, ctx->formatCtx, &ctx->videoCodec, ctx->format->video_codec); + } + + /* Now that all the parameters are set, we can open the audio and + * video codecs and allocate the necessary encode buffers. */ + if (ctx->videoStream) + openVideo(ctx, ctx->formatCtx, ctx->videoCodec, ctx->videoStream); + + /* open the output file, if needed */ + if (!(ctx->format->flags & AVFMT_NOFILE)) { + ret = avio_open(&ctx->formatCtx->pb, ctx->filename.c_str(), AVIO_FLAG_WRITE); + if (ret < 0) { + // fprintf(stderr, "Could not open '%s': %s\n", ctx->filename.c_str(), + // av_err2str(ret)); + return; + } + } + + /* Write the stream header, if any. */ + ret = avformat_write_header(ctx->formatCtx, NULL); + if (ret < 0) { + // fprintf(stderr, "Error occurred when opening output file: %s\n", + // av_err2str(ret)); + return; + } + + if (ctx->frame) + ctx->frame->pts = 0; + + _encoders[context] = ctx; + } else if(boost::iequals(req.name, "render.frame")) { + _workQueue.push(req); + } else if(boost::iequals(req.name, "render.end")) { + _workQueue.push(req); + } +} - } else if(boost::iequals(req.name, "render")) { +void FFMPEGInvoker::cancel(const std::string sendId) { +} +void FFMPEGInvoker::run(void* instance) { + FFMPEGInvoker* INSTANCE = (FFMPEGInvoker*)instance; + while(true) { + SendRequest req = INSTANCE->_workQueue.pop(); + if (INSTANCE->_isRunning) { + INSTANCE->process(req); + } else { + return; + } } } -void FFMPEGInvoker::cancel(const std::string sendId) { +void FFMPEGInvoker::finish(EncodingContext* ctx, const SendRequest& req) { + av_write_trailer(ctx->formatCtx); + + /* Close each codec. */ + if (ctx->videoStream) + closeVideo(ctx, ctx->formatCtx, ctx->videoStream); + + if (!(ctx->formatCtx->oformat->flags & AVFMT_NOFILE)) + /* Close the output file. */ + avio_close(ctx->formatCtx->pb); + + /* free the stream */ + avformat_free_context(ctx->formatCtx); + + // read file + std::ifstream movieFile(ctx->filename.c_str()); + movieFile.seekg(0, std::ios::end); + size_t length = movieFile.tellg(); + movieFile.seekg(0, std::ios::beg); + + char* movieBuffer = (char*)malloc(length); + movieFile.read(movieBuffer, length); + + // move to desktop for checking +// int err = rename(ctx->filename.c_str(), "/Users/sradomski/Desktop/foo.mpg"); +// if (err) { +// printf("%s", strerror(errno)); +// } + + std::string context; + Event::getParam(req.params, "context", context); + + Event event; + event.name = "render.done"; + event.data.compound["context"] = context; + event.data.compound["movie"] = Data(movieBuffer, length, true); + event.data.compound["mimetype"] = Data("video/mpeg", Data::VERBATIM); + event.data.compound["filename"] = Data(std::string("movie.") + ctx->extension, Data::VERBATIM); + + returnEvent(event); } -static AVFrame *frame; -static AVPicture src_picture, dst_picture; -static int frame_count; -static int sws_flags = SWS_BICUBIC; +void FFMPEGInvoker::process(const SendRequest& req) { -static AVStream *add_stream(AVFormatContext *oc, AVCodec **codec, - enum AVCodecID codec_id) { + std::string context; + Event::getParam(req.params, "context", context); + if (_encoders.find(context) == _encoders.end()) { + return; + } + + EncodingContext* ctx = _encoders[context]; + tthread::lock_guard<tthread::recursive_mutex> lock(ctx->mutex); + + // finish encoding and return + if(boost::iequals(req.name, "render.end")) { + finish(ctx, req); + delete _encoders[context]; + _encoders.erase(context); + } + + Data image; + Event::getParam(req.params, "frame", image); + if (!image) { + return; + } + + std::string format = "bmp"; + Event::getParam(req.params, "format", format); + + writeVideoFrame(ctx, ctx->formatCtx, ctx->videoStream, image.binary); + ctx->frame->pts += av_rescale_q(1, ctx->videoStream->codec->time_base, ctx->videoStream->time_base); + +} + +AVStream* FFMPEGInvoker::addStream(EncodingContext* ctx, AVFormatContext *oc, AVCodec **codec, + enum AVCodecID codec_id) { AVCodecContext *c; AVStream *st; - + /* find the encoder */ *codec = avcodec_find_encoder(codec_id); if (!(*codec)) { fprintf(stderr, "Could not find encoder for '%s'\n", - avcodec_get_name(codec_id)); - exit(1); + avcodec_get_name(codec_id)); + return NULL; } - + st = avformat_new_stream(oc, *codec); + ctx->videoPixFmt = (*codec)->pix_fmts[0]; if (!st) { fprintf(stderr, "Could not allocate stream\n"); - exit(1); + return NULL; } st->id = oc->nb_streams-1; c = st->codec; - + switch ((*codec)->type) { - case AVMEDIA_TYPE_AUDIO: - c->sample_fmt = AV_SAMPLE_FMT_FLTP; - c->bit_rate = 64000; - c->sample_rate = 44100; - c->channels = 2; - break; - - case AVMEDIA_TYPE_VIDEO: - c->codec_id = codec_id; - - c->bit_rate = 400000; - /* Resolution must be a multiple of two. */ - c->width = 352; - c->height = 288; - /* timebase: This is the fundamental unit of time (in seconds) in terms - * of which frame timestamps are represented. For fixed-fps content, - * timebase should be 1/framerate and timestamp increments should be - * identical to 1. */ - c->time_base.den = STREAM_FRAME_RATE; - c->time_base.num = 1; - c->gop_size = 12; /* emit one intra frame every twelve frames at most */ - c->pix_fmt = STREAM_PIX_FMT; - if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) { - /* just for testing, we also add B frames */ - c->max_b_frames = 2; - } - if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) { - /* Needed to avoid using macroblocks in which some coeffs overflow. - * This does not happen with normal video, it just happens here as - * the motion of the chroma plane does not match the luma plane. */ - c->mb_decision = 2; - } - break; - - default: - break; + case AVMEDIA_TYPE_AUDIO: + c->sample_fmt = AV_SAMPLE_FMT_FLTP; + c->bit_rate = 64000; + c->sample_rate = 44100; + c->channels = 2; + break; + + case AVMEDIA_TYPE_VIDEO: + c->codec_id = codec_id; + + c->bit_rate = 800000; + /* Resolution must be a multiple of two. */ + c->width = ctx->width; + c->height = ctx->height; + /* timebase: This is the fundamental unit of time (in seconds) in terms + * of which frame timestamps are represented. For fixed-fps content, + * timebase should be 1/framerate and timestamp increments should be + * identical to 1. */ + c->time_base.den = STREAM_FRAME_RATE; + c->time_base.num = 1; + c->gop_size = 12; /* emit one intra frame every twelve frames at most */ + c->pix_fmt = ctx->videoPixFmt; + if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) { + /* just for testing, we also add B frames */ + c->max_b_frames = 2; + } + if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) { + /* Needed to avoid using macroblocks in which some coeffs overflow. + * This does not happen with normal video, it just happens here as + * the motion of the chroma plane does not match the luma plane. */ + c->mb_decision = 2; + } + break; + + default: + break; } - + /* Some formats want stream headers to be separate. */ if (oc->oformat->flags & AVFMT_GLOBALHEADER) c->flags |= CODEC_FLAG_GLOBAL_HEADER; - + return st; } -static void open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st) { +void FFMPEGInvoker::openVideo(EncodingContext* ctx, AVFormatContext *oc, AVCodec *codec, AVStream *st) { int ret; AVCodecContext *c = st->codec; /* open the codec */ ret = avcodec_open2(c, codec, NULL); if (ret < 0) { - fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret)); - exit(1); + // fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret)); + return; } /* allocate and init a re-usable frame */ - frame = avcodec_alloc_frame(); - if (!frame) { + ctx->frame = avcodec_alloc_frame(); + if (!ctx->frame) { fprintf(stderr, "Could not allocate video frame\n"); - exit(1); + return; } /* Allocate the encoded raw picture. */ - ret = avpicture_alloc(&dst_picture, c->pix_fmt, c->width, c->height); + ret = avpicture_alloc(&ctx->dst_picture, c->pix_fmt, c->width, c->height); if (ret < 0) { - fprintf(stderr, "Could not allocate picture: %s\n", av_err2str(ret)); - exit(1); +// fprintf(stderr, "Could not allocate picture: %s\n", av_err2str(ret)); + return; } /* If the output format is not YUV420P, then a temporary YUV420P * picture is needed too. It is then converted to the required * output format. */ - if (c->pix_fmt != AV_PIX_FMT_YUV420P) { - ret = avpicture_alloc(&src_picture, AV_PIX_FMT_YUV420P, c->width, c->height); + if (c->pix_fmt != BMP_FORMAT) { + ret = avpicture_alloc(&ctx->src_picture, BMP_FORMAT, c->width, c->height); if (ret < 0) { - fprintf(stderr, "Could not allocate temporary picture: %s\n", - av_err2str(ret)); - exit(1); + // fprintf(stderr, "Could not allocate temporary picture: %s\n", + // av_err2str(ret)); + return; } } /* copy data and linesize picture pointers to frame */ - *((AVPicture *)frame) = dst_picture; + *((AVPicture *)ctx->frame) = ctx->dst_picture; } - -/* Prepare a dummy image. */ -static void fill_yuv_image(AVPicture *pict, int frame_index, - int width, int height) { - int x, y, i; - - i = frame_index; - - /* Y */ - for (y = 0; y < height; y++) - for (x = 0; x < width; x++) - pict->data[0][y * pict->linesize[0] + x] = x + y + i * 3; - - /* Cb and Cr */ - for (y = 0; y < height / 2; y++) { - for (x = 0; x < width / 2; x++) { - pict->data[1][y * pict->linesize[1] + x] = 128 + y + i * 2; - pict->data[2][y * pict->linesize[2] + x] = 64 + x + i * 5; - } - } -} - -static void write_video_frame(AVFormatContext *oc, AVStream *st) { + +void FFMPEGInvoker::writeVideoFrame(EncodingContext* ctx, AVFormatContext *oc, AVStream *st, boost::shared_ptr<Blob> image) { int ret; - static struct SwsContext *sws_ctx; AVCodecContext *c = st->codec; - if (frame_count >= STREAM_NB_FRAMES) { - /* No more frames to compress. The codec has a latency of a few - * frames if using B-frames, so we get the last frames by - * passing the same picture again. */ - } else { - if (c->pix_fmt != AV_PIX_FMT_YUV420P) { - /* as we only generate a YUV420P picture, we must convert it - * to the codec pixel format if needed */ - if (!sws_ctx) { - sws_ctx = sws_getContext(c->width, c->height, AV_PIX_FMT_YUV420P, - c->width, c->height, c->pix_fmt, - sws_flags, NULL, NULL, NULL); - if (!sws_ctx) { - fprintf(stderr, - "Could not initialize the conversion context\n"); - exit(1); - } + if (c->pix_fmt != BMP_FORMAT) { + /* as we only generate a YUV420P picture, we must convert it + * to the codec pixel format if needed */ + if (!ctx->sws_ctx) { + ctx->sws_ctx = sws_getContext(c->width, c->height, BMP_FORMAT, + c->width, c->height, c->pix_fmt, + ctx->sws_flags, NULL, NULL, NULL); + if (!ctx->sws_ctx) { + fprintf(stderr, + "Could not initialize the conversion context\n"); + return; } - fill_yuv_image(&src_picture, frame_count, c->width, c->height); - sws_scale(sws_ctx, - (const uint8_t * const *)src_picture.data, src_picture.linesize, - 0, c->height, dst_picture.data, dst_picture.linesize); - } else { - fill_yuv_image(&dst_picture, frame_count, c->width, c->height); } + + uint32_t headerOffset = 0; + headerOffset += image->_data[10] << 0; + headerOffset += image->_data[11] << 8; + headerOffset += image->_data[12] << 16; + headerOffset += image->_data[13] << 24; + +// std::cout << headerOffset + (c->width * c->height) << " / " << image->_size << std::endl; + + ret = avpicture_fill(&ctx->src_picture, (uint8_t*)(image->_data + headerOffset), BMP_FORMAT, c->width, c->height); + if (ret < 0) { + fprintf(stderr, + "Could not fill image from given bitmap\n"); + } + sws_scale(ctx->sws_ctx, + (const uint8_t * const *)ctx->src_picture.data, ctx->src_picture.linesize, + 0, c->height, ctx->dst_picture.data, ctx->dst_picture.linesize); + } else { + avpicture_fill(&ctx->dst_picture, (uint8_t*)image->_data, c->pix_fmt, c->width, c->height); } if (oc->oformat->flags & AVFMT_RAWPICTURE) { @@ -222,7 +396,7 @@ static void write_video_frame(AVFormatContext *oc, AVStream *st) { pkt.flags |= AV_PKT_FLAG_KEY; pkt.stream_index = st->index; - pkt.data = dst_picture.data[0]; + pkt.data = ctx->dst_picture.data[0]; pkt.size = sizeof(AVPicture); ret = av_interleaved_write_frame(oc, &pkt); @@ -232,10 +406,10 @@ static void write_video_frame(AVFormatContext *oc, AVStream *st) { av_init_packet(&pkt); /* encode the image */ - ret = avcodec_encode_video2(c, &pkt, frame, &got_packet); + ret = avcodec_encode_video2(c, &pkt, ctx->frame, &got_packet); if (ret < 0) { - fprintf(stderr, "Error encoding video frame: %s\n", av_err2str(ret)); - exit(1); + // fprintf(stderr, "Error encoding video frame: %s\n", av_err2str(ret)); + return; } /* If size is zero, it means the image was buffered. */ @@ -243,297 +417,25 @@ static void write_video_frame(AVFormatContext *oc, AVStream *st) { pkt.stream_index = st->index; /* Write the compressed frame to the media file. */ +// ret = av_write_frame(oc, &pkt); ret = av_interleaved_write_frame(oc, &pkt); } else { ret = 0; } } if (ret != 0) { - fprintf(stderr, "Error while writing video frame: %s\n", av_err2str(ret)); - exit(1); - } - frame_count++; -} - -static void close_video(AVFormatContext *oc, AVStream *st) { - avcodec_close(st->codec); - av_free(src_picture.data[0]); - av_free(dst_picture.data[0]); - av_free(frame); -} - -static float t, tincr, tincr2; - -static uint8_t **src_samples_data; -static int src_samples_linesize; -static int src_nb_samples; - -static int max_dst_nb_samples; -uint8_t **dst_samples_data; -int dst_samples_linesize; -int dst_samples_size; - -struct SwrContext *swr_ctx = NULL; - -static void open_audio(AVFormatContext *oc, AVCodec *codec, AVStream *st) { - AVCodecContext *c; - int ret; - - c = st->codec; - - /* open it */ - ret = avcodec_open2(c, codec, NULL); - if (ret < 0) { - fprintf(stderr, "Could not open audio codec: %s\n", av_err2str(ret)); - exit(1); - } - - /* init signal generator */ - t = 0; - tincr = 2 * M_PI * 110.0 / c->sample_rate; - /* increment frequency by 110 Hz per second */ - tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate; - - src_nb_samples = c->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE ? - 10000 : c->frame_size; - - ret = av_samples_alloc_array_and_samples(&src_samples_data, &src_samples_linesize, c->channels, - src_nb_samples, c->sample_fmt, 0); - if (ret < 0) { - fprintf(stderr, "Could not allocate source samples\n"); - exit(1); - } - - /* create resampler context */ - if (c->sample_fmt != AV_SAMPLE_FMT_S16) { - swr_ctx = swr_alloc(); - if (!swr_ctx) { - fprintf(stderr, "Could not allocate resampler context\n"); - exit(1); - } - - /* set options */ - av_opt_set_int (swr_ctx, "in_channel_count", c->channels, 0); - av_opt_set_int (swr_ctx, "in_sample_rate", c->sample_rate, 0); - av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); - av_opt_set_int (swr_ctx, "out_channel_count", c->channels, 0); - av_opt_set_int (swr_ctx, "out_sample_rate", c->sample_rate, 0); - av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", c->sample_fmt, 0); - - /* initialize the resampling context */ - if ((ret = swr_init(swr_ctx)) < 0) { - fprintf(stderr, "Failed to initialize the resampling context\n"); - exit(1); - } - } - - /* compute the number of converted samples: buffering is avoided - * ensuring that the output buffer will contain at least all the - * converted input samples */ - max_dst_nb_samples = src_nb_samples; - ret = av_samples_alloc_array_and_samples(&dst_samples_data, &dst_samples_linesize, c->channels, - max_dst_nb_samples, c->sample_fmt, 0); - if (ret < 0) { - fprintf(stderr, "Could not allocate destination samples\n"); - exit(1); - } - dst_samples_size = av_samples_get_buffer_size(NULL, c->channels, max_dst_nb_samples, - c->sample_fmt, 0); -} - -/* Prepare a 16 bit dummy audio frame of 'frame_size' samples and - * 'nb_channels' channels. */ -static void get_audio_frame(int16_t *samples, int frame_size, int nb_channels) { - int j, i, v; - int16_t *q; - - q = samples; - for (j = 0; j < frame_size; j++) { - v = (int)(sin(t) * 10000); - for (i = 0; i < nb_channels; i++) - *q++ = v; - t += tincr; - tincr += tincr2; - } -} - -static void write_audio_frame(AVFormatContext *oc, AVStream *st) { - AVCodecContext *c; - AVPacket pkt = { 0 }; // data and size must be 0; - AVFrame *frame = avcodec_alloc_frame(); - int got_packet, ret, dst_nb_samples; - - av_init_packet(&pkt); - c = st->codec; - - get_audio_frame((int16_t *)src_samples_data[0], src_nb_samples, c->channels); - - /* convert samples from native format to destination codec format, using the resampler */ - if (swr_ctx) { - /* compute destination number of samples */ - dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, c->sample_rate) + src_nb_samples, - c->sample_rate, c->sample_rate, AV_ROUND_UP); - if (dst_nb_samples > max_dst_nb_samples) { - av_free(dst_samples_data[0]); - ret = av_samples_alloc(dst_samples_data, &dst_samples_linesize, c->channels, - dst_nb_samples, c->sample_fmt, 0); - if (ret < 0) - exit(1); - max_dst_nb_samples = dst_nb_samples; - dst_samples_size = av_samples_get_buffer_size(NULL, c->channels, dst_nb_samples, - c->sample_fmt, 0); - } - - /* convert to destination format */ - ret = swr_convert(swr_ctx, - dst_samples_data, dst_nb_samples, - (const uint8_t **)src_samples_data, src_nb_samples); - if (ret < 0) { - fprintf(stderr, "Error while converting\n"); - exit(1); - } - } else { - dst_samples_data[0] = src_samples_data[0]; - dst_nb_samples = src_nb_samples; - } - - frame->nb_samples = dst_nb_samples; - avcodec_fill_audio_frame(frame, c->channels, c->sample_fmt, - dst_samples_data[0], dst_samples_size, 0); - - ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet); - if (ret < 0) { - fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret)); - exit(1); - } - - if (!got_packet) +// fprintf(stderr, "Error while writing video frame: %s\n", av_err2str(ret)); return; - - pkt.stream_index = st->index; - - /* Write the compressed frame to the media file. */ - ret = av_interleaved_write_frame(oc, &pkt); - if (ret != 0) { - fprintf(stderr, "Error while writing audio frame: %s\n", - av_err2str(ret)); - exit(1); } - avcodec_free_frame(&frame); + ctx->frame_count++; } -static void close_audio(AVFormatContext *oc, AVStream *st) { +void FFMPEGInvoker::closeVideo(EncodingContext* ctx, AVFormatContext *oc, AVStream *st) { avcodec_close(st->codec); - av_free(src_samples_data[0]); - av_free(dst_samples_data[0]); +// av_free(ctx->src_picture.data[0]); + av_free(ctx->dst_picture.data[0]); + av_free(ctx->frame); } -void FFMPEGInvoker::invoke(const InvokeRequest& req) { - -#if 0 - const char *filename; - AVOutputFormat *fmt; - AVFormatContext *oc; - AVStream *audio_st, *video_st; - AVCodec *audio_codec, *video_codec; - double audio_time, video_time; - int ret; - - filename = "foo.avi"; - // Register all formats and codecs - av_register_all(); - - /* allocate the output media context */ - avformat_alloc_output_context2(&oc, NULL, NULL, filename); - if (!oc) { - printf("Could not deduce output format from file extension: using MPEG.\n"); - avformat_alloc_output_context2(&oc, NULL, "mpeg", filename); - } - if (!oc) { - return 1; - } - fmt = oc->oformat; - - /* Add the audio and video streams using the default format codecs - * and initialize the codecs. */ - video_st = NULL; - audio_st = NULL; - - if (fmt->video_codec != AV_CODEC_ID_NONE) { - video_st = add_stream(oc, &video_codec, fmt->video_codec); - } - if (fmt->audio_codec != AV_CODEC_ID_NONE) { - audio_st = add_stream(oc, &audio_codec, fmt->audio_codec); - } - - /* Now that all the parameters are set, we can open the audio and - * video codecs and allocate the necessary encode buffers. */ - if (video_st) - open_video(oc, video_codec, video_st); - if (audio_st) - open_audio(oc, audio_codec, audio_st); - - av_dump_format(oc, 0, filename, 1); - - /* open the output file, if needed */ - if (!(fmt->flags & AVFMT_NOFILE)) { - ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE); - if (ret < 0) { - fprintf(stderr, "Could not open '%s': %s\n", filename, - av_err2str(ret)); - return 1; - } - } - - /* Write the stream header, if any. */ - ret = avformat_write_header(oc, NULL); - if (ret < 0) { - fprintf(stderr, "Error occurred when opening output file: %s\n", - av_err2str(ret)); - return 1; - } - - if (frame) - frame->pts = 0; - for (;;) { - /* Compute current audio and video time. */ - audio_time = audio_st ? audio_st->pts.val * av_q2d(audio_st->time_base) : 0.0; - video_time = video_st ? video_st->pts.val * av_q2d(video_st->time_base) : 0.0; - - if ((!audio_st || audio_time >= STREAM_DURATION) && - (!video_st || video_time >= STREAM_DURATION)) - break; - - /* write interleaved audio and video frames */ - if (!video_st || (video_st && audio_st && audio_time < video_time)) { - write_audio_frame(oc, audio_st); - } else { - write_video_frame(oc, video_st); - frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base); - } - } - - /* Write the trailer, if any. The trailer must be written before you - * close the CodecContexts open when you wrote the header; otherwise - * av_write_trailer() may try to use memory that was freed on - * av_codec_close(). */ - av_write_trailer(oc); - - /* Close each codec. */ - if (video_st) - close_video(oc, video_st); - if (audio_st) - close_audio(oc, audio_st); - - if (!(fmt->flags & AVFMT_NOFILE)) - /* Close the output file. */ - avio_close(oc->pb); - - /* free the stream */ - avformat_free_context(oc); - - return 0; -#endif -} }
\ No newline at end of file diff --git a/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h index 0337f25..e95b32e 100644 --- a/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h +++ b/src/uscxml/plugins/invoker/ffmpeg/FFMPEGInvoker.h @@ -4,11 +4,9 @@ #include <uscxml/Interpreter.h> extern "C" { -#include <libavutil/opt.h> -#include <libavutil/mathematics.h> +#include <libavutil/avutil.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> -#include <libswresample/swresample.h> } #ifdef BUILD_AS_PLUGINS @@ -37,14 +35,53 @@ public: protected: class EncodingContext { + public: + EncodingContext() : + format(NULL), + formatCtx(NULL), + audioStream(NULL), videoStream(NULL), + audioCodec(NULL), videoCodec(NULL), + audioTime(0), videoTime(0), + frame(NULL), + frame_count(0), + width(0), + height(0), + sws_flags(SWS_BICUBIC) {} + + virtual ~EncodingContext() { + if (sws_ctx) + sws_freeContext(sws_ctx); + } + + tthread::recursive_mutex mutex; + PixelFormat videoPixFmt; std::string filename; AVOutputFormat* format; AVFormatContext* formatCtx; - AVStream *audio_st, *video_st; - AVCodec *audio_codec, *video_codec; - double audio_time, video_time; + AVStream *audioStream, *videoStream; + AVCodec *audioCodec, *videoCodec, *imageCodec; + double audioTime, videoTime; + AVFrame *frame; + AVPicture src_picture, dst_picture; + int frame_count; + size_t width, height; + int sws_flags; + SwsContext *sws_ctx; + std::string extension; }; + AVStream* addStream(EncodingContext* ctx, AVFormatContext *oc, AVCodec **codec, enum AVCodecID codec_id); + void openVideo(EncodingContext* ctx, AVFormatContext *oc, AVCodec *codec, AVStream *st); + void writeVideoFrame(EncodingContext* ctx, AVFormatContext *oc, AVStream *st, boost::shared_ptr<Blob> image); + void closeVideo(EncodingContext* ctx, AVFormatContext *oc, AVStream *st); + + static void run(void*); + void finish(EncodingContext* ctx, const SendRequest& req); + void process(const SendRequest& req); + + std::set<tthread::thread*> _threads; + uscxml::concurrency::BlockingQueue<SendRequest> _workQueue; + bool _isRunning; std::map<std::string, EncodingContext*> _encoders; }; diff --git a/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp b/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp index 0f6b776..1292e3a 100644 --- a/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp +++ b/src/uscxml/plugins/invoker/filesystem/dirmon/DirMonInvoker.cpp @@ -65,20 +65,21 @@ void DirMonInvoker::invoke(const InvokeRequest& req) { return; } - if (boost::iequals(req.params.find("reportexisting")->second, "false")) + if (req.params.find("reportexisting") != req.params.end() && + boost::iequals(req.params.find("reportexisting")->second.atom, "false")) _reportExisting = false; if (req.params.find("recurse") != req.params.end() && - boost::iequals(req.params.find("recurse")->second, "true")) + boost::iequals(req.params.find("recurse")->second.atom, "true")) _recurse = true; if (req.params.find("reporthidden") != req.params.end() && - boost::iequals(req.params.find("reporthidden")->second, "true")) + boost::iequals(req.params.find("reporthidden")->second.atom, "true")) _reportHidden = true; std::string suffixList; if (req.params.find("suffix") != req.params.end()) { - suffixList = req.params.find("suffix")->second; + suffixList = req.params.find("suffix")->second.atom; } else if (req.params.find("suffixes") != req.params.end()) { - suffixList = req.params.find("suffixes")->second; + suffixList = req.params.find("suffixes")->second.atom; } if (suffixList.size() > 0) { @@ -92,9 +93,10 @@ void DirMonInvoker::invoke(const InvokeRequest& req) { } } - std::multimap<std::string, std::string>::const_iterator dirIter = req.params.find("dir"); + std::multimap<std::string, Data>::const_iterator dirIter = req.params.find("dir"); while(dirIter != req.params.upper_bound("dir")) { - URL url(dirIter->second); + // this is simplified - Data might be more elaborate than a simple string atom + URL url(dirIter->second.atom); if (!url.toAbsolute(_interpreter->getBaseURI()) || !boost::iequals(url.scheme(), "file")) { LOG(ERROR) << "Given directory '" << dirIter->second << "' cannot be transformed to absolute path"; } else { @@ -266,6 +268,8 @@ void DirectoryWatch::updateEntries(bool reportAsExisting) { } if ((unsigned)dirStat.st_mtime >= (unsigned)_lastChecked) { +// std::cout << "dirStat.st_mtime: " << dirStat.st_mtime << " / _lastChecked: " << _lastChecked << std::endl; + // there are changes in the directory std::set<std::string> currEntries; diff --git a/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGInvoker.cpp b/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGInvoker.cpp index 090f1b3..2b77baf 100644 --- a/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGInvoker.cpp +++ b/src/uscxml/plugins/invoker/graphics/openscenegraph/OSGInvoker.cpp @@ -1,5 +1,6 @@ #include "OSGInvoker.h" #include "uscxml/URL.h" +#include "uscxml/UUID.h" #include <glog/logging.h> #ifdef BUILD_AS_PLUGINS @@ -110,7 +111,7 @@ void OSGInvoker::processViewport(const Arabica::DOM::Node<std::string>& element) _nodes[element] = group; sceneView->setSceneData(group); - std::string name = (HAS_ATTR(element, "id") ? ATTR(element, "id") : Interpreter::getUUID()); + std::string name = (HAS_ATTR(element, "id") ? ATTR(element, "id") : UUID::getUUID()); unsigned int actualX = 0; unsigned int actualY = 0; diff --git a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp index e377e5c..0e8430c 100644 --- a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp +++ b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.cpp @@ -21,12 +21,12 @@ #define EVAL_PARAM_EXPR(param, expr, key) \ if (param.find(key) == param.end() && param.find(expr) != param.end() && _interpreter->getDataModel()) \ - param.insert(std::make_pair(key, _interpreter->getDataModel().evalAsString(param.find(expr)->second))); + param.insert(std::make_pair(key, _interpreter->getDataModel().evalAsString(param.find(expr)->second.atom))); #define CAST_PARAM(param, var, key, type) \ if (param.find(key) != param.end()) { \ - try { var = boost::lexical_cast<type>(param.find(key)->second); } \ - catch(...) { LOG(ERROR) << "Attribute " key " of sendrequest to osgconverter is of invalid format: " << param.find(key)->second; } \ + try { var = boost::lexical_cast<type>(param.find(key)->second.atom); } \ + catch(...) { LOG(ERROR) << "Attribute " key " of sendrequest to osgconverter is of invalid format: " << param.find(key)->second.atom; } \ } @@ -42,6 +42,7 @@ bool connect(pluma::Host& host) { OSGConverter::OSGConverter() : _isRunning(false) { // osg::setNotifyLevel(osg::DEBUG_FP); + osg::setNotifyLevel(osg::FATAL); } OSGConverter::~OSGConverter() { @@ -76,7 +77,7 @@ void OSGConverter::send(const SendRequest& req) { if (actualReq.params.find("source") == actualReq.params.end()) { // no explicit source if (actualReq.params.find("sourceexpr") != actualReq.params.end() && _interpreter->getDataModel()) { - actualReq.params.insert(std::make_pair("source", _interpreter->getDataModel().evalAsString(actualReq.params.find("sourceexpr")->second))); + actualReq.params.insert(std::make_pair("source", _interpreter->getDataModel().getStringAsData(actualReq.params.find("sourceexpr")->second))); } else { LOG(ERROR) << "SendRequests for osginvoker missing source or sourceExpr and datamodel"; return; @@ -86,20 +87,16 @@ void OSGConverter::send(const SendRequest& req) { if (actualReq.params.find("dest") == actualReq.params.end()) { // no explicit destination if (actualReq.params.find("destexpr") != actualReq.params.end() && _interpreter->getDataModel()) { - actualReq.params.insert(std::make_pair("dest", _interpreter->getDataModel().evalAsString(actualReq.params.find("destexpr")->second))); - } else { - LOG(ERROR) << "SendRequests for osginvoker missing dest or destExpr and datamodel"; - return; + actualReq.params.insert(std::make_pair("dest", _interpreter->getDataModel().getStringAsData(actualReq.params.find("destexpr")->second))); + boost::algorithm::replace_all(actualReq.params.find("dest")->second.atom, "//", "/"); + boost::algorithm::replace_all(actualReq.params.find("dest")->second.atom, "\\\\", "\\"); } } - boost::algorithm::replace_all(actualReq.params.find("dest")->second, "//", "/"); - boost::algorithm::replace_all(actualReq.params.find("dest")->second, "\\\\", "\\"); - if (actualReq.params.find("autorotate") == actualReq.params.end()) { if (actualReq.params.find("autorotateexpr") != actualReq.params.end()) { if (_interpreter->getDataModel()) { - actualReq.params.insert(std::make_pair("autorotate", _interpreter->getDataModel().evalAsString(actualReq.params.find("autorotateexpr")->second))); + actualReq.params.insert(std::make_pair("autorotate", _interpreter->getDataModel().getStringAsData(actualReq.params.find("autorotateexpr")->second))); } else { LOG(ERROR) << "SendRequests for osginvoker ncludes autorotateexpr but no datamodel is specified"; return; @@ -110,34 +107,25 @@ void OSGConverter::send(const SendRequest& req) { if (actualReq.params.find("format") == actualReq.params.end()) { // no explicit format if (actualReq.params.find("formatexpr") != actualReq.params.end() && _interpreter->getDataModel()) { - actualReq.params.insert(std::make_pair("format", _interpreter->getDataModel().evalAsString(actualReq.params.find("formatexpr")->second))); + actualReq.params.insert(std::make_pair("format", _interpreter->getDataModel().getStringAsData(actualReq.params.find("formatexpr")->second))); } else { std::string format; - size_t lastDot; - std::string dest = req.params.find("dest")->second; - if ((lastDot = dest.find_last_of(".")) != std::string::npos) { - lastDot++; - format = dest.substr(lastDot, dest.length() - lastDot); + std::string dest; + if (Event::getParam(actualReq.params, "dest", dest)) { + size_t lastDot; + if ((lastDot = dest.find_last_of(".")) != std::string::npos) { + lastDot++; + format = dest.substr(lastDot, dest.length() - lastDot); + } } if (format.length() == 0 || format.find_last_of(PATH_SEPERATOR) != std::string::npos) { // empty format or pathseperator in format format = "png"; } - actualReq.params.insert(std::make_pair("format", format)); + actualReq.params.insert(std::make_pair("format", Data(format, Data::VERBATIM))); } } -// assert(osgDB::Registry::instance()->getReaderWriterForExtension("png")); -// osgDB::Registry::ReaderWriterList formatList = osgDB::Registry::instance()->getReaderWriterList(); -// for (int i = 0; i < formatList.size(); i++) { -// std::map<std::string, std::string> funcDesc = formatList[i]->supportedProtocols(); -// std::map<std::string, std::string>::iterator funcDescIter = funcDesc.begin(); -// while(funcDescIter != funcDesc.end()) { -// std::cout << funcDescIter->first << ": " << funcDescIter->second << std::endl; -// funcDescIter++; -// } -// } - EVAL_PARAM_EXPR(actualReq.params, "heightexpr", "height"); EVAL_PARAM_EXPR(actualReq.params, "widthexpr", "width"); EVAL_PARAM_EXPR(actualReq.params, "pitchexpr", "pitch"); @@ -157,7 +145,8 @@ void OSGConverter::cancel(const std::string sendId) { void OSGConverter::invoke(const InvokeRequest& req) { int nrThreads = 1; - if (req.params.find("threads") != req.params.end() && isNumeric(req.params.find("threads")->second.c_str(), 10)) { + + if (req.params.find("threads") != req.params.end() && isNumeric(req.params.find("threads")->second.atom.c_str(), 10)) { nrThreads = strTo<int>(req.params.find("threads")->second); } @@ -185,22 +174,28 @@ void OSGConverter::process(const SendRequest& req) { int width = 640; int height = 480; - CAST_PARAM(req.params, width, "width", int); - CAST_PARAM(req.params, height, "height", int); + Event::getParam(req.params, "width", width); + Event::getParam(req.params, "height", height); assert(req.params.find("source") != req.params.end()); - assert(req.params.find("dest") != req.params.end()); assert(req.params.find("format") != req.params.end()); - std::string source = req.params.find("source")->second; - std::string dest = req.params.find("dest")->second; - std::string format = req.params.find("format")->second; + std::string source; + if (!Event::getParam(req.params, "source", source)) + reportFailure(req); + + std::string dest; + Event::getParam(req.params, "dest", dest); + + std::string format; + if (!Event::getParam(req.params, "format", format)) + reportFailure(req); bool autoRotate = true; if (req.params.find("autorotate") != req.params.end()) { - if (boost::iequals(req.params.find("autorotate")->second, "off") || - boost::iequals(req.params.find("autorotate")->second, "0") || - boost::iequals(req.params.find("autorotate")->second, "false")) { + if (boost::iequals(req.params.find("autorotate")->second.atom, "off") || + boost::iequals(req.params.find("autorotate")->second.atom, "0") || + boost::iequals(req.params.find("autorotate")->second.atom, "false")) { autoRotate = false; } } @@ -215,16 +210,22 @@ void OSGConverter::process(const SendRequest& req) { sceneGraph->addChild(model); osgDB::ReaderWriter::WriteResult result; - if (osgDB::Registry::instance()->getReaderWriterForExtension(format) != NULL) { - // write as another 3D file - result = osgDB::Registry::instance()->writeNode(*sceneGraph, dest, osgDB::Registry::instance()->getOptions()); + + osg::ref_ptr<osgDB::ReaderWriter> writer = osgDB::Registry::instance()->getReaderWriterForExtension(format); + if(writer.valid()) { + std::stringstream ss; + result = writer->writeNode(*sceneGraph, ss); if (result.success()) { - // we can know about success right here - reportSuccess(req); + if (dest.length() > 0) { + std::ofstream outFile(dest.c_str()); + outFile << ss.str(); + } + Data content(ss.str().c_str(), ss.str().size(), false); + reportSuccess(req, content); return; } } - + /** * If we failed to interpret the extension as another 3D file, try to make a screenshot. */ @@ -245,53 +246,66 @@ void OSGConverter::process(const SendRequest& req) { osgViewer::ScreenCaptureHandler* captureHandler = new osgViewer::ScreenCaptureHandler(cOp, -1); - osgViewer::Viewer viewer; - viewer.setSceneData(sceneGraph); - viewer.setCameraManipulator(new osgGA::TrackballManipulator()); - viewer.addEventHandler(captureHandler); - captureHandler->startCapture(); - - osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); - osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(ds); - traits->width = width; - traits->height = height; - traits->pbuffer = true; - osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get()); - - if (!gc.valid()) { - LOG(ERROR) << "Cannot create GraphicsContext!"; - return; - } + { +// tthread::lock_guard<tthread::recursive_mutex> lock(_viewerMutex); + osgViewer::Viewer viewer; + osg::ref_ptr<osg::GraphicsContext> gc; + + viewer.setSceneData(sceneGraph); + viewer.addEventHandler(captureHandler); + captureHandler->startCapture(); + + osg::DisplaySettings* ds = osg::DisplaySettings::instance().get(); + osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(ds); + traits->width = width; + traits->height = height; + traits->pbuffer = true; + gc = osg::GraphicsContext::createGraphicsContext(traits.get()); + + if (!gc.valid()) { + LOG(ERROR) << "Cannot create GraphicsContext!"; + return; + } + + GLenum pbuffer = gc->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT; - GLenum pbuffer = gc->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT; + viewer.setCameraManipulator(new osgGA::TrackballManipulator()); + viewer.getCamera()->setGraphicsContext(gc.get()); + viewer.getCamera()->setViewport(new osg::Viewport(0,0,traits->width,traits->height)); + viewer.getCamera()->setDrawBuffer(pbuffer); + viewer.getCamera()->setReadBuffer(pbuffer); - viewer.getCamera()->setGraphicsContext(gc.get()); - viewer.getCamera()->setViewport(new osg::Viewport(0,0,traits->width,traits->height)); - viewer.getCamera()->setDrawBuffer(pbuffer); - viewer.getCamera()->setReadBuffer(pbuffer); + double zoom = 1; + CAST_PARAM(req.params, zoom, "zoom", double); - double zoom = 1; - CAST_PARAM(req.params, zoom, "zoom", double); + // set background color + viewer.getCamera()->setClearColor(osg::Vec4f(1.0f,1.0f,1.0f,1.0f)); + viewer.getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + viewer.getCameraManipulator()->setByMatrix(osg::Matrix::lookAt(osg::Vec3d(0,0,bs.radius() * (-3.4 * zoom)), // eye + (osg::Vec3d)bs.center(), // center + osg::Vec3d(0,1,0))); // up - // set background color - viewer.getCamera()->setClearColor(osg::Vec4f(1.0f,1.0f,1.0f,1.0f)); - viewer.getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - viewer.getCameraManipulator()->setByMatrix(osg::Matrix::lookAt(osg::Vec3d(0,0,bs.radius() * (-3.4 * zoom)), // eye - (osg::Vec3d)bs.center(), // center - osg::Vec3d(0,1,0))); // up + // viewer.home(); -// viewer.home(); + // perform one viewer iteration + viewer.realize(); + viewer.frame(); - // perform one viewer iteration - viewer.realize(); - viewer.frame(); + viewer.removeEventHandler(captureHandler); + } + +// delete(cOp); +// delete(captureHandler); } -void OSGConverter::reportSuccess(const SendRequest& req) { +void OSGConverter::reportSuccess(const SendRequest& req, const Data& content) { Event event(req); + if (event.name.length() == 0) event.name = "convert"; event.name += ".success"; + if (content) + event.data.compound["content"] = content; returnEvent(event); } @@ -366,7 +380,7 @@ osg::ref_ptr<osg::Node> OSGConverter::setupGraph(const std::string filename, boo std::map<std::string, std::pair<long, osg::ref_ptr<osg::Node> > >::iterator modelIter = _models.begin(); while(modelIter != _models.end()) { // delete every model unused for 1 minutes - if (now - modelIter->second.first > 60000) { + if (now - modelIter->second.first > 6000) { _models.erase(modelIter++); } else { modelIter++; @@ -504,30 +518,79 @@ void OSGConverter::dumpMatrix(const osg::Matrix& m) { } void OSGConverter::NameRespectingWriteToFile::operator()(const osg::Image& image, const unsigned int context_id) { + + // write to memory first + std::string format; + if (_req.params.find("format") != _req.params.end()) { + format = _req.params.find("format")->second.atom; + } else { + _converter->reportFailure(_req); + } -// URL fileURL(_filename); -// fileURL.path() + osg::ref_ptr<osgDB::ReaderWriter::Options> op = new osgDB::ReaderWriter::Options(); + //op->setOptionString("JPEG_QUALITY 75"); + //op->setOptionString("PNG_COMPRESSION 9"); - std::string tmpName = _filename; - size_t pathSep = _filename.find_last_of(PATH_SEPERATOR); - if (pathSep != std::string::npos) { - tmpName = _filename.substr(0, pathSep) + PATH_SEPERATOR + ".tmp" + _filename.substr(pathSep + 1, _filename.length() - pathSep - 1); + // osgDB got confused when we write to a stringstream + std::string tempFile = URL::getTmpFilename(format); + osgDB::writeImageFile(image, tempFile, op); + + char* buffer = NULL; + size_t length = 0; + { + std::ifstream file(tempFile.c_str()); + + file.seekg(0, std::ios::end); + length = file.tellg(); + file.seekg(0, std::ios::beg); + buffer = (char*)malloc(length); + file.read(buffer, length); } + + remove(tempFile.c_str()); +// osg::ref_ptr<osgDB::ReaderWriter> writerFormat = osgDB::Registry::instance()->getReaderWriterForExtension(format); +// if(!writerFormat.valid()) +// _converter->reportFailure(_req); - bool success = osgDB::writeImageFile(image, tmpName); // <- no plugin to write to .tmp format - if (!success) { - _converter->reportFailure(_req); - return; - } - if (pathSep != std::string::npos) { - int err = rename(tmpName.c_str(), _filename.c_str()); - if (err) { - _converter->reportFailure(_req); +#if 0 + std::stringstream ssFormat; + + osgDB::ReaderWriter::WriteResult res = writerFormat->writeImage(image, ssFormat, op); + + if (_filename.length() > 0) { + std::string tmpName = _filename; + size_t pathSep = _filename.find_last_of(PATH_SEPERATOR); + if (pathSep != std::string::npos) { + tmpName = _filename.substr(0, pathSep) + PATH_SEPERATOR + ".tmp" + _filename.substr(pathSep + 1, _filename.length() - pathSep - 1); } - } - _converter->reportSuccess(_req); + { + std::ofstream outFile(tmpName.c_str()); + outFile << ssFormat.str(); + } + + if (pathSep != std::string::npos) { + int err = rename(tmpName.c_str(), _filename.c_str()); + if (err) { + _converter->reportFailure(_req); + } + } + } +#endif + + Data content; + content.compound[format] = Data(buffer, length, false); + + // save image as a raw rgba as well for ffmpeg - we are using the mpb format for now +// osg::ref_ptr<osgDB::ReaderWriter> writerRGBA = osgDB::Registry::instance()->getReaderWriterForExtension("rgba"); +// if(writerRGBA.valid()) { +// std::stringstream ssRGBA; +// osgDB::ReaderWriter::WriteResult res = writerRGBA->writeImage(image, ssRGBA, op); +// content.compound["rgba"] = Data(ssRGBA.str().c_str(), ssRGBA.str().size(), false); +// } + + _converter->reportSuccess(_req, content); } }
\ No newline at end of file diff --git a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h index 78d6c6c..2bc84a1 100644 --- a/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h +++ b/src/uscxml/plugins/invoker/graphics/openscenegraph/converter/OSGConverter.h @@ -31,7 +31,7 @@ public: virtual void cancel(const std::string sendId); virtual void invoke(const InvokeRequest& req); - void reportSuccess(const SendRequest& req); + void reportSuccess(const SendRequest& req, const Data& content); void reportFailure(const SendRequest& req); osg::Matrix requestToModelPose(const SendRequest& req); @@ -61,6 +61,8 @@ protected: osg::ref_ptr<osg::Node> setupGraph(const std::string filename, bool autoRotate = false); osg::ref_ptr<osg::Node> getOrigin(); + tthread::recursive_mutex _viewerMutex; + std::map<std::string, std::pair<long, osg::ref_ptr<osg::Node> > > _models; tthread::recursive_mutex _cacheMutex; diff --git a/src/uscxml/plugins/invoker/heartbeat/HeartbeatInvoker.cpp b/src/uscxml/plugins/invoker/heartbeat/HeartbeatInvoker.cpp index c7b91f6..5afbc66 100644 --- a/src/uscxml/plugins/invoker/heartbeat/HeartbeatInvoker.cpp +++ b/src/uscxml/plugins/invoker/heartbeat/HeartbeatInvoker.cpp @@ -49,8 +49,8 @@ void HeartbeatInvoker::invoke(const InvokeRequest& req) { InvokeRequest::params_t::const_iterator paramIter = req.params.begin(); while(paramIter != req.params.end()) { if (boost::iequals(paramIter->first, "interval")) { - intervalStr = paramIter->second; - NumAttr intervalAttr(paramIter->second); + intervalStr = paramIter->second.atom; + NumAttr intervalAttr(paramIter->second.atom); interval = strTo<double>(intervalAttr.value); if (false) { } else if (boost::iequals(intervalAttr.unit, "s")) { @@ -62,7 +62,7 @@ void HeartbeatInvoker::invoke(const InvokeRequest& req) { } } if (boost::iequals(paramIter->first, "eventname")) { - _event.name = paramIter->second; + _event.name = paramIter->second.atom; } paramIter++; } diff --git a/src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp b/src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp index 2c395a8..1a95396 100644 --- a/src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp +++ b/src/uscxml/plugins/invoker/http/HTTPServletInvoker.cpp @@ -52,15 +52,15 @@ void HTTPServletInvoker::send(const SendRequest& req) { HTTPServer::Reply httpReply(httpRequest); httpReply.content = req.content; - std::map<std::string, std::string>::const_iterator nameListIter = req.namelist.begin(); + std::map<std::string, Data>::const_iterator nameListIter = req.namelist.begin(); while(nameListIter != req.namelist.end()) { - httpReply.headers[nameListIter->first] = nameListIter->second; + httpReply.headers[nameListIter->first] = nameListIter->second.atom; nameListIter++; } - std::multimap<std::string, std::string>::const_iterator paramIter = req.params.begin(); + std::multimap<std::string, Data>::const_iterator paramIter = req.params.begin(); while(paramIter != req.params.end()) { - httpReply.headers[paramIter->first] = paramIter->second; + httpReply.headers[paramIter->first] = paramIter->second.atom; paramIter++; } @@ -78,10 +78,10 @@ void HTTPServletInvoker::invoke(const InvokeRequest& req) { if (req.params.find("path") == req.params.end()) { LOG(ERROR) << "Path parameter required with httpserver"; } - _path = (*req.params.find("path")).second; + _path = (*req.params.find("path")).second.atom; if (req.params.find("callback") != req.params.end()) { - _callback = (*req.params.find("callback")).second; + _callback = (*req.params.find("callback")).second.atom; } else { _callback = _path; std::replace(_callback.begin(), _callback.end(), '/', '.'); diff --git a/src/uscxml/plugins/invoker/miles/SpatialAudio.cpp b/src/uscxml/plugins/invoker/miles/SpatialAudio.cpp index fb91e40..9a26960 100644 --- a/src/uscxml/plugins/invoker/miles/SpatialAudio.cpp +++ b/src/uscxml/plugins/invoker/miles/SpatialAudio.cpp @@ -125,7 +125,7 @@ void SpatialAudio::invoke(const InvokeRequest& req) { getPosFromParams(req.params, _pos); - std::multimap<std::string, std::string>::const_iterator paramIter = req.params.begin(); + std::multimap<std::string, Data>::const_iterator paramIter = req.params.begin(); while(paramIter != req.params.end()) { if (boost::iequals(paramIter->first, "maxX")) _maxPos[0] = strTo<float>(paramIter->second); @@ -150,7 +150,7 @@ void SpatialAudio::invoke(const InvokeRequest& req) { } } -void SpatialAudio::getPosFromParams(const std::multimap<std::string, std::string>& params, float* position) { +void SpatialAudio::getPosFromParams(const std::multimap<std::string, Data>& params, float* position) { // vector explicitly given try { if (params.find("x") != params.end()) diff --git a/src/uscxml/plugins/invoker/miles/SpatialAudio.h b/src/uscxml/plugins/invoker/miles/SpatialAudio.h index 59ecfdb..e7ab830 100644 --- a/src/uscxml/plugins/invoker/miles/SpatialAudio.h +++ b/src/uscxml/plugins/invoker/miles/SpatialAudio.h @@ -34,7 +34,7 @@ public: virtual void invoke(const InvokeRequest& req); virtual void sendToParent(SendRequest& req); - void getPosFromParams(const std::multimap<std::string, std::string>& params, float* position); + void getPosFromParams(const std::multimap<std::string, Data>& params, float* position); static float posToRadian(const std::string& position); protected: diff --git a/src/uscxml/plugins/invoker/umundo/UmundoInvoker.cpp b/src/uscxml/plugins/invoker/umundo/UmundoInvoker.cpp index de5a580..6845280 100644 --- a/src/uscxml/plugins/invoker/umundo/UmundoInvoker.cpp +++ b/src/uscxml/plugins/invoker/umundo/UmundoInvoker.cpp @@ -63,7 +63,7 @@ void UmundoInvoker::send(const SendRequest& req) { std::string type; if (req.params.find("type") != req.params.end()) { // we are supposed to build a typed object - type = req.params.find("type")->second; + type = req.params.find("type")->second.atom; const google::protobuf::Message* protoMsg = umundo::PBSerializer::getProto(type); if (protoMsg == NULL) { @@ -139,17 +139,17 @@ void UmundoInvoker::invoke(const InvokeRequest& req) { std::string serviceName; if (req.params.find("channel") != req.params.end()) { - channelName = req.params.find("channel")->second; + channelName = req.params.find("channel")->second.atom; _isService = false; } else if (req.params.find("service") != req.params.end()) { - serviceName = req.params.find("service")->second; + serviceName = req.params.find("service")->second.atom; _isService = true; } else { LOG(ERROR) << "Invoking umundo needs a service or a channel param"; return; } if (req.params.find("domain") != req.params.end()) { - domain = req.params.find("domain")->second; + domain = req.params.find("domain")->second.atom; } _node = getNode(_interpreter, domain); @@ -157,7 +157,7 @@ void UmundoInvoker::invoke(const InvokeRequest& req) { if (req.params.find("type") != req.params.end()) { std::pair<InvokeRequest::params_t::const_iterator, InvokeRequest::params_t::const_iterator> typeRange = req.params.equal_range("types"); for (InvokeRequest::params_t::const_iterator it = typeRange.first; it != typeRange.second; it++) { - URL typeURI(it->second); + URL typeURI(it->second.atom); if (typeURI.toAbsolute(_interpreter->getBaseURI())) { std::string filename = typeURI.asLocalFile(".proto"); umundo::PBSerializer::addProto(filename); @@ -171,7 +171,7 @@ void UmundoInvoker::invoke(const InvokeRequest& req) { if (req.params.find("types") != req.params.end()) { std::pair<InvokeRequest::params_t::const_iterator, InvokeRequest::params_t::const_iterator> typeRange = req.params.equal_range("types"); for (InvokeRequest::params_t::const_iterator it = typeRange.first; it != typeRange.second; it++) { - URL typeURI(it->second); + URL typeURI(it->second.atom); if (typeURI.toAbsolute(_interpreter->getBaseURI()) && typeURI.scheme().compare("file") == 0) { umundo::PBSerializer::addProto(typeURI.path()); } else { diff --git a/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp b/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp index 90cebc3..74e51d6 100644 --- a/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp +++ b/src/uscxml/plugins/ioprocessor/basichttp/BasicHTTPIOProcessor.cpp @@ -187,10 +187,11 @@ void BasicHTTPIOProcessor::send(const SendRequest& req) { // event namelist if (req.namelist.size() > 0) { - std::map<std::string, std::string>::const_iterator namelistIter = req.namelist.begin(); + std::map<std::string, Data>::const_iterator namelistIter = req.namelist.begin(); while (namelistIter != req.namelist.end()) { char* keyCStr = evhttp_encode_uri(namelistIter->first.c_str()); - char* valueCStr = evhttp_encode_uri(namelistIter->second.c_str()); + // this is simplified - Data might be more elaborate than a simple string atom + char* valueCStr = evhttp_encode_uri(namelistIter->second.atom.c_str()); kvps << kvpSeperator << keyCStr << "=" << valueCStr; free(keyCStr); free(valueCStr); @@ -202,10 +203,11 @@ void BasicHTTPIOProcessor::send(const SendRequest& req) { // event params if (req.params.size() > 0) { - std::multimap<std::string, std::string>::const_iterator paramIter = req.params.begin(); + std::multimap<std::string, Data>::const_iterator paramIter = req.params.begin(); while (paramIter != req.params.end()) { char* keyCStr = evhttp_encode_uri(paramIter->first.c_str()); - char* valueCStr = evhttp_encode_uri(paramIter->second.c_str()); + // this is simplified - Data might be more elaborate than a simple string atom + char* valueCStr = evhttp_encode_uri(paramIter->second.atom.c_str()); kvps << kvpSeperator << keyCStr << "=" << valueCStr; free(keyCStr); free(valueCStr); diff --git a/src/uscxml/plugins/ioprocessor/modality/MMIHTTPIOProcessor.cpp b/src/uscxml/plugins/ioprocessor/modality/MMIHTTPIOProcessor.cpp index 67b518c..0009cc0 100644 --- a/src/uscxml/plugins/ioprocessor/modality/MMIHTTPIOProcessor.cpp +++ b/src/uscxml/plugins/ioprocessor/modality/MMIHTTPIOProcessor.cpp @@ -138,9 +138,10 @@ void MMIHTTPIOProcessor::send(const SendRequest& req) { // event namelist if (req.namelist.size() > 0) { - std::map<std::string, std::string>::const_iterator namelistIter = req.namelist.begin(); + std::map<std::string, Data>::const_iterator namelistIter = req.namelist.begin(); while (namelistIter != req.namelist.end()) { - kvps << kvpSeperator << evhttp_encode_uri(namelistIter->first.c_str()) << "=" << evhttp_encode_uri(namelistIter->second.c_str()); + // this is simplified - Data might be more elaborate than a simple string atom + kvps << kvpSeperator << evhttp_encode_uri(namelistIter->first.c_str()) << "=" << evhttp_encode_uri(namelistIter->second.atom.c_str()); kvpSeperator = "&"; // targetURL.addOutHeader(namelistIter->first, namelistIter->second); namelistIter++; @@ -149,9 +150,10 @@ void MMIHTTPIOProcessor::send(const SendRequest& req) { // event params if (req.params.size() > 0) { - std::multimap<std::string, std::string>::const_iterator paramIter = req.params.begin(); + std::multimap<std::string, Data>::const_iterator paramIter = req.params.begin(); while (paramIter != req.params.end()) { - kvps << kvpSeperator << evhttp_encode_uri(paramIter->first.c_str()) << "=" << evhttp_encode_uri(paramIter->second.c_str()); + // this is simplified - Data might be more elaborate than a simple string atom + kvps << kvpSeperator << evhttp_encode_uri(paramIter->first.c_str()) << "=" << evhttp_encode_uri(paramIter->second.atom.c_str()); kvpSeperator = "&"; // targetURL.addOutHeader(paramIter->first, paramIter->second); paramIter++; diff --git a/src/uscxml/server/HTTPServer.cpp b/src/uscxml/server/HTTPServer.cpp index e37d8cc..0521d74 100644 --- a/src/uscxml/server/HTTPServer.cpp +++ b/src/uscxml/server/HTTPServer.cpp @@ -194,14 +194,15 @@ void HTTPServer::httpRecvReqCallback(struct evhttp_request *req, void *callbackD // } // seperate path into components - std::stringstream ss(request.data.compound["path"].atom); - std::string item; - while(std::getline(ss, item, '/')) { - if (item.length() == 0) - continue; - request.data.compound["pathComponent"].array.push_back(Data(item, Data::VERBATIM)); + { + std::stringstream ss(request.data.compound["path"].atom); + std::string item; + while(std::getline(ss, item, '/')) { + if (item.length() == 0) + continue; + request.data.compound["pathComponent"].array.push_back(Data(item, Data::VERBATIM)); + } } - // parse query string struct evkeyvalq params; struct evkeyval *param; @@ -233,9 +234,27 @@ void HTTPServer::httpRecvReqCallback(struct evhttp_request *req, void *callbackD std::string contentType = request.data.compound["header"].compound["Content-Type"].atom; if (false) { } else if (boost::iequals(contentType, "application/x-www-form-urlencoded")) { - char* contentCStr = evhttp_decode_uri(request.data.compound["content"].atom.c_str()); - request.data.compound["content"].atom = contentCStr; - free(contentCStr); + // this is a form submit + std::stringstream ss(request.data.compound["content"].atom); + std::string item; + std::string key; + std::string value; + while(std::getline(ss, item, '=')) { + if (item.length() == 0) + continue; + if (key.length() == 0) { + key = item; + continue; + } + value = item; + char* keyCStr = evhttp_decode_uri(key.c_str()); + char* valueCStr = evhttp_decode_uri(value.c_str()); + request.data.compound["content"].compound[keyCStr] = Data(valueCStr, Data::VERBATIM); + free(keyCStr); + free(valueCStr); + key.clear(); + } + request.data.compound["content"].atom.clear(); } else if (boost::iequals(contentType, "application/json")) { request.data.compound["content"] = Data::fromJSON(request.data.compound["content"].atom); } diff --git a/src/uscxml/server/InterpreterServlet.cpp b/src/uscxml/server/InterpreterServlet.cpp index b59cc05..49f6677 100644 --- a/src/uscxml/server/InterpreterServlet.cpp +++ b/src/uscxml/server/InterpreterServlet.cpp @@ -35,7 +35,22 @@ bool InterpreterServlet::httpRecvRequest(const HTTPServer::Request& req) { event.name = "http." + event.data.compound["type"].atom; event.origin = toStr((uintptr_t)req.curlReq); - event.initContent(event.data.compound["content"].atom); + + if (event.data.compound["content"]) { + if (event.data.compound["content"].compound.size() > 0) { + std::map<std::string, Data>::iterator compoundIter = event.data.compound["content"].compound.begin(); + while(compoundIter != event.data.compound["content"].compound.end()) { +// std::cout << compoundIter->second.atom << std::endl; + Data json = Data::fromJSON(compoundIter->second.atom); + if (json) { +// std::cout << Data::toJSON(json) << std::endl; + compoundIter->second = json; + } + compoundIter++; + } + } + } + _interpreter->receive(event); return true; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 93c002e..5fcad93 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,6 +18,13 @@ if (SWI_FOUND) add_test(test-prolog-swi ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/uscxml-browser ${CMAKE_SOURCE_DIR}/test/samples/uscxml/test-prolog.scxml) endif() +if (FFMPEG_FOUND) + add_executable(test-ffmpeg src/test-ffmpeg.cpp) + target_link_libraries(test-ffmpeg uscxml) + add_test(test-ffmpeg ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test-ffmpeg) + set_target_properties(test-ffmpeg PROPERTIES FOLDER "Tests") +endif() + if (V8_FOUND) add_test(test-ecmascript ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/uscxml-browser ${CMAKE_SOURCE_DIR}/test/samples/uscxml/test-ecmascript.scxml) endif() diff --git a/test/src/test-ffmpeg.cpp b/test/src/test-ffmpeg.cpp new file mode 100644 index 0000000..c9dcfe1 --- /dev/null +++ b/test/src/test-ffmpeg.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @file + * libavformat API example. + * + * Output a media file in any supported libavformat format. + * The default codecs are used. + * @example doc/examples/muxing.c + */ + +#include <fstream> +#include <sstream> + +extern "C" { +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <math.h> + +#include <libavutil/opt.h> +#include <libavutil/mathematics.h> +#include <libavformat/avformat.h> +#include <libswscale/swscale.h> +#include <libswresample/swresample.h> +} +/* 5 seconds stream duration */ +#define STREAM_FRAME_RATE 25 /* 25 images/s */ +#define STREAM_PIX_FMT AV_PIX_FMT_YUV420P /* default pix_fmt */ +#define BMP_FORMAT AV_PIX_FMT_RGB24 + +static int sws_flags = SWS_BICUBIC; + +/* Add an output stream. */ +static AVStream *add_stream(AVFormatContext *oc, AVCodec **codec, + enum AVCodecID codec_id) +{ + AVCodecContext *c; + AVStream *st; + + /* find the encoder */ + *codec = avcodec_find_encoder(codec_id); + if (!(*codec)) { + fprintf(stderr, "Could not find encoder for '%s'\n", + avcodec_get_name(codec_id)); + exit(1); + } + + st = avformat_new_stream(oc, *codec); + if (!st) { + fprintf(stderr, "Could not allocate stream\n"); + exit(1); + } + st->id = oc->nb_streams-1; + c = st->codec; + + switch ((*codec)->type) { + case AVMEDIA_TYPE_AUDIO: + c->sample_fmt = AV_SAMPLE_FMT_FLTP; + c->bit_rate = 64000; + c->sample_rate = 44100; + c->channels = 2; + break; + + case AVMEDIA_TYPE_VIDEO: + c->codec_id = codec_id; + + c->bit_rate = 400000; + /* Resolution must be a multiple of two. */ + c->width = 352; + c->height = 288; + /* timebase: This is the fundamental unit of time (in seconds) in terms + * of which frame timestamps are represented. For fixed-fps content, + * timebase should be 1/framerate and timestamp increments should be + * identical to 1. */ + c->time_base.den = STREAM_FRAME_RATE; + c->time_base.num = 1; + c->gop_size = 12; /* emit one intra frame every twelve frames at most */ + c->pix_fmt = STREAM_PIX_FMT; + if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) { + /* just for testing, we also add B frames */ + c->max_b_frames = 2; + } + if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) { + /* Needed to avoid using macroblocks in which some coeffs overflow. + * This does not happen with normal video, it just happens here as + * the motion of the chroma plane does not match the luma plane. */ + c->mb_decision = 2; + } + break; + + default: + break; + } + + /* Some formats want stream headers to be separate. */ + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + return st; +} + + +/**************************************************************/ +/* video output */ + +static AVFrame *frame; +static AVPicture src_picture, dst_picture; +static int frame_count; + +static void open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st) +{ + int ret; + AVCodecContext *c = st->codec; + + /* open the codec */ + ret = avcodec_open2(c, codec, NULL); + if (ret < 0) { + fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret)); + exit(1); + } + + /* allocate and init a re-usable frame */ + frame = avcodec_alloc_frame(); + if (!frame) { + fprintf(stderr, "Could not allocate video frame\n"); + exit(1); + } + + /* Allocate the encoded raw picture. */ + ret = avpicture_alloc(&dst_picture, c->pix_fmt, c->width, c->height); + if (ret < 0) { + fprintf(stderr, "Could not allocate picture: %s\n", av_err2str(ret)); + exit(1); + } + + /* If the output format is not YUV420P, then a temporary YUV420P + * picture is needed too. It is then converted to the required + * output format. */ + if (c->pix_fmt != BMP_FORMAT) { + ret = avpicture_alloc(&src_picture, BMP_FORMAT, c->width, c->height); + if (ret < 0) { + fprintf(stderr, "Could not allocate temporary picture: %s\n", + av_err2str(ret)); + exit(1); + } + } + + /* copy data and linesize picture pointers to frame */ + *((AVPicture *)frame) = dst_picture; +} + + +/* Prepare a dummy image. */ +static void fill_rgba_image(AVPicture *pict, int frame_index, + int width, int height) +{ + int x, y, i; + + i = frame_index; + + std::stringstream ssFilename; + ssFilename << "/Users/sradomski/Desktop/ctrl/" << (i % 125) << ".bmp"; + + std::ifstream file(ssFilename.str().c_str()); + + file.seekg(0, std::ios::end); + size_t length = file.tellg(); + file.seekg(0, std::ios::beg); + + char* buffer = (char*)malloc(length); + file.read(buffer, length); + + uint32_t offset = 0; + offset += buffer[10] << 0; + offset += buffer[11] << 8; + offset += buffer[12] << 16; + offset += buffer[13] << 24; + offset--; + + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + pict->data[0][y * pict->linesize[0] + x * 3] = buffer[offset++]; + pict->data[0][y * pict->linesize[0] + x * 3 + 1] = buffer[offset++]; + pict->data[0][y * pict->linesize[0] + x * 3 + 2] = buffer[offset++]; + } + } + + free(buffer); +} + +static void write_video_frame(AVFormatContext *oc, AVStream *st) +{ + int ret; + static struct SwsContext *sws_ctx; + AVCodecContext *c = st->codec; + + if (c->pix_fmt != BMP_FORMAT) { + /* as we only generate a YUV420P picture, we must convert it + * to the codec pixel format if needed */ + if (!sws_ctx) { + sws_ctx = sws_getContext(c->width, c->height, BMP_FORMAT, + c->width, c->height, c->pix_fmt, + sws_flags, NULL, NULL, NULL); + if (!sws_ctx) { + fprintf(stderr, + "Could not initialize the conversion context\n"); + exit(1); + } + } + fill_rgba_image(&src_picture, frame_count, c->width, c->height); + sws_scale(sws_ctx, + (const uint8_t * const *)src_picture.data, src_picture.linesize, + 0, c->height, dst_picture.data, dst_picture.linesize); + } else { + fill_rgba_image(&dst_picture, frame_count, c->width, c->height); + } + + if (oc->oformat->flags & AVFMT_RAWPICTURE) { + /* Raw video case - directly store the picture in the packet */ + AVPacket pkt; + av_init_packet(&pkt); + + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.stream_index = st->index; + pkt.data = dst_picture.data[0]; + pkt.size = sizeof(AVPicture); + + ret = av_interleaved_write_frame(oc, &pkt); + } else { + AVPacket pkt = { 0 }; + int got_packet; + av_init_packet(&pkt); + + /* encode the image */ + ret = avcodec_encode_video2(c, &pkt, frame, &got_packet); + if (ret < 0) { + fprintf(stderr, "Error encoding video frame: %s\n", av_err2str(ret)); + exit(1); + } + /* If size is zero, it means the image was buffered. */ + + if (!ret && got_packet && pkt.size) { + pkt.stream_index = st->index; + + /* Write the compressed frame to the media file. */ + ret = av_interleaved_write_frame(oc, &pkt); + } else { + ret = 0; + } + } + if (ret != 0) { + fprintf(stderr, "Error while writing video frame: %s\n", av_err2str(ret)); + exit(1); + } + frame_count++; +} + +static void close_video(AVFormatContext *oc, AVStream *st) +{ + avcodec_close(st->codec); + av_free(src_picture.data[0]); + av_free(dst_picture.data[0]); + av_free(frame); +} + +/**************************************************************/ +/* media file output */ + +int main(int argc, char **argv) +{ + const char *filename; + AVOutputFormat *fmt; + AVFormatContext *oc; + AVStream *video_st; + AVCodec *video_codec; + int ret; + + /* Initialize libavcodec, and register all codecs and formats. */ + av_register_all(); + + filename = "/Users/sradomski/Desktop/test.mpg"; + + /* allocate the output media context */ + avformat_alloc_output_context2(&oc, NULL, NULL, filename); + if (!oc) { + printf("Could not deduce output format from file extension: using MPEG.\n"); + avformat_alloc_output_context2(&oc, NULL, "mpeg", filename); + } + if (!oc) { + return 1; + } + fmt = oc->oformat; + + /* Add the audio and video streams using the default format codecs + * and initialize the codecs. */ + video_st = NULL; + + if (fmt->video_codec != AV_CODEC_ID_NONE) { + video_st = add_stream(oc, &video_codec, fmt->video_codec); + } + + /* Now that all the parameters are set, we can open the audio and + * video codecs and allocate the necessary encode buffers. */ + if (video_st) + open_video(oc, video_codec, video_st); + + /* open the output file, if needed */ + if (!(fmt->flags & AVFMT_NOFILE)) { + ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE); + if (ret < 0) { + fprintf(stderr, "Could not open '%s': %s\n", filename, + av_err2str(ret)); + return 1; + } + } + + /* Write the stream header, if any. */ + ret = avformat_write_header(oc, NULL); + if (ret < 0) { + fprintf(stderr, "Error occurred when opening output file: %s\n", + av_err2str(ret)); + return 1; + } + + if (frame) + frame->pts = 0; + for (int i = 0; i < 125; i++) { + write_video_frame(oc, video_st); + frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base); + } + + /* Write the trailer, if any. The trailer must be written before you + * close the CodecContexts open when you wrote the header; otherwise + * av_write_trailer() may try to use memory that was freed on + * av_codec_close(). */ + av_write_trailer(oc); + + /* Close each codec. */ + if (video_st) + close_video(oc, video_st); + + if (!(fmt->flags & AVFMT_NOFILE)) + /* Close the output file. */ + avio_close(oc->pb); + + /* free the stream */ + avformat_free_context(oc); + + return 0; +} diff --git a/test/src/test-url.cpp b/test/src/test-url.cpp index e0f8343..ed892b8 100644 --- a/test/src/test-url.cpp +++ b/test/src/test-url.cpp @@ -47,7 +47,7 @@ int main(int argc, char** argv) { WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
-
+
std::string exeName = argv[0];
exeName = exeName.substr(exeName.find_last_of("\\/") + 1);
|