diff --git a/server/core/initializers/constants.ts b/server/core/initializers/constants.ts index 596b7688d..9e9dfbf90 100644 --- a/server/core/initializers/constants.ts +++ b/server/core/initializers/constants.ts @@ -1368,7 +1368,7 @@ function buildVideoMimetypeExt () { // The standard video format used by many Sony and Panasonic HD camcorders. // It is also used for storing high definition video on Blu-ray discs. - 'video/mp2t': '.mts', + 'video/mp2t': [ '.mts', 'ts' ], 'video/vnd.dlna.mpeg-tts': '.mts', 'video/m2ts': '.m2ts', diff --git a/server/core/lib/hls.ts b/server/core/lib/hls.ts index 6b473c488..331009abf 100644 --- a/server/core/lib/hls.ts +++ b/server/core/lib/hls.ts @@ -144,8 +144,7 @@ function updateMasterHLSPlaylist (video: MVideo, playlistArg: MStreamingPlaylist playlist.playlistUrl = await storeHLSFileFromContent({ playlist, pathOrFilename: playlist.playlistFilename, - content: masterPlaylistContent, - contentType: 'application/x-mpegurl; charset=utf-8' + content: masterPlaylistContent }) logger.info(`Updated master playlist file of video ${video.uuid} to object storage ${playlist.playlistUrl}`, lTags(video.uuid)) @@ -202,8 +201,7 @@ function updateSha256VODSegments (video: MVideo, playlistArg: MStreamingPlaylist playlist.segmentsSha256Url = await storeHLSFileFromContent({ playlist, pathOrFilename: playlist.segmentsSha256Filename, - content: JSON.stringify(json), - contentType: 'application/json; charset=utf-8' + content: JSON.stringify(json) }) } else { const outputPath = VideoPathManager.Instance.getFSHLSOutputPath(video, playlist.segmentsSha256Filename) diff --git a/server/core/lib/job-queue/handlers/move-to-object-storage.ts b/server/core/lib/job-queue/handlers/move-to-object-storage.ts index b66716808..70a25b5dc 100644 --- a/server/core/lib/job-queue/handlers/move-to-object-storage.ts +++ b/server/core/lib/job-queue/handlers/move-to-object-storage.ts @@ -122,8 +122,7 @@ async function moveCaptionFiles (captions: MVideoCaption[], hls: MStreamingPlayl caption.m3u8Url = await storeHLSFileFromContent({ playlist: hls, pathOrFilename: caption.m3u8Filename, - content, - contentType: 'application/vnd.apple.mpegurl; charset=utf-8' + content }) await caption.save() diff --git a/server/core/lib/live/shared/muxing-session.ts b/server/core/lib/live/shared/muxing-session.ts index 42b0f1e29..fcda0e93d 100644 --- a/server/core/lib/live/shared/muxing-session.ts +++ b/server/core/lib/live/shared/muxing-session.ts @@ -229,8 +229,7 @@ class MuxingSession extends EventEmitter implements MuxingSession { { playlist: this.streamingPlaylist, pathOrFilename: this.streamingPlaylist.playlistFilename, - content: masterContent, - contentType: 'application/x-mpegurl; charset=utf-8' + content: masterContent } ) @@ -419,8 +418,7 @@ class MuxingSession extends EventEmitter implements MuxingSession { storeHLSFileFromContent({ playlist: this.streamingPlaylist, pathOrFilename: m3u8Path, - content: filteredPlaylistContent, - contentType: 'application/x-mpegurl; charset=utf-8' + content: filteredPlaylistContent }) ) } catch (err) { diff --git a/server/core/lib/object-storage/object-storage-helpers.ts b/server/core/lib/object-storage/object-storage-helpers.ts index 0635a560f..818cba4da 100644 --- a/server/core/lib/object-storage/object-storage-helpers.ts +++ b/server/core/lib/object-storage/object-storage-helpers.ts @@ -50,8 +50,7 @@ async function storeObject (options: { objectStorageKey: string bucketInfo: BucketInfo isPrivate: boolean - - contentType?: string + contentType: string }): Promise { const { inputPath, objectStorageKey, bucketInfo, isPrivate, contentType } = options @@ -67,8 +66,7 @@ async function storeContent (options: { objectStorageKey: string bucketInfo: BucketInfo isPrivate: boolean - - contentType?: string + contentType: string }): Promise { const { content, objectStorageKey, bucketInfo, isPrivate, contentType } = options @@ -82,8 +80,7 @@ async function storeStream (options: { objectStorageKey: string bucketInfo: BucketInfo isPrivate: boolean - - contentType?: string + contentType: string }): Promise { const { stream, objectStorageKey, bucketInfo, isPrivate, contentType } = options diff --git a/server/core/lib/object-storage/user-export.ts b/server/core/lib/object-storage/user-export.ts index d6ca34ec8..f87c33f97 100644 --- a/server/core/lib/object-storage/user-export.ts +++ b/server/core/lib/object-storage/user-export.ts @@ -9,7 +9,8 @@ export function storeUserExportFile (stream: Readable, userExport: MUserExport) stream, objectStorageKey: generateUserExportObjectStorageKey(userExport.filename), bucketInfo: CONFIG.OBJECT_STORAGE.USER_EXPORTS, - isPrivate: true + isPrivate: true, + contentType: 'application/zip' }) } diff --git a/server/core/lib/object-storage/videos.ts b/server/core/lib/object-storage/videos.ts index 821cc4bd6..95a6748b7 100644 --- a/server/core/lib/object-storage/videos.ts +++ b/server/core/lib/object-storage/videos.ts @@ -2,7 +2,7 @@ import { logger } from '@server/helpers/logger.js' import { CONFIG } from '@server/initializers/config.js' import { MStreamingPlaylistVideo, MStreamingPlaylistVideoUUID, MVideo, MVideoCaption, MVideoFile } from '@server/types/models/index.js' import { MVideoSource } from '@server/types/models/video/video-source.js' -import { basename, join } from 'path' +import { basename, extname, join } from 'path' import { getHLSDirectory } from '../paths.js' import { VideoPathManager } from '../video-path-manager.js' import { @@ -25,6 +25,7 @@ import { updateObjectACL, updatePrefixACL } from './shared/index.js' +import { MIMETYPES } from '@server/initializers/constants.js' export function listHLSFileKeysOf (playlist: MStreamingPlaylistVideo) { return listKeysOfPrefix(generateHLSObjectBaseStorageKey(playlist), CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS) @@ -37,35 +38,38 @@ export function storeHLSFileFromFilename (playlist: MStreamingPlaylistVideo, fil inputPath: join(getHLSDirectory(playlist.Video), filename), objectStorageKey: generateHLSObjectStorageKey(playlist, filename), bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS, - isPrivate: playlist.Video.hasPrivateStaticPath() + isPrivate: playlist.Video.hasPrivateStaticPath(), + contentType: getObjectStorageContentType(filename) }) } export function storeHLSFileFromPath (playlist: MStreamingPlaylistVideo, path: string) { + const filename = basename(path) + return storeObject({ inputPath: path, - objectStorageKey: generateHLSObjectStorageKey(playlist, basename(path)), + objectStorageKey: generateHLSObjectStorageKey(playlist, filename), bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS, - isPrivate: playlist.Video.hasPrivateStaticPath() + isPrivate: playlist.Video.hasPrivateStaticPath(), + contentType: getObjectStorageContentType(filename) }) } -export function storeHLSFileFromContent ( - options: { - playlist: MStreamingPlaylistVideo - pathOrFilename: string - content: string - contentType: string - } -) { - const { playlist, pathOrFilename, content, contentType } = options +export function storeHLSFileFromContent (options: { + playlist: MStreamingPlaylistVideo + pathOrFilename: string + content: string +}) { + const { playlist, pathOrFilename, content } = options + + const filename = basename(pathOrFilename) return storeContent({ content, - objectStorageKey: generateHLSObjectStorageKey(playlist, basename(pathOrFilename)), + objectStorageKey: generateHLSObjectStorageKey(playlist, filename), bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS, isPrivate: playlist.Video.hasPrivateStaticPath(), - contentType + contentType: getObjectStorageContentType(filename) }) } @@ -76,7 +80,8 @@ export function storeWebVideoFile (video: MVideo, file: MVideoFile) { inputPath: VideoPathManager.Instance.getFSVideoFileOutputPath(video, file), objectStorageKey: generateWebVideoObjectStorageKey(file.filename), bucketInfo: CONFIG.OBJECT_STORAGE.WEB_VIDEOS, - isPrivate: video.hasPrivateStaticPath() + isPrivate: video.hasPrivateStaticPath(), + contentType: getObjectStorageContentType(file.filename) }) } @@ -88,7 +93,7 @@ export function storeVideoCaption (inputPath: string, filename: string) { objectStorageKey: generateCaptionObjectStorageKey(filename), bucketInfo: CONFIG.OBJECT_STORAGE.CAPTIONS, isPrivate: false, - contentType: 'text/vtt; charset=UTF-8' + contentType: getObjectStorageContentType(filename) }) } @@ -99,7 +104,8 @@ export function storeOriginalVideoFile (inputPath: string, filename: string) { inputPath, objectStorageKey: generateOriginalVideoObjectStorageKey(filename), bucketInfo: CONFIG.OBJECT_STORAGE.ORIGINAL_VIDEO_FILES, - isPrivate: true + isPrivate: true, + contentType: getObjectStorageContentType(filename) }) } @@ -279,3 +285,25 @@ export function getCaptionReadStream (options: { rangeHeader }) } + +// --------------------------------------------------------------------------- +// Private +// --------------------------------------------------------------------------- + +function getObjectStorageContentType (filename: string) { + if (filename.endsWith('.m3u8')) { + return 'application/x-mpegURL; charset=utf-8' + } + + if (filename.endsWith('.json')) { + return 'application/json; charset=utf-8' + } + + if (filename.endsWith('.vtt')) { + return 'text/vtt; charset=utf-8' + } + + const ext = extname(filename).toLowerCase() + + return MIMETYPES.VIDEO.EXT_MIMETYPE[ext] || MIMETYPES.AUDIO.EXT_MIMETYPE[ext] +}