代码
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2008 Sebastian Wiesner <lunaryorn@googlemail.com>
# 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
# http://sam.zoy.org/wtfpl/COPYING for more details.
"""
upload_image
============
Cmdline script, which uploads to different image hosters.
:author: Sebastian Wiesner
:contact: lunaryorn@googlemail.com
: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 "{0.name}" 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 = upload_form.click()
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 imagabanana.de"""
url = 'http://www.imagebanana.de/'
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 pics.ubuntu-projekte.de"""
url = 'http://www.ubuntu-pics.de/'
name = 'UbuntuPics'
webform_url = 'http://www.ubuntu-pics.de/easy.html'
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.name, 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.name, 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',
UbuntuPicsService.name)
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(sys.stdin.read())
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:
Post a Comment