Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : */
9 :
10 : #include <avmedia/modeltools.hxx>
11 : #include <avmedia/mediaitem.hxx>
12 : #include "mediamisc.hxx"
13 :
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/localfilehelper.hxx>
24 : #include <unotools/tempfile.hxx>
25 : #include <unotools/ucbstreamhelper.hxx>
26 :
27 : #include <boost/property_tree/ptree.hpp>
28 : #include <boost/property_tree/json_parser.hpp>
29 : #include <boost/optional.hpp>
30 :
31 : #include <config_features.h>
32 :
33 : #if HAVE_FEATURE_COLLADA
34 : #include <collada_headers.hxx>
35 : #include <GLTFAsset.h>
36 : #endif
37 :
38 : #include <string>
39 : #include <vector>
40 :
41 : using namespace ::com::sun::star;
42 : using namespace boost::property_tree;
43 :
44 : namespace avmedia {
45 :
46 : #if HAVE_FEATURE_COLLADA
47 :
48 0 : static void lcl_UnzipKmz(const OUString& rSourceURL, const OUString& rOutputFolderURL, OUString& o_rDaeFileURL)
49 : {
50 0 : o_rDaeFileURL.clear();
51 : uno::Reference<packages::zip::XZipFileAccess2> xNameAccess =
52 0 : packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rSourceURL);
53 0 : uno::Sequence< OUString > aNames = xNameAccess->getElementNames();
54 0 : for( sal_Int32 i = 0; i < aNames.getLength(); ++i )
55 : {
56 0 : const OUString sCopy = rOutputFolderURL + "/" + aNames[i];
57 0 : if( aNames[i].endsWithIgnoreAsciiCase(".dae") )
58 0 : o_rDaeFileURL = sCopy;
59 :
60 0 : uno::Reference<io::XInputStream> xInputStream(xNameAccess->getByName(aNames[i]), uno::UNO_QUERY);
61 :
62 : ::ucbhelper::Content aCopyContent(sCopy,
63 : uno::Reference<ucb::XCommandEnvironment>(),
64 0 : comphelper::getProcessComponentContext());
65 :
66 0 : aCopyContent.writeStream(xInputStream, true);
67 0 : }
68 0 : }
69 :
70 0 : bool KmzDae2Gltf(const OUString& rSourceURL, OUString& o_rOutput)
71 : {
72 0 : o_rOutput.clear();
73 0 : const bool bIsDAE = rSourceURL.endsWithIgnoreAsciiCase(".dae");
74 0 : const bool bIsKMZ = rSourceURL.endsWithIgnoreAsciiCase(".kmz");
75 0 : if( !bIsDAE && !bIsKMZ )
76 : {
77 : SAL_WARN("avmedia.opengl", "KmzDae2Gltf converter got a file with wrong extension\n" << rSourceURL);
78 0 : return false;
79 : }
80 :
81 : // Create a temporary folder for conversion
82 0 : OUString sOutput;
83 0 : ::utl::LocalFileHelper::ConvertPhysicalNameToURL(::utl::TempFile::CreateTempName(), sOutput);
84 : // remove .tmp extension
85 0 : sOutput = sOutput.copy(0, sOutput.getLength()-4);
86 :
87 0 : std::shared_ptr <GLTF::GLTFAsset> asset(new GLTF::GLTFAsset());
88 0 : asset->setBundleOutputPath(OUStringToOString( sOutput, RTL_TEXTENCODING_UTF8 ).getStr());
89 :
90 : // If *.dae file is not in the local file system, then copy it to a temp folder for the conversion
91 0 : OUString sInput = rSourceURL;
92 0 : const INetURLObject aSourceURLObj(rSourceURL);
93 0 : if( aSourceURLObj.GetProtocol() != INetProtocol::File )
94 : {
95 : try
96 : {
97 : ::ucbhelper::Content aSourceContent(rSourceURL,
98 : uno::Reference<ucb::XCommandEnvironment>(),
99 0 : comphelper::getProcessComponentContext());
100 :
101 0 : const OUString sTarget = sOutput + "/" + GetFilename(rSourceURL);
102 : ::ucbhelper::Content aTempContent(sTarget,
103 : uno::Reference<ucb::XCommandEnvironment>(),
104 0 : comphelper::getProcessComponentContext());
105 :
106 0 : aTempContent.writeStream(aSourceContent.openStream(), true);
107 0 : sInput = sTarget;
108 : }
109 0 : catch (const uno::Exception&)
110 : {
111 : SAL_WARN("avmedia.opengl", "Exception while trying to copy source file to the temp folder for conversion:\n" << sInput);
112 0 : return false;
113 : }
114 : }
115 :
116 0 : asset->setInputFilePath(OUStringToOString( sInput, RTL_TEXTENCODING_UTF8 ).getStr());
117 :
118 0 : if (bIsKMZ)
119 : {
120 0 : OUString sDaeFilePath;
121 0 : lcl_UnzipKmz(sInput, sOutput, sDaeFilePath);
122 0 : if ( sDaeFilePath.isEmpty() )
123 : {
124 : SAL_WARN("avmedia.opengl", "Cannot find dae file in kmz:\n" << rSourceURL);
125 0 : return false;
126 : }
127 :
128 0 : asset->setInputFilePath(OUStringToOString( sDaeFilePath, RTL_TEXTENCODING_UTF8 ).getStr());
129 : }
130 :
131 0 : GLTF::COLLADA2GLTFWriter writer(asset);
132 0 : writer.write();
133 : // Path to the .json file created by COLLADA2GLTFWriter
134 0 : o_rOutput = sOutput + "/" + GetFilename(sOutput) + ".json";
135 0 : return true;
136 : }
137 : #endif
138 :
139 0 : static void lcl_EmbedExternals(const OUString& rSourceURL, uno::Reference<embed::XStorage> xSubStorage, ::ucbhelper::Content& rContent)
140 : {
141 : // Create a temp file with which json parser can work.
142 0 : OUString sTempFileURL;
143 : const ::osl::FileBase::RC aErr =
144 0 : ::osl::FileBase::createTempFile(0, 0, &sTempFileURL);
145 0 : if (::osl::FileBase::E_None != aErr)
146 : {
147 : SAL_WARN("avmedia.opengl", "Cannot create temp file");
148 0 : return;
149 : }
150 : try
151 : {
152 : // Write json content to the temp file
153 : ::ucbhelper::Content aTempContent(sTempFileURL,
154 : uno::Reference<ucb::XCommandEnvironment>(),
155 0 : comphelper::getProcessComponentContext());
156 0 : aTempContent.writeStream(rContent.openStream(), true);
157 : }
158 0 : catch (uno::Exception const& e)
159 : {
160 : SAL_WARN("avmedia.opengl", "Exception: '" << e.Message << "'");
161 0 : return;
162 : }
163 :
164 : // Convert URL to a file path for loading
165 0 : const INetURLObject aURLObj(sTempFileURL);
166 0 : std::string sUrl = OUStringToOString( aURLObj.getFSysPath(INetURLObject::FSYS_DETECT), RTL_TEXTENCODING_UTF8 ).getStr();
167 :
168 : // Parse json, read externals' URI and modify this relative URI's so they remain valid in the new context.
169 0 : std::vector<std::string> vExternals;
170 0 : ptree aTree;
171 : try
172 : {
173 0 : json_parser::read_json( sUrl, aTree );
174 :
175 : // Buffers for geometry and animations
176 0 : for( ptree::value_type &rVal : aTree.get_child("buffers") )
177 : {
178 0 : const std::string sBufferUri(rVal.second.get<std::string>("path"));
179 0 : vExternals.push_back(sBufferUri);
180 : // Change path: make it contain only a file name
181 0 : aTree.put("buffers." + rVal.first + ".path.",sBufferUri.substr(sBufferUri.find_last_of('/')+1));
182 0 : }
183 : // Images for textures
184 0 : boost::optional< ptree& > aImages = aTree.get_child_optional("images");
185 0 : if( aImages )
186 : {
187 0 : for( ptree::value_type &rVal : aImages.get() )
188 : {
189 0 : const std::string sImageUri(rVal.second.get<std::string>("path"));
190 0 : if( !sImageUri.empty() )
191 : {
192 0 : vExternals.push_back(sImageUri);
193 : // Change path: make it contain only a file name
194 0 : aTree.put("images." + rVal.first + ".path.",sImageUri.substr(sImageUri.find_last_of('/')+1));
195 : }
196 0 : }
197 : }
198 : // Shaders (contains names only)
199 0 : for( ptree::value_type &rVal : aTree.get_child("programs") )
200 : {
201 0 : vExternals.push_back(rVal.second.get<std::string>("fragmentShader") + ".glsl");
202 0 : vExternals.push_back(rVal.second.get<std::string>("vertexShader") + ".glsl");
203 : }
204 :
205 : // Write out modified json
206 0 : json_parser::write_json( sUrl, aTree );
207 : }
208 0 : catch ( boost::exception const& )
209 : {
210 : SAL_WARN("avmedia.opengl", "Exception while parsing *.json file");
211 0 : return;
212 : }
213 :
214 : // Reload json with modified path to external resources
215 0 : rContent = ::ucbhelper::Content(sTempFileURL,
216 : uno::Reference<ucb::XCommandEnvironment>(),
217 0 : comphelper::getProcessComponentContext());
218 :
219 : // Store all external files next to the json file
220 0 : for( std::vector<std::string>::iterator aCIter = vExternals.begin(); aCIter != vExternals.end(); ++aCIter )
221 : {
222 0 : const OUString sAbsURL = INetURLObject::GetAbsURL(rSourceURL,OUString::createFromAscii(aCIter->c_str()));
223 :
224 : ::ucbhelper::Content aContent(sAbsURL,
225 : uno::Reference<ucb::XCommandEnvironment>(),
226 0 : comphelper::getProcessComponentContext());
227 :
228 : uno::Reference<io::XStream> const xStream(
229 0 : CreateStream(xSubStorage, GetFilename(sAbsURL)), uno::UNO_SET_THROW);
230 : uno::Reference<io::XOutputStream> const xOutStream(
231 0 : xStream->getOutputStream(), uno::UNO_SET_THROW);
232 :
233 0 : if (!aContent.openStream(xOutStream))
234 : {
235 : SAL_WARN("avmedia.opengl", "openStream to storage failed");
236 0 : return;
237 : }
238 0 : }
239 : }
240 :
241 0 : bool Embed3DModel( const uno::Reference<frame::XModel>& xModel,
242 : const OUString& rSourceURL, OUString& o_rEmbeddedURL)
243 : {
244 0 : OUString sSource = rSourceURL;
245 :
246 : #if HAVE_FEATURE_COLLADA
247 0 : if( !rSourceURL.endsWithIgnoreAsciiCase(".json") )
248 0 : KmzDae2Gltf(rSourceURL, sSource);
249 : #endif
250 :
251 : try
252 : {
253 : ::ucbhelper::Content aSourceContent(sSource,
254 : uno::Reference<ucb::XCommandEnvironment>(),
255 0 : comphelper::getProcessComponentContext());
256 :
257 : // Base storage
258 : uno::Reference<document::XStorageBasedDocument> const xSBD(xModel,
259 0 : uno::UNO_QUERY_THROW);
260 : uno::Reference<embed::XStorage> const xStorage(
261 0 : xSBD->getDocumentStorage(), uno::UNO_QUERY_THROW);
262 :
263 : // Model storage
264 0 : const OUString sModel("Models");
265 : uno::Reference<embed::XStorage> const xModelStorage(
266 0 : xStorage->openStorageElement(sModel, embed::ElementModes::WRITE));
267 :
268 : // Own storage of the corresponding model
269 0 : const OUString sFilename(GetFilename(sSource));
270 0 : const OUString sGLTFDir(sFilename.copy(0,sFilename.lastIndexOf('.')));
271 : uno::Reference<embed::XStorage> const xSubStorage(
272 0 : xModelStorage->openStorageElement(sGLTFDir, embed::ElementModes::WRITE));
273 :
274 : // Embed external resources
275 0 : lcl_EmbedExternals(sSource, xSubStorage, aSourceContent);
276 :
277 : // Save model file (.json)
278 : uno::Reference<io::XStream> const xStream(
279 0 : CreateStream(xSubStorage, sFilename), uno::UNO_SET_THROW);
280 : uno::Reference<io::XOutputStream> const xOutStream(
281 0 : xStream->getOutputStream(), uno::UNO_SET_THROW);
282 :
283 0 : if (!aSourceContent.openStream(xOutStream))
284 : {
285 : SAL_WARN("avmedia.opengl", "openStream to storage failed");
286 0 : return false;
287 : }
288 :
289 0 : const uno::Reference<embed::XTransactedObject> xSubTransaction(xSubStorage, uno::UNO_QUERY);
290 0 : if (xSubTransaction.is())
291 : {
292 0 : xSubTransaction->commit();
293 : }
294 0 : const uno::Reference<embed::XTransactedObject> xModelTransaction(xModelStorage, uno::UNO_QUERY);
295 0 : if (xModelTransaction.is())
296 : {
297 0 : xModelTransaction->commit();
298 : }
299 0 : const uno::Reference<embed::XTransactedObject> xTransaction(xStorage, uno::UNO_QUERY);
300 0 : if (xTransaction.is())
301 : {
302 0 : xTransaction->commit();
303 : }
304 :
305 0 : o_rEmbeddedURL = "vnd.sun.star.Package:" + sModel + "/" + sGLTFDir + "/" + sFilename;
306 0 : return true;
307 : }
308 0 : catch (uno::Exception const&)
309 : {
310 : SAL_WARN("avmedia.opengl", "Exception while trying to embed model");
311 : }
312 0 : return false;
313 : }
314 :
315 0 : bool IsModel(const OUString& rMimeType)
316 : {
317 0 : return rMimeType == AVMEDIA_MIMETYPE_JSON;
318 : }
319 :
320 435 : } // namespace avemdia
321 :
322 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|