Meshroom/meshroom/nodes/aliceVision/SketchfabUpload.py
Candice Bentéjac c498f48e1e [nodes] S-T: Harmonize and improve labels and descriptions
Use CamelCase for all labels, always end descriptions with periods, and
replace the mixed use of single and double quotes with double quotes
only.
2023-06-16 10:32:01 +02:00

288 lines
10 KiB
Python

__version__ = "1.0"
from meshroom.core import desc
import glob
import os
import json
import zipfile
import requests
import io
class BufferReader(io.BytesIO): # object to call the callback while the file is being uploaded
def __init__(self, buf=b'',
callback=None,
cb_args=(),
cb_kwargs={},
stopped=None):
self._callback = callback
self._cb_args = cb_args
self._cb_kwargs = cb_kwargs
self._stopped = stopped
self._progress = 0
self._len = len(buf)
io.BytesIO.__init__(self, buf)
def __len__(self):
return self._len
def read(self, n=-1):
chunk = io.BytesIO.read(self, n)
self._progress += int(len(chunk))
self._cb_kwargs.update({
'size' : self._len,
'progress': self._progress
})
if self._callback:
try:
self._callback(*self._cb_args, **self._cb_kwargs)
except Exception as e: # catches exception from the callback
self._cb_kwargs['logManager'].logger.warning('Error at callback: {}'.format(e))
if self._stopped():
raise RuntimeError('Node stopped by user')
return chunk
def progressUpdate(size=None, progress=None, logManager=None):
if not logManager.progressBar:
logManager.makeProgressBar(size, 'Upload progress:')
logManager.updateProgressBar(progress)
class SketchfabUpload(desc.Node):
size = desc.DynamicNodeSize('inputFiles')
category = 'Export'
documentation = '''
Upload a textured mesh on Sketchfab.
'''
inputs = [
desc.ListAttribute(
elementDesc=desc.File(
name="input",
label="Input",
description="",
value="",
uid=[0],
),
name="inputFiles",
label="Input Files",
description="Input Files to export.",
group="",
),
desc.StringParam(
name="apiToken",
label="API Token",
description="Get your token from https://sketchfab.com/settings/password.",
value="",
uid=[0],
),
desc.StringParam(
name="title",
label="Title",
description="Title cannot be longer than 48 characters.",
value="",
uid=[0],
),
desc.StringParam(
name="description",
label="Description",
description="Description cannot be longer than 1024 characters.",
value="",
uid=[0],
),
desc.ChoiceParam(
name="license",
label="License",
description="License label.",
value="CC Attribution",
values=["CC Attribution",
"CC Attribution-ShareAlike",
"CC Attribution-NoDerivs",
"CC Attribution-NonCommercial",
"CC Attribution-NonCommercial-ShareAlike",
"CC Attribution-NonCommercial-NoDerivs"],
exclusive=True,
uid=[0],
),
desc.ListAttribute(
elementDesc=desc.StringParam(
name="tag",
label="Tag",
description="Tag cannot be longer than 48 characters.",
value="",
uid=[0],
),
name="tags",
label="Tags",
description="Maximum of 42 separate tags.",
group="",
),
desc.ChoiceParam(
name="category",
label="Category",
description="Adding categories helps improve the discoverability of your model.",
value="none",
values=["none",
"animals-pets",
"architecture",
"art-abstract",
"cars-vehicles",
"characters-creatures",
"cultural-heritage-history",
"electronics-gadgets",
"fashion-style",
"food-drink",
"furniture-home",
"music",
"nature-plants",
"news-politics",
"people",
"places-travel",
"science-technology",
"sports-fitness",
"weapons-military"],
exclusive=True,
uid=[0],
),
desc.BoolParam(
name="isPublished",
label="Publish",
description="If the model is not published, it will be saved as a draft.",
value=False,
uid=[0],
),
desc.BoolParam(
name="isInspectable",
label="Inspectable",
description="Allow 2D view in model inspector.",
value=True,
uid=[0],
),
desc.BoolParam(
name="isPrivate",
label="Private",
description="Requires a pro account.",
value=False,
uid=[0],
),
desc.StringParam(
name="password",
label="Password",
description="Requires a pro account.",
value="",
uid=[0],
),
desc.ChoiceParam(
name="verboseLevel",
label="Verbose Level",
description="Verbosity level (critical, error, warning, info, debug).",
value="info",
values=["critical", "error", "warning", "info", "debug"],
exclusive=True,
uid=[],
),
]
def upload(self, apiToken, modelFile, data, chunk):
modelEndpoint = 'https://api.sketchfab.com/v3/models'
f = open(modelFile, 'rb')
file = {'modelFile': (os.path.basename(modelFile), f.read())}
file.update(data)
f.close()
(files, contentType) = requests.packages.urllib3.filepost.encode_multipart_formdata(file)
headers = {'Authorization': 'Token {}'.format(apiToken), 'Content-Type': contentType}
body = BufferReader(files, progressUpdate, cb_kwargs={'logManager': chunk.logManager}, stopped=self.stopped)
chunk.logger.info('Uploading...')
try:
r = requests.post(
modelEndpoint, **{'data': body, 'headers': headers})
chunk.logManager.completeProgressBar()
except requests.exceptions.RequestException as e:
chunk.logger.error(u'An error occurred: {}'.format(e))
raise RuntimeError()
if r.status_code != requests.codes.created:
chunk.logger.error(u'Upload failed with error: {}'.format(r.json()))
raise RuntimeError()
def resolvedPaths(self, inputFiles):
paths = []
for inputFile in inputFiles:
if os.path.isdir(inputFile.value):
for path, subdirs, files in os.walk(inputFile.value):
for name in files:
paths.append(os.path.join(path, name))
else:
for f in glob.glob(inputFile.value):
paths.append(f)
return paths
def stopped(self):
return self._stopped
def processChunk(self, chunk):
try:
self._stopped = False
chunk.logManager.start(chunk.node.verboseLevel.value)
uploadFile = ''
if not chunk.node.inputFiles:
chunk.logger.warning('Nothing to upload')
return
if chunk.node.apiToken.value == '':
chunk.logger.error('Need API token.')
raise RuntimeError()
if len(chunk.node.title.value) > 48:
chunk.logger.error('Title cannot be longer than 48 characters.')
raise RuntimeError()
if len(chunk.node.description.value) > 1024:
chunk.logger.error('Description cannot be longer than 1024 characters.')
raise RuntimeError()
tags = [ i.value.replace(' ', '-') for i in chunk.node.tags.value.values() ]
if all(len(i) > 48 for i in tags) and len(tags) > 0:
chunk.logger.error('Tags cannot be longer than 48 characters.')
raise RuntimeError()
if len(tags) > 42:
chunk.logger.error('Maximum of 42 separate tags.')
raise RuntimeError()
data = {
'name': chunk.node.title.value,
'description': chunk.node.description.value,
'license': chunk.node.license.value,
'tags': str(tags),
'isPublished': chunk.node.isPublished.value,
'isInspectable': chunk.node.isInspectable.value,
'private': chunk.node.isPrivate.value,
'password': chunk.node.password.value
}
if chunk.node.category.value != 'none':
data.update({'categories': chunk.node.category.value})
chunk.logger.debug('Data to be sent: {}'.format(str(data)))
# pack files into .zip to reduce file size and simplify process
uploadFile = os.path.join(chunk.node.internalFolder, 'temp.zip')
files = self.resolvedPaths(chunk.node.inputFiles.value)
zf = zipfile.ZipFile(uploadFile, 'w')
for file in files:
zf.write(file, os.path.basename(file))
zf.close()
chunk.logger.debug('Files added to zip: {}'.format(str(files)))
chunk.logger.debug('Created {}'.format(uploadFile))
chunk.logger.info('File size: {}MB'.format(round(os.path.getsize(uploadFile)/(1024*1024), 3)))
self.upload(chunk.node.apiToken.value, uploadFile, data, chunk)
chunk.logger.info('Upload successful. Your model is being processed on Sketchfab. It may take some time to show up on your "models" page.')
except Exception as e:
chunk.logger.error(e)
raise RuntimeError()
finally:
if os.path.isfile(uploadFile):
os.remove(uploadFile)
chunk.logger.debug('Deleted {}'.format(uploadFile))
chunk.logManager.end()
def stopProcess(self, chunk):
self._stopped = True