Monday, June 28, 2010


  #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2008 Sebastian Wiesner <>  # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms of the Do What The Fuck You Want # To Public License, Version 2, as published by Sam Hocevar. See # for more details.   """     upload_image     ============      Cmdline script, which uploads to different image hosters.      :author: Sebastian Wiesner     :contact:     :copyright: 2008 by Sebastian Wiesner     :license: WTFPL """  from __future__ import print_function  import os import sys import imghdr import urllib2 import webbrowser import subprocess from cStringIO import StringIO  import argparse import ClientForm import lxml.html from lxml.cssselect import CSSSelector   def detect_mimetype(img):     """Detects mime type of `img`.  Returns None, if mime type couldn't be     determined.      :type img: str or file-like object supporting ``read``, ``tell`` and                ``seek``     """     format = imghdr.what(img)     return ('image/' + format if format is not None else None)   class Service(object):     """Abstract class for services.  Each deriving class must re-define the     class variables.      :CVariables:     - `url`: Website of the service     - `name`:  A descriptive name of the upload service     """      def _fill_missing_arguments(self, image, mime_type, filename):         """Fills missing arguments to upload_file.  If `mimetype` is         ``None``, the mime type of `image` is guessed with         `detect_mimetype`.          If `filename` is ``None``, this method attempts to get the filename         from the `name` attribute of the stream denoted by `image`.  If this         fails, it sets the name to \"foo\".          All three arguments are returned properly initialized.  Note, that         the caller is responsible for proper closing of the returned         file-like object.          :returns: ``(image, mime_type, filename)``         :returntype: ``(file-like object, str, str)``         """         if mime_type is None:             mime_type = detect_mimetype(image)         if mime_type is None:             raise IOError('Couldn\'t detect mimetype of {0}'.format(image))          stream = (open(image, 'rb') if isinstance(image, basestring)                   else image)          if filename is None:             filename = getattr(stream, 'name', None)         if filename is None:             raise ValueError('No filename given')          return stream, mime_type, os.path.basename(filename)      def upload_image(self, image, mime_type=None, filename=None):         """Uploads `image` with `mime_type` as `filename`.          Returns ``(image_link, bbcode, links)``, where ``image_link`` is         direct link to the image, ``bbcode`` a bbcode line useful for         webforums and ``links`` the complete list of all links."""         raise NotImplementedError()      def __repr__(self):         if hasattr(self, 'name'):             return '<Service "{}" at {1}>'.format(self, id(self))         else:             return object.__repr__(self)   class WebFormService(Service):     """Service, which parses web forms, fills them and returns a list of     forms extracted from the web site.      Subclasses of this class should have an attribute ``webform_url``,     denoting the webform to parse, and must reimplement ``_get_links``.     """      def upload_image(self, image, mime_type=None, filename=None):         """Uploads `image` with `mime_type` as `filename`. Missing         values are autodetected.         """         html = self._get_webform_html()         forms = ClientForm.ParseResponse(html, backwards_compat=False)         upload_form = self._find_upload_webform(forms)         stream, mime_type, filename = self._fill_missing_arguments(             image, mime_type, filename)         try:             upload_form.add_file(stream, mime_type, filename)             self._fill_additional_form_fields(upload_form)             request =             response = urllib2.urlopen(request)             return self._get_links(response)         finally:             stream.close()      def _fill_additional_form_fields(self, form):         """Called to fill additional fields in `form`, if necessary.  The         default implementation does nothing.          The return value of this method is ignored.         """         pass       def _get_links(self, response):         """Extracts all links from `response`, which is a urllib2 response         object as returned by ``urllib2.urlopen``.          It must return ``(image_link, bbcode, links)``, where ``image_link``         is the direct link to the image, ``bbcode`` is a bbcode line usable         for webforums and ``links`` is the complete list of links."""         raise NotImplementedError()      def _get_webform_html(self):         """Returns html code of the upload webform.  The default         implementation uses ``urllib2.urlopen`` with ``self.webform_url`` to         retrieve the html code.          Subclasses can reimplement this, to customize loading."""         return urllib2.urlopen(self.webform_url)      def _find_upload_webform(self, forms):         """Finds the form which handles the upload.  The default         implementation blindly iterates over all forms and returns the one,         which has a file upload field.          Raises `ClientForm.ControlNotFoundError`, if no upload webform         was found."""         for form in forms:             try:                 form.find_control(type='file')                 return form             except ClientForm.ControlNotFoundError, err:                 # throw away forms, that don't have an upload control                 pass         else:             raise err   class ImageBananaService(WebFormService):     """Uploads images to"""      url = ''     name = 'ImageBanana'     webform_url = url     find_url_elements = CSSSelector('input .input_text')      def _get_links(self, response):         tree = lxml.html.parse(response)         urls = [el.get('value') for el in self.find_url_elements(tree)]         return urls[1], urls[3], urls   class UbuntuPicsService(WebFormService):     """Uploads images to"""      url = ''     name = 'UbuntuPics'     webform_url = ''      def _get_links(self, response):         tree = lxml.html.parse(response)         content = tree.getroot().get_element_by_id('content')         direct = content.get_element_by_id('direct').get('value')         bbcode = content.get_element_by_id('bbcode').get('value')         urls = content.xpath('input/attribute::value')         return direct, bbcode, urls   def copy_to_clipboard(text):     """Copy `text` to clipboard."""     p = subprocess.Popen(["xsel", "-i"], stdin=subprocess.PIPE)     return p.communicate(text)   def get_all_services():     objects = globals().itervalues()     classes = (obj for obj in objects if isinstance(obj, type))     services = (cls for cls in classes if issubclass(cls, Service))     return dict(((, s) for s in services if hasattr(s, 'name')))   SERVICES = get_all_services()   class ListServices(argparse.Action):     def __init__(self, *args, **kwargs):         if not 'nargs' in kwargs:             kwargs['nargs'] = 0         super(ListServices, self).__init__(*args, **kwargs)      def __call__(self, parser, namespace, values, option_string=None):         table = []         widths = [0, 0]         for service in SERVICES.itervalues():             row = [, service.url]             widths = map(max, zip(widths, map(len, row)))             table.append(row)         line_tmpl = '{0[0]:<{1[0]}} - {0[1]:<{1[1]}}'         for row in table:             print(line_tmpl.format(row, widths))         parser.exit()   def _parse_args():     parser = argparse.ArgumentParser(epilog=""" (C) 2008  Sebastian Wiesner, licensed under the terms of WTFPL 2.""",                           description=""" Uploads images. If you don't specify a file name, the image is read from standard input. An image read from standard input will be called \"stdin\" unless you specify an explicit filename using the --filename option.""")     parser.add_argument('--list-services', action=ListServices,                         help='List all upload services and exit.')     parser.add_argument('image', nargs='?', help='Image to upload. '                         '"-" means standard input, which is the default '                         'if unspecified.',)     upload_args = parser.add_argument_group('Upload settings',                                             'How to upload a file?')     upload_args.add_argument('-s', '--service', choices=SERVICES,                              help='Load image up to SERVICE. Defaults to '                              '"UbuntuPics". The default can be changed '                              'through the UPLOAD_IMAGE_DEFAULT_SERVICE '                              'environment variable.')     upload_args.add_argument('-f', '--filename', metavar='NAME',                              help='Transmits NAME as filename to the '                              'server. Defaults to local filename, or '                              '"stdin", if image data comes from standard '                              'input.')     upload_args.add_argument('-m', '--mime-type', metavar='TYPE',                              help='Force mimetype to be TYPE. If '                              'unspecified, mime type is auto-detected from '                              'image format.')     result_args = parser.add_argument_group('Result handling',                                             'What to do with the result of '                                             'the upload?')     result_args.add_argument('-c', '--copy',                              choices=['no', 'bbcode', 'image'],                              help='What to copy into clipboard?')     result_args.add_argument('-b', '--browser', action='store_true',                              help='If set, image url is opened in '                              'webbrowser.')     misc_args = parser.add_argument_group('Misc settings')     misc_args.add_argument('-d', '--delete', action='store_true',                            help='Delete the file after uploading (use with '                            'care!)', dest='delete')     def_service = os.environ.get('UPLOAD_IMAGE_DEFAULT_SERVICE',                             parser.set_defaults(copy='bbcode', service=def_service, image='-')     return parser.parse_args()   def main():     try:         # setup localized environment         import locale         locale.setlocale(locale.LC_ALL, '')         args = _parse_args()         print('Uploading to {0.service}...'.format(args), end='\n'*2)         # finalize command line arguments         if args.filename is None:             args.filename = (args.image if args.image != '-' else 'stdin')         if args.image == '-':             args.image = StringIO(         try:             # upload image and print return values             service = SERVICES[args.service]()             image, bbcode, urls = service.upload_image(                 args.image, args.mime_type, args.filename)             urls.sort()             urls.reverse()             print('bbcode:', bbcode)             print('image:', image, end='\n'*2)             for url in urls:                 print(url)         except ClientForm.ControlNotFoundError:             sys.exit('Website at {0.url} doesn\'t support file '                      'uploads'.format(service))         if args.copy != 'no':             try:                 copy_to_clipboard(locals()[args.copy])             except OSError as err:                 sys.exit(str(err))         if args.browser:             webbrowser.open_new_tab(image)         if args.delete:             os.remove(image)     except KeyboardInterrupt:         pass   if __name__ == '__main__':     main()
  upload_image – Command line image uploading upload_image automates some image hosting sites to allow uploading files from command line. Download upload_image Installation You need Python (at least version 2.6) installed and working. Installation of the script itself is rather easy, just place it somewhere in your $PATH. More complex is the installation of its dependencies: argparse: Easy and convenient command line parsing ClientForm: Webform automation lxml: HTML parsing and scraping All these packages are avialable on the PyPI: and can therefore be installed with easy_install: easy_install argparse ClientForm lxml>=2 If you want to have clipboard support, you will also need to manually install the xsel utility. Usage upload_image is very simple. It uploads the specified input file, or reads the image data from standard input. A single dash (-) as file name will read from standard input, too: # upload_image foobar.png There are some options that fine-tune the behaviour of this script. The --help gives an overview over all supported options, the most important are presented here. -f name, --filename name Sends name as filename to the server. This option defaults to the filename of the local file or to stdin, if data is read from standard input. The following will read a image file from standard input and send it to the server as foobar.png: # upload_image -f foobar.png < my_image.png -m type, --mime-type type Allows to overwrite the mimetype send to the server. Normally the type is detected from the image header using python's standard imghdr module. -s {UbuntuPics,ImageBanana}, --service service {UbuntuPics,ImageBanana} This option allows to specify the image hosting service where to upload the image file. Currently two services are supported, Image Banana and Ubuntu Pics. The default is read from UPLOAD_IMAGE_DEFAULT_SERVICE. If this is unset, Ubuntu Pics is used. Warning   Use of this script with any of the above services is at your own risk! The existence of this script doesn't mean, that the admins of these services appreciate its use with their sites. UPLOAD_IMAGE_DEFAULT_SERVICE This environment variable provides the default service for upload_image. Set this in your shell initialisation file (like~/.bashrc or ~/.zshrc) to switch to a specific service permanently. Note   The -s option overrules this setting! After a successful upload the script parses all links returned by the website and prints them on standard output. The link pointing to the image itself and the phpbb-compatible bbcode are printed first. Two options exist to work with the results from the web server: -c {no,bbcode,image}, --copy {no,bbcode,image} This option selects a link to be copied into clipboard. This needs the xsel utility. no copies nothing, bbcode copies a phpbb-compatible bbcode into clipboard, image copies to full url to the image. -b, --browser This options opens the image in your favourite webbrowser. You should set $BROWSER to something reasonable for this to work correctly. Example You often want to quickly take some screenshot to illustrate your problems or explanations in some webforum or in the usenet. I use the following command line to accomplish this conventiently: import png:- | upload_image -f shot.png -c bbcode This takes a screenshot using the import utility from the ImageMagick toolkit to take a screenshot and write as PNG file to standard output. This output is then piped to upload_image, which uploads the image as shot.png and copies the bbcode link into clipboard for use in your webbrowser. If creating a screenshot for the usenet, I use -c image instead to copy the full image url. Additional services Extending the script to support additional services shouldn't be that difficult. Contact me or send me a patch, if you want support for another image hosting site. License This programm is free software, you can redistribute and/or modify it under the terms of the WTFPL 2. 

No comments: