@mdgate/mobi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/mobi.d.ts +2 -0
- package/dist/to-markdown.d.ts +6 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @mdgate/mobi
|
|
2
|
+
|
|
3
|
+
Convert PalmDOC, MOBI, and KF8/AZW3 ebooks to Markdown. Outputs GitHub-Flavored Markdown. Works in
|
|
4
|
+
Node, Edge, and browsers. No native addons.
|
|
5
|
+
|
|
6
|
+
Handles: `.mobi`, `.azw`, `.azw3`, `.prc`
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { toMarkdown } from '@mdgate/mobi';
|
|
12
|
+
|
|
13
|
+
const markdown = await toMarkdown(bytes);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Compose with other converters:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { create } from '@mdgate/core';
|
|
20
|
+
import { mobi } from '@mdgate/mobi';
|
|
21
|
+
|
|
22
|
+
const convert = create([mobi()]);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Part of [mdgate converters](https://github.com/mdgate/converters); install
|
|
26
|
+
`@mdgate/converters` for every format at once.
|
|
27
|
+
|
|
28
|
+
## Limitations (v1)
|
|
29
|
+
|
|
30
|
+
- DRM-encrypted books are rejected (`ConvertError.encrypted`).
|
|
31
|
+
- Text records are extracted: uncompressed PalmDOC, PalmDOC LZ77, and HUFF/CDIC.
|
|
32
|
+
- KF8/AZW3 XHTML is converted when it can be isolated from the text stream. Skeleton + fragment
|
|
33
|
+
reconstruction via INDX is not implemented, so some AZW3 books lose structure.
|
|
34
|
+
- Images, fonts, audio, and NCX/toc indexes are not extracted.
|
|
35
|
+
- Topaz (`TPZ`) Kindle files are not supported.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{ConvertError as _}from"@mdgate/core";import{fileExtension as Fn}from"@mdgate/utils";import{parseXml as gn}from"@mdgate/containers";import{ConvertError as a}from"@mdgate/core";import{documentToMarkdown as V,emptyDocument as q,heading as y,plain as N}from"@mdgate/document";import{Stylesheet as hn,toBlocks as an}from"@mdgate/html";import{decode as sn,isAbsoluteUri as G,trim as H}from"@mdgate/utils";function p(n,t){return(n[t]??0)<<8|(n[t+1]??0)}function h(n,t){return((n[t]??0)<<24|(n[t+1]??0)<<16|(n[t+2]??0)<<8|(n[t+3]??0))>>>0}function $(n,t,r){if(t<0||r<=0||t+r>n.length)return"";let e="";for(let o=0;o<r;o+=1)e+=String.fromCharCode(n[t+o]);return e}function k(n,t,r){if(t+r.length>n.length)return!1;for(let e=0;e<r.length;e+=1)if(n[t+e]!==r.charCodeAt(e))return!1;return!0}function M(n,t){let r=new Uint8Array(t),e=0;for(let o of n)r.set(o,e),e+=o.length;return r}import{ConvertError as s}from"@mdgate/core";var L=1,I=2,P=17480,rn=1048576;function Y(n,t,r,e){let o=Math.min(e,rn);if(t===L){if(n.length>o)throw s.resourceLimit("max_entry_bytes","text record exceeds the read cap");return n}if(t===I)return en(n,o);if(t===P){if(r===void 0)throw s.malformed("HUFF/CDIC dictionary is missing");return r.unpack(n,o)}throw s.unsupported(`compression ${t}`)}function en(n,t){let r=new Uint8Array(Math.min(Math.max(n.length*2,64),t)),e=0,o=0;while(o<n.length){let f=n[o];if(o+=1,f>=1&&f<=8){let i=Math.min(f,n.length-o);r=O(r,e,i,t),r.set(n.subarray(o,o+i),e),e+=i,o+=i;continue}if(f<128){r=O(r,e,1,t),r[e]=f,e+=1;continue}if(f>=192){r=O(r,e,2,t),r[e]=32,r[e+1]=f^128,e+=2;continue}if(o>=n.length)break;let l=f<<8|n[o];o+=1;let u=l>>3&2047,g=(l&7)+3;if(u===0||u>e)throw s.malformed("invalid PalmDOC backreference");r=O(r,e,g,t);for(let i=0;i<g;i+=1)r[e+i]=r[e-u+i];e+=g}return r.subarray(0,e)}class D{dict1;minCode;maxCode;dictionary;constructor(n){if(n.length===0)throw s.malformed("HUFF/CDIC dictionary is missing");let t=on(n[0]);this.dict1=t.dict1,this.minCode=t.minCode,this.maxCode=t.maxCode,this.dictionary=[];for(let r=1;r<n.length;r+=1)fn(n[r],this.dictionary);if(this.dictionary.length===0)throw s.malformed("HUFF/CDIC dictionary is empty")}unpack(n,t){return C(n,this.dict1,this.minCode,this.maxCode,this.dictionary,t)}}function on(n){if(n.length<16||!k(n,0,"HUFF")||h(n,4)!==24)throw s.malformed("invalid HUFF header");let t=h(n,8),r=h(n,12);if(t+1024>n.length||r+256>n.length)throw s.malformed("truncated HUFF tables");let e=[];for(let l=0;l<256;l+=1){let u=h(n,t+l*4),g=u&31;if(g===0)throw s.malformed("invalid HUFF dictionary");e.push({codeLen:g,term:(u&128)!==0,maxCode:Z(u>>>8,32-g)})}let o=[0],f=[0];for(let l=0;l<32;l+=1){let u=h(n,r+l*8),g=h(n,r+l*8+4),i=32-(l+1);o.push(ln(u,i)),f.push(Z(g,i))}return{dict1:e,minCode:o,maxCode:f}}function fn(n,t){if(n.length<16||!k(n,0,"CDIC")||h(n,4)!==16)throw s.malformed("invalid CDIC header");let r=h(n,8),e=h(n,12);if(e>16)throw s.malformed("invalid CDIC bit width");let o=Math.min(1<<e,Math.max(0,r-t.length));if(16+o*2>n.length)throw s.malformed("truncated CDIC index");for(let f=0;f<o;f+=1){let u=16+p(n,16+f*2);if(u+2>n.length)throw s.malformed("truncated CDIC phrase");let g=p(n,u),i=g&32767,m=u+2;if(m+i>n.length)throw s.malformed("truncated CDIC phrase");t.push({data:n.subarray(m,m+i),unpacked:(g&32768)!==0})}}function C(n,t,r,e,o,f){let l=n.length*8,u=0,g=W(n,0),i=32,m=[],d=0;for(;;){if(i<=0)u+=4,g=W(n,u),i+=32;let x=Number(g>>BigInt(i)&0xffffffffn)>>>0,{codeLen:c,term:U,maxCode:F}=t[x>>>24];if(!U){while(c<r.length&&x<r[c])c+=1;if(c>=e.length)throw s.malformed("invalid HUFF code");F=e[c]}if(i-=c,l-=c,l<0)break;let X=32-c,tn=X>=32?0:F-x>>>X,A=o[tn];if(A===void 0)throw s.malformed("invalid HUFF phrase");if(!A.unpacked)A.data=C(A.data,t,r,e,o,f),A.unpacked=!0;if(m.push(A.data),d+=A.data.length,d>f)throw s.resourceLimit("max_entry_bytes","decompressed text exceeds the read cap")}return M(m,d)}function W(n,t){let r=0n;for(let e=0;e<8;e+=1)r=r<<8n|BigInt(n[t+e]??0);return r}function ln(n,t){if(t<=0)return n>>>0;if(t>=32)return 0;return Number(BigInt(n)<<BigInt(t)&0xffffffffn)>>>0}function Z(n,t){if(t<=0)return n>>>0;if(t>=32)return 4294967295;return Number((BigInt(n)+1n<<BigInt(t))-1n&0xffffffffn)>>>0}function O(n,t,r,e){if(t+r>e)throw s.resourceLimit("max_entry_bytes","decompressed text exceeds the read cap");if(t+r<=n.length)return n;let o=n.length===0?64:n.length;while(o<t+r)o*=2;let f=new Uint8Array(Math.min(o,e));return f.set(n.subarray(0,t)),f}function K(n,t){let r=n,e=t>>>1;while(e!==0){if((e&1)!==0){let o=un(r);if(o<=0||o>r.length)break;r=r.subarray(0,r.length-o)}e>>>=1}if((t&1)!==0&&r.length>0){let o=(r[r.length-1]&3)+1;if(o<=r.length)r=r.subarray(0,r.length-o)}return r}function un(n){let t=0,r=Math.min(n.length,4);for(let e=0;e<r;e+=1){let o=n[n.length-1-e];if((o&128)!==0)t=0;t=t<<7|o&127}return t}var z=1e5,S=134217728,B=78,w=4294967295,cn=new TextEncoder,mn={linkTarget(n){if(n.length===0)return;if(n.startsWith("#")){let t=n.slice(1);return t.length>0?{type:"anchor",id:t}:void 0}if(G(n))return{type:"external",url:n};return{type:"relative",url:n}},imageSource(n){if(n.length===0)return;if(G(n))return{type:"external",url:n};return},anchorId(n){return n}};function b(n){if(Rn(n)||_n(n)||En(n))return!1;if(n.length>=68){let t=$(n,60,8);if(t==="BOOKMOBI"||t==="TEXtREAd")return!0}return Sn(n)}function v(n){let t=xn(n),r=Mn(t.text);if(r.length>0){let o=q();if(t.title!==void 0)o.blocks.push(y(1,[N(t.title)]));for(let f of r)o.blocks.push(...wn(f));if(o.blocks.length===0)throw a.malformed("empty text");return V(Tn(o,t.title))}let e=q();if(t.title!==void 0)e.blocks.push(y(1,[N(t.title)]));for(let o of Pn(t.text))e.blocks.push({type:"paragraph",inlines:[N(o)]});if(e.blocks.length===0)throw a.malformed("empty text");return V(e)}function xn(n){let t=dn(n);if(t.records.length===0)throw a.missingPart("text");let r=t.records[0],e=J(r);if(e.encryption!==0)throw a.encrypted();let o=Q(r);if(o!==void 0&&j(o))throw a.encrypted();let f=o!==void 0?pn(r,o):void 0,l=nn(o?.encoding??1252),u=Ln(r,t.name,o,f,l),g={records:t.records,header:r,palm:e,mobi:o},i=f?.kf8Boundary;if(i!==void 0&&i+1<t.records.length){let d=t.records[i];if(k(d,0,"BOUNDARY")){let x=t.records[i+1],c=J(x);if(c.encryption!==0)throw a.encrypted();let U=Q(x);if(U!==void 0&&j(U))throw a.encrypted();g={records:t.records,header:x,palm:c,mobi:U}}}let m=kn(g.records,g.palm,g.mobi,l);return{title:u,text:m}}function dn(n){if(n.length<B+8)throw a.malformed("truncated Palm database");let t=p(n,76);if(t===0)throw a.malformed("Palm database has no records");if(t>z)throw a.resourceLimit("max_entry_count",`Palm database has ${t} records`);if(n.length<B+t*8)throw a.malformed("truncated record list");let r=[];for(let o=0;o<t;o+=1)r.push(h(n,B+o*8));let e=[];for(let o=0;o<t;o+=1){let f=r[o],l=o+1<t?r[o+1]:n.length;if(f>n.length||l<f)throw a.malformed("invalid record offset");e.push(n.subarray(f,Math.min(l,n.length)))}return{name:In(n),records:e}}function J(n){if(n.length<16)throw a.malformed("truncated PalmDOC header");let t=p(n,0);if(t!==L&&t!==I&&t!==P)throw a.unsupported(`compression ${t}`);return{compression:t,textLength:h(n,4),recordCount:p(n,8),encryption:p(n,12)}}function Q(n){if(n.length<24||!k(n,16,"MOBI"))return;let t=h(n,20);if(t<8)throw a.malformed("truncated MOBI header");let r=(f,l)=>n.length>=f+4?h(n,f):l,e=t>=228&&n.length>=244?h(n,240):0,o=t>=192&&n.length>=194?p(n,192):1;return{headerLength:t,encoding:n.length>=32?h(n,28):1252,firstNonBook:r(80,w),fullNameOffset:r(84,0),fullNameLength:r(88,0),firstImage:r(108,w),huffOffset:r(112,0),huffCount:r(116,0),exthFlags:r(128,0),drmOffset:r(168,w),drmCount:r(172,0),firstContent:o===0?1:o,extraFlags:e}}function pn(n,t){if((t.exthFlags&64)===0)return;let r=16+t.headerLength;if(r+12>n.length||!k(n,r,"EXTH"))return;let e=h(n,r+4),o=h(n,r+8);if(o>1e4)throw a.malformed("truncated EXTH header");let f=r+12,l=Math.min(n.length,r+e),u,g;for(let i=0;i<o&&f+8<=l;i+=1){let m=h(n,f),d=h(n,f+4);if(d<8||f+d>n.length)break;let x=n.subarray(f+8,f+d);if(m===503){let c=H(T(x,nn(t.encoding)));if(c.length>0)u=c}if(m===121&&x.length>=4)g=h(x,0);f+=d}return{title:u,kf8Boundary:g}}function kn(n,t,r,e){let o=r!==void 0&&r.firstContent>0?r.firstContent:1,f=o+Math.max(t.recordCount,1)-1;if(r!==void 0&&r.firstNonBook!==0&&r.firstNonBook!==w)f=Math.min(f,r.firstNonBook-1);if(r!==void 0&&r.firstImage!==0&&r.firstImage!==w)f=Math.min(f,r.firstImage-1);if(o>=n.length||f<o)throw a.missingPart("text");let l=t.textLength>0?Math.min(t.textLength,S):S,u=An(n,t,r),g=[],i=0;for(let d=o;d<=f&&d<n.length;d+=1){let x=n[d];if(x.length===0)continue;if(r!==void 0&&r.extraFlags!==0)x=K(x,r.extraFlags);let c=Y(x,t.compression,u,l-i);if(g.push(c),i+=c.length,i>S)throw a.resourceLimit("max_entry_bytes","book text exceeds the read cap");if(t.textLength>0&&i>=t.textLength)break}if(i===0)throw a.missingPart("text");let m=M(g,i);if(t.textLength>0&&m.length>t.textLength)m=m.subarray(0,t.textLength);return Dn(T(m,e))}function An(n,t,r){if(t.compression!==P)return;if(r===void 0||r.huffCount===0||r.huffOffset>=n.length)throw a.malformed("HUFF/CDIC dictionary is missing");let e=Math.min(n.length,r.huffOffset+r.huffCount);return new D(n.slice(r.huffOffset,e))}function wn(n){let t=On(n);try{let e=gn(cn.encode(Un(t))),o=Bn(e),f=Hn(e),l=an(o,f,mn);if(l.length>0)return l}catch(e){if(e instanceof a&&e.isFatal())throw e}let r=Nn(n);return r.length===0?[]:[{type:"paragraph",inlines:[N(r)]}]}function Un(n){return n.replace(/<(br|hr|img|meta|link|mbp:pagebreak)(\s[^>/]*)?\s*\/?>/gi,"<$1$2/>")}function Bn(n){if(n.local==="body")return n;let t=n.local==="html"?n:R(n,"html");if(t!==void 0)return R(t,"body")??t;return R(n,"body")??n}function R(n,t){for(let r of n.childElems())if(r.local===t)return r;return}function Hn(n){let t=new hn;for(let r of n.descendantElems())if(r.local==="style")t.add(r.text());return t}function Mn(n){let t=[],r=/<html\b[\s\S]*?<\/html>/gi,e=r.exec(n);while(e!==null)t.push(e[0]),e=r.exec(n);if(t.length>0)return t;if(/<[a-zA-Z][^>]*>/.test(n))return[n];return[]}function On(n){if(/<html[\s>]/i.test(n))return n;return`<html><body>${n}</body></html>`}function Pn(n){let t=n.replace(/\r\n/g,`
|
|
2
|
+
`).replace(/\r/g,`
|
|
3
|
+
`),r=[];for(let e of t.split(/\n(?:[ \t]*\n)+/)){let o=H(e.replace(/[ \t]*\n[ \t]*/g," "));if(o.length>0)r.push(o)}return r}function Nn(n){return H(n.replace(/<[^>]+>/g," ").replace(/\s+/g," "))}function Tn(n,t){if(t===void 0||n.blocks.length<2)return n;let r=n.blocks[0],e=n.blocks[1];if(r?.type!=="heading"||e?.type!=="heading")return n;let o=r.content[0],f=e.content[0];if(o?.type!=="text"||f?.type!=="text")return n;if(o.text.toLowerCase()!==f.text.toLowerCase())return n;return{...n,blocks:n.blocks.slice(1)}}function Ln(n,t,r,e,o){if(e?.title!==void 0&&e.title.length>0)return e.title;if(r!==void 0&&r.fullNameLength>0&&r.fullNameOffset>0){let f=r.fullNameOffset,l=f+r.fullNameLength;if(l<=n.length){let u=H(T(n.subarray(f,l),o));if(u.length>0)return u}}return t.length>0?t:void 0}function In(n){let t=Math.min(32,n.length),r=0;while(r<t&&n[r]!==0)r+=1;if(r===0)return"";return H(T(n.subarray(0,r),"windows-1252"))}function nn(n){if(n===65001||n===65000)return"utf-8";if(n===932)return"shift_jis";if(n===950)return"big5";if(n===949)return"euc-kr";if(n===936)return"gbk";return"windows-1252"}function T(n,t){try{return sn(n,t)}catch{return new TextDecoder("utf-8",{fatal:!1}).decode(n)}}function Dn(n){return n.includes("\x00")?n.replace(/\0+/g,""):n}function j(n){if(n.drmOffset===0||n.drmOffset===w)return!1;return n.drmCount!==0&&n.drmCount!==w}function Sn(n){if(n.length<B+8)return!1;let t=p(n,76);if(t<1||t>z)return!1;let r=h(n,B);return r+20<=n.length&&k(n,r+16,"MOBI")}function Rn(n){let t=0;if(n.length>=3&&n[0]===239&&n[1]===187&&n[2]===191)t=3;while(t<n.length){let r=n[t];if(r!==9&&r!==10&&r!==13&&r!==32)break;t+=1}return t+5<=n.length&&n[t]===37&&n[t+1]===80&&n[t+2]===68&&n[t+3]===70&&n[t+4]===45}function _n(n){return n.length>=8&&n[0]===208&&n[1]===207&&n[2]===17&&n[3]===224&&n[4]===161&&n[5]===177&&n[6]===26&&n[7]===225}function En(n){return n.length>=4&&n[0]===80&&n[1]===75&&(n[2]===3||n[2]===5||n[2]===7)&&(n[3]===4||n[3]===6||n[3]===8)}var Xn=new Set(["mobi","azw","azw3","prc"]);function E(){return{id:"mobi",sniff(n,t){if(b(n))return 2;if(t?.path!==void 0&&Xn.has(Fn(t.path)??""))return 1;return 0},convert(n){return $n(n),{markdown:v(n)}}}}function $n(n){if(Wn(n))throw _.unsupported("pdf");if(Zn(n))throw _.unsupported("ole");if(Yn(n))throw _.unsupported("zip")}function Wn(n){let t=Cn(n);return Kn(n,"%PDF-",t)}function Zn(n){return n.length>=8&&n[0]===208&&n[1]===207&&n[2]===17&&n[3]===224&&n[4]===161&&n[5]===177&&n[6]===26&&n[7]===225}function Yn(n){if(n.length<4||n[0]!==80||n[1]!==75)return!1;return n[2]===3&&n[3]===4||n[2]===5&&n[3]===6||n[2]===7&&n[3]===8}function Cn(n){let t=0;if(n.length>=3&&n[0]===239&&n[1]===187&&n[2]===191)t=3;while(t<n.length){let r=n[t];if(r!==9&&r!==10&&r!==13&&r!==32)break;t+=1}return t}function Kn(n,t,r){if(r+t.length>n.length)return!1;for(let e=0;e<t.length;e+=1)if(n[r+e]!==t.charCodeAt(e))return!1;return!0}import{create as Vn}from"@mdgate/core";var qn=Vn([E()]);function yn(n,t){return qn(n,t)}export{yn as toMarkdown,E as mobi};
|
package/dist/mobi.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ConvertHint } from '@mdgate/core';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a single file's bytes to Markdown with the mobi converter.
|
|
4
|
+
* `hint.path` is only a sniff hint; it is never read from disk.
|
|
5
|
+
*/
|
|
6
|
+
export declare function toMarkdown(bytes: Uint8Array, hint?: ConvertHint): Promise<string>;
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mdgate/mobi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "mdgate MOBI / AZW3 converter",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/**/*.js",
|
|
18
|
+
"dist/**/*.d.ts"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "bun ../../scripts/build-package.ts",
|
|
22
|
+
"prepublishOnly": "bun run build"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@mdgate/containers": "0.1.0",
|
|
26
|
+
"@mdgate/core": "0.1.3",
|
|
27
|
+
"@mdgate/document": "0.1.0",
|
|
28
|
+
"@mdgate/html": "0.1.0",
|
|
29
|
+
"@mdgate/utils": "0.1.2"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"mdgate",
|
|
39
|
+
"markdown",
|
|
40
|
+
"mobi",
|
|
41
|
+
"azw",
|
|
42
|
+
"azw3",
|
|
43
|
+
"kindle",
|
|
44
|
+
"palmdoc"
|
|
45
|
+
]
|
|
46
|
+
}
|