"""
Smart object module.
"""
from __future__ import absolute_import, unicode_literals
import contextlib
import logging
import io
import os
from psd_tools.constants import Tag
logger = logging.getLogger(__name__)
[docs]class SmartObject(object):
"""
Smart object that represents embedded or external file.
Smart objects are attached to
:py:class:`~psd_tools.api.layers.SmartObjectLayer`.
"""
def __init__(self, layer):
self._config = None
for key in (
Tag.SMART_OBJECT_LAYER_DATA1, Tag.SMART_OBJECT_LAYER_DATA2
):
if key in layer.tagged_blocks:
self._config = layer.tagged_blocks.get_data(key)
break
self._data = None
for key in (
Tag.LINKED_LAYER1, Tag.LINKED_LAYER2, Tag.LINKED_LAYER3,
Tag.LINKED_LAYER_EXTERNAL
):
if key in layer._psd.tagged_blocks:
data = layer._psd.tagged_blocks.get_data(key)
for item in data:
if item.uuid == self.unique_id:
self._data = item
break
if self._data:
break
@property
def kind(self):
"""Kind of the link, 'data', 'alias', or 'external'."""
return self._data.kind.name.lower()
@property
def filename(self):
"""Original file name of the object."""
return self._data.filename.strip('\x00')
[docs] @contextlib.contextmanager
def open(self, external_dir=None):
"""
Open the smart object as binary IO.
:param external_dir: Path to the directory of the external file.
Example::
with layer.smart_object.open() as f:
data = f.read()
"""
if self.kind == 'data':
with io.BytesIO(self._data.data) as f:
yield f
elif self.kind == 'external':
filepath = self._data.linked_file[b'fullPath'].value
filepath = filepath.replace('\x00', '').replace('file://', '')
if not os.path.exists(filepath):
filepath = self._data.linked_file[b'relPath'].value
filepath = filepath.replace('\x00', '')
if external_dir is not None:
filepath = os.path.join(external_dir, filepath)
with open(filepath, 'rb') as f:
yield f
else:
raise NotImplementedError('alias is not supported.')
@property
def data(self):
"""Embedded file content, or empty if kind is `external` or `alias`"""
if self.kind == 'data':
return self._data.data
else:
with self.open() as f:
return f.read()
@property
def unique_id(self):
"""UUID of the object."""
return self._config.data.get(b'Idnt').value.strip('\x00')
@property
def filesize(self):
"""File size of the object."""
if self.kind == 'data':
return len(self._data.data)
return self._data.filesize
@property
def filetype(self):
"""Preferred file extension, such as `jpg`."""
return self._data.filetype.lower().strip().decode('ascii')
[docs] def is_psd(self):
"""Return True if the file is embedded PSD/PSB."""
return self.filetype in ('8bpb', '8bps')
@property
def warp(self):
"""Warp parameters."""
return self._config.data.get(b'warp')
@property
def resolution(self):
"""Resolution of the object."""
return self._config.data.get(b'Rslt').value
[docs] def save(self, filename=None):
"""
Save the smart object to a file.
:param filename: File name to export. If None, use the embedded name.
"""
if filename is None:
filename = self.filename
with open(filename, 'wb') as f:
f.write(self.data)
def __repr__(self):
return "SmartObject(%r kind=%r type=%r size=%s)" % (
self.filename, self.kind, self.filetype, self.filesize
)