aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/activitypub/actors/refresh.ts2
-rw-r--r--server/lib/activitypub/crawl.ts3
-rw-r--r--server/lib/activitypub/follow.ts17
-rw-r--r--server/lib/activitypub/playlists/refresh.ts2
-rw-r--r--server/lib/activitypub/videos/refresh.ts2
-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/client-html.ts4
-rw-r--r--server/lib/hls.ts39
-rw-r--r--server/lib/job-queue/handlers/activitypub-cleaner.ts2
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts7
-rw-r--r--server/lib/job-queue/handlers/video-import.ts4
-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.ts32
-rw-r--r--server/lib/live/shared/muxing-session.ts7
-rw-r--r--server/lib/moderation.ts2
-rw-r--r--server/lib/plugins/register-helpers.ts222
-rw-r--r--server/lib/plugins/video-constant-manager-factory.ts139
-rw-r--r--server/lib/schedulers/plugins-check-scheduler.ts10
-rw-r--r--server/lib/schedulers/videos-redundancy-scheduler.ts7
-rw-r--r--server/lib/transcoding/video-transcoding.ts81
-rw-r--r--server/lib/video-paths.ts56
-rw-r--r--server/lib/video.ts4
24 files changed, 365 insertions, 320 deletions
diff --git a/server/lib/activitypub/actors/refresh.ts b/server/lib/activitypub/actors/refresh.ts
index b2fe3932f..0acaa9f62 100644
--- a/server/lib/activitypub/actors/refresh.ts
+++ b/server/lib/activitypub/actors/refresh.ts
@@ -4,7 +4,7 @@ import { PeerTubeRequestError } from '@server/helpers/requests'
4import { ActorLoadByUrlType } from '@server/lib/model-loaders' 4import { ActorLoadByUrlType } from '@server/lib/model-loaders'
5import { ActorModel } from '@server/models/actor/actor' 5import { ActorModel } from '@server/models/actor/actor'
6import { MActorAccountChannelId, MActorFull } from '@server/types/models' 6import { MActorAccountChannelId, MActorFull } from '@server/types/models'
7import { HttpStatusCode } from '@shared/core-utils' 7import { HttpStatusCode } from '@shared/models'
8import { fetchRemoteActor } from './shared' 8import { fetchRemoteActor } from './shared'
9import { APActorUpdater } from './updater' 9import { APActorUpdater } from './updater'
10import { getUrlFromWebfinger } from './webfinger' 10import { getUrlFromWebfinger } from './webfinger'
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts
index cd117f571..28ff5225a 100644
--- a/server/lib/activitypub/crawl.ts
+++ b/server/lib/activitypub/crawl.ts
@@ -1,3 +1,4 @@
1import { retryTransactionWrapper } from '@server/helpers/database-utils'
1import * as Bluebird from 'bluebird' 2import * as Bluebird from 'bluebird'
2import { URL } from 'url' 3import { URL } from 'url'
3import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' 4import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
@@ -51,7 +52,7 @@ async function crawlCollectionPage <T> (argUrl: string, handler: HandlerFunction
51 } 52 }
52 } 53 }
53 54
54 if (cleaner) await cleaner(startDate) 55 if (cleaner) await retryTransactionWrapper(cleaner, startDate)
55} 56}
56 57
57export { 58export {
diff --git a/server/lib/activitypub/follow.ts b/server/lib/activitypub/follow.ts
index c1bd667e0..741b54df5 100644
--- a/server/lib/activitypub/follow.ts
+++ b/server/lib/activitypub/follow.ts
@@ -31,6 +31,21 @@ async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors, transact
31 } 31 }
32} 32}
33 33
34// If we only have an host, use a default account handle
35function getRemoteNameAndHost (handleOrHost: string) {
36 let name = SERVER_ACTOR_NAME
37 let host = handleOrHost
38
39 const splitted = handleOrHost.split('@')
40 if (splitted.length === 2) {
41 name = splitted[0]
42 host = splitted[1]
43 }
44
45 return { name, host }
46}
47
34export { 48export {
35 autoFollowBackIfNeeded 49 autoFollowBackIfNeeded,
50 getRemoteNameAndHost
36} 51}
diff --git a/server/lib/activitypub/playlists/refresh.ts b/server/lib/activitypub/playlists/refresh.ts
index ef3cb3fe4..493e8c7ec 100644
--- a/server/lib/activitypub/playlists/refresh.ts
+++ b/server/lib/activitypub/playlists/refresh.ts
@@ -2,7 +2,7 @@ import { logger, loggerTagsFactory } from '@server/helpers/logger'
2import { PeerTubeRequestError } from '@server/helpers/requests' 2import { PeerTubeRequestError } from '@server/helpers/requests'
3import { JobQueue } from '@server/lib/job-queue' 3import { JobQueue } from '@server/lib/job-queue'
4import { MVideoPlaylist, MVideoPlaylistOwner } from '@server/types/models' 4import { MVideoPlaylist, MVideoPlaylistOwner } from '@server/types/models'
5import { HttpStatusCode } from '@shared/core-utils' 5import { HttpStatusCode } from '@shared/models'
6import { createOrUpdateVideoPlaylist } from './create-update' 6import { createOrUpdateVideoPlaylist } from './create-update'
7import { fetchRemoteVideoPlaylist } from './shared' 7import { fetchRemoteVideoPlaylist } from './shared'
8 8
diff --git a/server/lib/activitypub/videos/refresh.ts b/server/lib/activitypub/videos/refresh.ts
index a7b82f286..3af08acf4 100644
--- a/server/lib/activitypub/videos/refresh.ts
+++ b/server/lib/activitypub/videos/refresh.ts
@@ -4,7 +4,7 @@ import { ActorFollowScoreCache } from '@server/lib/files-cache'
4import { VideoLoadByUrlType } from '@server/lib/model-loaders' 4import { VideoLoadByUrlType } from '@server/lib/model-loaders'
5import { VideoModel } from '@server/models/video/video' 5import { VideoModel } from '@server/models/video/video'
6import { MVideoAccountLightBlacklistAllFiles, MVideoThumbnail } from '@server/types/models' 6import { MVideoAccountLightBlacklistAllFiles, MVideoThumbnail } from '@server/types/models'
7import { HttpStatusCode } from '@shared/core-utils' 7import { HttpStatusCode } from '@shared/models'
8import { fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared' 8import { fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared'
9import { APVideoUpdater } from './updater' 9import { APVideoUpdater } from './updater'
10 10
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/client-html.ts b/server/lib/client-html.ts
index 72194416d..a557c090f 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -5,7 +5,7 @@ import validator from 'validator'
5import { escapeHTML } from '@shared/core-utils/renderer' 5import { escapeHTML } from '@shared/core-utils/renderer'
6import { HTMLServerConfig } from '@shared/models' 6import { HTMLServerConfig } from '@shared/models'
7import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' 7import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n'
8import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' 8import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
9import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos' 9import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos'
10import { isTestInstance, sha256 } from '../helpers/core-utils' 10import { isTestInstance, sha256 } from '../helpers/core-utils'
11import { logger } from '../helpers/logger' 11import { logger } from '../helpers/logger'
@@ -162,7 +162,7 @@ class ClientHtml {
162 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(videoPlaylist.name)) 162 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(videoPlaylist.name))
163 customHtml = ClientHtml.addDescriptionTag(customHtml, mdToPlainText(videoPlaylist.description)) 163 customHtml = ClientHtml.addDescriptionTag(customHtml, mdToPlainText(videoPlaylist.description))
164 164
165 const url = videoPlaylist.getWatchUrl() 165 const url = WEBSERVER.URL + videoPlaylist.getWatchStaticPath()
166 const originUrl = videoPlaylist.url 166 const originUrl = videoPlaylist.url
167 const title = escapeHTML(videoPlaylist.name) 167 const title = escapeHTML(videoPlaylist.name)
168 const siteName = escapeHTML(CONFIG.INSTANCE.NAME) 168 const siteName = escapeHTML(CONFIG.INSTANCE.NAME)
diff --git a/server/lib/hls.ts b/server/lib/hls.ts
index 05be403f3..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,25 +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
40 for (const file of playlist.VideoFiles) {
41 const playlistFilename = getHlsResolutionPlaylistFilename(file.filename)
42
39 // If we did not generated a playlist for this resolution, skip 43 // If we did not generated a playlist for this resolution, skip
40 const filePlaylistPath = join(directory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) 44 const filePlaylistPath = join(directory, playlistFilename)
41 if (await pathExists(filePlaylistPath) === false) continue 45 if (await pathExists(filePlaylistPath) === false) continue
42 46
43 const videoFilePath = getVideoFilePath(streamingPlaylist, file) 47 const videoFilePath = getVideoFilePath(playlist, file)
44 48
45 const size = await getVideoStreamSize(videoFilePath) 49 const size = await getVideoStreamSize(videoFilePath)
46 50
@@ -58,29 +62,28 @@ async function updateMasterHLSPlaylist (video: MVideoWithFile) {
58 line += `,CODECS="${codecs.filter(c => !!c).join(',')}"` 62 line += `,CODECS="${codecs.filter(c => !!c).join(',')}"`
59 63
60 masterPlaylists.push(line) 64 masterPlaylists.push(line)
61 masterPlaylists.push(VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) 65 masterPlaylists.push(playlistFilename)
62 } 66 }
63 67
64 await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') 68 await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n')
65} 69}
66 70
67async function updateSha256VODSegments (video: MVideoWithFile) { 71async function updateSha256VODSegments (video: MVideoWithFile, playlist: MStreamingPlaylistFilesVideo) {
68 const json: { [filename: string]: { [range: string]: string } } = {} 72 const json: { [filename: string]: { [range: string]: string } } = {}
69 73
70 const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) 74 const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
71 const hlsPlaylist = video.getHLSPlaylist()
72 75
73 // For all the resolutions available for this video 76 // For all the resolutions available for this video
74 for (const file of hlsPlaylist.VideoFiles) { 77 for (const file of playlist.VideoFiles) {
75 const rangeHashes: { [range: string]: string } = {} 78 const rangeHashes: { [range: string]: string } = {}
76 79
77 const videoPath = getVideoFilePath(hlsPlaylist, file) 80 const videoPath = getVideoFilePath(playlist, file)
78 const playlistPath = join(playlistDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) 81 const resolutionPlaylistPath = join(playlistDirectory, getHlsResolutionPlaylistFilename(file.filename))
79 82
80 // Maybe the playlist is not generated for this resolution yet 83 // Maybe the playlist is not generated for this resolution yet
81 if (!await pathExists(playlistPath)) continue 84 if (!await pathExists(resolutionPlaylistPath)) continue
82 85
83 const playlistContent = await readFile(playlistPath) 86 const playlistContent = await readFile(resolutionPlaylistPath)
84 const ranges = getRangesFromPlaylist(playlistContent.toString()) 87 const ranges = getRangesFromPlaylist(playlistContent.toString())
85 88
86 const fd = await open(videoPath, 'r') 89 const fd = await open(videoPath, 'r')
@@ -96,7 +99,7 @@ async function updateSha256VODSegments (video: MVideoWithFile) {
96 json[videoFilename] = rangeHashes 99 json[videoFilename] = rangeHashes
97 } 100 }
98 101
99 const outputPath = join(playlistDirectory, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename()) 102 const outputPath = join(playlistDirectory, playlist.segmentsSha256Filename)
100 await outputJSON(outputPath, json) 103 await outputJSON(outputPath, json)
101} 104}
102 105
diff --git a/server/lib/job-queue/handlers/activitypub-cleaner.ts b/server/lib/job-queue/handlers/activitypub-cleaner.ts
index 1caca1dcc..56e2b0ceb 100644
--- a/server/lib/job-queue/handlers/activitypub-cleaner.ts
+++ b/server/lib/job-queue/handlers/activitypub-cleaner.ts
@@ -12,7 +12,7 @@ import { AP_CLEANER_CONCURRENCY } from '@server/initializers/constants'
12import { VideoModel } from '@server/models/video/video' 12import { VideoModel } from '@server/models/video/video'
13import { VideoCommentModel } from '@server/models/video/video-comment' 13import { VideoCommentModel } from '@server/models/video/video-comment'
14import { VideoShareModel } from '@server/models/video/video-share' 14import { VideoShareModel } from '@server/models/video/video-share'
15import { HttpStatusCode } from '@shared/core-utils' 15import { HttpStatusCode } from '@shared/models'
16import { logger } from '../../../helpers/logger' 16import { logger } from '../../../helpers/logger'
17import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 17import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
18 18
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 187cb652e..4d199f247 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -2,7 +2,7 @@ import * as Bull from 'bull'
2import { copy, stat } from 'fs-extra' 2import { copy, stat } from 'fs-extra'
3import { getLowercaseExtension } from '@server/helpers/core-utils' 3import { getLowercaseExtension } from '@server/helpers/core-utils'
4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
5import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 5import { generateWebTorrentVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
6import { UserModel } from '@server/models/user/user' 6import { UserModel } from '@server/models/user/user'
7import { MVideoFullLight } from '@server/types/models' 7import { MVideoFullLight } from '@server/types/models'
8import { VideoFileImportPayload } from '@shared/models' 8import { VideoFileImportPayload } from '@shared/models'
@@ -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
@@ -72,7 +71,7 @@ async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) {
72 const newVideoFile = new VideoFileModel({ 71 const newVideoFile = new VideoFileModel({
73 resolution: videoFileResolution, 72 resolution: videoFileResolution,
74 extname: fileExt, 73 extname: fileExt,
75 filename: generateVideoFilename(video, false, videoFileResolution, fileExt), 74 filename: generateWebTorrentVideoFilename(videoFileResolution, fileExt),
76 size, 75 size,
77 fps, 76 fps,
78 videoId: video.id 77 videoId: video.id
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 55498003d..6e425d09c 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -8,7 +8,7 @@ import { Hooks } from '@server/lib/plugins/hooks'
8import { ServerConfigManager } from '@server/lib/server-config-manager' 8import { ServerConfigManager } from '@server/lib/server-config-manager'
9import { isAbleToUploadVideo } from '@server/lib/user' 9import { isAbleToUploadVideo } from '@server/lib/user'
10import { addOptimizeOrMergeAudioJob } from '@server/lib/video' 10import { addOptimizeOrMergeAudioJob } from '@server/lib/video'
11import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 11import { generateWebTorrentVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
12import { ThumbnailModel } from '@server/models/video/thumbnail' 12import { ThumbnailModel } from '@server/models/video/thumbnail'
13import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/types/models/video/video-import' 13import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/types/models/video/video-import'
14import { 14import {
@@ -124,7 +124,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
124 extname: fileExt, 124 extname: fileExt,
125 resolution: videoFileResolution, 125 resolution: videoFileResolution,
126 size: stats.size, 126 size: stats.size,
127 filename: generateVideoFilename(videoImport.Video, false, videoFileResolution, fileExt), 127 filename: generateWebTorrentVideoFilename(videoFileResolution, fileExt),
128 fps, 128 fps,
129 videoId: videoImport.videoId 129 videoId: videoImport.videoId
130 } 130 }
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 014cd3fcf..f106d69fb 100644
--- a/server/lib/live/live-manager.ts
+++ b/server/lib/live/live-manager.ts
@@ -4,24 +4,25 @@ 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'
20import { MuxingSession } from './shared' 21import { MuxingSession } from './shared'
21 22
22const NodeRtmpSession = require('node-media-server/node_rtmp_session') 23const NodeRtmpSession = require('node-media-server/src/node_rtmp_session')
23const context = require('node-media-server/node_core_ctx') 24const context = require('node-media-server/src/node_core_ctx')
24const nodeMediaServerLogger = require('node-media-server/node_core_logger') 25const nodeMediaServerLogger = require('node-media-server/src/node_core_logger')
25 26
26// Disable node media server logs 27// Disable node media server logs
27nodeMediaServerLogger.setLogType(0) 28nodeMediaServerLogger.setLogType(0)
@@ -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/moderation.ts b/server/lib/moderation.ts
index 14e00518e..a42ab5b7f 100644
--- a/server/lib/moderation.ts
+++ b/server/lib/moderation.ts
@@ -23,7 +23,7 @@ import { ActivityCreate } from '../../shared/models/activitypub'
23import { VideoObject } from '../../shared/models/activitypub/objects' 23import { VideoObject } from '../../shared/models/activitypub/objects'
24import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' 24import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object'
25import { LiveVideoCreate, VideoCreate, VideoImportCreate } from '../../shared/models/videos' 25import { LiveVideoCreate, VideoCreate, VideoImportCreate } from '../../shared/models/videos'
26import { VideoCommentCreate } from '../../shared/models/videos/comment/video-comment.model' 26import { VideoCommentCreate } from '../../shared/models/videos/comment'
27import { ActorModel } from '../models/actor/actor' 27import { ActorModel } from '../models/actor/actor'
28import { UserModel } from '../models/user/user' 28import { UserModel } from '../models/user/user'
29import { VideoModel } from '../models/video/video' 29import { VideoModel } from '../models/video/video'
diff --git a/server/lib/plugins/register-helpers.ts b/server/lib/plugins/register-helpers.ts
index 09275f9ba..af533effd 100644
--- a/server/lib/plugins/register-helpers.ts
+++ b/server/lib/plugins/register-helpers.ts
@@ -1,13 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { logger } from '@server/helpers/logger' 2import { logger } from '@server/helpers/logger'
3import {
4 VIDEO_CATEGORIES,
5 VIDEO_LANGUAGES,
6 VIDEO_LICENCES,
7 VIDEO_PLAYLIST_PRIVACIES,
8 VIDEO_PRIVACIES
9} from '@server/initializers/constants'
10import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth' 3import { onExternalUserAuthenticated } from '@server/lib/auth/external-auth'
4import { VideoConstantManagerFactory } from '@server/lib/plugins/video-constant-manager-factory'
11import { PluginModel } from '@server/models/server/plugin' 5import { PluginModel } from '@server/models/server/plugin'
12import { 6import {
13 RegisterServerAuthExternalOptions, 7 RegisterServerAuthExternalOptions,
@@ -18,41 +12,18 @@ import {
18} from '@server/types/plugins' 12} from '@server/types/plugins'
19import { 13import {
20 EncoderOptionsBuilder, 14 EncoderOptionsBuilder,
21 PluginPlaylistPrivacyManager,
22 PluginSettingsManager, 15 PluginSettingsManager,
23 PluginStorageManager, 16 PluginStorageManager,
24 PluginVideoCategoryManager,
25 PluginVideoLanguageManager,
26 PluginVideoLicenceManager,
27 PluginVideoPrivacyManager,
28 RegisterServerHookOptions, 17 RegisterServerHookOptions,
29 RegisterServerSettingOptions, 18 RegisterServerSettingOptions,
30 serverHookObject 19 serverHookObject,
20 VideoPlaylistPrivacy,
21 VideoPrivacy
31} from '@shared/models' 22} from '@shared/models'
32import { VideoTranscodingProfilesManager } from '../transcoding/video-transcoding-profiles' 23import { VideoTranscodingProfilesManager } from '../transcoding/video-transcoding-profiles'
33import { buildPluginHelpers } from './plugin-helpers-builder' 24import { buildPluginHelpers } from './plugin-helpers-builder'
34 25
35type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
36type VideoConstant = { [key in number | string]: string }
37
38type UpdatedVideoConstant = {
39 [name in AlterableVideoConstant]: {
40 [ npmName: string]: {
41 added: { key: number | string, label: string }[]
42 deleted: { key: number | string, label: string }[]
43 }
44 }
45}
46
47export class RegisterHelpers { 26export class RegisterHelpers {
48 private readonly updatedVideoConstants: UpdatedVideoConstant = {
49 playlistPrivacy: { },
50 privacy: { },
51 language: { },
52 licence: { },
53 category: { }
54 }
55
56 private readonly transcodingProfiles: { 27 private readonly transcodingProfiles: {
57 [ npmName: string ]: { 28 [ npmName: string ]: {
58 type: 'vod' | 'live' 29 type: 'vod' | 'live'
@@ -78,6 +49,7 @@ export class RegisterHelpers {
78 private readonly onSettingsChangeCallbacks: ((settings: any) => Promise<any>)[] = [] 49 private readonly onSettingsChangeCallbacks: ((settings: any) => Promise<any>)[] = []
79 50
80 private readonly router: express.Router 51 private readonly router: express.Router
52 private readonly videoConstantManagerFactory: VideoConstantManagerFactory
81 53
82 constructor ( 54 constructor (
83 private readonly npmName: string, 55 private readonly npmName: string,
@@ -85,6 +57,7 @@ export class RegisterHelpers {
85 private readonly onHookAdded: (options: RegisterServerHookOptions) => void 57 private readonly onHookAdded: (options: RegisterServerHookOptions) => void
86 ) { 58 ) {
87 this.router = express.Router() 59 this.router = express.Router()
60 this.videoConstantManagerFactory = new VideoConstantManagerFactory(this.npmName)
88 } 61 }
89 62
90 buildRegisterHelpers (): RegisterServerOptions { 63 buildRegisterHelpers (): RegisterServerOptions {
@@ -96,13 +69,13 @@ export class RegisterHelpers {
96 const settingsManager = this.buildSettingsManager() 69 const settingsManager = this.buildSettingsManager()
97 const storageManager = this.buildStorageManager() 70 const storageManager = this.buildStorageManager()
98 71
99 const videoLanguageManager = this.buildVideoLanguageManager() 72 const videoLanguageManager = this.videoConstantManagerFactory.createVideoConstantManager<string>('language')
100 73
101 const videoLicenceManager = this.buildVideoLicenceManager() 74 const videoLicenceManager = this.videoConstantManagerFactory.createVideoConstantManager<number>('licence')
102 const videoCategoryManager = this.buildVideoCategoryManager() 75 const videoCategoryManager = this.videoConstantManagerFactory.createVideoConstantManager<number>('category')
103 76
104 const videoPrivacyManager = this.buildVideoPrivacyManager() 77 const videoPrivacyManager = this.videoConstantManagerFactory.createVideoConstantManager<VideoPrivacy>('privacy')
105 const playlistPrivacyManager = this.buildPlaylistPrivacyManager() 78 const playlistPrivacyManager = this.videoConstantManagerFactory.createVideoConstantManager<VideoPlaylistPrivacy>('playlistPrivacy')
106 79
107 const transcodingManager = this.buildTranscodingManager() 80 const transcodingManager = this.buildTranscodingManager()
108 81
@@ -122,12 +95,38 @@ export class RegisterHelpers {
122 settingsManager, 95 settingsManager,
123 storageManager, 96 storageManager,
124 97
125 videoLanguageManager, 98 videoLanguageManager: {
126 videoCategoryManager, 99 ...videoLanguageManager,
127 videoLicenceManager, 100 /** @deprecated use `addConstant` instead **/
101 addLanguage: videoLanguageManager.addConstant,
102 /** @deprecated use `deleteConstant` instead **/
103 deleteLanguage: videoLanguageManager.deleteConstant
104 },
105 videoCategoryManager: {
106 ...videoCategoryManager,
107 /** @deprecated use `addConstant` instead **/
108 addCategory: videoCategoryManager.addConstant,
109 /** @deprecated use `deleteConstant` instead **/
110 deleteCategory: videoCategoryManager.deleteConstant
111 },
112 videoLicenceManager: {
113 ...videoLicenceManager,
114 /** @deprecated use `addConstant` instead **/
115 addLicence: videoLicenceManager.addConstant,
116 /** @deprecated use `deleteConstant` instead **/
117 deleteLicence: videoLicenceManager.deleteConstant
118 },
128 119
129 videoPrivacyManager, 120 videoPrivacyManager: {
130 playlistPrivacyManager, 121 ...videoPrivacyManager,
122 /** @deprecated use `deleteConstant` instead **/
123 deletePrivacy: videoPrivacyManager.deleteConstant
124 },
125 playlistPrivacyManager: {
126 ...playlistPrivacyManager,
127 /** @deprecated use `deleteConstant` instead **/
128 deletePlaylistPrivacy: playlistPrivacyManager.deleteConstant
129 },
131 130
132 transcodingManager, 131 transcodingManager,
133 132
@@ -141,29 +140,7 @@ export class RegisterHelpers {
141 } 140 }
142 141
143 reinitVideoConstants (npmName: string) { 142 reinitVideoConstants (npmName: string) {
144 const hash = { 143 this.videoConstantManagerFactory.resetVideoConstants(npmName)
145 language: VIDEO_LANGUAGES,
146 licence: VIDEO_LICENCES,
147 category: VIDEO_CATEGORIES,
148 privacy: VIDEO_PRIVACIES,
149 playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES
150 }
151 const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ]
152
153 for (const type of types) {
154 const updatedConstants = this.updatedVideoConstants[type][npmName]
155 if (!updatedConstants) continue
156
157 for (const added of updatedConstants.added) {
158 delete hash[type][added.key]
159 }
160
161 for (const deleted of updatedConstants.deleted) {
162 hash[type][deleted.key] = deleted.label
163 }
164
165 delete this.updatedVideoConstants[type][npmName]
166 }
167 } 144 }
168 145
169 reinitTranscodingProfilesAndEncoders (npmName: string) { 146 reinitTranscodingProfilesAndEncoders (npmName: string) {
@@ -291,119 +268,6 @@ export class RegisterHelpers {
291 } 268 }
292 } 269 }
293 270
294 private buildVideoLanguageManager (): PluginVideoLanguageManager {
295 return {
296 addLanguage: (key: string, label: string) => {
297 return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label })
298 },
299
300 deleteLanguage: (key: string) => {
301 return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key })
302 }
303 }
304 }
305
306 private buildVideoCategoryManager (): PluginVideoCategoryManager {
307 return {
308 addCategory: (key: number, label: string) => {
309 return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label })
310 },
311
312 deleteCategory: (key: number) => {
313 return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key })
314 }
315 }
316 }
317
318 private buildVideoPrivacyManager (): PluginVideoPrivacyManager {
319 return {
320 deletePrivacy: (key: number) => {
321 return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key })
322 }
323 }
324 }
325
326 private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager {
327 return {
328 deletePlaylistPrivacy: (key: number) => {
329 return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key })
330 }
331 }
332 }
333
334 private buildVideoLicenceManager (): PluginVideoLicenceManager {
335 return {
336 addLicence: (key: number, label: string) => {
337 return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label })
338 },
339
340 deleteLicence: (key: number) => {
341 return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key })
342 }
343 }
344 }
345
346 private addConstant<T extends string | number> (parameters: {
347 npmName: string
348 type: AlterableVideoConstant
349 obj: VideoConstant
350 key: T
351 label: string
352 }) {
353 const { npmName, type, obj, key, label } = parameters
354
355 if (obj[key]) {
356 logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key)
357 return false
358 }
359
360 if (!this.updatedVideoConstants[type][npmName]) {
361 this.updatedVideoConstants[type][npmName] = {
362 added: [],
363 deleted: []
364 }
365 }
366
367 this.updatedVideoConstants[type][npmName].added.push({ key, label })
368 obj[key] = label
369
370 return true
371 }
372
373 private deleteConstant<T extends string | number> (parameters: {
374 npmName: string
375 type: AlterableVideoConstant
376 obj: VideoConstant
377 key: T
378 }) {
379 const { npmName, type, obj, key } = parameters
380
381 if (!obj[key]) {
382 logger.warn('Cannot delete %s by plugin %s: key %s does not exist.', type, npmName, key)
383 return false
384 }
385
386 if (!this.updatedVideoConstants[type][npmName]) {
387 this.updatedVideoConstants[type][npmName] = {
388 added: [],
389 deleted: []
390 }
391 }
392
393 const updatedConstants = this.updatedVideoConstants[type][npmName]
394
395 const alreadyAdded = updatedConstants.added.find(a => a.key === key)
396 if (alreadyAdded) {
397 updatedConstants.added.filter(a => a.key !== key)
398 } else if (obj[key]) {
399 updatedConstants.deleted.push({ key, label: obj[key] })
400 }
401
402 delete obj[key]
403
404 return true
405 }
406
407 private buildTranscodingManager () { 271 private buildTranscodingManager () {
408 const self = this 272 const self = this
409 273
diff --git a/server/lib/plugins/video-constant-manager-factory.ts b/server/lib/plugins/video-constant-manager-factory.ts
new file mode 100644
index 000000000..f04dde29f
--- /dev/null
+++ b/server/lib/plugins/video-constant-manager-factory.ts
@@ -0,0 +1,139 @@
1import { logger } from '@server/helpers/logger'
2import {
3 VIDEO_CATEGORIES,
4 VIDEO_LANGUAGES,
5 VIDEO_LICENCES,
6 VIDEO_PLAYLIST_PRIVACIES,
7 VIDEO_PRIVACIES
8} from '@server/initializers/constants'
9import { ConstantManager } from '@shared/models/plugins/server/plugin-constant-manager.model'
10
11type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
12type VideoConstant = Record<number | string, string>
13
14type UpdatedVideoConstant = {
15 [name in AlterableVideoConstant]: {
16 [ npmName: string]: {
17 added: VideoConstant[]
18 deleted: VideoConstant[]
19 }
20 }
21}
22
23const constantsHash: { [key in AlterableVideoConstant]: VideoConstant } = {
24 language: VIDEO_LANGUAGES,
25 licence: VIDEO_LICENCES,
26 category: VIDEO_CATEGORIES,
27 privacy: VIDEO_PRIVACIES,
28 playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES
29}
30
31export class VideoConstantManagerFactory {
32 private readonly updatedVideoConstants: UpdatedVideoConstant = {
33 playlistPrivacy: { },
34 privacy: { },
35 language: { },
36 licence: { },
37 category: { }
38 }
39
40 constructor (
41 private readonly npmName: string
42 ) {}
43
44 public resetVideoConstants (npmName: string) {
45 const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ]
46 for (const type of types) {
47 this.resetConstants({ npmName, type })
48 }
49 }
50
51 private resetConstants (parameters: { npmName: string, type: AlterableVideoConstant }) {
52 const { npmName, type } = parameters
53 const updatedConstants = this.updatedVideoConstants[type][npmName]
54
55 if (!updatedConstants) return
56
57 for (const added of updatedConstants.added) {
58 delete constantsHash[type][added.key]
59 }
60
61 for (const deleted of updatedConstants.deleted) {
62 constantsHash[type][deleted.key] = deleted.label
63 }
64
65 delete this.updatedVideoConstants[type][npmName]
66 }
67
68 public createVideoConstantManager<K extends number | string>(type: AlterableVideoConstant): ConstantManager<K> {
69 const { npmName } = this
70 return {
71 addConstant: (key: K, label: string) => this.addConstant({ npmName, type, key, label }),
72 deleteConstant: (key: K) => this.deleteConstant({ npmName, type, key }),
73 getConstantValue: (key: K) => constantsHash[type][key],
74 getConstants: () => constantsHash[type] as Record<K, string>,
75 resetConstants: () => this.resetConstants({ npmName, type })
76 }
77 }
78
79 private addConstant<T extends string | number> (parameters: {
80 npmName: string
81 type: AlterableVideoConstant
82 key: T
83 label: string
84 }) {
85 const { npmName, type, key, label } = parameters
86 const obj = constantsHash[type]
87
88 if (obj[key]) {
89 logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key)
90 return false
91 }
92
93 if (!this.updatedVideoConstants[type][npmName]) {
94 this.updatedVideoConstants[type][npmName] = {
95 added: [],
96 deleted: []
97 }
98 }
99
100 this.updatedVideoConstants[type][npmName].added.push({ key: key, label } as VideoConstant)
101 obj[key] = label
102
103 return true
104 }
105
106 private deleteConstant<T extends string | number> (parameters: {
107 npmName: string
108 type: AlterableVideoConstant
109 key: T
110 }) {
111 const { npmName, type, key } = parameters
112 const obj = constantsHash[type]
113
114 if (!obj[key]) {
115 logger.warn('Cannot delete %s by plugin %s: key %s does not exist.', type, npmName, key)
116 return false
117 }
118
119 if (!this.updatedVideoConstants[type][npmName]) {
120 this.updatedVideoConstants[type][npmName] = {
121 added: [],
122 deleted: []
123 }
124 }
125
126 const updatedConstants = this.updatedVideoConstants[type][npmName]
127
128 const alreadyAdded = updatedConstants.added.find(a => a.key === key)
129 if (alreadyAdded) {
130 updatedConstants.added.filter(a => a.key !== key)
131 } else if (obj[key]) {
132 updatedConstants.deleted.push({ key, label: obj[key] } as VideoConstant)
133 }
134
135 delete obj[key]
136
137 return true
138 }
139}
diff --git a/server/lib/schedulers/plugins-check-scheduler.ts b/server/lib/schedulers/plugins-check-scheduler.ts
index 9a1ae3ec5..c95e109b0 100644
--- a/server/lib/schedulers/plugins-check-scheduler.ts
+++ b/server/lib/schedulers/plugins-check-scheduler.ts
@@ -1,12 +1,12 @@
1import { chunk } from 'lodash'
2import { compareSemVer } from '@shared/core-utils'
1import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
2import { AbstractScheduler } from './abstract-scheduler'
3import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
4import { CONFIG } from '../../initializers/config' 4import { CONFIG } from '../../initializers/config'
5import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
5import { PluginModel } from '../../models/server/plugin' 6import { PluginModel } from '../../models/server/plugin'
6import { chunk } from 'lodash'
7import { getLatestPluginsVersion } from '../plugins/plugin-index'
8import { compareSemVer } from '../../../shared/core-utils/miscs/miscs'
9import { Notifier } from '../notifier' 7import { Notifier } from '../notifier'
8import { getLatestPluginsVersion } from '../plugins/plugin-index'
9import { AbstractScheduler } from './abstract-scheduler'
10 10
11export class PluginsCheckScheduler extends AbstractScheduler { 11export class PluginsCheckScheduler extends AbstractScheduler {
12 12
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 1ad63baf3..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 { generateVideoFilename, generateVideoStreamingPlaylistName, 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/**
@@ -60,7 +67,7 @@ async function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile
60 67
61 // Important to do this before getVideoFilename() to take in account the new filename 68 // Important to do this before getVideoFilename() to take in account the new filename
62 inputVideoFile.extname = newExtname 69 inputVideoFile.extname = newExtname
63 inputVideoFile.filename = generateVideoFilename(video, false, resolution, newExtname) 70 inputVideoFile.filename = generateWebTorrentVideoFilename(resolution, newExtname)
64 71
65 const videoOutputPath = getVideoFilePath(video, inputVideoFile) 72 const videoOutputPath = getVideoFilePath(video, inputVideoFile)
66 73
@@ -86,7 +93,7 @@ async function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolut
86 const newVideoFile = new VideoFileModel({ 93 const newVideoFile = new VideoFileModel({
87 resolution, 94 resolution,
88 extname, 95 extname,
89 filename: generateVideoFilename(video, false, resolution, extname), 96 filename: generateWebTorrentVideoFilename(resolution, extname),
90 size: 0, 97 size: 0,
91 videoId: video.id 98 videoId: video.id
92 }) 99 })
@@ -169,7 +176,7 @@ async function mergeAudioVideofile (video: MVideoFullLight, resolution: VideoRes
169 176
170 // Important to do this before getVideoFilename() to take in account the new file extension 177 // Important to do this before getVideoFilename() to take in account the new file extension
171 inputVideoFile.extname = newExtname 178 inputVideoFile.extname = newExtname
172 inputVideoFile.filename = generateVideoFilename(video, false, inputVideoFile.resolution, newExtname) 179 inputVideoFile.filename = generateWebTorrentVideoFilename(inputVideoFile.resolution, newExtname)
173 180
174 const videoOutputPath = getVideoFilePath(video, inputVideoFile) 181 const videoOutputPath = getVideoFilePath(video, inputVideoFile)
175 // ffmpeg generated a new video file, so update the video duration 182 // ffmpeg generated a new video file, so update the video duration
@@ -271,15 +278,15 @@ async function generateHlsPlaylistCommon (options: {
271 const videoTranscodedBasePath = join(transcodeDirectory, type) 278 const videoTranscodedBasePath = join(transcodeDirectory, type)
272 await ensureDir(videoTranscodedBasePath) 279 await ensureDir(videoTranscodedBasePath)
273 280
274 const videoFilename = generateVideoStreamingPlaylistName(video.uuid, 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)
@@ -319,20 +330,20 @@ async function generateHlsPlaylistCommon (options: {
319 resolution, 330 resolution,
320 extname, 331 extname,
321 size: 0, 332 size: 0,
322 filename: generateVideoFilename(video, true, resolution, extname), 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 1708c479a..1e4382108 100644
--- a/server/lib/video-paths.ts
+++ b/server/lib/video-paths.ts
@@ -3,29 +3,17 @@ import { extractVideo } from '@server/helpers/video'
3import { CONFIG } from '@server/initializers/config' 3import { 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'
7import { removeFragmentedMP4Ext } from '@shared/core-utils'
6 8
7// ################## Video file name ################## 9// ################## Video file name ##################
8 10
9function generateVideoFilename (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, isHls: boolean, resolution: number, extname: string) { 11function generateWebTorrentVideoFilename (resolution: number, extname: string) {
10 const video = extractVideo(videoOrPlaylist) 12 return buildUUID() + '-' + resolution + extname
11
12 // FIXME: use a generated uuid instead, that will break compatibility with PeerTube < 3.1
13 // const uuid = uuidv4()
14 const uuid = video.uuid
15
16 if (isHls) {
17 return generateVideoStreamingPlaylistName(uuid, resolution)
18 }
19
20 return generateWebTorrentVideoName(uuid, resolution, extname)
21} 13}
22 14
23function generateVideoStreamingPlaylistName (uuid: string, resolution: number) { 15function generateHLSVideoFilename (resolution: number) {
24 return `${uuid}-${resolution}-fragmented.mp4` 16 return `${buildUUID()}-${resolution}-fragmented.mp4`
25}
26
27function generateWebTorrentVideoName (uuid: string, resolution: number, extname: string) {
28 return uuid + '-' + resolution + extname
29} 17}
30 18
31function getVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile, isRedundancy = false) { 19function getVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile, isRedundancy = false) {
@@ -63,15 +51,28 @@ function getHLSDirectory (video: MVideoUUID, isRedundancy = false) {
63 return join(baseDir, video.uuid) 51 return join(baseDir, video.uuid)
64} 52}
65 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
66// ################## Torrents ################## 71// ################## Torrents ##################
67 72
68function generateTorrentFileName (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, resolution: number) { 73function generateTorrentFileName (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, resolution: number) {
69 const video = extractVideo(videoOrPlaylist)
70 const extension = '.torrent' 74 const extension = '.torrent'
71 75 const uuid = buildUUID()
72 // FIXME: use a generated uuid instead, that will break compatibility with PeerTube < 3.1
73 // const uuid = uuidv4()
74 const uuid = video.uuid
75 76
76 if (isStreamingPlaylist(videoOrPlaylist)) { 77 if (isStreamingPlaylist(videoOrPlaylist)) {
77 return `${uuid}-${resolution}-${videoOrPlaylist.getStringType()}${extension}` 78 return `${uuid}-${resolution}-${videoOrPlaylist.getStringType()}${extension}`
@@ -95,15 +96,18 @@ function getLocalVideoFileMetadataUrl (video: MVideoUUID, videoFile: MVideoFile)
95// --------------------------------------------------------------------------- 96// ---------------------------------------------------------------------------
96 97
97export { 98export {
98 generateVideoStreamingPlaylistName, 99 generateHLSVideoFilename,
99 generateWebTorrentVideoName, 100 generateWebTorrentVideoFilename,
100 generateVideoFilename, 101
101 getVideoFilePath, 102 getVideoFilePath,
102 103
103 generateTorrentFileName, 104 generateTorrentFileName,
104 getTorrentFilePath, 105 getTorrentFilePath,
105 106
106 getHLSDirectory, 107 getHLSDirectory,
108 generateHLSMasterPlaylistFilename,
109 generateHlsSha256SegmentsFilename,
110 getHlsResolutionPlaylistFilename,
107 111
108 getLocalVideoFileMetadataUrl, 112 getLocalVideoFileMetadataUrl,
109 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()) {