1 import { remove } from 'fs-extra'
2 import { extname, join } from 'path'
3 import { extractVideo } from '@server/helpers/video'
4 import { CONFIG } from '@server/initializers/config'
6 MStreamingPlaylistVideo,
9 MVideoFileStreamingPlaylistVideo,
12 } from '@server/types/models'
13 import { buildUUID } from '@shared/extra-utils'
14 import { VideoStorage } from '@shared/models'
15 import { makeHLSFileAvailable, makeWebTorrentFileAvailable } from './object-storage'
16 import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from './paths'
18 type MakeAvailableCB <T> = (path: string) => Promise<T> | T
20 class VideoPathManager {
22 private static instance: VideoPathManager
24 private constructor () {}
26 getFSHLSOutputPath (video: MVideoUUID, filename?: string) {
27 const base = getHLSDirectory(video)
28 if (!filename) return base
30 return join(base, filename)
33 getFSRedundancyVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
34 if (videoFile.isHLS()) {
35 const video = extractVideo(videoOrPlaylist)
37 return join(getHLSRedundancyDirectory(video), videoFile.filename)
40 return join(CONFIG.STORAGE.REDUNDANCY_DIR, videoFile.filename)
43 getFSVideoFileOutputPath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
44 if (videoFile.isHLS()) {
45 const video = extractVideo(videoOrPlaylist)
47 return join(getHLSDirectory(video), videoFile.filename)
50 return join(CONFIG.STORAGE.VIDEOS_DIR, videoFile.filename)
53 async makeAvailableVideoFile <T> (videoFile: MVideoFileVideo | MVideoFileStreamingPlaylistVideo, cb: MakeAvailableCB<T>) {
54 if (videoFile.storage === VideoStorage.FILE_SYSTEM) {
55 return this.makeAvailableFactory(
56 () => this.getFSVideoFileOutputPath(videoFile.getVideoOrStreamingPlaylist(), videoFile),
62 const destination = this.buildTMPDestination(videoFile.filename)
64 if (videoFile.isHLS()) {
65 const playlist = (videoFile as MVideoFileStreamingPlaylistVideo).VideoStreamingPlaylist
67 return this.makeAvailableFactory(
68 () => makeHLSFileAvailable(playlist, videoFile.filename, destination),
74 return this.makeAvailableFactory(
75 () => makeWebTorrentFileAvailable(videoFile.filename, destination),
81 async makeAvailableResolutionPlaylistFile <T> (videoFile: MVideoFileStreamingPlaylistVideo, cb: MakeAvailableCB<T>) {
82 const filename = getHlsResolutionPlaylistFilename(videoFile.filename)
84 if (videoFile.storage === VideoStorage.FILE_SYSTEM) {
85 return this.makeAvailableFactory(
86 () => join(getHLSDirectory(videoFile.getVideo()), filename),
92 const playlist = videoFile.VideoStreamingPlaylist
93 return this.makeAvailableFactory(
94 () => makeHLSFileAvailable(playlist, filename, this.buildTMPDestination(filename)),
100 async makeAvailablePlaylistFile <T> (playlist: MStreamingPlaylistVideo, filename: string, cb: MakeAvailableCB<T>) {
101 if (playlist.storage === VideoStorage.FILE_SYSTEM) {
102 return this.makeAvailableFactory(
103 () => join(getHLSDirectory(playlist.Video), filename),
109 return this.makeAvailableFactory(
110 () => makeHLSFileAvailable(playlist, filename, this.buildTMPDestination(filename)),
116 private async makeAvailableFactory <T> (method: () => Promise<string> | string, clean: boolean, cb: MakeAvailableCB<T>) {
119 const destination = await method()
122 result = await cb(destination)
124 if (destination && clean) await remove(destination)
128 if (clean) await remove(destination)
133 private buildTMPDestination (filename: string) {
134 return join(CONFIG.STORAGE.TMP_DIR, buildUUID() + extname(filename))
138 static get Instance () {
139 return this.instance || (this.instance = new this())
143 // ---------------------------------------------------------------------------