Verified Commit 1bb4c9ab authored by Chocobozzz's avatar Chocobozzz
Browse files

Add ability to delete a specific video file

parent 12d84abe
......@@ -107,6 +107,11 @@
<ul>
<li *ngFor="let file of video.files">
{{ file.resolution.label }}: {{ file.size | bytes: 1 }}
<my-global-icon
i18n-ngbTooltip ngbTooltip="Delete this file" iconName="delete" role="button"
(click)="removeVideoFile(video, file, 'webtorrent')"
></my-global-icon>
</li>
</ul>
</div>
......@@ -117,6 +122,11 @@
<ul>
<li *ngFor="let file of video.streamingPlaylists[0].files">
{{ file.resolution.label }}: {{ file.size | bytes: 1 }}
<my-global-icon
i18n-ngbTooltip ngbTooltip="Delete this file" iconName="delete" role="button"
(click)="removeVideoFile(video, file, 'hls')"
></my-global-icon>
</li>
</ul>
</div>
......
......@@ -13,6 +13,13 @@ my-embed {
.video-info > div {
display: flex;
my-global-icon {
width: 16px;
margin-left: 3px;
position: relative;
top: -2px;
}
}
.loading {
......
......@@ -8,7 +8,7 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
import { UserRight, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
import { UserRight, VideoFile, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
import { VideoAdminService } from './video-admin.service'
@Component({
......@@ -196,6 +196,22 @@ export class VideoListComponent extends RestTable implements OnInit {
})
}
async removeVideoFile (video: Video, file: VideoFile, type: 'hls' | 'webtorrent') {
const message = $localize`Are you sure you want to delete this ${file.resolution.label} file?`
const res = await this.confirmService.confirm(message, $localize`Delete file`)
if (res === false) return
this.videoService.removeFile(video.uuid, file.id, type)
.subscribe({
next: () => {
this.notifier.success($localize`File removed.`)
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
private async removeVideos (videos: Video[]) {
const message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`)(
{ count: videos.length },
......
......@@ -305,6 +305,11 @@ export class VideoService {
)
}
removeFile (videoId: number | string, fileId: number, type: 'hls' | 'webtorrent') {
return this.authHttp.delete(VideoService.BASE_VIDEO_URL + '/' + videoId + '/' + type + '/' + fileId)
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
runTranscoding (videoIds: (number | string)[], type: 'hls' | 'webtorrent') {
const body: VideoTranscodingCreate = { transcodingType: type }
......
......@@ -2,6 +2,7 @@ import express from 'express'
import toInt from 'validator/lib/toInt'
import { logger, loggerTagsFactory } from '@server/helpers/logger'
import { federateVideoIfNeeded } from '@server/lib/activitypub/videos'
import { removeAllWebTorrentFiles, removeHLSFile, removeHLSPlaylist, removeWebTorrentFile } from '@server/lib/video-file'
import { VideoFileModel } from '@server/models/video/video-file'
import { HttpStatusCode, UserRight } from '@shared/models'
import {
......@@ -9,10 +10,13 @@ import {
authenticate,
ensureUserHasRight,
videoFileMetadataGetValidator,
videoFilesDeleteHLSFileValidator,
videoFilesDeleteHLSValidator,
videoFilesDeleteWebTorrentFileValidator,
videoFilesDeleteWebTorrentValidator,
videosGetValidator
} from '../../../middlewares'
import { updatePlaylistAfterFileChange } from '@server/lib/hls'
const lTags = loggerTagsFactory('api', 'video')
const filesRouter = express.Router()
......@@ -27,14 +31,26 @@ filesRouter.delete('/:id/hls',
authenticate,
ensureUserHasRight(UserRight.MANAGE_VIDEO_FILES),
asyncMiddleware(videoFilesDeleteHLSValidator),
asyncMiddleware(removeHLSPlaylist)
asyncMiddleware(removeHLSPlaylistController)
)
filesRouter.delete('/:id/hls/:videoFileId',
authenticate,
ensureUserHasRight(UserRight.MANAGE_VIDEO_FILES),
asyncMiddleware(videoFilesDeleteHLSFileValidator),
asyncMiddleware(removeHLSFileController)
)
filesRouter.delete('/:id/webtorrent',
authenticate,
ensureUserHasRight(UserRight.MANAGE_VIDEO_FILES),
asyncMiddleware(videoFilesDeleteWebTorrentValidator),
asyncMiddleware(removeWebTorrentFiles)
asyncMiddleware(removeAllWebTorrentFilesController)
)
filesRouter.delete('/:id/webtorrent/:videoFileId',
authenticate,
ensureUserHasRight(UserRight.MANAGE_VIDEO_FILES),
asyncMiddleware(videoFilesDeleteWebTorrentFileValidator),
asyncMiddleware(removeWebTorrentFileController)
)
// ---------------------------------------------------------------------------
......@@ -51,33 +67,53 @@ async function getVideoFileMetadata (req: express.Request, res: express.Response
return res.json(videoFile.metadata)
}
async function removeHLSPlaylist (req: express.Request, res: express.Response) {
// ---------------------------------------------------------------------------
async function removeHLSPlaylistController (req: express.Request, res: express.Response) {
const video = res.locals.videoAll
logger.info('Deleting HLS playlist of %s.', video.url, lTags(video.uuid))
await removeHLSPlaylist(video)
await federateVideoIfNeeded(video, false, undefined)
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
async function removeHLSFileController (req: express.Request, res: express.Response) {
const video = res.locals.videoAll
const videoFileId = +req.params.videoFileId
const hls = video.getHLSPlaylist()
await video.removeStreamingPlaylistFiles(hls)
await hls.destroy()
logger.info('Deleting HLS file %d of %s.', videoFileId, video.url, lTags(video.uuid))
video.VideoStreamingPlaylists = video.VideoStreamingPlaylists.filter(p => p.id !== hls.id)
const playlist = await removeHLSFile(video, videoFileId)
if (playlist) await updatePlaylistAfterFileChange(video, playlist)
await federateVideoIfNeeded(video, false, undefined)
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
async function removeWebTorrentFiles (req: express.Request, res: express.Response) {
// ---------------------------------------------------------------------------
async function removeAllWebTorrentFilesController (req: express.Request, res: express.Response) {
const video = res.locals.videoAll
logger.info('Deleting WebTorrent files of %s.', video.url, lTags(video.uuid))
for (const file of video.VideoFiles) {
await video.removeWebTorrentFileAndTorrent(file)
await file.destroy()
}
await removeAllWebTorrentFiles(video)
await federateVideoIfNeeded(video, false, undefined)
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
}
async function removeWebTorrentFileController (req: express.Request, res: express.Response) {
const video = res.locals.videoAll
const videoFileId = +req.params.videoFileId
logger.info('Deleting WebTorrent file %d of %s.', videoFileId, video.url, lTags(video.uuid))
video.VideoFiles = []
await removeWebTorrentFile(video, videoFileId)
await federateVideoIfNeeded(video, false, undefined)
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
......
import { FfprobeData } from 'fluent-ffmpeg'
import { getMaxBitrate } from '@shared/core-utils'
import {
buildFileMetadata,
ffprobePromise,
getAudioStream,
getVideoStreamDuration,
getMaxAudioBitrate,
buildFileMetadata,
getVideoStreamBitrate,
getVideoStreamFPS,
getVideoStream,
getVideoStreamBitrate,
getVideoStreamDimensionsInfo,
getVideoStreamDuration,
getVideoStreamFPS,
hasAudioStream
} from '@shared/extra-utils/ffprobe'
import { VideoResolution, VideoTranscodingFPS } from '@shared/models'
......
import { Transaction } from 'sequelize/types'
import { CreationAttributes, Transaction } from 'sequelize/types'
import { deleteAllModels, filterNonExistingModels } from '@server/helpers/database-utils'
import { logger, LoggerTagsFn } from '@server/helpers/logger'
import { updatePlaceholderThumbnail, updateVideoMiniatureFromUrl } from '@server/lib/thumbnail'
......@@ -7,7 +7,15 @@ import { VideoCaptionModel } from '@server/models/video/video-caption'
import { VideoFileModel } from '@server/models/video/video-file'
import { VideoLiveModel } from '@server/models/video/video-live'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
import { MStreamingPlaylistFilesVideo, MThumbnail, MVideoCaption, MVideoFile, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
import {
MStreamingPlaylistFiles,
MStreamingPlaylistFilesVideo,
MThumbnail,
MVideoCaption,
MVideoFile,
MVideoFullLight,
MVideoThumbnail
} from '@server/types/models'
import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models'
import { getOrCreateAPActor } from '../../actors'
import { checkUrlsSameHost } from '../../url'
......@@ -125,38 +133,39 @@ export abstract class APVideoAbstractBuilder {
// Remove video playlists that do not exist anymore
await deleteAllModels(filterNonExistingModels(video.VideoStreamingPlaylists || [], newStreamingPlaylists), t)
const oldPlaylists = video.VideoStreamingPlaylists
video.VideoStreamingPlaylists = []
for (const playlistAttributes of streamingPlaylistAttributes) {
const streamingPlaylistModel = await this.insertOrReplaceStreamingPlaylist(playlistAttributes, t)
streamingPlaylistModel.Video = video
await this.setStreamingPlaylistFiles(video, streamingPlaylistModel, playlistAttributes.tagAPObject, t)
await this.setStreamingPlaylistFiles(oldPlaylists, streamingPlaylistModel, playlistAttributes.tagAPObject, t)
video.VideoStreamingPlaylists.push(streamingPlaylistModel)
}
}
private async insertOrReplaceStreamingPlaylist (attributes: VideoStreamingPlaylistModel['_creationAttributes'], t: Transaction) {
private async insertOrReplaceStreamingPlaylist (attributes: CreationAttributes<VideoStreamingPlaylistModel>, t: Transaction) {
const [ streamingPlaylist ] = await VideoStreamingPlaylistModel.upsert(attributes, { returning: true, transaction: t })
return streamingPlaylist as MStreamingPlaylistFilesVideo
}
private getStreamingPlaylistFiles (video: MVideoFullLight, type: VideoStreamingPlaylistType) {
const playlist = video.VideoStreamingPlaylists.find(s => s.type === type)
private getStreamingPlaylistFiles (oldPlaylists: MStreamingPlaylistFiles[], type: VideoStreamingPlaylistType) {
const playlist = oldPlaylists.find(s => s.type === type)
if (!playlist) return []
return playlist.VideoFiles
}
private async setStreamingPlaylistFiles (
video: MVideoFullLight,
oldPlaylists: MStreamingPlaylistFiles[],
playlistModel: MStreamingPlaylistFilesVideo,
tagObjects: ActivityTagObject[],
t: Transaction
) {
const oldStreamingPlaylistFiles = this.getStreamingPlaylistFiles(video, playlistModel.type)
const oldStreamingPlaylistFiles = this.getStreamingPlaylistFiles(oldPlaylists || [], playlistModel.type)
const newVideoFiles: MVideoFile[] = getFileAttributesFromUrl(playlistModel, tagObjects).map(a => new VideoFileModel(a))
......
import { close, ensureDir, move, open, outputJSON, read, readFile, remove, stat, writeFile } from 'fs-extra'
import { flatten, uniq } from 'lodash'
import PQueue from 'p-queue'
import { basename, dirname, join } from 'path'
import { MStreamingPlaylistFilesVideo, MVideo, MVideoUUID } from '@server/types/models'
import { MStreamingPlaylist, MStreamingPlaylistFilesVideo, MVideo } from '@server/types/models'
import { sha256 } from '@shared/extra-utils'
import { VideoStorage } from '@shared/models'
import { getAudioStreamCodec, getVideoStreamCodec, getVideoStreamDimensionsInfo } from '../helpers/ffmpeg'
......@@ -14,7 +15,7 @@ import { sequelizeTypescript } from '../initializers/database'
import { VideoFileModel } from '../models/video/video-file'
import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
import { storeHLSFile } from './object-storage'
import { getHlsResolutionPlaylistFilename } from './paths'
import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getHlsResolutionPlaylistFilename } from './paths'
import { VideoPathManager } from './video-path-manager'
async function updateStreamingPlaylistsInfohashesIfNeeded () {
......@@ -33,80 +34,123 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () {
}
}
async function updateMasterHLSPlaylist (video: MVideo, playlist: MStreamingPlaylistFilesVideo) {
const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ]
async function updatePlaylistAfterFileChange (video: MVideo, playlist: MStreamingPlaylist) {
let playlistWithFiles = await updateMasterHLSPlaylist(video, playlist)
playlistWithFiles = await updateSha256VODSegments(video, playlist)
for (const file of playlist.VideoFiles) {
const playlistFilename = getHlsResolutionPlaylistFilename(file.filename)
// Refresh playlist, operations can take some time
playlistWithFiles = await VideoStreamingPlaylistModel.loadWithVideoAndFiles(playlist.id)
playlistWithFiles.assignP2PMediaLoaderInfoHashes(video, playlistWithFiles.VideoFiles)
await playlistWithFiles.save()
await VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(playlist), async videoFilePath => {
const size = await getVideoStreamDimensionsInfo(videoFilePath)
video.setHLSPlaylist(playlistWithFiles)
}
const bandwidth = 'BANDWIDTH=' + video.getBandwidthBits(file)
const resolution = `RESOLUTION=${size?.width || 0}x${size?.height || 0}`
// ---------------------------------------------------------------------------
let line = `#EXT-X-STREAM-INF:${bandwidth},${resolution}`
if (file.fps) line += ',FRAME-RATE=' + file.fps
// Avoid concurrency issues when updating streaming playlist files
const playlistFilesQueue = new PQueue({ concurrency: 1 })
const codecs = await Promise.all([
getVideoStreamCodec(videoFilePath),
getAudioStreamCodec(videoFilePath)
])
function updateMasterHLSPlaylist (video: MVideo, playlistArg: MStreamingPlaylist): Promise<MStreamingPlaylistFilesVideo> {
return playlistFilesQueue.add(async () => {
const playlist = await VideoStreamingPlaylistModel.loadWithVideoAndFiles(playlistArg.id)
line += `,CODECS="${codecs.filter(c => !!c).join(',')}"`
const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ]
masterPlaylists.push(line)
masterPlaylists.push(playlistFilename)
})
}
for (const file of playlist.VideoFiles) {
const playlistFilename = getHlsResolutionPlaylistFilename(file.filename)
await VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(playlist), async videoFilePath => {
const size = await getVideoStreamDimensionsInfo(videoFilePath)
const bandwidth = 'BANDWIDTH=' + video.getBandwidthBits(file)
const resolution = `RESOLUTION=${size?.width || 0}x${size?.height || 0}`
let line = `#EXT-X-STREAM-INF:${bandwidth},${resolution}`
if (file.fps) line += ',FRAME-RATE=' + file.fps
const codecs = await Promise.all([
getVideoStreamCodec(videoFilePath),
getAudioStreamCodec(videoFilePath)
])
await VideoPathManager.Instance.makeAvailablePlaylistFile(playlist, playlist.playlistFilename, async masterPlaylistPath => {
line += `,CODECS="${codecs.filter(c => !!c).join(',')}"`
masterPlaylists.push(line)
masterPlaylists.push(playlistFilename)
})
}
if (playlist.playlistFilename) {
await video.removeStreamingPlaylistFile(playlist, playlist.playlistFilename)
}
playlist.playlistFilename = generateHLSMasterPlaylistFilename(video.isLive)
const masterPlaylistPath = VideoPathManager.Instance.getFSHLSOutputPath(video, playlist.playlistFilename)
await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n')
if (playlist.storage === VideoStorage.OBJECT_STORAGE) {
await storeHLSFile(playlist, playlist.playlistFilename, masterPlaylistPath)
playlist.playlistUrl = await storeHLSFile(playlist, playlist.playlistFilename)
await remove(masterPlaylistPath)
}
return playlist.save()
})
}
async function updateSha256VODSegments (video: MVideoUUID, playlist: MStreamingPlaylistFilesVideo) {
const json: { [filename: string]: { [range: string]: string } } = {}
// ---------------------------------------------------------------------------
async function updateSha256VODSegments (video: MVideo, playlistArg: MStreamingPlaylist): Promise<MStreamingPlaylistFilesVideo> {
return playlistFilesQueue.add(async () => {
const json: { [filename: string]: { [range: string]: string } } = {}
// For all the resolutions available for this video
for (const file of playlist.VideoFiles) {
const rangeHashes: { [range: string]: string } = {}
const fileWithPlaylist = file.withVideoOrPlaylist(playlist)
const playlist = await VideoStreamingPlaylistModel.loadWithVideoAndFiles(playlistArg.id)
await VideoPathManager.Instance.makeAvailableVideoFile(fileWithPlaylist, videoPath => {
// For all the resolutions available for this video
for (const file of playlist.VideoFiles) {
const rangeHashes: { [range: string]: string } = {}
const fileWithPlaylist = file.withVideoOrPlaylist(playlist)
return VideoPathManager.Instance.makeAvailableResolutionPlaylistFile(fileWithPlaylist, async resolutionPlaylistPath => {
const playlistContent = await readFile(resolutionPlaylistPath)
const ranges = getRangesFromPlaylist(playlistContent.toString())
await VideoPathManager.Instance.makeAvailableVideoFile(fileWithPlaylist, videoPath => {
const fd = await open(videoPath, 'r')
for (const range of ranges) {
const buf = Buffer.alloc(range.length)
await read(fd, buf, 0, range.length, range.offset)
return VideoPathManager.Instance.makeAvailableResolutionPlaylistFile(fileWithPlaylist, async resolutionPlaylistPath => {
const playlistContent = await readFile(resolutionPlaylistPath)
const ranges = getRangesFromPlaylist(playlistContent.toString())
rangeHashes[`${range.offset}-${range.offset + range.length - 1}`] = sha256(buf)
}
await close(fd)
const fd = await open(videoPath, 'r')
for (const range of ranges) {
const buf = Buffer.alloc(range.length)
await read(fd, buf, 0, range.length, range.offset)
const videoFilename = file.filename
json[videoFilename] = rangeHashes
rangeHashes[`${range.offset}-${range.offset + range.length - 1}`] = sha256(buf)
}
await close(fd)
const videoFilename = file.filename
json[videoFilename] = rangeHashes
})
})
})
}
}
const outputPath = VideoPathManager.Instance.getFSHLSOutputPath(video, playlist.segmentsSha256Filename)
await outputJSON(outputPath, json)
if (playlist.segmentsSha256Filename) {
await video.removeStreamingPlaylistFile(playlist, playlist.segmentsSha256Filename)
}
playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(video.isLive)
if (playlist.storage === VideoStorage.OBJECT_STORAGE) {
await storeHLSFile(playlist, playlist.segmentsSha256Filename)
await remove(outputPath)
}
const outputPath = VideoPathManager.Instance.getFSHLSOutputPath(video, playlist.segmentsSha256Filename)
await outputJSON(outputPath, json)
if (playlist.storage === VideoStorage.OBJECT_STORAGE) {
playlist.segmentsSha256Url = await storeHLSFile(playlist, playlist.segmentsSha256Filename)
await remove(outputPath)
}
return playlist.save()
})
}
// ---------------------------------------------------------------------------
async function buildSha256Segment (segmentPath: string) {
const buf = await readFile(segmentPath)
return sha256(buf)
......@@ -190,7 +234,8 @@ export {
updateSha256VODSegments,
buildSha256Segment,
downloadPlaylistSegments,
updateStreamingPlaylistsInfohashesIfNeeded
updateStreamingPlaylistsInfohashesIfNeeded,
updatePlaylistAfterFileChange
}
// ---------------------------------------------------------------------------
......
......@@ -55,7 +55,7 @@ async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) {
if (currentVideoFile) {
// Remove old file and old torrent
await video.removeWebTorrentFileAndTorrent(currentVideoFile)
await video.removeWebTorrentFile(currentVideoFile)
// Remove the old video file from the array
video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile)
......
......@@ -9,6 +9,7 @@ import { generateWebTorrentVideoFilename } from '@server/lib/paths'
import { VideoTranscodingProfilesManager } from '@server/lib/transcoding/default-transcoding-profiles'
import { isAbleToUploadVideo } from '@server/lib/user'
import { addOptimizeOrMergeAudioJob } from '@server/lib/video'
import { removeHLSPlaylist, removeWebTorrentFile } from '@server/lib/video-file'
import { VideoPathManager } from '@server/lib/video-path-manager'
import { approximateIntroOutroAdditionalSize } from '@server/lib/video-studio'
import { UserModel } from '@server/models/user/user'
......@@ -27,12 +28,12 @@ import {
} from '@shared/extra-utils'
import {
VideoStudioEditionPayload,
VideoStudioTaskPayload,
VideoStudioTask,
VideoStudioTaskCutPayload,
VideoStudioTaskIntroPayload,
VideoStudioTaskOutroPayload,
VideoStudioTaskWatermarkPayload,
VideoStudioTask
VideoStudioTaskPayload,
VideoStudioTaskWatermarkPayload
} from '@shared/models'
import { logger, loggerTagsFactory } from '../../../helpers/logger'
......@@ -89,7 +90,6 @@ async function processVideoStudioEdition (job: Job) {
await move(editionResultPath, outputPath)
await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath)
await removeAllFiles(video, newFile)
await newFile.save()
......@@ -197,18 +197,12 @@ async function buildNewFile (video: MVideoId, path: string) {
}
async function removeAllFiles (video: MVideoWithAllFiles, webTorrentFileException: MVideoFile) {
const hls = video.getHLSPlaylist()
if (hls) {
await video.removeStreamingPlaylistFiles(hls)
await hls.destroy()
}
await removeHLSPlaylist(video)
for (const file of video.VideoFiles) {
if (file.id === webTorrentFileException.id) continue
await video.removeWebTorrentFileAndTorrent(file)
await file.destroy()
await removeWebTorrentFile(video, file.id)
}
}
......
......@@ -149,7 +149,7 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay
if (payload.isMaxQuality && payload.autoDeleteWebTorrentIfNeeded && CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) {
// Remove webtorrent files if not enabled
for (const file of video.VideoFiles) {
await video.removeWebTorrentFileAndTorrent(file)
await video.removeWebTorrentFile(file)
await file.destroy()
}
......
......@@ -5,9 +5,8 @@ import { toEven } from '@server/helpers/core-utils'
import { retryTransactionWrapper } from '@server/helpers/database-utils'
import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
import { sequelizeTypescript } from '@server/initializers/database'
import { MStreamingPlaylistFilesVideo, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
import { VideoResolution, VideoStorage } from '../../../shared/models/videos'
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
import {
buildFileMetadata,
canDoQuickTranscode,
......@@ -18,17 +17,10 @@ import {
TranscodeVODOptionsType
} from '../../helpers/ffmpeg'