waha-shared 1.0.281 → 1.0.283
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/data/bibleAudios/bibleAudios.json +2 -10
- package/dist/data/bibleStatuses/bibleStatuses.json +116 -227
- package/dist/data/bibleTexts/bibleTexts.json +4 -15
- package/dist/data/dblAudioLicenses/dblAudioLicenses.json +450 -392
- package/dist/data/dblTextLicenses/dblTextLicenses.json +1368 -1160
- package/dist/data/languageAssets/languageAssets.json +5428 -4086
- package/dist/data/languages/index.d.ts +2 -0
- package/dist/data/languages/languages.json +28 -36
- package/dist/data/languages/languages.schema.json +8 -0
- package/dist/data/languages/languages.zod.d.ts +2 -0
- package/dist/data/languages/languages.zod.js +8 -0
- package/dist/data/lessonPauses/index.d.ts +7 -0
- package/dist/data/lessonPauses/index.js +7 -0
- package/dist/data/lessonPauses/lessonPauses.json +10 -0
- package/dist/data/lessonPauses/lessonPauses.schema.json +43 -0
- package/dist/data/lessonPauses/lessonPauses.zod.d.ts +9 -0
- package/dist/data/lessonPauses/lessonPauses.zod.js +23 -0
- package/dist/data/mediaDurations/mediaDurations.json +3985 -66
- package/dist/data/releaseNotes/releaseNotes.json +49 -2
- package/dist/data/screenshots/screenshots.json +1 -1
- package/dist/data/specialIds/specialIds.schema.json +11 -36
- package/dist/data/specialIds/specialIds.zod.js +11 -17
- package/dist/data/translationsApp/index.d.ts +1 -0
- package/dist/data/translationsApp/translationsApp.json +61 -20
- package/dist/data/translationsApp/translationsApp.schema.json +1 -0
- package/dist/data/translationsApp/translationsApp.zod.d.ts +2 -0
- package/dist/data/translationsApp/translationsApp.zod.js +1 -0
- package/dist/data/translationsIntroduction/translationsIntroduction.json +42 -0
- package/dist/data/translationsQuestion/translationsQuestion.json +1 -1
- package/dist/data/translationsSet/translationsSet.json +448 -0
- package/dist/data/translationsSpokenQuestion/translationsSpokenQuestion.json +1 -1
- package/dist/functions/scripturePassages.d.ts +17 -4
- package/dist/functions/scripturePassages.js +136 -5
- package/dist/functions/sets.d.ts +44 -6
- package/dist/functions/sets.js +100 -44
- package/dist/types/analytics.d.ts +2 -2
- package/dist/types/sets.d.ts +5 -2
- package/package.json +1 -1
|
@@ -7,7 +7,12 @@ exports.getScripturePassage = getScripturePassage;
|
|
|
7
7
|
exports.getLessonScripture = getLessonScripture;
|
|
8
8
|
exports.parseBibleSnapshot = parseBibleSnapshot;
|
|
9
9
|
exports.verseToSuperscript = verseToSuperscript;
|
|
10
|
+
exports.getLessonPauses = getLessonPauses;
|
|
11
|
+
exports.normalizeVerseTimings = normalizeVerseTimings;
|
|
10
12
|
const bibleStatuses_1 = require("../data/bibleStatuses");
|
|
13
|
+
const lessonPauses_1 = require("../data/lessonPauses");
|
|
14
|
+
const mediaDurations_1 = require("../data/mediaDurations");
|
|
15
|
+
const specialIds_1 = require("../data/specialIds");
|
|
11
16
|
const languages_1 = require("../functions/languages");
|
|
12
17
|
const bibleChapters_1 = require("../types/bibleChapters");
|
|
13
18
|
const sets_1 = require("./sets");
|
|
@@ -90,7 +95,7 @@ function getPassagesString(passageIds, language) {
|
|
|
90
95
|
let bookName =
|
|
91
96
|
// American sign language should use English book names.
|
|
92
97
|
language.languageId === 'ase'
|
|
93
|
-
? bibleStatuses_1.bibleStatuses.
|
|
98
|
+
? bibleStatuses_1.bibleStatuses.NLT.bookNames[startBook]
|
|
94
99
|
: language.bible.bookNames[startBook];
|
|
95
100
|
if (!bookName) {
|
|
96
101
|
bookName = language.bibleFallback?.bookNames[startBook];
|
|
@@ -132,10 +137,7 @@ function getPassagesString(passageIds, language) {
|
|
|
132
137
|
}
|
|
133
138
|
return (0, languages_1.numerals)(parts.join(', '), language.script.name);
|
|
134
139
|
}
|
|
135
|
-
/**
|
|
136
|
-
* Gets a scripture passage by extracting verses from chapters This is the core
|
|
137
|
-
* parsing logic, independent of the data source
|
|
138
|
-
*/
|
|
140
|
+
/** Gets a scripture passage by extracting verses from chapters documents. */
|
|
139
141
|
async function getScripturePassage({ bibleId, bibleFallbackId, passageId, getChapter, languageInfo, }) {
|
|
140
142
|
try {
|
|
141
143
|
const { startBook, startChapter, startVerse, endBook, endChapter, endVerse, } = parseVerseRange(passageId);
|
|
@@ -282,3 +284,132 @@ function verseToSuperscript(num) {
|
|
|
282
284
|
})
|
|
283
285
|
.join('');
|
|
284
286
|
}
|
|
287
|
+
/**
|
|
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.
|
|
292
|
+
*/
|
|
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
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return chunks;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Some lessons were built using a system that used slightly different pause
|
|
317
|
+
* values.
|
|
318
|
+
*/
|
|
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;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
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.
|
|
344
|
+
*/
|
|
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)
|
|
361
|
+
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) {
|
|
399
|
+
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 };
|
|
414
|
+
});
|
|
415
|
+
}
|
package/dist/functions/sets.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TranslationsApp } from '../data/translationsApp/translationsApp.zod';
|
|
2
2
|
import type { LanguageInfo, MeetTranslations } from '../types/languages';
|
|
3
|
+
import { ScripturePassage } from '../types/scripturePassages';
|
|
3
4
|
import { DbsInfo, Lesson, LessonInfo, SetInfo, VideoInfo } from '../types/sets';
|
|
4
5
|
export declare const firebaseUrl = "https://firebasestorage.googleapis.com/v0/b/waha-app-db.appspot.com/o/";
|
|
5
6
|
export declare function getSetInfo({ setId, meetLanguageId, setIds, t, }: {
|
|
@@ -22,10 +23,47 @@ export declare function shouldShowLesson(lessonInfo: LessonInfo | undefined, lan
|
|
|
22
23
|
* Calculate start times for each section based on question durations and total
|
|
23
24
|
* audio duration. Must be called after audio is loaded to get accurate total
|
|
24
25
|
* duration.
|
|
25
|
-
*
|
|
26
|
-
* @param thisLesson - The lesson to calculate timings for
|
|
27
|
-
* @param totalDuration - Total duration of the lesson audio in seconds
|
|
28
|
-
* @param languageInfo - The language information to look up question durations
|
|
29
|
-
* @returns Map of section ID to start time in seconds
|
|
30
26
|
*/
|
|
31
|
-
export declare function calculateSectionTimings(
|
|
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.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getSupportsVerseHighlight({ lessonInfo, scripture, totalDuration, }: {
|
|
62
|
+
lessonInfo: DbsInfo | VideoInfo;
|
|
63
|
+
scripture: Array<ScripturePassage | undefined>;
|
|
64
|
+
totalDuration: number | undefined;
|
|
65
|
+
}): {
|
|
66
|
+
computedLength: number;
|
|
67
|
+
diff: number;
|
|
68
|
+
passed: boolean;
|
|
69
|
+
};
|
package/dist/functions/sets.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.getLessonInfo = getLessonInfo;
|
|
|
6
6
|
exports.convertSToString = convertSToString;
|
|
7
7
|
exports.shouldShowLesson = shouldShowLesson;
|
|
8
8
|
exports.calculateSectionTimings = calculateSectionTimings;
|
|
9
|
+
exports.getSupportsVerseHighlight = getSupportsVerseHighlight;
|
|
9
10
|
const languageAssets_1 = require("../data/languageAssets");
|
|
10
11
|
const mediaDurations_1 = require("../data/mediaDurations");
|
|
11
12
|
const questions_1 = require("../data/questions");
|
|
@@ -118,6 +119,7 @@ function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuesti
|
|
|
118
119
|
hasBeep,
|
|
119
120
|
});
|
|
120
121
|
});
|
|
122
|
+
let introLength = undefined;
|
|
121
123
|
if (specialIds_1.specialIds.evaluationQuestionLessonIds.includes(lesson.lessonId)) {
|
|
122
124
|
sections.push({
|
|
123
125
|
id: 'eq',
|
|
@@ -141,6 +143,9 @@ function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuesti
|
|
|
141
143
|
text: t.introductions.introductions[lessonId],
|
|
142
144
|
hasBeep: false,
|
|
143
145
|
});
|
|
146
|
+
const introLanguageId = meetLanguageInfo.introBridge ?? meetLanguageId;
|
|
147
|
+
introLength =
|
|
148
|
+
mediaDurations_1.mediaDurations[introLanguageId]?.[`${introLanguageId}.${lessonId}.mp3`];
|
|
144
149
|
}
|
|
145
150
|
lesson.s?.forEach((passageId, index) => {
|
|
146
151
|
sections.push({
|
|
@@ -171,7 +176,6 @@ function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuesti
|
|
|
171
176
|
});
|
|
172
177
|
});
|
|
173
178
|
const baseInfo = {
|
|
174
|
-
lessonIntroduction: t.introductions.introduction,
|
|
175
179
|
lessonLink: `https://web.waha.app/lesson?lesson-id=${lessonId}&meet-language=${meetLanguageId}`,
|
|
176
180
|
lessonNumber,
|
|
177
181
|
lessonTitle,
|
|
@@ -179,6 +183,15 @@ function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuesti
|
|
|
179
183
|
...setInfo,
|
|
180
184
|
...lesson,
|
|
181
185
|
};
|
|
186
|
+
const fellowshipDuration = fellowshipQuestions.reduce((sum, questionId) => {
|
|
187
|
+
const key = `${meetLanguageId}.${questionId}.mp3`;
|
|
188
|
+
return sum + (mediaDurations_1.mediaDurations[meetLanguageId]?.[key] ?? 0);
|
|
189
|
+
}, 0);
|
|
190
|
+
let applicationDuration = applicationQuestions.reduce((sum, questionId) => {
|
|
191
|
+
return (sum +
|
|
192
|
+
(mediaDurations_1.mediaDurations[meetLanguageId]?.[`${meetLanguageId}.${questionId}.mp3`] ??
|
|
193
|
+
0));
|
|
194
|
+
}, 0);
|
|
182
195
|
if (specialIds_1.specialIds.evaluationQuestionLessonIds.includes(lesson.lessonId)) {
|
|
183
196
|
const languageId = meetLanguageInfo.introBridge ?? meetLanguageId;
|
|
184
197
|
const id = `${languageId}.eq`;
|
|
@@ -187,6 +200,8 @@ function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuesti
|
|
|
187
200
|
return {
|
|
188
201
|
type: 'eq',
|
|
189
202
|
...baseInfo,
|
|
203
|
+
fellowshipDuration,
|
|
204
|
+
applicationDuration,
|
|
190
205
|
eq: {
|
|
191
206
|
id,
|
|
192
207
|
languageId,
|
|
@@ -228,6 +243,9 @@ function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuesti
|
|
|
228
243
|
};
|
|
229
244
|
const trainingVideoId = `${meetLanguageInfo.trainingVideoLanguage}.${lessonId}`;
|
|
230
245
|
const trainingVideoFileName = `${trainingVideoId}.mp4`;
|
|
246
|
+
// Add the length of the training video to the application duration.
|
|
247
|
+
applicationDuration +=
|
|
248
|
+
mediaDurations_1.mediaDurations[meetLanguageInfo.trainingVideoLanguage]?.[trainingVideoFileName] ?? 0;
|
|
231
249
|
const trainingVideoPath = `${meetLanguageInfo.trainingVideoLanguage}/course/videos_compressed/${trainingVideoFileName}`;
|
|
232
250
|
trainingVideo = {
|
|
233
251
|
id: trainingVideoId,
|
|
@@ -246,6 +264,8 @@ function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuesti
|
|
|
246
264
|
return {
|
|
247
265
|
type: 'video',
|
|
248
266
|
...baseInfo,
|
|
267
|
+
fellowshipDuration,
|
|
268
|
+
applicationDuration,
|
|
249
269
|
lessonSectionHeader: thisSetTranslations?.sectionHeaders?.[lessonId],
|
|
250
270
|
lessonSectionBody: thisSetTranslations?.sectionBodies?.[lessonId],
|
|
251
271
|
video,
|
|
@@ -258,14 +278,17 @@ function getLessonInfo({ lessonId, meetLanguageInfo, setInfo, t, useSpokenQuesti
|
|
|
258
278
|
return {
|
|
259
279
|
type: 'dbs',
|
|
260
280
|
...baseInfo,
|
|
281
|
+
fellowshipDuration,
|
|
282
|
+
applicationDuration,
|
|
283
|
+
introLength,
|
|
261
284
|
full: {
|
|
262
285
|
id: `${meetLanguageId}.${lessonId}`,
|
|
263
286
|
localFileName: `${meetLanguageId}.${lessonId}.mp3`,
|
|
264
287
|
remoteFileName: `${meetLanguageId}.${lessonId}.mp3`,
|
|
265
288
|
languageId: meetLanguageId,
|
|
266
|
-
path: `${meetLanguageId}/full_lessons/${setInfo.setId}/${meetLanguageId}.${lessonId}.mp3`,
|
|
289
|
+
path: `${meetLanguageId}/full_lessons${meetLanguageInfo.audioAssetVersion ?? ''}/${setInfo.setId}/${meetLanguageId}.${lessonId}.mp3`,
|
|
267
290
|
url: exports.firebaseUrl +
|
|
268
|
-
encodeURIComponent(`${meetLanguageId}/full_lessons/${setInfo.setId}/${meetLanguageId}.${lessonId}.mp3`) +
|
|
291
|
+
encodeURIComponent(`${meetLanguageId}/full_lessons${meetLanguageInfo.audioAssetVersion ?? ''}/${setInfo.setId}/${meetLanguageId}.${lessonId}.mp3`) +
|
|
269
292
|
`?alt=media`,
|
|
270
293
|
},
|
|
271
294
|
dbsCast: {
|
|
@@ -324,12 +347,6 @@ function shouldShowLesson(lessonInfo, languageInfo) {
|
|
|
324
347
|
console.log('Missing lesson info or lessonTitle lesson:', lessonInfo?.lessonId);
|
|
325
348
|
return false;
|
|
326
349
|
}
|
|
327
|
-
else if (specialIds_1.specialIds.growingAsDmcSetIds.includes(lessonInfo.setId) &&
|
|
328
|
-
(lessonInfo.lessonIntroduction === undefined ||
|
|
329
|
-
lessonInfo.lessonIntroduction === '')) {
|
|
330
|
-
console.log('Missing intro for lesson:', lessonInfo.lessonId);
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
350
|
else if (lessonInfo.type === 'eq' &&
|
|
334
351
|
!languageAssets_1.languageAssets[languageInfo.introBridge ?? languageInfo.languageId].find((f) => f === lessonInfo.eq.remoteFileName)) {
|
|
335
352
|
console.log('Missing remote eq file for lesson:', lessonInfo.lessonId);
|
|
@@ -353,14 +370,10 @@ function shouldShowLesson(lessonInfo, languageInfo) {
|
|
|
353
370
|
* Calculate start times for each section based on question durations and total
|
|
354
371
|
* audio duration. Must be called after audio is loaded to get accurate total
|
|
355
372
|
* duration.
|
|
356
|
-
*
|
|
357
|
-
* @param thisLesson - The lesson to calculate timings for
|
|
358
|
-
* @param totalDuration - Total duration of the lesson audio in seconds
|
|
359
|
-
* @param languageInfo - The language information to look up question durations
|
|
360
|
-
* @returns Map of section ID to start time in seconds
|
|
361
373
|
*/
|
|
362
|
-
function calculateSectionTimings(thisLesson, totalDuration,
|
|
374
|
+
function calculateSectionTimings({ languageInfo, thisLesson, totalDuration, normalizedScripture, supportsVerseHighlight, }) {
|
|
363
375
|
const languageDurations = mediaDurations_1.mediaDurations[languageInfo.languageId];
|
|
376
|
+
const pauses = (0, scripturePassages_1.getLessonPauses)(thisLesson.meetLanguageId);
|
|
364
377
|
const getQuestionDuration = (questionId) => {
|
|
365
378
|
const fileName = `${languageInfo.languageId}.${questionId}.mp3`;
|
|
366
379
|
return languageDurations?.[fileName] ?? 0;
|
|
@@ -370,42 +383,85 @@ function calculateSectionTimings(thisLesson, totalDuration, languageInfo) {
|
|
|
370
383
|
timings.set('title', 0);
|
|
371
384
|
// Calculate fellowship question timings from the start
|
|
372
385
|
let currentTime = 0;
|
|
373
|
-
|
|
374
|
-
|
|
386
|
+
/** Add timings for each fellowship question. */
|
|
387
|
+
thisLesson.sections
|
|
388
|
+
.filter((s) => s.chapter === sets_2.Chapter.FELLOWSHIP)
|
|
389
|
+
.forEach((section) => {
|
|
375
390
|
timings.set(section.id, currentTime);
|
|
376
391
|
currentTime += getQuestionDuration(section.id);
|
|
377
392
|
});
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
393
|
+
/** Add the pause before the story chapter starts. */
|
|
394
|
+
currentTime += pauses.beforeStory;
|
|
395
|
+
/** Add the intro timing and pause if we have one. */
|
|
396
|
+
const introSection = thisLesson.sections.find((s) => s.chapter === sets_2.Chapter.INTRODUCTION);
|
|
397
|
+
if (introSection && thisLesson.type === 'dbs' && thisLesson.introLength) {
|
|
398
|
+
timings.set(introSection.id, currentTime);
|
|
399
|
+
currentTime += thisLesson.introLength;
|
|
400
|
+
}
|
|
401
|
+
const storySections = thisLesson.sections.filter((s) => s.chapter === sets_2.Chapter.STORY);
|
|
402
|
+
if (storySections.length > 0) {
|
|
403
|
+
/** First passage will always start right after all previous sections. */
|
|
404
|
+
timings.set(storySections[0].id, currentTime);
|
|
405
|
+
/** Subsequent passages require normalized scripture data to get timings for. */
|
|
406
|
+
if (storySections.length > 1 && supportsVerseHighlight)
|
|
407
|
+
storySections.slice(1).forEach((section) => {
|
|
408
|
+
const passageStartTime = normalizedScripture[section.indexWithinChapter]?.verses?.[0]
|
|
409
|
+
?.timings?.[0];
|
|
410
|
+
if (passageStartTime != null)
|
|
411
|
+
timings.set(section.id, passageStartTime);
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Application section starts after story ends, but we can work backwards and
|
|
416
|
+
* calculate its start time by subtracting its duration from the total
|
|
417
|
+
* duration. This way, if we don't have verse timings data, we can still have
|
|
418
|
+
* timings for the application section.
|
|
419
|
+
*/
|
|
420
|
+
const applicationStartTime = totalDuration - thisLesson.applicationDuration;
|
|
421
|
+
currentTime = applicationStartTime;
|
|
422
|
+
thisLesson.sections
|
|
423
|
+
.filter((s) => s.chapter === sets_2.Chapter.APPLICATION)
|
|
424
|
+
.forEach((section) => {
|
|
425
|
+
timings.set(section.id, currentTime);
|
|
402
426
|
if (section.id.includes('video') &&
|
|
403
427
|
thisLesson.type === 'video' &&
|
|
404
428
|
thisLesson.trainingVideo)
|
|
405
|
-
|
|
406
|
-
mediaDurations_1.mediaDurations[thisLesson.trainingVideo.languageId][thisLesson.trainingVideo.localFileName];
|
|
429
|
+
currentTime +=
|
|
430
|
+
mediaDurations_1.mediaDurations[thisLesson.trainingVideo.languageId]?.[thisLesson.trainingVideo.localFileName] ?? 0;
|
|
407
431
|
else
|
|
408
|
-
|
|
432
|
+
currentTime += getQuestionDuration(section.id);
|
|
409
433
|
});
|
|
410
434
|
return timings;
|
|
411
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Checks if pre-normalized verse timings for given scripture are valid. Does
|
|
438
|
+
* this by comparing the real duration of the full lesson mp3 file with the
|
|
439
|
+
* calculated duration based on individual question durations and verse timing
|
|
440
|
+
* data from bible chapter documents. For lessons generated programmatically
|
|
441
|
+
* with recent systems, they should be nearly identical. For older lessons that
|
|
442
|
+
* were made by hand, they may be off. If they're too off, they will be flagged
|
|
443
|
+
* as invalid and the lesson will default to not using verse timings.
|
|
444
|
+
*/
|
|
445
|
+
function getSupportsVerseHighlight({ lessonInfo, scripture, totalDuration, }) {
|
|
446
|
+
if (!totalDuration)
|
|
447
|
+
return { computedLength: 0, diff: 0, passed: false };
|
|
448
|
+
let lastEnd;
|
|
449
|
+
for (const passage of scripture) {
|
|
450
|
+
if (!passage)
|
|
451
|
+
continue;
|
|
452
|
+
for (const verse of passage.verses) {
|
|
453
|
+
if (verse.timings) {
|
|
454
|
+
if (lastEnd === undefined || verse.timings[1] > lastEnd) {
|
|
455
|
+
lastEnd = verse.timings[1];
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (lastEnd === undefined)
|
|
461
|
+
return { computedLength: 0, diff: 0, passed: false };
|
|
462
|
+
const computedLength = lastEnd +
|
|
463
|
+
(0, scripturePassages_1.getLessonPauses)(lessonInfo.meetLanguageId).afterStory +
|
|
464
|
+
lessonInfo.applicationDuration;
|
|
465
|
+
const diff = Math.abs(computedLength - totalDuration);
|
|
466
|
+
return { computedLength, diff, passed: diff < 3 };
|
|
467
|
+
}
|
|
@@ -30,8 +30,8 @@ type Meeting = {
|
|
|
30
30
|
payload: {
|
|
31
31
|
timeToCompletion: number;
|
|
32
32
|
isAudioFacilitator: boolean;
|
|
33
|
-
howManyShared
|
|
34
|
-
howManyObeyed
|
|
33
|
+
howManyShared: StoplightOption | undefined;
|
|
34
|
+
howManyObeyed: StoplightOption | undefined;
|
|
35
35
|
};
|
|
36
36
|
} | {
|
|
37
37
|
name: 'ScheduleNextMeeting';
|
package/dist/types/sets.d.ts
CHANGED
|
@@ -24,9 +24,12 @@ export interface Content {
|
|
|
24
24
|
export interface BaseInfo {
|
|
25
25
|
lessonNumber: string;
|
|
26
26
|
lessonTitle: string | undefined;
|
|
27
|
-
lessonIntroduction: string | undefined;
|
|
28
27
|
lessonLink: string;
|
|
29
28
|
sections: Section[];
|
|
29
|
+
/** Full duration in seconds of all of the lesson's fellowship questions. */
|
|
30
|
+
fellowshipDuration: number;
|
|
31
|
+
/** Full duration in seconds of all of the lesson's application questions. */
|
|
32
|
+
applicationDuration: number;
|
|
30
33
|
}
|
|
31
34
|
export interface Section {
|
|
32
35
|
id: string;
|
|
@@ -45,7 +48,7 @@ export interface DbsInfo extends BaseInfo, SetInfo, Lesson {
|
|
|
45
48
|
remoteFileName: string;
|
|
46
49
|
url: string;
|
|
47
50
|
};
|
|
48
|
-
|
|
51
|
+
introLength: number | undefined;
|
|
49
52
|
passagesString: string;
|
|
50
53
|
}
|
|
51
54
|
export interface VideoInfo extends BaseInfo, SetInfo, Lesson {
|