diff --git a/docs/src/dictionary/en-custom.txt b/docs/src/dictionary/en-custom.txt index e7d972194..d16b5abb1 100644 --- a/docs/src/dictionary/en-custom.txt +++ b/docs/src/dictionary/en-custom.txt @@ -19,6 +19,7 @@ CommonMark CriticMarkup Ctrl DOM +Dailymotion Dicts Donath ElementTree @@ -108,6 +109,7 @@ UTF Uml Unescape Validators +Vimeo Virtualenv Waylan YAML @@ -140,6 +142,7 @@ emojione escaper eslint facelessuser +fallbacks formatter formatter's formatters diff --git a/pymdownx/embed.py b/pymdownx/embed.py index 21ace5af4..90e85b7c0 100644 --- a/pymdownx/embed.py +++ b/pymdownx/embed.py @@ -28,6 +28,9 @@ """ ) +SOURCE_ATTR = ('srcset', 'sizes', 'media') +TRACK_ATTR = ('default', 'kind', 'label', 'srclang') + def youtube(url): """Match YouTube source and return a suitable URL.""" @@ -65,15 +68,27 @@ def vimeo(url): SERVICES = { "youtube": { "handler": youtube, - "defaults": {"allowfullscreen": '', "frameborder": '0'} + "defaults": { + "allowfullscreen": '', + "frameborder": '0', + "title": "YouTube video player" + } }, "dailymotion": { "handler": dailymotion, - "defaults": {"allowfullscreen": '', "frameborder": '0'} + "defaults": { + "allowfullscreen": '', + "frameborder": '0', + "title": "Dailymotion video player" + } }, "vimeo": { "handler": vimeo, - "defaults": {"allowfullscreen": '', "frameborder": '0'} + "defaults": { + "allowfullscreen": '', + "frameborder": '0', + "title": "Vimeo video player" + } } } @@ -117,30 +132,45 @@ def __init__(self, md, video_defaults, audio_defaults, services): super().__init__(md) - def process_embedded_media(self, links, parent_map): - """Process embedded video and audio files.""" + def convert_to_anchor(self, link, parent_map): + """Convert to a normal link.""" - # Evaluate links - for link in reversed(links): + parent = parent_map[link] + el = None + index = -1 + for index, c in enumerate(list(parent), 0): + if c is link: + src = link.attrib['src'] + el = etree.Element('a', {'href': src, 'download': ''}) + el.text = md_util.AtomicString(src) + break - # Save the attributes as we will reuse them - attrib = copy.copy(link.attrib) + parent.insert(index, el) + parent.remove(link) - # See if source matches the audio or video mime type - src = attrib.get('src', '') - m = self.MIMES.match(src) - if m is None: - continue + def process_embedded_media(self, link, parent_map): + """Process embedded video and audio files.""" - # Use whatever audio/video type specified or construct our own - # Reject any other types - mime = m.group(1).lower() + # Save the attributes as we will reuse them + attrib = copy.copy(link.attrib) - # We don't know what case the attributes are in, so normalize them. - keys = set([k.lower() for k in attrib.keys()]) + # See if source matches the audio or video mime type + src = attrib.get('src', '') + m = self.MIMES.match(src) + if m is None: + return - # Identify whether we are working with audio or video and save MIME type - mtype = '' + # Use whatever audio/video type specified or construct our own + # Reject any other types + mime = m.group(1).lower() + is_vtt = mime == 'vtt' + + # We don't know what case the attributes are in, so normalize them. + keys = set([k.lower() for k in attrib.keys()]) + + # Identify whether we are working with audio or video and save MIME type + mtype = '' + if not is_vtt: if 'type' in keys: v = attrib['type'] t = v.lower().split('/')[0] @@ -153,107 +183,145 @@ def process_embedded_media(self, links, parent_map): # Doesn't look like audio/video if not mtype: - continue + return - # Setup attributess for `` element + # Setup attributes for `` element vtype = mtype[:5].lower() attrib = {**copy.deepcopy(self.video_defaults if vtype == 'video' else self.audio_defaults), **attrib} src_attrib = {'src': src, 'type': mtype} del attrib['src'] del attrib['type'] + else: + # We need `` elements to be fallbacks. + if 'fallback' not in keys: + self.convert_to_anchor(link, parent_map) + return + # Setup attributes for `` element. + src_attrib = {'src': src} + del attrib['src'] - # Find any other `` specific attributes and check if there is an `alt` - alt = '' - for k in keys: - key = k.lower() - if key == 'alt': - alt = attrib[k] - del attrib[k] - elif key in ('srcset', 'sizes', 'media'): - src_attrib[key] = attrib[k] - del attrib[key] - - # Build the source element and apply the right type - source = etree.Element('source', src_attrib) - - # Find the parent and check if the next sibling is already a media group - # that we can attach to. If so, the current link will become the primary - # source, and the existing will become the fallback. - parent = parent_map[link] - one_more = False - sibling = None - index = -1 - mtype = src_attrib['type'][:5].lower() - for i, c in enumerate(parent, 0): - if one_more: - # If there is another sibling, see if it is already a video container - if c.tag.lower() == mtype and 'fallback' in c.attrib: - sibling = c - break - if c is link: - # Found where we live, now let's find our sibling - index = i - one_more = True - - # Attach the media source as the primary source, or construct a new group. - if sibling is not None: - # Insert the source at the top - sibling.insert(0, source) - # Update container's attributes - sibling.attrib.clear() - sibling.attrib.update(attrib) - # Update fallback link - last = list(sibling)[-1] - last.attrib['href'] = src - last.text = md_util.AtomicString(alt if alt else src) - else: - # Create media container and insert source - media = etree.Element(mtype, attrib) - media.append(source) - # Just in case the browser doesn't support `