sharpでHEICをJPEGに変換する

概要

sharpでHEICをJPEGに変換する方法を記述します。

準備

今回は、dockerのnodeの公式のイメージを利用します。

$ docker run --rm -it -v $(pwd):/home/node/test-sharp -w /home/node/test-sharp node bash

「-v」でホスト側のカレントディレクトリをコンテナの/home/node/test-sharpにマウントしています。

「-w」でコンテナの作業ディレクトリを/home/node/test-sharpに指定しています。

  • nodejsのバージョンの確認
# node --version
v16.9.0
  • sharpのインストール
# npm install sharp
# npm list
test-sharp@ /home/node/test-sharp
`-- sharp@0.29.1

0.29.1がインストールされています。

# cat <<EOF > index.js
const sharp = require('sharp')

sharp('sample.heic').jpeg().toFile('sample.jpg')
EOF

「sample.heic」はHEIC形式の画像ファイルです。 これをsharpを利用して、JPEG形式に変換して「sample.jpg」として保存します。

# node index.js

node:internal/process/promises:246
          triggerUncaughtException(err, true /* fromPromise */);
          ^

[Error: sample.heic: bad seek to 2727395
heif: Unsupported feature: Unsupported codec (4.3000)
...

sharpが利用しているlibvipsがHEICに対応していないため、エラーになります。

  • sharpが対応しているlibvipsのバージョンを確認
# cat node_modules/sharp/package.json
...
  "config": {
    "libvips": "8.11.3",
    "runtime": "napi",
    "target": 5
  },
...
  • sharpをアンインストール

このままではHEICを変換できないので、一旦、sharpをアンインストールします。

# npm uninstall sharp

変換方法

libvipsをHEICに対応させるため、「libde265」、「x265」、「libheif」、「libvips」をそれぞれソースからコンパイルしてインストールします。

  • 必要となるライブラリのインストール
# apt update
# apt install -y cmake gtk-doc-tools gobject-introspection
# cd /tmp
# git clone https://github.com/strukturag/libde265.git
# cd libde265/
# ./autogen.sh
# ./configure
# make
# make install
# cd /tmp
# git clone https://github.com/videolan/x265.git
# cd x265/build
# cmake ../source
# make
# make install
# cd /tmp
# git clone https://github.com/strukturag/libheif.git
# cd libheif/
# ./autogen.sh
# ./configure
# make
# make install
# cd /tmp
# curl -LO https://github.com/libvips/libvips/releases/download/v8.11.3/vips-8.11.3.tar.gz
# tar -zxf vips-8.11.3.tar.gz
# cd vips-8.11.3
# ./autogen.sh
# ./configure
# make
# make install

インストールされたlibvipsのバージョンを確認しておきます。

# pkg-config --modversion vips-cpp
8.11.3

共有ライブラリのキャッシュを更新します。

# ldconfig
  • sharpをインストール
# cd /home/node/test-sharp
# npm install sharp
  • HEICを変換する
# node index.js
  • 確認
# file sample.heic
sample.heic: ISO Media, HEIF Image HEVC Main or Main Still Picture Profile
# file sample.jpg
sample.jpg: JPEG image data, baseline, precision 8, 3024x4032, components 3

備考

Node.jsでパスからファイル名、親ディレクトリのパスを取得する

概要

パスからファイル名、親ディレクトリのパスを取得します。

取得方法

  • pathモジュールを利用します。
const path = require('path')
  • ファイル名を取得します。
path.basename('/nodejs.org/api/path.html')
// 'path.html'
path.dirname('/nodejs.org/api/path.html')
// '/nodejs.org/api'

備考

AWS S3の署名付きURLを試してみる

概要

AWS S3の署名付きURLを権限を変えながら動作を確認してみます。

準備

$ aws s3 mb s3://test-s3-presigned-url-0904
  • コンテンツのアップロード
$ echo '<!DOCTYPE html><meta charset=utf-8><title>test-s3-presigned-url-0904</title>test-s3-presigned-url-0904' > index.html

$ aws s3 cp index.html s3://test-s3-presigned-url-0904/index.html
  • IAMユーザの作成
$ aws iam create-user --user-name test-s3-user
  • アクセスキーの発行
$ aws iam create-access-key --user-name test-s3-user
  • クレデンシャルの設定

「AccessKeyId」と「SecretAccessKey」を ~/.aws/credentials に以下のように追記

[test-s3-user]
aws_access_key_id = xxxxxxxxxx
aws_secret_access_key = xxxxxxxxxx
region = ap-northeast-1
  • S3バケットを参照するためのIAMロールを作成
$ aws iam create-role \
  --role-name test-s3-viewer-role \
  --assume-role-policy-document file://trust-policy.json

$ aws iam put-role-policy \
  --role-name test-s3-viewer-role \
  --policy-name test-s3-viewer-policy \
  --policy-document file://test-s3-viewer-policy.json

・trust-policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::123456789012:user/test-s3-user"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

・test-s3-viewer-policy.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::test-s3-presigned-url-0904"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::test-s3-presigned-url-0904/*"
            ]
        }
    ]
}
  • IAMロールを利用できるように設定

~/.aws/config に以下を追記

[profile test-s3-viewer]
role_arn = arn:aws:iam::123456789012:role/test-s3-viewer-role
source_profile = test-s3-user
region = ap-northeast-1

動作確認

  • 署名付きURLの発行
$ aws s3 presign s3://test-s3-presigned-url-0904/index.html --profile test-s3-user
  • 署名付きURLでS3のオブジェクトを参照
$ curl ${上記で出力されたURL}

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>BPGTK2FCCCHWA36B</RequestId><HostId>A+TrprqqWadoewmYldeuuv5jwgdt3u/jy92y1KHV0Cdqn08iEtWpfFSExSrCzTJ+T1dkizwWtJw=</HostId></Error>

IAMユーザ「test-s3-user」には、S3バケットに権限を付与していないので、エラーになる。

  • 署名付きURLの発行 (権限あり)
$ aws s3 presign s3://test-s3-presigned-url-0904/index.html --profile test-s3-viewer
  • 署名付きURLでS3のオブジェクトを参照
$ curl ${上記で出力されたURL}

<!DOCTYPE html><meta charset=utf-8><title>test-s3-presigned-url-0904</title>test-s3-presigned-url-0904

S3のバケットに権限があるロールを利用して署名しているため、参照できる。

備考

MySQLのuptimeを確認する

概要

MySQLuptimeを確認する方法を記述します。

確認方法

dockerでMySQLを起動します。

$ docker run --name mysql-test -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:latest
$ docker exec -i -t mysql-test mysql -uroot -p
mysql> status;
--------------
mysql  Ver 8.0.26 for Linux on x86_64 (MySQL Community Server - GPL)

Connection id:      14
Current database:
Current user:       root@localhost
SSL:            Not in use
Current pager:      stdout
Using outfile:      ''
Using delimiter:    ;
Server version:     8.0.26 MySQL Community Server - GPL
Protocol version:   10
Connection:     Localhost via UNIX socket
Server characterset:    utf8mb4
Db     characterset:    utf8mb4
Client characterset:    latin1
Conn.  characterset:    latin1
UNIX socket:        /var/run/mysqld/mysqld.sock
Binary data as:     Hexadecimal
Uptime:         15 min 34 sec

Threads: 2  Questions: 13  Slow queries: 0  Opens: 134  Flush tables: 3  Open tables: 53  Queries per second avg: 0.013
--------------
  • uptimeの確認 (show status構文)
mysql> show global status like 'Uptime';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Uptime        | 1035  |
+---------------+-------+
1 row in set (0.01 sec)

参考

keyvを試してみる

概要

keyvを試してみます。

準備

バックエンドとして利用するRedisなどをDockerコンテナとして起動します。

  • docker-compose.yml
version: '3'
services:
  redis:
    image: "redis:latest"
    ports:
      - "6379:6379"
  mongo:
    image: mongo
    ports:
      - "27017:27017"
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
  mysql:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: db0
    ports:
      - "3306:3306"
  postgres:
    image: postgres
    restart: always
    environment:
      POSTGRES_PASSWORD: example
      POSTGRES_DB: db0
    ports:
      - "5432:5432"
  memcached:
    image: memcached
    ports:
      - "11211:11211"
  • 起動
$ docker compose up -d

インストール

$ npm install keyv
$ npm install @keyv/redis
$ npm install @keyv/mongo
$ npm install @keyv/mysql
$ npm install @keyv/postgres
$ npm install keyv-memcache

Redisを利用する場合

  • 実装
// npm install node-fetch
const fetch = require('node-fetch')
const Keyv = require('keyv')

const posts = new Keyv('redis://localhost:6379', { namespace: 'posts' })

const resPosts = await fetch('https://jsonplaceholder.typicode.com/posts/1')
const post = await resPosts.json()
await posts.set('1', post)

console.log(await posts.get('1'))
// post:  {
//   userId: 1,
//   id: 1,
//   title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
//   body: 'quia et suscipit\n' +
//     'suscipit recusandae consequuntur expedita et cum\n' +
//     'reprehenderit molestiae ut ut quas totam\n' +
//     'nostrum rerum est autem sunt rem eveniet architecto'
// }
  • Redisに保存されたデータ
$ docker compose exec redis redis-cli get 'posts:1'
"{\"value\":{\"userId\":1,\"id\":1,\"title\":\"sunt aut facere repellat provident occaecati excepturi optio reprehenderit\",\"body\":\"quia et suscipit\\nsuscipit recusandae consequuntur expedita et cum\\nreprehenderit molestiae ut ut quas totam\\nnostrum rerum est autem sunt rem eveniet architecto\"},\"expires\":null}"

MongoDBを利用する場合

  • 実装
// npm install node-fetch
const fetch = require('node-fetch')
const Keyv = require('keyv')

const albums = new Keyv('mongodb://root:example@127.0.0.1:27017/db0?authSource=admin', { namespace: 'db0', collection: 'albums' })

const resAlbums = await fetch('https://jsonplaceholder.typicode.com/albums/1')
const album = await resAlbums.json()
await albums.set('1', album)

console.log('album: ', await albums.get('1'))
// album:  { userId: 1, id: 1, title: 'quidem molestiae enim' }
  • MongoDBに保存されたデータ
$ docker compose exec mongo mongosh -u root --authenticationDatabase admin --eval 'db.albums.find()' 'mongodb://localhost:27017/db0'
[
  {
    _id: ObjectId("611d24ddecd3f242f44cec93"),
    key: 'db0:1',
    value: '{"value":{"userId":1,"id":1,"title":"quidem molestiae enim"},"expires":null}',
    expiresAt: null
  }
]

MySQLを利用する場合

  • 実装
// npm install node-fetch
const fetch = require('node-fetch')
const Keyv = require('keyv')

const photos = new Keyv('mysql://root:example@localhost:3306/db0', { table: 'photos' })

const resPhotos = await fetch('https://jsonplaceholder.typicode.com/photos/1')
const photo = await resPhotos.json()
await photos.set('1', photo)

console.log('photo: ', await photos.get('1'))
// photo:  {
//   albumId: 1,
//   id: 1,
//   title: 'accusamus beatae ad facilis cum similique qui sunt',
//   url: 'https://via.placeholder.com/600/92c952',
//   thumbnailUrl: 'https://via.placeholder.com/150/92c952'
// }
  • MySQLに保存されたデータ
$ docker compose exec mysql mysql -u root -p db0 -e 'select * from photos\G'
*************************** 1. row ***************************
  key: keyv:1
value: {"value":{"albumId":1,"id":1,"title":"accusamus beatae ad facilis cum similique qui sunt","url":"https://via.placeholder.com/600/92c952","thumbnailUrl":"https://via.placeholder.com/150/92c952"},"expires":null}

PostgreSQLを利用する場合

  • 実装
// npm install node-fetch
const fetch = require('node-fetch')
const Keyv = require('keyv')

const todos = new Keyv('postgres://postgres:example@localhost:5432/db0', { table: 'todos' })

const resTodos = await fetch('https://jsonplaceholder.typicode.com/todos/1')
const todo = await resTodos.json()
await todos.set('1', todo)

console.log('todo: ', await todos.get('1'))
// todo:  { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
$ docker compose exec postgres psql -U postgres -W -d db0 -c 'select * from todos;'
  key   |                                            value
--------+---------------------------------------------------------------------------------------------
 keyv:1 | {"value":{"userId":1,"id":1,"title":"delectus aut autem","completed":false},"expires":null}
(1 row)

Memcacheを利用する場合

  • 実装
// npm install node-fetch
const fetch = require('node-fetch')
const Keyv = require('keyv')
const KeyvMemcache = require('keyv-memcache')

const users = new Keyv({ store: new KeyvMemcache('localhost:11211'), namespace: 'users' })

const resUsers = await fetch('https://jsonplaceholder.typicode.com/users/1')
const user = await resUsers.json()
await users.set('1', user)

console.log('user: ', await users.get('1'))
// user:  {
//   id: 1,
//   name: 'Leanne Graham',
//   username: 'Bret',
//   email: 'Sincere@april.biz',
//   address: {
//     street: 'Kulas Light',
//     suite: 'Apt. 556',
//     city: 'Gwenborough',
//     zipcode: '92998-3874',
//     geo: { lat: '-37.3159', lng: '81.1496' }
//   },
//   phone: '1-770-736-8031 x56442',
//   website: 'hildegard.org',
//   company: {
//     name: 'Romaguera-Crona',
//     catchPhrase: 'Multi-layered client-server neural-net',
//     bs: 'harness real-time e-markets'
//   }
// }
  • Memcacheに保存されたデータ
$ telnet 127.0.0.1 11211
stats items
STAT items:9:number 1
STAT items:9:number_hot 0
STAT items:9:number_warm 0
STAT items:9:number_cold 1
STAT items:9:age_hot 0
STAT items:9:age_warm 0
STAT items:9:age 2524
STAT items:9:mem_requested 498
STAT items:9:evicted 0
STAT items:9:evicted_nonzero 0
STAT items:9:evicted_time 0
STAT items:9:outofmemory 0
STAT items:9:tailrepairs 0
STAT items:9:reclaimed 0
STAT items:9:expired_unfetched 0
STAT items:9:evicted_unfetched 0
STAT items:9:evicted_active 0
STAT items:9:crawler_reclaimed 0
STAT items:9:crawler_items_checked 8
STAT items:9:lrutail_reflocked 0
STAT items:9:moves_to_cold 1
STAT items:9:moves_to_warm 0
STAT items:9:moves_within_lru 0
STAT items:9:direct_reclaims 0
STAT items:9:hits_to_hot 1
STAT items:9:hits_to_warm 0
STAT items:9:hits_to_cold 0
STAT items:9:hits_to_temp 0
END

stats cachedump 9 10
ITEM users:users:1 [426 b; 0 s]

参考

Day.jsを試してみる

概要

Day.js を試してみます。

インストール

$ npm install dayjs

使い方

  • 基本
const dayjs = require('dayjs')

const day = dayjs('2021-08-17 15:00:00')
dayjs().format('YYYY-MM-DD HH:mm:ss')
dayjs().set('month', 5).month()
dayjs().add('year', 3)
dayjs().isBefore(dayjs().add(1, 'day'))
  • 現在日時の取得
const current = dayjs()
  • フォーマット
dayjs().format('YYYY-MM-DD')
// 2021-08-17

※ 利用できるフォーマットの一覧は「 List of all available parsing tokens」を参照

  • Dateオブジェクトから
dayjs(new Date(2021, 8, 17))
  • バリデーション
dayjs().isValid()
// true
dayjs(null).isValid()
// false
  • 年月日の取得
dayjs().format('YYYY-MM-DD')
// 2021-08-17

dayjs().year()
// 2021
dayjs().month()
// 7 (0-11)
dayjs().date()
// 17
  • 時分秒の取得
dayjs().format('YYYY-MM-DD HH:mm:ss')
// 2021-08-17 18:28:03

dayjs().hour()
// 18
dayjs().minute()
// 28
dayjs().second()
// 3
  • 日時の操作(加算 / 減算)
const current = dayjs()
current.format('YYYY-MM-DD')
// 2021-08-17

current.add(3, 'year').year()
// 2024
current.add(2, 'month').month()
// 9
current.add(3, 'day').date()
// 20

current.subtract(1, 'year').year()
// 2020
current.subtract(3, 'month').month()
// 4
current.subtract(7, 'day').date()
// 10
current.add(-7, 'day').date()
// 10

※ 指定できる単位の一覧は「List of all available units」を参照

  • 月初 / 月末、一日の開始 / 終了の取得
const current = dayjs()
current.format('YYYY-MM-DD')
// 2021-08-17

current.startOf('month').format('YYYY-MM-DD')
// 2021-08-01
current.endOf('month').format('YYYY-MM-DD')
// 2021-08-31

current.startOf('day').format('YYYY-MM-DD HH:mm:ss')
// 2021-08-17 00:00:00
current.endOf('day').format('YYYY-MM-DD HH:mm:ss')
// 2021-08-17 23:59:59
  • 差分の取得
const d1 = dayjs('2021-08-17')
const d2 = dayjs('2020-08-17')

d1.diff(d2, 'year')
// 1
d1.diff(d2, 'month')
// 12
d1.diff(d2, 'day')
// 365
  • 日付の比較
// const d1 = dayjs('2021-08-17')
const d3 = dayjs('2021-08-18')

d1.isBefore(d3)
// true

d1.isBefore(d3, 'day')
// true
d1.isBefore(d3, 'month')
// false
d1.isBefore(d3, 'year')
// false

d1.isAfter(d3)
// false

プラグインの利用

const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)

dayjs().format('YYYY-MM-DD HH:mm:ss')
// 2021-08-17 19:00:00 (ローカルタイム)

dayjs.utc().format('YYYY-MM-DD HH:mm:ss')
// 2021-08-17 10:00:00 (UTC)

dayjs().utc().format('YYYY-MM-DD HH:mm:ss')
// 2021-08-17 10:00:00 (UTCに変換)

dayjs.utc().local().format('YYYY-MM-DD HH:mm:ss')
// 2021-08-17 19:00:00 (ローカルタイムに変換)

dayjs().isUTC()
// false
dayjs().utc().isUTC()
// true
const isLeapYear = require('dayjs/plugin/isLeapYear')
dayjs.extend(isLeapYear)

dayjs('2021-08-17').isLeapYear()
// false
dayjs('2000-08-17').isLeapYear()
// true

備考

Node.jsでGoogle Drive APIを利用する

概要

Node.jsでGoogle Drive APIを利用する方法を記述します。

事前準備

  • Google Cloud Platformでプロジェクトを作成する
  • Google Drive APIを利用できるように設定する
  • サービスアカウントを作成して、認証情報をjson形式でダウンロードする

実装

  • ライブラリのインストール
$ npm install googleapis
  • 認証
const { google } = require('googleapis')

const auth = new google.auth.GoogleAuth({
  keyFile: './credentials.json', // 事前準備でダウンロードしたjson形式の認証情報ファイル
  scopes: [
    'https://www.googleapis.com/auth/drive.readonly',
    'https://www.googleapis.com/auth/drive.file'
  ]
})

const drive = google.drive({
  version: 'v3',
  auth
})
  • ファイルの一覧を取得
const res = await drive.files.list({
  pageSize: 100,
  fields: 'nextPageToken, files(id, name)'
})
const files = res.data.files
if (files.length) {
  console.log('Files: ')
  files.map((file) => {
    console.log(`${file.name} (${file.id})`)
  }
}
  • ファイルのアップロード
const path = require('path')
const fs = require('fs')

//npm install file-type
const fileType = require('file-type')

const ft = await fileType.fromFile('path/to/file')
const res = await drive.files.create({
  requestBody: {
    name: path.basename('path/to/file'),
    mimeType: ft.mime
  },
  media: {
    mimeType: ft.mime,
    body: fs.createReadStream('path/to/file')
  }
})
const data = res.data
console.log('uploadFile: ', { data })
// uploadFile:  {
//   data: {
//     kind: 'drive#file',
//     id: 'xxxxxxxxxx',
//     name: 'xxxxx',
//     mimeType: 'xxxxx/xxx'
//  }
// }
  • ファイルの共有
const res = await drive.permissions.create({
  resource: {
    type: 'user',
    role: 'writer',
    emailAddress: 'xxx@xxxxx'
  },
  fileId: 'file id',
  fields: 'id'
})
const data = res.data
console.log('shareFile: ', { data })
// shareFile:  {
//   data: {
//     id: 'xxxxx'
//   }
// }
const res = await drive.files.create({
  resource: {
    name: 'directory name',
    mimeType: 'application/vnd.google-apps.folder'
  },
  filed: 'id'
})
const data = res.data
console.log('createDirecotry: ', { data })
// createDirectory:  {
//   data: {
//     kind: 'drive#file',
//     id: 'xxxxxxxxxx',
//     name: 'directory name',
//     mimeType: 'application/vnd.google-apps.folder'
//   }
// }
const ft = await fileType.fromFile('path/to/file')
const res = await drive.files.create({
  resource: {
    name: path.basename('path/to/file'),
    parents: ['directory id']
  },
  media: {
    mimeType: ft.mime,
    body: fs.createReadStream('path/to/file')
  },
  fields: 'id'
})
const data = res.data
console.log('uploadFileInDirectory: ', { data })
// uploadFileInDirectory:  {
//   data: {
//     id: 'xxxxxxxxxx'
//   }
// }
  • ファイルの削除
const res = await drive.files.delete({
  fileId: 'file id'
})
const status = res.status
console.log('deleteFile: ', { status })
// deleteFile:  { status: 204 }

備考