1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <avmedia/modeltools.hxx>
11 #include <avmedia/mediaitem.hxx>
12 #include "mediamisc.hxx"
14 #include <com/sun/star/embed/ElementModes.hpp>
15 #include <com/sun/star/embed/XTransactedObject.hpp>
16 #include <com/sun/star/document/XStorageBasedDocument.hpp>
17 #include <com/sun/star/embed/XStorage.hpp>
18 #include <com/sun/star/packages/zip/ZipFileAccess.hpp>
19 #include <osl/file.hxx>
20 #include <comphelper/processfactory.hxx>
21 #include <tools/urlobj.hxx>
22 #include <ucbhelper/content.hxx>
23 #include <unotools/tempfile.hxx>
24 #include <unotools/ucbstreamhelper.hxx>
26 #include <boost/property_tree/ptree.hpp>
27 #include <boost/property_tree/json_parser.hpp>
28 #include <boost/optional.hpp>
30 #include <config_features.h>
32 #if HAVE_FEATURE_COLLADA
33 #include <collada_headers.hxx>
34 #include <GLTFAsset.h>
40 using namespace ::com::sun::star
;
41 using namespace boost::property_tree
;
45 #if HAVE_FEATURE_COLLADA
47 static void lcl_UnzipKmz(const OUString
& rSourceURL
, const OUString
& rOutputFolderURL
, OUString
& o_rDaeFileURL
)
49 o_rDaeFileURL
.clear();
50 uno::Reference
<packages::zip::XZipFileAccess2
> xNameAccess
=
51 packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rSourceURL
);
52 uno::Sequence
< OUString
> aNames
= xNameAccess
->getElementNames();
53 for( sal_Int32 i
= 0; i
< aNames
.getLength(); ++i
)
55 const OUString sCopy
= rOutputFolderURL
+ "/" + aNames
[i
];
56 if( aNames
[i
].endsWithIgnoreAsciiCase(".dae") )
57 o_rDaeFileURL
= sCopy
;
59 uno::Reference
<io::XInputStream
> xInputStream(xNameAccess
->getByName(aNames
[i
]), uno::UNO_QUERY
);
61 ::ucbhelper::Content
aCopyContent(sCopy
,
62 uno::Reference
<ucb::XCommandEnvironment
>(),
63 comphelper::getProcessComponentContext());
65 aCopyContent
.writeStream(xInputStream
, true);
70 bool KmzDae2Gltf(const OUString
& rSourceURL
, OUString
& o_rOutput
)
73 const bool bIsDAE
= rSourceURL
.endsWithIgnoreAsciiCase(".dae");
74 const bool bIsKMZ
= rSourceURL
.endsWithIgnoreAsciiCase(".kmz");
75 if( !bIsDAE
&& !bIsKMZ
)
77 SAL_WARN("avmedia.opengl", "KmzDae2Gltf converter got a file with wrong extension " << rSourceURL
);
81 // Create a temporary folder for conversion
83 osl::FileBase::getFileURLFromSystemPath(::utl::TempFile::CreateTempName(), sOutput
);
84 // remove .tmp extension
85 sOutput
= sOutput
.copy(0, sOutput
.getLength()-4);
87 std::shared_ptr
<GLTF::GLTFAsset
> asset(new GLTF::GLTFAsset());
88 asset
->setBundleOutputPath(OUStringToOString( sOutput
, RTL_TEXTENCODING_UTF8
).getStr());
90 // If *.dae file is not in the local file system, then copy it to a temp folder for the conversion
91 OUString sInput
= rSourceURL
;
92 const INetURLObject
aSourceURLObj(rSourceURL
);
93 if( aSourceURLObj
.GetProtocol() != INetProtocol::File
)
97 ::ucbhelper::Content
aSourceContent(rSourceURL
,
98 uno::Reference
<ucb::XCommandEnvironment
>(),
99 comphelper::getProcessComponentContext());
101 const OUString sTarget
= sOutput
+ "/" + GetFilename(rSourceURL
);
102 ::ucbhelper::Content
aTempContent(sTarget
,
103 uno::Reference
<ucb::XCommandEnvironment
>(),
104 comphelper::getProcessComponentContext());
106 aTempContent
.writeStream(aSourceContent
.openStream(), true);
109 catch (const uno::Exception
&)
111 SAL_WARN("avmedia.opengl", "Exception while trying to copy source file to the temp folder for conversion: " << sInput
);
116 asset
->setInputFilePath(OUStringToOString( sInput
, RTL_TEXTENCODING_UTF8
).getStr());
120 OUString sDaeFilePath
;
121 lcl_UnzipKmz(sInput
, sOutput
, sDaeFilePath
);
122 if ( sDaeFilePath
.isEmpty() )
124 SAL_WARN("avmedia.opengl", "Cannot find the file in kmz: " << rSourceURL
);
128 asset
->setInputFilePath(OUStringToOString( sDaeFilePath
, RTL_TEXTENCODING_UTF8
).getStr());
131 GLTF::COLLADA2GLTFWriter
writer(asset
);
133 // Path to the .json file created by COLLADA2GLTFWriter
134 o_rOutput
= sOutput
+ "/" + GetFilename(sOutput
) + ".json";
137 #endif // HAVE_FEATURE_COLLADA
140 static void lcl_EmbedExternals(const OUString
& rSourceURL
, const uno::Reference
<embed::XStorage
>& xSubStorage
, ::ucbhelper::Content
& rContent
)
142 // Create a temp file with which json parser can work.
143 OUString sTempFileURL
;
144 const ::osl::FileBase::RC aErr
=
145 ::osl::FileBase::createTempFile(nullptr, nullptr, &sTempFileURL
);
146 if (aErr
!= ::osl::FileBase::E_None
)
148 SAL_WARN("avmedia.opengl", "Cannot create temp file");
153 // Write json content to the temp file
154 ::ucbhelper::Content
aTempContent(sTempFileURL
,
155 uno::Reference
<ucb::XCommandEnvironment
>(),
156 comphelper::getProcessComponentContext());
157 aTempContent
.writeStream(rContent
.openStream(), true);
159 catch (uno::Exception
const& e
)
161 SAL_WARN("avmedia.opengl", "Exception: '" << e
.Message
<< "'");
165 // Convert URL to a file path for loading
166 const INetURLObject
aURLObj(sTempFileURL
);
167 std::string sUrl
= OUStringToOString( aURLObj
.getFSysPath(FSysStyle::Detect
), RTL_TEXTENCODING_UTF8
).getStr();
169 // Parse json, read externals' URI and modify this relative URI's so they remain valid in the new context.
170 std::vector
<std::string
> vExternals
;
174 json_parser::read_json( sUrl
, aTree
);
176 // Buffers for geometry and animations
177 for( ptree::value_type
&rVal
: aTree
.get_child("buffers") )
179 const std::string
sBufferUri(rVal
.second
.get
<std::string
>("path"));
180 vExternals
.push_back(sBufferUri
);
181 // Change path: make it contain only a file name
182 aTree
.put("buffers." + rVal
.first
+ ".path.",sBufferUri
.substr(sBufferUri
.find_last_of('/')+1));
184 // Images for textures
185 boost::optional
< ptree
& > aImages
= aTree
.get_child_optional("images");
188 for( ptree::value_type
&rVal
: aImages
.get() )
190 const std::string
sImageUri(rVal
.second
.get
<std::string
>("path"));
191 if( !sImageUri
.empty() )
193 vExternals
.push_back(sImageUri
);
194 // Change path: make it contain only a file name
195 aTree
.put("images." + rVal
.first
+ ".path.",sImageUri
.substr(sImageUri
.find_last_of('/')+1));
199 // Shaders (contains names only)
200 for( ptree::value_type
&rVal
: aTree
.get_child("programs") )
202 vExternals
.push_back(rVal
.second
.get
<std::string
>("fragmentShader") + ".glsl");
203 vExternals
.push_back(rVal
.second
.get
<std::string
>("vertexShader") + ".glsl");
206 // Write out modified json
207 json_parser::write_json( sUrl
, aTree
);
209 catch ( boost::exception
const& )
211 SAL_WARN("avmedia.opengl", "Exception while parsing *.json file");
215 // Reload json with modified path to external resources
216 rContent
= ::ucbhelper::Content(sTempFileURL
,
217 uno::Reference
<ucb::XCommandEnvironment
>(),
218 comphelper::getProcessComponentContext());
220 // Store all external files next to the json file
221 for( std::vector
<std::string
>::iterator aCIter
= vExternals
.begin(); aCIter
!= vExternals
.end(); ++aCIter
)
223 const OUString sAbsURL
= INetURLObject::GetAbsURL(rSourceURL
,OUString::createFromAscii(aCIter
->c_str()));
225 ::ucbhelper::Content
aContent(sAbsURL
,
226 uno::Reference
<ucb::XCommandEnvironment
>(),
227 comphelper::getProcessComponentContext());
229 uno::Reference
<io::XStream
> const xStream(
230 CreateStream(xSubStorage
, GetFilename(sAbsURL
)), uno::UNO_SET_THROW
);
231 uno::Reference
<io::XOutputStream
> const xOutStream(
232 xStream
->getOutputStream(), uno::UNO_SET_THROW
);
234 if (!aContent
.openStream(xOutStream
))
236 SAL_WARN("avmedia.opengl", "openStream to storage failed");
243 bool Embed3DModel( const uno::Reference
<frame::XModel
>& xModel
,
244 const OUString
& rSourceURL
, OUString
& o_rEmbeddedURL
)
246 OUString sSource
= rSourceURL
;
248 #if HAVE_FEATURE_COLLADA
249 if( !rSourceURL
.endsWithIgnoreAsciiCase(".json") )
250 KmzDae2Gltf(rSourceURL
, sSource
);
255 ::ucbhelper::Content
aSourceContent(sSource
,
256 uno::Reference
<ucb::XCommandEnvironment
>(),
257 comphelper::getProcessComponentContext());
260 uno::Reference
<document::XStorageBasedDocument
> const xSBD(xModel
,
261 uno::UNO_QUERY_THROW
);
262 uno::Reference
<embed::XStorage
> const xStorage(
263 xSBD
->getDocumentStorage(), uno::UNO_QUERY_THROW
);
266 const OUString
sModel("Models");
267 uno::Reference
<embed::XStorage
> const xModelStorage(
268 xStorage
->openStorageElement(sModel
, embed::ElementModes::WRITE
));
270 // Own storage of the corresponding model
271 const OUString
sFilename(GetFilename(sSource
));
272 const OUString
sGLTFDir(sFilename
.copy(0,sFilename
.lastIndexOf('.')));
273 uno::Reference
<embed::XStorage
> const xSubStorage(
274 xModelStorage
->openStorageElement(sGLTFDir
, embed::ElementModes::WRITE
));
276 // Embed external resources
277 lcl_EmbedExternals(sSource
, xSubStorage
, aSourceContent
);
279 // Save model file (.json)
280 uno::Reference
<io::XStream
> const xStream(
281 CreateStream(xSubStorage
, sFilename
), uno::UNO_SET_THROW
);
282 uno::Reference
<io::XOutputStream
> const xOutStream(
283 xStream
->getOutputStream(), uno::UNO_SET_THROW
);
285 if (!aSourceContent
.openStream(xOutStream
))
287 SAL_WARN("avmedia.opengl", "openStream to storage failed");
291 const uno::Reference
<embed::XTransactedObject
> xSubTransaction(xSubStorage
, uno::UNO_QUERY
);
292 if (xSubTransaction
.is())
294 xSubTransaction
->commit();
296 const uno::Reference
<embed::XTransactedObject
> xModelTransaction(xModelStorage
, uno::UNO_QUERY
);
297 if (xModelTransaction
.is())
299 xModelTransaction
->commit();
301 const uno::Reference
<embed::XTransactedObject
> xTransaction(xStorage
, uno::UNO_QUERY
);
302 if (xTransaction
.is())
304 xTransaction
->commit();
307 o_rEmbeddedURL
= "vnd.sun.star.Package:" + sModel
+ "/" + sGLTFDir
+ "/" + sFilename
;
310 catch (uno::Exception
const&)
312 SAL_WARN("avmedia.opengl", "Exception while trying to embed model");
318 bool IsModel(const OUString
& rMimeType
)
320 return rMimeType
== AVMEDIA_MIMETYPE_JSON
;
323 } // namespace avmedia
325 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */