🙄

【Next.js】iPhoneで撮影した写真の拡張子、HEIFとHEICをJPGに変換する方法

2023/11/29に公開3

はじめに

初めまして、現在エンジニアをしながら起業をしてサービスを開発中の橋田至です。

https://twitter.com/dall_develop

私は今Swappyという同人誌のフリマサイトを開発中です。
同人誌を売買するという観点から、ネイティブアプリでストアで公開するのは不可能だろうという考えから、ブラウザアプリとして開発を進めています。

iPhoneから撮影した写真は特有の拡張子になってしまうバグ

フリマサイトを作るとなると、モバイルファーストにすべきだと感じました。
そこで、iPhoneから撮影した写真をそのままwebにアップロードしてもらえた方がいいと感じます。

ファイルアップロードはCloudinaryというSaaSを使用しているのですが、ファイルアップロード時にiPhone特有の拡張子になってしまうことが原因でファイルアップロード時にエラーが発生していました。

https://cloudinary.com/

HEIFとHEICとは?

要するにスマホ用の画像の拡張子です。こいつが対応してないライブラリやブラウザがあったりして非常に辛いです😭

今回は画像のupload時にheicかheif立った場合、jpgに変換する処理を作成しました。

https://qiita.com/shiz/items/80413757817974bcc41a

変換ライブラリがRSCだと動かない

heic2anyというライブラリを使用したのですが、これはクライアントサイドじゃないと動きませんでした。
そこで、"use client"を使用している部分に使うようにコードを修正し、なんとか動くようになりました。

const processedFiles = await Promise.all(
      droppedFiles.map(async (file) => {
        const ext = file?.name.split(".").pop()?.toLowerCase();
        if (ext === "heic" || ext === "heif") {
          try {
            if (typeof window !== "undefined") {
              // import heic2any from "heic2any";
              // eslint-disable-next-line @typescript-eslint/no-var-requires
              const heic2any = require("heic2any");
              const output = await heic2any({
                blob: file,
                toType: "image/jpeg",
                quality: 0.7,
              });
              // Check if the output is a single Blob or an array of Blobs
              const outputBlob = Array.isArray(output) ? output[0] : output; // Assuming we use the first Blob if it's an array

              // Create a new File object from the Blob
              const newName = file.name.replace(/\.(heic|heif)$/i, "") + ".jpg";
              return new File([outputBlob], newName, {
                type: "image/jpeg",
              });
            }
          } catch (error) {
            console.error("Error converting HEIC/HEIF file:", error);
            return file; // Return the original file in case of an error
          }
        } else {
          return addGrayBackground(file);
        }
      }),
    );

https://alexcorvi.github.io/heic2any/

window is not defined

普通にライブラリをimportすると、上記のエラーが発生しました。
これに対処するために、requireを使い、if文の中で使用するとこでエラーを回避しています。

とはいえこのコードでいいのか??

このライブラリも全然バージョンがないので、更新はあまりされていないように見えます。
さらに冗長なコードもあると思われるので、より適切にNext.js(App Router)でHEIC,HEIF対応しているという方がいましたら教えていただけると幸いです。

issueなどを探すと他の方も結構困っているように見えました。

最後に

その他にも「こうすれば良いよ!」などの設定などありましたらお声かけいただけると嬉しいです!

また、Swappyの開発者は随時募集しておりますので、気軽にDMください!!

https://twitter.com/dall_develop

https://ring-brand-8a1.notion.site/5948c0f2bd5e44548fd6e1f814b137d0?pvs=4

参考

https://stackoverflow.com/questions/74842883/how-to-use-heic2any-in-next-js-client-side

https://qiita.com/ikea_shark_jk/items/a78d966f8157b24e57f8

https://zenn.dev/seya/articles/5faa498604a63e

https://stackoverflow.com/questions/74856178/how-to-convert-heic-to-jpg-in-react

https://github.com/vercel/next.js/discussions/30043

Discussion

fj68fj68

はじめまして。
libheif-jsのページを見ていたらこんなライブラリが紹介されていました。
私自身は使ったことがないので何とも言えませんが、NodeでもBrowserでも動くようです。
要件に合っているのかわかりませんが、ご参考まで。
https://github.com/catdad-experiments/heic-convert

橋田至橋田至

ありがとうございます!
こちらも使用してみたのですが、内部でfsが使われているようでこちらを使用した場合もエラーが発生したんですよね、、、

ただうまくやればこちらも使用できるのかなと思いますが僕は正しく動かすことができませんでした、、、

fj68fj68

内部でfsが使われているようでこちらを使用した場合もエラーが発生したんですよね、、、

環境やエラー内容が不明なので何とも言えませんが、考えられる可能性としては

  1. クライアントサイドのコードでrequire('heic-convert')している(クライアント側で使用したい場合に試すべきはrequire('heic-convert/browser')の方)
  2. サーバサイドのコードでrequire('heic-convert')require('fs')するとエラーが出る場合はheic-convertではなく環境設定の問題

かな、と思います。

このライブラリは一応「Nodeでもブラウザでも動くと思う」とREADMEにあったので、heic-convertと依存先のheic-decodeのソースをざっと眺めてみましたが、たしかにheic-convert/browserの方ではfsを始めとしてNode依存のモジュールは使われていないようでした(その先の依存関係で使われている可能性はありますが)。

2のケースの場合はwebpackの設定でクライアントのバンドルからNode依存のライブラリを除外していない可能性が考えられるかと思います。

https://stackoverflow.com/questions/44983810/how-do-i-use-a-node-js-module-with-next-js

これ以上は私も知見がなくお力になれそうにありませんが、無事解決できることを祈っております。