1 import { remove } from 'fs-extra'
2 import { extname, join } from 'path'
3 import { buildUUID } from '@server/helpers/uuid'
4 import { extractVideo } from '@server/helpers/video'
5 import { CONFIG } from '@server/initializers/config'
6 import { MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/types/models'
7 import { VideoStorage } from '@shared/models'
8 import { makeHLSFileAvailable, makeWebTorrentFileAvailable } from './object-storage'
9 import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from './paths'
11 type MakeAvailableCB <T> = (path: string) => Promise<T> | T
13 class VideoPathManager {
15 private static instance: VideoPathManager
17 private constructor () {}
19 getFSHLSOutputPath (video: MVideoUUID, filename?: string) {
20 const base = getHLSDirectory(video)
21 if (!filename) return base
23 return join(base, filename)
26 getFSRedundancyVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
27 if (videoFile.isHLS()) {
28 const video = extractVideo(videoOrPlaylist)
30 return join(getHLSRedundancyDirectory(video), videoFile.filename)
33 return join(CONFIG.STORAGE.REDUNDANCY_DIR, videoFile.filename)
36 getFSVideoFileOutputPath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
37 if (videoFile.isHLS()) {
38 const video = extractVideo(videoOrPlaylist)
40 return join(getHLSDirectory(video), videoFile.filename)
43 return join(CONFIG.STORAGE.VIDEOS_DIR, videoFile.filename)
46 async makeAvailableVideoFile <T> (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile, cb: MakeAvailableCB<T>) {
47 if (videoFile.storage === VideoStorage.FILE_SYSTEM) {
48 return this.makeAvailableFactory(
49 () => this.getFSVideoFileOutputPath(videoOrPlaylist, videoFile),
55 const destination = this.buildTMPDestination(videoFile.filename)
57 if (videoFile.isHLS()) {
58 const video = extractVideo(videoOrPlaylist)
60 return this.makeAvailableFactory(
61 () => makeHLSFileAvailable(videoOrPlaylist as MStreamingPlaylistVideo, video, videoFile.filename, destination),
67 return this.makeAvailableFactory(
68 () => makeWebTorrentFileAvailable(videoFile.filename, destination),
74 async makeAvailableResolutionPlaylistFile <T> (playlist: MStreamingPlaylistVideo, videoFile: MVideoFile, cb: MakeAvailableCB<T>) {
75 const filename = getHlsResolutionPlaylistFilename(videoFile.filename)
77 if (videoFile.storage === VideoStorage.FILE_SYSTEM) {
78 return this.makeAvailableFactory(
79 () => join(getHLSDirectory(playlist.Video), filename),
85 return this.makeAvailableFactory(
86 () => makeHLSFileAvailable(playlist, playlist.Video, filename, this.buildTMPDestination(filename)),
92 async makeAvailablePlaylistFile <T> (playlist: MStreamingPlaylistVideo, filename: string, cb: MakeAvailableCB<T>) {
93 if (playlist.storage === VideoStorage.FILE_SYSTEM) {
94 return this.makeAvailableFactory(
95 () => join(getHLSDirectory(playlist.Video), filename),
101 return this.makeAvailableFactory(
102 () => makeHLSFileAvailable(playlist, playlist.Video, filename, this.buildTMPDestination(filename)),
108 private async makeAvailableFactory <T> (method: () => Promise<string> | string, clean: boolean, cb: MakeAvailableCB<T>) {
111 const destination = await method()
114 result = await cb(destination)
116 if (destination && clean) await remove(destination)
120 if (clean) await remove(destination)
125 private buildTMPDestination (filename: string) {
126 return join(CONFIG.STORAGE.TMP_DIR, buildUUID() + extname(filename))
130 static get Instance () {
131 return this.instance || (this.instance = new this())
135 // ---------------------------------------------------------------------------