画像の情報を読み取るために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
つまり imageInfo が ImageInfo インスタンスじゃない です。
🔍 よくある原因(今回かなりこれっぽい)
あなたの 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();
});
💡 今回のエラーの変遷まとめ
最初のエラー
👉 「関数がない」
実態
👉 Promiseに対して呼んでる
根本原因
👉 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
考えられる原因は2つ👇
❌ パターン1:ext が undefined / null
JavaScript
if (!this.ext) return null;
👉 ここに入ってる
🔍 原因候補
JavaScript
const metadata \= await sharp(input).metadata();
画像によっては:
JavaScript
metadata.format \=== undefined
👉 return null になってる
② 途中で ext を消してる
JavaScript
this.ext \= null;
または
JavaScript
imageInfo.ext \= undefined;
例えば:
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;
}
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, ...)
👉 format → ext に入れてる
✅ 正しい使い方
✔ 方法1(そのまま)
JavaScript
imageInfo.getFormat()
✔ 方法2(ext直接)
JavaScript
imageInfo.getExt()
❌ NG
JavaScript
imageInfo.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;
}
🔥 よくあるミスまとめ
今回の流れ:
sharp.metadata().format は存在
それを ext に保存
でも later で format を参照
👉 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 / 安定性)を全部潰した実用版を出します。
✅ 修正版ポイント
🚀 修正版 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;
}
}
🎯 これで解決できる問題
JavaScript
imageInfo.format
👉 使えるようになる
✔ jpeg / jpg 問題
JavaScript
ext \= "jpeg" → "jpg"
👉 統一
👉 fallback対応済み
✔ Javaコード互換
JavaScript
imageInfo.getFormat()
imageInfo.format
👉 両対応
👍 補足(かなり重要)
AozoraEpub3移植では👇
👉 「ext」と「format」が混在するコードが多い
この修正版はそれを吸収する設計になってます
🚀 次にやると良い
もし今後👇出たら
Promise { ImageInfo }
配列ミス
async汚染
👉 全部まとめて整理できるので言ってください 👍