62633dfe02
Change-Id: I7168d44dab8e6a8e37bb7920d744ff32f5e52907 Reviewed-on: https://gerrit.libreoffice.org/24019 Tested-by: Jenkins <ci@libreoffice.org> Reviewed-by: Noel Grandin <noelgrandin@gmail.com>
325 lines
11 KiB
C++
325 lines
11 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include <avmedia/modeltools.hxx>
|
|
#include <avmedia/mediaitem.hxx>
|
|
#include "mediamisc.hxx"
|
|
|
|
#include <com/sun/star/embed/ElementModes.hpp>
|
|
#include <com/sun/star/embed/XTransactedObject.hpp>
|
|
#include <com/sun/star/document/XStorageBasedDocument.hpp>
|
|
#include <com/sun/star/embed/XStorage.hpp>
|
|
#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
|
|
#include <osl/file.hxx>
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <tools/urlobj.hxx>
|
|
#include <ucbhelper/content.hxx>
|
|
#include <unotools/tempfile.hxx>
|
|
#include <unotools/ucbstreamhelper.hxx>
|
|
|
|
#include <boost/property_tree/ptree.hpp>
|
|
#include <boost/property_tree/json_parser.hpp>
|
|
#include <boost/optional.hpp>
|
|
|
|
#include <config_features.h>
|
|
|
|
#if HAVE_FEATURE_COLLADA
|
|
#include <collada_headers.hxx>
|
|
#include <GLTFAsset.h>
|
|
#endif
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace boost::property_tree;
|
|
|
|
namespace avmedia {
|
|
|
|
#if HAVE_FEATURE_COLLADA
|
|
|
|
static void lcl_UnzipKmz(const OUString& rSourceURL, const OUString& rOutputFolderURL, OUString& o_rDaeFileURL)
|
|
{
|
|
o_rDaeFileURL.clear();
|
|
uno::Reference<packages::zip::XZipFileAccess2> xNameAccess =
|
|
packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rSourceURL);
|
|
uno::Sequence< OUString > aNames = xNameAccess->getElementNames();
|
|
for( sal_Int32 i = 0; i < aNames.getLength(); ++i )
|
|
{
|
|
const OUString sCopy = rOutputFolderURL + "/" + aNames[i];
|
|
if( aNames[i].endsWithIgnoreAsciiCase(".dae") )
|
|
o_rDaeFileURL = sCopy;
|
|
|
|
uno::Reference<io::XInputStream> xInputStream(xNameAccess->getByName(aNames[i]), uno::UNO_QUERY);
|
|
|
|
::ucbhelper::Content aCopyContent(sCopy,
|
|
uno::Reference<ucb::XCommandEnvironment>(),
|
|
comphelper::getProcessComponentContext());
|
|
|
|
aCopyContent.writeStream(xInputStream, true);
|
|
}
|
|
}
|
|
|
|
|
|
bool KmzDae2Gltf(const OUString& rSourceURL, OUString& o_rOutput)
|
|
{
|
|
o_rOutput.clear();
|
|
const bool bIsDAE = rSourceURL.endsWithIgnoreAsciiCase(".dae");
|
|
const bool bIsKMZ = rSourceURL.endsWithIgnoreAsciiCase(".kmz");
|
|
if( !bIsDAE && !bIsKMZ )
|
|
{
|
|
SAL_WARN("avmedia.opengl", "KmzDae2Gltf converter got a file with wrong extension\n" << rSourceURL);
|
|
return false;
|
|
}
|
|
|
|
// Create a temporary folder for conversion
|
|
OUString sOutput;
|
|
osl::FileBase::getFileURLFromSystemPath(::utl::TempFile::CreateTempName(), sOutput);
|
|
// remove .tmp extension
|
|
sOutput = sOutput.copy(0, sOutput.getLength()-4);
|
|
|
|
std::shared_ptr <GLTF::GLTFAsset> asset(new GLTF::GLTFAsset());
|
|
asset->setBundleOutputPath(OUStringToOString( sOutput, RTL_TEXTENCODING_UTF8 ).getStr());
|
|
|
|
// If *.dae file is not in the local file system, then copy it to a temp folder for the conversion
|
|
OUString sInput = rSourceURL;
|
|
const INetURLObject aSourceURLObj(rSourceURL);
|
|
if( aSourceURLObj.GetProtocol() != INetProtocol::File )
|
|
{
|
|
try
|
|
{
|
|
::ucbhelper::Content aSourceContent(rSourceURL,
|
|
uno::Reference<ucb::XCommandEnvironment>(),
|
|
comphelper::getProcessComponentContext());
|
|
|
|
const OUString sTarget = sOutput + "/" + GetFilename(rSourceURL);
|
|
::ucbhelper::Content aTempContent(sTarget,
|
|
uno::Reference<ucb::XCommandEnvironment>(),
|
|
comphelper::getProcessComponentContext());
|
|
|
|
aTempContent.writeStream(aSourceContent.openStream(), true);
|
|
sInput = sTarget;
|
|
}
|
|
catch (const uno::Exception&)
|
|
{
|
|
SAL_WARN("avmedia.opengl", "Exception while trying to copy source file to the temp folder for conversion:\n" << sInput);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
asset->setInputFilePath(OUStringToOString( sInput, RTL_TEXTENCODING_UTF8 ).getStr());
|
|
|
|
if (bIsKMZ)
|
|
{
|
|
OUString sDaeFilePath;
|
|
lcl_UnzipKmz(sInput, sOutput, sDaeFilePath);
|
|
if ( sDaeFilePath.isEmpty() )
|
|
{
|
|
SAL_WARN("avmedia.opengl", "Cannot find dae file in kmz:\n" << rSourceURL);
|
|
return false;
|
|
}
|
|
|
|
asset->setInputFilePath(OUStringToOString( sDaeFilePath, RTL_TEXTENCODING_UTF8 ).getStr());
|
|
}
|
|
|
|
GLTF::COLLADA2GLTFWriter writer(asset);
|
|
writer.write();
|
|
// Path to the .json file created by COLLADA2GLTFWriter
|
|
o_rOutput = sOutput + "/" + GetFilename(sOutput) + ".json";
|
|
return true;
|
|
}
|
|
#endif // HAVE_FEATURE_COLLADA
|
|
|
|
|
|
static void lcl_EmbedExternals(const OUString& rSourceURL, const uno::Reference<embed::XStorage>& xSubStorage, ::ucbhelper::Content& rContent)
|
|
{
|
|
// Create a temp file with which json parser can work.
|
|
OUString sTempFileURL;
|
|
const ::osl::FileBase::RC aErr =
|
|
::osl::FileBase::createTempFile(nullptr, nullptr, &sTempFileURL);
|
|
if (::osl::FileBase::E_None != aErr)
|
|
{
|
|
SAL_WARN("avmedia.opengl", "Cannot create temp file");
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
// Write json content to the temp file
|
|
::ucbhelper::Content aTempContent(sTempFileURL,
|
|
uno::Reference<ucb::XCommandEnvironment>(),
|
|
comphelper::getProcessComponentContext());
|
|
aTempContent.writeStream(rContent.openStream(), true);
|
|
}
|
|
catch (uno::Exception const& e)
|
|
{
|
|
SAL_WARN("avmedia.opengl", "Exception: '" << e.Message << "'");
|
|
return;
|
|
}
|
|
|
|
// Convert URL to a file path for loading
|
|
const INetURLObject aURLObj(sTempFileURL);
|
|
std::string sUrl = OUStringToOString( aURLObj.getFSysPath(INetURLObject::FSYS_DETECT), RTL_TEXTENCODING_UTF8 ).getStr();
|
|
|
|
// Parse json, read externals' URI and modify this relative URI's so they remain valid in the new context.
|
|
std::vector<std::string> vExternals;
|
|
ptree aTree;
|
|
try
|
|
{
|
|
json_parser::read_json( sUrl, aTree );
|
|
|
|
// Buffers for geometry and animations
|
|
for( ptree::value_type &rVal : aTree.get_child("buffers") )
|
|
{
|
|
const std::string sBufferUri(rVal.second.get<std::string>("path"));
|
|
vExternals.push_back(sBufferUri);
|
|
// Change path: make it contain only a file name
|
|
aTree.put("buffers." + rVal.first + ".path.",sBufferUri.substr(sBufferUri.find_last_of('/')+1));
|
|
}
|
|
// Images for textures
|
|
boost::optional< ptree& > aImages = aTree.get_child_optional("images");
|
|
if( aImages )
|
|
{
|
|
for( ptree::value_type &rVal : aImages.get() )
|
|
{
|
|
const std::string sImageUri(rVal.second.get<std::string>("path"));
|
|
if( !sImageUri.empty() )
|
|
{
|
|
vExternals.push_back(sImageUri);
|
|
// Change path: make it contain only a file name
|
|
aTree.put("images." + rVal.first + ".path.",sImageUri.substr(sImageUri.find_last_of('/')+1));
|
|
}
|
|
}
|
|
}
|
|
// Shaders (contains names only)
|
|
for( ptree::value_type &rVal : aTree.get_child("programs") )
|
|
{
|
|
vExternals.push_back(rVal.second.get<std::string>("fragmentShader") + ".glsl");
|
|
vExternals.push_back(rVal.second.get<std::string>("vertexShader") + ".glsl");
|
|
}
|
|
|
|
// Write out modified json
|
|
json_parser::write_json( sUrl, aTree );
|
|
}
|
|
catch ( boost::exception const& )
|
|
{
|
|
SAL_WARN("avmedia.opengl", "Exception while parsing *.json file");
|
|
return;
|
|
}
|
|
|
|
// Reload json with modified path to external resources
|
|
rContent = ::ucbhelper::Content(sTempFileURL,
|
|
uno::Reference<ucb::XCommandEnvironment>(),
|
|
comphelper::getProcessComponentContext());
|
|
|
|
// Store all external files next to the json file
|
|
for( std::vector<std::string>::iterator aCIter = vExternals.begin(); aCIter != vExternals.end(); ++aCIter )
|
|
{
|
|
const OUString sAbsURL = INetURLObject::GetAbsURL(rSourceURL,OUString::createFromAscii(aCIter->c_str()));
|
|
|
|
::ucbhelper::Content aContent(sAbsURL,
|
|
uno::Reference<ucb::XCommandEnvironment>(),
|
|
comphelper::getProcessComponentContext());
|
|
|
|
uno::Reference<io::XStream> const xStream(
|
|
CreateStream(xSubStorage, GetFilename(sAbsURL)), uno::UNO_SET_THROW);
|
|
uno::Reference<io::XOutputStream> const xOutStream(
|
|
xStream->getOutputStream(), uno::UNO_SET_THROW);
|
|
|
|
if (!aContent.openStream(xOutStream))
|
|
{
|
|
SAL_WARN("avmedia.opengl", "openStream to storage failed");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Embed3DModel( const uno::Reference<frame::XModel>& xModel,
|
|
const OUString& rSourceURL, OUString& o_rEmbeddedURL)
|
|
{
|
|
OUString sSource = rSourceURL;
|
|
|
|
#if HAVE_FEATURE_COLLADA
|
|
if( !rSourceURL.endsWithIgnoreAsciiCase(".json") )
|
|
KmzDae2Gltf(rSourceURL, sSource);
|
|
#endif
|
|
|
|
try
|
|
{
|
|
::ucbhelper::Content aSourceContent(sSource,
|
|
uno::Reference<ucb::XCommandEnvironment>(),
|
|
comphelper::getProcessComponentContext());
|
|
|
|
// Base storage
|
|
uno::Reference<document::XStorageBasedDocument> const xSBD(xModel,
|
|
uno::UNO_QUERY_THROW);
|
|
uno::Reference<embed::XStorage> const xStorage(
|
|
xSBD->getDocumentStorage(), uno::UNO_QUERY_THROW);
|
|
|
|
// Model storage
|
|
const OUString sModel("Models");
|
|
uno::Reference<embed::XStorage> const xModelStorage(
|
|
xStorage->openStorageElement(sModel, embed::ElementModes::WRITE));
|
|
|
|
// Own storage of the corresponding model
|
|
const OUString sFilename(GetFilename(sSource));
|
|
const OUString sGLTFDir(sFilename.copy(0,sFilename.lastIndexOf('.')));
|
|
uno::Reference<embed::XStorage> const xSubStorage(
|
|
xModelStorage->openStorageElement(sGLTFDir, embed::ElementModes::WRITE));
|
|
|
|
// Embed external resources
|
|
lcl_EmbedExternals(sSource, xSubStorage, aSourceContent);
|
|
|
|
// Save model file (.json)
|
|
uno::Reference<io::XStream> const xStream(
|
|
CreateStream(xSubStorage, sFilename), uno::UNO_SET_THROW);
|
|
uno::Reference<io::XOutputStream> const xOutStream(
|
|
xStream->getOutputStream(), uno::UNO_SET_THROW);
|
|
|
|
if (!aSourceContent.openStream(xOutStream))
|
|
{
|
|
SAL_WARN("avmedia.opengl", "openStream to storage failed");
|
|
return false;
|
|
}
|
|
|
|
const uno::Reference<embed::XTransactedObject> xSubTransaction(xSubStorage, uno::UNO_QUERY);
|
|
if (xSubTransaction.is())
|
|
{
|
|
xSubTransaction->commit();
|
|
}
|
|
const uno::Reference<embed::XTransactedObject> xModelTransaction(xModelStorage, uno::UNO_QUERY);
|
|
if (xModelTransaction.is())
|
|
{
|
|
xModelTransaction->commit();
|
|
}
|
|
const uno::Reference<embed::XTransactedObject> xTransaction(xStorage, uno::UNO_QUERY);
|
|
if (xTransaction.is())
|
|
{
|
|
xTransaction->commit();
|
|
}
|
|
|
|
o_rEmbeddedURL = "vnd.sun.star.Package:" + sModel + "/" + sGLTFDir + "/" + sFilename;
|
|
return true;
|
|
}
|
|
catch (uno::Exception const&)
|
|
{
|
|
SAL_WARN("avmedia.opengl", "Exception while trying to embed model");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool IsModel(const OUString& rMimeType)
|
|
{
|
|
return rMimeType == AVMEDIA_MIMETYPE_JSON;
|
|
}
|
|
|
|
} // namespace avmedia
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|