From e33d122e141b8baabbfdf79d055aed18979dd250 Mon Sep 17 00:00:00 2001 From: facelessuser Date: Thu, 23 Sep 2021 19:09:33 -0600 Subject: [PATCH] Add support for media services like YouTube, etc. --- pymdownx/embed.py | 129 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 9 deletions(-) diff --git a/pymdownx/embed.py b/pymdownx/embed.py index ca3f82310..21ace5af4 100644 --- a/pymdownx/embed.py +++ b/pymdownx/embed.py @@ -7,6 +7,77 @@ import copy +RE_YOUTUBE = re.compile( + r"""(?ix)^ + (?:https://(?:www\.)?youtube\.com/embed/|https://youtu\.be/|https://www.youtube.com/watch?v=) + (?P[a-z0-9-]+)(?P[?&].*)?$ + """ +) + +RE_DAILYMOTION = re.compile( + r"""(?ix)^ + (?:https://(?:www\.)?dailymotion\.com/(?:embed/)?video/) + (?P[a-z0-9]+)(?P[?&].*)?$ + """ +) + +RE_VIMEO = re.compile( + r"""(?ix)^ + (?:https://(?:www\.)?vimeo\.com/|https://player\.vimeo\.com/video/) + (?P[0-9]+)(?P[?&].*)?$ + """ +) + + +def youtube(url): + """Match YouTube source and return a suitable URL.""" + + src = None + m = RE_YOUTUBE.match(url) + if m: + key = m.group('id') + src = 'https://www.youtube.com/embed/{}'.format(key) + return src + + +def dailymotion(url): + """Match Dailymotion source and return a suitable URL.""" + + src = None + m = RE_DAILYMOTION.match(url) + if m: + key = m.group('id') + src = 'https://www.dailymotion.com/embed/video/{}'.format(key) + return src + + +def vimeo(url): + """Match Vimeo source and return a suitable URL.""" + + src = None + m = RE_VIMEO.match(url) + if m: + key = m.group('id') + src = 'https://player.vimeo.com/video/{}'.format(key) + return src + + +SERVICES = { + "youtube": { + "handler": youtube, + "defaults": {"allowfullscreen": '', "frameborder": '0'} + }, + "dailymotion": { + "handler": dailymotion, + "defaults": {"allowfullscreen": '', "frameborder": '0'} + }, + "vimeo": { + "handler": vimeo, + "defaults": {"allowfullscreen": '', "frameborder": '0'} + } +} + + class EmbedMediaTreeprocessor(Treeprocessor): """ Find video links and parse them up. @@ -32,20 +103,22 @@ class EmbedMediaTreeprocessor(Treeprocessor): 'flac': 'audio/flac' } - def __init__(self, md, video_defaults, audio_defaults): + def __init__(self, md, video_defaults, audio_defaults, services): """Initialize.""" + self.services = copy.deepcopy(SERVICES) + for k, v in services.items(): + if k not in self.services: + self.services[k] = copy.deepcopy(v) + else: + self.services[k] = {**self.services[k], **copy.deepcopy(v)} self.video_defaults = video_defaults self.audio_defaults = audio_defaults super().__init__(md) - def run(self, root): - """Shorten popular git repository links.""" - - # Grab the elements of interest and form a parent mapping - links = [e for e in root.iter() if e.tag.lower() in ('img',)] - parent_map = {c: p for p in root.iter() for c in p} + def process_embedded_media(self, links, parent_map): + """Process embedded video and audio files.""" # Evaluate links for link in reversed(links): @@ -146,6 +219,42 @@ def run(self, root): # Remove the old link parent.remove(link) + def process_embedded_media_service(self, links, parent_map): + """Process links to embedded media services like YouTube, etc.""" + + for link in reversed(links): + for service in self.services.values(): + src = service['handler'](link.attrib.get('href', '')) + if src: + orig = copy.copy(link.attrib) + orig['src'] = src + del orig['href'] + attrib = {**copy.copy(service['defaults']), **orig} + el = etree.Element('iframe', attrib) + parent = parent_map[link] + for index, c in enumerate(list(parent), 0): + if c is el: + break + parent.insert(index, el) + parent.remove(link) + + def run(self, root): + """Shorten popular git repository links.""" + + # Grab the elements of interest and form a parent mapping + images = [] + anchors = [] + parent_map = {c: p for p in root.iter() for c in p} + for e in root.iter(): + tag = e.tag.lower() + if tag == 'img': + images.append(e) + elif tag == 'a': + anchors.append(e) + + self.process_embedded_media(images, parent_map) + self.process_embedded_media_service(anchors, parent_map) + return root @@ -163,7 +272,8 @@ def __init__(self, *args, **kwargs): "audio_defaults": [ {'controls': '', 'preload': 'metadata'}, "Video default attributes - Default: {}" - ] + ], + "services": [{}, "Additional services and service overrides."] } super().__init__(*args, **kwargs) @@ -175,7 +285,8 @@ def extendMarkdown(self, md): media = EmbedMediaTreeprocessor( md, config['video_defaults'], - config['audio_defaults'] + config['audio_defaults'], + config['services'] ) md.treeprocessors.register(media, 'embed', 7.9)