Solar Eclipse in Ottawa on 2021-06-10.
authorNick Bowler <nbowler@draconx.ca>
Mon, 14 Jun 2021 03:43:46 +0000 (23:43 -0400)
committerNick Bowler <nbowler@draconx.ca>
Mon, 14 Jun 2021 03:46:58 +0000 (23:46 -0400)
17 files changed:
Rules
content/images/eclipse-20210610-clouds.jpg [new symlink]
content/images/eclipse-20210610-clouds.yaml [new file with mode: 0644]
content/images/eclipse-20210610-farewell.jpg [new symlink]
content/images/eclipse-20210610-farewell.yaml [new file with mode: 0644]
content/images/eclipse-20210610-max.jpg [new symlink]
content/images/eclipse-20210610-max.yaml [new file with mode: 0644]
content/images/eclipse-20210610-sunrise.jpg [new symlink]
content/images/eclipse-20210610-sunrise.yaml [new file with mode: 0644]
content/style.scss
content/weblog/eclipse-20210610.md [new file with mode: 0644]
layouts/copyright.xml [new file with mode: 0644]
layouts/default.xml
layouts/default.xsl
layouts/imginfo.xsl
lib/helpers.rb
lib/imginfo.rb

diff --git a/Rules b/Rules
index d3247cb8e2be32b62a75789495b9ed79ae29c05e..26bcb40ac7b1a8f0dbe86ba9f22b5edbc44d6300 100644 (file)
--- a/Rules
+++ b/Rules
@@ -100,6 +100,7 @@ postprocess do
 
                 # Find output reps corresponding to this key, if any
                 item.reps.each do |rep|
+                    next if rep.raw_path.nil?
                     next unless
                         FileUtils.identical?(item.raw_filename, rep.raw_path)
 
@@ -179,11 +180,21 @@ compile '/license/cc*.xhtml' do
 end
 
 compile '/images/*.jpg', rep: :large do
+    w, h = FastImage.size(item.raw_filename)
     filename = item.identifier.without_ext + '-t1200.' + item.identifier.ext
-    filter :imgresize, width: 1200, height: 1200, cache: filename
+    filter :imgresize, width: [w, 1200].min, height: [h, 1200].min, cache: filename
     write filename
 end
 
+compile '/images/*.jpg', rep: :medium do
+    w, h = FastImage.size(item.raw_filename)
+    filename = item.identifier.without_ext + '-t800.' + item.identifier.ext
+    if w > 900 or h > 900
+        filter :imgresize, width: 800, height: 800, cache: filename
+        write filename
+    end
+end
+
 compile '/images/*.jpg', rep: :info do
     filter :imginfo
     layout '/imginfo.xsl'
diff --git a/content/images/eclipse-20210610-clouds.jpg b/content/images/eclipse-20210610-clouds.jpg
new file mode 120000 (symlink)
index 0000000..0fb46fb
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/jf/z5/SHA512-s257188--05f166f1b15bb984ed150ea4a87e369f58dccdb3a561ae0ec2975da1e194bffc4da0a36dc14d2c6d881bc1424d1eff66241701291bab666f2a95beac66412415/SHA512-s257188--05f166f1b15bb984ed150ea4a87e369f58dccdb3a561ae0ec2975da1e194bffc4da0a36dc14d2c6d881bc1424d1eff66241701291bab666f2a95beac66412415
\ No newline at end of file
diff --git a/content/images/eclipse-20210610-clouds.yaml b/content/images/eclipse-20210610-clouds.yaml
new file mode 100644 (file)
index 0000000..dd687fa
--- /dev/null
@@ -0,0 +1,7 @@
+---
+title: 2021-06-10 Partial Eclipse Through Clouds
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  Almost 20 minutes after the maximum eclipse, the clouds begin to come back
+  to rejoin the fun.
diff --git a/content/images/eclipse-20210610-farewell.jpg b/content/images/eclipse-20210610-farewell.jpg
new file mode 120000 (symlink)
index 0000000..4131a74
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/00/F8/SHA512-s107816--4e990963d97828f1d68bca567d8f5ebd95cbca92106d7b185c3fe32d0bb7ee0f402273f1854929181526c98b0c0a83f8c6befb63bf4855be39e617814c70f38c/SHA512-s107816--4e990963d97828f1d68bca567d8f5ebd95cbca92106d7b185c3fe32d0bb7ee0f402273f1854929181526c98b0c0a83f8c6befb63bf4855be39e617814c70f38c
\ No newline at end of file
diff --git a/content/images/eclipse-20210610-farewell.yaml b/content/images/eclipse-20210610-farewell.yaml
new file mode 100644 (file)
index 0000000..a5ce817
--- /dev/null
@@ -0,0 +1,6 @@
+---
+title: 2021-06-10 Fare Thee Well, Eclipse
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  The spectacle is mostly over as the sun returns to hiding behind some clouds.
diff --git a/content/images/eclipse-20210610-max.jpg b/content/images/eclipse-20210610-max.jpg
new file mode 120000 (symlink)
index 0000000..4a36ece
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/Qv/71/SHA512-s456326--212c721a3ee49a09a1b7f55fd3a7db962cec291f2e1c9274b954e029bf41451476d5aef545596af7f1bce45132541a7a9760357bdc7b03ff50a8bf83d40b9d80/SHA512-s456326--212c721a3ee49a09a1b7f55fd3a7db962cec291f2e1c9274b954e029bf41451476d5aef545596af7f1bce45132541a7a9760357bdc7b03ff50a8bf83d40b9d80
\ No newline at end of file
diff --git a/content/images/eclipse-20210610-max.yaml b/content/images/eclipse-20210610-max.yaml
new file mode 100644 (file)
index 0000000..535ac54
--- /dev/null
@@ -0,0 +1,7 @@
+---
+title: 2021-06-10 Maximum Eclipse from Ottawa
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  Greatest eclipse as seen from Ottawa on 2021-06-10, with approximately 80% of
+  the sun obscured.
diff --git a/content/images/eclipse-20210610-sunrise.jpg b/content/images/eclipse-20210610-sunrise.jpg
new file mode 120000 (symlink)
index 0000000..c511776
--- /dev/null
@@ -0,0 +1 @@
+../../.git/annex/objects/V5/kz/SHA512-s55143--bb4418266b4e21b6a4b3f39f7aec3deb6593ce0fdaf3b7d60feca81e7398165315b69e989012e583352ad2d4960f890099bd570f39d7e286b14a07bcf6a70534/SHA512-s55143--bb4418266b4e21b6a4b3f39f7aec3deb6593ce0fdaf3b7d60feca81e7398165315b69e989012e583352ad2d4960f890099bd570f39d7e286b14a07bcf6a70534
\ No newline at end of file
diff --git a/content/images/eclipse-20210610-sunrise.yaml b/content/images/eclipse-20210610-sunrise.yaml
new file mode 100644 (file)
index 0000000..c85adbb
--- /dev/null
@@ -0,0 +1,7 @@
+---
+title: 2021-06-10 Sunrise from Ottawa
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+  The eclipse is well under way at sunrise.  Some clouds obscure the horizon
+  but after about 10 minutes the sun begins to rise above them.
index a550ea291b09dde4dcee40cb329bafe2c2565dd5..a5cffbed589639802d58df42e7c676ce3c766e36 100644 (file)
@@ -44,15 +44,39 @@ body {
     margin: 1em;
 }
 
-a:link { color: $linkdefaultcolour; }
-a:visited { color: $linkvisitedcolour; }
-a:active { color: $linkactivecolour; }
+a:link { color: $linkdefaultcolour; border-color: $linkdefaultcolour; }
+a:visited { color: $linkvisitedcolour; border-color: $linkvisitedcolour; }
+a:active { color: $linkactivecolour; border-color: $linkactivecolour; }
 
 h1 { @include header_size(60em, 2em); }
 h2 { @include header_size(60em, 1.5em); }
 h5 { @include header_size(60em, 1em); }
 
-p>img { max-width: 40em; width: 100%; height: auto; }
+p.img {
+    text-align: center;
+
+    img {
+        vertical-align: bottom;
+        max-width: 40em;
+        width: 100%;
+        height: auto;
+    }
+
+    a {
+        text-decoration: none;
+        display: inline-block;
+        border: solid 2px;
+    }
+
+    small {
+        color: $foregroundcolour;
+        text-align: justify;
+        @media (max-width: 24em) { text-align: left; }
+        padding: 0.5ex;
+        display: block;
+        font-size: 0.9em;
+    }
+}
 
 p, dt, dd, li {
     text-align: justify;
diff --git a/content/weblog/eclipse-20210610.md b/content/weblog/eclipse-20210610.md
new file mode 100644 (file)
index 0000000..2563fb1
--- /dev/null
@@ -0,0 +1,26 @@
+---
+title: Solar Eclipse in Ottawa on 2021-06-10
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+published: 2021-06-13T23:43:47-0400
+---
+
+On June 10, 2021, I tried to take some photos of the sun, but the moon
+kept getting in the way.
+
+<%= item_to_img(@items["/images/eclipse-20210610-sunrise.jpg"], caption: <<EOF
+Typical Ottawa: a perfectly nice day except for that one stupid cloud in the sky.
+EOF
+) %>
+<%= item_to_img(@items["/images/eclipse-20210610-max.jpg"], caption: <<EOF
+I told the cloud that I called by-law and will stand here until they come.
+EOF
+) %>
+<%= item_to_img(@items["/images/eclipse-20210610-clouds.jpg"], caption: <<EOF
+But the cloud came back, the very next hour.
+EOF
+) %>
+<%= item_to_img(@items["/images/eclipse-20210610-farewell.jpg"], caption: <<EOF
+I will follow this cloud home if I have to!
+EOF
+) %>
diff --git a/layouts/copyright.xml b/layouts/copyright.xml
new file mode 100644 (file)
index 0000000..a3153bb
--- /dev/null
@@ -0,0 +1,29 @@
+<%
+s = ' ' * (@indent || 2)
+copyyears = {}
+copyauthors = {}
+[@item.fetch(:copyright, [])].flatten.each do |copyright| %>
+<%=s%><copyright><%= copyright %></copyright><%
+x = expand_copyright(copyright)
+x[:years].each do |y| copyyears[y] = 1 end
+copyauthors[x[:name]] = 1
+end
+copyyears.keys.sort.each do |y| %>
+<%=s%><copyright-year><%= y %></copyright-year><%
+end
+copyauthors.keys.sort.each do |i| %>
+<%=s%><copyright-holder><%= i %></copyright-holder><%
+end
+  if @item[:license] then
+    licref = @item[:license].split
+    lic = find_license(licref[0]) %>
+<%=s%><license>
+<%=s%>  <identifier><%= File::basename(lic.identifier.without_ext) %></identifier>
+<%=s%>  <shortname><%= license_shortname(lic) %></shortname>
+<%=s%>  <name><%= [lic[:title], licref[1..-1]].compact.join(" ").strip %></name>
+<%=s%>  <uri><%= item_uri(lic) %></uri>
+<%=s%>  <modification-allowed><%=
+      lic[:"modification-allowed"] == true ? "yes" : "no"
+    %></modification-allowed>
+<%=s%></license><%
+end %>
index 949290a5af4d29559336c5eccb0cabc282f1a06d..2e003aefeb975fd077edf9e3b8848782b06c07a8 100644 (file)
     doc_header = doc.xpath("string(//xhtml:h1)", Xmlns)
   end
 %><document>
-  <title><%= @item.fetch(:title, doc_header) %></title>
-<% [@item.fetch(:copyright, [])].flatten.each do |copyright|
-%>  <copyright><%= copyright %></copyright>
-<% end
-  if @item[:license] then
-    licref = @item[:license].split
-    lic = find_license(licref[0])
-%>  <license>
-    <identifier><%= File::basename(lic.identifier.without_ext) %></identifier>
-    <name><%= [lic[:title], licref[1..-1]].compact.join(" ").strip %></name>
-    <uri><%= item_uri(lic) %></uri>
-    <modification-allowed><%=
-      lic[:"modification-allowed"] == true ? "yes" : "no"
-    %></modification-allowed>
-  </license>
-<% end
-%>  <source>
+  <title><%= @item.fetch(:title, doc_header) %></title><%=
+render '/copyright.xml' %><%
+find_images.each do |item| %>
+  <image>
+    <title><%= item[:attrname] || item[:title] %></title>
+    <uri><%= item_uri(item, rep: :info) %></uri><%=
+render '/copyright.xml', :item => item, :indent => 4
+%>  </image><%
+end %>
+  <source>
 <% if @item.raw_filename %>
     <file><%= item_source(@item) %></file>
 <% elsif File.basename(@item.identifier) == "index.lst" %>
index a97f784c2d3e2cb63a4446e4b6eaadf0ed2f7f21..2e14ce8e29fa93cb737624d069474c95548040db 100644 (file)
@@ -22,8 +22,9 @@
   xmlns:xhtml='http://www.w3.org/1999/xhtml'
   xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
   xmlns:func='http://exslt.org/functions'
+  xmlns:exslt='http://exslt.org/common'
   xmlns:f='http://draconx.ca/my-functions'
-  extension-element-prefixes='func f'
+  extension-element-prefixes='exslt func f'
   exclude-result-prefixes='xhtml'>
 
 <xsl:import href='layouts/functions.xsl' />
   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
 </xsl:template>
 
+<xsl:template name='notransform' mode='notransform' match='node()|@*'>
+  <xsl:copy>
+    <xsl:apply-templates mode='notransform' select='node()|@*' />
+  </xsl:copy>
+</xsl:template>
+
 <!--
   Nokogiri's pretty-printer is a bit weird.  Regardless of the indentation
   setting, if an element has no child text nodes then it will be pretty-
 </xsl:template>
 
 <xsl:template match='license'>
+  <xsl:variable name='node' select='.' />
   <p>
-    <xsl:text>Copying and distribution of this material</xsl:text>
+    <xsl:choose>
+      <xsl:when test='/document/image[license != $node]'>
+        <xsl:text>Except as otherwise noted, copying</xsl:text>
+      </xsl:when>
+      <xsl:otherwise>Copying</xsl:otherwise>
+    </xsl:choose>
+    <xsl:text> and distribution of this material</xsl:text>
     <xsl:if test='normalize-space(modification-allowed)="yes"'>
       <xsl:text>, with or without modification,</xsl:text>
     </xsl:if>
 </xsl:template>
 
 <xsl:template name='image-attribution'>
-<!--
-  <xsl:variable name='x' select='/document/image[copyright-holder="Nick Bowler"][1]' />
-  <xsl:variable name='y' select='/document/image[copyright-holder="Nick Bowler"][4]' />
--->
   <xsl:variable name='images-fragment'>
     <xsl:for-each select='/document/image'>
       <xsl:sort select='number(copyright-holder = /document/copyright-holder)'
   </xsl:if>
 </xsl:template>
 
+<xsl:template match='xhtml:p[count(*)=1 and normalize-space(text())=""
+                             and descendant::xhtml:img]'>
+  <xsl:copy>
+    <xsl:apply-templates select='@*[local-name() != "class"]' />
+    <xsl:attribute name='class'>
+      <xsl:if test='@class'>
+        <xsl:value-of select='concat(@class, " ")' />
+      </xsl:if>
+      <xsl:text>img</xsl:text>
+    </xsl:attribute>
+    <xsl:apply-templates select='node()' />
+  </xsl:copy>
+</xsl:template>
+
 <xsl:template match='/'>
   <html>
     <head>
       <div id='footer'>
         <xsl:apply-templates select='/document/copyright' />
         <xsl:apply-templates select='/document/license' />
+        <xsl:if test='/document/image'>
+          <xsl:call-template name='image-attribution' />
+        </xsl:if>
         <xsl:apply-templates select='/document/source' />
       </div>
     </body>
index 7859d8b8bb46a06b13fba6fe7c9f06c14e736b18..e39c07d7fa923ad0a6e9886536550bdd5fb3b962 100644 (file)
@@ -30,7 +30,7 @@
   <xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
 </xsl:template>
 
-<xsl:template name='exif'>
+<xsl:template name='exif' mode='exif' match='*'>
   <xsl:param name='node' select='.' />
   <xsl:param name='name' select='local-name($node)' />
 
@@ -39,6 +39,7 @@
     <td><xsl:value-of select='$node' /></td>
   </tr>
 </xsl:template>
+
 <xsl:template match='exif'>
   <h2>Metadata</h2>
   <table>
       <tr><th>Attribute</th><th>Value</th></tr>
     </thead>
     <tbody>
-      <xsl:call-template name='exif'>
-        <xsl:with-param name='node' select='make' />
+      <xsl:apply-templates select='make' mode='exif'>
         <xsl:with-param name='name' select='"Camera make"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
-        <xsl:with-param name='node' select='model' />
+      </xsl:apply-templates>
+      <xsl:apply-templates select='model' mode='exif'>
         <xsl:with-param name='name' select='"Camera model"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
-        <xsl:with-param name='node' select='date_time_original' />
+      </xsl:apply-templates>
+      <xsl:apply-templates select='date_time_original' mode='exif'>
         <xsl:with-param name='name' select='"Date taken"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
+      </xsl:apply-templates>
+      <xsl:apply-templates select='exposure_time' mode='exif'>
         <xsl:with-param name='node' select='concat(exposure_time, "s")' />
         <xsl:with-param name='name' select='"Shutter speed"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
+      </xsl:apply-templates>
+      <xsl:apply-templates select='f_number' mode='exif'>
         <xsl:with-param name='node' select='concat("f/", f_number)' />
         <xsl:with-param name='name' select='"Aperture"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
-        <xsl:with-param name='node' select='exposure_bias_value' />
+      </xsl:apply-templates>
+      <xsl:apply-templates select='exposure_bias_value' mode='exif'>
         <xsl:with-param name='name' select='"Exposure compensation"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
+      </xsl:apply-templates>
+      <xsl:apply-templates select='focal_length' mode='exif'>
         <xsl:with-param name='node' select='concat(focal_length, "mm")' />
         <xsl:with-param name='name' select='"Focal length"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
-        <xsl:with-param name='node' select='flash' />
+      </xsl:apply-templates>
+      <xsl:apply-templates select='flash' mode='exif'>
         <xsl:with-param name='name' select='"Flash"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
-        <xsl:with-param name='node' select='iso_speed_ratings' />
+      </xsl:apply-templates>
+      <xsl:apply-templates select='iso_speed_ratings' mode='exif'>
         <xsl:with-param name='name' select='"ISO speed rating"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
-        <xsl:with-param name='node' select='exposure_program' />
+      </xsl:apply-templates>
+      <xsl:apply-templates select='exposure_program' mode='exif'>
         <xsl:with-param name='name' select='"Exposure program"' />
-      </xsl:call-template>
-      <xsl:call-template name='exif'>
-        <xsl:with-param name='node' select='metering_mode' />
+      </xsl:apply-templates>
+      <xsl:apply-templates select='metering_mode' mode='exif'>
         <xsl:with-param name='name' select='"Metering mode"' />
-      </xsl:call-template>
+      </xsl:apply-templates>
     </tbody>
   </table>
 </xsl:template>
index c9a1cc3d503f343e63175bce9695c39d3e11b989..cc0bf046b6beb8e89c1950c8021ab8061c21ff8a 100644 (file)
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 require 'nokogiri'
+require 'fastimage'
 
-use_helper Nanoc::Helpers::Breadcrumbs
 use_helper Nanoc::Helpers::Blogging
+use_helper Nanoc::Helpers::Breadcrumbs
+use_helper Nanoc::Helpers::Rendering
 
 Xmlns = {
     'xhtml' => 'http://www.w3.org/1999/xhtml'
@@ -75,6 +77,81 @@ def counter(name = :default, item = @item)
     name.to_s.capitalize + " " + ($counters[item][name] += 1).to_s
 end
 
+def img_rep_fallback(item, rep)
+    return rep unless item.reps[rep].raw_path.nil?
+    return :large
+end
+
+def item_to_img(item, rep: :large, alt: nil, caption: nil)
+    unless item
+        return "[image not found]"
+    end
+
+    alt ||= item[:title]
+    caption ||= alt
+    caption = caption.strip
+    caption.gsub!(/\s+/, " ")
+
+    rep = img_rep_fallback(item, rep)
+    attrs = { :src => item_uri(item, rep: rep), :alt => item[:title] }
+    attrs[:width], attrs[:height] = FastImage.size(item.reps[rep].raw_path)
+
+    b = Nokogiri::XML::Builder.new do |xml|
+        xml.a(:href => item_uri(item, rep: :info)) {
+            xml.img(attrs)
+            unless caption.empty?
+                xml << " &#x2060;"
+                xml.small {
+                    xml << caption
+                }
+            end
+        }
+    end
+    b.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
+end
+
+def expand_copyright(copyright)
+    result = { :years => {} }
+
+    /^([-–[:digit:][:space:],]*)(.*)$/.match(copyright) do |m|
+        m[1].split(/\s*,\s*/).each do |range|
+            lo, hi = range.split(/[^[:digit:]]+/)
+            for y in (lo..hi||lo)
+                result[:years][y] = 1
+            end
+        end
+
+        result[:years] = result[:years].keys.sort
+        result[:name] = m[2]
+        return result
+    end
+
+    return nil
+end
+
+def path_to_rep(path)
+    @items.find_all(File::dirname(path) + "/*").each do |item|
+        item.reps.each do |rep|
+            return rep if rep.path == path
+        end
+    end
+    return nil
+end
+
+def find_images(item = @item)
+    return [] if item.binary?
+
+    result = {}
+    doc = Nokogiri::HTML(item.compiled_content(snapshot: :pre))
+    doc.xpath("//img/@src").each do |imgsrc|
+        rep = path_to_rep(imgsrc.value)
+        if rep
+            result[rep.item.identifier] ||= rep.item
+        end
+    end
+    return result.values
+end
+
 def human_filesize(size)
     units = ["B", "KiB", "MiB", "GiB"]
     prec = 0
@@ -90,3 +167,7 @@ def human_filesize(size)
 
     sprintf("%.*f %s", prec, size + 0.05, unit)
 end
+
+def license_shortname(item)
+    item.fetch(:shortname, File::basename(item.identifier.without_ext).upcase)
+end
index 13d6b01c6c3a8d3aa5d3e82b4a6125aa4a088181..668cd5a645e68d586160d83234c85df7bbc53863 100644 (file)
@@ -25,17 +25,18 @@ class ImgInfoFilter < Nanoc::Filter
 
     def do_variant(xml, name, item = @item, rep: :default)
         file = item.reps[rep].raw_path
+        unless file.nil?
+            w, h = FastImage.size(file)
+            sz = File.size(file)
 
-        w, h = FastImage.size(file)
-        sz = File.size(file)
-
-        xml.variant {
-            xml.name(name)
-            xml.uri(item_uri(item, rep: rep))
-            xml.width(w)
-            xml.height(h)
-            xml.filesize(human_filesize(sz))
-        }
+            xml.variant {
+                xml.name(name)
+                xml.uri(item_uri(item, rep: rep))
+                xml.width(w)
+                xml.height(h)
+                xml.filesize(human_filesize(sz))
+            }
+        end
     end
 
     def run(filename, params = {})
@@ -44,6 +45,7 @@ class ImgInfoFilter < Nanoc::Filter
         b = Nokogiri::XML::Builder.new do |xml|
             xml.image {
                 do_variant(xml, "Large", rep: :large)
+                do_variant(xml, "Medium", rep: :medium)
                 do_variant(xml, "Original")