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