mirror of
https://github.com/nicetry247/offlineacademy.git
synced 2026-07-26 11:58:44 +00:00
feat: add multilingual subtitle tracks
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { buildSubtitleTrackMap, languageCodeToLabel, normalizeSubtitleLang } from '@/lib/subtitles'
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
const tracks = buildSubtitleTrackMap(
|
||||
['01 - Lesson.mp4', '02 - Other.mp4'],
|
||||
[
|
||||
'01 - Lesson.vtt',
|
||||
'01 - Lesson.en.vtt',
|
||||
'01 - Lesson.es.vtt',
|
||||
'01 - Lesson.pt-BR.srt',
|
||||
'02 - Other.ja.vtt',
|
||||
'unrelated.en.vtt',
|
||||
],
|
||||
(fileName) => `Course/Module/${fileName}`
|
||||
)
|
||||
|
||||
const lessonTracks = tracks.get('01 - Lesson') || []
|
||||
assert(lessonTracks.length === 4, `expected 4 subtitle tracks for 01 - Lesson, got ${lessonTracks.length}`)
|
||||
assert(lessonTracks[0].lang === 'en', 'legacy subtitle should become default English track')
|
||||
assert(lessonTracks[0].isDefault === true, 'first/default English track should be default')
|
||||
assert(lessonTracks.some(track => track.lang === 'es' && track.label === 'Spanish'), 'Spanish track missing')
|
||||
assert(lessonTracks.some(track => track.lang === 'pt-BR' && track.label === 'Portuguese (Brazil)'), 'pt-BR track missing')
|
||||
|
||||
const otherTracks = tracks.get('02 - Other') || []
|
||||
assert(otherTracks.length === 1, `expected 1 subtitle track for 02 - Other, got ${otherTracks.length}`)
|
||||
assert(otherTracks[0].lang === 'ja' && otherTracks[0].label === 'Japanese', 'Japanese track missing')
|
||||
|
||||
assert(normalizeSubtitleLang('PT-br') === 'pt-BR', 'pt-BR normalization failed')
|
||||
assert(languageCodeToLabel('fil') === 'Filipino', 'Filipino label failed')
|
||||
assert(!tracks.has('unrelated'), 'unrelated subtitle should not create a track group')
|
||||
|
||||
console.log('subtitle parser verification passed')
|
||||
@@ -0,0 +1,59 @@
|
||||
import { mkdir, rm, writeFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { scanCoursesDirectory } from '@/lib/scanner'
|
||||
|
||||
function assert(condition: unknown, message: string) {
|
||||
if (!condition) {
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const root = '/tmp/offlineacademy-subtitle-scan-smoke'
|
||||
const coursesRoot = join(root, 'My_Courses')
|
||||
const modulePath = join(coursesRoot, 'Subtitle Smoke Course', '01 - Module')
|
||||
|
||||
await rm(root, { recursive: true, force: true })
|
||||
await mkdir(modulePath, { recursive: true })
|
||||
|
||||
await writeFile(join(modulePath, '01 - Lesson.mp4'), '')
|
||||
await writeFile(join(modulePath, '01 - Lesson.vtt'), 'WEBVTT\n\n00:00:00.000 --> 00:00:01.000\nLegacy English\n')
|
||||
await writeFile(join(modulePath, '01 - Lesson.en.vtt'), 'WEBVTT\n\n00:00:00.000 --> 00:00:01.000\nEnglish\n')
|
||||
await writeFile(join(modulePath, '01 - Lesson.es.vtt'), 'WEBVTT\n\n00:00:00.000 --> 00:00:01.000\nEspañol\n')
|
||||
await writeFile(join(modulePath, '01 - Lesson.ja.vtt'), 'WEBVTT\n\n00:00:00.000 --> 00:00:01.000\n日本語\n')
|
||||
await writeFile(join(modulePath, '01 - Lesson.pt-BR.srt'), '1\n00:00:00,000 --> 00:00:01,000\nPortuguês\n')
|
||||
|
||||
await prisma.setting.upsert({
|
||||
where: { key: 'coursesRoot' },
|
||||
update: { value: coursesRoot },
|
||||
create: { key: 'coursesRoot', value: coursesRoot },
|
||||
})
|
||||
|
||||
const result = await scanCoursesDirectory({ maxConcurrency: 1, maxLessonsPerCourse: 10, maxModulesPerCourse: 5 })
|
||||
assert(result.errors.length === 0, `expected no scan errors, got ${JSON.stringify(result.errors)}`)
|
||||
|
||||
const lesson = await prisma.lesson.findFirst({
|
||||
where: { fileName: '01 - Lesson.mp4' },
|
||||
include: { subtitles: { orderBy: [{ isDefault: 'desc' }, { label: 'asc' }] } },
|
||||
})
|
||||
|
||||
assert(lesson, 'expected scanned lesson to exist')
|
||||
assert(lesson?.subtitlePath, 'expected legacy subtitlePath fallback to be set')
|
||||
assert(lesson?.subtitles.length === 5, `expected 5 subtitle tracks, got ${lesson?.subtitles.length}`)
|
||||
assert(lesson?.subtitles.some(track => track.lang === 'es' && track.label === 'Spanish'), 'expected Spanish subtitle track')
|
||||
assert(lesson?.subtitles.some(track => track.lang === 'ja' && track.label === 'Japanese'), 'expected Japanese subtitle track')
|
||||
assert(lesson?.subtitles.some(track => track.lang === 'pt-BR' && track.label === 'Portuguese (Brazil)' && track.format === 'srt'), 'expected pt-BR SRT subtitle track')
|
||||
assert(lesson?.subtitles.filter(track => track.isDefault).length === 1, 'expected exactly one default subtitle track')
|
||||
|
||||
console.log('subtitle scan verification passed')
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(error => {
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
Reference in New Issue
Block a user