Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

画像をJavaScriptでXHR経由で読み込む方法

f:id:fa11enprince:20201111022041j:plain
やむをえない事情があり、JWT認証を通過して画像を表示させる必要があったので、
XHR(Ajax)で画像を読み込む方法を調べました。
同一ドメインならこんな苦労はしないのにーとか最近そんなことを悩んでいます。

サーバーサイドのコード

何でもいいのですが、最近お気に入りのexpress
サクッとなんか書くのが楽なんです。環境構築も長楽だし。
JavaScriptいいお。
Nodeはversion 12くらい。Expressはversion 4です。

$ express --view=ejs myapp

とかでサクッとひな形作って、
とりあえずroutes/image.jsとかいうルーターを作り、単にバイナリデータを返します。

const express = require('express');
const router = express.Router();
const fs = require('fs');

router.get('/', async (req, res, next) => {
    fs.readFile('./public/images/nyam.jpg', (err, data) => {
        if (err) {
            console.error(err);
            res.sendStatus(500);
            return;
        }
        res.writeHead(200, {'Content-Type': 'image/jpg'});
        res.end(data);
    });
});

module.exports = router;

index.jsのコードは省きます。
nyam.jpgというのは猫の画像です。にゃーむ。
当然PythonのFlaskでもRuby On Railsでもなんでもええです。楽なやつにしましょう。
Spring Bootでもいいですが若干だるいと思います。

フロントエンドのコード

jQueryでも実はできるとのことですが、まぁXHRでいいでしょう。 Blobってのを使うと超絶らくちんです。
Base64ならなんでもできるぜい、っていう素敵なおっさんから話は聞いてましたけど
めんどっちいんでBlobで。

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <div style='height: 300px;'>
      <img id='img' alt='nyam' height="300" />
    </div>
  </body>
  <script>
  var loadBinaryImage = function(url, cb) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        cb(this.response);
      }
    }
    xhr.open('GET', url);
    xhr.responseType = 'blob';
    xhr.send();
  }
  document.addEventListener('DOMContentLoaded', function () {
    loadBinaryImage('./image', function (blob) {
      var img = document.getElementById('img');
      img.src = URL.createObjectURL(blob);
    });
  });
  </script>
</html>

これを調べた動機

そもそも画像を表示するなら

<img src='http://localhost:3000/images/nyam.jpg' alt='nyam.jpg'>

で事足りるわけですが、 やんごとなき事情によりAuthorization: Bearer [token]がないと画像が取れねーよって状況になってるので調べました。 さてと動画というと… 206の応答を返してごにょごにょして…

こんな感じでできるっぽい。
重い動画の場合は知らん。
そもそもほかのドメインに動画読み取らせるのはなぁという…。

参考リンク

XMLHttpRequestで取得した画像バイナリをJavaScriptでロードする - kinjouj.github.io
AjaxでバイナリのJPEG画像データを受け取って表示する - Qiita

Hyper-VのGuestでWSL2でdockerを動かしてみる

f:id:fa11enprince:20200819231041j:plain
こんなのすぐに終わるかなー。と思ったら、案の定、罠が仕掛けられてました。単なる備忘録です。
Windows 10 Enterprise 2004でお試ししてみました。

Windows 10 Enterpriseのゲストを作成してみる

ダウンロード編

評価版の仮想環境を作れるよーってことで、QuickからWindows 10 dev environmentを作ります。
期限付きとはいえ、太っ腹っすな、最近のMSさん。
が、しょっぱなから失敗する。
"Failed hash verification" in Hyper-V Quick Create with Windows 10 dev environment
とか出るよ。

ということで、直にDLする。
https://download.microsoft.com/download/7/c/e/7ce1271d-04bb-4f0f-b1f4-e031bccb9712/WinDev2007Eval.HyperVGen2.zip
んで、Explorerで管理者としてアクセス
C:\Users\Public\Documents\Hyper-V\
移動してダウンロードしたWinDev2007Eval.HyperVGen2.zipを展開した中にある、
WinDev2007Eval.vhdxファイルを
C:\Users\Public\Documents\Hyper-V\Virtual hard disks
に置く。別にここじゃなくてもよいのだけれど、ダウンロードフォルダに置きっぱなしもなんだかなぁというよくわからない心情でこうしました。
Zipファイルの解凍めちゃ遅い…。そこそこいいスペックのマシーンなんだけどな。M2.SSDでCore i7-9700KなのでそこそこおれTUEEEEができるスペックのはず。

Guestマシン作成編

Hyper-V Managerから
Local intallation sourceで、Change installation sourceで先ほどのvhdxを選ぶ
Name: Windows 10 dev environment
Network: Default Switch
Create Virtual Machineを押す
これでOK

Docker DesktopをWSL2でやってみる

Hyper-VのGeuest上で何も考えずにDocker Desktopをインストールすると、
Please enable the Virtual Machine Platform Windows feature and ensure virtualization is enabled in the BIOS.
とか出る。またか。。。

Install Windows Subsystem for Linux (WSL) on Windows 10 | Microsoft Docs
仮想マシン プラットフォーム" オプション機能を有効にしてみる
Powre Shellを管理者で起動して以下のコマンドを実行。
ついでにWSL2をデフォにする。

> dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
> wsl --set-default-version 2

これでもダメ
はいはいVT-xが有効になってないのね。
GuestのVT-xが有効かどうか確認するためにTask ManagerをみるとVirtualization: Enabledのかわりに
Virtual Machine: Trueになってる
おーこれはどうするんだ?と調べたらすぐに出てきた。

HOST側のPowerShellで管理者権限で実行
kakkoyakakko2.hatenablog.com

Nested Virtualization を設定

Set-VMProcessor -VMName "<VMName>" -ExposeVirtualizationExtensions $true

これをやってGuestを立ち上げると…(なんか時間かかるけど…) f:id:fa11enprince:20200819231441p:plain
できたできた。
ああ、そうかHyper-VのGuest上のでWSL2だとやっぱり内部的にHyper-Vを使ってるから、
Hyper-VのHyper-VになるからNested virtualizationってことが必要なのか。

所感

Hyper-V使えるPro EditionだったけどWindowsのお試し環境をお手軽に作れるのはうれしい。
WSL2速くないすか?
あと、2004にしたらWSL2に乗り換えようかなと思いました。

Windows 10の「フォト」アプリでiPhoneからの画像転送がうまくいかない


iPhoneから写真を転送しようとUSBで繋ぐと、「フォト」アプリが立ち上がって、
インポートするように促してくるのに、
「問題が発生しました。ファイルがインポートされていない可能性があります。」
とほぼ確実にエラーが出てきます。その対処法を書きます。
詳細のリンクを押してもまるで役に立たないし、リトライすると同じファイルがリネームされてダブってしまいます。
せめて中途半端にインポートした写真を残さずにゴミ箱に入れてくれと思います。エラーも詳細がわかりません(十中八九動画の変換エラーです)。 次期アップデートでこの辺りが治ってくれると嬉しい。
2023年06月24日 追記
ちなみに私のWindows 11環境ではiPhoneをUSBで繋ぎっぱなしで再起動するとExplorer上で認識されないようなので、 再起動してからUSBで繋いでiPhone上で許可としないといけないようです。
iTunesからの読み込みはその限りではありません。
前からそうだった説はあります。

前提条件と結論

おおよそこのバージョンに近ければ状況はほぼ同じだと思います。
私の場合の環境を書いておきます。

OS バージョン
Windows 10 Pro 1903
iOS 13.6

結論を先に書くと対処法1,2がおすすめです。

まるで効果がないガセネタ対処法

  • ケーブルを純正にする
    →関係ありません

対処法1 iPhone側で画像・動画の形式の変換をしないようにする

  • メリット
    フォトアプリがそのまま使えてすべてのエラーが解決するわけではありませんが、9割方成功する方法です。  
  • デメリット
    動画はデフォルトでWindows上ではそのままでは見れない。

画像・動画の形式の変換をしないようにするにはiPhone上で、
設定 > 写真から一番下の元のフォーマットのままを選択する(デフォルトは自動)

これをやってからもう一度インポートするとエラーが出ずに正常終了できるかと思います。
これでも解決しない場合は対処法2か3を試します。

対処法2 Explorerの画像とビデオのインポートを使う

  • メリット
    確実に成功する。どのファイルでエラーが起きたかわかる。リトライが可能。
  • デメリット
    フォルダ分けが通常のフォトアプリの分け方と違う。

ExplorerでiPhoneアイコンで右クリック > 画像とビデオのインポート

あとは「すべての新しい項目のインポート」を選んで「次へ」を押しましょう。
グループ分けができるのですが、奇妙なフォルダわけを行うので1フォルダにまとめてしまうのが良いでしょう。
インポートが実行されてエラーが出るとダイアログが出現するのでスキップするなりしましょう。

対処法3 フォトアプリを使わずiCloudアプリを使う

  • メリット
    従来からある方法で安定している
  • デメリット
    iPhone→クラウド→Windowsとなるし、そもそもiCloudで大きい容量を契約していないと使えない。 さらに、わざわざiCloudアプリを入れないといけないうえにiCloudのアカウントが必要(iPhone使っていたら持っているが)。

対処法4 iTunes経由で同期

  • メリット
    同期はできるが、2020年現時点ではデメリットが大きすぎて使うメリットが皆無。
  • デメリット
    iPhoneにしかないデータが消失する可能性がある(ということが過去にあった。現在は不明)。PCにしかなくてiPhoneにない場合もあり得る。複数デバイス間で同期している場合さらにわけがわからなくなる。そもそも複数デバイス間の同期はシステム的にも難易度が高い。

これはやめたほうが良い。

対処法5 何らかの別アプリで写真を抜き出す

実は書いていてこれが一番いいような気がしてきた…。
のですが、特によさそうなものが見つからなかったのでやはり対処法1,2が良いかと思います。

参考

投稿日・更新日で古い記事だと強調表示するカスタマイズ

f:id:fa11enprince:20200805232031j:plain

概要

はてなブログの記事毎のページで更新日を表示しつつ、投稿日 or 更新日から1年以上経過していたら
「この記事は投稿・更新されてから一年以上経過しています」と表示します。
更新日ははてなブログ上では表示されないので、自分で書く必要があります。
どこでもいいのですが記事の先頭とかに次のようなHTMLを記述します

<time class="entry-updated updated">2015-01-24</time>

表示例PC1

f:id:fa11enprince:20200805232227p:plain
投稿日の右側に更新日がアイコン付きで表示され、さらに見出し下部に、 「この記事は投稿・更新されてから一年以上経過しています」と表示されています。

表示例PC2

f:id:fa11enprince:20200805232253p:plain
同様です。こちらの例はここに示した方法とは別にCSSで別途調整したほうが良いかもしれません。
スマホの表示例は割愛します。

設定方法

JavaScriptとCSSを各箇所に追加します。

headに要素を追加

設定 > 詳細設定 > headに要素を追加

<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<script>
(function(){
  document.addEventListener('DOMContentLoaded', function() {
    var dstr_updated = insertUpdatedDate();
    if (dstr_updated !== '') {
      displayRecentlyNotUpdated(dstr_updated);
    }
  }, false);
  
  function insertUpdatedDate() {
    var entry = document.getElementsByClassName("page-entry");
    if (entry.length >= 1) {  // 記事のみのページで有効
      var entry_date_wrapper = entry[0].getElementsByClassName("date first");
      var dstr_updated = entry[0].getElementsByClassName("entry-updated updated");
      if (entry_date_wrapper.length >= 1 && dstr_updated.length >= 1) {
        entry_date_wrapper[0].appendChild(dstr_updated[0]);
        return dstr_updated[0].innerText;
      }
    }
  }
   
  function displayRecentlyNotUpdated(dstr_updated) {
    var today = new Date();
    // 記事の投稿日時
    var entry_created = document.getElementsByClassName("updated");   // .entry-footer-time > .updated
    var dst_created = entry_created.item(0).getAttribute("datetime");
    var entry_date = new Date(dst_created);
    // 記事の投稿日時に一年を加算する
    entry_date.setFullYear(entry_date.getFullYear() + 1);
    var entry_updated_date = undefined;
    if (dstr_updated != null) {
      entry_updated_date = new Date(dstr_updated);
      // 記事の更新日時に一年を加算する
      entry_updated_date.setFullYear(entry_updated_date.getFullYear() + 1);
    }
    // 記事の投稿日時が記事を開いた日時よりも過去ならメッセージを追加
    if (entry_date <= today) {
      if (entry_updated_date === undefined || entry_updated_date <= today) {
        var doc = document.getElementsByClassName("entry-content").item(0);
        var entry_notice = "<div class=\"entry-notice\">この記事は投稿・更新されてから一年以上経過しています</div>";
        doc.innerHTML =  entry_notice + doc.innerHTML;
      }
    }
  }
})();
</script>

PC

デザイン > カスタマイズ > デザインCSSに下記コードを入れてください。

.entry-content .updated {
    display:none;
}
.page-entry .entry-header .updated {
    display:inline-block;
    font-size: 15px; /* PCのみ */
}
.page-entry .entry-header .updated:before {
    font-family: FontAwesome;
    content: "\f021";
    left: 0;
    top: 0;
    color: #454545;
    padding-left: 20px;
    padding-right: 8px;
}
.entry-notice {
    text-align: right;
    font-size: 80%;
    color: #999;
    background-color: #F0F0FA;
    margin-bottom: 20px;
}

スマホ

レスポンシブ設定の場合は、特に何もしなくて大丈夫です。
レスポンシブ設定でない場合は、デザイン > スマートフォンの記事上に下記コードを入れてください。

<style>
.entry-content .updated {
    display:none;
}
.page-entry .entry-header .updated {
    display:inline-block;
}
.page-entry .entry-header .updated:before {
    font-family: FontAwesome;
    content: "\f021";
    left: 0;
    top: 0;
    color: #454545;
    padding-left: 20px;
    padding-right: 8px;
}
.entry-notice {
    text-align: right;
    font-size: 80%;
    color: #999;
    background-color: #F0F0FA;
    margin-bottom: 20px;
}
</style>

PCとほぼ同じ内容でコピペなのがイケてないですが、無理に共通化するよりましだと思いました。
生成されるHTMLに付与されるクラスが微妙に違ったりするので…。
なお、微妙にスタイルを変えたい場合は適宜CSSを調整してください。

参考

記事の更新日を手動で表示させる:はてなブログ

はてなブログで古い記事にメッセージを表示するスクリプト

CSSとJSがだんだん肥大化してきてえらいこっちゃになってますが、WordPressをメンテするよりましかなとおもいながらメンテしています。
整理も兼ねてこの記事を書きました。