- 投稿日:2021-03-05T19:48:45+09:00
ContentType書き換えでImageMagickのリサイズを成功させる
はじめに
先日、「オブジェクト名の変更」でアップロード済み画像のサムネイル生成を簡単にという記事を書きました。
その作業の中で、ContentTypeの問題によって一部の画像でImageMagickのリサイズ(サムネイル生成)に失敗していることが分かりました。
今回はなぜそうなったのか、どう対処したのかについてご紹介します。リサイズに失敗する原因
ContentTypeを指定しないと、application/octet-streamになる
FirebaseのCloud Storageにファイルをアップロードする時にContentTypeが指定されていないと、判別できない場合はアップロード方法に応じて
application/octet-stream
またはapplication/x-www-form-urlencoded
に設定されます。
実際、一部の画像がapplication/octet-stream
になってしまっていました。application/octet-streamではImageMagickで画像のリサイズができない
画像のリサイズはImageMagickを利用しています。
ImageMagickでContentTypeがapplication/octet-stream
だと、画像のリサイズができません。サムネイル生成できるようにする対処法
ContentTypeを書き換える
ContentTypeが
application/octet-stream
の画像が存在することでImageMagickのリサイズが失敗していることが分かりました。
そこでContentTypeを以下のように書き換えましょう。// ファイルからメタデータを取得する const file = await bucket.file(imagePath) await file.getMetadata() if (file.metadata.contentType == 'application/octet-stream') { // octet-streamだったら、image/jpegなどcontentTypeを書き換える const contentType = 'image/jpeg' await file.setMetadata({ contentType: contentType, }) }サンプルでは簡単なソースコードをご紹介するために、application/octet-streamだったらimage/jpegに決め打ちで置き換えるようにしています。
実際のソースコードはjpg, jpeg, png, gifなど、必要なContentTypeを拡張子で判定してセットしています。このようなcontentTypeの書き換えを行ったところ、無事にImageMagickで画像のリサイズができるようになりました。
最後に
このようなContentTypeの書き換えが発生しないように、本来はアップロード時にきちんと指定するのが良いと思います。
とはいえアップロード済みの画像のContentTypeを書き換える場面もあるかもしれません。
そんな時はこちらの方法を試してみてください。
- 投稿日:2021-03-05T16:57:18+09:00
Google Apps Script練習 (Gmailの新着メールをLINEに転送)
GASを練習したいと思い、また自分自身Gmailのメールを見逃してしまう事が多いのでそれをなんとか出来ないかと思いスクリプトを作成しました。
gas.jsconst LINE_NOTIFY_TOKEN = PropertiesService .getScriptProperties() .getProperty('LINE_NOTIFY_TOKEN') const endPoint = 'https://notify-api.line.me/api/notify' // 1. 転送したいメールの送信元アドレスを指定 const fromAddress = [''].join(' OR ') // 2. トリガーの設定間隔と合わせる const minutesInterval = 5 function main() { const notices = fetchNotices() if (notices.length === 0) { return } for (const notice of notices) { send(notice) } } function fetchNotices() { const now = Math.floor(new Date().getTime() / 1000) const intervalMinutesAgo = now - (60 * minutesInterval) // 3. 検索条件を設定 const query = `(is:unread from:(${fromAddress}) after:${intervalMinutesAgo})` // 4. メールを取得する const threads = GmailApp.search(query) if (threads.length === 0) { return [] } const mails = GmailApp.getMessagesForThreads(threads) const notices = [] for (const messages of mails) { const latestMessage = messages.pop() const notice = ` -------------------------------------- 件名: ${latestMessage.getSubject()} 受信日: ${latestMessage.getDate().toLocaleString()} From: ${latestMessage.getFrom()} -------------------------------------- ${latestMessage.getPlainBody().slice(0, 350)} ` notices.push(notice) // 5. メールを既読にする latestMessage.markRead() } return notices } function send(notice) { if (LINE_NOTIFY_TOKEN === null) { Logger.log('LINE_NOTIFY_TOKEN is not set.') return } const options = { 'method': 'POST', 'headers': {'Authorization': `Bearer ${LINE_NOTIFY_TOKEN}`}, 'payload': {'message': notice}, } UrlFetchApp.fetch(endPoint, options) }GmailにおけるThreadとMessageの違いについて理解に時間がかかりました。
Thread: あるメールとそのメールに対する一連の返信(配列みたいになる)
Message: 単体のメール1つGASもLINE APIも、本当便利…
GASって、他にもいろんな事できるんですね…Googleスプレッドシートを活用して議事録をいじったり…
まだまだ知らない事ばかりなので、一度ガッツリ時間を取って勉強したい。
- 投稿日:2021-03-05T01:01:13+09:00
NRIハッカソン bit.Connect 2020 に参加してきたまとめ
はじめに
NRIハッカソン bit.Connect - Hack for NEWSTYLE というイベントに参加してきました。
https://bitconnect.nri.co.jp/IBMクラウドのファンクションを活用していたのを評価され、IBM賞をいただきました!
企業さんの技術サポートも手厚く、第一線の方に直接Slackで手取り足取り教えてもらえたのでめちゃくちゃ贅沢な時間を過ごせました!
つくったもの
お地蔵さんデモ動画
https://www.youtube.com/watch?v=8mviNWsBKp8余談ですが、最初は磁石にピップエレキバンを使ってみたんですが、意外と磁力が弱く、動作が安定しないので、100均で買った磁石に付け替えました。
ストーリーなど作品の詳細はこちら
https://protopedia.net/prototype/2151ハードウェアの実装
今回は2人チームで参加し、私の担当がobnizを用いたハードウェアの部分だったので、実装について困ったことや役に立ったことなどをつらつらと書いていきます
IBMクラウドをはじめて触った友人(AWSは日常的に触ってる人)曰く、とても使いやすかったとのこと
・AWSみたいな迷子になりそうなUIではなく、やりたいことがどうしたらいいのかすぐわかるような素晴らしいUI
・ハッカソンで使うなら、初めてでもこっちの方が楽かもしれない
とのことなので、私も触ってみようかなと思いました。
構成について
今回の作品でobnizでやるべきことは以下の3点でした。
1. IBMクラウドのファンクションからAPIを叩いてハードウェアで動作をさせる
2. 1が起きてから元に戻るまでの時間を計測する
3. 2の結果をIBMクラウドのファンクションに知らせる1~3の流れは、ユーザーがすぐ行動すれば一瞬ですが、なかなか行動しないユーザーもいると想定され、待機時間が読めません。
なので本来は、1の機能だけを請け負うobnizと2~3の機能だけを請け負うobnizの2台構成でやるべきですが、所持台数の制限と、ハッカソンという限られた時間の中で対応するために、1~3を一つのobnizで実装できるように試行錯誤を行いました。(obnizの木戸さんありがとうございました!)obnizクラウドでWebhookURLを吐き出す
結論から言うとこれは、今回の要件には適していなかったので他のサービスを利用しました。
obnizクラウド はobnizのホスティングサービスで簡単にWebhookURLを吐き出せました(今回初利用)
obniz のコンソールにアクセスし、デバイスを選択します(デバイスをまだアカウントに紐づけてない人は紐付けをしてから)
いろんなテンプレートがありますが、今回は「空のプロジェクト」を選択
コードをちゃちゃっと書いて
<html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" /> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@3.x/obniz.js" crossorigin="anonymous" ></script> </head> <body> <div id="obniz-debug"></div> <div class="container"> <div class="text-center"> <p id="start_date-text">開始時間 : </p><p id="start_date"></p> <p id="end_date-text">終了時間 : </p><p id="end_date"></p> </div> </div> <script> //type in your obniz ID var obniz = new Obniz("OBNIZ_ID_HERE"); obniz.onconnect = async function () { // obnizクラウド上での動作かどうかの判定 if (Obniz.App.isCloudRunning()) { obniz.display.print('===== start'); // ソレノイドを0,1に繋ぐ var solenoid = obniz.wired('Solenoid', {gnd:0, signal:1}); solenoid.click(); // マグネットスイッチを9,10,11に繋ぐ var ct10 = obniz.wired("CT10", {gnd:9, vcc:10, signal: 11}); myFunc = async function(){ const start_date = new Date(); $("#start_date").text(start_date); obniz.display.print("start_date"); obniz.display.print(start_date); // マグネットスイッチがONになる(笠が被される)のを待つ(ハッカソンみあふれるコード) await ct10.stateWait(true); const end_date = new Date(); $("#end_date").text(end_date); obniz.display.print("end_date"); obniz.display.print(end_date); const score = end_date - start_date obniz.display.draw(score); } // APIをキックしてすぐだと、誤作動するので若干ディレイをかけて実行 setTimeout(myFunc, 1000); // 実際に動かしたコードではないので雰囲気が伝われば $.ajax({ type: 'POST', // このURLはすでに無効です url: 'https://b3fcdcbe.us-south.apigw.appdomain.cloud/ojizo/record', data: { score: score }, dataType: 'json' }); } } </script> </body> </html>アプリの設定からブラウザ実行にチェックを入れて設定を更新します
デバイス一覧から「Webhook URLの確認」が選択できるようになります
注意点
obnizクラウドではawaitをかけて待機していても、接続が最大30秒までなので今回の要件的には適さないようでした。
待機ができる
以上のことをobnizの木戸さんに相談したところ、 repl.it というサービスで実現できるかもとの情報をいただき、試してみました。
repl.itではNode.jsのホスティングが無料ででき、APIとしても公開できます。コードをnodejs用にすこし修正し(こちら大変たすけていただきました?♂️ありがとうございました?♂️)
const express = require('express'); const Obniz = require('obniz'); const fetch = require("node-fetch") const app = express(); const port = 3000; let solenoid = null; let ct10 = null; let obniz = new Obniz("xxxxxxxx"); obniz.onconnect = function(){ console.log("connected") solenoid = obniz.wired('Solenoid', {gnd:0, signal:1}); ct10 = obniz.wired("CT10", {gnd:9, vcc:10, signal: 11}); } // トップページに来たときに app.get('/', async (req, res) => { //とりあえずレスポンスは先に返す(ブラウザ対策) res.json({"status": "OK"}); if (obniz.connectionState === "connected") { obniz.display.print('===== start'); solenoid.click(); const start_date = new Date(); // $("#start_date").text(start_date); obniz.display.print("start_date"); obniz.display.print(start_date); await ct10.stateWait(true); const end_date = new Date(); obniz.display.print("end_date"); obniz.display.print(end_date); score = end_date - start_date // $.ajaxはnodejsで使えないのでfetchに変換 const data = { user_id: obniz.id, start_date: start_date, end_date: end_date, }; await fetch('https://b3fcdcbe.us-south.apigw.appdomain.cloud/ojizo/record' , { method: "POST", mode: 'cors', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) } }) app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) }); console.log("wakeup")これで10分は待機できるようになりました!
まとめ
・ひさびさのハッカソンはすごく楽しい
・ピップエレキバンの磁力はそんなに強くない
・ハードウェアは極力シンプルな動きにしないとツラくなる(今回で言うと、やっぱどうにかobniz二台構成にしたほうがやりやすかったろうなぁ)
・IBMクラウドのファンクションはわかりやすくて便利だぞ
- 投稿日:2021-03-05T00:33:26+09:00
【TypeScript】型付けしてエラーを片付ける
この記事の目的
TypeScriptを使うとJavaScriptで発生する予期せぬバグが減るっていわれてるけど、具体的にどのようなケースでエラーが減るの?という疑問を具体的な例で解決する。
どんなエラーが片付くか?
ケース1: タイプミスが減りバグが抑制される
TypeScriptは、JavaScriptで起こりうるタイプミスや型付けができないことによって発生するバグを抑制してくれます。
以下のJavaScriptのコードを見てみましょう。
このコードは最後のコンソール出力でnoteBook.color
を出力しようとしていますが、noteBook.colorr
となっているためタイプミスが発生していると考えられます。sample.jsconst noteBook = { color: "黒", price: 980, size: "A4" } console.log(noteBook.colorr); // colorをcolorrにしてあるこちらのJavaScriptのコードを実行してみると、undefinedが出力されます。
特にエラーの文言は出力していないため、この変数を使った処理で予期せぬバグが発生しない限り、このエラーには気付けません。$ node sample.js undefined
そこで、TypeScriptの登場です。
上記のsample.js
をsample.ts
にコピペしてみます。sample.tsconst noteBook = { color: "黒", price: 980, size: "A4" } console.log(noteBook.colorr); // Property 'colorr' does not exist on type '{ color: string; price: number; size: string; }'. Did you mean 'color'?JavaScriptの場合だとタイプミスがあった場合でもundefined が返却されるだけでしたが、TypeScriptの場合はコーディング中に指摘してくれます。
エラーとしては、type '{ color: string; price: number; size: string; }' にはプロパティ 'colorr' が存在しません。colorのことでしょうか?
と出力してくれています。
VsCodeでコーディングした場合はこのように指摘してくれます。(ありがたい...!!)
ケース2: 静的型付けで変数の型を制約させる
JavaScriptの変数は動的型付けとなり、どんな値でも変数に代入することができます。
しかし、複数人でアプリケーションを構築する場合、予期せぬ値を使用されてしまうことがあります。
例えば、「数値型を引数に取りたい関数に文字列を渡してしまう」などです。
TypeScriptでは予め変数の型を定義できるため、他の値が設定されそうになると「この変数(引数)は数値型なので文字列型は代入できませんよ」と伝えてくれる機能があります。まずは以下のJavaScriptのコードを見てみましょう。
sample2.jsfunction sum(a, b) { console.log(a + b); } sum(1, 2);こちらのコードを実行すると以下のように出力されます。
$ node sample2.js 3
当たり前ですね。笑
次に、sum関数の引数に文字列を入れてsample2.js
を実行してみようと思います。sample2.jsfunction sum(a, b) { console.log(a + b); } sum(1, "2"); //2つ目の引数に文字列"2"を代入こちらを実行したら、一般的にはエラーを出力してもらいたいところですがJavaScriptの場合は以下のようにエラーを出力せずに実行できてしまうんです。。
$ node sample2.js 12
このようにsum関数に
文字列"2"
が代入されたことによって、 JavaScriptさんはa + b
を文字列の連結と認識してしまい"1" + "2" = "12"
という処理をしてしまったようです。
これはsum関数を実装したプログラマーにとっては予期せぬ振る舞いですね?こういったエラーですが、TypeScriptの
型定義
で解決できます!!
以下のコードは上記のsampe2.jsのsum関数の引数に対して、型定義
を施してあります。
型定義
の方法は簡単で、引数(変数)のとなりに:型
を書くことで実現できます。
今回は、引数には数値型しか受け付けたくないので、引数の隣に:number
を記述しています。sample2.jsfunction sum(a: number, b: number) { console.log(a + b) } sum(1, "2"); //Argument of type 'string' is not assignable to parameter of type 'number'.お、TypeScriptさんはコード実行前に最後の行に対して、エラーを指摘しているようです。
string 型の引数は number 型のパラメータには代入できません。
と指摘されています。
そのとおりですね。とても親切。まとめ
以上がTypeScriptを使うことによって、エラーが解決される具体的なケースでした。
TypeScriptを使えば、エラーに迅速に気づけますし、バグを含んだコードをcommitするリスクも低減されるので手戻り工数も削減されます。それでは、よいTypeScriptライフを!
もし、よろしければLGTMいただけると幸いです!(ブログを書く励みになります!)