Initial commit of the converter image transformation refactor

This commit is contained in:
Roberto Rosario
2011-07-15 06:16:14 -04:00
parent 358216fac5
commit 743ae0fce0
17 changed files with 303 additions and 114 deletions

View File

@@ -1,7 +1,16 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ImproperlyConfigured
from navigation.api import register_sidebar_template from navigation.api import register_sidebar_template
from converter.utils import load_backend
from converter.conf.settings import GRAPHICS_BACKEND
formats_list = {'text': _('file formats'), 'view': 'formats_list', 'famfam': 'pictures'} formats_list = {'text': _('file formats'), 'view': 'formats_list', 'famfam': 'pictures'}
register_sidebar_template(['formats_list'], 'converter_file_formats_help.html') register_sidebar_template(['formats_list'], 'converter_file_formats_help.html')
try:
backend = load_backend().ConverterClass()
except ImproperlyConfigured:
raise ImproperlyConfigured(u'Missing or incorrect converter backend: %s' % GRAPHICS_BACKEND)

View File

@@ -3,7 +3,6 @@ import subprocess
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.template.defaultfilters import slugify from django.template.defaultfilters import slugify
from django.core.exceptions import ImproperlyConfigured
from common import TEMPORARY_DIRECTORY from common import TEMPORARY_DIRECTORY
from documents.utils import document_save_to_temp_dir from documents.utils import document_save_to_temp_dir
@@ -12,21 +11,22 @@ from converter.conf.settings import UNPAPER_PATH
from converter.conf.settings import OCR_OPTIONS from converter.conf.settings import OCR_OPTIONS
from converter.conf.settings import UNOCONV_PATH from converter.conf.settings import UNOCONV_PATH
from converter.exceptions import UnpaperError, OfficeConversionError from converter.exceptions import UnpaperError, OfficeConversionError
from converter.utils import load_backend from converter.literals import DEFAULT_PAGE_NUMBER, \
from converter.literals import DEFAULT_PAGE_INDEX_NUMBER, \
DEFAULT_OCR_FILE_FORMAT, QUALITY_DEFAULT, DEFAULT_ZOOM_LEVEL, \ DEFAULT_OCR_FILE_FORMAT, QUALITY_DEFAULT, DEFAULT_ZOOM_LEVEL, \
DEFAULT_ROTATION, DEFAULT_FILE_FORMAT, QUALITY_PRINT DEFAULT_ROTATION, DEFAULT_FILE_FORMAT, QUALITY_PRINT
from converter import backend
from converter.literals import TRANSFORMATION_CHOICES
from converter.literals import TRANSFORMATION_RESIZE, \
TRANSFORMATION_ROTATE, TRANSFORMATION_DENSITY, \
TRANSFORMATION_ZOOM
from converter.literals import DIMENSION_SEPARATOR
CONVERTER_OFFICE_FILE_EXTENSIONS = [ CONVERTER_OFFICE_FILE_EXTENSIONS = [
u'ods', u'docx', u'doc' u'ods', u'docx', u'doc'
] ]
try:
backend = load_backend().ConverterClass()
except ImproperlyConfigured:
raise ImproperlyConfigured(u'Missing or incorrect converter backend: %s' % GRAPHICS_BACKEND)
def cleanup(filename): def cleanup(filename):
""" """
Tries to remove the given filename. Ignores non-existent files Tries to remove the given filename. Ignores non-existent files
@@ -107,18 +107,18 @@ def convert_document(document, *args, **kwargs):
def convert(input_filepath, *args, **kwargs): def convert(input_filepath, *args, **kwargs):
size = kwargs.get('size') size = kwargs.get('size')
file_format = kwargs.get('file_format', DEFAULT_FILE_FORMAT) file_format = kwargs.get('file_format', DEFAULT_FILE_FORMAT)
extra_options = kwargs.get('extra_options', u'')
zoom = kwargs.get('zoom', DEFAULT_ZOOM_LEVEL) zoom = kwargs.get('zoom', DEFAULT_ZOOM_LEVEL)
rotation = kwargs.get('rotation', DEFAULT_ROTATION) rotation = kwargs.get('rotation', DEFAULT_ROTATION)
page = kwargs.get('page', DEFAULT_PAGE_INDEX_NUMBER) page = kwargs.get('page', DEFAULT_PAGE_NUMBER)
cleanup_files = kwargs.get('cleanup_files', True) cleanup_files = kwargs.get('cleanup_files', True)
quality = kwargs.get('quality', QUALITY_DEFAULT) quality = kwargs.get('quality', QUALITY_DEFAULT)
transformations = kwargs.get('transformations', [])
unoconv_output = None unoconv_output = None
output_filepath = create_image_cache_filename(input_filepath, *args, **kwargs) output_filepath = create_image_cache_filename(input_filepath, *args, **kwargs)
if os.path.exists(output_filepath): #if os.path.exists(output_filepath):
return output_filepath # return output_filepath
path, extension = os.path.splitext(input_filepath) path, extension = os.path.splitext(input_filepath)
if extension[1:].lower() in CONVERTER_OFFICE_FILE_EXTENSIONS: if extension[1:].lower() in CONVERTER_OFFICE_FILE_EXTENSIONS:
@@ -128,18 +128,33 @@ def convert(input_filepath, *args, **kwargs):
input_filepath = result input_filepath = result
extra_options = u'' extra_options = u''
input_arg = u'%s[%s]' % (input_filepath, page) #TODO: not here in the backend
extra_options += u' -resize %s' % size input_arg = u'%s[%s]' % (input_filepath, page-1)
transformations.append(
{
'transformation': TRANSFORMATION_RESIZE,
'arguments': dict(zip([u'width', u'height'], size.split(DIMENSION_SEPARATOR)))
}
)
if zoom != 100: if zoom != 100:
extra_options += u' -resize %d%% ' % zoom transformations.append(
{
'transformation': TRANSFORMATION_ZOOM,
'arguments': {'percent': zoom}
}
)
if rotation != 0 and rotation != 360: if rotation != 0 and rotation != 360:
extra_options += u' -rotate %d ' % rotation transformations.append(
{
'transformation': TRANSFORMATION_ROTATE,
'arguments': {'degrees': rotation}
}
)
if format == u'jpg':
extra_options += u' -quality 85'
try: try:
backend.convert_file(input_filepath=input_arg, arguments=extra_options, output_filepath=u'%s:%s' % (file_format, output_filepath), quality=quality) backend.convert_file(input_filepath=input_arg, output_filepath=u'%s:%s' % (file_format, output_filepath), quality=quality, transformations=transformations)
finally: finally:
if cleanup_files: if cleanup_files:
cleanup(input_filepath) cleanup(input_filepath)
@@ -150,11 +165,7 @@ def convert(input_filepath, *args, **kwargs):
def get_page_count(input_filepath): def get_page_count(input_filepath):
try: return backend.get_page_count(input_filepath)
return len(backend.identify_file(unicode(input_filepath)).splitlines())
except:
#TODO: send to other page number identifying program
return 1
def get_document_dimensions(document, *args, **kwargs): def get_document_dimensions(document, *args, **kwargs):
@@ -166,7 +177,7 @@ def get_document_dimensions(document, *args, **kwargs):
return [0, 0] return [0, 0]
def convert_document_for_ocr(document, page=DEFAULT_PAGE_INDEX_NUMBER, file_format=DEFAULT_OCR_FILE_FORMAT): def convert_document_for_ocr(document, page=DEFAULT_PAGE_NUMBER, file_format=DEFAULT_OCR_FILE_FORMAT):
#Extract document file #Extract document file
input_filepath = document_save_to_temp_dir(document, document.uuid) input_filepath = document_save_to_temp_dir(document, document.uuid)
@@ -178,7 +189,7 @@ def convert_document_for_ocr(document, page=DEFAULT_PAGE_INDEX_NUMBER, file_form
unpaper_output_file = u'%s_unpaper_out%s%spnm' % (temp_path, page, os.extsep) unpaper_output_file = u'%s_unpaper_out%s%spnm' % (temp_path, page, os.extsep)
convert_output_file = u'%s_ocr%s%s%s' % (temp_path, page, os.extsep, file_format) convert_output_file = u'%s_ocr%s%s%s' % (temp_path, page, os.extsep, file_format)
input_arg = u'%s[%s]' % (input_filepath, page) input_arg = u'%s[%s]' % (input_filepath, page-1)
try: try:
document_page = document.documentpage_set.get(page_number=page + 1) document_page = document.documentpage_set.get(page_number=page + 1)
@@ -198,3 +209,12 @@ def convert_document_for_ocr(document, page=DEFAULT_PAGE_INDEX_NUMBER, file_form
cleanup(unpaper_output_file) cleanup(unpaper_output_file)
return convert_output_file return convert_output_file
def get_available_transformations_choices():
result = []
for transformation in backend.get_available_transformations():
transformation_template = u'%s %s' % (TRANSFORMATION_CHOICES[transformation]['label'], u','.join(['<%s>' % argument['name'] if argument['required'] else '[%s]' % argument['name'] for argument in TRANSFORMATION_CHOICES[transformation]['arguments']]))
result.append([transformation, transformation_template])
return result

View File

@@ -21,9 +21,6 @@ class ConverterBase(object):
def get_available_transformations(self): def get_available_transformations(self):
raise NotImplementedError("Your %s class has not defined a get_available_transformations() method, which is required." % self.__class__.__name__) raise NotImplementedError("Your %s class has not defined a get_available_transformations() method, which is required." % self.__class__.__name__)
def get_available_transformations_labels(self):
return ([(name, data['label']) for name, data in self.get_available_transformations().items()])
def get_transformation_string(self, transformation_list): def get_transformation_string(self, transformation_list):
transformations = [] transformations = []
warnings = [] warnings = []
@@ -41,3 +38,5 @@ class ConverterBase(object):
return u' '.join(transformations), warnings return u' '.join(transformations), warnings
def get_page_count(self):
raise NotImplementedError("Your %s class has not defined a get_page_count() method, which is required." % self.__class__.__name__)

View File

@@ -8,6 +8,10 @@ from converter.conf.settings import GM_SETTINGS
from converter.literals import QUALITY_DEFAULT, QUALITY_SETTINGS from converter.literals import QUALITY_DEFAULT, QUALITY_SETTINGS
from converter.exceptions import ConvertError, UnknownFormat, IdentifyError from converter.exceptions import ConvertError, UnknownFormat, IdentifyError
from converter.backends import ConverterBase from converter.backends import ConverterBase
from converter.literals import TRANSFORMATION_RESIZE, \
TRANSFORMATION_ROTATE, TRANSFORMATION_DENSITY, \
TRANSFORMATION_ZOOM
from converter.literals import DIMENSION_SEPARATOR
CONVERTER_ERROR_STRING_NO_DECODER = u'No decode delegate for this image format' CONVERTER_ERROR_STRING_NO_DECODER = u'No decode delegate for this image format'
CONVERTER_ERROR_STARTS_WITH = u'starts with' CONVERTER_ERROR_STARTS_WITH = u'starts with'
@@ -28,7 +32,29 @@ class ConverterClass(ConverterBase):
return proc.stdout.read() return proc.stdout.read()
def convert_file(self, input_filepath, output_filepath, quality=QUALITY_DEFAULT, arguments=None): def convert_file(self, input_filepath, output_filepath, transformations=None, quality=QUALITY_DEFAULT):
arguments = []
if transformations:
for transformation in transformations:
if transformation['transformation'] == TRANSFORMATION_RESIZE:
dimensions = []
dimensions.append(unicode(transformation['arguments']['width']))
if 'height' in transformation['arguments']:
dimensions.append(unicode(transformation['arguments']['height']))
arguments.append(u'-resize')
arguments.append(u'%s' % DIMENSION_SEPARATOR.join(dimensions))
elif transformation['transformation'] == TRANSFORMATION_ZOOM:
arguments.append(u'-resize')
arguments.append(u'%d%%' % transformation['arguments']['zoom'])
elif transformation['transformation'] == TRANSFORMATION_ROTATE:
arguments.append(u'-rotate')
arguments.append(u'%s' % transformation['arguments']['degrees'])
print 'arguments: %s' % arguments
#if format == u'jpg':
# extra_options += u' -quality 85'
command = [] command = []
command.append(unicode(GM_PATH)) command.append(unicode(GM_PATH))
command.append(u'convert') command.append(u'convert')
@@ -36,8 +62,9 @@ class ConverterClass(ConverterBase):
command.extend(unicode(GM_SETTINGS).split()) command.extend(unicode(GM_SETTINGS).split())
command.append(unicode(input_filepath)) command.append(unicode(input_filepath))
if arguments: if arguments:
command.extend(unicode(arguments).split()) command.extend(arguments)
command.append(unicode(output_filepath)) command.append(unicode(output_filepath))
print 'command: %s' % command
proc = subprocess.Popen(command, close_fds=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) proc = subprocess.Popen(command, close_fds=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
return_code = proc.wait() return_code = proc.wait()
if return_code != 0: if return_code != 0:
@@ -76,10 +103,22 @@ class ConverterClass(ConverterBase):
def get_available_transformations(self): def get_available_transformations(self):
return { return [
'rotate': { TRANSFORMATION_RESIZE, TRANSFORMATION_ROTATE, \
'label': _(u'Rotate [degrees]'), TRANSFORMATION_DENSITY, TRANSFORMATION_ZOOM
'arguments': [{'name': 'degrees'}], ]
'command_line': u'-rotate %(degrees)d'
}
} def get_page_count(self, input_filepath):
try:
return len(self.identify_file(unicode(input_filepath)).splitlines())
except:
#TODO: send to other page number identifying program
return 1
def _get_transformation_string():
pass
#'command_line': u'-rotate %(degrees)d'
# }
#}

View File

@@ -9,7 +9,10 @@ from converter.api import QUALITY_DEFAULT, QUALITY_SETTINGS
from converter.exceptions import ConvertError, UnknownFormat, \ from converter.exceptions import ConvertError, UnknownFormat, \
IdentifyError IdentifyError
from converter.backends import ConverterBase from converter.backends import ConverterBase
from converter.literals import TRANSFORMATION_RESIZE, \
TRANSFORMATION_ROTATE, TRANSFORMATION_DENSITY, \
TRANSFORMATION_ZOOM
CONVERTER_ERROR_STRING_NO_DECODER = u'no decode delegate for this image format' CONVERTER_ERROR_STRING_NO_DECODER = u'no decode delegate for this image format'
@@ -29,6 +32,8 @@ class ConverterClass(ConverterBase):
def convert_file(self, input_filepath, output_filepath, quality=QUALITY_DEFAULT, arguments=None): def convert_file(self, input_filepath, output_filepath, quality=QUALITY_DEFAULT, arguments=None):
#if format == u'jpg':
# extra_options += u' -quality 85'
command = [] command = []
command.append(unicode(IM_CONVERT_PATH)) command.append(unicode(IM_CONVERT_PATH))
command.extend(unicode(QUALITY_SETTINGS[quality]).split()) command.extend(unicode(QUALITY_SETTINGS[quality]).split())
@@ -73,10 +78,15 @@ class ConverterClass(ConverterBase):
def get_available_transformations(self): def get_available_transformations(self):
return { return [
'rotate': { TRANSFORMATION_RESIZE, TRANSFORMATION_ROTATE, \
'label': _(u'Rotate [degrees]'), TRANSFORMATION_DENSITY, TRANSFORMATION_ZOOM
'arguments': [{'name': 'degrees'}], ]
'command_line': u'-rotate %(degrees)d'
}
} def get_page_count(self, input_filepath):
try:
return len(self.identify_file(unicode(input_filepath)).splitlines())
except:
#TODO: send to other page number identifying program
return 1

View File

@@ -0,0 +1,3 @@
from PIL import Image
Image.init()

View File

@@ -0,0 +1,80 @@
from PIL import Image
from django.utils.translation import ugettext_lazy as _
from converter.literals import QUALITY_DEFAULT, QUALITY_SETTINGS
from converter.exceptions import ConvertError, UnknownFormat, IdentifyError
from converter.backends import ConverterBase
from converter.literals import TRANSFORMATION_RESIZE, \
TRANSFORMATION_ROTATE
class ConverterClass(ConverterBase):
def identify_file(self, input_filepath, arguments=None):
pass
def get_page_count(self, input_filepath):
page_count = 1
im = Image.open(input_filepath)
try:
while 1:
im.seek(im.tell()+1)
page_count += 1
# do something to im
except EOFError:
pass # end of sequence
return page_count
def convert_file(self, input_filepath, output_filepath, quality=QUALITY_DEFAULT, arguments=None):
im = Image.open(input_filepath)
outfile, format = output_filepath.split(u':')
im.save(outfile, format)
'''
command = []
command.append(unicode(GM_PATH))
command.append(u'convert')
command.extend(unicode(QUALITY_SETTINGS[quality]).split())
command.extend(unicode(GM_SETTINGS).split())
command.append(unicode(input_filepath))
if arguments:
command.extend(unicode(arguments).split())
command.append(unicode(output_filepath))
proc = subprocess.Popen(command, close_fds=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
return_code = proc.wait()
if return_code != 0:
#Got an error from convert program
error_line = proc.stderr.readline()
if (CONVERTER_ERROR_STRING_NO_DECODER in error_line) or (CONVERTER_ERROR_STARTS_WITH in error_line):
#Try to determine from error message which class of error is it
raise UnknownFormat
else:
raise ConvertError(error_line)
'''
def get_format_list(self):
"""
Introspect PIL's internal registry to obtain a list of the
supported file types
"""
formats = []
for format_name in Image.ID:
formats.append((format_name, u''))
return formats
def get_available_transformations(self):
return [
TRANSFORMATION_RESIZE, TRANSFORMATION_ROTATE
]
def get_page_count(self, input_filepath):
try:
return len(self.identify_file(unicode(input_filepath)).splitlines())
except:
#TODO: send to other page number identifying program
return 1

View File

@@ -12,7 +12,7 @@ register_settings(
{'name': u'UNPAPER_PATH', 'global_name': u'CONVERTER_UNPAPER_PATH', 'default': u'/usr/bin/unpaper', 'description': _(u'File path to unpaper program.'), 'exists': True}, {'name': u'UNPAPER_PATH', 'global_name': u'CONVERTER_UNPAPER_PATH', 'default': u'/usr/bin/unpaper', 'description': _(u'File path to unpaper program.'), 'exists': True},
{'name': u'GM_PATH', 'global_name': u'CONVERTER_GM_PATH', 'default': u'/usr/bin/gm', 'description': _(u'File path to graphicsmagick\'s program.'), 'exists': True}, {'name': u'GM_PATH', 'global_name': u'CONVERTER_GM_PATH', 'default': u'/usr/bin/gm', 'description': _(u'File path to graphicsmagick\'s program.'), 'exists': True},
{'name': u'GM_SETTINGS', 'global_name': u'CONVERTER_GM_SETTINGS', 'default': u''}, {'name': u'GM_SETTINGS', 'global_name': u'CONVERTER_GM_SETTINGS', 'default': u''},
{'name': u'GRAPHICS_BACKEND', 'global_name': u'CONVERTER_GRAPHICS_BACKEND', 'default': u'converter.backends.imagemagick', 'description': _(u'Graphics conversion backend to use. Options are: converter.backends.imagemagick and converter.backends.graphicsmagick.')}, {'name': u'GRAPHICS_BACKEND', 'global_name': u'CONVERTER_GRAPHICS_BACKEND', 'default': u'converter.backends.python', 'description': _(u'Graphics conversion backend to use. Options are: converter.backends.imagemagick, converter.backends.graphicsmagick and converter.backends.python.')},
{'name': u'UNOCONV_PATH', 'global_name': u'CONVERTER_UNOCONV_PATH', 'default': u'/usr/bin/unoconv', 'exists': True}, {'name': u'UNOCONV_PATH', 'global_name': u'CONVERTER_UNOCONV_PATH', 'default': u'/usr/bin/unoconv', 'exists': True},
{'name': u'OCR_OPTIONS', 'global_name': u'CONVERTER_OCR_OPTIONS', 'default': u'-colorspace Gray -depth 8 -resample 200x200'}, {'name': u'OCR_OPTIONS', 'global_name': u'CONVERTER_OCR_OPTIONS', 'default': u'-colorspace Gray -depth 8 -resample 200x200'},
{'name': u'DEFAULT_OPTIONS', 'global_name': u'CONVERTER_DEFAULT_OPTIONS', 'default': u''}, {'name': u'DEFAULT_OPTIONS', 'global_name': u'CONVERTER_DEFAULT_OPTIONS', 'default': u''},

View File

@@ -1,3 +1,5 @@
from django.utils.translation import ugettext_lazy as _
from converter.conf.settings import DEFAULT_OPTIONS from converter.conf.settings import DEFAULT_OPTIONS
from converter.conf.settings import LOW_QUALITY_OPTIONS from converter.conf.settings import LOW_QUALITY_OPTIONS
from converter.conf.settings import HIGH_QUALITY_OPTIONS from converter.conf.settings import HIGH_QUALITY_OPTIONS
@@ -5,7 +7,7 @@ from converter.conf.settings import PRINT_QUALITY_OPTIONS
DEFAULT_ZOOM_LEVEL = 100 DEFAULT_ZOOM_LEVEL = 100
DEFAULT_ROTATION = 0 DEFAULT_ROTATION = 0
DEFAULT_PAGE_INDEX_NUMBER = 0 DEFAULT_PAGE_NUMBER = 1
DEFAULT_FILE_FORMAT = u'jpg' DEFAULT_FILE_FORMAT = u'jpg'
DEFAULT_OCR_FILE_FORMAT = u'tif' DEFAULT_OCR_FILE_FORMAT = u'tif'
@@ -20,3 +22,43 @@ QUALITY_SETTINGS = {
QUALITY_HIGH: HIGH_QUALITY_OPTIONS, QUALITY_HIGH: HIGH_QUALITY_OPTIONS,
QUALITY_PRINT: PRINT_QUALITY_OPTIONS QUALITY_PRINT: PRINT_QUALITY_OPTIONS
} }
DIMENSION_SEPARATOR = u'x'
TRANSFORMATION_RESIZE = u'resize'
TRANSFORMATION_ROTATE = u'rotate'
TRANSFORMATION_DENSITY = u'density'
TRANSFORMATION_ZOOM = u'zoom'
TRANSFORMATION_CHOICES = {
TRANSFORMATION_RESIZE: {
'label': _(u'Resize'),
'description': _(u'Resize.'),
'arguments': [
{'name': 'width', 'label': _(u'width'), 'required': True},
{'name': 'height', 'label': _(u'height'), 'required': False},
]
},
TRANSFORMATION_ROTATE: {
'label': _(u'Rotate'),
'description': _(u'Rotate by n degress.'),
'arguments': [
{'name': 'degrees', 'label': _(u'degrees'), 'required': True}
]
},
TRANSFORMATION_DENSITY: {
'label': _(u'Density'),
'description': _(u'Change the resolution (ie: DPI) without resizing.'),
'arguments': [
{'name': 'width', 'label': _(u'width'), 'required': True},
{'name': 'height', 'label': _(u'height'), 'required': False},
]
},
TRANSFORMATION_ZOOM: {
'label': _(u'Zoom'),
'description': _(u'Zoom by n percent.'),
'arguments': [
{'name': 'percent', 'label': _(u'percent'), 'required': True}
]
},
}

View File

@@ -1,38 +1,18 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from django.template import RequestContext from django.template import RequestContext
from django.utils.importlib import import_module
from converter import backend
from converter.conf.settings import GRAPHICS_BACKEND from converter.conf.settings import GRAPHICS_BACKEND
def _lazy_load(fn):
_cached = []
def _decorated():
if not _cached:
_cached.append(fn())
return _cached[0]
return _decorated
@_lazy_load
def _get_backend():
return import_module(GRAPHICS_BACKEND)
try:
backend = _get_backend()
except ImportError:
raise ImportError(u'Missing or incorrect converter backend: %s' % GRAPHICS_BACKEND)
def formats_list(request): def formats_list(request):
#check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW]) #check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW])
context = { context = {
'title': _(u'suported file formats'), 'title': _(u'suported file formats'),
'hide_object': True, 'hide_object': True,
'object_list': backend.get_format_list(), 'object_list': sorted(backend.get_format_list()),
'extra_columns': [ 'extra_columns': [
{ {
'name': _(u'name'), 'name': _(u'name'),

View File

@@ -13,3 +13,11 @@ class RecentDocumentManager(models.Manager):
to_delete = self.model.objects.filter(user=user)[RECENT_COUNT:] to_delete = self.model.objects.filter(user=user)[RECENT_COUNT:]
for recent_to_delete in to_delete: for recent_to_delete in to_delete:
recent_to_delete.delete() recent_to_delete.delete()
class DocumentPageTransformationManager(models.Manager):
def get_for_document_page(self, document_page):
return self.model.objects.filter(document_page=document_page)
def get_for_document_page_as_list(self, document_page):
return list([{'transformation': transformation['transformation'], 'arguments': eval(transformation['arguments'])} for transformation in self.get_for_document_page(document_page).values('transformation', 'arguments')])

View File

@@ -12,12 +12,13 @@ from python_magic import magic
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from dynamic_search.api import register from dynamic_search.api import register
from converter.api import get_page_count from converter.api import get_page_count
from converter.api import backend from converter.api import get_available_transformations_choices
from documents.conf.settings import CHECKSUM_FUNCTION from documents.conf.settings import CHECKSUM_FUNCTION
from documents.conf.settings import UUID_FUNCTION from documents.conf.settings import UUID_FUNCTION
from documents.conf.settings import STORAGE_BACKEND from documents.conf.settings import STORAGE_BACKEND
from documents.managers import RecentDocumentManager from documents.managers import RecentDocumentManager, \
DocumentPageTransformationManager
def get_filename_from_uuid(instance, filename): def get_filename_from_uuid(instance, filename):
@@ -259,9 +260,6 @@ class DocumentPage(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return ('document_page_view', [self.pk]) return ('document_page_view', [self.pk])
def get_transformation_string(self):
return backend.get_transformation_string(self.documentpagetransformation_set.values('transformation', 'arguments'))
class DocumentPageTransformation(models.Model): class DocumentPageTransformation(models.Model):
""" """
@@ -270,9 +268,11 @@ class DocumentPageTransformation(models.Model):
""" """
document_page = models.ForeignKey(DocumentPage, verbose_name=_(u'document page')) document_page = models.ForeignKey(DocumentPage, verbose_name=_(u'document page'))
order = models.PositiveIntegerField(default=0, blank=True, null=True, verbose_name=_(u'order'), db_index=True) order = models.PositiveIntegerField(default=0, blank=True, null=True, verbose_name=_(u'order'), db_index=True)
transformation = models.CharField(choices=backend.get_available_transformations_labels(), max_length=128, verbose_name=_(u'transformation')) transformation = models.CharField(choices=get_available_transformations_choices(), max_length=128, verbose_name=_(u'transformation'))
arguments = models.TextField(blank=True, null=True, verbose_name=_(u'arguments'), help_text=_(u'Use dictionaries to indentify arguments, example: {\'degrees\':90}')) arguments = models.TextField(blank=True, null=True, verbose_name=_(u'arguments'), help_text=_(u'Use dictionaries to indentify arguments, example: {\'degrees\':90}'))
objects = DocumentPageTransformationManager()
def __unicode__(self): def __unicode__(self):
return u'"%s" for %s' % (self.get_transformation_display(), unicode(self.document_page)) return u'"%s" for %s' % (self.get_transformation_display(), unicode(self.document_page))

View File

@@ -285,7 +285,7 @@ def document_edit(request, document_id):
'object': document, 'object': document,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
'''
def calculate_converter_arguments(document, *args, **kwargs): def calculate_converter_arguments(document, *args, **kwargs):
size = kwargs.pop('size', PREVIEW_SIZE) size = kwargs.pop('size', PREVIEW_SIZE)
quality = kwargs.pop('quality', QUALITY_DEFAULT) quality = kwargs.pop('quality', QUALITY_DEFAULT)
@@ -308,7 +308,7 @@ def calculate_converter_arguments(document, *args, **kwargs):
} }
return arguments, warnings return arguments, warnings
'''
def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_DEFAULT): def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_DEFAULT):
check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW]) check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW])
@@ -327,14 +327,17 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_
rotation = int(request.GET.get('rotation', 0)) % 360 rotation = int(request.GET.get('rotation', 0)) % 360
arguments, warnings = calculate_converter_arguments(document, size=size, file_format=DEFAULT_FILE_FORMAT, quality=quality, page=page, zoom=zoom, rotation=rotation) #arguments, warnings = calculate_converter_arguments(document, size=size, file_format=DEFAULT_FILE_FORMAT, quality=quality, page=page, zoom=zoom, rotation=rotation)
if warnings and (request.user.is_staff or request.user.is_superuser): #if warnings and (request.user.is_staff or request.user.is_superuser):
for warning in warnings: # for warning in warnings:
messages.warning(request, _(u'Page transformation error: %s') % warning) # messages.warning(request, _(u'Page transformation error: %s') % warning)
transformations = DocumentPageTransformation.objects.get_for_document_page_as_list(document)
try: try:
output_file = convert_document(document, **arguments) #output_file = convert_document(document, **arguments)
output_file = convert_document(document, size=size, file_format=DEFAULT_FILE_FORMAT, quality=quality, page=page, zoom=zoom, rotation=rotation, transformations=transformations)
except UnkownConvertError, e: except UnkownConvertError, e:
if request.user.is_staff or request.user.is_superuser: if request.user.is_staff or request.user.is_superuser:
messages.error(request, e) messages.error(request, e)

View File

@@ -6,3 +6,6 @@ class SourceTransformationManager(models.Manager):
def get_for_object(self, obj): def get_for_object(self, obj):
ct = ContentType.objects.get_for_model(obj) ct = ContentType.objects.get_for_model(obj)
return self.model.objects.filter(content_type=ct).filter(object_id=obj.pk) return self.model.objects.filter(content_type=ct).filter(object_id=obj.pk)
def get_for_object_as_list(self, obj):
return list([{'transformation': transformation['transformation'], 'arguments': eval(transformation['arguments'])} for transformation in self.get_for_object(obj).values('transformation', 'arguments')])

View File

@@ -6,7 +6,8 @@ from django.contrib.contenttypes import generic
from documents.models import DocumentType from documents.models import DocumentType
from documents.managers import RecentDocumentManager from documents.managers import RecentDocumentManager
from metadata.models import MetadataType from metadata.models import MetadataType
from converter.api import backend from converter.api import get_available_transformations_choices
from converter.literals import DIMENSION_SEPARATOR
from sources.managers import SourceTransformationManager from sources.managers import SourceTransformationManager
@@ -118,7 +119,7 @@ class StagingFolder(InteractiveBaseModel):
if self.preview_height: if self.preview_height:
dimensions.append(unicode(self.preview_height)) dimensions.append(unicode(self.preview_height))
return u'x'.join(dimensions) return DIMENSION_SEPARATOR.join(dimensions)
class Meta(InteractiveBaseModel.Meta): class Meta(InteractiveBaseModel.Meta):
verbose_name = _(u'staging folder') verbose_name = _(u'staging folder')
@@ -162,8 +163,8 @@ class SourceTransformation(models.Model):
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id') content_object = generic.GenericForeignKey('content_type', 'object_id')
order = models.PositiveIntegerField(default=0, blank=True, null=True, verbose_name=_(u'order'), db_index=True) order = models.PositiveIntegerField(default=0, blank=True, null=True, verbose_name=_(u'order'), db_index=True)
transformation = models.CharField(choices=backend.get_available_transformations_labels(), max_length=128, verbose_name=_(u'transformation')) transformation = models.CharField(choices=get_available_transformations_choices(), max_length=128, verbose_name=_(u'transformation'))
arguments = models.TextField(blank=True, null=True, verbose_name=_(u'arguments'), help_text=_(u'Use dictionaries to indentify arguments, example: {\'degrees\':90}')) arguments = models.TextField(blank=True, null=True, verbose_name=_(u'arguments'), help_text=_(u'Use dictionaries to indentify arguments, example: %s') % u'{\'degrees\':90}')
objects = SourceTransformationManager() objects = SourceTransformationManager()

View File

@@ -106,16 +106,15 @@ class StagingFile(object):
def upload(self): def upload(self):
""" """
Return a StagingFile encapsulated in a File class instance to Return a StagingFile encapsulated in a File class instance to
allow for easier upload a staging files allow for easier upload of staging files
""" """
try: try:
return File(file(self.filepath, 'rb'), name=self.filename) return File(file(self.filepath, 'rb'), name=self.filename)
except Exception, exc: except Exception, exc:
raise Exception(ugettext(u'Unable to upload staging file: %s') % exc) raise Exception(ugettext(u'Unable to upload staging file: %s') % exc)
def delete(self, preview_size): def delete(self, preview_size, transformations):
# tranformation_string, errors = get_transformation_string(DEFAULT_TRANSFORMATIONS) cache_cleanup(self.filepath, size=preview_size, transformations=transformations)
cache_cleanup(self.filepath, size=preview_size)# , extra_options=tranformation_string)
try: try:
os.unlink(self.filepath) os.unlink(self.filepath)
except OSError, exc: except OSError, exc:
@@ -124,24 +123,7 @@ class StagingFile(object):
else: else:
raise OSError(ugettext(u'Unable to delete staging file: %s') % exc) raise OSError(ugettext(u'Unable to delete staging file: %s') % exc)
def preview(self, preview_size): def preview(self, preview_size, transformations):
errors = [] errors = []
# tranformation_string, errors = get_transformation_string(DEFAULT_TRANSFORMATIONS) output_file = convert(self.filepath, size=preview_size, cleanup_files=False, transformations=transformations)
# output_file = convert(self.filepath, size=STAGING_FILES_PREVIEW_SIZE, extra_options=tranformation_string, cleanup_files=False)
output_file = convert(self.filepath, size=preview_size, cleanup_files=False)
return output_file, errors return output_file, errors
def get_transformation_string(transformations):
transformation_list = []
errors = []
#for transformation in transformations:
# try:
# if transformation['name'] in TRANFORMATION_CHOICES:
# output = TRANFORMATION_CHOICES[transformation['name']] % eval(transformation['arguments'])
# transformation_list.append(output)
# except Exception, e:
# errors.append(e)
#tranformation_string = ' '.join(transformation_list)
return tranformation_string, errors

View File

@@ -285,7 +285,10 @@ def staging_file_preview(request, source_type, source_id, staging_file_id):
staging_folder = get_object_or_404(StagingFolder, pk=source_id) staging_folder = get_object_or_404(StagingFolder, pk=source_id)
StagingFile = create_staging_file_class(request, staging_folder.folder_path) StagingFile = create_staging_file_class(request, staging_folder.folder_path)
try: try:
output_file, errors = StagingFile.get(staging_file_id).preview(staging_folder.get_preview_size()) output_file, errors = StagingFile.get(staging_file_id).preview(
preview_size=staging_folder.get_preview_size(),
transformations=SourceTransformation.objects.get_for_object_as_list(staging_folder)
)
if errors and (request.user.is_staff or request.user.is_superuser): if errors and (request.user.is_staff or request.user.is_superuser):
for error in errors: for error in errors:
messages.warning(request, _(u'Staging file transformation error: %(error)s') % { messages.warning(request, _(u'Staging file transformation error: %(error)s') % {
@@ -318,7 +321,10 @@ def staging_file_delete(request, source_type, source_id, staging_file_id):
if request.method == 'POST': if request.method == 'POST':
try: try:
staging_file.delete(staging_folder.get_preview_size()) staging_file.delete(
preview_size=staging_folder.get_preview_size(),
transformations=SourceTransformation.objects.get_for_object_as_list(staging_folder)
)
messages.success(request, _(u'Staging file delete successfully.')) messages.success(request, _(u'Staging file delete successfully.'))
except Exception, e: except Exception, e:
messages.error(request, e) messages.error(request, e)
@@ -509,6 +515,8 @@ def setup_source_transformation_edit(request, transformation_id):
form = SourceTransformationForm(instance=source_transformation, data=request.POST) form = SourceTransformationForm(instance=source_transformation, data=request.POST)
if form.is_valid(): if form.is_valid():
try: try:
# Test the validity of the argument field
eval(form.cleaned_data['arguments'])
form.save() form.save()
messages.success(request, _(u'Source transformation edited successfully')) messages.success(request, _(u'Source transformation edited successfully'))
return HttpResponseRedirect(next) return HttpResponseRedirect(next)
@@ -598,6 +606,8 @@ def setup_source_transformation_create(request, source_type, source_id):
form = SourceTransformationForm_create(request.POST) form = SourceTransformationForm_create(request.POST)
if form.is_valid(): if form.is_valid():
try: try:
# Test the validity of the argument field
eval(form.cleaned_data['arguments'])
source_tranformation = form.save(commit=False) source_tranformation = form.save(commit=False)
source_tranformation.content_object = source source_tranformation.content_object = source
source_tranformation.save() source_tranformation.save()