Merge pull request #64 from technomancer702/feature/upscaling
Feature/upscaling bug fixes
This commit is contained in:
+1
-1
@@ -8,5 +8,5 @@ coverage/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
dist/
|
dist/
|
||||||
release/
|
release/
|
||||||
|
.vscode
|
||||||
transcode-cache/
|
transcode-cache/
|
||||||
|
|||||||
@@ -4148,6 +4148,12 @@ kbd {
|
|||||||
/* Green for Direct */
|
/* Green for Direct */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.transcode-status.upscaling {
|
||||||
|
background: rgba(156, 39, 176, 0.8);
|
||||||
|
/* Purple for Upscaling */
|
||||||
|
box-shadow: 0 0 10px rgba(156, 39, 176, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.transcode-status.hidden {
|
.transcode-status.hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -587,6 +587,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h3>Upscaling</h3>
|
||||||
|
<div class="setting-item">
|
||||||
|
<label class="setting-toggle">
|
||||||
|
<input type="checkbox" id="setting-upscale-enabled">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">Enable Upscaling</span>
|
||||||
|
<span class="setting-hint">Upscale lower-resolution content to a higher resolution during transcoding.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item" id="upscale-method-container" style="display: none;">
|
||||||
|
<div class="setting-info" style="flex: 1;">
|
||||||
|
<span class="setting-label">Upscale Method</span>
|
||||||
|
<span class="setting-hint">Hardware is faster; Software (Lanczos) is higher quality.</span>
|
||||||
|
</div>
|
||||||
|
<select id="setting-upscale-method" class="source-select" style="width: auto; min-width: 200px;">
|
||||||
|
<option value="hardware">Hardware (GPU)</option>
|
||||||
|
<option value="software">Software (Lanczos)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item" id="upscale-target-container" style="display: none;">
|
||||||
|
<div class="setting-info" style="flex: 1;">
|
||||||
|
<span class="setting-label">Upscale Target</span>
|
||||||
|
<span class="setting-hint">Target resolution for upscaled output.</span>
|
||||||
|
</div>
|
||||||
|
<select id="setting-upscale-target" class="source-select" style="width: auto; min-width: 200px;">
|
||||||
|
<option value="720p">720p (HD)</option>
|
||||||
|
<option value="1080p" selected>1080p (Full HD)</option>
|
||||||
|
<option value="4k">4K (2160p)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Stream Processing</h3>
|
<h3>Stream Processing</h3>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
|
|||||||
@@ -920,8 +920,8 @@ class EpgGuide {
|
|||||||
);
|
);
|
||||||
if (channel) {
|
if (channel) {
|
||||||
await window.app.channelList.selectChannel({ channelId: channel.id });
|
await window.app.channelList.selectChannel({ channelId: channel.id });
|
||||||
// Switch to home page
|
// Switch to live TV page
|
||||||
document.querySelector('[data-page="home"]').click();
|
document.querySelector('[data-page="live"]').click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -814,16 +814,17 @@ class VideoPlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.needsTranscode) {
|
if (info.needsTranscode || this.settings.upscaleEnabled) {
|
||||||
// Incompatible audio (AC3/EAC3/DTS) - use transcode session
|
// Incompatible audio (AC3/EAC3/DTS) or Upscaling enabled - use transcode session
|
||||||
console.log('[Player] Auto: Using HLS transcode session');
|
console.log(`[Player] Auto: Using HLS transcode session (${this.settings.upscaleEnabled ? 'Upscaling' : 'Incompatible audio/video'})`);
|
||||||
|
|
||||||
// Heuristic: If video is h264, it's likely compatible, so only copy video (audio transcode only)
|
// Heuristic: If video is h264, it's likely compatible, so only copy video (audio transcode only)
|
||||||
// If video is hevc/other, we need full transcode
|
// BUT: If upscaling is enabled, we MUST encode.
|
||||||
const videoMode = (info.video && info.video.includes('h264')) ? 'copy' : 'encode';
|
const videoMode = (info.video && info.video.includes('h264') && !this.settings.upscaleEnabled) ? 'copy' : 'encode';
|
||||||
const statusText = videoMode === 'copy' ? 'Transcoding (Audio)' : 'Transcoding (Video)';
|
const statusText = videoMode === 'copy' ? 'Transcoding (Audio)' : (this.settings.upscaleEnabled ? 'Upscaling' : 'Transcoding (Video)');
|
||||||
|
const statusMode = this.settings.upscaleEnabled ? 'upscaling' : 'transcoding';
|
||||||
|
|
||||||
this.updateTranscodeStatus('transcoding', statusText);
|
this.updateTranscodeStatus(statusMode, statusText);
|
||||||
const playlistUrl = await this.startTranscodeSession(streamUrl, {
|
const playlistUrl = await this.startTranscodeSession(streamUrl, {
|
||||||
videoMode,
|
videoMode,
|
||||||
videoCodec: info.video,
|
videoCodec: info.video,
|
||||||
@@ -863,10 +864,12 @@ class VideoPlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CHECK: Force Video Transcode (Full)
|
// CHECK: Force Video Transcode (Full) or Upscaling
|
||||||
if (this.settings.forceVideoTranscode) {
|
if (this.settings.forceVideoTranscode || this.settings.upscaleEnabled) {
|
||||||
console.log('[Player] Force Video Transcode enabled. Starting session (encode)...');
|
const statusText = this.settings.upscaleEnabled ? 'Upscaling' : 'Transcoding (Video)';
|
||||||
this.updateTranscodeStatus('transcoding', 'Transcoding (Video)');
|
const statusMode = this.settings.upscaleEnabled ? 'upscaling' : 'transcoding';
|
||||||
|
console.log(`[Player] ${statusText} enabled. Starting session (encode)...`);
|
||||||
|
this.updateTranscodeStatus(statusMode, statusText);
|
||||||
const playlistUrl = await this.startTranscodeSession(streamUrl, { videoMode: 'encode' });
|
const playlistUrl = await this.startTranscodeSession(streamUrl, { videoMode: 'encode' });
|
||||||
this.currentUrl = playlistUrl;
|
this.currentUrl = playlistUrl;
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,44 @@ class SettingsPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upscaling Settings
|
||||||
|
const upscaleEnabledToggle = document.getElementById('setting-upscale-enabled');
|
||||||
|
const upscaleMethodSelect = document.getElementById('setting-upscale-method');
|
||||||
|
const upscaleTargetSelect = document.getElementById('setting-upscale-target');
|
||||||
|
const upscaleMethodContainer = document.getElementById('upscale-method-container');
|
||||||
|
const upscaleTargetContainer = document.getElementById('upscale-target-container');
|
||||||
|
|
||||||
|
// Helper to toggle upscale options visibility
|
||||||
|
const toggleUpscaleOptions = (enabled) => {
|
||||||
|
if (upscaleMethodContainer) upscaleMethodContainer.style.display = enabled ? 'flex' : 'none';
|
||||||
|
if (upscaleTargetContainer) upscaleTargetContainer.style.display = enabled ? 'flex' : 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load upscaling settings
|
||||||
|
if (upscaleEnabledToggle) {
|
||||||
|
upscaleEnabledToggle.checked = s.upscaleEnabled || false;
|
||||||
|
toggleUpscaleOptions(upscaleEnabledToggle.checked);
|
||||||
|
}
|
||||||
|
if (upscaleMethodSelect) upscaleMethodSelect.value = s.upscaleMethod || 'hardware';
|
||||||
|
if (upscaleTargetSelect) upscaleTargetSelect.value = s.upscaleTarget || '1080p';
|
||||||
|
|
||||||
|
// Upscaling event handlers
|
||||||
|
upscaleEnabledToggle?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.upscaleEnabled = upscaleEnabledToggle.checked;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
toggleUpscaleOptions(upscaleEnabledToggle.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
upscaleMethodSelect?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.upscaleMethod = upscaleMethodSelect.value;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
upscaleTargetSelect?.addEventListener('change', () => {
|
||||||
|
this.app.player.settings.upscaleTarget = upscaleTargetSelect.value;
|
||||||
|
this.app.player.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
// Stream processing toggles
|
// Stream processing toggles
|
||||||
forceProxyToggle?.addEventListener('change', () => {
|
forceProxyToggle?.addEventListener('change', () => {
|
||||||
this.app.player.settings.forceProxy = forceProxyToggle.checked;
|
this.app.player.settings.forceProxy = forceProxyToggle.checked;
|
||||||
|
|||||||
@@ -395,14 +395,16 @@ class WatchPage {
|
|||||||
this.currentStreamInfo = info;
|
this.currentStreamInfo = info;
|
||||||
this.updateQualityBadge();
|
this.updateQualityBadge();
|
||||||
|
|
||||||
if (info.needsTranscode) {
|
if (info.needsTranscode || settings.upscaleEnabled) {
|
||||||
console.log('[WatchPage] Auto: Using HLS transcode session (incompatible audio/video)');
|
console.log(`[WatchPage] Auto: Using HLS transcode session (${settings.upscaleEnabled ? 'Upscaling' : 'Incompatible audio/video'})`);
|
||||||
|
|
||||||
// Heuristic: If video is h264/compat, copy video. Usage: Audio fix.
|
// Heuristic: If video is h264/compat, copy video. Usage: Audio fix.
|
||||||
const videoMode = (info.video && info.video.includes('h264')) ? 'copy' : 'encode';
|
// BUT: If upscaling is enabled, we MUST encode.
|
||||||
const statusText = videoMode === 'copy' ? 'Transcoding (Audio)' : 'Transcoding (Video)';
|
const videoMode = (info.video && info.video.includes('h264') && !settings.upscaleEnabled) ? 'copy' : 'encode';
|
||||||
|
const statusText = videoMode === 'copy' ? 'Transcoding (Audio)' : (settings.upscaleEnabled ? 'Upscaling' : 'Transcoding (Video)');
|
||||||
|
const statusMode = settings.upscaleEnabled ? 'upscaling' : 'transcoding';
|
||||||
|
|
||||||
this.updateTranscodeStatus('transcoding', statusText);
|
this.updateTranscodeStatus(statusMode, statusText);
|
||||||
const playlistUrl = await this.startTranscodeSession(url, {
|
const playlistUrl = await this.startTranscodeSession(url, {
|
||||||
videoMode,
|
videoMode,
|
||||||
seekOffset: this.resumeTime, // Ensure seekOffset is passed
|
seekOffset: this.resumeTime, // Ensure seekOffset is passed
|
||||||
@@ -434,10 +436,12 @@ class WatchPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority 1: Force Video Transcode (Full)
|
// Priority 1: Force Video Transcode (Full) or Upscaling
|
||||||
if (settings.forceVideoTranscode) {
|
if (settings.forceVideoTranscode || settings.upscaleEnabled) {
|
||||||
console.log('[WatchPage] Force Video Transcode enabled. Starting session (encode)...');
|
const statusText = settings.upscaleEnabled ? 'Upscaling' : 'Transcoding (Video)';
|
||||||
this.updateTranscodeStatus('transcoding', 'Transcoding (Video)');
|
const statusMode = settings.upscaleEnabled ? 'upscaling' : 'transcoding';
|
||||||
|
console.log(`[WatchPage] ${statusText} enabled. Starting session (encode)...`);
|
||||||
|
this.updateTranscodeStatus(statusMode, statusText);
|
||||||
const playlistUrl = await this.startTranscodeSession(url, {
|
const playlistUrl = await this.startTranscodeSession(url, {
|
||||||
videoMode: 'encode',
|
videoMode: 'encode',
|
||||||
seekOffset: this.resumeTime
|
seekOffset: this.resumeTime
|
||||||
|
|||||||
+5
-1
@@ -79,7 +79,11 @@ function getDefaultSettings() {
|
|||||||
audioMixPreset: 'auto', // auto | itu | night | cinematic | passthrough
|
audioMixPreset: 'auto', // auto | itu | night | cinematic | passthrough
|
||||||
// Probe cache settings
|
// Probe cache settings
|
||||||
probeCacheTTL: 300, // 5 minutes for URL probe cache
|
probeCacheTTL: 300, // 5 minutes for URL probe cache
|
||||||
seriesProbeCacheDays: 7 // 7 days for series episode probe cache
|
seriesProbeCacheDays: 7, // 7 days for series episode probe cache
|
||||||
|
// Upscaling settings
|
||||||
|
upscaleEnabled: false,
|
||||||
|
upscaleMethod: 'hardware', // hardware | software
|
||||||
|
upscaleTarget: '1080p' // 1080p | 4k | 720p
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ router.post('/session', async (req, res) => {
|
|||||||
maxResolution: settings.maxResolution || '1080p',
|
maxResolution: settings.maxResolution || '1080p',
|
||||||
quality: settings.quality || 'medium',
|
quality: settings.quality || 'medium',
|
||||||
audioMixPreset: settings.audioMixPreset || 'auto', // Audio downmix preset
|
audioMixPreset: settings.audioMixPreset || 'auto', // Audio downmix preset
|
||||||
|
// Upscaling options
|
||||||
|
upscaleEnabled: settings.upscaleEnabled || false,
|
||||||
|
upscaleMethod: settings.upscaleMethod || 'hardware',
|
||||||
|
upscaleTarget: settings.upscaleTarget || '1080p',
|
||||||
videoMode: videoMode, // 'copy' or 'encode'
|
videoMode: videoMode, // 'copy' or 'encode'
|
||||||
videoCodec: videoCodec, // 'h264', 'hevc', etc.
|
videoCodec: videoCodec, // 'h264', 'hevc', etc.
|
||||||
audioCodec: audioCodec, // 'aac', 'ac3', etc.
|
audioCodec: audioCodec, // 'aac', 'ac3', etc.
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ class TranscodeSession extends EventEmitter {
|
|||||||
hwEncoder: options.hwEncoder || 'software',
|
hwEncoder: options.hwEncoder || 'software',
|
||||||
maxResolution: options.maxResolution || '1080p',
|
maxResolution: options.maxResolution || '1080p',
|
||||||
quality: options.quality || 'medium',
|
quality: options.quality || 'medium',
|
||||||
|
// Upscaling options
|
||||||
|
upscaleEnabled: options.upscaleEnabled || false,
|
||||||
|
upscaleMethod: options.upscaleMethod || 'hardware', // 'hardware' or 'software'
|
||||||
|
upscaleTarget: options.upscaleTarget || '1080p',
|
||||||
...options
|
...options
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -355,7 +359,9 @@ class TranscodeSession extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get target height based on maxResolution setting
|
* Get target height based on maxResolution or upscaleTarget setting
|
||||||
|
* When upscaling is enabled, uses the upscaleTarget resolution.
|
||||||
|
* Otherwise, uses maxResolution to cap the output.
|
||||||
*/
|
*/
|
||||||
getTargetHeight() {
|
getTargetHeight() {
|
||||||
const resolutionMap = {
|
const resolutionMap = {
|
||||||
@@ -364,15 +370,61 @@ class TranscodeSession extends EventEmitter {
|
|||||||
'720p': 720,
|
'720p': 720,
|
||||||
'480p': 480
|
'480p': 480
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// When upscaling is enabled, use the upscale target resolution
|
||||||
|
if (this.options.upscaleEnabled) {
|
||||||
|
const target = resolutionMap[this.options.upscaleTarget] || 1080;
|
||||||
|
console.log(`[TranscodeSession ${this.id}] Upscale target height: ${target}p`);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, use max resolution as the cap
|
||||||
return resolutionMap[this.options.maxResolution] || 1080;
|
return resolutionMap[this.options.maxResolution] || 1080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build scale filter string based on encoder and upscaling settings
|
||||||
|
* @param {string} encoder - The encoder being used
|
||||||
|
* @param {number} height - Target height
|
||||||
|
*/
|
||||||
|
buildScaleFilter(encoder, height) {
|
||||||
|
const useUpscale = this.options.upscaleEnabled;
|
||||||
|
const upscaleMethod = this.options.upscaleMethod || 'hardware';
|
||||||
|
|
||||||
|
// Log upscaling status
|
||||||
|
if (useUpscale) {
|
||||||
|
console.log(`[TranscodeSession ${this.id}] Upscaling: ${upscaleMethod} method to ${height}p`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardware scaling filters (for both upscale and downscale)
|
||||||
|
if (upscaleMethod === 'hardware' || !useUpscale) {
|
||||||
|
switch (encoder) {
|
||||||
|
case 'nvenc':
|
||||||
|
// NVIDIA CUDA scaling with Lanczos for upscaling
|
||||||
|
return `scale_cuda=-2:${height}:interp_algo=lanczos`;
|
||||||
|
case 'vaapi':
|
||||||
|
return `scale_vaapi=w=-2:h=${height}:format=nv12`;
|
||||||
|
case 'qsv':
|
||||||
|
return `scale_qsv=w=-2:h=${height}`;
|
||||||
|
case 'amf':
|
||||||
|
// AMF uses CPU decode, so use software scale
|
||||||
|
return useUpscale ? `scale=-2:${height}:flags=lanczos` : `scale=-2:${height}`;
|
||||||
|
case 'software':
|
||||||
|
default:
|
||||||
|
return useUpscale ? `scale=-2:${height}:flags=lanczos` : `scale=-2:${height}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Software Lanczos scaling (high quality, slower)
|
||||||
|
return `scale=-2:${height}:flags=lanczos`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NVIDIA NVENC encoder arguments
|
* NVIDIA NVENC encoder arguments
|
||||||
*/
|
*/
|
||||||
addNvencEncoderArgs(args, height, qp) {
|
addNvencEncoderArgs(args, height, qp) {
|
||||||
// Video filter for scaling on GPU
|
// Video filter for scaling on GPU
|
||||||
args.push('-vf', `scale_cuda=-2:${height}:interp_algo=lanczos`);
|
args.push('-vf', this.buildScaleFilter('nvenc', height));
|
||||||
|
|
||||||
// NVENC encoder with quality settings
|
// NVENC encoder with quality settings
|
||||||
// Using portable options that work across FFmpeg builds
|
// Using portable options that work across FFmpeg builds
|
||||||
@@ -381,7 +433,8 @@ class TranscodeSession extends EventEmitter {
|
|||||||
'-preset', 'p4', // Balanced preset (p1=fastest, p7=best)
|
'-preset', 'p4', // Balanced preset (p1=fastest, p7=best)
|
||||||
'-rc', 'constqp', // Constant QP mode
|
'-rc', 'constqp', // Constant QP mode
|
||||||
'-qp', String(qp),
|
'-qp', String(qp),
|
||||||
'-bf', '3' // B-frames for better compression
|
'-bf', '3', // B-frames for better compression
|
||||||
|
'-pix_fmt', 'yuv420p' // Force 8-bit output for compatibility
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +443,7 @@ class TranscodeSession extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
addAmfEncoderArgs(args, height, qp) {
|
addAmfEncoderArgs(args, height, qp) {
|
||||||
// CPU decoding + software scale + AMF encode
|
// CPU decoding + software scale + AMF encode
|
||||||
args.push('-vf', `scale=-2:${height}`);
|
args.push('-vf', this.buildScaleFilter('amf', height));
|
||||||
|
|
||||||
args.push(
|
args.push(
|
||||||
'-c:v', 'h264_amf',
|
'-c:v', 'h264_amf',
|
||||||
@@ -398,7 +451,8 @@ class TranscodeSession extends EventEmitter {
|
|||||||
'-rc', 'cqp', // Constant QP
|
'-rc', 'cqp', // Constant QP
|
||||||
'-qp_i', String(qp),
|
'-qp_i', String(qp),
|
||||||
'-qp_p', String(qp + 2),
|
'-qp_p', String(qp + 2),
|
||||||
'-qp_b', String(qp + 4)
|
'-qp_b', String(qp + 4),
|
||||||
|
'-pix_fmt', 'yuv420p' // Force 8-bit output for compatibility
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,7 +464,7 @@ class TranscodeSession extends EventEmitter {
|
|||||||
// 1. scale_vaapi to resize on GPU
|
// 1. scale_vaapi to resize on GPU
|
||||||
// 2. Ensure output format is nv12 for maximum encoder compatibility
|
// 2. Ensure output format is nv12 for maximum encoder compatibility
|
||||||
// The format is handled automatically when using -hwaccel_output_format vaapi
|
// The format is handled automatically when using -hwaccel_output_format vaapi
|
||||||
args.push('-vf', `scale_vaapi=w=-2:h=${height}:format=nv12`);
|
args.push('-vf', this.buildScaleFilter('vaapi', height));
|
||||||
|
|
||||||
// VAAPI encoder with quality setting
|
// VAAPI encoder with quality setting
|
||||||
// Note: -global_quality is the portable way to set quality for VAAPI
|
// Note: -global_quality is the portable way to set quality for VAAPI
|
||||||
@@ -418,7 +472,8 @@ class TranscodeSession extends EventEmitter {
|
|||||||
'-c:v', 'h264_vaapi',
|
'-c:v', 'h264_vaapi',
|
||||||
'-profile:v', 'main', // Use main profile for compatibility
|
'-profile:v', 'main', // Use main profile for compatibility
|
||||||
'-global_quality', String(qp),
|
'-global_quality', String(qp),
|
||||||
'-bf', '3'
|
'-bf', '3',
|
||||||
|
'-pix_fmt', 'yuv420p' // Force 8-bit output for compatibility
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,14 +482,15 @@ class TranscodeSession extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
addQsvEncoderArgs(args, height, qp) {
|
addQsvEncoderArgs(args, height, qp) {
|
||||||
// Scale on QSV
|
// Scale on QSV
|
||||||
args.push('-vf', `scale_qsv=w=-2:h=${height}`);
|
args.push('-vf', this.buildScaleFilter('qsv', height));
|
||||||
|
|
||||||
args.push(
|
args.push(
|
||||||
'-c:v', 'h264_qsv',
|
'-c:v', 'h264_qsv',
|
||||||
'-preset', 'medium',
|
'-preset', 'medium',
|
||||||
'-global_quality', String(qp),
|
'-global_quality', String(qp),
|
||||||
'-look_ahead', '1',
|
'-look_ahead', '1',
|
||||||
'-look_ahead_depth', '40'
|
'-look_ahead_depth', '40',
|
||||||
|
'-pix_fmt', 'yuv420p' // Force 8-bit output for compatibility
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,8 +498,8 @@ class TranscodeSession extends EventEmitter {
|
|||||||
* Software encoder arguments (fallback)
|
* Software encoder arguments (fallback)
|
||||||
*/
|
*/
|
||||||
addSoftwareEncoderArgs(args, height, crf) {
|
addSoftwareEncoderArgs(args, height, crf) {
|
||||||
// Software scaling
|
// Software scaling (use Lanczos for upscaling if enabled)
|
||||||
args.push('-vf', `scale=-2:${height}`);
|
args.push('-vf', this.buildScaleFilter('software', height));
|
||||||
|
|
||||||
args.push(
|
args.push(
|
||||||
'-c:v', 'libx264',
|
'-c:v', 'libx264',
|
||||||
|
|||||||
Reference in New Issue
Block a user