1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 from mediagoblin
import mg_globals
as mgg
25 from mediagoblin
.processing
import (
26 FilenameBuilder
, MediaProcessor
,
27 ProcessingManager
, request_from_args
,
28 get_process_filename
, store_public
,
31 from mediagoblin
.media_types
.stl
import model_loader
34 _log
= logging
.getLogger(__name__
)
35 SUPPORTED_FILETYPES
= ['stl', 'obj']
36 MEDIA_TYPE
= 'mediagoblin.media_types.stl'
38 BLEND_FILE
= pkg_resources
.resource_filename(
39 'mediagoblin.media_types.stl',
42 'blender_render.blend'))
43 BLEND_SCRIPT
= pkg_resources
.resource_filename(
44 'mediagoblin.media_types.stl',
50 def sniff_handler(media_file
, filename
):
51 _log
.info('Sniffing {0}'.format(MEDIA_TYPE
))
53 name
, ext
= os
.path
.splitext(filename
)
54 clean_ext
= ext
[1:].lower()
56 if clean_ext
in SUPPORTED_FILETYPES
:
57 _log
.info('Found file extension in supported filetypes')
60 _log
.debug('Media present, extension not found in {0}'.format(
66 def blender_render(config
):
68 Called to prerender a model.
70 env
= {"RENDER_SETUP" : json
.dumps(config
), "DISPLAY":":0"}
79 class CommonStlProcessor(MediaProcessor
):
81 Provides a common base for various stl processing steps
83 acceptable_files
= ['original']
85 def common_setup(self
):
86 # Pull down and set up the processing file
87 self
.process_filename
= get_process_filename(
88 self
.entry
, self
.workbench
, self
.acceptable_files
)
89 self
.name_builder
= FilenameBuilder(self
.process_filename
)
96 ext
= self
.name_builder
.ext
[1:]
103 def _set_model(self
):
105 Attempt to parse the model file and divine some useful
106 information about it.
108 with
open(self
.process_filename
, 'rb') as model_file
:
109 self
.model
= model_loader
.auto_detect(model_file
, self
.ext
)
111 def _set_greatest(self
):
112 greatest
= [self
.model
.width
, self
.model
.height
, self
.model
.depth
]
114 self
.greatest
= greatest
[-1]
116 def copy_original(self
):
118 self
.entry
, self
.process_filename
,
119 self
.name_builder
.fill('{basename}{ext}'))
121 def _snap(self
, keyname
, name
, camera
, size
, project
="ORTHO"):
122 filename
= self
.name_builder
.fill(name
)
123 workbench_path
= self
.workbench
.joinpath(filename
)
125 "model_path": self
.process_filename
,
126 "model_ext": self
.ext
,
127 "camera_coord": camera
,
128 "camera_focus": self
.model
.average
,
129 "camera_clip": self
.greatest
*10,
130 "greatest": self
.greatest
,
131 "projection": project
,
134 "out_file": workbench_path
,
138 # make sure the image rendered to the workbench path
139 assert os
.path
.exists(workbench_path
)
142 store_public(self
.entry
, keyname
, workbench_path
, filename
)
144 def _skip_processing(self
, keyname
, **kwargs
):
145 file_metadata
= self
.entry
.get_file_metadata(keyname
)
147 if not file_metadata
:
151 if keyname
== 'thumb':
152 if kwargs
.get('thumb_size') != file_metadata
.get('thumb_size'):
155 if kwargs
.get('size') != file_metadata
.get('size'):
160 def generate_thumb(self
, thumb_size
=None):
162 thumb_size
= (mgg
.global_config
['media:thumb']['max_width'],
163 mgg
.global_config
['media:thumb']['max_height'])
165 if self
._skip
_processing
('thumb', thumb_size
=thumb_size
):
170 "{basename}.thumb.jpg",
171 [0, self
.greatest
*-1.5, self
.greatest
],
175 self
.entry
.set_file_metadata('thumb', thumb_size
=thumb_size
)
177 def generate_perspective(self
, size
=None):
179 size
= (mgg
.global_config
['media:medium']['max_width'],
180 mgg
.global_config
['media:medium']['max_height'])
182 if self
._skip
_processing
('perspective', size
=size
):
187 "{basename}.perspective.jpg",
188 [0, self
.greatest
*-1.5, self
.greatest
],
192 self
.entry
.set_file_metadata('perspective', size
=size
)
194 def generate_topview(self
, size
=None):
196 size
= (mgg
.global_config
['media:medium']['max_width'],
197 mgg
.global_config
['media:medium']['max_height'])
199 if self
._skip
_processing
('top', size
=size
):
204 "{basename}.top.jpg",
205 [self
.model
.average
[0], self
.model
.average
[1],
209 self
.entry
.set_file_metadata('top', size
=size
)
211 def generate_frontview(self
, size
=None):
213 size
= (mgg
.global_config
['media:medium']['max_width'],
214 mgg
.global_config
['media:medium']['max_height'])
216 if self
._skip
_processing
('front', size
=size
):
221 "{basename}.front.jpg",
222 [self
.model
.average
[0], self
.greatest
*-2,
223 self
.model
.average
[2]],
226 self
.entry
.set_file_metadata('front', size
=size
)
228 def generate_sideview(self
, size
=None):
230 size
= (mgg
.global_config
['media:medium']['max_width'],
231 mgg
.global_config
['media:medium']['max_height'])
233 if self
._skip
_processing
('side', size
=size
):
238 "{basename}.side.jpg",
239 [self
.greatest
*-2, self
.model
.average
[1],
240 self
.model
.average
[2]],
243 self
.entry
.set_file_metadata('side', size
=size
)
245 def store_dimensions(self
):
247 Put model dimensions into the database
250 "center_x": self
.model
.average
[0],
251 "center_y": self
.model
.average
[1],
252 "center_z": self
.model
.average
[2],
253 "width": self
.model
.width
,
254 "height": self
.model
.height
,
255 "depth": self
.model
.depth
,
256 "file_type": self
.ext
,
258 self
.entry
.media_data_init(**dimensions
)
261 class InitialProcessor(CommonStlProcessor
):
263 Initial processing step for new stls
266 description
= "Initial processing"
269 def media_is_eligible(cls
, entry
=None, state
=None):
271 Determine if this media type is eligible for processing
276 "unprocessed", "failed")
279 def generate_parser(cls
):
280 parser
= argparse
.ArgumentParser(
281 description
=cls
.description
,
287 metavar
=('max_width', 'max_height'),
293 metavar
=('max_width', 'max_height'),
299 def args_to_request(cls
, args
):
300 return request_from_args(
301 args
, ['size', 'thumb_size'])
303 def process(self
, size
=None, thumb_size
=None):
305 self
.generate_thumb(thumb_size
=thumb_size
)
306 self
.generate_perspective(size
=size
)
307 self
.generate_topview(size
=size
)
308 self
.generate_frontview(size
=size
)
309 self
.generate_sideview(size
=size
)
310 self
.store_dimensions()
312 self
.delete_queue_file()
315 class Resizer(CommonStlProcessor
):
317 Resizing process steps for processed stls
320 description
= 'Resize thumbnail and mediums'
324 def media_is_eligible(cls
, entry
=None, state
=None):
326 Determine if this media type is eligible for processing
330 return state
in 'processed'
333 def generate_parser(cls
):
334 parser
= argparse
.ArgumentParser(
335 description
=cls
.description
,
341 metavar
=('max_width', 'max_height'),
346 choices
=['medium', 'thumb'])
351 def args_to_request(cls
, args
):
352 return request_from_args(
353 args
, ['size', 'file'])
355 def process(self
, file, size
=None):
358 self
.generate_perspective(size
=size
)
359 self
.generate_topview(size
=size
)
360 self
.generate_frontview(size
=size
)
361 self
.generate_sideview(size
=size
)
362 elif file == 'thumb':
363 self
.generate_thumb(thumb_size
=size
)
366 class StlProcessingManager(ProcessingManager
):
368 super(StlProcessingManager
, self
).__init
__()
369 self
.add_processor(InitialProcessor
)
370 self
.add_processor(Resizer
)