31 Commits

Author SHA1 Message Date
901d20e265 more settings 2024-04-13 18:01:06 -04:00
98acf0be91 basic setting config 2024-04-13 17:34:49 -04:00
4c9ee97fb9 replace -cq 19 with -cq 21 2024-04-13 17:24:27 -04:00
8fda9f4435 replace -cq 30 with -cq 19 2024-04-13 17:19:07 -04:00
72d52d1580 replace -crf 30 with -cq 30 (no difference) 2024-04-13 17:12:51 -04:00
1bd02a4902 add -crf 30 to see if it changes output quality 2024-04-13 17:07:43 -04:00
21b28b35f2 add stream suffix to -c:v for testing 2024-04-13 16:59:21 -04:00
92b9e78076 remove explicit bitrates, add h264_nvencwq 2024-04-13 16:50:57 -04:00
dff32f1466 decrease buffer size 2024-04-13 16:31:26 -04:00
1e7cc6961d set default preset to p7 2024-04-13 14:05:18 -04:00
70a974335f maybe fix bitrate shit nigga 2024-04-13 14:01:10 -04:00
38936ff6ef bump version 2024-04-10 10:02:22 -04:00
506c47f3db bump default VOD quality to p5 2024-04-10 10:02:04 -04:00
e6e4097b5f bump version 2024-04-10 09:27:25 -04:00
39d08a8592 revert some changes, alter default quality levels 2024-04-10 09:27:04 -04:00
2a087cc3c0 I don't even know 2024-04-09 23:33:51 -04:00
7d6779ecd6 remove -cq to account for file size issues 2024-04-09 23:17:24 -04:00
3981e65bad update shit so it just takes the fuck 2024-04-09 23:00:56 -04:00
dba92b3043 actually remove -cq shit, it's not really necessary 2024-03-10 17:52:33 -04:00
2bab551803 adjust -cq shit due to file size 2024-03-10 17:52:02 -04:00
29d44fc31a update profile name 2024-03-10 17:36:43 -04:00
9c7a2de2e9 wheeee 2024-03-10 17:26:43 -04:00
acaf777612 revert all changes 2024-03-09 12:03:49 -05:00
aa9d12c3e9 wheee 2024-03-09 11:59:38 -05:00
c50d491ed4 bump version, merge graf's changes 2024-03-08 18:28:45 -05:00
dd700ffeaa remove erroneous options, update version 2024-03-07 20:53:49 -05:00
5c954b4c0d add -cq option 2024-03-07 20:44:49 -05:00
2d0694dc8c whee 2024-03-07 20:22:56 -05:00
33da091ba7 maybe fix a thing 2024-03-07 20:08:06 -05:00
b77a9d6866 niggers 2024-03-07 19:58:50 -05:00
4a96d3c212 fix random shit 2024-03-07 19:58:31 -05:00
8 changed files with 2397 additions and 5872 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
node_modules/
dist/
# dist/

227
dist/main.js vendored Normal file
View File

@ -0,0 +1,227 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.unregister = exports.register = void 0;
let logger;
let transcodingManager;
const DEFAULT_HARDWARE_DECODE = false;
const DEFAULT_VOD_QUALITY = "p7";
const DEFAULT_LIVE_QUALITY = "hq";
const DEFAULT_CQ = 23;
const DEFAULT_BITRATES = new Map([
[0, 64 * 1000],
[144, 320 * 1000],
[360, 780 * 1000],
[480, 1500 * 1000],
[720, 2800 * 1000],
[1080, 5200 * 1000],
[1440, 10000 * 1000],
[2160, 22000 * 1000]
]);
let pluginSettings = {
hardwareDecode: DEFAULT_HARDWARE_DECODE,
vodQuality: DEFAULT_VOD_QUALITY,
liveQuality: DEFAULT_LIVE_QUALITY,
cqQuality: DEFAULT_CQ,
baseBitrate: new Map(DEFAULT_BITRATES)
};
let latestStreamNum = 9999;
async function register({ settingsManager, peertubeHelpers, transcodingManager: transcode, registerSetting }) {
logger = peertubeHelpers.logger;
transcodingManager = transcode;
logger.info("Registering peertube-plugin-nctv-hardware-encode");
const encoder = 'h264_nvenc';
const profileName = 'nctv-nvenc';
transcodingManager.addVODProfile(encoder, profileName, vodBuilder);
transcodingManager.addVODEncoderPriority('video', encoder, 1000);
transcodingManager.addLiveProfile(encoder, profileName, liveBuilder);
transcodingManager.addLiveEncoderPriority('video', encoder, 1000);
await loadSettings(settingsManager);
registerSetting({
name: 'hardware-decode',
label: 'Hardware decode',
type: 'input-checkbox',
descriptionHTML: 'Use hardware video decoder instead of software decoder. This will slightly improve performance but may cause some issues with some videos. If you encounter issues, disable this option and restart failed jobs.',
default: DEFAULT_HARDWARE_DECODE,
private: false
});
registerSetting({
name: 'vod-quality',
label: 'VOD Quality',
type: 'select',
options: [
{ label: 'fastest', value: 'p1' },
{ label: 'faster', value: 'p2' },
{ label: 'fast', value: 'p3' },
{ label: 'medium (default)', value: 'p4' },
{ label: 'slow', value: 'p5' },
{ label: 'slower', value: 'p6' },
{ label: 'slowest', value: 'p7' }
],
descriptionHTML: 'This parameter controls the speed / quality tradeoff. Slower speed mean better quality. Faster speed mean lower quality. This setting is hardware dependent, you may need to experiment to find the best value for your hardware.',
default: DEFAULT_VOD_QUALITY.toString(),
private: false
});
registerSetting({
name: 'live-quality',
label: 'Live Quality',
type: 'select',
options: [
{ label: 'low latency', value: 'll' },
{ label: 'high quality (default)', value: 'hq' },
{ label: 'low latency high performance', value: 'ull' }
],
descriptionHTML: 'This parameter controls the speed / quality tradeoff. High performance mean lower quality.',
default: DEFAULT_LIVE_QUALITY.toString(),
private: false
});
registerSetting({
name: 'constant-quality',
label: 'Constant Quality (-cq)',
descriptionHTML: 'Edit the constant quality (-cq) for videos. The lower the number, the higher the perceived visual fidelity and file size.',
type: 'input',
default: DEFAULT_CQ.toString(),
private: false,
});
registerSetting({
name: 'base-bitrate-description',
label: 'Base bitrate',
type: 'html',
html: '',
descriptionHTML: `The base bitrate for video in bits. We take the min bitrate between the bitrate setting and video bitrate.<br/>This is the bitrate used when the video is transcoded at 30 FPS. The bitrate will be scaled linearly between this value and the maximum bitrate when the video is transcoded at 60 FPS. Wrong values are replaced by default values.`,
private: true,
});
for (const [resolution, bitrate] of pluginSettings.baseBitrate) {
logger.info("registering bitrate setting: " + bitrate.toString());
registerSetting({
name: `base-bitrate-${resolution}`,
label: `Base bitrate for ${printResolution(resolution)}`,
type: 'input',
default: DEFAULT_BITRATES.get(resolution)?.toString(),
descriptionHTML: `Default value: ${DEFAULT_BITRATES.get(resolution)}`,
private: false
});
}
settingsManager.onSettingsChange(async (settings) => {
loadSettings(settingsManager);
});
}
exports.register = register;
async function unregister() {
logger.info("Unregistering peertube-plugin-nctv-hardware-encode");
transcodingManager.removeAllProfilesAndEncoderPriorities();
return true;
}
exports.unregister = unregister;
async function loadSettings(settingsManager) {
pluginSettings.hardwareDecode = await settingsManager.getSetting('hardware-decode') == "true";
pluginSettings.vodQuality = parseInt(await settingsManager.getSetting('vod-quality')) || DEFAULT_VOD_QUALITY;
pluginSettings.liveQuality = parseInt(await settingsManager.getSetting('live-quality')) || DEFAULT_LIVE_QUALITY;
pluginSettings.cqQuality = parseInt(await settingsManager.getSetting('constant-quality')) || DEFAULT_CQ;
for (const [resolution, bitrate] of DEFAULT_BITRATES) {
const key = `base-bitrate-${resolution}`;
const storedValue = await settingsManager.getSetting(key);
pluginSettings.baseBitrate.set(resolution, parseInt(storedValue) || bitrate);
logger.info(`Bitrate ${printResolution(resolution)}: ${pluginSettings.baseBitrate.get(resolution)}`);
}
logger.info(`Hardware decode: ${pluginSettings.hardwareDecode}`);
logger.info(`VOD Quality: ${pluginSettings.vodQuality}`);
logger.info(`Live Quality: ${pluginSettings.liveQuality}`);
}
function printResolution(resolution) {
switch (resolution) {
case 0: return 'audio only';
case 144:
case 360:
case 480:
case 720:
case 1080:
case 1440:
return `${resolution}p`;
case 2160: return '4K';
default: return 'Unknown';
}
}
function buildInitOptions() {
if (pluginSettings.hardwareDecode) {
return [
'-hwaccel cuda',
'-hwaccel_output_format cuda'
];
}
else {
return [
'-hwaccel cuda'
];
}
}
async function vodBuilder(params) {
const { resolution, fps, streamNum, inputBitrate } = params;
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`;
// let targetBitrate = getTargetBitrate(resolution, fps);
let targetBitrate = inputBitrate;
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum);
// if (targetBitrate > inputBitrate) {
// targetBitrate = inputBitrate;
// }
logger.info(`Building encoder options, received ${JSON.stringify(params)}`);
if (shouldInitVaapi && streamNum != undefined) {
latestStreamNum = streamNum;
}
let options = {
scaleFilter: {
name: 'scale'
},
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
outputOptions: [
`-preset ${pluginSettings.vodQuality}`,
// `-b:v${streamSuffix} ${targetBitrate}`,
// `-bufsize ${targetBitrate * 2}`,
//`-crf 21`, // increased 19 to 21, file size massive
`-profile:v${streamSuffix} high`,
`-cq 22`,
`-c:v${streamSuffix} h264_nvenc`
]
};
logger.info(`EncoderOptions: ${JSON.stringify(options)}`);
return options;
}
async function liveBuilder(params) {
const { resolution, fps, streamNum, inputBitrate } = params;
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`;
// let targetBitrate = getTargetBitrate(resolution, fps);
let targetBitrate = inputBitrate;
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum);
// if (targetBitrate > inputBitrate) {
// targetBitrate = inputBitrate;
// }
logger.info(`Building encoder options, received ${JSON.stringify(params)}`);
if (shouldInitVaapi && streamNum != undefined) {
latestStreamNum = streamNum;
}
const options = {
scaleFilter: {
name: 'scale'
},
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
outputOptions: [
`-tune ${pluginSettings.liveQuality}`,
// `-r:v${streamSuffix} ${fps}`,
`-profile:v${streamSuffix} high`,
`-c:v${streamSuffix} h264_nvenc`,
`-cq 22`
// `-g:v${streamSuffix} ${fps * 2}`,
// `-b:v${streamSuffix} ${targetBitrate}`,
// `-bufsize ${targetBitrate * 2}`,
]
};
logger.info(`EncoderOptions: ${JSON.stringify(options)}`);
return options;
}
function getTargetBitrate(resolution, fps) {
const baseBitrate = pluginSettings.baseBitrate.get(resolution) || 0;
const maxBitrate = baseBitrate * 1.4;
const maxBitrateDifference = maxBitrate - baseBitrate;
const maxFpsDifference = 60 - 30;
return Math.floor(baseBitrate + (fps - 30) * (maxBitrateDifference / maxFpsDifference));
}
//# sourceMappingURL=main.js.map

1
dist/main.js.map vendored Normal file

File diff suppressed because one or more lines are too long

6603
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "peertube-plugin-nctv-nvenc-transcode",
"version": "1.0.0",
"version": "1.0.7",
"license": "MIT",
"description": "Plugin that adds transcode profiles which use NVIDIA NVENC for hardware acceleration",
"engine": {
@ -30,6 +30,6 @@
"devDependencies": {
"@peertube/peertube-types": "^5.1.0",
"@tsconfig/node16": "^1.0.3",
"typescript": "^5.1.6"
"typescript": "^5.4.2"
}
}

View File

@ -1,283 +0,0 @@
import { PluginSettingsManager, PluginTranscodingManager } from "@peertube/peertube-types"
import { EncoderOptions, EncoderOptionsBuilderParams, RegisterServerOptions, VideoResolution } from "@peertube/peertube-types"
import { Logger } from 'winston'
let logger : Logger
let transcodingManager : PluginTranscodingManager
const DEFAULT_HARDWARE_DECODE : boolean = false
const DEFAULT_VOD_QUALITY : number = 15
const DEFAULT_LIVE_QUALITY : number = 7
const DEFAULT_BITRATES : Map<VideoResolution, number> = new Map([
[VideoResolution.H_NOVIDEO, 64 * 1000],
[VideoResolution.H_144P, 320 * 1000],
[VideoResolution.H_360P, 780 * 1000],
[VideoResolution.H_480P, 1500 * 1000],
[VideoResolution.H_720P, 2800 * 1000],
[VideoResolution.H_1080P, 5200 * 1000],
[VideoResolution.H_1440P, 10_000 * 1000],
[VideoResolution.H_4K, 22_000 * 1000]
])
interface PluginSettings {
hardwareDecode : boolean
vodQuality: number
liveQuality: number
baseBitrate: Map<VideoResolution, number>
}
let pluginSettings : PluginSettings = {
hardwareDecode: DEFAULT_HARDWARE_DECODE,
vodQuality: DEFAULT_VOD_QUALITY,
liveQuality: DEFAULT_LIVE_QUALITY,
baseBitrate: new Map(DEFAULT_BITRATES)
}
let latestStreamNum = 9999
export async function register({settingsManager, peertubeHelpers, transcodingManager: transcode, registerSetting} :RegisterServerOptions) {
logger = peertubeHelpers.logger
transcodingManager = transcode
logger.info("Registering peertube-plugin-hardware-encode");
const encoder = 'h264_nvenc'
const profileName = 'nvenc'
// Add trasncoding profiles
transcodingManager.addVODProfile(encoder, profileName, vodBuilder)
transcodingManager.addVODEncoderPriority('video', encoder, 1000)
transcodingManager.addLiveProfile(encoder, profileName, liveBuilder)
transcodingManager.addLiveEncoderPriority('video', encoder, 1000)
// Load existing settings and default to constants if not present
await loadSettings(settingsManager)
registerSetting({
name: 'hardware-decode',
label: 'Hardware decode',
type: 'input-checkbox',
descriptionHTML: 'Use hardware video decoder instead of software decoder. This will slightly improve performance but may cause some issues with some videos. If you encounter issues, disable this option and restart failed jobs.',
default: DEFAULT_HARDWARE_DECODE,
private: false
})
registerSetting({
name: 'vod-quality',
label: 'VOD Quality',
type: 'select',
options: [
{ label: 'fastest', value: '12' },
{ label: 'faster', value: '13' },
{ label: 'fast', value: '14' },
{ label: 'medium (default)', value: '15' },
{ label: 'slow', value: '16' },
{ label: 'slower', value: '17' },
{ label: 'slowest', value: '18' },
{ label: 'nctv', value: 'p7' }
],
descriptionHTML: 'This parameter controls the speed / quality tradeoff. Slower speed mean better quality. Faster speed mean lower quality. This setting is hardware dependent, you may need to experiment to find the best value for your hardware.',
default: DEFAULT_VOD_QUALITY.toString(),
private: false
})
registerSetting({
name: 'live-quality',
label: 'Live Quality',
type: 'select',
options: [
{ label: 'low latency (default)', value: '7' },
{ label: 'low latency high quality', value: '8' },
{ label: 'low latency high performance', value: '9' }
],
descriptionHTML: 'This parameter controls the speed / quality tradeoff. High performance mean lower quality.',
default: DEFAULT_LIVE_QUALITY.toString(),
private: false
})
registerSetting({
name: 'base-bitrate-description',
label: 'Base bitrate',
type: 'html',
html: '',
descriptionHTML: `The base bitrate for video in bits. We take the min bitrate between the bitrate setting and video bitrate.<br/>This is the bitrate used when the video is transcoded at 30 FPS. The bitrate will be scaled linearly between this value and the maximum bitrate when the video is transcoded at 60 FPS. Wrong values are replaced by default values.`,
private: true,
})
for (const [resolution, bitrate] of pluginSettings.baseBitrate) {
logger.info("registering bitrate setting: "+ bitrate.toString())
registerSetting({
name: `base-bitrate-${resolution}`,
label: `Base bitrate for ${printResolution(resolution)}`,
type: 'input',
default: DEFAULT_BITRATES.get(resolution)?.toString(),
descriptionHTML: `Default value: ${DEFAULT_BITRATES.get(resolution)}`,
private: false
})
}
settingsManager.onSettingsChange(async (settings) => {
loadSettings(settingsManager)
})
}
export async function unregister() {
logger.info("Unregistering peertube-plugin-hardware-encode")
transcodingManager.removeAllProfilesAndEncoderPriorities()
return true
}
async function loadSettings(settingsManager: PluginSettingsManager) {
pluginSettings.hardwareDecode = await settingsManager.getSetting('hardware-decode') == "true"
pluginSettings.vodQuality = parseInt(await settingsManager.getSetting('vod-quality') as string) || DEFAULT_VOD_QUALITY
pluginSettings.liveQuality = parseInt(await settingsManager.getSetting('live-quality') as string) || DEFAULT_LIVE_QUALITY
for (const [resolution, bitrate] of DEFAULT_BITRATES) {
const key = `base-bitrate-${resolution}`
const storedValue = await settingsManager.getSetting(key) as string
pluginSettings.baseBitrate.set(resolution, parseInt(storedValue) || bitrate)
logger.info(`Bitrate ${printResolution(resolution)}: ${pluginSettings.baseBitrate.get(resolution)}`)
}
logger.info(`Hardware decode: ${pluginSettings.hardwareDecode}`)
logger.info(`VOD Quality: ${pluginSettings.vodQuality}`)
logger.info(`Live Quality: ${pluginSettings.liveQuality}`)
}
function printResolution(resolution : VideoResolution) : string {
switch (resolution) {
case VideoResolution.H_NOVIDEO: return 'audio only'
case VideoResolution.H_144P:
case VideoResolution.H_360P:
case VideoResolution.H_480P:
case VideoResolution.H_720P:
case VideoResolution.H_1080P:
case VideoResolution.H_1440P:
return `${resolution}p`
case VideoResolution.H_4K: return '4K'
default: return 'Unknown'
}
}
function buildInitOptions() {
if (pluginSettings.hardwareDecode) {
return [
'-vcodec h264_nvenc'
]
} else {
return [
'-vcodec h264_nvenc'
]
}
}
async function vodBuilder(params: EncoderOptionsBuilderParams) : Promise<EncoderOptions> {
const { resolution, fps, streamNum, inputBitrate } = params
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`
let targetBitrate = getTargetBitrate(resolution, fps)
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum)
if (targetBitrate > inputBitrate) {
targetBitrate = inputBitrate
}
logger.info(`Building encoder options, received ${JSON.stringify(params)}`)
if (shouldInitVaapi && streamNum != undefined) {
latestStreamNum = streamNum
}
// You can also return a promise
let options : EncoderOptions = {
scaleFilter: {
// software decode requires specifying pixel format for hardware filter and upload it to GPU
// name: pluginSettings.hardwareDecode ? 'scale_vaapi' : 'format=nv12,hwupload,scale_vaapi'
name: 'scale'
},
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
outputOptions: [
`-preset ${pluginSettings.vodQuality}`,
`-b:v${streamSuffix} ${targetBitrate}`,
`-bufsize ${targetBitrate * 2}`,
`-bf 4`
]
}
logger.info(`EncoderOptions: ${JSON.stringify(options)}`)
return options
}
async function liveBuilder(params: EncoderOptionsBuilderParams) : Promise<EncoderOptions> {
const { resolution, fps, streamNum, inputBitrate } = params
const streamSuffix = streamNum == undefined ? '' : `:${streamNum}`
let targetBitrate = getTargetBitrate(resolution, fps)
let shouldInitVaapi = (streamNum == undefined || streamNum <= latestStreamNum)
if (targetBitrate > inputBitrate) {
targetBitrate = inputBitrate
}
logger.info(`Building encoder options, received ${JSON.stringify(params)}`)
if (shouldInitVaapi && streamNum != undefined) {
latestStreamNum = streamNum
}
// You can also return a promise
const options = {
scaleFilter: {
// name: pluginSettings.hardwareDecode ? 'scale_vaapi' : 'format=nv12,hwupload,scale_vaapi'
name: 'scale'
},
inputOptions: shouldInitVaapi ? buildInitOptions() : [],
outputOptions: [
`-preset ${pluginSettings.liveQuality}`,
`-r:v${streamSuffix} ${fps}`,
`-profile:v${streamSuffix} high`,
`-g:v${streamSuffix} ${fps*2}`,
`-b:v${streamSuffix} ${targetBitrate}`,
`-bufsize ${targetBitrate * 2}`,
`-bf 4`
]
}
logger.info(`EncoderOptions: ${JSON.stringify(options)}`)
return options
}
/**
* Calculate the target bitrate based on video resolution and FPS.
*
* The calculation is based on two values:
* Bitrate at VideoTranscodingFPS.AVERAGE is always the same as
* getBaseBitrate(). Bitrate at VideoTranscodingFPS.MAX is always
* getBaseBitrate() * 1.4. All other values are calculated linearly
* between these two points.
*/
function getTargetBitrate (resolution : VideoResolution, fps : number) : number {
const baseBitrate = pluginSettings.baseBitrate.get(resolution) || 0
// The maximum bitrate, used when fps === VideoTranscodingFPS.MAX
// Based on numbers from Youtube, 60 fps bitrate divided by 30 fps bitrate:
// 720p: 2600 / 1750 = 1.49
// 1080p: 4400 / 3300 = 1.33
const maxBitrate = baseBitrate * 1.4
const maxBitrateDifference = maxBitrate - baseBitrate
const maxFpsDifference = 60 - 30
// For 1080p video with default settings, this results in the following formula:
// 3300 + (x - 30) * (1320/30)
// Example outputs:
// 1080p10: 2420 kbps, 1080p30: 3300 kbps, 1080p60: 4620 kbps
// 720p10: 1283 kbps, 720p30: 1750 kbps, 720p60: 2450 kbps
return Math.floor(baseBitrate + (fps - 30) * (maxBitrateDifference / maxFpsDifference))
}

View File

@ -1,24 +0,0 @@
{
"extends": "@tsconfig/node16/tsconfig.json",
"compilerOptions": {
"moduleResolution": "node", // Tell tsc to look in node_modules for modules
"strict": true, // That implies alwaysStrict, noImplicitAny, noImplicitThis
"alwaysStrict": true, // should already be true because of strict:true
"noImplicitAny": true, // should already be true because of strict:true
"noImplicitThis": true, // should already be true because of strict:true
"noImplicitReturns": true,
"strictBindCallApply": true, // should already be true because of strict:true
"noUnusedLocals": true,
"removeComments": true,
"sourceMap": true,
"outDir": "../dist/",
"paths": {}
},
"include": [
"./**/*"
],
"exclude": []
}

1125
yarn.lock

File diff suppressed because it is too large Load Diff