aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/transcoding/hls-transcoding.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-04-21 14:55:10 +0200
committerChocobozzz <chocobozzz@cpy.re>2023-05-09 08:57:34 +0200
commit0c9668f77901e7540e2c7045eb0f2974a4842a69 (patch)
tree226d3dd1565b0bb56588897af3b8530e6216e96b /server/lib/transcoding/hls-transcoding.ts
parent6bcb854cdea8688a32240bc5719c7d139806e00b (diff)
downloadPeerTube-0c9668f77901e7540e2c7045eb0f2974a4842a69.tar.gz
PeerTube-0c9668f77901e7540e2c7045eb0f2974a4842a69.tar.zst
PeerTube-0c9668f77901e7540e2c7045eb0f2974a4842a69.zip
Implement remote runner jobs in server
Move ffmpeg functions to @shared
Diffstat (limited to 'server/lib/transcoding/hls-transcoding.ts')
-rw-r--r--server/lib/transcoding/hls-transcoding.ts181
1 files changed, 181 insertions, 0 deletions
diff --git a/server/lib/transcoding/hls-transcoding.ts b/server/lib/transcoding/hls-transcoding.ts
new file mode 100644
index 000000000..cffa859c7
--- /dev/null
+++ b/server/lib/transcoding/hls-transcoding.ts
@@ -0,0 +1,181 @@
1import { MutexInterface } from 'async-mutex'
2import { Job } from 'bullmq'
3import { ensureDir, move, stat } from 'fs-extra'
4import { basename, extname as extnameUtil, join } from 'path'
5import { retryTransactionWrapper } from '@server/helpers/database-utils'
6import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
7import { sequelizeTypescript } from '@server/initializers/database'
8import { MVideo, MVideoFile } from '@server/types/models'
9import { pick } from '@shared/core-utils'
10import { getVideoStreamDuration, getVideoStreamFPS } from '@shared/ffmpeg'
11import { VideoResolution } from '@shared/models'
12import { CONFIG } from '../../initializers/config'
13import { VideoFileModel } from '../../models/video/video-file'
14import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
15import { updatePlaylistAfterFileChange } from '../hls'
16import { generateHLSVideoFilename, getHlsResolutionPlaylistFilename } from '../paths'
17import { buildFileMetadata } from '../video-file'
18import { VideoPathManager } from '../video-path-manager'
19import { buildFFmpegVOD } from './shared'
20
21// Concat TS segments from a live video to a fragmented mp4 HLS playlist
22export async function generateHlsPlaylistResolutionFromTS (options: {
23 video: MVideo
24 concatenatedTsFilePath: string
25 resolution: VideoResolution
26 fps: number
27 isAAC: boolean
28 inputFileMutexReleaser: MutexInterface.Releaser
29}) {
30 return generateHlsPlaylistCommon({
31 type: 'hls-from-ts' as 'hls-from-ts',
32 inputPath: options.concatenatedTsFilePath,
33
34 ...pick(options, [ 'video', 'resolution', 'fps', 'inputFileMutexReleaser', 'isAAC' ])
35 })
36}
37
38// Generate an HLS playlist from an input file, and update the master playlist
39export function generateHlsPlaylistResolution (options: {
40 video: MVideo
41 videoInputPath: string
42 resolution: VideoResolution
43 fps: number
44 copyCodecs: boolean
45 inputFileMutexReleaser: MutexInterface.Releaser
46 job?: Job
47}) {
48 return generateHlsPlaylistCommon({
49 type: 'hls' as 'hls',
50 inputPath: options.videoInputPath,
51
52 ...pick(options, [ 'video', 'resolution', 'fps', 'copyCodecs', 'inputFileMutexReleaser', 'job' ])
53 })
54}
55
56export async function onHLSVideoFileTranscoding (options: {
57 video: MVideo
58 videoFile: MVideoFile
59 videoOutputPath: string
60 m3u8OutputPath: string
61}) {
62 const { video, videoFile, videoOutputPath, m3u8OutputPath } = options
63
64 // Create or update the playlist
65 const playlist = await retryTransactionWrapper(() => {
66 return sequelizeTypescript.transaction(async transaction => {
67 return VideoStreamingPlaylistModel.loadOrGenerate(video, transaction)
68 })
69 })
70 videoFile.videoStreamingPlaylistId = playlist.id
71
72 const mutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
73
74 try {
75 // VOD transcoding is a long task, refresh video attributes
76 await video.reload()
77
78 const videoFilePath = VideoPathManager.Instance.getFSVideoFileOutputPath(playlist, videoFile)
79 await ensureDir(VideoPathManager.Instance.getFSHLSOutputPath(video))
80
81 // Move playlist file
82 const resolutionPlaylistPath = VideoPathManager.Instance.getFSHLSOutputPath(video, basename(m3u8OutputPath))
83 await move(m3u8OutputPath, resolutionPlaylistPath, { overwrite: true })
84 // Move video file
85 await move(videoOutputPath, videoFilePath, { overwrite: true })
86
87 // Update video duration if it was not set (in case of a live for example)
88 if (!video.duration) {
89 video.duration = await getVideoStreamDuration(videoFilePath)
90 await video.save()
91 }
92
93 const stats = await stat(videoFilePath)
94
95 videoFile.size = stats.size
96 videoFile.fps = await getVideoStreamFPS(videoFilePath)
97 videoFile.metadata = await buildFileMetadata(videoFilePath)
98
99 await createTorrentAndSetInfoHash(playlist, videoFile)
100
101 const oldFile = await VideoFileModel.loadHLSFile({
102 playlistId: playlist.id,
103 fps: videoFile.fps,
104 resolution: videoFile.resolution
105 })
106
107 if (oldFile) {
108 await video.removeStreamingPlaylistVideoFile(playlist, oldFile)
109 await oldFile.destroy()
110 }
111
112 const savedVideoFile = await VideoFileModel.customUpsert(videoFile, 'streaming-playlist', undefined)
113
114 await updatePlaylistAfterFileChange(video, playlist)
115
116 return { resolutionPlaylistPath, videoFile: savedVideoFile }
117 } finally {
118 mutexReleaser()
119 }
120}
121
122// ---------------------------------------------------------------------------
123
124async function generateHlsPlaylistCommon (options: {
125 type: 'hls' | 'hls-from-ts'
126 video: MVideo
127 inputPath: string
128
129 resolution: VideoResolution
130 fps: number
131
132 inputFileMutexReleaser: MutexInterface.Releaser
133
134 copyCodecs?: boolean
135 isAAC?: boolean
136
137 job?: Job
138}) {
139 const { type, video, inputPath, resolution, fps, copyCodecs, isAAC, job, inputFileMutexReleaser } = options
140 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
141
142 const videoTranscodedBasePath = join(transcodeDirectory, type)
143 await ensureDir(videoTranscodedBasePath)
144
145 const videoFilename = generateHLSVideoFilename(resolution)
146 const videoOutputPath = join(videoTranscodedBasePath, videoFilename)
147
148 const resolutionPlaylistFilename = getHlsResolutionPlaylistFilename(videoFilename)
149 const m3u8OutputPath = join(videoTranscodedBasePath, resolutionPlaylistFilename)
150
151 const transcodeOptions = {
152 type,
153
154 inputPath,
155 outputPath: m3u8OutputPath,
156
157 resolution,
158 fps,
159 copyCodecs,
160
161 isAAC,
162
163 inputFileMutexReleaser,
164
165 hlsPlaylist: {
166 videoFilename
167 }
168 }
169
170 await buildFFmpegVOD(job).transcode(transcodeOptions)
171
172 const newVideoFile = new VideoFileModel({
173 resolution,
174 extname: extnameUtil(videoFilename),
175 size: 0,
176 filename: videoFilename,
177 fps: -1
178 })
179
180 await onHLSVideoFileTranscoding({ video, videoFile: newVideoFile, videoOutputPath, m3u8OutputPath })
181}