1
+ from collections import OrderedDict
2
+ from pathlib import Path
1
3
from typing import Any , Dict
2
4
from urllib .parse import urljoin
3
- from pathlib import Path
4
5
5
6
import docutils .nodes as nodes
6
7
from sphinx .application import Sphinx
24
25
"tiff" : "image/tiff" ,
25
26
}
26
27
28
+ # A selection from https://www.iana.org/assignments/media-types/media-types.xhtml#video
29
+ VIDEO_MIME_TYPES = {
30
+ "3gpp" : "video/3gpp" ,
31
+ "3gp2" : "video/3gp2" ,
32
+ "3gpp2" : "video/3gpp2" ,
33
+ "av1" : "video/AV1" ,
34
+ "dv" : "video/DV" ,
35
+ "h263" : "video/H263" ,
36
+ "h264" : "video/H264" ,
37
+ "h265" : "video/H265" ,
38
+ "jpeg" : "video/JPEG" ,
39
+ "jpeg2000" : "video/jpeg2000" ,
40
+ "MPV" : "video/MPV" ,
41
+ "ogg" : "video/ogg" ,
42
+ "quicktime" : "video/quicktime" ,
43
+ "raw" : "video/raw" ,
44
+ "vc1" : "video/vc1" ,
45
+ "vc2" : "video/vc2" ,
46
+ "vp8" : "video/VP8" ,
47
+ "mp4" : "video/mp4" ,
48
+ "webm" : "video/webm" ,
49
+ "m4v" : "video/mp4" ,
50
+ }
51
+
52
+ # A selection from https://www.iana.org/assignments/media-types/media-types.xhtml#audio
53
+ AUDIO_MIME_TYPES = {
54
+ "wave" : "audio/x-wav" ,
55
+ "wav" : "audio/x-wav" ,
56
+ "webm" : "audio/webm" ,
57
+ "ogg" : "audio/ogg" ,
58
+ "3gpp" : "audio/3gpp" ,
59
+ "3gpp2" : "audio/3gpp2" ,
60
+ "3gp2" : "audio/3gp2" ,
61
+ "aac" : "audio/aac" ,
62
+ "ac3" : "audio/ac3" ,
63
+ "aptx" : "audio/aptx" ,
64
+ "dv" : "audio/DV" ,
65
+ "mpeg" : "audio/mpeg" ,
66
+ "opus" : "audio/opus" ,
67
+ "vorbis" : "audio/vorbis" ,
68
+ "m3u" : "audio/x-mpegurl" ,
69
+ "m3u8" : "audio/x-mpegurl" ,
70
+ "mid" : "audio/midi" ,
71
+ "midi" : "audio/midi" ,
72
+ "mp2" : "audio/mpeg" ,
73
+ "mp3" : "audio/mpeg" ,
74
+ "m4a" : "audio/mp4" ,
75
+ "aiff" : "audio/x-aiff" ,
76
+ }
77
+
27
78
28
79
def make_tag (property : str , content : str ) -> str :
29
80
return f'<meta property="{ property } " content="{ content } " />\n '
30
81
31
82
32
83
def get_tags (
33
- context : Dict [str , Any ], doctree : nodes .document , config : Dict [str , Any ]
84
+ app : Sphinx ,
85
+ pagename : str ,
86
+ context : Dict [str , Any ],
87
+ doctree : nodes .document ,
88
+ config : Dict [str , Any ],
34
89
) -> str :
35
90
36
91
# Set length of description
@@ -39,35 +94,31 @@ def get_tags(
39
94
except ValueError :
40
95
desc_len = DEFAULT_DESCRIPTION_LENGTH
41
96
97
+ meta = OrderedDict ()
98
+
42
99
# Get the title and parse any html in it
43
100
title = get_title (context ["title" ], skip_html_tags = False )
44
101
title_excluding_html = get_title (context ["title" ], skip_html_tags = True )
102
+ meta ["og:title" ] = title
45
103
46
104
# Parse/walk doctree for metadata (tag/description)
47
105
description = get_description (doctree , desc_len , [title , title_excluding_html ])
48
-
49
- tags = "\n "
50
-
51
- # title tag
52
- tags += make_tag ("og:title" , title )
106
+ meta ["og:description" ] = description
53
107
54
108
# type tag
55
- tags += make_tag ( "og:type" , config ["ogp_type" ])
109
+ meta [ "og:type" ] = config ["ogp_type" ]
56
110
57
111
# url tag
58
112
# Get the URL of the specific page
59
113
page_url = urljoin (
60
114
config ["ogp_site_url" ], context ["pagename" ] + context ["file_suffix" ]
61
115
)
62
- tags += make_tag ( "og:url" , page_url )
116
+ meta [ "og:url" ] = page_url
63
117
64
118
# site name tag
65
119
site_name = config ["ogp_site_name" ]
66
120
if site_name :
67
- tags += make_tag ("og:site_name" , site_name )
68
-
69
- # description tag
70
- tags += make_tag ("og:description" , description )
121
+ meta ["og:site_name" ] = site_name
71
122
72
123
# image tag
73
124
# Get basic values from config
@@ -85,15 +136,40 @@ def get_tags(
85
136
ogp_image_alt = first_image .get ("alt" , None )
86
137
87
138
if image_url :
88
- tags += make_tag ( "og:image" , image_url )
139
+ meta [ "og:image" ] = image_url
89
140
90
141
# Add image alt text (either provided by config or from site_name)
91
142
if isinstance (ogp_image_alt , str ):
92
- tags += make_tag ( "og:image:alt" , ogp_image_alt )
143
+ meta [ "og:image:alt" ] = ogp_image_alt
93
144
elif ogp_image_alt is None and site_name :
94
- tags += make_tag ( "og:image:alt" , site_name )
145
+ meta [ "og:image:alt" ] = site_name
95
146
elif ogp_image_alt is None and title :
96
- tags += make_tag ("og:image:alt" , title )
147
+ meta ["og:image:alt" ] = title
148
+
149
+ # apply overrides
150
+ meta .update (context .get ("meta" , {}))
151
+
152
+ # fix relative overrides
153
+ for prop in ["og:image" , "og:audio" , "og:video" ]:
154
+ value = meta .get ("prop" , "" )
155
+ if not (value .startswith ("http://" ) or value .startswith ("https://" )):
156
+ meta [prop ] = app .env .relfn2path (value , pagename )
157
+
158
+ # add mime types
159
+ def add_mime_type (prop : str , mime_types : dict ):
160
+ if prop not in meta :
161
+ return
162
+ value = meta [prop ]
163
+ ext = value .split ("." )[- 1 ].split ("?" )[0 ].split ("#" )[0 ].lower ()
164
+ if ext in mime_types :
165
+ meta [prop + ":type" ] = mime_types [ext ]
166
+
167
+ add_mime_type ("og:image" , IMAGE_MIME_TYPES )
168
+ add_mime_type ("og:video" , VIDEO_MIME_TYPES )
169
+ add_mime_type ("og:audio" , AUDIO_MIME_TYPES )
170
+
171
+ # write tags
172
+ tags = "" .join ([make_tag (k , v ) for k , v in meta .items ()])
97
173
98
174
# custom tags
99
175
tags += "\n " .join (config ["ogp_custom_meta_tags" ])
@@ -109,7 +185,7 @@ def html_page_context(
109
185
doctree : nodes .document ,
110
186
) -> None :
111
187
if doctree :
112
- context ["metatags" ] += get_tags (context , doctree , app .config )
188
+ context ["metatags" ] += get_tags (app , pagename , context , doctree , app .config )
113
189
114
190
115
191
def setup (app : Sphinx ) -> Dict [str , Any ]:
0 commit comments