Skip to content

Commit

Permalink
Add audio only transcoding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Chocobozzz committed Nov 25, 2019
1 parent 6ad88df commit 3a149e9
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ class ResolutionMenuButton extends MenuButton {
// Skip auto resolution, we'll add it ourselves
if (d.id === -1) continue

const label = d.id === 0
? this.player.localize('Audio-only')
: d.label

this.menu.addChild(new ResolutionMenuItem(
this.player_,
{
id: d.id,
label: d.id == 0 ? this.player .localize('Audio-only') : d.label,
label,
selected: d.selected,
callback: data.qualitySwitchCallback
})
Expand Down
12 changes: 6 additions & 6 deletions client/src/assets/player/webtorrent/webtorrent-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,15 @@ class WebTorrentPlugin extends Plugin {
this.player.bigPlayButton.hide()
}

// Audio-only (resolutionId == 0) gets special treatment
if (resolutionId > 0) {
// Hide poster to have black background
this.player.removeClass('vjs-playing-audio-only-content')
this.player.posterImage.hide()
} else {
// Audio-only (resolutionId === 0) gets special treatment
if (resolutionId === 0) {
// Audio-only: show poster, do not auto-hide controls
this.player.addClass('vjs-playing-audio-only-content')
this.player.posterImage.show()
} else {
// Hide poster to have black background
this.player.removeClass('vjs-playing-audio-only-content')
this.player.posterImage.hide()
}

const newVideoFile = this.videoFiles.find(f => f.resolution.id === resolutionId)
Expand Down
2 changes: 1 addition & 1 deletion config/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ transcoding:
allow_audio_files: false
threads: 2
resolutions:
0p: true
0p: false
240p: true
360p: true
480p: true
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ function convertCustomConfigBody (body: CustomConfig) {
function keyConverter (k: string) {
// Transcoding resolutions exception
if (/^\d{3,4}p$/.exec(k)) return k
if (/^0p$/.exec(k)) return k
if (k === '0p') return k

return snakeCase(k)
}
Expand Down
54 changes: 23 additions & 31 deletions server/helpers/ffmpeg-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,9 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
async function getVideoFileSize (path: string) {
const videoStream = await getVideoStreamFromFile(path)

return videoStream == null
? {
width: 0,
height: 0
}
: {
width: videoStream.width,
height: videoStream.height
}
return videoStream === null
? { width: 0, height: 0 }
: { width: videoStream.width, height: videoStream.height }
}

async function getVideoFileResolution (path: string) {
Expand All @@ -57,13 +51,10 @@ async function getVideoFileResolution (path: string) {

async function getVideoFileFPS (path: string) {
const videoStream = await getVideoStreamFromFile(path)

if (videoStream == null) {
return 0
}
if (videoStream === null) return 0

for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
const valuesText: string = videoStream[key]
const valuesText: string = videoStream[ key ]
if (!valuesText) continue

const [ frames, seconds ] = valuesText.split('/')
Expand Down Expand Up @@ -128,7 +119,7 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima
}
}

type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' | 'split-audio'
type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' | 'only-audio'

interface BaseTranscodeOptions {
type: TranscodeOptionsType
Expand Down Expand Up @@ -159,11 +150,15 @@ interface MergeAudioTranscodeOptions extends BaseTranscodeOptions {
audioPath: string
}

interface SplitAudioTranscodeOptions extends BaseTranscodeOptions {
type: 'split-audio'
interface OnlyAudioTranscodeOptions extends BaseTranscodeOptions {
type: 'only-audio'
}

type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | SplitAudioTranscodeOptions | QuickTranscodeOptions
type TranscodeOptions = HLSTranscodeOptions
| VideoTranscodeOptions
| MergeAudioTranscodeOptions
| OnlyAudioTranscodeOptions
| QuickTranscodeOptions

function transcode (options: TranscodeOptions) {
return new Promise<void>(async (res, rej) => {
Expand All @@ -177,8 +172,8 @@ function transcode (options: TranscodeOptions) {
command = await buildHLSCommand(command, options)
} else if (options.type === 'merge-audio') {
command = await buildAudioMergeCommand(command, options)
} else if (options.type === 'split-audio') {
command = await buildAudioSplitCommand(command, options)
} else if (options.type === 'only-audio') {
command = await buildOnlyAudioCommand(command, options)
} else {
command = await buildx264Command(command, options)
}
Expand Down Expand Up @@ -220,7 +215,7 @@ async function canDoQuickTranscode (path: string): Promise<boolean> {
if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false

// check audio params (if audio stream exists)
// check audio params (if audio stream exists)
if (parsedAudio.audioStream) {
if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false

Expand Down Expand Up @@ -293,8 +288,8 @@ async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: M
return command
}

async function buildAudioSplitCommand (command: ffmpeg.FfmpegCommand, options: SplitAudioTranscodeOptions) {
command = await presetAudioSplit(command)
async function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, options: OnlyAudioTranscodeOptions) {
command = await presetOnlyAudio(command)

return command
}
Expand Down Expand Up @@ -350,9 +345,7 @@ function getVideoStreamFromFile (path: string) {
if (err) return rej(err)

const videoStream = metadata.streams.find(s => s.codec_type === 'video')
//if (!videoStream) return rej(new Error('Cannot find video stream of ' + path))

return res(videoStream)
return res(videoStream || null)
})
})
}
Expand Down Expand Up @@ -384,7 +377,7 @@ async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, input: string,
* A toolbox to play with audio
*/
namespace audio {
export const get = (option: string) => {
export const get = (videoPath: string) => {
// without position, ffprobe considers the last input only
// we make it consider the first input only
// if you pass a file path to pos, then ffprobe acts on that file directly
Expand All @@ -394,7 +387,7 @@ namespace audio {
if (err) return rej(err)

if ('streams' in data) {
const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
const audioStream = data.streams.find(stream => stream[ 'codec_type' ] === 'audio')
if (audioStream) {
return res({
absolutePath: data.format.filename,
Expand All @@ -406,7 +399,7 @@ namespace audio {
return res({ absolutePath: data.format.filename })
}

return ffmpeg.ffprobe(option, parseFfprobe)
return ffmpeg.ffprobe(videoPath, parseFfprobe)
})
}

Expand Down Expand Up @@ -506,8 +499,7 @@ async function presetCopy (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.Ffmpeg
.audioCodec('copy')
}


async function presetAudioSplit (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> {
async function presetOnlyAudio (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> {
return command
.format('mp4')
.audioCodec('copy')
Expand Down
55 changes: 11 additions & 44 deletions server/lib/video-transcoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,51 +83,18 @@ async function transcodeNewResolution (video: MVideoWithFile, resolution: VideoR

const transcodeOptions = resolution === VideoResolution.H_NOVIDEO
? {
type: 'split-audio' as 'split-audio',
inputPath: videoInputPath,
outputPath: videoTranscodedPath,
resolution,
}
type: 'only-audio' as 'only-audio',
inputPath: videoInputPath,
outputPath: videoTranscodedPath,
resolution
}
: {
type: 'video' as 'video',
inputPath: videoInputPath,
outputPath: videoTranscodedPath,
resolution,
isPortraitMode: isPortrait
}

await transcode(transcodeOptions)

return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
}

/**
* Extract audio into a separate audio-only mp4.
*/
async function splitAudioFile (video: MVideoWithFile) {
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
const extname = '.mp4'
const resolution = VideoResolution.H_NOVIDEO

// We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
const videoInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile()))

const newVideoFile = new VideoFileModel({
resolution,
extname,
size: 0,
videoId: video.id
})
const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile))
const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile))

const transcodeOptions = {
type: 'split-audio' as 'split-audio',
inputPath: videoInputPath,
outputPath: videoTranscodedPath,
resolution
}
type: 'video' as 'video',
inputPath: videoInputPath,
outputPath: videoTranscodedPath,
resolution,
isPortraitMode: isPortrait
}

await transcode(transcodeOptions)

Expand Down
108 changes: 108 additions & 0 deletions server/tests/api/videos/audio-only.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* tslint:disable:no-unused-expression */

import * as chai from 'chai'
import 'mocha'
import {
checkDirectoryIsEmpty,
checkSegmentHash,
checkTmpIsEmpty,
cleanupTests,
doubleFollow,
flushAndRunMultipleServers,
getPlaylist,
getVideo, makeGetRequest, makeRawRequest,
removeVideo, root,
ServerInfo,
setAccessTokensToServers, updateCustomSubConfig,
updateVideo,
uploadVideo,
waitJobs, webtorrentAdd
} from '../../../../shared/extra-utils'
import { VideoDetails } from '../../../../shared/models/videos'
import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
import { join } from 'path'
import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants'
import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution, audio, getVideoFileSize } from '@server/helpers/ffmpeg-utils'

const expect = chai.expect

describe('Test audio only video transcoding', function () {
let servers: ServerInfo[] = []
let videoUUID: string

before(async function () {
this.timeout(120000)

const configOverride = {
transcoding: {
enabled: true,
resolutions: {
'0p': true,
'240p': true,
'360p': false,
'480p': false,
'720p': false,
'1080p': false,
'2160p': false
},
hls: {
enabled: true
},
webtorrent: {
enabled: true
}
}
}
servers = await flushAndRunMultipleServers(2, configOverride)

// Get the access tokens
await setAccessTokensToServers(servers)

// Server 1 and server 2 follow each other
await doubleFollow(servers[0], servers[1])
})

it('Should upload a video and transcode it', async function () {
this.timeout(120000)

const resUpload = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'audio only'})
videoUUID = resUpload.body.video.uuid

await waitJobs(servers)

for (const server of servers) {
const res = await getVideo(server.url, videoUUID)
const video: VideoDetails = res.body

expect(video.streamingPlaylists).to.have.lengthOf(1)

for (const files of [ video.files, video.streamingPlaylists[0].files ]) {
expect(files).to.have.lengthOf(3)
expect(files[0].resolution.id).to.equal(720)
expect(files[1].resolution.id).to.equal(240)
expect(files[2].resolution.id).to.equal(0)
}
}
})

it('0p transcoded video should not have video', async function () {
const paths = [
join(root(), 'test' + servers[ 0 ].internalServerNumber, 'videos', videoUUID + '-0.mp4'),
join(root(), 'test' + servers[ 0 ].internalServerNumber, 'streaming-playlists', 'hls', videoUUID, videoUUID + '-0-fragmented.mp4')
]

for (const path of paths) {
const { audioStream } = await audio.get(path)
expect(audioStream[ 'codec_name' ]).to.be.equal('aac')
expect(audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000)

const size = await getVideoFileSize(path)
expect(size.height).to.equal(0)
expect(size.width).to.equal(0)
}
})

after(async function () {
await cleanupTests(servers)
})
})
1 change: 1 addition & 0 deletions server/tests/api/videos/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './audio-only'
import './multiple-servers'
import './services'
import './single-server'
Expand Down

0 comments on commit 3a149e9

Please sign in to comment.