aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/activitypub/videos/shared/abstract-builder.ts12
-rw-r--r--server/lib/activitypub/videos/shared/object-to-model-attributes.ts13
-rw-r--r--server/lib/hls.ts35
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts3
-rw-r--r--server/lib/job-queue/handlers/video-live-ending.ts15
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts3
-rw-r--r--server/lib/live/live-manager.ts26
-rw-r--r--server/lib/live/shared/muxing-session.ts7
-rw-r--r--server/lib/schedulers/videos-redundancy-scheduler.ts7
-rw-r--r--server/lib/transcoding/video-transcoding.ts71
-rw-r--r--server/lib/video-paths.ts29
-rw-r--r--server/lib/video.ts4
12 files changed, 131 insertions, 94 deletions
diff --git a/server/lib/activitypub/videos/shared/abstract-builder.ts b/server/lib/activitypub/videos/shared/abstract-builder.ts
index e89c94bcd..f995fe637 100644
--- a/server/lib/activitypub/videos/shared/abstract-builder.ts
+++ b/server/lib/activitypub/videos/shared/abstract-builder.ts
@@ -1,6 +1,6 @@
1import { Transaction } from 'sequelize/types' 1import { Transaction } from 'sequelize/types'
2import { checkUrlsSameHost } from '@server/helpers/activitypub' 2import { checkUrlsSameHost } from '@server/helpers/activitypub'
3import { deleteNonExistingModels } from '@server/helpers/database-utils' 3import { deleteAllModels, filterNonExistingModels } from '@server/helpers/database-utils'
4import { logger, LoggerTagsFn } from '@server/helpers/logger' 4import { logger, LoggerTagsFn } from '@server/helpers/logger'
5import { updatePlaceholderThumbnail, updateVideoMiniatureFromUrl } from '@server/lib/thumbnail' 5import { updatePlaceholderThumbnail, updateVideoMiniatureFromUrl } from '@server/lib/thumbnail'
6import { setVideoTags } from '@server/lib/video' 6import { setVideoTags } from '@server/lib/video'
@@ -111,8 +111,7 @@ export abstract class APVideoAbstractBuilder {
111 const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) 111 const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a))
112 112
113 // Remove video files that do not exist anymore 113 // Remove video files that do not exist anymore
114 const destroyTasks = deleteNonExistingModels(video.VideoFiles || [], newVideoFiles, t) 114 await deleteAllModels(filterNonExistingModels(video.VideoFiles || [], newVideoFiles), t)
115 await Promise.all(destroyTasks)
116 115
117 // Update or add other one 116 // Update or add other one
118 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'video', t)) 117 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'video', t))
@@ -124,13 +123,11 @@ export abstract class APVideoAbstractBuilder {
124 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) 123 const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
125 124
126 // Remove video playlists that do not exist anymore 125 // Remove video playlists that do not exist anymore
127 const destroyTasks = deleteNonExistingModels(video.VideoStreamingPlaylists || [], newStreamingPlaylists, t) 126 await deleteAllModels(filterNonExistingModels(video.VideoStreamingPlaylists || [], newStreamingPlaylists), t)
128 await Promise.all(destroyTasks)
129 127
130 video.VideoStreamingPlaylists = [] 128 video.VideoStreamingPlaylists = []
131 129
132 for (const playlistAttributes of streamingPlaylistAttributes) { 130 for (const playlistAttributes of streamingPlaylistAttributes) {
133
134 const streamingPlaylistModel = await this.insertOrReplaceStreamingPlaylist(playlistAttributes, t) 131 const streamingPlaylistModel = await this.insertOrReplaceStreamingPlaylist(playlistAttributes, t)
135 streamingPlaylistModel.Video = video 132 streamingPlaylistModel.Video = video
136 133
@@ -163,8 +160,7 @@ export abstract class APVideoAbstractBuilder {
163 160
164 const newVideoFiles: MVideoFile[] = getFileAttributesFromUrl(playlistModel, tagObjects).map(a => new VideoFileModel(a)) 161 const newVideoFiles: MVideoFile[] = getFileAttributesFromUrl(playlistModel, tagObjects).map(a => new VideoFileModel(a))
165 162
166 const destroyTasks = deleteNonExistingModels(oldStreamingPlaylistFiles, newVideoFiles, t) 163 await deleteAllModels(filterNonExistingModels(oldStreamingPlaylistFiles, newVideoFiles), t)
167 await Promise.all(destroyTasks)
168 164
169 // Update or add other one 165 // Update or add other one
170 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'streaming-playlist', t)) 166 const upsertTasks = newVideoFiles.map(f => VideoFileModel.customUpsert(f, 'streaming-playlist', t))
diff --git a/server/lib/activitypub/videos/shared/object-to-model-attributes.ts b/server/lib/activitypub/videos/shared/object-to-model-attributes.ts
index 85548428c..1fa16295d 100644
--- a/server/lib/activitypub/videos/shared/object-to-model-attributes.ts
+++ b/server/lib/activitypub/videos/shared/object-to-model-attributes.ts
@@ -7,10 +7,11 @@ import { logger } from '@server/helpers/logger'
7import { getExtFromMimetype } from '@server/helpers/video' 7import { getExtFromMimetype } from '@server/helpers/video'
8import { ACTIVITY_PUB, MIMETYPES, P2P_MEDIA_LOADER_PEER_VERSION, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '@server/initializers/constants' 8import { ACTIVITY_PUB, MIMETYPES, P2P_MEDIA_LOADER_PEER_VERSION, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '@server/initializers/constants'
9import { generateTorrentFileName } from '@server/lib/video-paths' 9import { generateTorrentFileName } from '@server/lib/video-paths'
10import { VideoCaptionModel } from '@server/models/video/video-caption'
10import { VideoFileModel } from '@server/models/video/video-file' 11import { VideoFileModel } from '@server/models/video/video-file'
11import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' 12import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
12import { FilteredModelAttributes } from '@server/types' 13import { FilteredModelAttributes } from '@server/types'
13import { MChannelId, MStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoId } from '@server/types/models' 14import { isStreamingPlaylist, MChannelId, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoId } from '@server/types/models'
14import { 15import {
15 ActivityHashTagObject, 16 ActivityHashTagObject,
16 ActivityMagnetUrlObject, 17 ActivityMagnetUrlObject,
@@ -23,7 +24,6 @@ import {
23 VideoPrivacy, 24 VideoPrivacy,
24 VideoStreamingPlaylistType 25 VideoStreamingPlaylistType
25} from '@shared/models' 26} from '@shared/models'
26import { VideoCaptionModel } from '@server/models/video/video-caption'
27 27
28function getThumbnailFromIcons (videoObject: VideoObject) { 28function getThumbnailFromIcons (videoObject: VideoObject) {
29 let validIcons = videoObject.icon.filter(i => i.width > THUMBNAILS_SIZE.minWidth) 29 let validIcons = videoObject.icon.filter(i => i.width > THUMBNAILS_SIZE.minWidth)
@@ -80,8 +80,8 @@ function getFileAttributesFromUrl (
80 80
81 const extname = getExtFromMimetype(MIMETYPES.VIDEO.MIMETYPE_EXT, fileUrl.mediaType) 81 const extname = getExtFromMimetype(MIMETYPES.VIDEO.MIMETYPE_EXT, fileUrl.mediaType)
82 const resolution = fileUrl.height 82 const resolution = fileUrl.height
83 const videoId = (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? null : videoOrPlaylist.id 83 const videoId = isStreamingPlaylist(videoOrPlaylist) ? null : videoOrPlaylist.id
84 const videoStreamingPlaylistId = (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? videoOrPlaylist.id : null 84 const videoStreamingPlaylistId = isStreamingPlaylist(videoOrPlaylist) ? videoOrPlaylist.id : null
85 85
86 const attribute = { 86 const attribute = {
87 extname, 87 extname,
@@ -130,8 +130,13 @@ function getStreamingPlaylistAttributesFromObject (video: MVideoId, videoObject:
130 130
131 const attribute = { 131 const attribute = {
132 type: VideoStreamingPlaylistType.HLS, 132 type: VideoStreamingPlaylistType.HLS,
133
134 playlistFilename: basename(playlistUrlObject.href),
133 playlistUrl: playlistUrlObject.href, 135 playlistUrl: playlistUrlObject.href,
136
137 segmentsSha256Filename: basename(segmentsSha256UrlObject.href),
134 segmentsSha256Url: segmentsSha256UrlObject.href, 138 segmentsSha256Url: segmentsSha256UrlObject.href,
139
135 p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrlObject.href, files), 140 p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrlObject.href, files),
136 p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION, 141 p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION,
137 videoId: video.id, 142 videoId: video.id,
diff --git a/server/lib/hls.ts b/server/lib/hls.ts
index 212bd095b..32b02bc26 100644
--- a/server/lib/hls.ts
+++ b/server/lib/hls.ts
@@ -1,7 +1,7 @@
1import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' 1import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra'
2import { flatten, uniq } from 'lodash' 2import { flatten, uniq } from 'lodash'
3import { basename, dirname, join } from 'path' 3import { basename, dirname, join } from 'path'
4import { MVideoWithFile } from '@server/types/models' 4import { MStreamingPlaylistFilesVideo, MVideoWithFile } from '@server/types/models'
5import { sha256 } from '../helpers/core-utils' 5import { sha256 } from '../helpers/core-utils'
6import { getAudioStreamCodec, getVideoStreamCodec, getVideoStreamSize } from '../helpers/ffprobe-utils' 6import { getAudioStreamCodec, getVideoStreamCodec, getVideoStreamSize } from '../helpers/ffprobe-utils'
7import { logger } from '../helpers/logger' 7import { logger } from '../helpers/logger'
@@ -12,7 +12,7 @@ import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from
12import { sequelizeTypescript } from '../initializers/database' 12import { sequelizeTypescript } from '../initializers/database'
13import { VideoFileModel } from '../models/video/video-file' 13import { VideoFileModel } from '../models/video/video-file'
14import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 14import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
15import { getVideoFilePath } from './video-paths' 15import { getHlsResolutionPlaylistFilename, getVideoFilePath } from './video-paths'
16 16
17async function updateStreamingPlaylistsInfohashesIfNeeded () { 17async function updateStreamingPlaylistsInfohashesIfNeeded () {
18 const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() 18 const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion()
@@ -22,27 +22,29 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () {
22 await sequelizeTypescript.transaction(async t => { 22 await sequelizeTypescript.transaction(async t => {
23 const videoFiles = await VideoFileModel.listByStreamingPlaylist(playlist.id, t) 23 const videoFiles = await VideoFileModel.listByStreamingPlaylist(playlist.id, t)
24 24
25 playlist.p2pMediaLoaderInfohashes = VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlist.playlistUrl, videoFiles) 25 playlist.assignP2PMediaLoaderInfoHashes(playlist.Video, videoFiles)
26 playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION 26 playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION
27
27 await playlist.save({ transaction: t }) 28 await playlist.save({ transaction: t })
28 }) 29 })
29 } 30 }
30} 31}
31 32
32async function updateMasterHLSPlaylist (video: MVideoWithFile) { 33async function updateMasterHLSPlaylist (video: MVideoWithFile, playlist: MStreamingPlaylistFilesVideo) {
33 const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 34 const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
35
34 const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] 36 const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ]
35 const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename())
36 const streamingPlaylist = video.getHLSPlaylist()
37 37
38 for (const file of streamingPlaylist.VideoFiles) { 38 const masterPlaylistPath = join(directory, playlist.playlistFilename)
39 const playlistFilename = VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution) 39
40 for (const file of playlist.VideoFiles) {
41 const playlistFilename = getHlsResolutionPlaylistFilename(file.filename)
40 42
41 // If we did not generated a playlist for this resolution, skip 43 // If we did not generated a playlist for this resolution, skip
42 const filePlaylistPath = join(directory, playlistFilename) 44 const filePlaylistPath = join(directory, playlistFilename)
43 if (await pathExists(filePlaylistPath) === false) continue 45 if (await pathExists(filePlaylistPath) === false) continue
44 46
45 const videoFilePath = getVideoFilePath(streamingPlaylist, file) 47 const videoFilePath = getVideoFilePath(playlist, file)
46 48
47 const size = await getVideoStreamSize(videoFilePath) 49 const size = await getVideoStreamSize(videoFilePath)
48 50
@@ -66,23 +68,22 @@ async function updateMasterHLSPlaylist (video: MVideoWithFile) {
66 await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') 68 await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n')
67} 69}
68 70
69async function updateSha256VODSegments (video: MVideoWithFile) { 71async function updateSha256VODSegments (video: MVideoWithFile, playlist: MStreamingPlaylistFilesVideo) {
70 const json: { [filename: string]: { [range: string]: string } } = {} 72 const json: { [filename: string]: { [range: string]: string } } = {}
71 73
72 const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 74 const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
73 const hlsPlaylist = video.getHLSPlaylist()
74 75
75 // For all the resolutions available for this video 76 // For all the resolutions available for this video
76 for (const file of hlsPlaylist.VideoFiles) { 77 for (const file of playlist.VideoFiles) {
77 const rangeHashes: { [range: string]: string } = {} 78 const rangeHashes: { [range: string]: string } = {}
78 79
79 const videoPath = getVideoFilePath(hlsPlaylist, file) 80 const videoPath = getVideoFilePath(playlist, file)
80 const playlistPath = join(playlistDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) 81 const resolutionPlaylistPath = join(playlistDirectory, getHlsResolutionPlaylistFilename(file.filename))
81 82
82 // Maybe the playlist is not generated for this resolution yet 83 // Maybe the playlist is not generated for this resolution yet
83 if (!await pathExists(playlistPath)) continue 84 if (!await pathExists(resolutionPlaylistPath)) continue
84 85
85 const playlistContent = await readFile(playlistPath) 86 const playlistContent = await readFile(resolutionPlaylistPath)
86 const ranges = getRangesFromPlaylist(playlistContent.toString()) 87 const ranges = getRangesFromPlaylist(playlistContent.toString())
87 88
88 const fd = await open(videoPath, 'r') 89 const fd = await open(videoPath, 'r')
@@ -98,7 +99,7 @@ async function updateSha256VODSegments (video: MVideoWithFile) {
98 json[videoFilename] = rangeHashes 99 json[videoFilename] = rangeHashes
99 } 100 }
100 101
101 const outputPath = join(playlistDirectory, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename()) 102 const outputPath = join(playlistDirectory, playlist.segmentsSha256Filename)
102 await outputJSON(outputPath, json) 103 await outputJSON(outputPath, json)
103} 104}
104 105
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 1783f206a..4d199f247 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -61,8 +61,7 @@ async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) {
61 61
62 if (currentVideoFile) { 62 if (currentVideoFile) {
63 // Remove old file and old torrent 63 // Remove old file and old torrent
64 await video.removeFile(currentVideoFile) 64 await video.removeFileAndTorrent(currentVideoFile)
65 await currentVideoFile.removeTorrent()
66 // Remove the old video file from the array 65 // Remove the old video file from the array
67 video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) 66 video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile)
68 67
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts
index 9eba41bf8..386ccdc7b 100644
--- a/server/lib/job-queue/handlers/video-live-ending.ts
+++ b/server/lib/job-queue/handlers/video-live-ending.ts
@@ -7,12 +7,12 @@ import { buildConcatenatedName, cleanupLive, LiveSegmentShaStore } from '@server
7import { generateVideoMiniature } from '@server/lib/thumbnail' 7import { generateVideoMiniature } from '@server/lib/thumbnail'
8import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/video-transcoding' 8import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/video-transcoding'
9import { publishAndFederateIfNeeded } from '@server/lib/video' 9import { publishAndFederateIfNeeded } from '@server/lib/video'
10import { getHLSDirectory } from '@server/lib/video-paths' 10import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getHLSDirectory } from '@server/lib/video-paths'
11import { VideoModel } from '@server/models/video/video' 11import { VideoModel } from '@server/models/video/video'
12import { VideoFileModel } from '@server/models/video/video-file' 12import { VideoFileModel } from '@server/models/video/video-file'
13import { VideoLiveModel } from '@server/models/video/video-live' 13import { VideoLiveModel } from '@server/models/video/video-live'
14import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' 14import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
15import { MVideo, MVideoLive } from '@server/types/models' 15import { MStreamingPlaylist, MVideo, MVideoLive } from '@server/types/models'
16import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@shared/models' 16import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@shared/models'
17import { logger } from '../../../helpers/logger' 17import { logger } from '../../../helpers/logger'
18 18
@@ -43,7 +43,7 @@ async function processVideoLiveEnding (job: Bull.Job) {
43 return cleanupLive(video, streamingPlaylist) 43 return cleanupLive(video, streamingPlaylist)
44 } 44 }
45 45
46 return saveLive(video, live) 46 return saveLive(video, live, streamingPlaylist)
47} 47}
48 48
49// --------------------------------------------------------------------------- 49// ---------------------------------------------------------------------------
@@ -54,14 +54,14 @@ export {
54 54
55// --------------------------------------------------------------------------- 55// ---------------------------------------------------------------------------
56 56
57async function saveLive (video: MVideo, live: MVideoLive) { 57async function saveLive (video: MVideo, live: MVideoLive, streamingPlaylist: MStreamingPlaylist) {
58 const hlsDirectory = getHLSDirectory(video, false) 58 const hlsDirectory = getHLSDirectory(video, false)
59 const replayDirectory = join(hlsDirectory, VIDEO_LIVE.REPLAY_DIRECTORY) 59 const replayDirectory = join(hlsDirectory, VIDEO_LIVE.REPLAY_DIRECTORY)
60 60
61 const rootFiles = await readdir(hlsDirectory) 61 const rootFiles = await readdir(hlsDirectory)
62 62
63 const playlistFiles = rootFiles.filter(file => { 63 const playlistFiles = rootFiles.filter(file => {
64 return file.endsWith('.m3u8') && file !== 'master.m3u8' 64 return file.endsWith('.m3u8') && file !== streamingPlaylist.playlistFilename
65 }) 65 })
66 66
67 await cleanupLiveFiles(hlsDirectory) 67 await cleanupLiveFiles(hlsDirectory)
@@ -80,7 +80,12 @@ async function saveLive (video: MVideo, live: MVideoLive) {
80 80
81 const hlsPlaylist = videoWithFiles.getHLSPlaylist() 81 const hlsPlaylist = videoWithFiles.getHLSPlaylist()
82 await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) 82 await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id)
83
84 // Reset playlist
83 hlsPlaylist.VideoFiles = [] 85 hlsPlaylist.VideoFiles = []
86 hlsPlaylist.playlistFilename = generateHLSMasterPlaylistFilename()
87 hlsPlaylist.segmentsSha256Filename = generateHlsSha256SegmentsFilename()
88 await hlsPlaylist.save()
84 89
85 let durationDone = false 90 let durationDone = false
86 91
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index f5ba6f435..36d9594af 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -125,8 +125,7 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay
125 if (payload.isMaxQuality && CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) { 125 if (payload.isMaxQuality && CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) {
126 // Remove webtorrent files if not enabled 126 // Remove webtorrent files if not enabled
127 for (const file of video.VideoFiles) { 127 for (const file of video.VideoFiles) {
128 await video.removeFile(file) 128 await video.removeFileAndTorrent(file)
129 await file.removeTorrent()
130 await file.destroy() 129 await file.destroy()
131 } 130 }
132 131
diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts
index da764e009..f106d69fb 100644
--- a/server/lib/live/live-manager.ts
+++ b/server/lib/live/live-manager.ts
@@ -4,16 +4,17 @@ import { isTestInstance } from '@server/helpers/core-utils'
4import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution } from '@server/helpers/ffprobe-utils' 4import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution } from '@server/helpers/ffprobe-utils'
5import { logger, loggerTagsFactory } from '@server/helpers/logger' 5import { logger, loggerTagsFactory } from '@server/helpers/logger'
6import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' 6import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config'
7import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, VIEW_LIFETIME, WEBSERVER } from '@server/initializers/constants' 7import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, VIEW_LIFETIME } from '@server/initializers/constants'
8import { UserModel } from '@server/models/user/user' 8import { UserModel } from '@server/models/user/user'
9import { VideoModel } from '@server/models/video/video' 9import { VideoModel } from '@server/models/video/video'
10import { VideoLiveModel } from '@server/models/video/video-live' 10import { VideoLiveModel } from '@server/models/video/video-live'
11import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' 11import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
12import { MStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoLiveVideo } from '@server/types/models' 12import { MStreamingPlaylistVideo, MVideo, MVideoLiveVideo } from '@server/types/models'
13import { VideoState, VideoStreamingPlaylistType } from '@shared/models' 13import { VideoState, VideoStreamingPlaylistType } from '@shared/models'
14import { federateVideoIfNeeded } from '../activitypub/videos' 14import { federateVideoIfNeeded } from '../activitypub/videos'
15import { JobQueue } from '../job-queue' 15import { JobQueue } from '../job-queue'
16import { PeerTubeSocket } from '../peertube-socket' 16import { PeerTubeSocket } from '../peertube-socket'
17import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename } from '../video-paths'
17import { LiveQuotaStore } from './live-quota-store' 18import { LiveQuotaStore } from './live-quota-store'
18import { LiveSegmentShaStore } from './live-segment-sha-store' 19import { LiveSegmentShaStore } from './live-segment-sha-store'
19import { cleanupLive } from './live-utils' 20import { cleanupLive } from './live-utils'
@@ -392,19 +393,18 @@ class LiveManager {
392 return resolutionsEnabled.concat([ originResolution ]) 393 return resolutionsEnabled.concat([ originResolution ])
393 } 394 }
394 395
395 private async createLivePlaylist (video: MVideo, allResolutions: number[]) { 396 private async createLivePlaylist (video: MVideo, allResolutions: number[]): Promise<MStreamingPlaylistVideo> {
396 const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid) 397 const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(video)
397 const [ videoStreamingPlaylist ] = await VideoStreamingPlaylistModel.upsert({
398 videoId: video.id,
399 playlistUrl,
400 segmentsSha256Url: WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsSha256SegmentsStaticPath(video.uuid, video.isLive),
401 p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrl, allResolutions),
402 p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION,
403 398
404 type: VideoStreamingPlaylistType.HLS 399 playlist.playlistFilename = generateHLSMasterPlaylistFilename(true)
405 }, { returning: true }) as [ MStreamingPlaylist, boolean ] 400 playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(true)
406 401
407 return Object.assign(videoStreamingPlaylist, { Video: video }) 402 playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION
403 playlist.type = VideoStreamingPlaylistType.HLS
404
405 playlist.assignP2PMediaLoaderInfoHashes(video, allResolutions)
406
407 return playlist.save()
408 } 408 }
409 409
410 static get Instance () { 410 static get Instance () {
diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts
index 26467f060..709d6c615 100644
--- a/server/lib/live/shared/muxing-session.ts
+++ b/server/lib/live/shared/muxing-session.ts
@@ -112,13 +112,16 @@ class MuxingSession extends EventEmitter {
112 this.ffmpegCommand = CONFIG.LIVE.TRANSCODING.ENABLED 112 this.ffmpegCommand = CONFIG.LIVE.TRANSCODING.ENABLED
113 ? await getLiveTranscodingCommand({ 113 ? await getLiveTranscodingCommand({
114 rtmpUrl: this.rtmpUrl, 114 rtmpUrl: this.rtmpUrl,
115
115 outPath, 116 outPath,
117 masterPlaylistName: this.streamingPlaylist.playlistFilename,
118
116 resolutions: this.allResolutions, 119 resolutions: this.allResolutions,
117 fps: this.fps, 120 fps: this.fps,
118 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), 121 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
119 profile: CONFIG.LIVE.TRANSCODING.PROFILE 122 profile: CONFIG.LIVE.TRANSCODING.PROFILE
120 }) 123 })
121 : getLiveMuxingCommand(this.rtmpUrl, outPath) 124 : getLiveMuxingCommand(this.rtmpUrl, outPath, this.streamingPlaylist.playlistFilename)
122 125
123 logger.info('Running live muxing/transcoding for %s.', this.videoUUID, this.lTags) 126 logger.info('Running live muxing/transcoding for %s.', this.videoUUID, this.lTags)
124 127
@@ -182,7 +185,7 @@ class MuxingSession extends EventEmitter {
182 } 185 }
183 186
184 private watchMasterFile (outPath: string) { 187 private watchMasterFile (outPath: string) {
185 this.masterWatcher = chokidar.watch(outPath + '/master.m3u8') 188 this.masterWatcher = chokidar.watch(outPath + '/' + this.streamingPlaylist.playlistFilename)
186 189
187 this.masterWatcher.on('add', async () => { 190 this.masterWatcher.on('add', async () => {
188 this.emit('master-playlist-created', { videoId: this.videoId }) 191 this.emit('master-playlist-created', { videoId: this.videoId })
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts
index b5a5eb697..103ab1fab 100644
--- a/server/lib/schedulers/videos-redundancy-scheduler.ts
+++ b/server/lib/schedulers/videos-redundancy-scheduler.ts
@@ -267,7 +267,8 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
267 logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, strategy) 267 logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, strategy)
268 268
269 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) 269 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid)
270 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) 270 const masterPlaylistUrl = playlist.getMasterPlaylistUrl(video)
271 await downloadPlaylistSegments(masterPlaylistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT)
271 272
272 const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ 273 const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({
273 expiresOn, 274 expiresOn,
@@ -282,7 +283,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
282 283
283 await sendCreateCacheFile(serverActor, video, createdModel) 284 await sendCreateCacheFile(serverActor, video, createdModel)
284 285
285 logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) 286 logger.info('Duplicated playlist %s -> %s.', masterPlaylistUrl, createdModel.url)
286 } 287 }
287 288
288 private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) { 289 private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) {
@@ -330,7 +331,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
330 private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) { 331 private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) {
331 if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` 332 if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
332 333
333 return `${object.VideoStreamingPlaylist.playlistUrl}` 334 return `${object.VideoStreamingPlaylist.getMasterPlaylistUrl(object.VideoStreamingPlaylist.Video)}`
334 } 335 }
335 336
336 private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylistFiles[]) { 337 private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylistFiles[]) {
diff --git a/server/lib/transcoding/video-transcoding.ts b/server/lib/transcoding/video-transcoding.ts
index d70f7f474..d2a556360 100644
--- a/server/lib/transcoding/video-transcoding.ts
+++ b/server/lib/transcoding/video-transcoding.ts
@@ -10,11 +10,18 @@ import { transcode, TranscodeOptions, TranscodeOptionsType } from '../../helpers
10import { canDoQuickTranscode, getDurationFromVideoFile, getMetadataFromFile, getVideoFileFPS } from '../../helpers/ffprobe-utils' 10import { canDoQuickTranscode, getDurationFromVideoFile, getMetadataFromFile, getVideoFileFPS } from '../../helpers/ffprobe-utils'
11import { logger } from '../../helpers/logger' 11import { logger } from '../../helpers/logger'
12import { CONFIG } from '../../initializers/config' 12import { CONFIG } from '../../initializers/config'
13import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../../initializers/constants' 13import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants'
14import { VideoFileModel } from '../../models/video/video-file' 14import { VideoFileModel } from '../../models/video/video-file'
15import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' 15import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
16import { updateMasterHLSPlaylist, updateSha256VODSegments } from '../hls' 16import { updateMasterHLSPlaylist, updateSha256VODSegments } from '../hls'
17import { generateHLSVideoFilename, generateWebTorrentVideoFilename, getVideoFilePath } from '../video-paths' 17import {
18 generateHLSMasterPlaylistFilename,
19 generateHlsSha256SegmentsFilename,
20 generateHLSVideoFilename,
21 generateWebTorrentVideoFilename,
22 getHlsResolutionPlaylistFilename,
23 getVideoFilePath
24} from '../video-paths'
18import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' 25import { VideoTranscodingProfilesManager } from './video-transcoding-profiles'
19 26
20/** 27/**
@@ -272,14 +279,14 @@ async function generateHlsPlaylistCommon (options: {
272 await ensureDir(videoTranscodedBasePath) 279 await ensureDir(videoTranscodedBasePath)
273 280
274 const videoFilename = generateHLSVideoFilename(resolution) 281 const videoFilename = generateHLSVideoFilename(resolution)
275 const playlistFilename = VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution) 282 const resolutionPlaylistFilename = getHlsResolutionPlaylistFilename(videoFilename)
276 const playlistFileTranscodePath = join(videoTranscodedBasePath, playlistFilename) 283 const resolutionPlaylistFileTranscodePath = join(videoTranscodedBasePath, resolutionPlaylistFilename)
277 284
278 const transcodeOptions = { 285 const transcodeOptions = {
279 type, 286 type,
280 287
281 inputPath, 288 inputPath,
282 outputPath: playlistFileTranscodePath, 289 outputPath: resolutionPlaylistFileTranscodePath,
283 290
284 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), 291 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
285 profile: CONFIG.TRANSCODING.PROFILE, 292 profile: CONFIG.TRANSCODING.PROFILE,
@@ -299,19 +306,23 @@ async function generateHlsPlaylistCommon (options: {
299 306
300 await transcode(transcodeOptions) 307 await transcode(transcodeOptions)
301 308
302 const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid)
303
304 // Create or update the playlist 309 // Create or update the playlist
305 const [ videoStreamingPlaylist ] = await VideoStreamingPlaylistModel.upsert({ 310 const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(video)
306 videoId: video.id, 311
307 playlistUrl, 312 if (!playlist.playlistFilename) {
308 segmentsSha256Url: WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsSha256SegmentsStaticPath(video.uuid, video.isLive), 313 playlist.playlistFilename = generateHLSMasterPlaylistFilename(video.isLive)
309 p2pMediaLoaderInfohashes: [], 314 }
310 p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION, 315
316 if (!playlist.segmentsSha256Filename) {
317 playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(video.isLive)
318 }
319
320 playlist.p2pMediaLoaderInfohashes = []
321 playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION
311 322
312 type: VideoStreamingPlaylistType.HLS 323 playlist.type = VideoStreamingPlaylistType.HLS
313 }, { returning: true }) as [ MStreamingPlaylistFilesVideo, boolean ] 324
314 videoStreamingPlaylist.Video = video 325 await playlist.save()
315 326
316 // Build the new playlist file 327 // Build the new playlist file
317 const extname = extnameUtil(videoFilename) 328 const extname = extnameUtil(videoFilename)
@@ -321,18 +332,18 @@ async function generateHlsPlaylistCommon (options: {
321 size: 0, 332 size: 0,
322 filename: videoFilename, 333 filename: videoFilename,
323 fps: -1, 334 fps: -1,
324 videoStreamingPlaylistId: videoStreamingPlaylist.id 335 videoStreamingPlaylistId: playlist.id
325 }) 336 })
326 337
327 const videoFilePath = getVideoFilePath(videoStreamingPlaylist, newVideoFile) 338 const videoFilePath = getVideoFilePath(playlist, newVideoFile)
328 339
329 // Move files from tmp transcoded directory to the appropriate place 340 // Move files from tmp transcoded directory to the appropriate place
330 const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 341 const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
331 await ensureDir(baseHlsDirectory) 342 await ensureDir(baseHlsDirectory)
332 343
333 // Move playlist file 344 // Move playlist file
334 const playlistPath = join(baseHlsDirectory, playlistFilename) 345 const resolutionPlaylistPath = join(baseHlsDirectory, resolutionPlaylistFilename)
335 await move(playlistFileTranscodePath, playlistPath, { overwrite: true }) 346 await move(resolutionPlaylistFileTranscodePath, resolutionPlaylistPath, { overwrite: true })
336 // Move video file 347 // Move video file
337 await move(join(videoTranscodedBasePath, videoFilename), videoFilePath, { overwrite: true }) 348 await move(join(videoTranscodedBasePath, videoFilename), videoFilePath, { overwrite: true })
338 349
@@ -342,20 +353,20 @@ async function generateHlsPlaylistCommon (options: {
342 newVideoFile.fps = await getVideoFileFPS(videoFilePath) 353 newVideoFile.fps = await getVideoFileFPS(videoFilePath)
343 newVideoFile.metadata = await getMetadataFromFile(videoFilePath) 354 newVideoFile.metadata = await getMetadataFromFile(videoFilePath)
344 355
345 await createTorrentAndSetInfoHash(videoStreamingPlaylist, newVideoFile) 356 await createTorrentAndSetInfoHash(playlist, newVideoFile)
346 357
347 await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined) 358 await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined)
348 videoStreamingPlaylist.VideoFiles = await videoStreamingPlaylist.$get('VideoFiles')
349 359
350 videoStreamingPlaylist.p2pMediaLoaderInfohashes = VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes( 360 const playlistWithFiles = playlist as MStreamingPlaylistFilesVideo
351 playlistUrl, videoStreamingPlaylist.VideoFiles 361 playlistWithFiles.VideoFiles = await playlist.$get('VideoFiles')
352 ) 362 playlist.assignP2PMediaLoaderInfoHashes(video, playlistWithFiles.VideoFiles)
353 await videoStreamingPlaylist.save() 363
364 await playlist.save()
354 365
355 video.setHLSPlaylist(videoStreamingPlaylist) 366 video.setHLSPlaylist(playlist)
356 367
357 await updateMasterHLSPlaylist(video) 368 await updateMasterHLSPlaylist(video, playlistWithFiles)
358 await updateSha256VODSegments(video) 369 await updateSha256VODSegments(video, playlistWithFiles)
359 370
360 return playlistPath 371 return resolutionPlaylistPath
361} 372}
diff --git a/server/lib/video-paths.ts b/server/lib/video-paths.ts
index b7068190c..1e4382108 100644
--- a/server/lib/video-paths.ts
+++ b/server/lib/video-paths.ts
@@ -4,19 +4,16 @@ import { CONFIG } from '@server/initializers/config'
4import { HLS_REDUNDANCY_DIRECTORY, HLS_STREAMING_PLAYLIST_DIRECTORY, STATIC_PATHS, WEBSERVER } from '@server/initializers/constants' 4import { HLS_REDUNDANCY_DIRECTORY, HLS_STREAMING_PLAYLIST_DIRECTORY, STATIC_PATHS, WEBSERVER } from '@server/initializers/constants'
5import { isStreamingPlaylist, MStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/types/models' 5import { isStreamingPlaylist, MStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/types/models'
6import { buildUUID } from '@server/helpers/uuid' 6import { buildUUID } from '@server/helpers/uuid'
7import { removeFragmentedMP4Ext } from '@shared/core-utils'
7 8
8// ################## Video file name ################## 9// ################## Video file name ##################
9 10
10function generateWebTorrentVideoFilename (resolution: number, extname: string) { 11function generateWebTorrentVideoFilename (resolution: number, extname: string) {
11 const uuid = buildUUID() 12 return buildUUID() + '-' + resolution + extname
12
13 return uuid + '-' + resolution + extname
14} 13}
15 14
16function generateHLSVideoFilename (resolution: number) { 15function generateHLSVideoFilename (resolution: number) {
17 const uuid = buildUUID() 16 return `${buildUUID()}-${resolution}-fragmented.mp4`
18
19 return `${uuid}-${resolution}-fragmented.mp4`
20} 17}
21 18
22function getVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile, isRedundancy = false) { 19function getVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile, isRedundancy = false) {
@@ -54,6 +51,23 @@ function getHLSDirectory (video: MVideoUUID, isRedundancy = false) {
54 return join(baseDir, video.uuid) 51 return join(baseDir, video.uuid)
55} 52}
56 53
54function getHlsResolutionPlaylistFilename (videoFilename: string) {
55 // Video file name already contain resolution
56 return removeFragmentedMP4Ext(videoFilename) + '.m3u8'
57}
58
59function generateHLSMasterPlaylistFilename (isLive = false) {
60 if (isLive) return 'master.m3u8'
61
62 return buildUUID() + '-master.m3u8'
63}
64
65function generateHlsSha256SegmentsFilename (isLive = false) {
66 if (isLive) return 'segments-sha256.json'
67
68 return buildUUID() + '-segments-sha256.json'
69}
70
57// ################## Torrents ################## 71// ################## Torrents ##################
58 72
59function generateTorrentFileName (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, resolution: number) { 73function generateTorrentFileName (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, resolution: number) {
@@ -91,6 +105,9 @@ export {
91 getTorrentFilePath, 105 getTorrentFilePath,
92 106
93 getHLSDirectory, 107 getHLSDirectory,
108 generateHLSMasterPlaylistFilename,
109 generateHlsSha256SegmentsFilename,
110 getHlsResolutionPlaylistFilename,
94 111
95 getLocalVideoFileMetadataUrl, 112 getLocalVideoFileMetadataUrl,
96 113
diff --git a/server/lib/video.ts b/server/lib/video.ts
index daf998704..61fee4949 100644
--- a/server/lib/video.ts
+++ b/server/lib/video.ts
@@ -5,7 +5,7 @@ import { sequelizeTypescript } from '@server/initializers/database'
5import { TagModel } from '@server/models/video/tag' 5import { TagModel } from '@server/models/video/tag'
6import { VideoModel } from '@server/models/video/video' 6import { VideoModel } from '@server/models/video/video'
7import { FilteredModelAttributes } from '@server/types' 7import { FilteredModelAttributes } from '@server/types'
8import { MThumbnail, MUserId, MVideo, MVideoFile, MVideoTag, MVideoThumbnail, MVideoUUID } from '@server/types/models' 8import { MThumbnail, MUserId, MVideoFile, MVideoTag, MVideoThumbnail, MVideoUUID } from '@server/types/models'
9import { ThumbnailType, VideoCreate, VideoPrivacy, VideoTranscodingPayload } from '@shared/models' 9import { ThumbnailType, VideoCreate, VideoPrivacy, VideoTranscodingPayload } from '@shared/models'
10import { federateVideoIfNeeded } from './activitypub/videos' 10import { federateVideoIfNeeded } from './activitypub/videos'
11import { JobQueue } from './job-queue/job-queue' 11import { JobQueue } from './job-queue/job-queue'
@@ -105,7 +105,7 @@ async function publishAndFederateIfNeeded (video: MVideoUUID, wasLive = false) {
105 } 105 }
106} 106}
107 107
108async function addOptimizeOrMergeAudioJob (video: MVideo, videoFile: MVideoFile, user: MUserId) { 108async function addOptimizeOrMergeAudioJob (video: MVideoUUID, videoFile: MVideoFile, user: MUserId) {
109 let dataInput: VideoTranscodingPayload 109 let dataInput: VideoTranscodingPayload
110 110
111 if (videoFile.isAudio()) { 111 if (videoFile.isAudio()) {