たくさんの自由帳

S3 にある写真に後から Content-Type を付与したい

投稿日 : | 0 日前

文字数(だいたい) : 2722

どうもこんばんわ。

AWSってことでAmazonのはなしでも、
手持ちのイヤホンの電池が本当に持たなくなってしまったので、買ってみた、Echo Buds
セールで半額!、この値段でノイズキャンセリングいいじゃんって。

数日しか経ってないけど感想!

  • フィット感はちょっと落ちそう感、いや、落ちそう感はあるけど落ちたこと無い
    • スポーツ用のあれをつけるべき?
  • ノイキャンは値段考えたらすごいほうだと思う、流石にハイエンドと比べるのは
  • 片耳外すと音楽が止まってしまう?
  • 一回タップでノイズキャンセリング切り替え出来ると嬉しい!!
    • 長押しの反応が鈍い!!

本題

S3の画像をCloudFrontで配信しているわけですが、
ブラウザの画像を新しいタブで開くを押すと何故かダウンロードウィンドウになってしまう。

ダイアログ

画像を開いてくれるんじゃないの。。。?
デカく見たいよね?

原因

どうやらContent-Typeのデフォルトがバイナリデータとして扱われ(まあそうか)、
バイナリの場合ブラウザはダウンロードのダイアログを出すだけをするらしい。

たとえ中身が画像だとしても、そのたとえすらもしない。出すだけ。

S3 は Content-Type つけてくれないの?

いいえ、ブラウザからアップロードする場合や、CLIの場合はついてそう。

CLI

一方、CDKを使う場合は明示的にContent-Typeを指定しないと、バイナリデータのくくりになってしまうそうです。

AltText

全部に Content-Type をつけていく

というわけで、今回は既にS3にある写真に対して、Content-Typeを付与していこうというという記事です。
metadata directive replaceとかで検索すればいっぱい出てくると思います。

環境

Kotlin/JVMで書こうって思ったんだけど、直近までNext.js書いてたしで、
この程度ならesbuildTypeScriptをトランスパイルしてNode.jsで実行でいいか、て。

なまえあたい
言語TypeScript
Node.jsv20.9.0

AWS アクセスキーを取得

Node.jsから読者さんのAWS S3を操作できるように、APIにつけるアクセスキーを払い出してもらいます。
省略しますが、IAM ユーザー作成→S3 の権限付与アクセスキーを発行の流れになると思います。

アクセスキー、シークレットアクセスキーが入手できたら完了です。
(ぜったい秘密に!!)

TypeScript プロジェクトを揃えていく

多分typescriptも入れないといけない気がするが、もうそこまでしたくないので、、、
適当にフォルダを作って、esbuildと、CDKを入れます。

npm init -y
npm install --save-exact --save-dev esbuild
npm i @aws-sdk/client-s3

検索の上の方ででてくるaws-sdkV2で、これはメンテナンスモードなので、
ちゃんとV3の方を入れましょう。

できたら、package.jsonesbuildのトランスパイル+Node.jsで実行、をやってくれるコマンドを書きます。
"start": の一行ですね。これでesbuildTSJSに変換してくれて、出来たのをNode.jsで実行。

{
  "name": "aws-s3-metadata-edit",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "esbuild index.ts --bundle --outfile=out.js && node out.js"
  },

index.ts

全部張ります。

BUCKET_NAMEには、メタデータを変更したいファイルが入っているS3 Bucketの名前、
ACCESS_KEY_IDにはアクセスキーの値を、
SECRET_ACCESS_KEYにはシークレットアクセスキーの値を入れてください。
リージョンも東京以外を選んでいる場合は直してください。

やっていることは、S3 Bucketの中身一覧を取得し、
forで回し各Keyを得ます。あとはCopyObjectCommandで上書きしていく感じです。MetadataDirectiveですね。

Content-Type、今回は拡張子から取得していますが、拡張子すら無くなってしまった場合は、
バイナリデータからMIME-Typeを推測する何かが必要になりそう。マジックナンバーってやつかな。

import {
    S3Client,
    ListObjectsV2Command,
    CopyObjectCommand
} from "@aws-sdk/client-s3"


// ここは各自変更してください
const BUCKET_NAME = ""
const ACCESS_KEY_ID = ""
const SECRET_ACCESS_KEY = ""
// ここまで

async function main() {

    // 認証
    const s3Client = new S3Client({
        region: 'ap-northeast-1',
        credentials: {
            accessKeyId: ACCESS_KEY_ID,
            secretAccessKey: SECRET_ACCESS_KEY
        }
    })

    // 取得
    const response = await s3Client.send(
        new ListObjectsV2Command({ Bucket: BUCKET_NAME })
    )

    // 成功してれば
    if (response.Contents) {
        for (const obj of response.Contents) {

            // 今回は拡張子を入れているので、それから Content-Type を探す
            let mimeType = 'application/octet-stream'
            switch (obj.Key?.split('.')[1]) {
                case "jpg":
                    mimeType = 'image/jpeg'
                    break
                case "png":
                    mimeType = 'image/png'
                    break
            }

            // 実行
            await s3Client.send(
                new CopyObjectCommand({
                    Bucket: BUCKET_NAME,
                    CopySource: `${BUCKET_NAME}/${obj.Key}`,
                    Key: obj.Key,
                    ContentType: mimeType, // これ!!
                    MetadataDirective: 'REPLACE'
                })
            )

            console.log(`完了 ${obj.Key}`)
        }
    }
}

main() // エントリーポイントを呼び出す

変数埋めたら、以下のコマンドを叩く。 これで、各ファイルに対して上書きでメタデータが付与されているはずです。

たーみなる

完成

できた!

画像です

そーすこーど

どーぞー

おわりに

今回のコード、上書きする順番はバラバラなので、一覧表示とかしていると壊れるかも。