Fix live replay duration glitch
This commit is contained in:
parent
543e187262
commit
2650d6d489
|
@ -110,7 +110,7 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima
|
||||||
// Transcode meta function
|
// Transcode meta function
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' | 'only-audio'
|
type TranscodeOptionsType = 'hls' | 'hls-from-ts' | 'quick-transcode' | 'video' | 'merge-audio' | 'only-audio'
|
||||||
|
|
||||||
interface BaseTranscodeOptions {
|
interface BaseTranscodeOptions {
|
||||||
type: TranscodeOptionsType
|
type: TranscodeOptionsType
|
||||||
|
@ -134,6 +134,14 @@ interface HLSTranscodeOptions extends BaseTranscodeOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HLSFromTSTranscodeOptions extends BaseTranscodeOptions {
|
||||||
|
type: 'hls-from-ts'
|
||||||
|
|
||||||
|
hlsPlaylist: {
|
||||||
|
videoFilename: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface QuickTranscodeOptions extends BaseTranscodeOptions {
|
interface QuickTranscodeOptions extends BaseTranscodeOptions {
|
||||||
type: 'quick-transcode'
|
type: 'quick-transcode'
|
||||||
}
|
}
|
||||||
|
@ -153,6 +161,7 @@ interface OnlyAudioTranscodeOptions extends BaseTranscodeOptions {
|
||||||
|
|
||||||
type TranscodeOptions =
|
type TranscodeOptions =
|
||||||
HLSTranscodeOptions
|
HLSTranscodeOptions
|
||||||
|
| HLSFromTSTranscodeOptions
|
||||||
| VideoTranscodeOptions
|
| VideoTranscodeOptions
|
||||||
| MergeAudioTranscodeOptions
|
| MergeAudioTranscodeOptions
|
||||||
| OnlyAudioTranscodeOptions
|
| OnlyAudioTranscodeOptions
|
||||||
|
@ -163,6 +172,7 @@ const builders: {
|
||||||
} = {
|
} = {
|
||||||
'quick-transcode': buildQuickTranscodeCommand,
|
'quick-transcode': buildQuickTranscodeCommand,
|
||||||
'hls': buildHLSVODCommand,
|
'hls': buildHLSVODCommand,
|
||||||
|
'hls-from-ts': buildHLSVODFromTSCommand,
|
||||||
'merge-audio': buildAudioMergeCommand,
|
'merge-audio': buildAudioMergeCommand,
|
||||||
'only-audio': buildOnlyAudioCommand,
|
'only-audio': buildOnlyAudioCommand,
|
||||||
'video': buildx264VODCommand
|
'video': buildx264VODCommand
|
||||||
|
@ -292,31 +302,6 @@ function getLiveMuxingCommand (rtmpUrl: string, outPath: string) {
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
async function hlsPlaylistToFragmentedMP4 (replayDirectory: string, segmentFiles: string[], outputPath: string) {
|
|
||||||
const concatFilePath = join(replayDirectory, 'concat.txt')
|
|
||||||
|
|
||||||
function cleaner () {
|
|
||||||
remove(concatFilePath)
|
|
||||||
.catch(err => logger.error('Cannot remove concat file in %s.', replayDirectory, { err }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// First concat the ts files to a mp4 file
|
|
||||||
const content = segmentFiles.map(f => 'file ' + f)
|
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
await writeFile(concatFilePath, content + '\n')
|
|
||||||
|
|
||||||
const command = getFFmpeg(concatFilePath)
|
|
||||||
command.inputOption('-safe 0')
|
|
||||||
command.inputOption('-f concat')
|
|
||||||
|
|
||||||
command.outputOption('-c:v copy')
|
|
||||||
command.audioFilter('aresample=async=1:first_pts=0')
|
|
||||||
command.output(outputPath)
|
|
||||||
|
|
||||||
return runCommand(command, cleaner)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildStreamSuffix (base: string, streamNum?: number) {
|
function buildStreamSuffix (base: string, streamNum?: number) {
|
||||||
if (streamNum !== undefined) {
|
if (streamNum !== undefined) {
|
||||||
return `${base}:${streamNum}`
|
return `${base}:${streamNum}`
|
||||||
|
@ -336,8 +321,7 @@ export {
|
||||||
generateImageFromVideoFile,
|
generateImageFromVideoFile,
|
||||||
TranscodeOptions,
|
TranscodeOptions,
|
||||||
TranscodeOptionsType,
|
TranscodeOptionsType,
|
||||||
transcode,
|
transcode
|
||||||
hlsPlaylistToFragmentedMP4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -447,6 +431,16 @@ function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCommonHLSVODCommandOptions (command: ffmpeg.FfmpegCommand, outputPath: string) {
|
||||||
|
return command.outputOption('-hls_time 4')
|
||||||
|
.outputOption('-hls_list_size 0')
|
||||||
|
.outputOption('-hls_playlist_type vod')
|
||||||
|
.outputOption('-hls_segment_filename ' + outputPath)
|
||||||
|
.outputOption('-hls_segment_type fmp4')
|
||||||
|
.outputOption('-f hls')
|
||||||
|
.outputOption('-hls_flags single_file')
|
||||||
|
}
|
||||||
|
|
||||||
async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) {
|
async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) {
|
||||||
const videoPath = getHLSVideoPath(options)
|
const videoPath = getHLSVideoPath(options)
|
||||||
|
|
||||||
|
@ -454,19 +448,27 @@ async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTr
|
||||||
else if (options.resolution === VideoResolution.H_NOVIDEO) command = presetOnlyAudio(command)
|
else if (options.resolution === VideoResolution.H_NOVIDEO) command = presetOnlyAudio(command)
|
||||||
else command = await buildx264VODCommand(command, options)
|
else command = await buildx264VODCommand(command, options)
|
||||||
|
|
||||||
command = command.outputOption('-hls_time 4')
|
addCommonHLSVODCommandOptions(command, videoPath)
|
||||||
.outputOption('-hls_list_size 0')
|
|
||||||
.outputOption('-hls_playlist_type vod')
|
return command
|
||||||
.outputOption('-hls_segment_filename ' + videoPath)
|
}
|
||||||
.outputOption('-hls_segment_type fmp4')
|
|
||||||
.outputOption('-f hls')
|
async function buildHLSVODFromTSCommand (command: ffmpeg.FfmpegCommand, options: HLSFromTSTranscodeOptions) {
|
||||||
.outputOption('-hls_flags single_file')
|
const videoPath = getHLSVideoPath(options)
|
||||||
|
|
||||||
|
command.inputOption('-safe 0')
|
||||||
|
command.inputOption('-f concat')
|
||||||
|
|
||||||
|
command.outputOption('-c:v copy')
|
||||||
|
command.audioFilter('aresample=async=1:first_pts=0')
|
||||||
|
|
||||||
|
addCommonHLSVODCommandOptions(command, videoPath)
|
||||||
|
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) {
|
async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) {
|
||||||
if (options.type !== 'hls') return
|
if (options.type !== 'hls' && options.type !== 'hls-from-ts') return
|
||||||
|
|
||||||
const fileContent = await readFile(options.outputPath)
|
const fileContent = await readFile(options.outputPath)
|
||||||
|
|
||||||
|
@ -480,7 +482,7 @@ async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) {
|
||||||
await writeFile(options.outputPath, newContent)
|
await writeFile(options.outputPath, newContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHLSVideoPath (options: HLSTranscodeOptions) {
|
function getHLSVideoPath (options: HLSTranscodeOptions | HLSFromTSTranscodeOptions) {
|
||||||
return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}`
|
return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import * as Bull from 'bull'
|
import * as Bull from 'bull'
|
||||||
import { copy, readdir, remove } from 'fs-extra'
|
import { copy, readdir, remove } from 'fs-extra'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { hlsPlaylistToFragmentedMP4 } from '@server/helpers/ffmpeg-utils'
|
|
||||||
import { getDurationFromVideoFile, getVideoFileResolution } from '@server/helpers/ffprobe-utils'
|
import { getDurationFromVideoFile, getVideoFileResolution } from '@server/helpers/ffprobe-utils'
|
||||||
import { VIDEO_LIVE } from '@server/initializers/constants'
|
import { VIDEO_LIVE } from '@server/initializers/constants'
|
||||||
import { generateVideoMiniature } from '@server/lib/thumbnail'
|
import { generateVideoMiniature } from '@server/lib/thumbnail'
|
||||||
import { publishAndFederateIfNeeded } from '@server/lib/video'
|
import { publishAndFederateIfNeeded } from '@server/lib/video'
|
||||||
import { getHLSDirectory } from '@server/lib/video-paths'
|
import { getHLSDirectory } from '@server/lib/video-paths'
|
||||||
import { generateHlsPlaylist } from '@server/lib/video-transcoding'
|
import { generateHlsPlaylistFromTS } from '@server/lib/video-transcoding'
|
||||||
import { VideoModel } from '@server/models/video/video'
|
import { VideoModel } from '@server/models/video/video'
|
||||||
import { VideoFileModel } from '@server/models/video/video-file'
|
import { VideoFileModel } from '@server/models/video/video-file'
|
||||||
import { VideoLiveModel } from '@server/models/video/video-live'
|
import { VideoLiveModel } from '@server/models/video/video-live'
|
||||||
|
@ -71,32 +70,6 @@ async function saveLive (video: MVideo, live: MVideoLive) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const replayFiles = await readdir(replayDirectory)
|
|
||||||
|
|
||||||
const resolutions: number[] = []
|
|
||||||
let duration: number
|
|
||||||
|
|
||||||
for (const playlistFile of playlistFiles) {
|
|
||||||
const playlistPath = join(replayDirectory, playlistFile)
|
|
||||||
const { videoFileResolution } = await getVideoFileResolution(playlistPath)
|
|
||||||
|
|
||||||
// Put the final mp4 in the hls directory, and not in the replay directory
|
|
||||||
const mp4TmpPath = buildMP4TmpPath(hlsDirectory, videoFileResolution)
|
|
||||||
|
|
||||||
// Playlist name is for example 3.m3u8
|
|
||||||
// Segments names are 3-0.ts 3-1.ts etc
|
|
||||||
const shouldStartWith = playlistFile.replace(/\.m3u8$/, '') + '-'
|
|
||||||
|
|
||||||
const segmentFiles = replayFiles.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts'))
|
|
||||||
await hlsPlaylistToFragmentedMP4(replayDirectory, segmentFiles, mp4TmpPath)
|
|
||||||
|
|
||||||
if (!duration) {
|
|
||||||
duration = await getDurationFromVideoFile(mp4TmpPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
resolutions.push(videoFileResolution)
|
|
||||||
}
|
|
||||||
|
|
||||||
await cleanupLiveFiles(hlsDirectory)
|
await cleanupLiveFiles(hlsDirectory)
|
||||||
|
|
||||||
await live.destroy()
|
await live.destroy()
|
||||||
|
@ -105,7 +78,6 @@ async function saveLive (video: MVideo, live: MVideoLive) {
|
||||||
// Reinit views
|
// Reinit views
|
||||||
video.views = 0
|
video.views = 0
|
||||||
video.state = VideoState.TO_TRANSCODE
|
video.state = VideoState.TO_TRANSCODE
|
||||||
video.duration = duration
|
|
||||||
|
|
||||||
await video.save()
|
await video.save()
|
||||||
|
|
||||||
|
@ -116,21 +88,35 @@ async function saveLive (video: MVideo, live: MVideoLive) {
|
||||||
await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id)
|
await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id)
|
||||||
hlsPlaylist.VideoFiles = []
|
hlsPlaylist.VideoFiles = []
|
||||||
|
|
||||||
for (const resolution of resolutions) {
|
const replayFiles = await readdir(replayDirectory)
|
||||||
const videoInputPath = buildMP4TmpPath(hlsDirectory, resolution)
|
let duration: number
|
||||||
const { isPortraitMode } = await getVideoFileResolution(videoInputPath)
|
|
||||||
|
|
||||||
await generateHlsPlaylist({
|
for (const playlistFile of playlistFiles) {
|
||||||
|
const playlistPath = join(replayDirectory, playlistFile)
|
||||||
|
const { videoFileResolution, isPortraitMode } = await getVideoFileResolution(playlistPath)
|
||||||
|
|
||||||
|
// Playlist name is for example 3.m3u8
|
||||||
|
// Segments names are 3-0.ts 3-1.ts etc
|
||||||
|
const shouldStartWith = playlistFile.replace(/\.m3u8$/, '') + '-'
|
||||||
|
|
||||||
|
const segmentFiles = replayFiles.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts'))
|
||||||
|
|
||||||
|
const outputPath = await generateHlsPlaylistFromTS({
|
||||||
video: videoWithFiles,
|
video: videoWithFiles,
|
||||||
videoInputPath,
|
replayDirectory,
|
||||||
resolution: resolution,
|
segmentFiles,
|
||||||
copyCodecs: true,
|
resolution: videoFileResolution,
|
||||||
isPortraitMode
|
isPortraitMode
|
||||||
})
|
})
|
||||||
|
|
||||||
await remove(videoInputPath)
|
if (!duration) {
|
||||||
|
videoWithFiles.duration = await getDurationFromVideoFile(outputPath)
|
||||||
|
await videoWithFiles.save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await remove(replayDirectory)
|
||||||
|
|
||||||
// Regenerate the thumbnail & preview?
|
// Regenerate the thumbnail & preview?
|
||||||
if (videoWithFiles.getMiniature().automaticallyGenerated === true) {
|
if (videoWithFiles.getMiniature().automaticallyGenerated === true) {
|
||||||
await generateVideoMiniature(videoWithFiles, videoWithFiles.getMaxQualityFile(), ThumbnailType.MINIATURE)
|
await generateVideoMiniature(videoWithFiles, videoWithFiles.getMaxQualityFile(), ThumbnailType.MINIATURE)
|
||||||
|
@ -161,8 +147,7 @@ async function cleanupLiveFiles (hlsDirectory: string) {
|
||||||
filename.endsWith('.m3u8') ||
|
filename.endsWith('.m3u8') ||
|
||||||
filename.endsWith('.mpd') ||
|
filename.endsWith('.mpd') ||
|
||||||
filename.endsWith('.m4s') ||
|
filename.endsWith('.m4s') ||
|
||||||
filename.endsWith('.tmp') ||
|
filename.endsWith('.tmp')
|
||||||
filename === VIDEO_LIVE.REPLAY_DIRECTORY
|
|
||||||
) {
|
) {
|
||||||
const p = join(hlsDirectory, filename)
|
const p = join(hlsDirectory, filename)
|
||||||
|
|
||||||
|
@ -171,7 +156,3 @@ async function cleanupLiveFiles (hlsDirectory: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMP4TmpPath (basePath: string, resolution: number) {
|
|
||||||
return join(basePath, resolution + '-tmp.mp4')
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { copyFile, ensureDir, move, remove, stat } from 'fs-extra'
|
import { copyFile, ensureDir, move, remove, stat, writeFile } from 'fs-extra'
|
||||||
import { basename, extname as extnameUtil, join } from 'path'
|
import { basename, extname as extnameUtil, join } from 'path'
|
||||||
import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
|
import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
|
||||||
import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models'
|
import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models'
|
||||||
|
@ -163,15 +163,104 @@ async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: Video
|
||||||
return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
|
return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Concat TS segments from a live video to a fragmented mp4 HLS playlist
|
||||||
|
async function generateHlsPlaylistFromTS (options: {
|
||||||
|
video: MVideoWithFile
|
||||||
|
replayDirectory: string
|
||||||
|
segmentFiles: string[]
|
||||||
|
resolution: VideoResolution
|
||||||
|
isPortraitMode: boolean
|
||||||
|
}) {
|
||||||
|
const concatFilePath = join(options.replayDirectory, 'concat.txt')
|
||||||
|
|
||||||
|
function cleaner () {
|
||||||
|
remove(concatFilePath)
|
||||||
|
.catch(err => logger.error('Cannot remove concat file in %s.', options.replayDirectory, { err }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First concat the ts files to a mp4 file
|
||||||
|
const content = options.segmentFiles.map(f => 'file ' + f)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
await writeFile(concatFilePath, content + '\n')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const outputPath = await generateHlsPlaylistCommon({
|
||||||
|
video: options.video,
|
||||||
|
resolution: options.resolution,
|
||||||
|
isPortraitMode: options.isPortraitMode,
|
||||||
|
inputPath: concatFilePath,
|
||||||
|
type: 'hls-from-ts' as 'hls-from-ts'
|
||||||
|
})
|
||||||
|
|
||||||
|
cleaner()
|
||||||
|
|
||||||
|
return outputPath
|
||||||
|
} catch (err) {
|
||||||
|
cleaner()
|
||||||
|
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate an HLS playlist from an input file, and update the master playlist
|
// Generate an HLS playlist from an input file, and update the master playlist
|
||||||
async function generateHlsPlaylist (options: {
|
function generateHlsPlaylist (options: {
|
||||||
video: MVideoWithFile
|
video: MVideoWithFile
|
||||||
videoInputPath: string
|
videoInputPath: string
|
||||||
resolution: VideoResolution
|
resolution: VideoResolution
|
||||||
copyCodecs: boolean
|
copyCodecs: boolean
|
||||||
isPortraitMode: boolean
|
isPortraitMode: boolean
|
||||||
}) {
|
}) {
|
||||||
const { video, videoInputPath, resolution, copyCodecs, isPortraitMode } = options
|
return generateHlsPlaylistCommon({
|
||||||
|
video: options.video,
|
||||||
|
resolution: options.resolution,
|
||||||
|
copyCodecs: options.copyCodecs,
|
||||||
|
isPortraitMode: options.isPortraitMode,
|
||||||
|
inputPath: options.videoInputPath,
|
||||||
|
type: 'hls' as 'hls'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
generateHlsPlaylist,
|
||||||
|
generateHlsPlaylistFromTS,
|
||||||
|
optimizeOriginalVideofile,
|
||||||
|
transcodeNewResolution,
|
||||||
|
mergeAudioVideofile
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) {
|
||||||
|
const stats = await stat(transcodingPath)
|
||||||
|
const fps = await getVideoFileFPS(transcodingPath)
|
||||||
|
const metadata = await getMetadataFromFile(transcodingPath)
|
||||||
|
|
||||||
|
await move(transcodingPath, outputPath, { overwrite: true })
|
||||||
|
|
||||||
|
videoFile.size = stats.size
|
||||||
|
videoFile.fps = fps
|
||||||
|
videoFile.metadata = metadata
|
||||||
|
|
||||||
|
await createTorrentAndSetInfoHash(video, videoFile)
|
||||||
|
|
||||||
|
await VideoFileModel.customUpsert(videoFile, 'video', undefined)
|
||||||
|
video.VideoFiles = await video.$get('VideoFiles')
|
||||||
|
|
||||||
|
return video
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateHlsPlaylistCommon (options: {
|
||||||
|
type: 'hls' | 'hls-from-ts'
|
||||||
|
video: MVideoWithFile
|
||||||
|
inputPath: string
|
||||||
|
resolution: VideoResolution
|
||||||
|
copyCodecs?: boolean
|
||||||
|
isPortraitMode: boolean
|
||||||
|
}) {
|
||||||
|
const { type, video, inputPath, resolution, copyCodecs, isPortraitMode } = options
|
||||||
|
|
||||||
const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
|
const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
|
||||||
await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid))
|
await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid))
|
||||||
|
@ -180,9 +269,9 @@ async function generateHlsPlaylist (options: {
|
||||||
const videoFilename = generateVideoStreamingPlaylistName(video.uuid, resolution)
|
const videoFilename = generateVideoStreamingPlaylistName(video.uuid, resolution)
|
||||||
|
|
||||||
const transcodeOptions = {
|
const transcodeOptions = {
|
||||||
type: 'hls' as 'hls',
|
type,
|
||||||
|
|
||||||
inputPath: videoInputPath,
|
inputPath,
|
||||||
outputPath,
|
outputPath,
|
||||||
|
|
||||||
availableEncoders,
|
availableEncoders,
|
||||||
|
@ -242,35 +331,5 @@ async function generateHlsPlaylist (options: {
|
||||||
await updateMasterHLSPlaylist(video)
|
await updateMasterHLSPlaylist(video)
|
||||||
await updateSha256VODSegments(video)
|
await updateSha256VODSegments(video)
|
||||||
|
|
||||||
return video
|
return outputPath
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
generateHlsPlaylist,
|
|
||||||
optimizeOriginalVideofile,
|
|
||||||
transcodeNewResolution,
|
|
||||||
mergeAudioVideofile
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) {
|
|
||||||
const stats = await stat(transcodingPath)
|
|
||||||
const fps = await getVideoFileFPS(transcodingPath)
|
|
||||||
const metadata = await getMetadataFromFile(transcodingPath)
|
|
||||||
|
|
||||||
await move(transcodingPath, outputPath, { overwrite: true })
|
|
||||||
|
|
||||||
videoFile.size = stats.size
|
|
||||||
videoFile.fps = fps
|
|
||||||
videoFile.metadata = metadata
|
|
||||||
|
|
||||||
await createTorrentAndSetInfoHash(video, videoFile)
|
|
||||||
|
|
||||||
await VideoFileModel.customUpsert(videoFile, 'video', undefined)
|
|
||||||
video.VideoFiles = await video.$get('VideoFiles')
|
|
||||||
|
|
||||||
return video
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import * as Bluebird from 'bluebird'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
|
||||||
import {
|
import {
|
||||||
AllowNull,
|
AllowNull,
|
||||||
BelongsTo,
|
BelongsTo,
|
||||||
|
@ -15,14 +18,19 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
|
import { MAccountId, MChannelId } from '@server/types/models'
|
||||||
|
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
|
||||||
|
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
|
||||||
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
||||||
import { buildServerIdsFollowedBy, buildWhereIdOrUUID, getPlaylistSort, getPlaylistSort, isOutdated, throwIfNotValid } from '../utils'
|
import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
|
||||||
|
import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model'
|
||||||
|
import { activityPubCollectionPagination } from '../../helpers/activitypub'
|
||||||
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
import {
|
import {
|
||||||
isVideoPlaylistDescriptionValid,
|
isVideoPlaylistDescriptionValid,
|
||||||
isVideoPlaylistNameValid,
|
isVideoPlaylistNameValid,
|
||||||
isVideoPlaylistPrivacyValid
|
isVideoPlaylistPrivacyValid
|
||||||
} from '../../helpers/custom-validators/video-playlists'
|
} from '../../helpers/custom-validators/video-playlists'
|
||||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
|
||||||
import {
|
import {
|
||||||
ACTIVITY_PUB,
|
ACTIVITY_PUB,
|
||||||
CONSTRAINTS_FIELDS,
|
CONSTRAINTS_FIELDS,
|
||||||
|
@ -32,18 +40,7 @@ import {
|
||||||
VIDEO_PLAYLIST_TYPES,
|
VIDEO_PLAYLIST_TYPES,
|
||||||
WEBSERVER
|
WEBSERVER
|
||||||
} from '../../initializers/constants'
|
} from '../../initializers/constants'
|
||||||
import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model'
|
import { MThumbnail } from '../../types/models/video/thumbnail'
|
||||||
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
|
|
||||||
import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { VideoPlaylistElementModel } from './video-playlist-element'
|
|
||||||
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
|
|
||||||
import { activityPubCollectionPagination } from '../../helpers/activitypub'
|
|
||||||
import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
|
|
||||||
import { ThumbnailModel } from './thumbnail'
|
|
||||||
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
|
|
||||||
import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
|
|
||||||
import * as Bluebird from 'bluebird'
|
|
||||||
import {
|
import {
|
||||||
MVideoPlaylistAccountThumbnail,
|
MVideoPlaylistAccountThumbnail,
|
||||||
MVideoPlaylistAP,
|
MVideoPlaylistAP,
|
||||||
|
@ -52,8 +49,11 @@ import {
|
||||||
MVideoPlaylistFullSummary,
|
MVideoPlaylistFullSummary,
|
||||||
MVideoPlaylistIdWithElements
|
MVideoPlaylistIdWithElements
|
||||||
} from '../../types/models/video/video-playlist'
|
} from '../../types/models/video/video-playlist'
|
||||||
import { MThumbnail } from '../../types/models/video/thumbnail'
|
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
|
||||||
import { MAccountId, MChannelId } from '@server/types/models'
|
import { buildServerIdsFollowedBy, buildWhereIdOrUUID, getPlaylistSort, isOutdated, throwIfNotValid } from '../utils'
|
||||||
|
import { ThumbnailModel } from './thumbnail'
|
||||||
|
import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
|
||||||
|
import { VideoPlaylistElementModel } from './video-playlist-element'
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
|
AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
|
||||||
|
|
|
@ -430,6 +430,8 @@ describe('Test live', function () {
|
||||||
expect(video.files).to.have.lengthOf(0)
|
expect(video.files).to.have.lengthOf(0)
|
||||||
|
|
||||||
const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
|
const hlsPlaylist = video.streamingPlaylists.find(s => s.type === VideoStreamingPlaylistType.HLS)
|
||||||
|
await makeRawRequest(hlsPlaylist.playlistUrl, 200)
|
||||||
|
await makeRawRequest(hlsPlaylist.segmentsSha256Url, 200)
|
||||||
|
|
||||||
expect(hlsPlaylist.files).to.have.lengthOf(resolutions.length)
|
expect(hlsPlaylist.files).to.have.lengthOf(resolutions.length)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue