1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-05 10:49:28 +02:00

Create and inject caption playlist in HLS master

This commit is contained in:
Chocobozzz 2025-04-08 15:26:02 +02:00
parent a7be820abc
commit 6e44e7e29a
No known key found for this signature in database
GPG key ID: 583A612D890159BE
49 changed files with 1368 additions and 401 deletions

View file

@ -23,11 +23,14 @@ import { VideoModel } from '@server/models/video/video.js'
import {
MStreamingPlaylistFiles,
MThumbnail,
MVideo, MVideoAP, MVideoCaption,
MVideo,
MVideoAP,
MVideoCaption,
MVideoCaptionLanguageUrl,
MVideoChapter,
MVideoFile,
MVideoFullLight, MVideoLiveWithSetting,
MVideoFullLight,
MVideoLiveWithSetting,
MVideoPassword
} from '@server/types/models/index.js'
import { MVideoSource } from '@server/types/models/video/video-source.js'
@ -37,11 +40,12 @@ import { extname, join } from 'path'
import { PassThrough, Readable } from 'stream'
import { AbstractUserExporter, ExportResult } from './abstract-user-exporter.js'
export class VideosExporter extends AbstractUserExporter <VideoExportJSON> {
constructor (private readonly options: ConstructorParameters<typeof AbstractUserExporter<VideoExportJSON>>[0] & {
withVideoFiles: boolean
}) {
export class VideosExporter extends AbstractUserExporter<VideoExportJSON> {
constructor (
private readonly options: ConstructorParameters<typeof AbstractUserExporter<VideoExportJSON>>[0] & {
withVideoFiles: boolean
}
) {
super(options)
}
@ -89,10 +93,10 @@ export class VideosExporter extends AbstractUserExporter <VideoExportJSON> {
const live = video.isLive
? await VideoLiveModel.loadByVideoIdWithSettings(videoId)
: undefined;
: undefined
// We already have captions, so we can set it to the video object
(video as any).VideoCaptions = captions
;(video as any).VideoCaptions = captions
// Then fetch more attributes for AP serialization
const videoAP = await video.lightAPToFullAP(undefined)
@ -320,7 +324,7 @@ export class VideosExporter extends AbstractUserExporter <VideoExportJSON> {
const relativePathsFromJSON = {
videoFile: null as string,
thumbnail: null as string,
captions: {} as { [ lang: string ]: string }
captions: {} as { [lang: string]: string }
}
if (this.options.withVideoFiles) {
@ -333,9 +337,10 @@ export class VideosExporter extends AbstractUserExporter <VideoExportJSON> {
archivePath: videoPath,
// Prefer using original file if possible
readStreamFactory: () => source?.keptOriginalFilename
? this.generateVideoSourceReadStream(source)
: this.generateVideoFileReadStream({ video, videoFile, separatedAudioFile })
readStreamFactory: () =>
source?.keptOriginalFilename
? this.generateVideoSourceReadStream(source)
: this.generateVideoFileReadStream({ video, videoFile, separatedAudioFile })
})
relativePathsFromJSON.videoFile = join(this.relativeStaticDirPath, videoPath)
@ -407,7 +412,7 @@ export class VideosExporter extends AbstractUserExporter <VideoExportJSON> {
private async generateCaptionReadStream (caption: MVideoCaption): Promise<Readable> {
if (caption.storage === FileStorage.FILE_SYSTEM) {
return createReadStream(caption.getFSPath())
return createReadStream(caption.getFSFilePath())
}
const { stream } = await getCaptionReadStream({ filename: caption.filename, rangeHeader: undefined })

View file

@ -37,7 +37,7 @@ import { LocalVideoCreator, ThumbnailOptions } from '@server/lib/local-video-cre
import { isLocalVideoFileAccepted } from '@server/lib/moderation.js'
import { Hooks } from '@server/lib/plugins/hooks.js'
import { isUserQuotaValid } from '@server/lib/user.js'
import { createLocalCaption } from '@server/lib/video-captions.js'
import { createLocalCaption, updateHLSMasterOnCaptionChange } from '@server/lib/video-captions.js'
import { buildNextVideoState } from '@server/lib/video-state.js'
import { VideoChannelModel } from '@server/models/video/video-channel.js'
import { VideoModel } from '@server/models/video/video.js'
@ -49,12 +49,33 @@ import { AbstractUserImporter } from './abstract-user-importer.js'
const lTags = loggerTagsFactory('user-import')
type ImportObject = VideoExportJSON['videos'][0]
type SanitizedObject = Pick<ImportObject, 'name' | 'duration' | 'channel' | 'privacy' | 'archiveFiles' | 'captions' | 'category' |
'licence' | 'language' | 'description' | 'support' | 'nsfw' | 'isLive' | 'commentsPolicy' | 'downloadEnabled' | 'waitTranscoding' |
'originallyPublishedAt' | 'tags' | 'live' | 'passwords' | 'source' | 'chapters'>
export class VideosImporter extends AbstractUserImporter <VideoExportJSON, ImportObject, SanitizedObject> {
type SanitizedObject = Pick<
ImportObject,
| 'name'
| 'duration'
| 'channel'
| 'privacy'
| 'archiveFiles'
| 'captions'
| 'category'
| 'licence'
| 'language'
| 'description'
| 'support'
| 'nsfw'
| 'isLive'
| 'commentsPolicy'
| 'downloadEnabled'
| 'waitTranscoding'
| 'originallyPublishedAt'
| 'tags'
| 'live'
| 'passwords'
| 'source'
| 'chapters'
>
export class VideosImporter extends AbstractUserImporter<VideoExportJSON, ImportObject, SanitizedObject> {
protected getImportObjects (json: VideoExportJSON) {
return json.videos
}
@ -257,6 +278,7 @@ export class VideosImporter extends AbstractUserImporter <VideoExportJSON, Impor
private async importCaptions (video: MVideoFullLight, videoImportData: SanitizedObject) {
const captionPaths: string[] = []
let updateHLS = false
for (const captionImport of videoImportData.captions) {
const relativeFilePath = videoImportData.archiveFiles?.captions?.[captionImport.language]
@ -270,7 +292,7 @@ export class VideosImporter extends AbstractUserImporter <VideoExportJSON, Impor
if (!await this.isFileValidOrLog(absoluteFilePath, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max)) continue
await createLocalCaption({
const caption = await createLocalCaption({
video,
language: captionImport.language,
path: absoluteFilePath,
@ -278,6 +300,12 @@ export class VideosImporter extends AbstractUserImporter <VideoExportJSON, Impor
})
captionPaths.push(absoluteFilePath)
if (caption.m3u8Filename) updateHLS = true
}
if (updateHLS && video.getHLSPlaylist()) {
await updateHLSMasterOnCaptionChange(video, video.getHLSPlaylist())
}
return captionPaths