nx_mvault_context.nx source
↩ module page · 72 lines · 3000 B
1// nx_mvault_context.nx -- CONTEXTUAL ingestion: type from surrounding context,
2// not just per-file magic bytes.
3//
4// WHY (operator 2026-07-17): "ingest ... hentai formats or other things that
5// may look like images but really be books ... the ingestion to be contextual."
6// A numbered image sequence in a gallery/doujin dir is ONE book, not N loose
7// images; a .cbz/.cbr is a comic; a .pdf/.epub is a book. The per-file sniffer
8// (nx_media_sniff) sees only bytes -- it would file every doujin page as a
9// separate loose IMAGE. This layer applies the CONTEXT the reindexer computes
10// (archive magic, in-a-numbered-sequence, ebook) to fold them into one work.
11//
12// The reindexer computes a ctx flag per item (from dir scan + these magic
13// detectors + extension) and calls mv_context_type; a whole image-sequence
14// dir becomes ONE MANGA item (its pages are members, not separate items).
15//
16// Composes nx_mvault_ingest (mv_type / MV_TYPE_*). license_tier: ORIGINAL
17
18import "nx_syscalls.nx"
19import "nx_media_pool.nx"
20import "nx_mvault_ingest.nx"
21
22// ===== context flags (reindexer-computed) =====
23const MV_CTX_NONE: i64 = 0 // no context -> per-file sniff decides
24const MV_CTX_PDF: i64 = 1 // %PDF -> book
25const MV_CTX_EPUB: i64 = 2 // .epub (zip) -> book
26const MV_CTX_CBZ: i64 = 3 // comic zip -> manga
27const MV_CTX_CBR: i64 = 4 // comic rar -> manga
28const MV_CTX_IMGSEQ: i64 = 5 // page N of a numbered image gallery -> manga
29
30// ===== container magic detectors (building blocks for ctx computation) =====
31
32// PDF: "%PDF"
33func mv_is_pdf(b: *u8, n: i64) -> i64 {
34 if n < 4 { return 0 }
35 if b[0] != (0x25 as u8) { return 0 } // %
36 if b[1] != (0x50 as u8) { return 0 } // P
37 if b[2] != (0x44 as u8) { return 0 } // D
38 if b[3] != (0x46 as u8) { return 0 } // F
39 return 1
40}
41
42// ZIP local file header: "PK\x03\x04" (cbz / epub / docx are zip containers)
43func mv_is_zip(b: *u8, n: i64) -> i64 {
44 if n < 4 { return 0 }
45 if b[0] != (0x50 as u8) { return 0 } // P
46 if b[1] != (0x4B as u8) { return 0 } // K
47 if b[2] != (0x03 as u8) { return 0 }
48 if b[3] != (0x04 as u8) { return 0 }
49 return 1
50}
51
52// RAR: "Rar!" (cbr comic archives)
53func mv_is_rar(b: *u8, n: i64) -> i64 {
54 if n < 4 { return 0 }
55 if b[0] != (0x52 as u8) { return 0 } // R
56 if b[1] != (0x61 as u8) { return 0 } // a
57 if b[2] != (0x72 as u8) { return 0 } // r
58 if b[3] != (0x21 as u8) { return 0 } // !
59 return 1
60}
61
62// ===== contextual type =====
63// ctx wins (it encodes the surrounding structure); else fall back to the
64// per-file sniffed type via mv_type.
65func mv_context_type(sniff_type: i64, ctx: i64) -> i64 {
66 if ctx == MV_CTX_PDF { return MV_TYPE_BOOK }
67 if ctx == MV_CTX_EPUB { return MV_TYPE_BOOK }
68 if ctx == MV_CTX_CBZ { return MV_TYPE_MANGA }
69 if ctx == MV_CTX_CBR { return MV_TYPE_MANGA }
70 if ctx == MV_CTX_IMGSEQ { return MV_TYPE_MANGA }
71 return mv_type(MV_HINT_NONE, sniff_type)
72}