diff options
-rw-r--r-- | client/src/assets/player/peertube-player-manager.ts | 1 | ||||
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 49 | ||||
-rw-r--r-- | server/lib/hls.ts | 8 | ||||
-rw-r--r-- | server/tests/api/videos/audio-only.ts | 4 | ||||
-rw-r--r-- | server/tests/api/videos/video-hls.ts | 5 |
5 files changed, 57 insertions, 10 deletions
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index 4564b6c3e..bda718cff 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts | |||
@@ -262,6 +262,7 @@ export class PeertubePlayerManager { | |||
262 | }, | 262 | }, |
263 | html5: { | 263 | html5: { |
264 | hlsjsConfig: { | 264 | hlsjsConfig: { |
265 | capLevelToPlayerSize: true, | ||
265 | autoStartLoad: false, | 266 | autoStartLoad: false, |
266 | liveSyncDurationCount: 7, | 267 | liveSyncDurationCount: 7, |
267 | loader: new p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass() | 268 | loader: new p2pMediaLoaderModule.Engine(p2pMediaLoaderConfig).createLoaderClass() |
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index ff80991b2..1eea05d1e 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -32,7 +32,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
32 | return resolutionsEnabled | 32 | return resolutionsEnabled |
33 | } | 33 | } |
34 | 34 | ||
35 | async function getVideoFileSize (path: string) { | 35 | async function getVideoStreamSize (path: string) { |
36 | const videoStream = await getVideoStreamFromFile(path) | 36 | const videoStream = await getVideoStreamFromFile(path) |
37 | 37 | ||
38 | return videoStream === null | 38 | return videoStream === null |
@@ -40,8 +40,45 @@ async function getVideoFileSize (path: string) { | |||
40 | : { width: videoStream.width, height: videoStream.height } | 40 | : { width: videoStream.width, height: videoStream.height } |
41 | } | 41 | } |
42 | 42 | ||
43 | async function getVideoStreamCodec (path: string) { | ||
44 | const videoStream = await getVideoStreamFromFile(path) | ||
45 | |||
46 | if (!videoStream) return '' | ||
47 | |||
48 | const videoCodec = videoStream.codec_tag_string | ||
49 | |||
50 | const baseProfileMatrix = { | ||
51 | 'High': '6400', | ||
52 | 'Main': '4D40', | ||
53 | 'Baseline': '42E0' | ||
54 | } | ||
55 | |||
56 | let baseProfile = baseProfileMatrix[videoStream.profile] | ||
57 | if (!baseProfile) { | ||
58 | logger.warn('Cannot get video profile codec of %s.', path, { videoStream }) | ||
59 | baseProfile = baseProfileMatrix['High'] // Fallback | ||
60 | } | ||
61 | |||
62 | const level = videoStream.level.toString(16) | ||
63 | |||
64 | return `${videoCodec}.${baseProfile}${level}` | ||
65 | } | ||
66 | |||
67 | async function getAudioStreamCodec (path: string) { | ||
68 | const { audioStream } = await audio.get(path) | ||
69 | |||
70 | if (!audioStream) return '' | ||
71 | |||
72 | const audioCodec = audioStream.codec_name | ||
73 | if (audioCodec.codec_name === 'aac') return 'mp4a.40.2' | ||
74 | |||
75 | logger.warn('Cannot get audio codec of %s.', path, { audioStream }) | ||
76 | |||
77 | return 'mp4a.40.2' // Fallback | ||
78 | } | ||
79 | |||
43 | async function getVideoFileResolution (path: string) { | 80 | async function getVideoFileResolution (path: string) { |
44 | const size = await getVideoFileSize(path) | 81 | const size = await getVideoStreamSize(path) |
45 | 82 | ||
46 | return { | 83 | return { |
47 | videoFileResolution: Math.min(size.height, size.width), | 84 | videoFileResolution: Math.min(size.height, size.width), |
@@ -229,7 +266,9 @@ async function canDoQuickTranscode (path: string): Promise<boolean> { | |||
229 | // --------------------------------------------------------------------------- | 266 | // --------------------------------------------------------------------------- |
230 | 267 | ||
231 | export { | 268 | export { |
232 | getVideoFileSize, | 269 | getVideoStreamCodec, |
270 | getAudioStreamCodec, | ||
271 | getVideoStreamSize, | ||
233 | getVideoFileResolution, | 272 | getVideoFileResolution, |
234 | getDurationFromVideoFile, | 273 | getDurationFromVideoFile, |
235 | generateImageFromVideoFile, | 274 | generateImageFromVideoFile, |
@@ -448,8 +487,8 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolut | |||
448 | let localCommand = command | 487 | let localCommand = command |
449 | .format('mp4') | 488 | .format('mp4') |
450 | .videoCodec('libx264') | 489 | .videoCodec('libx264') |
451 | .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution | 490 | .outputOption('-level 3.1') // 3.1 is the minimal resource allocation for our highest supported resolution |
452 | .outputOption('-b_strategy 1') // NOTE: b-strategy 1 - heuristic algorythm, 16 is optimal B-frames for it | 491 | .outputOption('-b_strategy 1') // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it |
453 | .outputOption('-bf 16') // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16 | 492 | .outputOption('-bf 16') // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16 |
454 | .outputOption('-pix_fmt yuv420p') // allows import of source material with incompatible pixel formats (e.g. MJPEG video) | 493 | .outputOption('-pix_fmt yuv420p') // allows import of source material with incompatible pixel formats (e.g. MJPEG video) |
455 | .outputOption('-map_metadata -1') // strip all metadata | 494 | .outputOption('-map_metadata -1') // strip all metadata |
diff --git a/server/lib/hls.ts b/server/lib/hls.ts index 943721dd7..c94b599df 100644 --- a/server/lib/hls.ts +++ b/server/lib/hls.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { basename, dirname, join } from 'path' | 1 | import { basename, dirname, join } from 'path' |
2 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' | 2 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' |
3 | import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' | 3 | import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' |
4 | import { getVideoFileSize } from '../helpers/ffmpeg-utils' | 4 | import { getVideoStreamSize, getAudioStreamCodec, getVideoStreamCodec } from '../helpers/ffmpeg-utils' |
5 | import { sha256 } from '../helpers/core-utils' | 5 | import { sha256 } from '../helpers/core-utils' |
6 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 6 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
7 | import { logger } from '../helpers/logger' | 7 | import { logger } from '../helpers/logger' |
@@ -42,7 +42,7 @@ async function updateMasterHLSPlaylist (video: MVideoWithFile) { | |||
42 | 42 | ||
43 | const videoFilePath = getVideoFilePath(streamingPlaylist, file) | 43 | const videoFilePath = getVideoFilePath(streamingPlaylist, file) |
44 | 44 | ||
45 | const size = await getVideoFileSize(videoFilePath) | 45 | const size = await getVideoStreamSize(videoFilePath) |
46 | 46 | ||
47 | const bandwidth = 'BANDWIDTH=' + video.getBandwidthBits(file) | 47 | const bandwidth = 'BANDWIDTH=' + video.getBandwidthBits(file) |
48 | const resolution = `RESOLUTION=${size.width}x${size.height}` | 48 | const resolution = `RESOLUTION=${size.width}x${size.height}` |
@@ -50,6 +50,10 @@ async function updateMasterHLSPlaylist (video: MVideoWithFile) { | |||
50 | let line = `#EXT-X-STREAM-INF:${bandwidth},${resolution}` | 50 | let line = `#EXT-X-STREAM-INF:${bandwidth},${resolution}` |
51 | if (file.fps) line += ',FRAME-RATE=' + file.fps | 51 | if (file.fps) line += ',FRAME-RATE=' + file.fps |
52 | 52 | ||
53 | const audioCodec = await getAudioStreamCodec(filePlaylistPath) | ||
54 | const videoCodec = await getVideoStreamCodec(filePlaylistPath) | ||
55 | line += `,CODECS="${videoCodec},${audioCodec}"` | ||
56 | |||
53 | masterPlaylists.push(line) | 57 | masterPlaylists.push(line) |
54 | masterPlaylists.push(VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) | 58 | masterPlaylists.push(VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) |
55 | } | 59 | } |
diff --git a/server/tests/api/videos/audio-only.ts b/server/tests/api/videos/audio-only.ts index f5b6a26e5..f12d730cc 100644 --- a/server/tests/api/videos/audio-only.ts +++ b/server/tests/api/videos/audio-only.ts | |||
@@ -22,7 +22,7 @@ import { VideoDetails } from '../../../../shared/models/videos' | |||
22 | import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' | 22 | import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' |
23 | import { join } from 'path' | 23 | import { join } from 'path' |
24 | import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants' | 24 | import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants' |
25 | import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution, audio, getVideoFileSize } from '@server/helpers/ffmpeg-utils' | 25 | import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution, audio, getVideoStreamSize } from '@server/helpers/ffmpeg-utils' |
26 | 26 | ||
27 | const expect = chai.expect | 27 | const expect = chai.expect |
28 | 28 | ||
@@ -96,7 +96,7 @@ describe('Test audio only video transcoding', function () { | |||
96 | expect(audioStream[ 'codec_name' ]).to.be.equal('aac') | 96 | expect(audioStream[ 'codec_name' ]).to.be.equal('aac') |
97 | expect(audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000) | 97 | expect(audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000) |
98 | 98 | ||
99 | const size = await getVideoFileSize(path) | 99 | const size = await getVideoStreamSize(path) |
100 | expect(size.height).to.equal(0) | 100 | expect(size.height).to.equal(0) |
101 | expect(size.width).to.equal(0) | 101 | expect(size.width).to.equal(0) |
102 | } | 102 | } |
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts index 289209177..bde3b5656 100644 --- a/server/tests/api/videos/video-hls.ts +++ b/server/tests/api/videos/video-hls.ts | |||
@@ -66,7 +66,10 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn | |||
66 | const masterPlaylist = res.text | 66 | const masterPlaylist = res.text |
67 | 67 | ||
68 | for (const resolution of resolutions) { | 68 | for (const resolution of resolutions) { |
69 | expect(masterPlaylist).to.match(new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+')) | 69 | const reg = new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+,CODECS="avc1.64001f,mp4a.40.2"') |
70 | |||
71 | expect(masterPlaylist).to.match(reg) | ||
72 | expect(masterPlaylist).to.contain(`${resolution}.m3u8`) | ||
70 | expect(masterPlaylist).to.contain(`${resolution}.m3u8`) | 73 | expect(masterPlaylist).to.contain(`${resolution}.m3u8`) |
71 | } | 74 | } |
72 | } | 75 | } |