waha-shared 1.0.306 → 1.0.307

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.
Files changed (102) hide show
  1. package/dist/data/areas/areas.json +50 -48
  2. package/dist/data/areas/areas.schema.json +2 -2
  3. package/dist/data/areas/areas.zod.d.ts +1 -1
  4. package/dist/data/areas/areas.zod.js +1 -1
  5. package/dist/data/areas/index.d.ts +1 -1
  6. package/dist/data/bibleAudios/bibleAudios.json +581 -314
  7. package/dist/data/bibleAudios/bibleAudios.schema.json +17 -8
  8. package/dist/data/bibleAudios/bibleAudios.zod.d.ts +43 -2
  9. package/dist/data/bibleAudios/bibleAudios.zod.js +16 -7
  10. package/dist/data/bibleAudios/index.d.ts +2 -1
  11. package/dist/data/bibleBooks/bibleBooks.json +792 -790
  12. package/dist/data/bibleStatuses/bibleStatuses.json +7981 -8480
  13. package/dist/data/bibleStatuses/bibleStatuses.schema.json +6 -0
  14. package/dist/data/bibleStatuses/bibleStatuses.zod.d.ts +1 -0
  15. package/dist/data/bibleStatuses/bibleStatuses.zod.js +4 -0
  16. package/dist/data/bibleStatuses/index.d.ts +1 -0
  17. package/dist/data/bibleTexts/bibleTexts.json +269 -184
  18. package/dist/data/bibleTexts/bibleTexts.schema.json +7 -0
  19. package/dist/data/bibleTexts/bibleTexts.zod.d.ts +28 -0
  20. package/dist/data/bibleTexts/bibleTexts.zod.js +10 -3
  21. package/dist/data/bibleTexts/index.d.ts +2 -0
  22. package/dist/data/clones/clones.json +2 -0
  23. package/dist/data/countries/countries.json +480 -480
  24. package/dist/data/countriesAndLanguages/countriesAndLanguages.json +6812 -6810
  25. package/dist/data/crowdinLanguages/crowdinLanguages.json +1440 -1438
  26. package/dist/data/crowdinLanguages/crowdinLanguages.schema.json +3 -3
  27. package/dist/data/crowdinLanguages/crowdinLanguages.zod.d.ts +2 -2
  28. package/dist/data/crowdinLanguages/crowdinLanguages.zod.js +2 -2
  29. package/dist/data/crowdinLanguages/index.d.ts +2 -2
  30. package/dist/data/curriculumFoundations/curriculumFoundations.json +86 -69
  31. package/dist/data/curriculumQuestions/curriculumQuestions.json +194 -192
  32. package/dist/data/curriculumTopics/curriculumTopics.json +51 -8
  33. package/dist/data/dblAudioLicenses/dblAudioLicenses.json +36 -7
  34. package/dist/data/dblTextLicenses/dblTextLicenses.json +4 -4
  35. package/dist/data/firebase.d.ts +1 -0
  36. package/dist/data/firebase.js +4 -0
  37. package/dist/data/iosVoiceOverLanguages/iosVoiceOverLanguages.json +1 -1
  38. package/dist/data/iso6933LanguageCodes/iso6933LanguageCodes.json +1 -1
  39. package/dist/data/languages/index.d.ts +20 -10
  40. package/dist/data/languages/languages.json +2983 -2008
  41. package/dist/data/languages/languages.schema.json +93 -47
  42. package/dist/data/languages/languages.zod.d.ts +93 -30
  43. package/dist/data/languages/languages.zod.js +59 -37
  44. package/dist/data/lessonPauses/index.d.ts +0 -1
  45. package/dist/data/lessonPauses/lessonPauses.json +2 -3
  46. package/dist/data/lessonPauses/lessonPauses.schema.json +1 -11
  47. package/dist/data/lessonPauses/lessonPauses.zod.d.ts +0 -1
  48. package/dist/data/lessonPauses/lessonPauses.zod.js +0 -3
  49. package/dist/data/mediaDurations/mediaDurations.json +35291 -6076
  50. package/dist/data/mediaDurations/mediaDurations.schema.json +1 -1
  51. package/dist/data/mediaDurations/mediaDurations.zod.js +1 -1
  52. package/dist/data/notification/index.d.ts +1 -1
  53. package/dist/data/notification/notification.json +93 -169
  54. package/dist/data/phoneLanguages/phoneLanguages.json +637 -635
  55. package/dist/data/questions/questions.json +149 -147
  56. package/dist/data/releaseNotes/releaseNotes.json +126 -124
  57. package/dist/data/screenshots/screenshots.json +1 -1
  58. package/dist/data/scripts/scripts.json +6 -1
  59. package/dist/data/sets/index.d.ts +3 -3
  60. package/dist/data/sets/sets.json +1770 -480
  61. package/dist/data/sets/sets.schema.json +1 -1
  62. package/dist/data/sets/sets.zod.d.ts +3 -3
  63. package/dist/data/sets/sets.zod.js +3 -6
  64. package/dist/data/translationsApp/index.d.ts +1 -0
  65. package/dist/data/translationsApp/translationsApp.json +1776 -1568
  66. package/dist/data/translationsApp/translationsApp.schema.json +1 -0
  67. package/dist/data/translationsApp/translationsApp.zod.d.ts +2 -0
  68. package/dist/data/translationsApp/translationsApp.zod.js +1 -0
  69. package/dist/data/translationsFtb/translationsFtb.json +120 -664
  70. package/dist/data/translationsIntroduction/translationsIntroduction.json +1 -1
  71. package/dist/data/translationsQuestion/translationsQuestion.json +65 -33
  72. package/dist/data/translationsSet/translationsSet.json +30155 -19418
  73. package/dist/data/translationsSet/translationsSet.schema.json +3 -4
  74. package/dist/data/translationsSet/translationsSet.zod.js +4 -4
  75. package/dist/data/translationsSolarSpeaker/translationsSolarSpeaker.json +1 -1
  76. package/dist/data/translationsSpokenQuestion/translationsSpokenQuestion.json +65 -33
  77. package/dist/data/youtubePlaylists/youtubePlaylists.json +10 -10
  78. package/dist/data/youtubeVideos/youtubeVideos.json +82 -82
  79. package/dist/functions/bibleBooks.d.ts +16 -0
  80. package/dist/functions/bibleBooks.js +101 -0
  81. package/dist/functions/bibles.d.ts +44 -0
  82. package/dist/functions/bibles.js +291 -0
  83. package/dist/functions/languages.d.ts +6 -7
  84. package/dist/functions/languages.js +51 -53
  85. package/dist/functions/scripturePassages.d.ts +34 -49
  86. package/dist/functions/scripturePassages.js +167 -304
  87. package/dist/functions/sets.d.ts +13 -52
  88. package/dist/functions/sets.js +193 -205
  89. package/dist/functions/utils.d.ts +2 -0
  90. package/dist/functions/utils.js +8 -0
  91. package/dist/types/bibleChapters.d.ts +2 -2
  92. package/dist/types/bibleChapters.js +1 -1
  93. package/dist/types/languages.d.ts +39 -9
  94. package/dist/types/languages.js +2 -0
  95. package/dist/types/sets.d.ts +21 -11
  96. package/package.json +1 -1
  97. package/dist/data/languageAssets/index.d.ts +0 -1
  98. package/dist/data/languageAssets/index.js +0 -7
  99. package/dist/data/languageAssets/languageAssets.json +0 -45406
  100. package/dist/data/languageAssets/languageAssets.schema.json +0 -19
  101. package/dist/data/languageAssets/languageAssets.zod.d.ts +0 -3
  102. package/dist/data/languageAssets/languageAssets.zod.js +0 -7
@@ -1,61 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseVerseRange = parseVerseRange;
3
+ exports.parseVerseRange = void 0;
4
4
  exports.getChapterUrl = getChapterUrl;
5
5
  exports.getPassagesString = getPassagesString;
6
6
  exports.getScripturePassage = getScripturePassage;
7
7
  exports.getLessonScripture = getLessonScripture;
8
- exports.parseBibleSnapshot = parseBibleSnapshot;
9
8
  exports.verseToSuperscript = verseToSuperscript;
10
- exports.getLessonPauses = getLessonPauses;
9
+ exports.getFtbDuration = getFtbDuration;
10
+ exports.enrichSections = enrichSections;
11
11
  exports.normalizeVerseTimings = normalizeVerseTimings;
12
12
  const bibleStatuses_1 = require("../data/bibleStatuses");
13
- const lessonPauses_1 = require("../data/lessonPauses");
13
+ const firebase_1 = require("../data/firebase");
14
14
  const mediaDurations_1 = require("../data/mediaDurations");
15
- const specialIds_1 = require("../data/specialIds");
16
15
  const languages_1 = require("../functions/languages");
17
- const bibleChapters_1 = require("../types/bibleChapters");
18
- const sets_1 = require("./sets");
19
- /**
20
- * Parses a verse range string like "GEN.1.1-GEN.1.25" or "JHN.3.16" or "REV.22"
21
- * Returns the start and end verse IDs
22
- */
23
- function parseVerseRange(passageId) {
24
- // Handle single verse: "GEN.1.1" -> "GEN.1.1-GEN.1.1"
25
- const parts = passageId.includes('-')
26
- ? passageId.split('-')
27
- : [passageId, passageId];
28
- const startParts = parts[0].split('.');
29
- const endParts = parts[1].split('.');
30
- const startBook = startParts[0];
31
- const startChapter = startParts[1];
32
- const startVerse = startParts.length > 2 ? startParts[2] : '1';
33
- const endBook = endParts[0];
34
- const endChapter = endParts[1];
35
- const endVerse = endParts.length > 2 ? endParts[2] : '1';
36
- return {
37
- startBook,
38
- startChapter,
39
- startVerse,
40
- endBook,
41
- endChapter,
42
- endVerse,
43
- startChapterId: `${startBook}.${startChapter}`,
44
- endChapterId: `${endBook}.${endChapter}`,
45
- };
46
- }
16
+ const sets_1 = require("../types/sets");
17
+ const bibleBooks_1 = require("./bibleBooks");
18
+ const bibles_1 = require("./bibles");
19
+ var bibleBooks_2 = require("./bibleBooks");
20
+ Object.defineProperty(exports, "parseVerseRange", { enumerable: true, get: function () { return bibleBooks_2.parseVerseRange; } });
47
21
  function getChapterUrl(params) {
48
- const startBook = 'startBook' in params
49
- ? params.startBook
50
- : parseVerseRange(params.passageId).startBook;
51
- const startChapter = 'startChapter' in params
52
- ? params.startChapter
53
- : parseVerseRange(params.passageId).startChapter;
54
- return (sets_1.firebaseUrl +
22
+ const book = 'book' in params ? params.book : (0, bibleBooks_1.parseVerseRange)(params.passageId).book;
23
+ const chapter = 'chapter' in params
24
+ ? params.chapter
25
+ : (0, bibleBooks_1.parseVerseRange)(params.passageId).chapter;
26
+ return (firebase_1.firebaseUrl +
55
27
  encodeURIComponent('audio_bibles' +
56
28
  `/${params.bibleAudioId}` +
57
- `/${startBook}` +
58
- `/${startBook}_${startChapter.padStart(3, '0')}.mp3`) +
29
+ `/${book}` +
30
+ `/${book}_${chapter.padStart(3, '0')}.mp3`) +
59
31
  `?alt=media`);
60
32
  }
61
33
  /** Extracts verses from a chapter based on verse numbers */
@@ -85,170 +57,90 @@ function extractVersesFromChapter(chapter, startVerse, endVerse) {
85
57
  * ["GEN.1.1-GEN.1.25", "GEN.2.4-GEN.2.7", "EXO.3.3-EXO.3.25"] -> "Genesis 1: 1-25, 2: 4-7, Exodus 3: 3-25"
86
58
  * ["JHN.1.1"] -> "John 1: 1"
87
59
  */
88
- function getPassagesString(passageIds, language) {
89
- if (!passageIds || passageIds.length === 0)
90
- return '';
60
+ function getPassagesString(passages) {
91
61
  const parts = [];
92
62
  let currentBook = null;
93
- for (const passageId of passageIds) {
94
- const { startBook, startChapter, startVerse, endChapter, endVerse } = parseVerseRange(passageId);
95
- let bookName =
96
- // American sign language should use English book names.
97
- language.languageId === 'ase'
98
- ? bibleStatuses_1.bibleStatuses.NLT.bookNames[startBook]
99
- : language.bible.bookNames[startBook];
100
- if (!bookName) {
101
- bookName = language.bibleFallback?.bookNames[startBook];
102
- if (!bookName)
103
- continue;
104
- }
105
- // New book - include book name
106
- if (currentBook !== startBook) {
107
- currentBook = startBook;
108
- // Same chapter
109
- if (startChapter === endChapter) {
110
- if (startVerse === endVerse) {
111
- parts.push(`${bookName} ${startChapter}: ${startVerse}`);
112
- }
113
- else {
114
- parts.push(`${bookName} ${startChapter}: ${startVerse}-${endVerse}`);
115
- }
116
- }
117
- else {
118
- // Different chapters
119
- parts.push(`${bookName} ${startChapter}: ${startVerse}-${endChapter}: ${endVerse}`);
120
- }
63
+ for (const passage of passages) {
64
+ const bibleInfo = (0, bibles_1.getBibleInfo)(passage.bibleId);
65
+ if (!bibleInfo)
66
+ continue;
67
+ const { book, chapter, startVerse, endVerse } = (0, bibleBooks_1.parseVerseRange)(passage.passageId);
68
+ const bookName = bibleInfo.bookNames[book];
69
+ if (!bookName)
70
+ continue;
71
+ const verseRange = startVerse === endVerse ? startVerse : `${startVerse}-${endVerse}`;
72
+ if (currentBook !== book) {
73
+ currentBook = book;
74
+ parts.push((0, languages_1.numerals)(`${bookName} ${chapter}: ${verseRange}`, bibleInfo.language.script));
121
75
  }
122
76
  else {
123
- // Same book - omit book name
124
- if (startChapter === endChapter) {
125
- if (startVerse === endVerse) {
126
- parts.push(`${startChapter}: ${startVerse}`);
127
- }
128
- else {
129
- parts.push(`${startChapter}: ${startVerse}-${endVerse}`);
130
- }
131
- }
132
- else {
133
- // Different chapters
134
- parts.push(`${startChapter}: ${startVerse}-${endChapter}: ${endVerse}`);
135
- }
77
+ parts.push((0, languages_1.numerals)(`${chapter}: ${verseRange}`, bibleInfo.language.script));
136
78
  }
137
79
  }
138
- return (0, languages_1.numerals)(parts.join(', '), language.script.name);
80
+ return parts.join(', ');
139
81
  }
140
82
  /** Gets a scripture passage by extracting verses from chapters documents. */
141
- async function getScripturePassage({ bibleId, bibleFallbackId, passageId, getChapter, languageInfo, }) {
83
+ async function getScripturePassage({ bibleId, passageId, getChapter, noTextAvailableString, }) {
84
+ const bibleInfo = (0, bibles_1.getBibleInfo)(bibleId);
85
+ const { startVerse, endVerse, chapterId } = (0, bibleBooks_1.parseVerseRange)(passageId);
86
+ const noTextAvailableReturn = {
87
+ bibleId,
88
+ header: getPassagesString([{ passageId, bibleId }]),
89
+ passageId,
90
+ verses: [],
91
+ };
92
+ if (!bibleInfo)
93
+ return noTextAvailableReturn;
94
+ const returnExpected = bibleInfo.availableTextChapters.includes(chapterId) ||
95
+ bibleInfo.availableTimingsChapters.includes(chapterId);
96
+ /**
97
+ * In this case, we know the bible does not have text for this chapter, so the
98
+ * user will see a placeholder verse.
99
+ */
100
+ if (!returnExpected) {
101
+ noTextAvailableReturn.verses = [
102
+ { text: noTextAvailableString, verseId: '' },
103
+ ];
104
+ return noTextAvailableReturn;
105
+ }
142
106
  try {
143
- const { startBook, startChapter, startVerse, endBook, endChapter, endVerse, } = parseVerseRange(passageId);
144
107
  const startVerseNum = parseInt(startVerse);
145
108
  const endVerseNum = parseInt(endVerse);
146
- // Same chapter - simple case
147
- if (startBook === endBook && startChapter === endChapter) {
148
- const chapterId = `${startBook}.${startChapter}`;
149
- let chapter = await getChapter({ chapterId, bibleId });
150
- if (!chapter && bibleFallbackId)
151
- chapter = await getChapter({ chapterId, bibleId: bibleFallbackId });
152
- if (!chapter) {
153
- console.warn(`Chapter not found: ${bibleId}.${chapterId} for passage ${passageId}`);
154
- return;
155
- }
156
- const verses = extractVersesFromChapter(chapter, startVerseNum, endVerseNum);
157
- if (verses.length === 0) {
158
- console.warn(`No verses found for passage ${passageId}`);
159
- return;
160
- }
161
- return {
162
- passageId,
163
- bibleId,
164
- header: getPassagesString([passageId], languageInfo),
165
- verses,
166
- };
109
+ const chapter = await getChapter({ chapterId, bibleId });
110
+ if (!chapter)
111
+ return noTextAvailableReturn;
112
+ const verses = extractVersesFromChapter(chapter, startVerseNum, endVerseNum);
113
+ if (verses.length === 0) {
114
+ console.warn(`No verses found for passage ${bibleId};${passageId}`);
115
+ return noTextAvailableReturn;
167
116
  }
168
- // Multi-chapter passage (rare but possible)
169
- // Example: GEN.1.26-GEN.2.3
170
- const chapters = [];
171
- // Get all chapters in the range
172
- const startChapterNum = parseInt(startChapter);
173
- const endChapterNum = parseInt(endChapter);
174
- for (let i = startChapterNum; i <= endChapterNum; i++) {
175
- const chapterId = `${startBook}.${i}`;
176
- let chapter = await getChapter({ chapterId, bibleId });
177
- if (!chapter && bibleFallbackId)
178
- chapter = await getChapter({ chapterId, bibleId: bibleFallbackId });
179
- if (chapter) {
180
- chapters.push(chapter);
181
- }
182
- else {
183
- console.warn(`Missing chapter in range: ${bibleId}.${chapterId}`);
184
- }
185
- }
186
- if (chapters.length === 0) {
187
- return;
188
- }
189
- // Extract verses from each chapter
190
- const allVerses = [];
191
- chapters.forEach((chapter, index) => {
192
- if (index === 0) {
193
- // First chapter - from startVerse to end
194
- const lastVerse = Math.max(...chapter.verses.map((v) => parseInt(v.verseId.split('.')[2])));
195
- allVerses.push(...extractVersesFromChapter(chapter, startVerseNum, lastVerse));
196
- }
197
- else if (index === chapters.length - 1) {
198
- // Last chapter - from beginning to endVerse
199
- allVerses.push(...extractVersesFromChapter(chapter, 1, endVerseNum));
200
- }
201
- else {
202
- // Middle chapters - all verses
203
- allVerses.push(...chapter.verses);
204
- }
205
- });
117
+ if (!verses.some((v) => v.text))
118
+ verses[0].text = noTextAvailableString;
206
119
  return {
207
120
  passageId,
208
- bibleId,
209
- header: getPassagesString([passageId], languageInfo),
210
- verses: allVerses,
121
+ bibleId: chapter.translationId,
122
+ header: getPassagesString([{ passageId, bibleId }]),
123
+ verses,
211
124
  };
212
125
  }
213
126
  catch (error) {
214
127
  console.error(`Error getting scripture passage ${passageId} in ${bibleId}`, error);
215
- return;
128
+ throw error;
216
129
  }
217
130
  }
218
- /**
219
- * Gets multiple scripture passages efficiently (Future optimization: could
220
- * batch the chapter fetches)
221
- */
222
- async function getLessonScripture(params) {
223
- const passages = await Promise.all(params.passageIds.map((passageId) => getScripturePassage({ ...params, passageId })));
224
- return passages;
225
- }
226
- function parseBibleSnapshot(snapshot, bible) {
227
- const validChapters = [];
228
- if (!snapshot) {
229
- console.warn('No snapshot for', bible);
230
- return [];
231
- }
232
- else if (snapshot.empty) {
233
- console.warn('Empty snapshot for', bible);
234
- return [];
235
- }
236
- for (const doc of snapshot.docs) {
237
- const chapterParse = bibleChapters_1.BibleChapter.safeParse(doc.data());
238
- if (!chapterParse.success) {
239
- console.warn('Skipping invalid bible chapter document', doc.id, chapterParse.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`));
240
- }
241
- else {
242
- validChapters.push(chapterParse.data);
243
- }
244
- }
245
- if (validChapters.length === 0) {
246
- console.warn('No valid chapters for', bible);
247
- return [];
248
- }
249
- else {
250
- return validChapters;
251
- }
131
+ /** Gets all scripture passages for a lesson. */
132
+ async function getLessonScripture({ getChapter, lessonInfo, noTextAvailableString, }) {
133
+ return Promise.all(lessonInfo.sections
134
+ .filter((s) => s.chapter === sets_1.Chapter.STORY)
135
+ .map((s) => {
136
+ const [bibleId, passageId] = s.id.split(';');
137
+ return getScripturePassage({
138
+ bibleId,
139
+ passageId,
140
+ getChapter,
141
+ noTextAvailableString,
142
+ });
143
+ }));
252
144
  }
253
145
  /**
254
146
  * Converts a verse number to a superscript string. This is useful for
@@ -285,131 +177,102 @@ function verseToSuperscript(num) {
285
177
  .join('');
286
178
  }
287
179
  /**
288
- * Splits a passage's verses into per-chapter chunks. Single-chapter passages
289
- * produce one chunk. Multi-chapter passages (rare, e.g. GEN.1.26-GEN.2.3)
290
- * produce one chunk per chapter, with isPartOfPrevious=true on the 2nd+ chunks.
291
- * This mirrors leviathan's passage_ids_by_chapter splitting logic.
180
+ * Returns the FTB audio duration + afterFtb pause for a section, or 0 if the
181
+ * section has no FTB.
292
182
  */
293
- function splitPassageIntoChunks(verses) {
294
- if (verses.length === 0)
295
- return [];
296
- const chunks = [];
297
- let currentChapterId = null;
298
- for (const verse of verses) {
299
- const parts = verse.verseId.split('.');
300
- const chapterId = `${parts[0]}.${parts[1]}`;
301
- if (chapterId !== currentChapterId) {
302
- chunks.push({
303
- verses: [verse],
304
- bookId: parts[0],
305
- isPartOfPrevious: currentChapterId !== null,
306
- });
307
- currentChapterId = chapterId;
308
- }
309
- else {
310
- chunks[chunks.length - 1].verses.push(verse);
311
- }
183
+ function getFtbDuration(section, lessonInfo) {
184
+ if (!section.ftbFileName)
185
+ return 0;
186
+ const ftbDuration = mediaDurations_1.mediaDurations[lessonInfo.contentLanguages.ftbs]?.[section.ftbFileName];
187
+ if (ftbDuration == null) {
188
+ console.warn(`FTB duration not found for ${section.ftbFileName}`);
312
189
  }
313
- return chunks;
190
+ return (ftbDuration ?? 0) + lessonInfo.lessonPauses.afterFtb;
314
191
  }
315
192
  /**
316
- * Some lessons were built using a system that used slightly different pause
317
- * values.
193
+ * Fills in `length` (for verse-timed story sections) and `startTime` (for all
194
+ * sections) by accumulating forward through the lesson. After this, every
195
+ * section has both fields set.
318
196
  */
319
- const alternativeLessonPauses = {
320
- beforeStory: 1,
321
- afterStory: 1,
322
- betweenPassages: 0.5,
323
- beforeFtb: 1,
324
- afterFtb: 0.5,
325
- };
326
- /**
327
- * List of languages whose lessons were built using slightly different pause
328
- * values.
329
- */
330
- const usesAlternativeLessonPauses = ['tel', 'som', 'kmr', 'swz', 'vie'];
331
- /** Returns the correct lesson pauses for a given language. */
332
- function getLessonPauses(languageId) {
333
- return usesAlternativeLessonPauses.includes(languageId)
334
- ? alternativeLessonPauses
335
- : lessonPauses_1.lessonPauses;
197
+ function enrichSections(lessonInfo, scripture) {
198
+ const storySections = lessonInfo.sections.filter((s) => s.chapter === sets_1.Chapter.STORY);
199
+ // Forward pass: compute lengths and start times for all non-application
200
+ // sections.
201
+ let currentTime = 0;
202
+ const enriched = lessonInfo.sections.map((section) => {
203
+ if (!section.chapter)
204
+ return { ...section, startTime: 0, length: 0 };
205
+ currentTime += section.pauseBefore ?? 0;
206
+ let length;
207
+ const startTime = currentTime;
208
+ if (section.chapter !== sets_1.Chapter.STORY) {
209
+ length = mediaDurations_1.mediaDurations[section.languageId]?.[section.fileName];
210
+ }
211
+ else {
212
+ currentTime += getFtbDuration(section, lessonInfo);
213
+ const [bibleId, passageId] = section.id.split(';');
214
+ length = bibleStatuses_1.bibleStatuses[bibleId]?.customPassages?.[passageId];
215
+ if (!length) {
216
+ const passage = scripture[storySections.indexOf(section)];
217
+ if (passage) {
218
+ const firstTimed = passage.verses.find((v) => v.timings);
219
+ const lastTimed = [...passage.verses].reverse().find((v) => v.timings);
220
+ if (firstTimed?.timings && lastTimed?.timings) {
221
+ length = lastTimed.timings[1] - firstTimed.timings[0];
222
+ }
223
+ }
224
+ }
225
+ }
226
+ if (length == null) {
227
+ console.warn(`Missing duration for section ${section.id} (${section.chapter}, ${section.fileName})`);
228
+ }
229
+ currentTime += length ?? 0;
230
+ return { ...section, length: length ?? 0, startTime };
231
+ });
232
+ // Backward pass: compute application section start times from the end of the
233
+ // lesson file, so they are always correct even if story durations are
234
+ // slightly off.
235
+ const fullFileName = lessonInfo.type === 'dbs'
236
+ ? lessonInfo.full.localFileName
237
+ : lessonInfo.video.localFileName;
238
+ const totalDuration = mediaDurations_1.mediaDurations[lessonInfo.languageId]?.[fullFileName] ?? 0;
239
+ if (totalDuration > 0) {
240
+ let timeFromEnd = totalDuration;
241
+ const applicationSections = enriched.filter((s) => s.chapter === sets_1.Chapter.APPLICATION);
242
+ for (const section of [...applicationSections].reverse()) {
243
+ timeFromEnd -= section.length;
244
+ section.startTime = timeFromEnd;
245
+ timeFromEnd -= section.pauseBefore ?? 0;
246
+ }
247
+ }
248
+ return enriched;
336
249
  }
337
250
  /**
338
- * Converts verse timings within each scripture passage from chapter-relative to
339
- * full-lesson relative. This includes accounting for question durations,
340
- * pauses, and from-the-book durations.
341
- *
342
- * Multi-chapter passages are split into per-chapter chunks internally, since
343
- * the audio is clipped from individual chapter files.
251
+ * Converts verse timings from chapter-relative to lesson-relative using each
252
+ * section's `startTime` (set by {@link enrichSections}).
344
253
  */
345
- function normalizeVerseTimings({ lessonInfo, scripture, }) {
346
- const languageInfo = (0, languages_1.getLanguageInfo)(lessonInfo.meetLanguageId);
347
- const pauses = getLessonPauses(lessonInfo.meetLanguageId);
348
- let cumulativeTime = lessonInfo.fellowshipDuration + pauses.beforeStory;
349
- if (specialIds_1.specialIds.introductionLessonIds.includes(lessonInfo.lessonId) &&
350
- lessonInfo.type === 'dbs' &&
351
- lessonInfo.introLength)
352
- cumulativeTime += lessonInfo.introLength;
353
- let lastBook = null;
354
- /** Tracks the overall passage index (not chunk index) for FTB/pause logic. */
355
- let globalChunkIndex = 0;
356
- return scripture?.map((passage) => {
357
- if (!passage)
358
- return undefined;
359
- const chunks = splitPassageIntoChunks(passage.verses);
360
- if (chunks.length === 0)
254
+ function normalizeVerseTimings({ enrichedSections, scripture, lessonInfo, }) {
255
+ const storySections = enrichedSections.filter((s) => s.chapter === sets_1.Chapter.STORY);
256
+ return scripture.map((passage, passageIndex) => {
257
+ const section = storySections[passageIndex];
258
+ if (!section)
361
259
  return passage;
362
- const adjustedVerses = [];
363
- for (const chunk of chunks) {
364
- const firstTimedVerse = chunk.verses.find((v) => v.timings);
365
- if (!firstTimedVerse?.timings) {
366
- // No timing data — pass through unadjusted
367
- adjustedVerses.push(...chunk.verses);
368
- if (!chunk.isPartOfPrevious)
369
- globalChunkIndex++;
370
- continue;
371
- }
372
- const chunkAudioStart = firstTimedVerse.timings[0];
373
- // Add FTB + pauses. Chunks that are continuations of a multi-chapter
374
- // passage (isPartOfPrevious) get no pause — they are concatenated directly.
375
- if (!chunk.isPartOfPrevious) {
376
- if (lastBook !== chunk.bookId) {
377
- if (globalChunkIndex !== 0) {
378
- cumulativeTime += pauses.beforeFtb;
379
- }
380
- const ftbLanguage = languageInfo.titles ?? languageInfo.languageId;
381
- const ftbKey = `${ftbLanguage}.${chunk.bookId}.mp3`;
382
- const ftbDuration = mediaDurations_1.mediaDurations[ftbLanguage]?.[ftbKey];
383
- if (ftbDuration != null)
384
- cumulativeTime += ftbDuration;
385
- else {
386
- console.error(`FTB duration not found for ${ftbKey}`);
387
- return passage;
388
- }
389
- cumulativeTime += pauses.afterFtb;
390
- lastBook = chunk.bookId;
391
- }
392
- else if (globalChunkIndex !== 0) {
393
- cumulativeTime += pauses.betweenPassages;
394
- }
395
- globalChunkIndex++;
396
- }
397
- const offset = cumulativeTime - chunkAudioStart;
398
- for (const verse of chunk.verses) {
260
+ const firstTimedVerse = passage.verses.find((v) => v.timings);
261
+ if (!firstTimedVerse?.timings)
262
+ return passage;
263
+ const offset = section.startTime +
264
+ getFtbDuration(section, lessonInfo) -
265
+ firstTimedVerse.timings[0];
266
+ return {
267
+ ...passage,
268
+ verses: passage.verses.map((verse) => {
399
269
  if (!verse.timings)
400
- adjustedVerses.push(verse);
401
- else
402
- adjustedVerses.push({
403
- ...verse,
404
- timings: [verse.timings[0] + offset, verse.timings[1] + offset],
405
- });
406
- }
407
- // Advance cumulative time by this chunk's audio duration
408
- const lastTimedVerse = [...chunk.verses].reverse().find((v) => v.timings);
409
- if (lastTimedVerse?.timings) {
410
- cumulativeTime += lastTimedVerse.timings[1] - chunkAudioStart;
411
- }
412
- }
413
- return { ...passage, verses: adjustedVerses };
270
+ return verse;
271
+ return {
272
+ ...verse,
273
+ timings: [verse.timings[0] + offset, verse.timings[1] + offset],
274
+ };
275
+ }),
276
+ };
414
277
  });
415
278
  }
@@ -1,17 +1,15 @@
1
1
  import { TranslationsApp } from '../data/translationsApp/translationsApp.zod';
2
2
  import type { LanguageInfo, MeetTranslations } from '../types/languages';
3
- import { ScripturePassage } from '../types/scripturePassages';
4
- import { DbsInfo, Lesson, LessonInfo, SetInfo, VideoInfo } from '../types/sets';
5
- export declare const firebaseUrl = "https://firebasestorage.googleapis.com/v0/b/waha-app-db.appspot.com/o/";
6
- export declare function getSetInfo({ setId, meetLanguageId, setIds, t, }: {
3
+ import { DbsInfo, EnrichedSection, Lesson, LessonInfo, SetInfo, VideoInfo } from '../types/sets';
4
+ export declare function getSetInfo({ setId, languageId, setIds, t, }: {
7
5
  setId: string;
8
- meetLanguageId: string;
6
+ languageId: string;
9
7
  t: MeetTranslations & TranslationsApp[string];
10
8
  setIds: string[];
11
9
  }): SetInfo | undefined;
12
- export declare function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuestions, }: {
10
+ export declare function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions, }: {
13
11
  lessonId: Lesson['lessonId'] | undefined;
14
- meetLanguageInfo: LanguageInfo;
12
+ languageInfo: LanguageInfo;
15
13
  setInfo?: SetInfo;
16
14
  t: MeetTranslations & TranslationsApp[string];
17
15
  useSpokenQuestions?: boolean;
@@ -20,50 +18,13 @@ export declare function convertSToString(time: number): string;
20
18
  /** Determines whether a lesson should be visible in Waha. */
21
19
  export declare function shouldShowLesson(lessonInfo: LessonInfo | undefined, languageInfo: LanguageInfo): boolean;
22
20
  /**
23
- * Calculate start times for each section based on question durations and total
24
- * audio duration. Must be called after audio is loaded to get accurate total
25
- * duration.
26
- */
27
- export declare function calculateSectionTimings({ languageInfo, thisLesson, totalDuration, normalizedScripture, supportsVerseHighlight, }: {
28
- thisLesson: DbsInfo | VideoInfo;
29
- totalDuration: number;
30
- languageInfo: LanguageInfo;
31
- normalizedScripture: Array<ScripturePassage | undefined>;
32
- /**
33
- * Determine if the timings are valid to be used for section timings.
34
- *
35
- * Supports verse highlight example:
36
- *
37
- * - GEN.1.1-GEN.1.10 -> 83
38
- * - GEN.2.1-GEN.2.4 -> 100
39
- * - GEN.2.7-GEN.2.10 -> 120
40
- *
41
- * Doesn't support verse highlight example:
42
- *
43
- * - GEN.1.1-GEN.1.10 -> 83
44
- * - GEN.2.1-GEN.2.4 -> undefined
45
- * - GEN.2.7-GEN.2.10 -> undefined
46
- *
47
- * First passage is always valid because its start time is simply the length
48
- * of the previous audio. Subsequent passages require good verse timing data.
49
- */
50
- supportsVerseHighlight: boolean;
51
- }): Map<string, number>;
52
- /**
53
- * Checks if pre-normalized verse timings for given scripture are valid. Does
54
- * this by comparing the real duration of the full lesson mp3 file with the
55
- * calculated duration based on individual question durations and verse timing
56
- * data from bible chapter documents. For lessons generated programmatically
57
- * with recent systems, they should be nearly identical. For older lessons that
58
- * were made by hand, they may be off. If they're too off, they will be flagged
59
- * as invalid and the lesson will default to not using verse timings.
21
+ * Validates that the computed lesson duration (from section lengths) matches
22
+ * the actual audio file duration. Sections must be enriched (all have `length`)
23
+ * via {@link enrichSections} before calling this.
24
+ *
25
+ * @returns The difference between the computed length and the total duration.
60
26
  */
61
- export declare function getSupportsVerseHighlight({ lessonInfo, scripture, totalDuration, }: {
27
+ export declare function getExpectedLessonDuration({ enrichedSections, lessonInfo, }: {
28
+ enrichedSections: EnrichedSection[];
62
29
  lessonInfo: DbsInfo | VideoInfo;
63
- scripture: Array<ScripturePassage | undefined>;
64
- totalDuration: number | undefined;
65
- }): {
66
- computedLength: number;
67
- diff: number;
68
- passed: boolean;
69
- };
30
+ }): number;