@treeviz/gedcom-parser 2.1.16 → 2.1.18

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/dist/cli/index.js CHANGED
@@ -94,6 +94,30 @@ var mediaIdValue = (obje, path) => {
94
94
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
95
95
  };
96
96
  var resolveObjeMediaId = (obje) => mediaIdValue(obje, "RIN") || mediaIdValue(obje, "_CLON._OID") || mediaIdValue(obje, "_MSER._LKID") || mediaIdValue(obje, "_OID") || mediaIdValue(obje, "_LKID");
97
+ var ANCESTRY_TREE_MEDIA_NAMESPACE = 1093;
98
+ var ANCESTRY_NAMESPACE_IN_URL = /\/namespaces\/(\d+)\/media\//i;
99
+ var parseAncestryNamespaceFromUrl = (url) => {
100
+ if (!url) {
101
+ return void 0;
102
+ }
103
+ const match = url.match(ANCESTRY_NAMESPACE_IN_URL);
104
+ const namespace = match?.[1] ? Number(match[1]) : NaN;
105
+ return Number.isFinite(namespace) ? namespace : void 0;
106
+ };
107
+ var isAncestryOriginObje = (obje) => {
108
+ if (!obje) {
109
+ return false;
110
+ }
111
+ const orig = mediaIdValue(obje, "_ORIG");
112
+ if (orig?.toLowerCase() === "acom") {
113
+ return true;
114
+ }
115
+ const file = mediaIdValue(obje, "FILE") ?? "";
116
+ if (parseAncestryNamespaceFromUrl(file)) {
117
+ return true;
118
+ }
119
+ return Boolean(resolveObjeMediaId(obje) && !file.trim());
120
+ };
97
121
  var ANCESTRY_MEDIA_RETRIEVAL_PREFIX = "https://www.ancestry.com/api/media/retrieval/v2/image";
98
122
  var ANCESTRY_MEDIA_QUERY = "client=trees-mediaservice&imageQuality=hq";
99
123
  var ancestryMediaPath = (namespace, imgId) => `/namespaces/${namespace}/media/${imgId}?${ANCESTRY_MEDIA_QUERY}`;
@@ -887,8 +911,25 @@ var Common = class _Common {
887
911
  getHeadRecord() {
888
912
  return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
889
913
  }
914
+ unwrapHeadRecord(node) {
915
+ if (!node) {
916
+ return void 0;
917
+ }
918
+ const common = node instanceof List ? node.index(0) : node;
919
+ if (!common || typeof common.get !== "function") {
920
+ return void 0;
921
+ }
922
+ const nested = common.get("HEAD");
923
+ if (nested instanceof List) {
924
+ return nested.index(0) ?? common;
925
+ }
926
+ if (nested instanceof _Common) {
927
+ return nested;
928
+ }
929
+ return common;
930
+ }
890
931
  getOriginalHeadRecord() {
891
- return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
932
+ return this.unwrapHeadRecord(get(this, ORIG_HEAD_TAG)) || this.unwrapHeadRecord(get(this.getGedcom(), ORIG_HEAD_TAG)) || this.unwrapHeadRecord(get(this.getHeadRecord(), ORIG_HEAD_TAG));
892
933
  }
893
934
  getSourceHeads() {
894
935
  const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
@@ -896,9 +937,23 @@ var Common = class _Common {
896
937
  return Boolean(head && heads.indexOf(head) === index);
897
938
  });
898
939
  }
940
+ sourceHeadField(head, path) {
941
+ const normalized = path.endsWith(".value") ? path.slice(0, -".value".length) : path;
942
+ const node = head.get(normalized);
943
+ if (!node) {
944
+ return void 0;
945
+ }
946
+ if (node instanceof List) {
947
+ return node.index(0)?.toValue();
948
+ }
949
+ if (typeof node.toValue === "function") {
950
+ return node.toValue();
951
+ }
952
+ return void 0;
953
+ }
899
954
  getFromSourceHeads(path) {
900
955
  for (const head of this.getSourceHeads()) {
901
- const value = get(head, path);
956
+ const value = this.sourceHeadField(head, path);
902
957
  if (value !== void 0 && value !== null && value !== "") {
903
958
  return value;
904
959
  }
@@ -907,8 +962,8 @@ var Common = class _Common {
907
962
  }
908
963
  sourStartsWith(prefix) {
909
964
  return this.getSourceHeads().some((head) => {
910
- const sour = get(head, "SOUR.value");
911
- return !!sour?.toLowerCase()?.startsWith(prefix);
965
+ const sour = this.sourceHeadField(head, "SOUR");
966
+ return typeof sour === "string" && sour.toLowerCase().startsWith(prefix);
912
967
  });
913
968
  }
914
969
  isGenoPro() {
@@ -926,6 +981,51 @@ var Common = class _Common {
926
981
  isMyHeritage() {
927
982
  return this.sourStartsWith("myheritage");
928
983
  }
984
+ /**
985
+ * GEDCOM root for Ancestry media helpers.
986
+ * `this._gedcom` when called on a child record; `this` when called on the file root.
987
+ *
988
+ * Cannot use `instanceof GedCom`: `GedComType` is a type alias (erased at runtime),
989
+ * and importing the `GedCom` class here would cycle (`GedCom extends Common`).
990
+ * The root is the only Common with an `objes()` method.
991
+ */
992
+ hostGedcom() {
993
+ const linked = this.getGedcom();
994
+ if (linked) {
995
+ return linked;
996
+ }
997
+ if ("objes" in this && typeof this.objes === "function") {
998
+ return this;
999
+ }
1000
+ return void 0;
1001
+ }
1002
+ /** True when the file still has Ancestry tree media (`_ORIG acom`, `_OID`, retrieval URL). */
1003
+ hasAncestryOriginMedia() {
1004
+ const objes = this.hostGedcom()?.objes?.();
1005
+ return objes?.values().some((obje) => isAncestryOriginObje(obje)) ?? false;
1006
+ }
1007
+ /**
1008
+ * Namespace for Ancestry tree photos. Prefer a `namespaces/{id}` FILE URL
1009
+ * (TreeViz GEDCOM 7 / GEDZIP export), otherwise 1093 for member-tree media.
1010
+ */
1011
+ getAncestryMediaNamespace() {
1012
+ const objes = this.hostGedcom()?.objes?.();
1013
+ if (objes) {
1014
+ for (const obje of objes.values()) {
1015
+ const file = obje?.get("FILE")?.toValue();
1016
+ const fromUrl = parseAncestryNamespaceFromUrl(
1017
+ typeof file === "string" ? file : void 0
1018
+ );
1019
+ if (fromUrl) {
1020
+ return fromUrl;
1021
+ }
1022
+ }
1023
+ }
1024
+ if (this.isAncestry() || this.hasAncestryOriginMedia()) {
1025
+ return ANCESTRY_TREE_MEDIA_NAMESPACE;
1026
+ }
1027
+ return void 0;
1028
+ }
929
1029
  /**
930
1030
  * Get the source type as a string (for prefixing tree IDs and names)
931
1031
  * Returns the detected source type or undefined if unknown
@@ -942,13 +1042,13 @@ var Common = class _Common {
942
1042
  }
943
1043
  isFamilySearch() {
944
1044
  return this.getSourceHeads().some((head) => {
945
- const sourName = get(head, "SOUR.NAME.value");
1045
+ const sourName = this.sourceHeadField(head, "SOUR.NAME");
946
1046
  return sourName === "FamilySearch API";
947
1047
  });
948
1048
  }
949
1049
  isGNO2GED() {
950
1050
  return this.getSourceHeads().some((head) => {
951
- const sour = get(head, "SOUR.value");
1051
+ const sour = this.sourceHeadField(head, "SOUR");
952
1052
  return sour === "GNO2GED";
953
1053
  });
954
1054
  }
@@ -993,8 +1093,7 @@ var Common = class _Common {
993
1093
  return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
994
1094
  }
995
1095
  getMyHeritageTreeName() {
996
- const path = "HEAD.FILE.value";
997
- const treeDetails = get(this, path) || get(this.getGedcom(), path);
1096
+ const treeDetails = this.getFromSourceHeads("FILE.value") || (get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value"));
998
1097
  return treeDetails?.match(
999
1098
  /Exported by MyHeritage.com from (?<tree>.+) in.+$/
1000
1099
  )?.groups?.tree;
@@ -1011,7 +1110,7 @@ var Common = class _Common {
1011
1110
  if (treeName) {
1012
1111
  return treeName;
1013
1112
  }
1014
- const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
1113
+ const fileName = this.getFromSourceHeads("FILE.value") || get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
1015
1114
  return fileName || "FamilySearch Import";
1016
1115
  }
1017
1116
  getAhnenblattTreeId() {
@@ -2388,6 +2487,44 @@ var resolveLocalMediaUrl = async (path, context) => {
2388
2487
  };
2389
2488
  var implemented = (type, ...args) => {
2390
2489
  };
2490
+
2491
+ // src/utils/multimedia.ts
2492
+ var inferMediaForm = (file, fallback = "bin") => {
2493
+ const withoutQuery = file.split("?")[0] || file;
2494
+ const ext = getFileExtension(withoutQuery);
2495
+ if (ext) {
2496
+ return ext.toLowerCase() === "jpeg" ? "jpg" : ext.toLowerCase();
2497
+ }
2498
+ return fallback;
2499
+ };
2500
+ var mergeMediaLists = (...lists) => {
2501
+ const merged = {};
2502
+ for (const list of lists) {
2503
+ if (!list) continue;
2504
+ Object.assign(merged, list);
2505
+ }
2506
+ return merged;
2507
+ };
2508
+ var mergeMediaListsByObjeKey = (...lists) => {
2509
+ const merged = {};
2510
+ const seen = /* @__PURE__ */ new Set();
2511
+ for (const list of lists) {
2512
+ if (!list) {
2513
+ continue;
2514
+ }
2515
+ for (const [id, item] of Object.entries(list)) {
2516
+ const objeKey = item.key ? String(item.key) : "";
2517
+ if (objeKey && seen.has(objeKey)) {
2518
+ continue;
2519
+ }
2520
+ merged[id] = item;
2521
+ if (objeKey) {
2522
+ seen.add(objeKey);
2523
+ }
2524
+ }
2525
+ }
2526
+ return merged;
2527
+ };
2391
2528
  var uniqueItemsCache = /* @__PURE__ */ new WeakMap();
2392
2529
  var setNestedGroup = (obj, key, value, uniqueCounting = true) => {
2393
2530
  const parts = Array.isArray(key) ? key : key.split(/,\s*/);
@@ -3546,7 +3683,10 @@ var Indi = class extends Common {
3546
3683
  return result;
3547
3684
  }
3548
3685
  ancestryLink() {
3549
- const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3686
+ let www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3687
+ if (!www || /treeviz/i.test(String(www))) {
3688
+ www = "www.ancestry.com";
3689
+ }
3550
3690
  const tree = this.getAncestryTreeId();
3551
3691
  if (this.id) {
3552
3692
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -3558,14 +3698,15 @@ var Indi = class extends Common {
3558
3698
  async ancestryMedia(namespace) {
3559
3699
  const list = {};
3560
3700
  const objeList = this.get("OBJE")?.toList().copy();
3561
- const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3701
+ this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3562
3702
  const tree = this.getAncestryTreeId();
3703
+ const usedNamespace = namespace || this.getAncestryMediaNamespace() || ANCESTRY_TREE_MEDIA_NAMESPACE;
3563
3704
  if (objeList) {
3564
3705
  await Promise.all(
3565
3706
  objeList.map(async (objeRef) => {
3566
3707
  const key = objeXrefKey(objeRef, 0);
3567
3708
  const obje = objeRef?.standardizeMedia(
3568
- namespace,
3709
+ usedNamespace,
3569
3710
  true,
3570
3711
  ancestryMediaFileUrl
3571
3712
  );
@@ -3577,27 +3718,9 @@ var Indi = class extends Common {
3577
3718
  const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3578
3719
  let imgId = media;
3579
3720
  const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
3580
- if (!hasEmbeddedOrLocalFile && (!www || !tree || !this.id)) {
3581
- return;
3582
- }
3583
3721
  if (!this.id) {
3584
3722
  return;
3585
3723
  }
3586
- if (!namespace && !url) {
3587
- try {
3588
- if (media) {
3589
- const mediaDetailsResponse = await fetch(
3590
- `https://www.ancestry.com/api/media/viewer/v2/trees/${tree}/media?id=${media}`
3591
- );
3592
- const mediaDetails = await mediaDetailsResponse.json();
3593
- if (mediaDetails.url) {
3594
- url = `${mediaDetails.url}&imageQuality=hq`;
3595
- }
3596
- }
3597
- } catch (_e) {
3598
- }
3599
- url = url || `https://${www}/mediaui-viewer/tree/${tree}/media/${media}`;
3600
- }
3601
3724
  if (!imgId && hasEmbeddedOrLocalFile && url) {
3602
3725
  imgId = typeof media === "string" && media || `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}`;
3603
3726
  }
@@ -3623,9 +3746,12 @@ var Indi = class extends Common {
3623
3746
  return list;
3624
3747
  }
3625
3748
  myheritageLink(poolId = 0) {
3626
- const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
3749
+ let www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
3750
+ if (!www || /treeviz/i.test(www)) {
3751
+ www = "myheritage.com";
3752
+ }
3627
3753
  const site = this.getMyHeritageTreeId();
3628
- const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
3754
+ const file = (this.getFromSourceHeads("FILE.value") || this._gedcom?.HEAD?.get("FILE")?.toValue())?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
3629
3755
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
3630
3756
  if (normalizedFile && this.id) {
3631
3757
  const id = Number(this.id.replace(/@|I/g, "")) + poolId;
@@ -3846,16 +3972,26 @@ var Indi = class extends Common {
3846
3972
  return list;
3847
3973
  }
3848
3974
  async multimedia(namespace) {
3849
- if (this?.isAncestry()) {
3850
- return await this.ancestryMedia(namespace);
3975
+ const lists = [];
3976
+ const ownObjes = this.get("OBJE")?.toList()?.values() ?? [];
3977
+ const ownAncestryMedia = ownObjes.some(
3978
+ (obje) => !!obje && (isAncestryOriginObje(obje) || isAncestryOriginObje(obje.ref))
3979
+ );
3980
+ if (this.isAncestry() || ownAncestryMedia) {
3981
+ lists.push(
3982
+ await this.ancestryMedia(
3983
+ namespace || this.getAncestryMediaNamespace()
3984
+ )
3985
+ );
3851
3986
  }
3852
3987
  if (this?.isMyHeritage()) {
3853
- return this.myheritageMedia();
3988
+ lists.push(this.myheritageMedia());
3854
3989
  }
3855
3990
  if (this?.isGeni()) {
3856
- return this.geniMedia();
3991
+ lists.push(this.geniMedia());
3857
3992
  }
3858
- return this.universalMedia();
3993
+ lists.push(this.universalMedia());
3994
+ return mergeMediaListsByObjeKey(...lists);
3859
3995
  }
3860
3996
  async getProfilePicture(namespace, onlyPrimary = true) {
3861
3997
  if (!this.id) {
@@ -9024,7 +9160,7 @@ var ensureExtensionSchema = (gedcom, extraTags) => {
9024
9160
 
9025
9161
  // package.json
9026
9162
  var package_default = {
9027
- version: "2.1.16"};
9163
+ version: "2.1.18"};
9028
9164
 
9029
9165
  // src/utils/get-product-details.ts
9030
9166
  var isDevelopment = () => {
@@ -9032,24 +9168,6 @@ var isDevelopment = () => {
9032
9168
  };
9033
9169
  var getVersion = () => package_default.version;
9034
9170
 
9035
- // src/utils/multimedia.ts
9036
- var inferMediaForm = (file, fallback = "bin") => {
9037
- const withoutQuery = file.split("?")[0] || file;
9038
- const ext = getFileExtension(withoutQuery);
9039
- if (ext) {
9040
- return ext.toLowerCase() === "jpeg" ? "jpg" : ext.toLowerCase();
9041
- }
9042
- return fallback;
9043
- };
9044
- var mergeMediaLists = (...lists) => {
9045
- const merged = {};
9046
- for (const list of lists) {
9047
- if (!list) continue;
9048
- Object.assign(merged, list);
9049
- }
9050
- return merged;
9051
- };
9052
-
9053
9171
  // src/classes/note.ts
9054
9172
  var CommonNote = class extends Common {
9055
9173
  constructor(gedcom, id, main, parent) {
@@ -9200,6 +9318,10 @@ var Obje = class extends Common {
9200
9318
  if (lkidNode && !newObject.get("_LKID")) {
9201
9319
  newObject.set("_LKID", lkidNode);
9202
9320
  }
9321
+ const origNode = this.get("_ORIG");
9322
+ if (origNode && !newObject.get("_ORIG")) {
9323
+ newObject.set("_ORIG", origNode);
9324
+ }
9203
9325
  if (override) {
9204
9326
  this.remove("FILE");
9205
9327
  this.remove("FORM");
@@ -409,6 +409,30 @@ var mediaIdValue = (obje, path) => {
409
409
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
410
410
  };
411
411
  var resolveObjeMediaId = (obje) => mediaIdValue(obje, "RIN") || mediaIdValue(obje, "_CLON._OID") || mediaIdValue(obje, "_MSER._LKID") || mediaIdValue(obje, "_OID") || mediaIdValue(obje, "_LKID");
412
+ var ANCESTRY_TREE_MEDIA_NAMESPACE = 1093;
413
+ var ANCESTRY_NAMESPACE_IN_URL = /\/namespaces\/(\d+)\/media\//i;
414
+ var parseAncestryNamespaceFromUrl = (url) => {
415
+ if (!url) {
416
+ return void 0;
417
+ }
418
+ const match = url.match(ANCESTRY_NAMESPACE_IN_URL);
419
+ const namespace = match?.[1] ? Number(match[1]) : NaN;
420
+ return Number.isFinite(namespace) ? namespace : void 0;
421
+ };
422
+ var isAncestryOriginObje = (obje) => {
423
+ if (!obje) {
424
+ return false;
425
+ }
426
+ const orig = mediaIdValue(obje, "_ORIG");
427
+ if (orig?.toLowerCase() === "acom") {
428
+ return true;
429
+ }
430
+ const file = mediaIdValue(obje, "FILE") ?? "";
431
+ if (parseAncestryNamespaceFromUrl(file)) {
432
+ return true;
433
+ }
434
+ return Boolean(resolveObjeMediaId(obje) && !file.trim());
435
+ };
412
436
  var ANCESTRY_MEDIA_RETRIEVAL_PREFIX = "https://www.ancestry.com/api/media/retrieval/v2/image";
413
437
  var ANCESTRY_MEDIA_QUERY = "client=trees-mediaservice&imageQuality=hq";
414
438
  var ancestryMediaPath = (namespace, imgId) => `/namespaces/${namespace}/media/${imgId}?${ANCESTRY_MEDIA_QUERY}`;
@@ -784,8 +808,25 @@ var Common = class _Common {
784
808
  getHeadRecord() {
785
809
  return get(this, "HEAD") || get(this.getGedcom(), "HEAD");
786
810
  }
811
+ unwrapHeadRecord(node) {
812
+ if (!node) {
813
+ return void 0;
814
+ }
815
+ const common = node instanceof List ? node.index(0) : node;
816
+ if (!common || typeof common.get !== "function") {
817
+ return void 0;
818
+ }
819
+ const nested = common.get("HEAD");
820
+ if (nested instanceof List) {
821
+ return nested.index(0) ?? common;
822
+ }
823
+ if (nested instanceof _Common) {
824
+ return nested;
825
+ }
826
+ return common;
827
+ }
787
828
  getOriginalHeadRecord() {
788
- return get(this, ORIG_HEAD_TAG) || get(this.getGedcom(), ORIG_HEAD_TAG) || get(this.getHeadRecord(), ORIG_HEAD_TAG);
829
+ return this.unwrapHeadRecord(get(this, ORIG_HEAD_TAG)) || this.unwrapHeadRecord(get(this.getGedcom(), ORIG_HEAD_TAG)) || this.unwrapHeadRecord(get(this.getHeadRecord(), ORIG_HEAD_TAG));
789
830
  }
790
831
  getSourceHeads() {
791
832
  const heads = [this.getOriginalHeadRecord(), this.getHeadRecord()];
@@ -793,9 +834,23 @@ var Common = class _Common {
793
834
  return Boolean(head && heads.indexOf(head) === index);
794
835
  });
795
836
  }
837
+ sourceHeadField(head, path) {
838
+ const normalized = path.endsWith(".value") ? path.slice(0, -".value".length) : path;
839
+ const node = head.get(normalized);
840
+ if (!node) {
841
+ return void 0;
842
+ }
843
+ if (node instanceof List) {
844
+ return node.index(0)?.toValue();
845
+ }
846
+ if (typeof node.toValue === "function") {
847
+ return node.toValue();
848
+ }
849
+ return void 0;
850
+ }
796
851
  getFromSourceHeads(path) {
797
852
  for (const head of this.getSourceHeads()) {
798
- const value = get(head, path);
853
+ const value = this.sourceHeadField(head, path);
799
854
  if (value !== void 0 && value !== null && value !== "") {
800
855
  return value;
801
856
  }
@@ -804,8 +859,8 @@ var Common = class _Common {
804
859
  }
805
860
  sourStartsWith(prefix) {
806
861
  return this.getSourceHeads().some((head) => {
807
- const sour = get(head, "SOUR.value");
808
- return !!sour?.toLowerCase()?.startsWith(prefix);
862
+ const sour = this.sourceHeadField(head, "SOUR");
863
+ return typeof sour === "string" && sour.toLowerCase().startsWith(prefix);
809
864
  });
810
865
  }
811
866
  isGenoPro() {
@@ -823,6 +878,51 @@ var Common = class _Common {
823
878
  isMyHeritage() {
824
879
  return this.sourStartsWith("myheritage");
825
880
  }
881
+ /**
882
+ * GEDCOM root for Ancestry media helpers.
883
+ * `this._gedcom` when called on a child record; `this` when called on the file root.
884
+ *
885
+ * Cannot use `instanceof GedCom`: `GedComType` is a type alias (erased at runtime),
886
+ * and importing the `GedCom` class here would cycle (`GedCom extends Common`).
887
+ * The root is the only Common with an `objes()` method.
888
+ */
889
+ hostGedcom() {
890
+ const linked = this.getGedcom();
891
+ if (linked) {
892
+ return linked;
893
+ }
894
+ if ("objes" in this && typeof this.objes === "function") {
895
+ return this;
896
+ }
897
+ return void 0;
898
+ }
899
+ /** True when the file still has Ancestry tree media (`_ORIG acom`, `_OID`, retrieval URL). */
900
+ hasAncestryOriginMedia() {
901
+ const objes = this.hostGedcom()?.objes?.();
902
+ return objes?.values().some((obje) => isAncestryOriginObje(obje)) ?? false;
903
+ }
904
+ /**
905
+ * Namespace for Ancestry tree photos. Prefer a `namespaces/{id}` FILE URL
906
+ * (TreeViz GEDCOM 7 / GEDZIP export), otherwise 1093 for member-tree media.
907
+ */
908
+ getAncestryMediaNamespace() {
909
+ const objes = this.hostGedcom()?.objes?.();
910
+ if (objes) {
911
+ for (const obje of objes.values()) {
912
+ const file = obje?.get("FILE")?.toValue();
913
+ const fromUrl = parseAncestryNamespaceFromUrl(
914
+ typeof file === "string" ? file : void 0
915
+ );
916
+ if (fromUrl) {
917
+ return fromUrl;
918
+ }
919
+ }
920
+ }
921
+ if (this.isAncestry() || this.hasAncestryOriginMedia()) {
922
+ return ANCESTRY_TREE_MEDIA_NAMESPACE;
923
+ }
924
+ return void 0;
925
+ }
826
926
  /**
827
927
  * Get the source type as a string (for prefixing tree IDs and names)
828
928
  * Returns the detected source type or undefined if unknown
@@ -839,13 +939,13 @@ var Common = class _Common {
839
939
  }
840
940
  isFamilySearch() {
841
941
  return this.getSourceHeads().some((head) => {
842
- const sourName = get(head, "SOUR.NAME.value");
942
+ const sourName = this.sourceHeadField(head, "SOUR.NAME");
843
943
  return sourName === "FamilySearch API";
844
944
  });
845
945
  }
846
946
  isGNO2GED() {
847
947
  return this.getSourceHeads().some((head) => {
848
- const sour = get(head, "SOUR.value");
948
+ const sour = this.sourceHeadField(head, "SOUR");
849
949
  return sour === "GNO2GED";
850
950
  });
851
951
  }
@@ -890,8 +990,7 @@ var Common = class _Common {
890
990
  return this.getFromSourceHeads("SOUR._TREE.value") || (get(this, "HEAD.SOUR._TREE.value") || get(this.getGedcom(), "HEAD.SOUR._TREE.value"));
891
991
  }
892
992
  getMyHeritageTreeName() {
893
- const path = "HEAD.FILE.value";
894
- const treeDetails = get(this, path) || get(this.getGedcom(), path);
993
+ const treeDetails = this.getFromSourceHeads("FILE.value") || (get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value"));
895
994
  return treeDetails?.match(
896
995
  /Exported by MyHeritage.com from (?<tree>.+) in.+$/
897
996
  )?.groups?.tree;
@@ -908,7 +1007,7 @@ var Common = class _Common {
908
1007
  if (treeName) {
909
1008
  return treeName;
910
1009
  }
911
- const fileName = get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
1010
+ const fileName = this.getFromSourceHeads("FILE.value") || get(this, "HEAD.FILE.value") || get(this.getGedcom(), "HEAD.FILE.value");
912
1011
  return fileName || "FamilySearch Import";
913
1012
  }
914
1013
  getAhnenblattTreeId() {
@@ -2273,6 +2372,28 @@ var resolveLocalMediaUrl = async (path, context) => {
2273
2372
  };
2274
2373
  var implemented = (type, ...args) => {
2275
2374
  };
2375
+
2376
+ // src/utils/multimedia.ts
2377
+ var mergeMediaListsByObjeKey = (...lists) => {
2378
+ const merged = {};
2379
+ const seen = /* @__PURE__ */ new Set();
2380
+ for (const list of lists) {
2381
+ if (!list) {
2382
+ continue;
2383
+ }
2384
+ for (const [id, item] of Object.entries(list)) {
2385
+ const objeKey = item.key ? String(item.key) : "";
2386
+ if (objeKey && seen.has(objeKey)) {
2387
+ continue;
2388
+ }
2389
+ merged[id] = item;
2390
+ if (objeKey) {
2391
+ seen.add(objeKey);
2392
+ }
2393
+ }
2394
+ }
2395
+ return merged;
2396
+ };
2276
2397
  var uniqueItemsCache = /* @__PURE__ */ new WeakMap();
2277
2398
  var setNestedGroup = (obj, key, value, uniqueCounting = true) => {
2278
2399
  const parts = Array.isArray(key) ? key : key.split(/,\s*/);
@@ -3532,7 +3653,10 @@ var Indi = class extends Common {
3532
3653
  return result;
3533
3654
  }
3534
3655
  ancestryLink() {
3535
- const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3656
+ let www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3657
+ if (!www || /treeviz/i.test(String(www))) {
3658
+ www = "www.ancestry.com";
3659
+ }
3536
3660
  const tree = this.getAncestryTreeId();
3537
3661
  if (this.id) {
3538
3662
  return `https://${www}/family-tree/person/tree/${tree}/person/${this.id.replace(
@@ -3544,14 +3668,15 @@ var Indi = class extends Common {
3544
3668
  async ancestryMedia(namespace) {
3545
3669
  const list = {};
3546
3670
  const objeList = this.get("OBJE")?.toList().copy();
3547
- const www = this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3671
+ this.getFromSourceHeads("SOUR.CORP.WWW.value") || this._gedcom?.HEAD?.SOUR?.CORP?.WWW?.value;
3548
3672
  const tree = this.getAncestryTreeId();
3673
+ const usedNamespace = namespace || this.getAncestryMediaNamespace() || ANCESTRY_TREE_MEDIA_NAMESPACE;
3549
3674
  if (objeList) {
3550
3675
  await Promise.all(
3551
3676
  objeList.map(async (objeRef) => {
3552
3677
  const key = objeXrefKey(objeRef, 0);
3553
3678
  const obje = objeRef?.standardizeMedia(
3554
- namespace,
3679
+ usedNamespace,
3555
3680
  true,
3556
3681
  ancestryMediaFileUrl
3557
3682
  );
@@ -3563,27 +3688,9 @@ var Indi = class extends Common {
3563
3688
  const type = resolveObjeForm(obje) ?? (url ? getFileExtension(url) : void 0) ?? "raw";
3564
3689
  let imgId = media;
3565
3690
  const hasEmbeddedOrLocalFile = !!url && (url.startsWith("data:") || url.startsWith("blob:") || !/^https?:\/\//i.test(url));
3566
- if (!hasEmbeddedOrLocalFile && (!www || !tree || !this.id)) {
3567
- return;
3568
- }
3569
3691
  if (!this.id) {
3570
3692
  return;
3571
3693
  }
3572
- if (!namespace && !url) {
3573
- try {
3574
- if (media) {
3575
- const mediaDetailsResponse = await fetch(
3576
- `https://www.ancestry.com/api/media/viewer/v2/trees/${tree}/media?id=${media}`
3577
- );
3578
- const mediaDetails = await mediaDetailsResponse.json();
3579
- if (mediaDetails.url) {
3580
- url = `${mediaDetails.url}&imageQuality=hq`;
3581
- }
3582
- }
3583
- } catch (_e) {
3584
- }
3585
- url = url || `https://${www}/mediaui-viewer/tree/${tree}/media/${media}`;
3586
- }
3587
3694
  if (!imgId && hasEmbeddedOrLocalFile && url) {
3588
3695
  imgId = typeof media === "string" && media || `local-${String(key).replace(/[@]/g, "")}-${getFileExtension(url) || "bin"}`;
3589
3696
  }
@@ -3609,9 +3716,12 @@ var Indi = class extends Common {
3609
3716
  return list;
3610
3717
  }
3611
3718
  myheritageLink(poolId = 0) {
3612
- const www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
3719
+ let www = (this.getFromSourceHeads("SOUR.CORP.value") || this._gedcom?.HEAD?.SOUR?.CORP?.value)?.toLowerCase().replace(/^www\./gi, "");
3720
+ if (!www || /treeviz/i.test(www)) {
3721
+ www = "myheritage.com";
3722
+ }
3613
3723
  const site = this.getMyHeritageTreeId();
3614
- const file = this._gedcom?.HEAD?.get("FILE")?.toValue()?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
3724
+ const file = (this.getFromSourceHeads("FILE.value") || this._gedcom?.HEAD?.get("FILE")?.toValue())?.match(/Exported by MyHeritage.com from .+ in (?<site>.+) on .+$/)?.groups?.site;
3615
3725
  const normalizedFile = file?.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
3616
3726
  if (normalizedFile && this.id) {
3617
3727
  const id = Number(this.id.replace(/@|I/g, "")) + poolId;
@@ -3832,16 +3942,26 @@ var Indi = class extends Common {
3832
3942
  return list;
3833
3943
  }
3834
3944
  async multimedia(namespace) {
3835
- if (this?.isAncestry()) {
3836
- return await this.ancestryMedia(namespace);
3945
+ const lists = [];
3946
+ const ownObjes = this.get("OBJE")?.toList()?.values() ?? [];
3947
+ const ownAncestryMedia = ownObjes.some(
3948
+ (obje) => !!obje && (isAncestryOriginObje(obje) || isAncestryOriginObje(obje.ref))
3949
+ );
3950
+ if (this.isAncestry() || ownAncestryMedia) {
3951
+ lists.push(
3952
+ await this.ancestryMedia(
3953
+ namespace || this.getAncestryMediaNamespace()
3954
+ )
3955
+ );
3837
3956
  }
3838
3957
  if (this?.isMyHeritage()) {
3839
- return this.myheritageMedia();
3958
+ lists.push(this.myheritageMedia());
3840
3959
  }
3841
3960
  if (this?.isGeni()) {
3842
- return this.geniMedia();
3961
+ lists.push(this.geniMedia());
3843
3962
  }
3844
- return this.universalMedia();
3963
+ lists.push(this.universalMedia());
3964
+ return mergeMediaListsByObjeKey(...lists);
3845
3965
  }
3846
3966
  async getProfilePicture(namespace, onlyPrimary = true) {
3847
3967
  if (!this.id) {