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
- libde265のコンパイル・インストール
# cd /tmp # git clone https://github.com/strukturag/libde265.git # cd libde265/ # ./autogen.sh # ./configure # make # make install
- x265のコンパイル・インストール
# cd /tmp # git clone https://github.com/videolan/x265.git # cd x265/build # cmake ../source # make # make install
- libheifのコンパイル・インストール
# cd /tmp # git clone https://github.com/strukturag/libheif.git # cd libheif/ # ./autogen.sh # ./configure # make # make install
- libvipsのコンパイル・インストール
# 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でパスからファイル名、親ディレクトリのパスを取得する
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を確認する
概要
確認方法
- MySQLの起動
dockerでMySQLを起動します。
$ docker run --name mysql-test -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:latest
- MySQLへ接続
$ docker exec -i -t mysql-test mysql -uroot -p
- uptimeの確認
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 }
- PostgreSQLに保存されたデータ
$ 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 }