From 4f4b3ddd920c95d380a2f25dabd4b68457bb1564 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Wed, 24 Jun 2020 01:01:30 -0400 Subject: [PATCH] First stab at images and git-annex. Let's add a photo and some filters to produce a handy little image information page. It's not so nice to store these (relatively) huge files in git, so we'll store them in git-annex and update the deployment scripts to handle it. Nothing links to this image yet as this is pushed mainly to validate the git-annex deployment, but it will be accessible by direct URI. The intention is that the site should still compile with missing annexed components, just that those items will be missing. In the future we could perhaps replace them with stubs (so at least the description part of the info page would work) but we'll save that activity for another day. --- .gitattributes | 1 + Rules | 54 +++++++++++ content/images/k8ne-capacitors.jpg | 1 + content/images/k8ne-capacitors.yaml | 51 ++++++++++ content/style.scss | 53 ++++++++++- layouts/imginfo.xsl | 138 ++++++++++++++++++++++++++++ lib/helpers.rb | 14 +++ lib/imginfo.rb | 111 ++++++++++++++++++++++ lib/imgresize.rb | 37 ++++++++ 9 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 .gitattributes create mode 120000 content/images/k8ne-capacitors.jpg create mode 100644 content/images/k8ne-capacitors.yaml create mode 100644 layouts/imginfo.xsl create mode 100644 lib/imginfo.rb create mode 100644 lib/imgresize.rb diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dc3671a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* annex.backend=SHA512 diff --git a/Rules b/Rules index 5473788..f3f1f0a 100644 --- a/Rules +++ b/Rules @@ -53,6 +53,44 @@ postprocess do end end end + + # Register URLs for git-annex keys + unless (uribase = ENV['ANNEX_URI_BASE'].to_s.chomp("/")).empty? + Open3.popen2("git", "annex", "registerurl") do |stdin, stdout, result| + @items.each do |item| + l = File.readlink(item.raw_filename) + next unless l =~ %r{/annex/objects/} + + key = File.basename(l) + + # Find output reps corresponding to this key, if any + item.reps.each do |rep| + next unless + FileUtils.identical?(item.raw_filename, rep.raw_path) + + loop do + STDOUT.write(stdout.read_nonblock(100)) + rescue EOFError, IO::WaitReadable + break + end + + stdin.printf("%s %s%s\n", key, uribase, rep.path) + end + rescue Errno::EINVAL + end + + stdin.close + loop do + STDOUT.write(stdout.readpartial(100)) + rescue EOFError + break + end + + unless (rc = result.value).success? + printf("git annex registerurl failed: %s\n", rc.to_s) + end + end + end end compile '/license/gpl*.md' do @@ -99,6 +137,22 @@ compile '/license/cc*.xhtml' do write to_xhtml end +compile '/images/*.jpg', rep: :large do + filter :imgresize, width: 1200, height: 1200 + write item.identifier.without_ext + '-t1200.' + item.identifier.ext +end + +compile '/images/*.jpg', rep: :info do + filter :imginfo + layout '/imginfo.xsl' + layout '/default.xml' + layout '/default.xsl' + filter :relativize_paths, type: :xml + filter :xhtml_compat + filter :remove_wj + write to_xhtml +end + compile '/**/*.scss' do filter :sass, syntax: :scss filter :css_source, uribase: \ diff --git a/content/images/k8ne-capacitors.jpg b/content/images/k8ne-capacitors.jpg new file mode 120000 index 0000000..2a9a2dc --- /dev/null +++ b/content/images/k8ne-capacitors.jpg @@ -0,0 +1 @@ +../../.git/annex/objects/JW/0W/SHA512-s2751176--43e58095d1a7a641599f648c2e3985ee166e58c0694ef53ccc64ef1027753f36f82a012f5122067b3d0230fb8570cf1167789925147ef0fadbe88b04732f9167/SHA512-s2751176--43e58095d1a7a641599f648c2e3985ee166e58c0694ef53ccc64ef1027753f36f82a012f5122067b3d0230fb8570cf1167789925147ef0fadbe88b04732f9167 \ No newline at end of file diff --git a/content/images/k8ne-capacitors.yaml b/content/images/k8ne-capacitors.yaml new file mode 100644 index 0000000..aa99a36 --- /dev/null +++ b/content/images/k8ne-capacitors.yaml @@ -0,0 +1,51 @@ +--- +title: ASUS K8N-E Deluxe Capacitor Locations +copyright: 2020 Nick Bowler +license: cc-by-sa-4.0 +description: | +

Photo with overlay marking the locations and types of all + electrolytic capacitors on my ASUS K8N-E Deluxe motherboard.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LabelQty.ValueDimensionsBrand
117820µF,  6.3VPitch: 3.5mm, Diameter: 8mm, Height: 13mmOST
261500µF, 6.3VPitch: 3.5mm, Diameter: 8mm, Height: 20mmLtec
331000µF, 16VPitch: 3.5mm, Diameter: 8mm, Height: 20mmNippon Chemi-Con
414100µF,  16VPitch: 2.0mm, Diameter: 5mm, Height: 12mmGSC
+

Several of the OST and Ltec (the latter obscured by the heatsink in + the photo) capacitors have obvious signs of catastrophic failure. + The other types show no obvious damage.

diff --git a/content/style.scss b/content/style.scss index 79d4ffb..b0c042c 100644 --- a/content/style.scss +++ b/content/style.scss @@ -26,9 +26,12 @@ $linkactivecolour: #ff0000; $linkvisitedcolour: #800080; $ruledefaultcolour: #d3d3d3; +$rulestrongcolour: #696969; $annotationcolour: #708090; +$tableshadecolour: #f5f5f5; + @mixin header_size($maxwidth, $fontsize) { font-size: $fontsize; max-width: 1em * ($maxwidth / $fontsize); @@ -48,19 +51,21 @@ a:active { color: $linkactivecolour; } h1 { @include header_size(60em, 2em); } h2 { @include header_size(60em, 1.5em); } +p>img { max-width: 40em; width: 100%; height: auto; } + p, dt, dd, li { text-align: justify; padding: 0; margin: 0; } -p, div, ul, ol, dl, hr { +p, table, div, ul, ol, dl, hr { max-width: 50em; padding: 0; margin: 0; } -p, body>div { margin: 1em 0; } +p, table, body>div { margin: 1em 0; } li { margin: 0 0 0 2em; } dd { margin: 0 0 0 1em; } @@ -88,6 +93,50 @@ kbd { .permalink { visibility: hidden; } } +// General table styles. +table { + border: 1px solid $ruledefaultcolour; + border-collapse: collapse; + width: 100%; +} + +table>* { font-size: 0.9em; } +caption { + caption-side: top; + font-weight: bold; + font-size: 1em; + text-align: left; + margin: 0 0 0.5em 0; +} + +td, th { + vertical-align: middle; + text-align: left; + padding: 1ex; + margin: 0; +} + +thead>tr, tbody>tr { border: solid $ruledefaultcolour; } +th, thead>tr { border-bottom: 1px solid $rulestrongcolour; } +*>table, *>th { border: none; } +thead>tr { border-width: 1px; } +tbody>tr { border-width: 0 1px; } + +td + td { box-shadow: -1px 0 $backgroundcolour; } + +tbody>tr { + &:nth-of-type(even) { background-color: $tableshadecolour; } + &:last-child { border-bottom: solid 1px $ruledefaultcolour; } +} + +// Specific table styles +table.cc { + &>tr>*:first-child, &>*>tr>*:first-child { + &+* { text-align: center; } + text-align: center; + } +} + // Site header rules #breadcrumbs>*, #sitetitle>* { font-size: 0.8em; } #breadcrumbs { diff --git a/layouts/imginfo.xsl b/layouts/imginfo.xsl new file mode 100644 index 0000000..7859d8b --- /dev/null +++ b/layouts/imginfo.xsl @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + +

Metadata

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeValue
+
+ + +
  • + + + +
  • +
    + + +

    Description

    + + + + + +

    +
    +
    +
    + + +

    + + + #desc + + +

    + +

    Files

    +
      + + + +
    + +
    + +
    diff --git a/lib/helpers.rb b/lib/helpers.rb index d2f3a7c..7425075 100644 --- a/lib/helpers.rb +++ b/lib/helpers.rb @@ -63,3 +63,17 @@ def item_longdesc(item) p = xml.xpath('//xhtml:p', Xmlns) if p.empty? then nil else p[0].xpath('string(.)') end end + +def human_filesize(size) + units = ["B", "KiB", "MiB", "GiB"] + + for unit in units + if (size < 1024) + break + end + + size /= 1024.0 + end + + sprintf("%.1f %s", size + 0.05, unit) +end diff --git a/lib/imginfo.rb b/lib/imginfo.rb new file mode 100644 index 0000000..13d6b01 --- /dev/null +++ b/lib/imginfo.rb @@ -0,0 +1,111 @@ +# Nick's web site: imginfo filter. Generate XML representation of image +# metadata which can be further processed into a web page. +# +# Copyright © 2020 Nick Bowler +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +class ImgInfoFilter < Nanoc::Filter + identifier :imginfo + type :binary => :text + + require 'exifr/jpeg' + require 'fastimage' + + def do_variant(xml, name, item = @item, rep: :default) + file = item.reps[rep].raw_path + + 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)) + } + end + + def run(filename, params = {}) + exif = EXIFR::JPEG.new(filename).to_hash + + b = Nokogiri::XML::Builder.new do |xml| + xml.image { + do_variant(xml, "Large", rep: :large) + do_variant(xml, "Original") + + + if @item[:description] + xml.description(:xmlns => 'http://www.w3.org/1999/xhtml') { + xml << @item[:description] + } + end + + xml.exif { + exif.each do |key, value| + # Convert some fields to more useful forms... + case key + when :f_number, :exposure_bias_value + value = sprintf("%.1f", value) + when :focal_length + value = sprintf("%d\n", value) + when :gps_version_id + value = value.bytes.join(".") + when :exposure_program + case value + when 1 then value = "Manual" + when 2 then value = "Normal program" + when 3 then value = "Aperture priority" + when 4 then value = "Shutter priority" + end + when :metering_mode + case value + when 1 then value = "Average" + when 2 then value = "Center weighted average" + when 3 then value = "Spot" + when 4 then value = "Multi-spot" + when 5 then value = "Pattern" + when 6 then vlaue = "Partial" + end + when :flash + tmp = if value & 1 == 1 then "Yes" else "No" end + case (value >> 3) & 3 + when 1, 2 then tmp += ", compulsory" + when 3 then tmp += ", auto" + end + case (value >> 1) & 3 + when 2 then tmp += ", return light not detected" + when 3 then tmp += ", return light detected" + end + value = tmp + end + + case value + when String + value.delete!("\x00") + value.strip! + when Time + # EXIF does not do timezones so don't display one + value = value.strftime("%Y-%m-%d %H:%M:%S") + end + + xml.send((key.to_s << "_").to_sym, value.to_s) + end + } + } + end + b.to_xml + end +end diff --git a/lib/imgresize.rb b/lib/imgresize.rb new file mode 100644 index 0000000..e539f37 --- /dev/null +++ b/lib/imgresize.rb @@ -0,0 +1,37 @@ +# Nick's web site: imgresize filter. Helper for resizing images during site +# compilations for thumbnails, etc. +# +# Copyright © 2016, 2020 Nick Bowler +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +class ImgResize < Nanoc::Filter + identifier :imgresize + type :binary + + def run(filename, params = {}) + w = if params[:width] then params[:width].to_i end + h = if params[:height] then params[:height].to_i end + scale = [w, h && "x", h].join + + args = ["-scale", scale, filename] + case filename + when /\.jpg$/ + args << "-quality" << "85" + end + args << output_filename + + system('gm', 'convert', *args) + end +end -- 2.43.0