KDP(電子出版)のメモ 急急如律令

Amazon Kindleダイレクト・パブリッシングでの電子出版や電子書籍の作成販売について、文章やイラストの作成や編集方法について書いています。

画像の情報を読み取るためにJimpやsharpを使い

画像の情報を読み取るためにJimpやsharpを使いasync awaitを入れると処理の順番が変わる。 awaitを入れないと画像情報はpromiseのママになる。

AozoraEpub3.jsからepubWriter.write、epub3Writer = new Epub3Writer(${jarPath}template/); Epub3Writer,writeはasync

src\writer\Epub3Writer.js でthis.imageInfosがからの配列だったのでthis.imageInfosを追加する部分を見ていく。 async getImageFilePath(srcImageFileName, lineNum, bookInfo)

src\converter\AozoraEpub3Converter.jsの該当部分ではPromise { }なのでawaitを入れる必要がある。 convertTextLineToEpub3内の const dstFileName = this.writer.getImageFilePath(srcFilePath, lineNum, this.bookInfo);

convertTextLineToEpub3にasyncを入れると

../image/0001.jpg file:///C:/Users/Owner/Documents/GitHub/AozoraEpub3JS/src/writer/Epub3Writer.js:1311 return this.imageInfoReader.getImageAlt(srcFilePath); ^

TypeError: Cannot read properties of null (reading 'getImageAlt') at Epub3Writer.getAlt (file:///C:/Users/Owner/Documents/GitHub/AozoraEpub3JS/src/writer/Epub3Writer.js:1311:37) at AozoraEpub3Converter.convertTextLineToEpub3 (file:///C:/Users/Owner/Documents/GitHub/AozoraEpub3JS/src/converter/AozoraEpub3Converter.js:2413:49)

You:

TypeError: imageInfo.getOutFileName is not a function at Epub3Writer.write (file:///C:/Users/Owner/Documents/GitHub/AozoraEpub3JS/src/writer/Epub3Writer.js:807:105) at async convertFile (file:///C:/Users/Owner/Documents/GitHub/AozoraEpub3JS/src/AozoraEpub3.js:543:5) at async main (file:///C:/Users/Owner/Documents/GitHub/AozoraEpub3JS/src/AozoraEpub3.js:471:7) エラーが発生しました : imageInfo.getOutFileName is not a function import fs from "fs"; import sharp from "sharp"; export default class ImageInfo { /** 画像の情報を生成 * @param {string} ext png jpg gif webp など */ constructor(ext, width, height) { this.id = null; // ファイルID this.outFileName = null; // 出力ファイル名 this.ext = ext ? ext.toLowerCase() : null; this.width = width ?? -1; this.height = height ?? -1; this.outWidth = -1; this.outHeight = -1; this.isCover = false; this.rotateAngle = 0; } // ========================= // static 生成メソッド // ========================= /** ファイルから画像情報を生成 */ static async getImageInfoFromFile(imageFile) { const buffer = await fs.promises.readFile(imageFile); return await ImageInfo.getImageInfo(buffer); } /** 画像ストリーム / Buffer から画像情報を生成 */ static async getImageInfo(input) { if (!input) return null; try { const metadata = await sharp(input).metadata(); if (!metadata || !metadata.format) return null; return new ImageInfo( metadata.format, // png jpeg webp gif avif など metadata.width, metadata.height ); } catch (e) { return null; } } /** 既に幅高さが分かっている場合 */ static getImageInfoFromRaw(ext, width, height) { return new ImageInfo(ext, width, height); } // ========================= // getter / setter // ========================= getId() { return this.id; } setId(id) { this.id = id; } getOutFileName() { return this.outFileName; } setOutFileName(file) { this.outFileName = file; } setExt(ext) { this.ext = ext?.toLowerCase(); } getExt() { return this.ext; } /** mime形式(image/png)を返す */ getFormat() { if (!this.ext) return null; return "image/" + (this.ext === "jpg" ? "jpeg" : this.ext); } getIsCover() { return this.isCover; } setIsCover(isCover) { this.isCover = isCover; } getWidth() { return this.width; } setWidth(width) { this.width = width; } getHeight() { return this.height; } setHeight(height) { this.height = height; } getOutWidth() { return this.outWidth; } setOutWidth(outWidth) { this.outWidth = outWidth; } getOutHeight() { return this.outHeight; } setOutHeight(outHeight) { this.outHeight = outHeight; } }

ChatGPT:

このエラー、原因はかなり典型的です👇

TypeError: imageInfo.getOutFileName is not a function

つまり imageInfoImageInfo インスタンスじゃない です。


🔍 よくある原因(今回かなりこれっぽい)

あなたの ImageInfo クラス自体は問題ありません。
getOutFileName() もちゃんと定義されています。

なので問題はここ👇

imageInfo に入っているものが違う

例えばこんな状態になっている可能性があります:

JavaScript

const imageInfo \= await ImageInfo.getImageInfo(...);

一見OKですが、実際には👇

パターン1:null が入ってる

JavaScript

if (!metadata || !metadata.format) return null;

→ つまり imageInfo === null

でもこの場合は普通

Cannot read properties of null

になるので可能性は低め。


パターン2(本命):プレーンオブジェクト化してる

どこかでこうなってませんか?

JavaScript

imageInfo \= { ...imageInfo };

または

JavaScript

JSON.parse(JSON.stringify(imageInfo))

これをやると👇

👉 クラスのメソッドが全部消える

結果:

JavaScript

imageInfo.getOutFileName // undefined

パターン3:配列や別型が混ざってる

JavaScript

imageInfos.push(someObject); // ← ImageInfoじゃない

✅ 確認してほしい(最短デバッグ)

エラーが出る直前にこれ入れてください👇

JavaScript

console.log(imageInfo);  
console.log(typeof imageInfo);  
console.log(imageInfo instanceof ImageInfo);  
console.log(imageInfo?.constructor?.name);

🎯 期待される結果

JavaScript

true  
"ImageInfo"

❌ もしこうなったら

ケース1

JavaScript

false  
"Object"

👉 spread / JSON 化してる


ケース2

JavaScript

false  
undefined

👉 null / undefined


💡 修正方法

✔ 方法1:クラスを壊さない

NG:

JavaScript

const copy \= { ...imageInfo };

OK:

JavaScript

const copy \= imageInfo; // 参照でOK

✔ 方法2:復元する

もしJSON経由してるなら👇

JavaScript

const raw \= JSON.parse(...);  
const imageInfo \= Object.assign(new ImageInfo(), raw);

✔ 方法3:生成元を統一

必ずここから作る:

JavaScript

await ImageInfo.getImageInfo(...)

🧠 補足(今回かなり重要)

あなたのプロジェクト(AozoraEpub3移植)だと👇

  • Java → JS移植

  • DTOっぽく扱ってる箇所あり

  • 非同期+構造変換あり

👉 どこかで「ただのデータ」に変換されてる可能性が高い


🚀 もしよければ

Epub3Writer.js の 807行目付近

貼ってくれれば
どこで壊れてるかピンポイントで特定できます 👍

You:

[ ImageInfo { id: '0001', outFileName: '0001.jpg', ext: 'jpeg', width: 1512, height: 2150, outWidth: -1, outHeight: -1, isCover: false, rotateAngle: 0 }, ImageInfo { id: '0002', outFileName: '0002.webp', ext: 'webp', width: 550, height: 404, outWidth: -1, outHeight: -1, isCover: false, rotateAngle: 0 }, ImageInfo { id: '0003', outFileName: '0003.jpg', ext: 'jpeg', width: 640, height: 400, outWidth: -1, outHeight: -1, isCover: false, rotateAngle: 0 } ]

ChatGPT:

このログ、かなり重要で👇

JavaScript

\[  
  ImageInfo { ... },  
  ImageInfo { ... },  
  ImageInfo { ... }  
\]

👉 中身は完全に正しい(ちゃんと ImageInfo インスタンス)

なので結論はこれです👇


🎯 原因

👉 imageInfo が「配列」になってる

エラーはこれ:

imageInfo.getOutFileName is not a function

でも実際の imageInfo は👇

JavaScript

imageInfo \= \[ ImageInfo, ImageInfo, ImageInfo \]

つまり

JavaScript

imageInfo.getOutFileName()

を呼んでるけど

👉 配列にはそんなメソッドない


🔥 典型的なやらかしパターン

たぶんどこかでこうなってる👇

JavaScript

const imageInfo \= imageInfos; // ← 配列をそのまま代入

または

JavaScript

for (...) {  
  imageInfo \= imageInfos; // ← 上書きミス  
}

✅ 正しい使い方

✔ パターン1:ループする

JavaScript

for (const imageInfo of imageInfos) {  
  console.log(imageInfo.getOutFileName());  
}

✔ パターン2:1つだけ取りたい

JavaScript

const imageInfo \= imageInfos\[0\];  
imageInfo.getOutFileName();

✔ パターン3:map

JavaScript

const names \= imageInfos.map(i => i.getOutFileName());

🧠 今回の本質

  • クラスは壊れてない ✅

  • メソッドも存在する ✅

  • 変数の中身が違うだけ ❌


🔍 追加で確認すると一発

エラー直前で👇

JavaScript

console.log(Array.isArray(imageInfo));

👉 true が出るはず


🚀 ほぼ確定ポイント

エラー箇所:

JavaScript

Epub3Writer.js:807

この行たぶん👇こういうコード

JavaScript

imageInfo.getOutFileName()

👉 ここを

JavaScript

imageInfos\[i\].getOutFileName()

とかに直す必要あり


👍 まとめ

今回のバグは珍しくなくて:

👉 「単体」と「配列」を取り違えた」だけ


もしよければ
Epub3Writer.js 800〜820行 貼ってくれれば
どの変数がズレてるかピンポイント修正出せます 👍

You:

Promise { ImageInfo { id: '0001', outFileName: '0001.jpg', ext: 'jpeg', width: 1512, height: 2150, outWidth: -1, outHeight: -1, isCover: false, rotateAngle: 0 } } object false Promise

ChatGPT:

いいログです、これで完全に特定できます 👍


🎯 原因(確定)

JavaScript

Promise {  
  ImageInfo { ... }  
}

👉 imageInfo は ImageInfo ではなく Promise

だから:

JavaScript

imageInfo.getOutFileName()

👉 ❌ 存在しない(Promiseにはない)


🔥 なぜこうなるか

あなたのコード👇

JavaScript

static async getImageInfo(input)

👉 async = 必ず Promise を返す


つまり👇

JavaScript

const imageInfo \= ImageInfo.getImageInfo(...)

は実際には

JavaScript

const imageInfo \= Promise<ImageInfo\>

✅ 解決方法

✔ 正解:await する

JavaScript

const imageInfo \= await ImageInfo.getImageInfo(input);

❌ NGパターン(今これ)

JavaScript

const imageInfo \= ImageInfo.getImageInfo(input);  
  
imageInfo.getOutFileName(); // ← 💥

🧠 あなたのログの意味

JavaScript

typeof imageInfo           // object(Promiseもobject)  
imageInfo instanceof ImageInfo // false  
imageInfo.constructor.name // "Promise"

👉 完全に Promise


🚀 修正ポイント

Epub3Writer 側

たぶんこうなってる👇

JavaScript

const imageInfo \= ImageInfo.getImageInfo(...);

👉 修正:

JavaScript

const imageInfo \= await ImageInfo.getImageInfo(...);

⚠️ 重要(ここハマりやすい)

呼び出し元も async 必須

JavaScript

async write(...) {  
  const imageInfo \= await ImageInfo.getImageInfo(...);  
}

❗ もし async にできない場合

JavaScript

ImageInfo.getImageInfo(...).then(imageInfo => {  
  imageInfo.getOutFileName();  
});

💡 今回のエラーの変遷まとめ

  1. 最初のエラー
    👉 「関数がない」

  2. 実態
    👉 Promiseに対して呼んでる

  3. 根本原因
    👉 await忘れ


🎯 一発チェック

これを見ればOK👇

JavaScript

console.log(imageInfo.constructor.name);
  • "ImageInfo" → OK

  • "Promise" → ❌ await忘れ


👍 まとめ

👉 今回の本質はこれ

「async関数の戻り値をそのまま使っている」


必要なら
「AozoraEpub3全体で async汚染を最小にする設計」も整理できるので言ってください 👍

You:

Formatの値がない getFormat() { if (!this.ext) return null; return "image/" + (this.ext === "jpg" ? "jpeg" : this.ext); }

ChatGPT:

いいところに気づいてます 👍
この問題はシンプルで、でもハマりやすいやつです。


🎯 結論

👉 this.ext"jpg" じゃなくて "jpeg" になってる


🔍 あなたの現在の実装

JavaScript

getFormat() {  
  if (!this.ext) return null;  
  return "image/" + (this.ext \=== "jpg" ? "jpeg" : this.ext);  
}

❗ 問題点

sharp().metadata()format は👇

JavaScript

"jpeg"

つまり:

JavaScript

this.ext \=== "jpg"  // false

結果👇

JavaScript

"image/" + "jpeg" \= "image/jpeg"

👉 一見OK


🤔 じゃあなぜ「Formatがない」?

考えられる原因は2つ👇


❌ パターン1:ext が undefined / null

JavaScript

if (!this.ext) return null;

👉 ここに入ってる


🔍 原因候補

① metadata.format が undefined

JavaScript

const metadata \= await sharp(input).metadata();

画像によっては:

JavaScript

metadata.format \=== undefined

👉 return null になってる


② 途中で ext を消してる

JavaScript

this.ext \= null;

または

JavaScript

imageInfo.ext \= undefined;

❌ パターン2:formatを使う側の問題

例えば:

JavaScript

const format \= imageInfo.getFormat();
JavaScript

if (!format) {  
  // Formatがない  
}

👉 null が返ってる


✅ 改善案(おすすめ)

✔ extの正規化を入れる

JavaScript

setExt(ext) {  
  if (!ext) {  
    this.ext \= null;  
    return;  
  }  
  
  ext \= ext.toLowerCase();  
  
  if (ext \=== "jpeg") ext \= "jpg";  
  
  this.ext \= ext;  
}

✔ getFormatを強化

JavaScript

getFormat() {  
  if (!this.ext) return null;  
  
  const ext \= this.ext.toLowerCase();  
  
  if (ext \=== "jpg" || ext \=== "jpeg") {  
    return "image/jpeg";  
  }  
  
  return "image/" + ext;  
}

💡 さらに安全にするなら

JavaScript

getFormat() {  
  if (!this.ext) return "image/jpeg"; // fallback  
  
  const ext \= this.ext.toLowerCase();  
  
  switch (ext) {  
    case "jpg":  
    case "jpeg":  
      return "image/jpeg";  
    case "png":  
    case "webp":  
    case "gif":  
    case "avif":  
      return "image/" + ext;  
    default:  
      return "image/" + ext;  
  }  
}

🔍 デバッグ用(ここ重要)

一度これ出してください👇

JavaScript

console.log("ext:", this.ext);

🎯 まとめ

今回の本質👇

  • sharp"jpeg" を返す

  • あなたのコードは "jpg" 前提

  • または ext 自体が undefined


🚀 次にやるといいこと

もし可能なら👇貼ってください

  • getImageInfo() の戻り値ログ

  • getFormat() 呼び出し箇所

👉 どこで ext が消えてるか特定できます

You:

取得したImageInfoにformatがない [ ImageInfo { id: '0001', outFileName: '0001.jpg', ext: 'jpeg', width: 1512, height: 2150, outWidth: -1, outHeight: -1, isCover: false, rotateAngle: 0 }, ImageInfo { id: '0002', outFileName: '0002.webp', ext: 'webp', width: 550, height: 404, outWidth: -1, outHeight: -1, isCover: false, rotateAngle: 0 }, ImageInfo { id: '0003', outFileName: '0003.jpg', ext: 'jpeg', width: 640, height: 400, outWidth: -1, outHeight: -1, isCover: false, rotateAngle: 0 } ]

ChatGPT:

これはバグじゃなくて 設計の勘違い です 👍


🎯 結論

👉 format というプロパティは ImageInfo に存在しない

あなたのクラス👇

JavaScript

this.ext \= ext ? ext.toLowerCase() : null;

👉 保存しているのは ext


🔍 つまり何が起きてるか

ログ👇

JavaScript

ImageInfo {  
  ext: 'jpeg'  
}

👉 正常

でもどこかで👇やってるはず

JavaScript

imageInfo.format

👉 ❌ undefined(そんなプロパティない)


🧠 元ネタ(混乱の原因)

JavaScript

const metadata \= await sharp(input).metadata();

ここでは👇

JavaScript

metadata.format // ← ある

でも👇

JavaScript

new ImageInfo(metadata.format, ...)

👉 formatext に入れてる


✅ 正しい使い方

✔ 方法1(そのまま)

JavaScript

imageInfo.getFormat()

✔ 方法2(ext直接)

JavaScript

imageInfo.getExt()

❌ NG

JavaScript

imageInfo.format // ← 存在しない

💡 もし format プロパティ欲しいなら

✔ getter追加

JavaScript

get format() {  
  return this.getFormat();  
}

これで👇

JavaScript

imageInfo.format

使えるようになる


👍 おすすめ修正(AozoraEpub3的)

Java版に寄せるなら👇

JavaScript

getFormat() {  
  if (!this.ext) return null;  
  
  if (this.ext \=== "jpg" || this.ext \=== "jpeg") {  
    return "image/jpeg";  
  }  
  
  return "image/" + this.ext;  
}

🔥 よくあるミスまとめ

今回の流れ:

  1. sharp.metadata().format は存在

  2. それを ext に保存

  3. でも later で format を参照

  4. 👉 undefined


🎯 まとめ

👉 ImageInfoには format は無い、extだけある


🚀 ワンポイント改善

もしコード全体で混乱してるなら👇

JavaScript

this.format \= ext;

に変えてもいい(Java寄せ)


必要なら
👉 Java版 ImageInfo と完全互換のJS版 作れます(AozoraEpub3用)

You:

修正版が欲しい import fs from "fs"; import sharp from "sharp"; export default class ImageInfo { /** 画像の情報を生成 * @param {string} ext png jpg gif webp など */ constructor(ext, width, height) { this.id = null; // ファイルID this.outFileName = null; // 出力ファイル名 this.ext = ext ? ext.toLowerCase() : null; this.width = width ?? -1; this.height = height ?? -1; this.outWidth = -1; this.outHeight = -1; this.isCover = false; this.rotateAngle = 0; } // ========================= // static 生成メソッド // ========================= /** ファイルから画像情報を生成 */ static async getImageInfoFromFile(imageFile) { const buffer = await fs.promises.readFile(imageFile); return await ImageInfo.getImageInfo(buffer); } /** 画像ストリーム / Buffer から画像情報を生成 */ static async getImageInfo(input) { if (!input) return null; try { const metadata = await sharp(input).metadata(); if (!metadata || !metadata.format) return null; return new ImageInfo( metadata.format, // png jpeg webp gif avif など metadata.width, metadata.height ); } catch (e) { return null; } } /** 既に幅高さが分かっている場合 */ static getImageInfoFromRaw(ext, width, height) { return new ImageInfo(ext, width, height); } // ========================= // getter / setter // ========================= getId() { return this.id; } setId(id) { this.id = id; } getOutFileName() { return this.outFileName; } setOutFileName(file) { this.outFileName = file; } setExt(ext) { this.ext = ext?.toLowerCase(); } getExt() { return this.ext; } /** mime形式(image/png)を返す */ getFormat() { if (!this.ext) return null; return "image/" + (this.ext === "jpg" ? "jpeg" : this.ext); } getIsCover() { return this.isCover; } setIsCover(isCover) { this.isCover = isCover; } getWidth() { return this.width; } setWidth(width) { this.width = width; } getHeight() { return this.height; } setHeight(height) { this.height = height; } getOutWidth() { return this.outWidth; } setOutWidth(outWidth) { this.outWidth = outWidth; } getOutHeight() { return this.outHeight; } setOutHeight(outHeight) { this.outHeight = outHeight; } }

ChatGPT:

了解 👍
AozoraEpub3互換寄り+今回の不具合(format / ext / 安定性)を全部潰した実用版を出します。


✅ 修正版ポイント

  • jpeg → jpg 正規化

  • getFormat() を完全対応(image/jpeg など)

  • format プロパティ互換(Java的に使える)

  • sharpformat 欠損対策

  • null耐性強化

  • デバッグしやすく


🚀 修正版 ImageInfo.js

JavaScript

import fs from "fs";  
import sharp from "sharp";  
  
export default class ImageInfo {  
  /\*\* 画像の情報を生成  
   \* @param {string} ext png jpg gif webp など  
   \*/  
  constructor(ext, width, height) {  
    this.id \= null;                // ファイルID  
    this.outFileName \= null;       // 出力ファイル名  
  
    this.setExt(ext);  
  
    this.width \= width ?? \-1;  
    this.height \= height ?? \-1;  
  
    this.outWidth \= \-1;  
    this.outHeight \= \-1;  
  
    this.isCover \= false;  
    this.rotateAngle \= 0;  
  }  
  
  // =========================  
  // static 生成メソッド  
  // =========================  
  
  /\*\* ファイルから画像情報を生成 \*/  
  static async getImageInfoFromFile(imageFile) {  
    const buffer \= await fs.promises.readFile(imageFile);  
    return await ImageInfo.getImageInfo(buffer);  
  }  
  
  /\*\* 画像ストリーム / Buffer から画像情報を生成 \*/  
  static async getImageInfo(input) {  
    if (!input) return null;  
  
    try {  
      const metadata \= await sharp(input).metadata();  
  
      if (!metadata) return null;  
  
      // formatが取れないケース対策  
      let format \= metadata.format;  
  
      if (!format) {  
        // fallback: jpeg扱い  
        format \= "jpeg";  
      }  
  
      return new ImageInfo(  
        format,  
        metadata.width ?? \-1,  
        metadata.height ?? \-1  
      );  
  
    } catch (e) {  
      return null;  
    }  
  }  
  
  /\*\* 既に幅高さが分かっている場合 \*/  
  static getImageInfoFromRaw(ext, width, height) {  
    return new ImageInfo(ext, width, height);  
  }  
  
  // =========================  
  // getter / setter  
  // =========================  
  
  getId() {  
    return this.id;  
  }  
  
  setId(id) {  
    this.id \= id;  
  }  
  
  getOutFileName() {  
    return this.outFileName;  
  }  
  
  setOutFileName(file) {  
    this.outFileName \= file;  
  }  
  
  /\*\* 拡張子を正規化してセット \*/  
  setExt(ext) {  
    if (!ext) {  
      this.ext \= null;  
      return;  
    }  
  
    ext \= ext.toLowerCase();  
  
    // 正規化(重要)  
    if (ext \=== "jpeg") ext \= "jpg";  
  
    this.ext \= ext;  
  }  
  
  getExt() {  
    return this.ext;  
  }  
  
  /\*\* MIME形式を返す(image/jpegなど) \*/  
  getFormat() {  
    if (!this.ext) return null;  
  
    switch (this.ext) {  
      case "jpg":  
      case "jpeg":  
        return "image/jpeg";  
      case "png":  
      case "webp":  
      case "gif":  
      case "avif":  
        return "image/" + this.ext;  
      default:  
        return "image/" + this.ext;  
    }  
  }  
  
  /\*\* Java互換:formatプロパティとしてもアクセス可能 \*/  
  get format() {  
    return this.getFormat();  
  }  
  
  getIsCover() {  
    return this.isCover;  
  }  
  
  setIsCover(isCover) {  
    this.isCover \= isCover;  
  }  
  
  getWidth() {  
    return this.width;  
  }  
  
  setWidth(width) {  
    this.width \= width;  
  }  
  
  getHeight() {  
    return this.height;  
  }  
  
  setHeight(height) {  
    this.height \= height;  
  }  
  
  getOutWidth() {  
    return this.outWidth;  
  }  
  
  setOutWidth(outWidth) {  
    this.outWidth \= outWidth;  
  }  
  
  getOutHeight() {  
    return this.outHeight;  
  }  
  
  setOutHeight(outHeight) {  
    this.outHeight \= outHeight;  
  }  
}

🎯 これで解決できる問題

✔ format が無い問題

JavaScript

imageInfo.format

👉 使えるようになる


✔ jpeg / jpg 問題

JavaScript

ext \= "jpeg" → "jpg"

👉 統一


✔ sharp の format undefined 問題

👉 fallback対応済み


✔ Javaコード互換

JavaScript

imageInfo.getFormat()  
imageInfo.format

👉 両対応


👍 補足(かなり重要)

AozoraEpub3移植では👇

👉 「ext」と「format」が混在するコードが多い

この修正版はそれを吸収する設計になってます


🚀 次にやると良い

もし今後👇出たら

  • Promise { ImageInfo }

  • 配列ミス

  • async汚染

👉 全部まとめて整理できるので言ってください 👍