From a246ccca69e863904718537f45a17d226b33a123 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Wed, 30 Nov 2011 21:21:39 +0100 Subject: [PATCH] ASCII media type support & fix a bug in file submission error handling * Added ASCII media processing * Added ASCII media display * Added ASCII media type Rebased from Joar Wandborg's ascii art branch (squashed to remove the commits borrowing code of dubious license) Fixed a bug in file submission error handling: - Moved file-extension condition out of loop (what did it do there?) - Updated file submission tests - Changed error handling in file submission, should now report more than absolutely necessary. --- extlib/inconsolata/INFO.txt | 4 + extlib/inconsolata/Inconsolata.otf | Bin 0 -> 58464 bytes extlib/inconsolata/Inconsolata.pfa | 1088 ++++ extlib/inconsolata/Inconsolata.sfd | 5730 +++++++++++++++++ extlib/inconsolata/OFL_1.1.txt | 97 + extlib/inconsolata/textest.pdf | Bin 0 -> 22783 bytes mediagoblin/media_types/__init__.py | 20 +- mediagoblin/media_types/ascii/__init__.py | 27 + mediagoblin/media_types/ascii/asciitoimage.py | 172 + .../media_types/ascii/fonts/Inconsolata.otf | 1 + mediagoblin/media_types/ascii/processing.py | 93 + mediagoblin/static/css/base.css | 12 + mediagoblin/static/fonts/Inconsolata.otf | 1 + mediagoblin/submit/views.py | 8 +- .../mediagoblin/media_displays/ascii.html | 40 + .../mediagoblin/media_displays/image.html | 18 + .../mediagoblin/media_displays/video.html | 18 + mediagoblin/tests/test_submission.py | 5 +- 18 files changed, 7323 insertions(+), 11 deletions(-) create mode 100644 extlib/inconsolata/INFO.txt create mode 100644 extlib/inconsolata/Inconsolata.otf create mode 100644 extlib/inconsolata/Inconsolata.pfa create mode 100644 extlib/inconsolata/Inconsolata.sfd create mode 100644 extlib/inconsolata/OFL_1.1.txt create mode 100644 extlib/inconsolata/textest.pdf create mode 100644 mediagoblin/media_types/ascii/__init__.py create mode 100644 mediagoblin/media_types/ascii/asciitoimage.py create mode 120000 mediagoblin/media_types/ascii/fonts/Inconsolata.otf create mode 100644 mediagoblin/media_types/ascii/processing.py create mode 120000 mediagoblin/static/fonts/Inconsolata.otf create mode 100644 mediagoblin/templates/mediagoblin/media_displays/ascii.html diff --git a/extlib/inconsolata/INFO.txt b/extlib/inconsolata/INFO.txt new file mode 100644 index 00000000..61d3a0f1 --- /dev/null +++ b/extlib/inconsolata/INFO.txt @@ -0,0 +1,4 @@ +Inconsolata +----------- + +This font is found at http://www.levien.com/type/myfonts/inconsolata.html diff --git a/extlib/inconsolata/Inconsolata.otf b/extlib/inconsolata/Inconsolata.otf new file mode 100644 index 0000000000000000000000000000000000000000..348889828d894b9b57fb6435497d5c034448cb1b GIT binary patch literal 58464 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, (), +with Reserved Font Name . +Copyright (c) , (), +with Reserved Font Name . +Copyright (c) , (). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/extlib/inconsolata/textest.pdf b/extlib/inconsolata/textest.pdf new file mode 100644 index 0000000000000000000000000000000000000000..923aa349f72d4501368af9bb0adbe4696fb8227d GIT binary patch literal 22783 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 literal 0 HcmV?d00001 diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py index 6f94c714..7b9bf0d7 100644 --- a/mediagoblin/media_types/__init__.py +++ b/mediagoblin/media_types/__init__.py @@ -69,16 +69,20 @@ def get_media_type_and_manager(filename): ''' Get the media type and manager based on a filename ''' - for media_type, manager in get_media_managers(): - if filename.find('.') > 0: - # Get the file extension - ext = os.path.splitext(filename)[1].lower() - else: - raise InvalidFileType( - _('Could not find any file extension in "{filename}"').format( - filename=filename)) + if filename.find('.') > 0: + # Get the file extension + ext = os.path.splitext(filename)[1].lower() + else: + raise Exception( + _(u'Could not extract any file extension from "{filename}"').format( + filename=filename)) + for media_type, manager in get_media_managers(): # Omit the dot from the extension and match it against # the media manager if ext[1:] in manager['accepted_extensions']: return media_type, manager + else: + raise FileTypeNotSupported( + # TODO: Provide information on which file types are supported + _(u'Sorry, I don\'t support that file type :(')) diff --git a/mediagoblin/media_types/ascii/__init__.py b/mediagoblin/media_types/ascii/__init__.py new file mode 100644 index 00000000..21b31d0e --- /dev/null +++ b/mediagoblin/media_types/ascii/__init__.py @@ -0,0 +1,27 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from mediagoblin.media_types.ascii.processing import process_ascii + + +MEDIA_MANAGER = { + "human_readable": "ASCII", + "processor": process_ascii, # alternately a string, + # 'mediagoblin.media_types.image.processing'? + "display_template": "mediagoblin/media_displays/ascii.html", + "default_thumb": "images/media_thumbs/ascii.jpg", + "accepted_extensions": [ + "txt"]} diff --git a/mediagoblin/media_types/ascii/asciitoimage.py b/mediagoblin/media_types/ascii/asciitoimage.py new file mode 100644 index 00000000..39c75a19 --- /dev/null +++ b/mediagoblin/media_types/ascii/asciitoimage.py @@ -0,0 +1,172 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import Image +import ImageFont +import ImageDraw +import logging +import pkg_resources +import os + +_log = logging.getLogger(__name__) + +class AsciiToImage(object): + ''' + Converter of ASCII art into image files, preserving whitespace + + kwargs: + - font: Path to font file + default: fonts/Inconsolata.otf + - font_size: Font size, ``int`` + default: 11 + ''' + + # Font file path + _font = None + + _font_size = 11 + + # ImageFont instance + _if = None + + # ImageFont + _if_dims = None + + # Image instance + _im = None + + def __init__(self, **kw): + if kw.get('font'): + self._font = kw.get('font') + else: + self._font = pkg_resources.resource_filename( + 'mediagoblin.media_types.ascii', + os.path.join('fonts', 'Inconsolata.otf')) + + if kw.get('font_size'): + self._font_size = kw.get('font_size') + + _log.info('Setting font to {0}, size {1}'.format( + self._font, + self._font_size)) + + self._if = ImageFont.truetype( + self._font, + self._font_size) + + # ,-,-^-'-^'^-^'^-'^-. + # ( I am a wall socket )Oo, ___ + # `-.,.-.,.-.-.,.-.--' ' ` + # Get the size, in pixels of the '.' character + self._if_dims = self._if.getsize('.') + # `---' + + def convert(self, text, destination): + # TODO: Detect if text is a file-like, if so, act accordingly + im = self._create_image(text) + + # PIL's Image.save will handle both file-likes and paths + if im.save(destination): + _log.info('Saved image in {0}'.format( + destination)) + + def _create_image(self, text): + ''' + Write characters to a PIL image canvas. + + TODO: + - Character set detection and decoding, + http://pypi.python.org/pypi/chardet + ''' + # TODO: Account for alternative line endings + lines = text.split('\n') + + line_lengths = [len(i) for i in lines] + + # Calculate destination size based on text input and character size + im_dims = ( + max(line_lengths) * self._if_dims[0], + len(line_lengths) * self._if_dims[1]) + + _log.info('Destination image dimensions will be {0}'.format( + im_dims)) + + im = Image.new( + 'RGBA', + im_dims, + (255, 255, 255, 0)) + + draw = ImageDraw.Draw(im) + + char_pos = [0, 0] + + for line in lines: + line_length = len(line) + + _log.debug('Writing line at {0}'.format(char_pos)) + + for _pos in range(0, line_length): + char = line[_pos] + + px_pos = self._px_pos(char_pos) + + _log.debug('Writing character "{0}" at {1} (px pos {2}'.format( + char, + char_pos, + px_pos)) + + draw.text( + px_pos, + char, + font=self._if, + fill=(0, 0, 0, 255)) + + char_pos[0] += 1 + + # Reset X position, increment Y position + char_pos[0] = 0 + char_pos[1] += 1 + + return im + + def _px_pos(self, char_pos): + ''' + Helper function to calculate the pixel position based on + character position and character dimensions + ''' + px_pos = [0, 0] + for index, val in zip(range(0, len(char_pos)), char_pos): + px_pos[index] = char_pos[index] * self._if_dims[index] + + return px_pos + + +if __name__ == "__main__": + import urllib + txt = urllib.urlopen('file:///home/joar/Dropbox/ascii/install-all-the-dependencies.txt') + + _log.setLevel(logging.DEBUG) + logging.basicConfig() + + converter = AsciiToImage() + + converter.convert(txt.read(), '/tmp/test.png') + + ''' + im, x, y, duration = renderImage(h, 10) + print "Rendered image in %.5f seconds" % duration + im.save('tldr.png', "PNG") + ''' diff --git a/mediagoblin/media_types/ascii/fonts/Inconsolata.otf b/mediagoblin/media_types/ascii/fonts/Inconsolata.otf new file mode 120000 index 00000000..4e742b5e --- /dev/null +++ b/mediagoblin/media_types/ascii/fonts/Inconsolata.otf @@ -0,0 +1 @@ +../../../../extlib/inconsolata/Inconsolata.otf \ No newline at end of file diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py new file mode 100644 index 00000000..a74690c1 --- /dev/null +++ b/mediagoblin/media_types/ascii/processing.py @@ -0,0 +1,93 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +import asciitoimage +import chardet +import os +import Image + +from mediagoblin import mg_globals as mgg +from mediagoblin.processing import create_pub_filepath, THUMB_SIZE + + +def process_ascii(entry): + ''' + Code to process a txt file + ''' + workbench = mgg.workbench_manager.create_workbench() + # Conversions subdirectory to avoid collisions + conversions_subdir = os.path.join( + workbench.dir, 'conversions') + os.mkdir(conversions_subdir) + + queued_filepath = entry['queued_media_file'] + queued_filename = workbench.localized_file( + mgg.queue_store, queued_filepath, + 'source') + + queued_file = file(queued_filename, 'rb') + + with queued_file: + queued_file_charset = chardet.detect(queued_file.read()) + + queued_file.seek(0) # Rewind the queued file + + thumb_filepath = create_pub_filepath( + entry, 'thumbnail.png') + + tmp_thumb_filename = os.path.join( + conversions_subdir, thumb_filepath[-1]) + + converter = asciitoimage.AsciiToImage() + + thumb = converter._create_image( + queued_file.read()) + + with file(tmp_thumb_filename, 'w') as thumb_file: + thumb.thumbnail(THUMB_SIZE, Image.ANTIALIAS) + thumb.save(thumb_file) + + mgg.public_store.copy_local_to_storage( + tmp_thumb_filename, thumb_filepath) + + queued_file.seek(0) + + original_filepath = create_pub_filepath(entry, queued_filepath[-1]) + + with mgg.public_store.get_file(original_filepath, 'wb') \ + as original_file: + original_file.write(queued_file.read()) + + + queued_file.seek(0) # Rewind *again* + + unicode_filepath = create_pub_filepath(entry, 'unicode.txt') + + with mgg.public_store.get_file(unicode_filepath, 'wb') \ + as unicode_file: + unicode_file.write( + unicode(queued_file.read().decode( + queued_file_charset['encoding'])).encode( + 'ascii', + 'xmlcharrefreplace')) + + mgg.queue_store.delete_file(queued_filepath) + entry['queued_media_file'] = [] + media_files_dict = entry.setdefault('media_files', {}) + media_files_dict['thumb'] = thumb_filepath + media_files_dict['unicode'] = unicode_filepath + media_files_dict['original'] = original_filepath + + entry.save() diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index ecdd0474..382ba88a 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -402,3 +402,15 @@ table.media_panel th { margin-top: 10px; margin-left: 10px; } + +/* ASCII art */ + +@font-face { + font-family: Inconsolata; + src: local('Inconsolata'), url('../fonts/Inconsolata.otf') format('opentype') +} + +.ascii-wrapper pre { + font-family: Inconsolata, monospace; + line-height: 1em; +} \ No newline at end of file diff --git a/mediagoblin/static/fonts/Inconsolata.otf b/mediagoblin/static/fonts/Inconsolata.otf new file mode 120000 index 00000000..777be657 --- /dev/null +++ b/mediagoblin/static/fonts/Inconsolata.otf @@ -0,0 +1 @@ +../../../extlib/inconsolata/Inconsolata.otf \ No newline at end of file diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py index 4e4c7c43..443d0e52 100644 --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@ -128,9 +128,13 @@ def submit_start(request): return redirect(request, "mediagoblin.user_pages.user_home", user=request.user.username) - except InvalidFileType, exc: + except Exception as e: + ''' + This section is intended to catch exceptions raised in + mediagobling.media_types + ''' submit_form.file.errors.append( - _(u'Invalid file type.')) + e) return render_to_response( request, diff --git a/mediagoblin/templates/mediagoblin/media_displays/ascii.html b/mediagoblin/templates/mediagoblin/media_displays/ascii.html new file mode 100644 index 00000000..9e77066a --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/ascii.html @@ -0,0 +1,40 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +#} + +{% extends 'mediagoblin/user_pages/media.html' %} + +{% block mediagoblin_media %} +
+
+      {%- autoescape False -%}
+      {{- request.app.public_store.get_file(
+             media['media_files']['unicode']).read()|string -}}
+      {%- endautoescape -%}
+    
+
+ {% if 'original' in media.media_files %} +

+ + {%- trans -%} + Original + {%- endtrans -%} + +

+ {% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/media_displays/image.html b/mediagoblin/templates/mediagoblin/media_displays/image.html index ad60fa94..94420e89 100644 --- a/mediagoblin/templates/mediagoblin/media_displays/image.html +++ b/mediagoblin/templates/mediagoblin/media_displays/image.html @@ -1 +1,19 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +#} + {% extends 'mediagoblin/user_pages/media.html' %} diff --git a/mediagoblin/templates/mediagoblin/media_displays/video.html b/mediagoblin/templates/mediagoblin/media_displays/video.html index ada50e28..fc08f963 100644 --- a/mediagoblin/templates/mediagoblin/media_displays/video.html +++ b/mediagoblin/templates/mediagoblin/media_displays/video.html @@ -1,3 +1,21 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +#} + {% extends 'mediagoblin/user_pages/media.html' %} {% block mediagoblin_media %} diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index 7c372745..4a0543a8 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -1,3 +1,4 @@ + # GNU MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. # @@ -16,6 +17,7 @@ import urlparse import pkg_resources +import re from nose.tools import assert_equal, assert_true, assert_false @@ -216,7 +218,8 @@ class TestSubmission: context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/submit/start.html'] form = context['submit_form'] - assert form.file.errors == [u'Invalid file type.'] + assert re.match(r'^Could not extract any file extension from ".*?"$', str(form.file.errors[0])) + assert len(form.file.errors) == 1 # NOTE: The following 2 tests will ultimately fail, but they # *will* pass the initial form submission step. Instead, -- 2.25.1