gedcom-ts 2026.5.4 → 2026.6.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/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes of gedcom-ts
4
4
 
5
+ ## [2026.6.0] - 2026-06-01
6
+
7
+ ### Added
8
+
9
+ - **Multimédia GEDCOM 7 (`OBJE`)** : import et export complets des enregistrements `0 @O…@ OBJE` avec plusieurs `FILE`, `FORM`, `MEDI` (`AUDIO`, `PHOTO`, `VIDEO`, …), `PHRASE`, `TITL` et variantes `FILE`.`TRAN` (transcodage, WebVTT).
10
+ - **`ReadGed.objeRecordsById`** : map des OBJE absorbés depuis le fichier (round-trip via `GedcomExportOptions.objeRecordsById`).
11
+ - **Types et utilitaires OBJE** : `ObjeRecord`, `ObjeFileEntry`, `primaryObjeFileUri`, `parseStandaloneObjeBlock`, `formatObjeRecordBlock`, `guessMediFromForm`, `registerObjeRecord`.
12
+ - **Entretiens audio / histoire orale** : actes `EVEN` + `TYPE Interview` (ou « Oral history ») avec lien `2 OBJE`, note de transcription (`2 NOTE`) et API dédiée — `createAudioInterviewAct`, `attachAudioInterviewToAct`, `editAct(act).asAudioInterview()`, `editAudioInterviewAct`.
13
+ - **Détection et lecture** : `isAudioInterviewAct`, `isAudioInterviewEvenType`, `getAudioInterviewTranscription`, `getAudioInterviewMedia`, `getAudioInterviewObjeRecords`, `primaryAudioInterviewUri`, `nextObjeXrefId`.
14
+ - **Constantes** : `GEDCOM_7_EVEN_TYPE_AUDIO_INTERVIEW`, `GEDCOM_7_EVEN_TYPE_ORAL_HISTORY`.
15
+ - **`GedcomExportOptionsEdit.setObjeRecordsById`** / **`clearObjeRecordsById`**.
16
+ - **Tests** : `parseStandaloneObje`, `guessMediFromForm`, `audioInterviewAct`, extension des specs export / import `maximal70`.
17
+
18
+ ### Changed
19
+
20
+ - **Export OBJE** : émission de `MEDI` dérivé du MIME lorsque absent ; sérialisation multi-fichiers via `formatObjeRecordBlock`.
21
+ - **Import** : `objePayloadById` remplacé par `objeRecordsById` (`PersonGedcomImportOptions`, `ActsByExtraction`).
22
+ - **CI** : script `test:ci` (`build` puis `vitest run`) pour les tests qui lisent `dist/` ; `prepublishOnly` aligné.
23
+
24
+ ### Fixed
25
+
26
+ - **GEDZIP** : inclusion des binaires locaux liés par pointeur `OBJE` (chemins résolus depuis `sourceUri` / `relativePath`).
27
+ - **FORM audio** : reconnaissance de **WebM**, OGG, WAV, M4A, FLAC, etc. (`guessMediaFormFromUri`, `guessMediaFormFromFile`) ; affinage des exports `application/octet-stream` issus de l’UI.
28
+
5
29
  ## [2026.5.4] - 2026-05-24
6
30
 
7
31
  ### Added
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
  - work with a typed JSON model (persons, acts, dates, places, notes, media, name variants, attributes)
7
7
  - edit the model in-place through a fluent, chainable API (`editPerson`, `editAct`, …)
8
8
  - export data back to GEDCOM (`.ged`) or GEDZIP (`.zip`)
9
+ - attach **audio** and other media via GEDCOM 7 `OBJE` records (`MEDI AUDIO`, multi-`FILE`, `TRAN`)
10
+ - model **oral history interviews** as `EVEN` acts with `OBJE` + transcription notes
9
11
  - geocode event places (OpenStreetMap / Nominatim) and group similar city names
10
12
  - generate a **genealogy booklet** as PDF (`gedcom-ts/booklet`, 11 locales including RTL Arabic/Hebrew)
11
13
 
@@ -19,13 +21,14 @@ A graphical demo showcasing the public API (import a `.ged` / `.zip`, browse the
19
21
  - [Quick start](#quick-start)
20
22
  - [Geocoding places](#geocoding-places)
21
23
  - [Genealogy booklet (PDF)](#genealogy-booklet-pdf)
24
+ - [Audio and oral history (GEDCOM 7)](#audio-and-oral-history-gedcom-7)
22
25
  - [API reference](#api-reference)
23
26
  - [Error handling](#error-handling)
24
27
 
25
28
  ## Package
26
29
 
27
30
  - NPM: [gedcom-ts](https://www.npmjs.com/package/gedcom-ts)
28
- - Version format **CalVer** `AAAA.M.micro` (e.g. `2026.5.3` = May 2026). See [CHANGELOG.md](CHANGELOG.md).
31
+ - Current release **`2026.6.0`** — audio / OBJE / oral history. Version format **CalVer** `AAAA.M.micro` (e.g. `2026.6.0` = June 2026). See [CHANGELOG.md](CHANGELOG.md).
29
32
  - Entry points:
30
33
  - **`gedcom-ts`** — import, model, edit layer, export, geocoding
31
34
  - **`gedcom-ts/booklet`** — PDF livret (`pdf-lib` en peerDependency, chargé depuis `node_modules`)
@@ -367,6 +370,65 @@ await drawGedcomTsLogoOnPage(page, defaultCoverLogoOptions());
367
370
  | Font preload (host) | `registerBookletPdfFontBytes`, `registerPdfFontkit`, `localeNeedsUnicodePdfFont` |
368
371
  | Logo | `ensureBookletLogo`, `drawGedcomTsLogoOnPage`, `getGedcomTsLogoPaths`, `defaultCoverLogoOptions` |
369
372
 
373
+ ## Audio and oral history (GEDCOM 7)
374
+
375
+ Since **`2026.6.0`**, gedcom-ts models audio according to [FamilySearch GEDCOM 7](https://gedcom.io/): external files referenced by **`OBJE`** records (`FILE` + `FORM` + optional `MEDI AUDIO` + `TRAN` for alternate formats or WebVTT). Text transcriptions belong in **`NOTE`** under the event, not inside `OBJE`.
376
+
377
+ **Oral history pattern:** `1 EVEN` + `2 TYPE Interview` (or `Oral history`) + `2 OBJE @O…@` + `2 NOTE` (transcription).
378
+
379
+ ```ts
380
+ import {
381
+ createAudioInterviewAct,
382
+ editAct,
383
+ ExportGedzipFile,
384
+ type Person,
385
+ type ReadGed,
386
+ } from "gedcom-ts";
387
+
388
+ async function addInterview(readGed: ReadGed, person: Person, mp3: File) {
389
+ const objeMap = new Map(readGed.objeRecordsById);
390
+
391
+ const { act, objeRecord } = createAudioInterviewAct(
392
+ {
393
+ description: "Entretien avec grand-mère",
394
+ participantIndis: [person.INDI],
395
+ ownerIndi: person.INDI,
396
+ audioFile: mp3,
397
+ transcription: "Bonjour, je m'appelle Marie…",
398
+ vttUri: "media/transcript.vtt", // optional FILE.TRAN on OBJE
399
+ },
400
+ readGed.persons,
401
+ objeMap,
402
+ );
403
+
404
+ person.acts.add(act);
405
+ if (objeRecord) {
406
+ objeMap.set(objeRecord.id, objeRecord);
407
+ }
408
+
409
+ await new ExportGedzipFile("my-tree", readGed.persons, {
410
+ extraTopLevelRecords: readGed.preservedTopLevelRecords,
411
+ objeRecordsById: objeMap,
412
+ }).download();
413
+ }
414
+
415
+ // enrich an existing act:
416
+ editAct(act)
417
+ .asAudioInterview(readGed.persons, objeMap)
418
+ .setTranscription("Suite de l'entretien…")
419
+ .attachAudio({ audioUri: "https://example.org/rec.mp3", ownerIndi: person.INDI });
420
+ ```
421
+
422
+ | Goal | API |
423
+ | --- | --- |
424
+ | Create interview act | `createAudioInterviewAct` |
425
+ | Attach audio to act | `attachAudioInterviewToAct`, `editAct(act).asAudioInterview()` |
426
+ | Detect imported interviews | `isAudioInterviewAct`, `isAudioInterviewEvenType` |
427
+ | Read transcription / media | `getAudioInterviewTranscription`, `primaryAudioInterviewUri` |
428
+ | OBJE round-trip | `readGed.objeRecordsById` → `objeRecordsById` in export options |
429
+ | Low-level OBJE parse/emit | `parseStandaloneObjeBlock`, `formatObjeRecordBlock`, `guessMediFromForm` |
430
+ | EVEN type constants | `GEDCOM_7_EVEN_TYPE_AUDIO_INTERVIEW`, `GEDCOM_7_EVEN_TYPE_ORAL_HISTORY` |
431
+
370
432
  ## API reference
371
433
 
372
434
  Short description and a minimal snippet for each export of **`gedcom-ts`**. For the PDF booklet, see [Genealogy booklet (PDF)](#genealogy-booklet-pdf) (`gedcom-ts/booklet`).
@@ -416,7 +478,7 @@ try {
416
478
 
417
479
  #### `ReadGed`
418
480
 
419
- Result of an import. Top-level GEDCOM records not modeled into the typed graph (`OBJE`, `REPO`, `SOUR`, `SUBM`, …) are kept in order on `readGed.preservedTopLevelRecords` for round-trip export.
481
+ Result of an import. Standalone `0 @O…@ OBJE` blocks with at least one `FILE` are parsed into **`objeRecordsById`** (multi-file, `MEDI`, `TRAN`). Other top-level records (`REPO`, `SOUR`, `SUBM`, …) stay on `preservedTopLevelRecords` for round-trip export.
420
482
 
421
483
  Notable members:
422
484
 
@@ -430,6 +492,7 @@ Notable members:
430
492
  | `placesMap: Map<string, Place>` | City name → `Place` (first occurrence wins). |
431
493
  | `mapFiles: Map<string, File>` | Relative path → media `File` (for ZIP imports). |
432
494
  | `datasetVersion: GedcomDatasetVersion` | `"7.0"`, `"5.5"` or `"unknown"`. |
495
+ | `objeRecordsById: Map<number, ObjeRecord>` | Parsed `OBJE` records (audio, images, …) keyed by `@O{n}@`. |
433
496
  | `preservedTopLevelRecords: string[]` | Raw blocks for partial round-trip. |
434
497
  | `resolveIndividualPointer(raw)` | Resolves `@I12@`, `I12`, `@Homer_Simpson@`, `Homer_Simpson` to a `Person`. |
435
498
  | `getChildrenForParent(parent)` | All children attached to a parent (via `FAMS`). |
@@ -471,6 +534,7 @@ import { ExportGedcomFile, type ReadGed } from "gedcom-ts";
471
534
  function exportGed(readGed: ReadGed) {
472
535
  new ExportGedcomFile("my-tree", readGed.persons, {
473
536
  extraTopLevelRecords: readGed.preservedTopLevelRecords,
537
+ objeRecordsById: readGed.objeRecordsById,
474
538
  headLanguageTag: "fr-FR",
475
539
  headCopyright: "© 2026 Family Archive",
476
540
  headDestination: "https://gedcom.io/",
@@ -499,6 +563,7 @@ Options shared by both exporters:
499
563
  | Option | Effect |
500
564
  | ---------------------- | -------------------------------------------------------------------------------------------------------- |
501
565
  | `extraTopLevelRecords` | Raw `0 …` blocks re-emitted before `SUBM` / `TRLR` (round-trip with `readGed.preservedTopLevelRecords`). |
566
+ | `objeRecordsById` | Full `OBJE` records (multi-`FILE`, `MEDI AUDIO`, `TRAN`) from `readGed.objeRecordsById`. |
502
567
  | `headLanguageTag` | BCP 47 tag for `HEAD`.`LANG` (defaults to `en-US`). |
503
568
  | `headCopyright` | One-line `1 COPR` notice. |
504
569
  | `headDestination` | Value of `HEAD`.`DEST` (target app / URI). |
@@ -644,7 +709,7 @@ const payload = place.toGedcom7PlacPayload(); // "Paris, , Île-de-France, Franc
644
709
 
645
710
  #### `MultimediaFile`, `MultimediaFiles`
646
711
 
647
- Wraps either a local `File`, an external URI (`sourceUri`) or an OBJE pointer (`objeXrefId`) so the same model can survive a GED / ZIP round-trip.
712
+ Wraps either a local `File`, an external URI (`sourceUri`) or an OBJE pointer (`objeXrefId`) so the same model can survive a GED / ZIP round-trip. See [Audio and oral history](#audio-and-oral-history-gedcom-7) for interview workflows.
648
713
 
649
714
  ```ts
650
715
  import { MultimediaFile, MultimediaFiles } from "gedcom-ts";
@@ -719,7 +784,8 @@ editDateAct(person.acts.list[0].dateAct!).setExactDate(1900, "JAN", 1);
719
784
  | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
720
785
  | `editPerson(person)` / `PersonEdit` | Identity, `FAMS` / `FAMC` / `removeFams*`, `nameVariants()` / `attributes()` / `multimedia()`, bulk `clear*`, entry points to `acts()` and `notes()`. |
721
786
  | `editActs(acts)` / `ActsEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeAct`, `removeLast`, `clear`, `sortByDate`, `at`, `indexOfAct`. |
722
- | `editAct(act)` / `ActEdit` | `setType`, `setIndis`, dates, place, EVEN fields, preserved lines, `notes()`, `multimedia()`, `clearNotes`, `clearMultimedia`. |
787
+ | `editAct(act)` / `ActEdit` | `setType`, `setIndis`, dates, place, EVEN fields, preserved lines, `notes()`, `multimedia()`, `asAudioInterview()`, `clearNotes`, `clearMultimedia`. |
788
+ | `editAudioInterviewAct(act)` / `AudioInterviewActEdit` | Oral-history facade: `attachAudio`, `setTranscription`, `setDescription`, `registerPendingObje`, `isAudioInterview`, … |
723
789
  | `editDateAct(dateAct)` / `DateActEdit` | `clear`, `applyGedcomPayload`, `setExactDate`, `setQualified`, `setBetween`, `setFromTo`, `setTime`, `setDatePhrase` / `appendDatePhrase`, `setVerbatimPayload`. |
724
790
  | `editPlace(place)` / `PlaceEdit` | `setFromGedcom7Payload`, `setCity`, `setCounty`, `setState`, `setCountry`, `setPlacPhrase` / `appendPlacPhrase` / `clearPlacPhrase`, `setCoordinates` / `clearCoordinates` / `replaceCoordinateModel`, `clearStructured`. |
725
791
  | `editNotes(notes)` / `NotesEdit` | `add`, `addNew`, `insertAt`, `insertNewAt`, `replaceAt`, `removeAt`, `removeNote`, `removeLast`, `clear`, `at`, `indexOfNote`. |
@@ -785,7 +851,7 @@ validateReadGed(readGed, {
785
851
  | Export | Role |
786
852
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
787
853
  | `editReadGed(readGed)` / `ReadGedEdit` | `addPerson`, `removePersonByIndi`, `preserved()` → `PreservedTopLevelEdit` (`append`, `insertAt`, `replaceAt`, `removeAt`, `clear`) on `preservedTopLevelRecords`. Low-level helpers: `addPersonToReadGed`, `removePersonFromReadGedByIndi`. |
788
- | `editGedcomExportOptions(opts)` / `GedcomExportOptionsEdit` | Fluent setters for `extraTopLevelRecords`, `headLanguageTag`, `headCopyright`, `headDestination`, `headSchemaTagDefs`. |
854
+ | `editGedcomExportOptions(opts)` / `GedcomExportOptionsEdit` | Fluent setters for `extraTopLevelRecords`, `objeRecordsById`, `headLanguageTag`, `headCopyright`, `headDestination`, `headSchemaTagDefs`. |
789
855
  | `clonePerson(person, newIndi)` / `cloneAct(act)` / `person.clone` / `act.clone` | Deep copies for templates or undo stacks. |
790
856
  | `nextFamilyId(persons)` | Next internal family id `F`. |
791
857
  | `createMarriageFamily`, `linkChildToFamily`, `unlinkChildFromFamily`, `removeFamilyReferencesFromDataset` | Create family `F` and spouse acts; optional `{ eventTag }` (default `MARR`, also `ENGA`, banns, contract, `EVEN` + `CreateActInit`, …). See `GEDCOM_7_PAIR_UNION_EVENT_TAGS`. |
@@ -832,12 +898,13 @@ const version = resolveDatasetVersion(headLines); // "7.0" | "5.5" | "unknown"
832
898
 
833
899
  #### `guessMediaFormFromUri(uri)`
834
900
 
835
- Best-effort media type detection from a path/URI (jpg, png, gif, webp, mp3, mp4, pdf, …). Used internally to fill `OBJE`.`FILE`.`FORM`.
901
+ Best-effort media type detection from a path/URI or local `File` (`file.type` when set). Covers images, **audio** (mp3, **webm**, ogg, wav, m4a, flac, …), video, WebVTT, PDF. Used to fill `OBJE`.`FILE`.`FORM`. Pair with `guessMediFromForm` for `MEDI` (`AUDIO`, `PHOTO`, …). `refineGenericObjeForm` upgrades legacy `application/octet-stream` exports using the file extension.
836
902
 
837
903
  ```ts
838
- import { guessMediaFormFromUri } from "gedcom-ts";
904
+ import { guessMediaFormFromUri, guessMediaFormFromFile } from "gedcom-ts";
839
905
 
840
- guessMediaFormFromUri("https://example.com/photo.jpg"); // "image/jpeg"
906
+ guessMediaFormFromUri("media/recording.webm"); // "audio/webm"
907
+ guessMediaFormFromFile(new File(["x"], "take.webm", { type: "audio/webm" })); // "audio/webm"
841
908
  ```
842
909
 
843
910
  #### `extractPersonNameVariants(personLines)` / `extractIndiAttributes(personLines)`
@@ -863,7 +930,7 @@ const primary = selectPrimaryNameVariant(person.nameVariants);
863
930
 
864
931
  #### `GEDCOM_LIBRARY_VERSION`
865
932
 
866
- CalVer string written in exported `HEAD`.`SOUR`.`VERS` (same value as the npm package version, e.g. `2026.5.2`).
933
+ CalVer string written in exported `HEAD`.`SOUR`.`VERS` (same value as the npm package version, e.g. `2026.6.0`).
867
934
 
868
935
  ```ts
869
936
  import { GEDCOM_LIBRARY_VERSION } from "gedcom-ts";