waha-shared 1.0.316 → 1.0.317

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.
@@ -6,7 +6,6 @@ exports.convertSToString = convertSToString;
6
6
  exports.shouldShowLesson = shouldShowLesson;
7
7
  exports.getExpectedLessonDuration = getExpectedLessonDuration;
8
8
  const bibleStatuses_1 = require("../data/bibleStatuses");
9
- const mediaDurations_1 = require("../data/mediaDurations");
10
9
  const sets_1 = require("../data/sets");
11
10
  const specialIds_1 = require("../data/specialIds");
12
11
  const youtubeVideos_1 = require("../data/youtubeVideos");
@@ -96,52 +95,41 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
96
95
  id: 'title',
97
96
  index: 0,
98
97
  indexWithinChapter: 0,
99
- fileName: '',
100
- languageId,
98
+ path: '',
101
99
  },
102
100
  ];
103
101
  fellowshipQuestions.forEach((questionId, index) => {
104
- const fileName = `${languageInfo.contentLanguages.questionAudio}.${questionId}.mp3`;
105
102
  sections.push({
106
103
  id: questionId,
107
- languageId: languageInfo.contentLanguages.questionAudio,
108
104
  index: sections.length,
109
105
  header: index === 0 ? t.fellowship : undefined,
110
106
  chapter: sets_2.Chapter.FELLOWSHIP,
111
107
  indexWithinChapter: index,
112
108
  text: fellowshipQuestionsText[index],
113
- fileName,
109
+ path: (0, utils_1.join)(languageInfo.contentLanguages.questionAudio, 'questions', [languageInfo.contentLanguages.questionAudio, questionId, 'mp3'].join('.')),
114
110
  });
115
111
  });
116
112
  if (specialIds_1.specialIds.evaluationQuestionLessonIds.includes(lesson.lessonId)) {
117
113
  sections.push({
118
114
  id: 'eq',
119
- languageId: languageInfo.contentLanguages.intros,
120
115
  index: sections.length,
121
116
  chapter: sets_2.Chapter.INTRODUCTION,
122
117
  indexWithinChapter: 0,
123
118
  pauseBefore: 0,
124
119
  text: t.introductions.introductions.eq,
125
- fileName: `${languageInfo.contentLanguages.intros}.eq.mp3`,
120
+ path: (0, utils_1.join)(languageInfo.contentLanguages.intros, [languageInfo.contentLanguages.intros, 'eq', 'mp3'].join('.')),
126
121
  });
127
122
  }
128
123
  else if (specialIds_1.specialIds.growingAsDmcSetIds.includes(setInfo.setId)) {
129
- const introFileName = [
130
- languageInfo.contentLanguages.intros,
131
- lessonId,
132
- 'intro',
133
- 'mp3',
134
- ].join('.');
135
124
  sections.push({
136
125
  id: 'introduction',
137
- languageId: languageInfo.contentLanguages.intros,
138
126
  index: sections.length,
139
127
  chapter: sets_2.Chapter.INTRODUCTION,
140
128
  indexWithinChapter: 0,
141
129
  pauseBefore: languageInfo.lessonPauses.beforeFtb,
142
130
  header: t.introductions.introduction,
143
131
  text: t.introductions.introductions[lessonId],
144
- fileName: introFileName,
132
+ path: (0, utils_1.join)(languageInfo.contentLanguages.intros, [languageInfo.contentLanguages.intros, lessonId, 'intro', 'mp3'].join('.')),
145
133
  });
146
134
  }
147
135
  let lastBook = null;
@@ -155,18 +143,17 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
155
143
  const ftbRequired = lastBook !== passageInfo.book;
156
144
  sections.push({
157
145
  id: `${bibleId};${passageId}`,
158
- languageId,
159
146
  index: sections.length,
160
147
  chapter: sets_2.Chapter.STORY,
161
148
  indexWithinChapter: index,
162
149
  pauseBefore: ftbRequired
163
150
  ? languageInfo.lessonPauses.beforeFtb
164
151
  : languageInfo.lessonPauses.betweenPassages,
165
- ftbFileName: ftbRequired
166
- ? `${languageInfo.contentLanguages.ftbs}.${passageInfo.book}.mp3`
152
+ ftbPath: ftbRequired
153
+ ? (0, utils_1.join)(languageInfo.contentLanguages.ftbs, 'ftb', [languageInfo.contentLanguages.ftbs, passageInfo.book, 'mp3'].join('.'))
167
154
  : undefined,
168
- fileName: bibleStatuses_1.bibleStatuses[bibleId]?.customPassages?.[passageId]
169
- ? passageId + '.mp3'
155
+ path: bibleStatuses_1.bibleStatuses[bibleId]?.customPassages?.[passageId]
156
+ ? (0, utils_1.join)('audio_bibles', bibleId, 'custom_passages', [passageId, 'mp3'].join('.'))
170
157
  : passageInfo.chapterAudioFileName,
171
158
  header: (0, scripturePassages_1.getPassagesString)([{ bibleId, passageId }]),
172
159
  });
@@ -191,16 +178,21 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
191
178
  const ftbRequired = lastBook !== passageInfo.book;
192
179
  sections.push({
193
180
  id: `${modifier.bibleId};${passageId}`,
194
- languageId,
195
181
  index: sections.length,
196
182
  chapter: sets_2.Chapter.STORY,
197
183
  indexWithinChapter: concatStoryIndex,
198
- fileName: passageInfo.chapterAudioFileName,
184
+ path: bibleStatuses_1.bibleStatuses[modifier.bibleId]?.customPassages?.[passageId]
185
+ ? (0, utils_1.join)('audio_bibles', modifier.bibleId, 'custom_passages', [passageId, 'mp3'].join('.'))
186
+ : passageInfo.chapterAudioFileName,
199
187
  pauseBefore: ftbRequired
200
188
  ? languageInfo.lessonPauses.beforeFtb
201
189
  : languageInfo.lessonPauses.betweenPassages,
202
- ftbFileName: ftbRequired
203
- ? `${languageInfo.contentLanguages.ftbs}.${passageInfo.book}.mp3`
190
+ ftbPath: ftbRequired
191
+ ? (0, utils_1.join)(languageInfo.contentLanguages.ftbs, 'ftb', [
192
+ languageInfo.contentLanguages.ftbs,
193
+ passageInfo.book,
194
+ 'mp3',
195
+ ].join('.'))
204
196
  : undefined,
205
197
  header: (0, scripturePassages_1.getPassagesString)([{ bibleId: modifier.bibleId, passageId }]),
206
198
  });
@@ -209,14 +201,8 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
209
201
  });
210
202
  });
211
203
  applicationQuestions.forEach((questionId, index) => {
212
- const fileName = questionId.includes('video')
213
- ? `${languageInfo.contentLanguages.trainingVideos}.${lessonId}.mp4`
214
- : `${languageInfo.contentLanguages.questionAudio}.${questionId}.mp3`;
215
204
  sections.push({
216
205
  id: questionId,
217
- languageId: questionId.includes('video')
218
- ? languageInfo.contentLanguages.trainingVideos
219
- : languageInfo.contentLanguages.questionAudio,
220
206
  index: sections.length,
221
207
  chapter: sets_2.Chapter.APPLICATION,
222
208
  indexWithinChapter: index,
@@ -225,7 +211,17 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
225
211
  text: questionId.includes('video')
226
212
  ? t.training_video
227
213
  : applicationQuestionsText[index],
228
- fileName,
214
+ path: questionId.includes('video')
215
+ ? (0, utils_1.join)(languageInfo.contentLanguages.trainingVideos, 'course', 'videos_compressed', [
216
+ languageInfo.contentLanguages.trainingVideos,
217
+ lessonId,
218
+ 'mp4',
219
+ ].join('.'))
220
+ : (0, utils_1.join)(languageInfo.contentLanguages.questionAudio, 'questions', [
221
+ languageInfo.contentLanguages.questionAudio,
222
+ questionId,
223
+ 'mp3',
224
+ ].join('.')),
229
225
  });
230
226
  });
231
227
  const baseInfo = {
@@ -241,86 +237,62 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
241
237
  .filter((s) => s.chapter === sets_2.Chapter.FELLOWSHIP)
242
238
  .reduce((sum, section) => sum +
243
239
  (section.pauseBefore ?? 0) +
244
- (mediaDurations_1.mediaDurations[languageInfo.contentLanguages.questionAudio]?.[section.fileName] ?? 0), 0);
240
+ ((0, utils_1.getCachedDuration)(section.path) ?? 0), 0);
245
241
  let applicationDuration = sections
246
242
  .filter((s) => s.chapter === sets_2.Chapter.APPLICATION)
247
243
  .reduce((sum, section) => sum +
248
244
  (section.pauseBefore ?? 0) +
249
- (mediaDurations_1.mediaDurations[languageInfo.contentLanguages.questionAudio]?.[section.fileName] ?? 0), 0);
245
+ ((0, utils_1.getCachedDuration)(section.path) ?? 0), 0);
250
246
  if (specialIds_1.specialIds.evaluationQuestionLessonIds.includes(lesson.lessonId)) {
251
- const eqId = `${languageInfo.contentLanguages.intros}.eq`;
252
- const eqFileName = `${eqId}.mp3`;
253
- const eqPath = (0, utils_1.join)(languageInfo.contentLanguages.intros, 'introductions', eqFileName);
254
247
  return {
255
248
  type: 'eq',
256
249
  ...baseInfo,
257
250
  fellowshipDuration,
258
251
  applicationDuration,
259
- eq: {
260
- id: eqId,
261
- localFileName: eqFileName,
262
- remoteFileName: eqFileName,
263
- path: eqPath,
264
- url: (0, utils_1.firebasePath)(eqPath),
265
- },
252
+ eqPath: (0, utils_1.join)(languageInfo.contentLanguages.intros, 'introductions', `${languageInfo.contentLanguages.intros}.eq.mp3`),
266
253
  };
267
254
  }
268
- else if (setInfo.setId === specialIds_1.specialIds.dmCourseSetId ||
269
- languageId === 'ase') {
270
- let video;
271
- let trainingVideo;
255
+ const assetVersion = languageInfo.audioAssetVersion ?? '';
256
+ if (setInfo.setId === specialIds_1.specialIds.dmCourseSetId || languageId === 'ase') {
257
+ let videoPath;
258
+ let trainingVideoPath;
259
+ let trainingVideoNarrationPath;
260
+ let trainingVideoOriginalPath;
261
+ let trainingVideoNarrationWithMusicPath;
262
+ let youtubeLink;
272
263
  if (languageId === 'ase') {
273
- const aseVideoLessonId = `${languageId}.${lessonId}`;
274
- const aseVideoLocalFileName = `${aseVideoLessonId}.mp4`;
275
- const aseVideoRemoteFileName = `${aseVideoLessonId}.1080.mp4`;
276
- const aseVideoPath = (0, utils_1.join)(languageId, 'full_lessons', setInfo.setId, aseVideoRemoteFileName);
277
- video = {
278
- id: aseVideoLessonId,
279
- localFileName: aseVideoLocalFileName,
280
- remoteFileName: aseVideoRemoteFileName,
281
- path: aseVideoPath,
282
- url: (0, utils_1.firebasePath)(aseVideoPath),
283
- };
284
- trainingVideo = undefined;
264
+ videoPath = (0, utils_1.join)(languageId, `full_lessons${assetVersion}`, setInfo.setId, [languageId, lessonId, '1080', 'mp4'].join('.'));
265
+ trainingVideoPath = undefined;
285
266
  }
286
267
  else {
287
- const videoLessonId = [
268
+ videoPath = (0, utils_1.join)(languageId, `full_lessons`, setInfo.setId, [
288
269
  languageId,
289
270
  languageInfo.contentLanguages.trainingVideos,
290
271
  lessonId,
291
- ].join('.');
292
- const videoLessonRemoteFileName = `${videoLessonId}.1080.mp4`;
293
- const videoLessonLocalFileName = `${videoLessonId}.mp4`;
294
- const videoPath = (0, utils_1.join)(languageId, 'full_lessons', setInfo.setId, videoLessonRemoteFileName);
295
- video = {
296
- id: videoLessonId,
297
- localFileName: videoLessonLocalFileName,
298
- remoteFileName: videoLessonRemoteFileName,
299
- path: videoPath,
300
- url: (0, utils_1.firebasePath)(videoPath),
301
- };
302
- const trainingVideoId = [
303
- languageInfo.contentLanguages.trainingVideos,
304
- lessonId,
305
- ].join('.');
306
- const trainingVideoFileName = `${trainingVideoId}.mp4`;
272
+ '1080',
273
+ 'mp4',
274
+ ].join('.'));
275
+ const trainingVideosDir = (0, utils_1.join)(languageInfo.contentLanguages.trainingVideos, 'course');
276
+ trainingVideoPath = (0, utils_1.join)(trainingVideosDir, 'videos_compressed', [languageInfo.contentLanguages.trainingVideos, lessonId, 'mp4'].join('.'));
277
+ trainingVideoNarrationPath = (0, utils_1.join)(trainingVideosDir, 'audio', [languageInfo.contentLanguages.trainingVideos, lessonId, 'mp3'].join('.'));
278
+ trainingVideoOriginalPath = (0, utils_1.join)(trainingVideosDir, 'videos_export', [languageInfo.contentLanguages.trainingVideos, lessonId, 'm4v'].join('.'));
279
+ trainingVideoNarrationWithMusicPath = (0, utils_1.join)(trainingVideosDir, 'audio_with_music', [languageInfo.contentLanguages.trainingVideos, lessonId, 'mp3'].join('.'));
307
280
  // Add the length of the training video to the application duration.
308
- applicationDuration +=
309
- mediaDurations_1.mediaDurations[languageInfo.contentLanguages.trainingVideos]?.[trainingVideoFileName] ?? 0;
310
- const trainingVideoPath = (0, utils_1.join)(languageInfo.contentLanguages.trainingVideos, 'course', 'videos_compressed', trainingVideoFileName);
311
- trainingVideo = {
312
- id: trainingVideoId,
313
- localFileName: trainingVideoFileName,
314
- remoteFileName: trainingVideoFileName,
315
- path: trainingVideoPath,
316
- url: (0, utils_1.firebasePath)(trainingVideoPath),
317
- };
281
+ applicationDuration += (0, utils_1.getCachedDuration)(trainingVideoPath) ?? 0;
282
+ if (videoPath && (0, utils_1.basename)(videoPath) in youtubeVideos_1.youtubeVideos)
283
+ youtubeLink =
284
+ 'https://www.youtube.com/watch?v=' +
285
+ youtubeVideos_1.youtubeVideos[(0, utils_1.basename)(videoPath)];
286
+ if (specialIds_1.specialIds.dmCourseVideoOnlyLessonIds.includes(lessonId))
287
+ videoPath = trainingVideoPath;
318
288
  }
319
- let youtubeLink;
320
- if (video.remoteFileName && video.remoteFileName in youtubeVideos_1.youtubeVideos)
321
- youtubeLink =
322
- 'https://www.youtube.com/watch?v=' +
323
- youtubeVideos_1.youtubeVideos[video.remoteFileName];
289
+ const fullPath = (0, utils_1.join)(languageId, `full_lessons${assetVersion}`, setInfo.setId, [
290
+ languageId,
291
+ languageInfo.contentLanguages.trainingVideos,
292
+ lessonId,
293
+ 'mp3',
294
+ ].join('.'));
295
+ const storyPath = (0, utils_1.join)(languageId, `full_lessons${assetVersion}`, setInfo.setId, [languageId, lessonId, 'story', 'mp3'].join('.'));
324
296
  return {
325
297
  type: 'video',
326
298
  ...baseInfo,
@@ -328,44 +300,29 @@ function getLessonInfo({ lessonId, languageInfo, setInfo, t, useSpokenQuestions,
328
300
  applicationDuration,
329
301
  lessonSectionHeader: thisSetTranslations?.sectionHeaders?.[lessonId],
330
302
  lessonSectionBody: thisSetTranslations?.sectionBodies?.[lessonId],
331
- video,
332
- trainingVideo,
303
+ videoPath,
304
+ trainingVideoPath,
305
+ trainingVideoNarrationPath,
306
+ trainingVideoOriginalPath,
307
+ trainingVideoNarrationWithMusicPath,
308
+ fullPath: lesson.s.length > 0 ? fullPath : undefined,
309
+ storyPath: lesson.s.length > 0 ? storyPath : undefined,
333
310
  youtubeLink,
334
311
  passagesString,
335
312
  };
336
313
  }
337
314
  else {
338
- const assetVersion = languageInfo.audioAssetVersion ?? '';
339
- const fullLessonId = `${languageId}.${lessonId}`;
340
- const fullLessonFileName = `${fullLessonId}.mp3`;
341
- const fullLessonPath = (0, utils_1.join)(languageId, `full_lessons${assetVersion}`, setInfo.setId, fullLessonFileName);
342
- const storyId = fullLessonId + '.story';
343
- const storyFileName = `${storyId}.mp3`;
344
- const storyPath = (0, utils_1.join)(languageId, `full_lessons${assetVersion}`, setInfo.setId, storyFileName);
315
+ const fullPath = (0, utils_1.join)(languageId, `full_lessons${assetVersion}`, setInfo.setId, `${languageId}.${lessonId}.mp3`);
316
+ const storyPath = fullPath.replace('.mp3', '.story.mp3');
345
317
  return {
346
318
  type: 'dbs',
347
319
  ...baseInfo,
348
320
  fellowshipDuration,
349
321
  applicationDuration,
350
322
  passagesString,
351
- full: {
352
- id: fullLessonId,
353
- localFileName: fullLessonFileName,
354
- remoteFileName: fullLessonFileName,
355
- path: fullLessonPath,
356
- url: (0, utils_1.firebasePath)(fullLessonPath),
357
- },
358
- story: {
359
- id: storyId,
360
- localFileName: storyFileName,
361
- remoteFileName: storyFileName,
362
- path: storyPath,
363
- url: (0, utils_1.firebasePath)(storyPath),
364
- },
365
- dbsCast: {
366
- remoteFileName: `${languageId}.${lessonId}.1080.mp4`,
367
- url: (0, utils_1.firebasePath)((0, utils_1.join)(languageId, `full_lessons${assetVersion}`, setInfo.setId, `${languageId}.${lessonId}.1080.mp4`)),
368
- },
323
+ fullPath,
324
+ storyPath,
325
+ dbsCastPath: (0, utils_1.join)(languageId, `full_lessons`, setInfo.setId, [languageId, lessonId, '1080', 'mp4'].join('.')),
369
326
  };
370
327
  }
371
328
  }
@@ -410,27 +367,25 @@ function convertSToString(time) {
410
367
  }
411
368
  }
412
369
  /** Determines whether a lesson should be visible in Waha. */
413
- function shouldShowLesson(lessonInfo, languageInfo) {
370
+ function shouldShowLesson(lessonInfo) {
414
371
  if (lessonInfo?.lessonTitle === undefined || lessonInfo.lessonTitle === '') {
415
372
  console.log('Missing lesson info or lessonTitle lesson:', lessonInfo?.lessonId);
416
373
  return false;
417
374
  }
418
375
  else if (lessonInfo.type === 'eq' &&
419
- mediaDurations_1.mediaDurations[languageInfo.contentLanguages.intros]?.[lessonInfo.eq.remoteFileName] === undefined) {
376
+ (0, utils_1.getCachedDuration)(lessonInfo.eqPath) === undefined) {
420
377
  console.log('Missing remote eq file for lesson:', lessonInfo.lessonId);
421
378
  return false;
422
379
  }
423
380
  else if (lessonInfo.type === 'dbs' &&
424
- mediaDurations_1.mediaDurations[lessonInfo.languageId]?.[lessonInfo.full.remoteFileName] ===
425
- undefined) {
381
+ (0, utils_1.getCachedDuration)(lessonInfo.fullPath) === undefined) {
426
382
  console.log('Missing remote dbs file for lesson:', lessonInfo.lessonId);
427
383
  return false;
428
384
  }
429
385
  else if (lessonInfo.type === 'video' &&
430
- lessonInfo.video &&
431
- mediaDurations_1.mediaDurations[lessonInfo.languageId]?.[lessonInfo.video.remoteFileName] ===
432
- undefined) {
433
- console.log('Missing remote video for lesson:', lessonInfo.video.remoteFileName);
386
+ lessonInfo.videoPath &&
387
+ (0, utils_1.getCachedDuration)(lessonInfo.videoPath) === undefined) {
388
+ console.log('Missing remote video for lesson:', lessonInfo.lessonId);
434
389
  return false;
435
390
  }
436
391
  else
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Minimal structural shape of `@google-cloud/storage`'s `Bucket`. Defined
3
+ * structurally so this module doesn't pull `@google-cloud/storage` into the
4
+ * shared package's dependency graph — callers pass the real bucket.
5
+ */
6
+ export interface UploadBucket {
7
+ file(path: string): {
8
+ exists(): Promise<[boolean]>;
9
+ setMetadata(meta: {
10
+ metadata: Record<string, string>;
11
+ }): Promise<unknown>;
12
+ };
13
+ upload(localPath: string, options: {
14
+ destination: string;
15
+ contentType: string;
16
+ public?: boolean;
17
+ }): Promise<unknown>;
18
+ }
19
+ export type UploadResult = 'uploaded' | 'skipped';
20
+ /**
21
+ * Upload a local file to a Cloud Storage bucket. The content type is inferred
22
+ * from the file extension via {@link CONTENT_TYPE_BY_EXTENSION}. Returns
23
+ * `'skipped'` if the remote file already exists and `overwrite` is falsy,
24
+ * `'uploaded'` otherwise. Throws if the underlying bucket operation fails.
25
+ *
26
+ * Sets a `duration` metadata field (in seconds) from ffprobe when probing
27
+ * succeeds; metadata failures are swallowed since they're non-fatal.
28
+ */
29
+ export declare function uploadFile({ bucket, localPath, overwrite, remotePath, }: {
30
+ bucket: UploadBucket;
31
+ localPath: string;
32
+ remotePath: string;
33
+ overwrite?: boolean;
34
+ }): Promise<UploadResult>;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadFile = uploadFile;
4
+ const path_1 = require("path");
5
+ const ffmpeg_1 = require("./ffmpeg");
6
+ const CONTENT_TYPE_BY_EXTENSION = {
7
+ '.mp3': 'audio/mpeg',
8
+ '.mp4': 'video/mp4',
9
+ };
10
+ function contentTypeFor(path) {
11
+ const ext = (0, path_1.extname)(path).toLowerCase();
12
+ const contentType = CONTENT_TYPE_BY_EXTENSION[ext];
13
+ if (!contentType)
14
+ throw new Error(`uploadFile: no content type mapped for extension '${ext}' (path: ${path}). ` +
15
+ `Add it to CONTENT_TYPE_BY_EXTENSION in shared/functions/upload.ts.`);
16
+ return contentType;
17
+ }
18
+ /**
19
+ * Upload a local file to a Cloud Storage bucket. The content type is inferred
20
+ * from the file extension via {@link CONTENT_TYPE_BY_EXTENSION}. Returns
21
+ * `'skipped'` if the remote file already exists and `overwrite` is falsy,
22
+ * `'uploaded'` otherwise. Throws if the underlying bucket operation fails.
23
+ *
24
+ * Sets a `duration` metadata field (in seconds) from ffprobe when probing
25
+ * succeeds; metadata failures are swallowed since they're non-fatal.
26
+ */
27
+ async function uploadFile({ bucket, localPath, overwrite, remotePath, }) {
28
+ const contentType = contentTypeFor(localPath);
29
+ const [exists] = await bucket.file(remotePath).exists();
30
+ if (exists && !overwrite)
31
+ return 'skipped';
32
+ await bucket.upload(localPath, {
33
+ destination: remotePath,
34
+ contentType,
35
+ public: true,
36
+ });
37
+ try {
38
+ const duration = await (0, ffmpeg_1.getAudioDuration)(localPath);
39
+ if (!isNaN(duration) && duration > 0) {
40
+ await bucket.file(remotePath).setMetadata({
41
+ metadata: { duration: duration.toString() },
42
+ });
43
+ }
44
+ }
45
+ catch {
46
+ // Non-fatal — proceed even if setting duration metadata fails.
47
+ }
48
+ return 'uploaded';
49
+ }
@@ -1,2 +1,7 @@
1
1
  export declare const join: (...parts: string[]) => string;
2
- export declare const firebasePath: (path: string) => string;
2
+ export declare const basename: (p: string) => string;
3
+ export declare const first: (p: string) => string;
4
+ export declare const dirname: (p: string) => string;
5
+ export declare const extname: (p: string) => string;
6
+ export declare const basenamenoext: (p: string) => string;
7
+ export declare const getCachedDuration: (path: string) => number | undefined;
@@ -1,8 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.firebasePath = exports.join = void 0;
4
- const firebase_1 = require("../data/firebase");
3
+ exports.getCachedDuration = exports.basenamenoext = exports.extname = exports.dirname = exports.first = exports.basename = exports.join = void 0;
4
+ const mediaDurations_1 = require("../data/mediaDurations");
5
5
  const join = (...parts) => parts.join('/').replace(/\/+/g, '/');
6
6
  exports.join = join;
7
- const firebasePath = (path) => firebase_1.firebaseUrl + encodeURIComponent(path) + `?alt=media`;
8
- exports.firebasePath = firebasePath;
7
+ const basename = (p) => p.split('/').pop();
8
+ exports.basename = basename;
9
+ const first = (p) => p.split('/')[0];
10
+ exports.first = first;
11
+ const dirname = (p) => p.substring(0, p.lastIndexOf('/'));
12
+ exports.dirname = dirname;
13
+ const extname = (p) => p.slice(p.lastIndexOf('.'));
14
+ exports.extname = extname;
15
+ const basenamenoext = (p) => {
16
+ const base = (0, exports.basename)(p);
17
+ const dotIndex = base.lastIndexOf('.');
18
+ return dotIndex !== -1 ? base.slice(0, dotIndex) : base;
19
+ };
20
+ exports.basenamenoext = basenamenoext;
21
+ const getCachedDuration = (path) => mediaDurations_1.mediaDurations[(0, exports.first)(path)]?.[(0, exports.basename)(path)];
22
+ exports.getCachedDuration = getCachedDuration;
@@ -13,13 +13,6 @@ export interface SetInfo extends Set {
13
13
  setTag?: string;
14
14
  setLink: string;
15
15
  }
16
- export interface Content {
17
- id: string;
18
- localFileName: string;
19
- remoteFileName: string;
20
- path: string;
21
- url: string;
22
- }
23
16
  export interface BaseInfo extends LanguageInfo {
24
17
  lessonNumber: string;
25
18
  lessonTitle: string | undefined;
@@ -32,7 +25,6 @@ export interface BaseInfo extends LanguageInfo {
32
25
  }
33
26
  export interface Section {
34
27
  id: string;
35
- languageId: string;
36
28
  header?: string;
37
29
  text?: string;
38
30
  index: number;
@@ -44,8 +36,8 @@ export interface Section {
44
36
  * Filename for the "From the Book" clip to insert before this section, if
45
37
  * there is one.
46
38
  */
47
- ftbFileName?: string;
48
- fileName: string;
39
+ ftbPath?: string;
40
+ path: string;
49
41
  }
50
42
  export interface EnrichedSection extends Section {
51
43
  startTime: number;
@@ -60,18 +52,29 @@ export interface EnrichedSection extends Section {
60
52
  }
61
53
  export interface DbsInfo extends BaseInfo, SetInfo, Lesson {
62
54
  type: 'dbs';
63
- full: Content;
64
- story: Content;
65
- dbsCast: {
66
- remoteFileName: string;
67
- url: string;
68
- };
55
+ fullPath: string;
56
+ storyPath: string;
57
+ dbsCastPath: string;
69
58
  passagesString: string;
70
59
  }
71
60
  export interface VideoInfo extends BaseInfo, SetInfo, Lesson {
72
61
  type: 'video';
73
- video: Content;
74
- trainingVideo: Content | undefined;
62
+ videoPath: string;
63
+ trainingVideoPath: string | undefined;
64
+ trainingVideoNarrationPath: string | undefined;
65
+ trainingVideoNarrationWithMusicPath: string | undefined;
66
+ trainingVideoOriginalPath: string | undefined;
67
+ /**
68
+ * Full audio file (training-video audio + scripture + questions). Populated
69
+ * only for video lessons that have audio to play (e.g. dmCourse lessons with
70
+ * scripture passages). Video-only lessons leave this undefined.
71
+ */
72
+ fullPath?: string;
73
+ /**
74
+ * Story audio file (scripture passages only). Same population rules as
75
+ * `full`.
76
+ */
77
+ storyPath?: string;
75
78
  lessonSectionHeader: string | undefined;
76
79
  lessonSectionBody: string | undefined;
77
80
  youtubeLink: string | undefined;
@@ -79,7 +82,7 @@ export interface VideoInfo extends BaseInfo, SetInfo, Lesson {
79
82
  }
80
83
  export interface EqInfo extends BaseInfo, SetInfo, Lesson {
81
84
  type: 'eq';
82
- eq: Content;
85
+ eqPath: string;
83
86
  }
84
87
  export type LessonInfo = DbsInfo | VideoInfo | EqInfo;
85
88
  export declare enum Chapter {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waha-shared",
3
- "version": "1.0.316",
3
+ "version": "1.0.317",
4
4
  "author": "Waha",
5
5
  "dependencies": {
6
6
  "@types/signale": "^1.4.7",