Announcing djangoembed, rich media consuming and providing with Django

I'm pleased to announce the release of djangoembed, a django app for consuming and providing rich media.

What is OEmbed?

OEmbed is a format for allowing a rich representation of a url. If you've used Facebook you've probably seen this feature before -- linking a YouTube video will embed an actual video player in the news feed, automatically. The player is represented by some HTML, plus there may be additional metadata like the author, a link to their channel, the title of the video, or even a thumbnail.

How it works

OEmbed is pretty simple from a consuming standpoint -- you take a url, send it to a providers' API endpoint, and get back some JSON with the requisite metadata.

For example:

curl http://www.youtube.com/oembed?url=http://www.youtube.com/watch?v=u1ZGIrNf71Q
{'author_name': '666sonic666',
 'author_url': 'http://www.youtube.com/user/666sonic666';,
 'height': 344,
 'html': '<object width="425" height="344">...(omitted)...</object>',
 'provider_name': 'YouTube',
 'provider_url': 'http://www.youtube.com/';,
 'thumbnail_height': 360,
 'thumbnail_url': 'http://i2.ytimg.com/vi/u1ZGIrNf71Q/hqdefault.jpg';,
 'thumbnail_width': 480,
 'title': 'Monkey Drummer - Chris Cunningham + Aphex Twin',
 'type': 'video',
 'version': '1.0',
 'width': 425}

Several of the bigger media sites offer support for OEmbed directly, including YouTube, flickr, Vimeo and for those that don't, a third party named embed.ly can provide OEmbed compatible metadata for over 100 sites. djangoembed ships preconfigured with support for over 60 third party providers.

Example oembed requests

Consuming Rich Media

djangoembed provides several different utilities for converting links into rich media. The most commonly used would be the templatetag and filter.

{% load oembed_tags %}
{{ blog.content|oembed:"600x600" }}

... elsewhere ...

{% oembed %}{{ blog.additional_content }}{% endoembed %}

This will run the blog entry's content through a parser (more on these in a moment) and return with all oembed-able links converted to photos, videos, etc.

Parsers are configurable

There are currently three different parsers:

  • Block parser will take a block of text and whenever it finds an oembed-able url will convert it.
  • Text parser, instead, will only replace links that occur on their own line with a rich object.
  • HTML parser is aware of markup and will only attempt to embed urls that are not already linked up

OEmbed by default will use the HTML parser (which when given plain text works like the Text parser), but which parser you use can easily be configured.

Providing rich media

djangoembed, like the admin site, supports an internal registry of rich media providers. By default, these are all third party, but that doesn't mean you can't provide (and consume) your own rich content. The tests contain examples of what an OEmbed provider looks like. Essentially, you need to specify a few things, then fill in any additional metadata you'd like to provide.

class Photo(models.Model):
    name = models.CharField(max_length=255)
    image = models.ImageField(upload_to='images')
    width = models.IntegerField(blank=True, null=True, editable=False)
    height = models.IntegerField(blank=True, null=True, editable=False)

    def get_absolute_url(self):
        return reverse('photo_detail', args=[self.pk])

class PhotoProvider(DjangoProvider):
    resource_type = 'photo'

    class Meta:
        model = Photo # or, optionally/additionally a queryset
        named_view = 'photo_detail' # a named view, almost always the detail page
        fields_to_match = {'photo_id': 'pk'} # a mapping of url params -> model params

    def title(self, obj):
        return obj.name

Like the admin site, and a number of other reusable apps, djangoembed has the convention of storing all providers in a certain module in your app directories -- oembed_providers.py -- which is then autodiscover-able. If you'd like your project's content to be oembed-able from other sites, simply include the oembed urls in your urlconf.

curl http://charlesleifer.com/oembed/json/?url=http://charlesleifer.com/photos/1337/
{"provider_url": "http://charlesleifer.com";, 
 "title": "1337",
 "url": "1337.jpg;, 
 "thumbnail_width": 200, 
 "height": 420, 
 "width": 560, 
 "version": "1.0", 
 "author_name": "Charles Leifer", 
 "provider_name": "charlesleifer.com", 
 "thumbnail_url": "1337_200x150.jpg;, 
 "type": "photo", 
 "thumbnail_height": 150, 
 "author_url": "http://www.charlesleifer.com/";
}

What now?

Currently the docs are in need of being fleshed out. If you're interested in what is possible, the tests are a good place to start.

Links

Comments (5)

  1. Just curious: How does this differ from Eric Florenzano's django_oembed?

  2. Eric's project (django-oembed) was the jumping off point for djangoembed. django-oembed provides support for consuming oembed-able urls in plain text, but the consumer is not aware of markup. Besides the difference in consumers, the biggest difference is that djangoembed offers support for providing content. There are utilities/classes that make it easy to turn your own models into oembed-able objects. I work in the news industry, and we've been using djangoembed to both provide and consume our own news stories and photos.

  3. Also seems to require lxml (pythonic binding for the libxml2 and libxslt libraries). If that's in the install notes, I missed it!

  4. It looks better than django-oembed. I'll try, but there is a problem with TextField as unique in MySQL (match field in StoredOEmbed). http://code.djangoproject.com/ticket/...>

  5. Arthur, thanks for you comment! An issue has been created on the repo and I'll look at fixing this. The reason for the constraint is because of a race condition with get_or_create(), so I thought I'd make it enforce unique at the db level. I think the quick hack is to slap a conditional in the migration, but I'm not sure if that'll work. You can get along without it, though you might occasionally see MultipleObjectsReturned exceptions. Thanks again for your comment!

    We're tracking the issue here: http://github.com/worldcompany/django...>

Commenting has been disabled for this entry

If you'd like to discuss an aspect of this post, feel free to contact me via email.