├── oembed ├── __init__.py ├── templatetags │ ├── __init__.py │ └── oembed_tags.py ├── views.py ├── templates │ └── oembed │ │ ├── link.html │ │ ├── rich.html │ │ ├── video.html │ │ └── photo.html ├── locale │ └── de │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── admin.py ├── models.py ├── tests.py ├── core.py └── fixtures │ └── initial_data.json ├── .gitignore ├── tests ├── settings.py └── runtests.py ├── CONTRIBUTORS.txt ├── MANIFEST.in ├── docs ├── index.txt ├── readme.txt ├── usage.txt └── installation.txt ├── README.txt ├── setup.py ├── INSTALL.txt └── LICENSE.txt /oembed/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oembed/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /oembed/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /oembed/templates/oembed/link.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %}{{ response.html }}{% endautoescape %} -------------------------------------------------------------------------------- /oembed/templates/oembed/rich.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %}{{ response.html }}{% endautoescape %} -------------------------------------------------------------------------------- /oembed/templates/oembed/video.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %}{{ response.html }}{% endautoescape %} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | *.swp 4 | *.lock 5 | *.pid 6 | *.svn 7 | build 8 | dist 9 | *.egg-info 10 | -------------------------------------------------------------------------------- /oembed/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jezdez/django-oembed/master/oembed/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /oembed/templates/oembed/photo.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %}{{ response.title }}{% endautoescape %} -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | DATABASE_ENGINE = 'sqlite3' 2 | ROOT_URLCONF = '' 3 | SITE_ID = 1 4 | INSTALLED_APPS = ( 5 | 'oembed', 6 | ) 7 | -------------------------------------------------------------------------------- /oembed/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from oembed.models import ProviderRule, StoredOEmbed 4 | 5 | admin.site.register(ProviderRule) 6 | admin.site.register(StoredOEmbed) -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | Eric Florenzano 2 | Idan Gazit 3 | Jannis Leidel 4 | Nathan Borror 5 | Thejaswi Puthraya 6 | Brian Rosner 7 | Jonathan Stasiak -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTORS.txt 2 | include INSTALL.txt 3 | include LICENSE.txt 4 | include README.txt 5 | recursive-include docs *.txt 6 | include oembed/fixtures/initial_data.json 7 | recursive-include oembed/locale/* *.po *.mo 8 | recursive-include oembed/templates *.html 9 | -------------------------------------------------------------------------------- /docs/index.txt: -------------------------------------------------------------------------------- 1 | ############# 2 | django-oembed 3 | ############# 4 | 5 | Django-oembed is a collection of Django tools which make it easy to change text 6 | filled with oembed links into the embedded objects themselves. 7 | 8 | Contents: 9 | 10 | .. toctree:: 11 | 12 | readme.txt 13 | installation.txt 14 | usage.txt 15 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | ======================= 2 | django-oembed 3 | ======================= 4 | 5 | This is a collection of tools for Django to allow for replacing links in text 6 | with OEmbed. This application also provides utilities to make this process not 7 | prohibitively expensive CPU-wise. 8 | 9 | For installation instructions, read INSTALL.txt. 10 | 11 | Visit the google code page at http://django-oembed.googlecode.com/ -------------------------------------------------------------------------------- /docs/readme.txt: -------------------------------------------------------------------------------- 1 | ======================= 2 | django-oembed 3 | ======================= 4 | 5 | This is a collection of tools for Django to allow for replacing links in text 6 | with OEmbed. This application also provides utilities to make this process not 7 | prohibitively expensive CPU-wise. 8 | 9 | For installation instructions, read INSTALL.txt. 10 | 11 | Visit the google code page at http://django-oembed.googlecode.com/ -------------------------------------------------------------------------------- /tests/runtests.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('..') 3 | 4 | import os 5 | # Make a backup of DJANGO_SETTINGS_MODULE environment variable to restore later. 6 | backup = os.environ.get('DJANGO_SETTINGS_MODULE', '') 7 | os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' 8 | 9 | from django.test.simple import run_tests 10 | 11 | if __name__ == "__main__": 12 | failures = run_tests(['oembed',], verbosity=9) 13 | if failures: 14 | sys.exit(failures) 15 | # Reset the DJANGO_SETTINGS_MODULE to what it was before running tests. 16 | os.environ['DJANGO_SETTINGS_MODULE'] = backup 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-oembed', 5 | version='0.1.3', 6 | description='A collection of Django tools which make it easy to change' 7 | 'text filled with oembed links into the embedded objects themselves.', 8 | author='Eric Florenzano', 9 | author_email='floguy@gmail.com', 10 | url='http://django-oembed.googlecode.com', 11 | packages=find_packages(), 12 | classifiers=[ 13 | 'Development Status :: 3 - Alpha', 14 | 'Environment :: Web Environment', 15 | 'Intended Audience :: Developers', 16 | 'License :: OSI Approved :: BSD License', 17 | 'Operating System :: OS Independent', 18 | 'Programming Language :: Python', 19 | 'Framework :: Django', 20 | ], 21 | include_package_data=True, 22 | zip_safe=False, 23 | ) 24 | -------------------------------------------------------------------------------- /oembed/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.db import models 3 | from django.utils.translation import ugettext_lazy as _ 4 | 5 | JSON = 1 6 | XML = 2 7 | FORMAT_CHOICES = ( 8 | (JSON, "JSON"), 9 | (XML, "XML"), 10 | ) 11 | 12 | class ProviderRule(models.Model): 13 | name = models.CharField(_("name"), max_length=128, null=True, blank=True) 14 | regex = models.CharField(_("regex"), max_length=2000) 15 | endpoint = models.CharField(_("endpoint"), max_length=2000) 16 | format = models.IntegerField(_("format"), choices=FORMAT_CHOICES) 17 | 18 | def __unicode__(self): 19 | return self.name or self.endpoint 20 | 21 | 22 | class StoredOEmbed(models.Model): 23 | match = models.TextField(_("match")) 24 | max_width = models.IntegerField(_("max width")) 25 | max_height = models.IntegerField(_("max height")) 26 | html = models.TextField(_("html")) 27 | date_added = models.DateTimeField(_("date added"), default=datetime.datetime.now) 28 | 29 | def __unicode__(self): 30 | return self.match 31 | 32 | -------------------------------------------------------------------------------- /docs/usage.txt: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | First you must add it to your INSTALLED_APPS:: 5 | 6 | INSTALLED_APPS = ( 7 | .... 8 | 'oembed', 9 | ) 10 | 11 | Then in your template, include the oembed tags:: 12 | 13 | {% load oembed_tags %} 14 | 15 | Then, surround something with the oembed tag. It will search for oembed-able links and replace them with the proper embed:: 16 | 17 | {% oembed %} 18 | There is this great video at http://www.viddler.com/explore/SYSTM/videos/49/ 19 | {% endoembed %} 20 | 21 | Will result in:: 22 | 23 | There is this great video at 32 | 33 | There is an optional width and height parameter, that can be invoked thusly:: 34 | 35 | {% oembed 320x240 %}...{% endoembed %} -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | Thanks for downloading django-oembed! 2 | 3 | To install it, first use subversion to check out the source code: 4 | 5 | svn checkout http://django-oembed.googlecode.com/svn/trunk/ django-oembed 6 | 7 | Now, link the included ``oembed`` directory to your pythonpath. On Debian 8 | variants, it would look something like this. 9 | 10 | sudo ln -s `pwd`/django-oembed/oembed /usr/lib/python2.5/site-packages/ 11 | 12 | To use it with a Django installation, first place 'oembed' in the INSTALLED_APPS 13 | tuple of your settings.py file like so: 14 | 15 | INSTALLED_APPS = ( 16 | # ... 17 | 'oembed', 18 | ) 19 | 20 | Then syncdb, and here is sample usage in a template. 21 | 22 | {% load oembed_tags %} 23 | {% oembed %} 24 | {% for link in links %}{{ link.href }}{% endfor %} 25 | {% endoembed %} 26 | 27 | In the previous example, any link.href would be replaced with an OEmbed-fetched 28 | embed. 29 | 30 | The templatetag takes one optional second argument, which you can figure out by 31 | looking at this usage: 32 | 33 | {% oembed 320x240 %} 34 | 35 | Note that this application requires Python 2.3 or later, and Django later than 36 | 0.96. You can obtain Python from http://www.python.org/ and Django from 37 | http://www.djangoproject.com/. -------------------------------------------------------------------------------- /oembed/locale/de/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: django-oembed\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2010-04-21 14:32+0200\n" 12 | "PO-Revision-Date: 2010-04-21 14:32+0200\n" 13 | "Last-Translator: Jannis Leidel \n" 14 | "Language-Team: de \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 19 | 20 | #: models.py:13 21 | msgid "name" 22 | msgstr "Name" 23 | 24 | #: models.py:14 25 | msgid "regex" 26 | msgstr "Regulärer Ausdruck" 27 | 28 | #: models.py:15 29 | msgid "endpoint" 30 | msgstr "Endpunkt" 31 | 32 | #: models.py:16 33 | msgid "format" 34 | msgstr "Format" 35 | 36 | #: models.py:23 37 | msgid "match" 38 | msgstr "Match" 39 | 40 | #: models.py:24 41 | msgid "max width" 42 | msgstr "Maximale Breite" 43 | 44 | #: models.py:25 45 | msgid "max height" 46 | msgstr "Maximale Höhe" 47 | 48 | #: models.py:26 49 | msgid "html" 50 | msgstr "HTML-Rohdaten" 51 | 52 | #: models.py:27 53 | msgid "date added" 54 | msgstr "Erstellungsdatum" 55 | -------------------------------------------------------------------------------- /docs/installation.txt: -------------------------------------------------------------------------------- 1 | Installing django-oembed 2 | ======================== 3 | 4 | Thanks for downloading django-oembed! 5 | 6 | To install it, first use subversion to check out the source code:: 7 | 8 | svn checkout http://django-oembed.googlecode.com/svn/trunk/ django-oembed 9 | 10 | Now, link the included ``oembed`` directory to your pythonpath. On Debian 11 | variants, it would look something like this:: 12 | 13 | sudo ln -s `pwd`/django-oembed/oembed /usr/lib/python2.5/site-packages/ 14 | 15 | To use it with a Django installation, first place 'oembed' in the INSTALLED_APPS 16 | tuple of your settings.py file like so:: 17 | 18 | INSTALLED_APPS = ( 19 | # ... 20 | 'oembed', 21 | ) 22 | 23 | Then syncdb, and here is sample usage in a template:: 24 | 25 | {% load oembed_tags %} 26 | {% oembed %} 27 | {% for link in links %}{{ link.href }}{% endfor %} 28 | {% endoembed %} 29 | 30 | In the previous example, any link.href would be replaced with an OEmbed-fetched 31 | embed. 32 | 33 | The templatetag takes one optional second argument, which you can figure out by 34 | looking at this usage:: 35 | 36 | {% oembed 320x240 %} 37 | 38 | Note that this application requires Python 2.3 or later, and Django later than 39 | 0.96. You can obtain Python from http://www.python.org/ and Django from 40 | http://www.djangoproject.com/. -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2010, Eric Florenzano 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the author nor the names of other 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /oembed/templatetags/oembed_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.template.defaultfilters import stringfilter 3 | from oembed.core import replace 4 | 5 | register = template.Library() 6 | 7 | def oembed(input, args): 8 | if args: 9 | width, height = args.lower().split('x') 10 | if not width and height: 11 | raise template.TemplateSyntaxError("Oembed's optional WIDTHxHEIGH" \ 12 | "T argument requires WIDTH and HEIGHT to be positive integers.") 13 | else: 14 | width, height = None, None 15 | return replace(input, max_width=width, max_height=height) 16 | oembed.is_safe = True 17 | oembed = stringfilter(oembed) 18 | 19 | register.filter('oembed', oembed) 20 | 21 | def do_oembed(parser, token): 22 | """ 23 | A node which parses everything between its two nodes, and replaces any links 24 | with OEmbed-provided objects, if possible. 25 | 26 | Supports one optional argument, which is the maximum width and height, 27 | specified like so: 28 | 29 | {% oembed 640x480 %}http://www.viddler.com/explore/SYSTM/videos/49/{% endoembed %} 30 | """ 31 | args = token.contents.split() 32 | if len(args) > 2: 33 | raise template.TemplateSyntaxError("Oembed tag takes only one (option" \ 34 | "al) argument: WIDTHxHEIGHT, where WIDTH and HEIGHT are positive " \ 35 | "integers.") 36 | if len(args) == 2: 37 | width, height = args[1].lower().split('x') 38 | if not width and height: 39 | raise template.TemplateSyntaxError("Oembed's optional WIDTHxHEIGH" \ 40 | "T argument requires WIDTH and HEIGHT to be positive integers.") 41 | else: 42 | width, height = None, None 43 | nodelist = parser.parse(('endoembed',)) 44 | parser.delete_first_token() 45 | return OEmbedNode(nodelist, width, height) 46 | 47 | register.tag('oembed', do_oembed) 48 | 49 | class OEmbedNode(template.Node): 50 | def __init__(self, nodelist, width, height): 51 | self.nodelist = nodelist 52 | self.width = width 53 | self.height = height 54 | 55 | def render(self, context): 56 | kwargs = {} 57 | if self.width and self.height: 58 | kwargs['max_width'] = self.width 59 | kwargs['max_height'] = self.height 60 | return replace(self.nodelist.render(context), **kwargs) 61 | -------------------------------------------------------------------------------- /oembed/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from oembed.core import replace 3 | 4 | class OEmbedTests(TestCase): 5 | noembed = ur"This is text that should not match any regex." 6 | end = ur"There is this great video at %s" 7 | start = ur"%s is a video that I like." 8 | middle = ur"There is a movie here: %s and I really like it." 9 | trailing_comma = ur"This is great %s, but it might not work." 10 | trailing_period = ur"I like this video, located at %s." 11 | 12 | locs = [u"http://www.viddler.com/explore/SYSTM/videos/49/", 13 | u"http://www.slideshare.net/hues/easter-plants", 14 | u"http://www.scribd.com/doc/28452730/Easter-Cards", 15 | u"http://screenr.com/gzS", 16 | u"http://www.5min.com/Video/How-to-Decorate-Easter-Eggs-with-Decoupage-142076462", 17 | u"http://www.howcast.com/videos/328008-How-To-Marble-Easter-Eggs", 18 | u"http://my.opera.com/nirvanka/albums/showpic.dml?album=519866&picture=7173711", 19 | u"http://img20.yfrog.com/i/dy6.jpg/", 20 | u"http://tweetphoto.com/8069529", 21 | u"http://www.flickr.com/photos/jaimewalsh/4489497178/", 22 | u"http://twitpic.com/1cm8us", 23 | u"http://imgur.com/6pLoN", 24 | u"http://twitgoo.com/1p94", 25 | u"http://www.23hq.com/Greetingdesignstudio/photo/5464607", 26 | u"http://www.youtube.com/watch?v=Zk7dDekYej0", 27 | u"http://www.veoh.com/browse/videos/category/educational/watch/v7054535EZGFJqyX", 28 | u"http://www.justin.tv/venom24", 29 | u"http://qik.com/video/1445889", 30 | u"http://revision3.com/diggnation/2005-10-06", 31 | u"http://www.dailymotion.com/video/xcss6b_big-cat-easter_animals", 32 | u"http://www.collegehumor.com/video:1682246", 33 | u"http://www.twitvid.com/BC0BA", 34 | u"http://www.break.com/usercontent/2006/11/18/the-evil-easter-bunny-184789", 35 | u"http://vids.myspace.com/index.cfm?fuseaction=vids.individual&videoid=103920940", 36 | u"http://www.metacafe.com/watch/2372088/easter_eggs/", 37 | u"http://blip.tv/file/770127", 38 | u"http://video.google.com/videoplay?docid=2320995867449957036", 39 | u"http://www.revver.com/video/1574939/easter-bunny-house/", 40 | u"http://video.yahoo.com/watch/4530253/12135472", 41 | u"http://www.viddler.com/explore/cheezburger/videos/379/", 42 | u"http://www.liveleak.com/view?i=d91_1239548947", 43 | u"http://www.hulu.com/watch/23349/nova-secrets-of-lost-empires-ii-easter-island", 44 | u"http://movieclips.com/watch/jaws_1975/youre_gonna_need_a_bigger_boat/", 45 | u"http://crackle.com/c/How_To/How_to_Make_Ukraine_Easter_Eggs/2262274", 46 | u"http://www.fancast.com/tv/Saturday-Night-Live/10009/1083396482/Easter-Album/videos", 47 | u"http://www.funnyordie.com/videos/040dac4eff/easter-eggs", 48 | u"http://vimeo.com/10429123", 49 | u"http://www.ted.com/talks/robert_ballard_on_exploring_the_oceans.html", 50 | u"http://www.thedailyshow.com/watch/tue-february-29-2000/headlines---leap-impact", 51 | u"http://www.colbertnation.com/the-colbert-report-videos/181772/march-28-2006/intro---3-28-06", 52 | u"http://www.traileraddict.com/trailer/easter-parade/trailer", 53 | u"http://www.lala.com/#album/432627041169206995/Rihanna/Rated_R", 54 | u"http://www.amazon.com/gp/product/B001EJMS6K/ref=s9_simh_gw_p200_i1?pf_rd_m=ATVPDKIKX0DER", 55 | u"http://animoto.com/s/oH9VwgjOU9hpbgYXNDwLNQ", 56 | u"http://xkcd.com/726/"] 57 | 58 | def get_oembed(self, url): 59 | try: 60 | return replace('%s' % url) 61 | except Exception, e: 62 | self.fail("URL: %s failed for this reason: %s" % (url, str(e))) 63 | 64 | def testNoEmbed(self): 65 | self.assertEquals( 66 | replace(self.noembed), 67 | self.noembed 68 | ) 69 | 70 | def testEnd(self): 71 | for loc in self.locs: 72 | embed = self.get_oembed(loc) 73 | 74 | if not embed or embed == loc: 75 | self.fail("URL: %s did not produce an embed object" % loc) 76 | 77 | for text in (self.end, self.start, self.middle, self.trailing_comma, self.trailing_period): 78 | self.assertEquals( 79 | replace(text % loc), 80 | text % embed 81 | ) 82 | 83 | def testManySameEmbeds(self): 84 | loc = self.locs[1] 85 | embed = self.get_oembed(loc) 86 | 87 | text = " ".join([self.middle % loc] * 100) 88 | resp = " ".join([self.middle % embed] * 100) 89 | self.assertEquals(replace(text), resp) -------------------------------------------------------------------------------- /oembed/core.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib2 3 | import gzip 4 | from heapq import heappush, heappop 5 | try: 6 | from cStringIO import StringIO 7 | except ImportError: 8 | from StringIO import StringIO 9 | try: 10 | import simplejson 11 | except ImportError: 12 | from django.utils import simplejson 13 | from django.conf import settings 14 | from django.utils.http import urlencode 15 | from django.utils.safestring import mark_safe 16 | from oembed.models import ProviderRule, StoredOEmbed 17 | from django.template.loader import render_to_string 18 | import logging 19 | logger = logging.getLogger("oembed core") 20 | 21 | END_OVERRIDES = (')', ',', '.', '>', ']', ';') 22 | MAX_WIDTH = getattr(settings, "OEMBED_MAX_WIDTH", 320) 23 | MAX_HEIGHT = getattr(settings, "OEMBED_MAX_HEIGHT", 240) 24 | FORMAT = getattr(settings, "OEMBED_FORMAT", "json") 25 | 26 | def fetch(url, user_agent="django-oembed/0.1"): 27 | """ 28 | Fetches from a URL, respecting GZip encoding, etc. 29 | """ 30 | request = urllib2.Request(url) 31 | request.add_header('User-Agent', user_agent) 32 | request.add_header('Accept-Encoding', 'gzip') 33 | opener = urllib2.build_opener() 34 | f = opener.open(request) 35 | result = f.read() 36 | if f.headers.get('content-encoding', '') == 'gzip': 37 | result = gzip.GzipFile(fileobj=StringIO(result)).read() 38 | f.close() 39 | return result 40 | 41 | def re_parts(regex_list, text): 42 | """ 43 | An iterator that returns the entire text, but split by which regex it 44 | matched, or none at all. If it did, the first value of the returned tuple 45 | is the index into the regex list, otherwise -1. 46 | 47 | >>> first_re = re.compile('asdf') 48 | >>> second_re = re.compile('an') 49 | >>> list(re_parts([first_re, second_re], 'This is an asdf test.')) 50 | [(-1, 'This is '), (1, 'an'), (-1, ' '), (0, 'asdf'), (-1, ' test.')] 51 | 52 | >>> list(re_parts([first_re, second_re], 'asdfasdfasdf')) 53 | [(0, 'asdf'), (0, 'asdf'), (0, 'asdf')] 54 | 55 | >>> list(re_parts([], 'This is an asdf test.')) 56 | [(-1, 'This is an asdf test.')] 57 | 58 | >>> third_re = re.compile('sdf') 59 | >>> list(re_parts([first_re, second_re, third_re], 'This is an asdf test.')) 60 | [(-1, 'This is '), (1, 'an'), (-1, ' '), (0, 'asdf'), (-1, ' test.')] 61 | """ 62 | def match_compare(x, y): 63 | return x.start() - y.start() 64 | prev_end = 0 65 | iter_dict = dict((r, r.finditer(text)) for r in regex_list) 66 | 67 | # a heapq containing matches 68 | matches = [] 69 | 70 | # bootstrap the search with the first hit for each iterator 71 | for regex, iterator in iter_dict.items(): 72 | try: 73 | match = iterator.next() 74 | heappush(matches, (match.start(), match)) 75 | except StopIteration: 76 | iter_dict.pop(regex) 77 | 78 | # process matches, revisiting each iterator from which a match is used 79 | while matches: 80 | # get the earliest match 81 | start, match = heappop(matches) 82 | end = match.end() 83 | if start > prev_end: 84 | # yield the text from current location to start of match 85 | yield (-1, text[prev_end:start]) 86 | # yield the match 87 | yield (regex_list.index(match.re), text[start:end]) 88 | # get the next match from the iterator for this match 89 | if match.re in iter_dict: 90 | try: 91 | newmatch = iter_dict[match.re].next() 92 | heappush(matches, (newmatch.start(), newmatch)) 93 | except StopIteration: 94 | iter_dict.pop(match.re) 95 | prev_end = end 96 | 97 | # yield text from end of last match to end of text 98 | last_bit = text[prev_end:] 99 | if len(last_bit) > 0: 100 | yield (-1, last_bit) 101 | 102 | def replace(text, max_width=MAX_WIDTH, max_height=MAX_HEIGHT): 103 | """ 104 | Scans a block of text, replacing anything matched by a ``ProviderRule`` 105 | pattern with an OEmbed html snippet, if possible. 106 | 107 | Templates should be stored at oembed/{format}.html, so for example: 108 | 109 | oembed/video.html 110 | 111 | These templates are passed a context variable, ``response``, which is a 112 | dictionary representation of the response. 113 | """ 114 | rules = list(ProviderRule.objects.all()) 115 | patterns = [re.compile(r.regex, re.I) for r in rules] # Compiled patterns from the rules 116 | parts = [] # The parts that we will assemble into the final return value. 117 | indices = [] # List of indices of parts that need to be replaced with OEmbed stuff. 118 | indices_rules = [] # List of indices into the rules in order for which index was gotten by. 119 | urls = set() # A set of URLs to try to lookup from the database. 120 | stored = {} # A mapping of URLs to StoredOEmbed objects. 121 | index = 0 122 | # First we pass through the text, populating our data structures. 123 | for i, part in re_parts(patterns, text): 124 | if i == -1: 125 | parts.append(part) 126 | index += 1 127 | else: 128 | to_append = "" 129 | # If the link ends with one of our overrides, build a list 130 | while part[-1] in END_OVERRIDES: 131 | to_append += part[-1] 132 | part = part[:-1] 133 | indices.append(index) 134 | urls.add(part) 135 | indices_rules.append(i) 136 | parts.append(part) 137 | index += 1 138 | if to_append: 139 | parts.append(to_append) 140 | index += 1 141 | # Now we fetch a list of all stored patterns, and put it in a dictionary 142 | # mapping the URL to to the stored model instance. 143 | for stored_embed in StoredOEmbed.objects.filter(match__in=urls, max_width=max_width, max_height = max_height): 144 | stored[stored_embed.match] = stored_embed 145 | # Now we're going to do the actual replacement of URL to embed. 146 | for i, id_to_replace in enumerate(indices): 147 | rule = rules[indices_rules[i]] 148 | part = parts[id_to_replace] 149 | try: 150 | # Try to grab the stored model instance from our dictionary, and 151 | # use the stored HTML fragment as a replacement. 152 | parts[id_to_replace] = stored[part].html 153 | except KeyError: 154 | try: 155 | # Build the URL based on the properties defined in the OEmbed spec. 156 | sep = "?" in rule.endpoint and "&" or "?" 157 | q = urlencode({"url": part, 158 | "maxwidth": max_width, 159 | "maxheight": max_height, 160 | "format": FORMAT}) 161 | url = u"%s%s%s" % (rule.endpoint, sep, q) 162 | # Fetch the link and parse the JSON. 163 | resp = simplejson.loads(fetch(url)) 164 | # Depending on the embed type, grab the associated template and 165 | # pass it the parsed JSON response as context. 166 | replacement = render_to_string('oembed/%s.html' % resp['type'], {'response': resp}) 167 | if replacement: 168 | stored_embed = StoredOEmbed.objects.create( 169 | match = part, 170 | max_width = max_width, 171 | max_height = max_height, 172 | html = replacement, 173 | ) 174 | stored[stored_embed.match] = stored_embed 175 | parts[id_to_replace] = replacement 176 | else: 177 | raise ValueError 178 | except ValueError: 179 | parts[id_to_replace] = part 180 | except KeyError: 181 | parts[id_to_replace] = part 182 | except urllib2.HTTPError: 183 | parts[id_to_replace] = part 184 | # Combine the list into one string and return it. 185 | return mark_safe(u''.join(parts)) 186 | -------------------------------------------------------------------------------- /oembed/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "oembed.providerrule", 5 | "fields": { 6 | "regex": "http://\\S*?flickr.com/\\S*", 7 | "endpoint": "http://www.flickr.com/services/oembed/", 8 | "name": "Flickr", 9 | "format": 1 10 | } 11 | }, 12 | { 13 | "pk": 2, 14 | "model": "oembed.providerrule", 15 | "fields": { 16 | "regex": "http://\\S*?viddler.com/\\S*", 17 | "endpoint": "http://lab.viddler.com/services/oembed/", 18 | "name": "Viddler", 19 | "format": 1 20 | } 21 | }, 22 | { 23 | "pk": 3, 24 | "model": "oembed.providerrule", 25 | "fields": { 26 | "regex": "http://qik.com/\\S*", 27 | "endpoint": "http://qik.com/api/oembed.json", 28 | "name": "Qik", 29 | "format": 1 30 | } 31 | }, 32 | { 33 | "pk": 4, 34 | "model": "oembed.providerrule", 35 | "fields": { 36 | "regex": "http://\\S*?pownce.com/\\S*", 37 | "endpoint": "http://api.pownce.com/2.1/oembed.json", 38 | "name": "Pownce", 39 | "format": 1 40 | } 41 | }, 42 | { 43 | "pk": 5, 44 | "model": "oembed.providerrule", 45 | "fields": { 46 | "regex": "http://\\S*?revision3.com/\\S*", 47 | "endpoint": "http://revision3.com/api/oembed/", 48 | "name": "Revision3", 49 | "format": 1 50 | } 51 | }, 52 | { 53 | "pk": 6, 54 | "model": "oembed.providerrule", 55 | "fields": { 56 | "regex": "http://\\S*.collegehumor.com/video:\\S*", 57 | "endpoint": "http://oohembed.com/oohembed/", 58 | "name": "CollegeHumor Video (OohEmbed)", 59 | "format": 1 60 | } 61 | }, 62 | { 63 | "pk": 7, 64 | "model": "oembed.providerrule", 65 | "fields": { 66 | "regex": "http://\\S*.funnyordie.com/videos/\\S*", 67 | "endpoint": "http://oohembed.com/oohembed/", 68 | "name": "Funny or Die Video (OohEmbed)", 69 | "format": 1 70 | } 71 | }, 72 | { 73 | "pk": 8, 74 | "model": "oembed.providerrule", 75 | "fields": { 76 | "regex": "http://video.google.com/videoplay?\\S*", 77 | "endpoint": "http://oohembed.com/oohembed/", 78 | "name": "Google Video (OohEmbed)", 79 | "format": 1 80 | } 81 | }, 82 | { 83 | "pk": 9, 84 | "model": "oembed.providerrule", 85 | "fields": { 86 | "regex": "http://www.hulu.com/watch/\\S*", 87 | "endpoint": "http://oohembed.com/oohembed/", 88 | "name": "Hulu (OohEmbed)", 89 | "format": 1 90 | } 91 | }, 92 | { 93 | "pk": 10, 94 | "model": "oembed.providerrule", 95 | "fields": { 96 | "regex": "http://\\S*.metacafe.com/watch/\\S*", 97 | "endpoint": "http://oohembed.com/oohembed/", 98 | "name": "Metacafe (OohEmbed)", 99 | "format": 1 100 | } 101 | }, 102 | { 103 | "pk": 11, 104 | "model": "oembed.providerrule", 105 | "fields": { 106 | "regex": "http://twitter.com/\\S*/statuses/\\S*", 107 | "endpoint": "http://oohembed.com/oohembed/", 108 | "name": "Twitter Status (OohEmbed)", 109 | "format": 1 110 | } 111 | }, 112 | { 113 | "pk": 12, 114 | "model": "oembed.providerrule", 115 | "fields": { 116 | "regex": "http://\\S*.wikipedia.org/wiki/\\S*", 117 | "endpoint": "http://oohembed.com/oohembed/", 118 | "name": "Wikipedia (OohEmbed)", 119 | "format": 1 120 | } 121 | }, 122 | { 123 | "pk": 13, 124 | "model": "oembed.providerrule", 125 | "fields": { 126 | "regex": "http://\\S*.youtube.com/watch\\S*", 127 | "endpoint": "http://www.youtube.com/oembed", 128 | "name": "YouTube", 129 | "format": 1 130 | } 131 | }, 132 | { 133 | "pk": 14, 134 | "model": "oembed.providerrule", 135 | "fields": { 136 | "regex": "http://vimeo.com/\\S*", 137 | "endpoint": "http://vimeo.com/api/oembed.json", 138 | "name": "Vimeo", 139 | "format": 1 140 | } 141 | }, 142 | { 143 | "pk": 15, 144 | "model": "oembed.providerrule", 145 | "fields": { 146 | "regex": "http://www.slideshare.net/\\S*/\\S*", 147 | "endpoint": "http://api.embed.ly/v1/api/oembed", 148 | "name": "SlideShare (Embedly)", 149 | "format": 1 150 | } 151 | }, 152 | { 153 | "pk": 16, 154 | "model": "oembed.providerrule", 155 | "fields": { 156 | "regex": "http://\\S*.scribd.com/doc/\\S*", 157 | "endpoint": "http://api.embed.ly/v1/api/oembed", 158 | "name": "Scribd (Embedly)", 159 | "format": 1 160 | } 161 | }, 162 | { 163 | "pk": 17, 164 | "model": "oembed.providerrule", 165 | "fields": { 166 | "regex": "http://screenr.com/\\S*", 167 | "endpoint": "http://api.embed.ly/v1/api/oembed", 168 | "name": "Screenr (Embedly)", 169 | "format": 1 170 | } 171 | }, 172 | { 173 | "pk": 18, 174 | "model": "oembed.providerrule", 175 | "fields": { 176 | "regex": "http://www.5min.com/Video/\\S*", 177 | "endpoint": "http://api.embed.ly/v1/api/oembed", 178 | "name": "5min (Embedly)", 179 | "format": 1 180 | } 181 | }, 182 | { 183 | "pk": 19, 184 | "model": "oembed.providerrule", 185 | "fields": { 186 | "regex": "http://www.howcast.com/videos/\\S*", 187 | "endpoint": "http://api.embed.ly/v1/api/oembed", 188 | "name": "Howcast (Embedly)", 189 | "format": 1 190 | } 191 | }, 192 | { 193 | "pk": 20, 194 | "model": "oembed.providerrule", 195 | "fields": { 196 | "regex": "(http://\\S*?screencast.com/\\S*/media/\\S*|http://\\S*?screencast.com/t/\\S*)", 197 | "endpoint": "http://api.embed.ly/v1/api/oembed", 198 | "name": "Screencast (Embedly)", 199 | "format": 1 200 | } 201 | }, 202 | { 203 | "pk": 21, 204 | "model": "oembed.providerrule", 205 | "fields": { 206 | "regex": "http://www.clearspring.com/widgets/\\S*", 207 | "endpoint": "http://api.embed.ly/v1/api/oembed", 208 | "name": "Clearspring (Embedly)", 209 | "format": 1 210 | } 211 | }, 212 | { 213 | "pk": 22, 214 | "model": "oembed.providerrule", 215 | "fields": { 216 | "regex": "(http://my.opera.com/\\S*/albums/show.dml\\?id=\\S*|http://my.opera.com/\\S*/albums/showpic.dml\\?album=\\S*&picture=\\S*)", 217 | "endpoint": "http://api.embed.ly/v1/api/oembed", 218 | "name": "My Opera (Embedly)", 219 | "format": 1 220 | } 221 | }, 222 | { 223 | "pk": 23, 224 | "model": "oembed.providerrule", 225 | "fields": { 226 | "regex": "http://\\S*yfrog.\\S*/\\S*", 227 | "endpoint": "http://api.embed.ly/v1/api/oembed", 228 | "name": "Yfrog (Embedly)", 229 | "format": 1 230 | } 231 | }, 232 | { 233 | "pk": 24, 234 | "model": "oembed.providerrule", 235 | "fields": { 236 | "regex": "http://tweetphoto.com/\\S*", 237 | "endpoint": "http://api.embed.ly/v1/api/oembed", 238 | "name": "TweetPhoto (Embedly)", 239 | "format": 1 240 | } 241 | }, 242 | { 243 | "pk": 25, 244 | "model": "oembed.providerrule", 245 | "fields": { 246 | "regex": "http://\\S*twitpic.com/\\S*", 247 | "endpoint": "http://api.embed.ly/v1/api/oembed", 248 | "name": "TwitPic (Embedly)", 249 | "format": 1 250 | } 251 | }, 252 | { 253 | "pk": 26, 254 | "model": "oembed.providerrule", 255 | "fields": { 256 | "regex": "http://\\S*imgur.com/\\S*", 257 | "endpoint": "http://api.embed.ly/v1/api/oembed", 258 | "name": "Imgur (Embedly)", 259 | "format": 1 260 | } 261 | }, 262 | { 263 | "pk": 27, 264 | "model": "oembed.providerrule", 265 | "fields": { 266 | "regex": "http://twitgoo.com/\\S*", 267 | "endpoint": "http://api.embed.ly/v1/api/oembed", 268 | "name": "TwitGoo (Embedly)", 269 | "format": 1 270 | } 271 | }, 272 | { 273 | "pk": 28, 274 | "model": "oembed.providerrule", 275 | "fields": { 276 | "regex": "(http://i\\S*.photobucket.com/albums/\\S*|http://gi\\S*.photobucket.com/groups/\\S*)", 277 | "endpoint": "http://api.embed.ly/v1/api/oembed", 278 | "name": "Photobucket (Embedly)", 279 | "format": 1 280 | } 281 | }, 282 | { 283 | "pk": 29, 284 | "model": "oembed.providerrule", 285 | "fields": { 286 | "regex": "http://phodroid.com/\\S*/\\S*/\\S*", 287 | "endpoint": "http://api.embed.ly/v1/api/oembed", 288 | "name": "Phodroid (Embedly)", 289 | "format": 1 290 | } 291 | }, 292 | { 293 | "pk": 30, 294 | "model": "oembed.providerrule", 295 | "fields": { 296 | "regex": "http://xkcd.com/\\S*", 297 | "endpoint": "http://api.embed.ly/v1/api/oembed", 298 | "name": "xkcd (Embedly)", 299 | "format": 1 300 | } 301 | }, 302 | { 303 | "pk": 31, 304 | "model": "oembed.providerrule", 305 | "fields": { 306 | "regex": "http://\\S*?23hq.com/\\S*/photo/\\S*", 307 | "endpoint": "http://api.embed.ly/v1/api/oembed", 308 | "name": "23 HQ (Embedly)", 309 | "format": 1 310 | } 311 | }, 312 | { 313 | "pk": 32, 314 | "model": "oembed.providerrule", 315 | "fields": { 316 | "regex": "(http://\\S*amazon.\\S*/gp/product/\\S*|http://\\S*amazon.\\S*/\\S*/dp/\\S*|http://\\S*amazon.\\S*/dp/\\S*|http://\\S*amazon.\\S*/o/ASIN/\\S*|http://\\S*amazon.\\S*/gp/offer-listing/\\S*|http://\\S*amazon.\\S*/\\S*/ASIN/\\S*|http://\\S*amazon.\\S*/gp/product/images/\\S*)", 317 | "endpoint": "http://api.embed.ly/v1/api/oembed", 318 | "name": "Amazon (Embedly)", 319 | "format": 1 320 | } 321 | }, 322 | { 323 | "pk": 33, 324 | "model": "oembed.providerrule", 325 | "fields": { 326 | "regex": "http://www.veoh.com/\\S*/watch/\\S*", 327 | "endpoint": "http://api.embed.ly/v1/api/oembed", 328 | "name": "Veoh (Embedly)", 329 | "format": 1 330 | } 331 | }, 332 | { 333 | "pk": 34, 334 | "model": "oembed.providerrule", 335 | "fields": { 336 | "regex": "http://\\S*justin.tv/\\S*", 337 | "endpoint": "http://api.embed.ly/v1/api/oembed", 338 | "name": "Justin.tv (Embedly)", 339 | "format": 1 340 | } 341 | }, 342 | { 343 | "pk": 35, 344 | "model": "oembed.providerrule", 345 | "fields": { 346 | "regex": "http://www.ustream.tv/(recorded|channel)/\\S*", 347 | "endpoint": "http://api.embed.ly/v1/api/oembed", 348 | "name": "UStream (Embedly)", 349 | "format": 1 350 | } 351 | }, 352 | { 353 | "pk": 36, 354 | "model": "oembed.providerrule", 355 | "fields": { 356 | "regex": "(http://\\S*.dailymotion.com/video/\\S*|http://\\S*.dailymotion.com/\\S*/video/\\S*)", 357 | "endpoint": "http://api.embed.ly/v1/api/oembed", 358 | "name": "Daily Motion (Embedly)", 359 | "format": 1 360 | } 361 | }, 362 | { 363 | "pk": 37, 364 | "model": "oembed.providerrule", 365 | "fields": { 366 | "regex": "http://www.twitvid.com/\\S*", 367 | "endpoint": "http://api.embed.ly/v1/api/oembed", 368 | "name": "TwitVid (Embedly)", 369 | "format": 1 370 | } 371 | }, 372 | { 373 | "pk": 38, 374 | "model": "oembed.providerrule", 375 | "fields": { 376 | "regex": "http://www.break.com/\\S*/\\S*", 377 | "endpoint": "http://api.embed.ly/v1/api/oembed", 378 | "name": "Break.com (Embedly)", 379 | "format": 1 380 | } 381 | }, 382 | { 383 | "pk": 39, 384 | "model": "oembed.providerrule", 385 | "fields": { 386 | "regex": "http://(www|vids).myspace.com/index.cfm\\?fuseaction=\\S*&videoid\\S*", 387 | "endpoint": "http://api.embed.ly/v1/api/oembed", 388 | "name": "Myspace Videos (Embedly)", 389 | "format": 1 390 | } 391 | }, 392 | { 393 | "pk": 40, 394 | "model": "oembed.providerrule", 395 | "fields": { 396 | "regex": "http://\\S*blip.tv/file/\\S*", 397 | "endpoint": "http://api.embed.ly/v1/api/oembed", 398 | "name": "Blip.tv (Embedly)", 399 | "format": 1 400 | } 401 | }, 402 | { 403 | "pk": 41, 404 | "model": "oembed.providerrule", 405 | "fields": { 406 | "regex": "http://\\S*revver.com/video/\\S*", 407 | "endpoint": "http://api.embed.ly/v1/api/oembed", 408 | "name": "Revver (Embedly)", 409 | "format": 1 410 | } 411 | }, 412 | { 413 | "pk": 42, 414 | "model": "oembed.providerrule", 415 | "fields": { 416 | "regex": "(http://video.yahoo.com/watch/\\S*/\\S*|http://video.yahoo.com/network/\\S*)", 417 | "endpoint": "http://api.embed.ly/v1/api/oembed", 418 | "name": "Yahoo! Video (Embedly)", 419 | "format": 1 420 | } 421 | }, 422 | { 423 | "pk": 43, 424 | "model": "oembed.providerrule", 425 | "fields": { 426 | "regex": "http://\\S*?liveleak.com/view?\\S*", 427 | "endpoint": "http://api.embed.ly/v1/api/oembed", 428 | "name": "LiveLeak (Embedly)", 429 | "format": 1 430 | } 431 | }, 432 | { 433 | "pk": 44, 434 | "model": "oembed.providerrule", 435 | "fields": { 436 | "regex": "http://animoto.com/(play|s)/\\S*", 437 | "endpoint": "http://api.embed.ly/v1/api/oembed", 438 | "name": "Animoto (Embedly)", 439 | "format": 1 440 | } 441 | }, 442 | { 443 | "pk": 45, 444 | "model": "oembed.providerrule", 445 | "fields": { 446 | "regex": "http://dotsub.com/view/\\S*", 447 | "endpoint": "http://api.embed.ly/v1/api/oembed", 448 | "name": "dotSUB (Embedly)", 449 | "format": 1 450 | } 451 | }, 452 | { 453 | "pk": 46, 454 | "model": "oembed.providerrule", 455 | "fields": { 456 | "regex": "http://soundcloud.com/\\S*", 457 | "endpoint": "http://api.embed.ly/v1/api/oembed", 458 | "name": "SoundCloud (Embedly)", 459 | "format": 1 460 | } 461 | }, 462 | { 463 | "pk": 47, 464 | "model": "oembed.providerrule", 465 | "fields": { 466 | "regex": "http://www.lala.com/#*(album|song)/\\S*", 467 | "endpoint": "http://api.embed.ly/v1/api/oembed", 468 | "name": "Lala (Embedly)", 469 | "format": 1 470 | } 471 | }, 472 | { 473 | "pk": 48, 474 | "model": "oembed.providerrule", 475 | "fields": { 476 | "regex": "(http://movieclips.com/watch/\\S*/\\S*/|http://movieclips.com/watch/\\S*/\\S*/\\S*/\\S*)", 477 | "endpoint": "http://api.embed.ly/v1/api/oembed", 478 | "name": "Movie Clips (Embedly)", 479 | "format": 1 480 | } 481 | }, 482 | { 483 | "pk": 49, 484 | "model": "oembed.providerrule", 485 | "fields": { 486 | "regex": "http://\\S*crackle.com/c/\\S*", 487 | "endpoint": "http://api.embed.ly/v1/api/oembed", 488 | "name": "Crackle (Embedly)", 489 | "format": 1 490 | } 491 | }, 492 | { 493 | "pk": 50, 494 | "model": "oembed.providerrule", 495 | "fields": { 496 | "regex": "http://www.fancast.com/\\S*/videos", 497 | "endpoint": "http://api.embed.ly/v1/api/oembed", 498 | "name": "Fancast (Embedly)", 499 | "format": 1 500 | } 501 | }, 502 | { 503 | "pk": 51, 504 | "model": "oembed.providerrule", 505 | "fields": { 506 | "regex": "http://www.ted.com/talks/\\S*.html", 507 | "endpoint": "http://api.embed.ly/v1/api/oembed", 508 | "name": "TED (Embedly)", 509 | "format": 1 510 | } 511 | }, 512 | { 513 | "pk": 52, 514 | "model": "oembed.providerrule", 515 | "fields": { 516 | "regex": "http://\\S*omnisio.com/\\S*", 517 | "endpoint": "http://api.embed.ly/v1/api/oembed", 518 | "name": "Omnisio (Embedly)", 519 | "format": 1 520 | } 521 | }, 522 | { 523 | "pk": 53, 524 | "model": "oembed.providerrule", 525 | "fields": { 526 | "regex": "http://\\S*nfb.ca/film/\\S*", 527 | "endpoint": "http://api.embed.ly/v1/api/oembed", 528 | "name": "NFB (Embedly)", 529 | "format": 1 530 | } 531 | }, 532 | { 533 | "pk": 54, 534 | "model": "oembed.providerrule", 535 | "fields": { 536 | "regex": "(http://www.thedailyshow.com/(watch|full-episodes)/\\S*|http://www.thedailyshow.com/collection/\\S*/\\S*/\\S*)", 537 | "endpoint": "http://api.embed.ly/v1/api/oembed", 538 | "name": "The Daily Show (Embedly)", 539 | "format": 1 540 | } 541 | }, 542 | { 543 | "pk": 55, 544 | "model": "oembed.providerrule", 545 | "fields": { 546 | "regex": "http://movies.yahoo.com/movie/\\S*/(video|info|trailer)/\\S*", 547 | "endpoint": "http://api.embed.ly/v1/api/oembed", 548 | "name": "Yahoo! Movies", 549 | "format": 1 550 | } 551 | }, 552 | { 553 | "pk": 56, 554 | "model": "oembed.providerrule", 555 | "fields": { 556 | "regex": "http://www.colbertnation.com/(the-colbert-report-collections|full-episodes|the-colbert-report-videos)/\\S*", 557 | "endpoint": "http://api.embed.ly/v1/api/oembed", 558 | "name": "Colbert Nation (Embedly)", 559 | "format": 1 560 | } 561 | }, 562 | { 563 | "pk": 57, 564 | "model": "oembed.providerrule", 565 | "fields": { 566 | "regex": "http://www.comedycentral.com/videos/index.jhtml?\\S*", 567 | "endpoint": "http://api.embed.ly/v1/api/oembed", 568 | "name": "Comedy Central (Embedly)", 569 | "format": 1 570 | } 571 | }, 572 | { 573 | "pk": 58, 574 | "model": "oembed.providerrule", 575 | "fields": { 576 | "regex": "http://\\S*theonion.com/video/\\S*", 577 | "endpoint": "http://api.embed.ly/v1/api/oembed", 578 | "name": "The Onion (Embedly)", 579 | "format": 1 580 | } 581 | }, 582 | { 583 | "pk": 59, 584 | "model": "oembed.providerrule", 585 | "fields": { 586 | "regex": "http://wordpress.tv/\\S*/\\S*/\\S*/\\S*/", 587 | "endpoint": "http://api.embed.ly/v1/api/oembed", 588 | "name": "WordPress TV (Embedly)", 589 | "format": 1 590 | } 591 | }, 592 | { 593 | "pk": 60, 594 | "model": "oembed.providerrule", 595 | "fields": { 596 | "regex": "http://www.traileraddict.com/(trailer|clip|poster)/\\S*", 597 | "endpoint": "http://api.embed.ly/v1/api/oembed", 598 | "name": "Trailer Addict (Embedly)", 599 | "format": 1 600 | } 601 | }, 602 | { 603 | "pk": 61, 604 | "model": "oembed.providerrule", 605 | "fields": { 606 | "regex": "http://www.escapistmagazine.com/videos/\\S*", 607 | "endpoint": "http://api.embed.ly/v1/api/oembed", 608 | "name": "The Escapist (Embedly)", 609 | "format": 1 610 | } 611 | } 612 | ] 613 | --------------------------------------------------------------------------------