Agregar configuración de pantalla dividida: posición y estiramiento
This commit is contained in:
parent
244b956936
commit
6fc05fab1d
@ -496,23 +496,58 @@ class FFMPEG extends events.EventEmitter {
|
||||
|
||||
// Split-screen composition (independent of watermark)
|
||||
// If we have a split-screen secondary input configured, compose
|
||||
// it to the right of the main video using hstack after scaling
|
||||
// it with the main video based on position (right/left/top/bottom)
|
||||
if (typeof(this._splitOverlayFile) !== 'undefined' && this._splitScreenConfig && this.audioOnly !== true) {
|
||||
console.log("[DEBUG] Building split-screen composition...");
|
||||
try {
|
||||
const splitPercent = Number(this._splitScreenConfig.widthPercent) || 35;
|
||||
const sideW = Math.max(16, Math.round(this.wantedW * splitPercent / 100.0));
|
||||
const mainW = Math.max(16, this.wantedW - sideW);
|
||||
console.log(`[DEBUG] Split-screen dimensions: main=${mainW}x${this.wantedH}, side=${sideW}x${this.wantedH}`);
|
||||
// scale secondary to wanted height, preserving aspect
|
||||
videoComplex += `;[${this._splitOverlayFile}:v]scale=${sideW}:${this.wantedH}:force_original_aspect_ratio=decrease[side_scaled]`;
|
||||
// scale main (currentVideo) to remaining width and wanted height
|
||||
videoComplex += `;${currentVideo}scale=${mainW}:${this.wantedH}:force_original_aspect_ratio=decrease[main_scaled]`;
|
||||
// pad both to exact dimensions if necessary
|
||||
videoComplex += `;[main_scaled]pad=${mainW}:${this.wantedH}:(ow-iw)/2:(oh-ih)/2[main_padded]`;
|
||||
videoComplex += `;[side_scaled]pad=${sideW}:${this.wantedH}:(ow-iw)/2:(oh-ih)/2[side_padded]`;
|
||||
// stack horizontally
|
||||
videoComplex += `;[main_padded][side_padded]hstack=inputs=2[comb2]`;
|
||||
const position = this._splitScreenConfig.position || 'right';
|
||||
const stretch = !!this._splitScreenConfig.stretch;
|
||||
|
||||
let mainW, mainH, sideW, sideH;
|
||||
let stackType; // 'hstack' or 'vstack'
|
||||
let stackOrder; // 'main-first' or 'side-first'
|
||||
|
||||
// Calculate dimensions based on position
|
||||
if (position === 'right' || position === 'left') {
|
||||
// Horizontal split
|
||||
sideW = Math.max(16, Math.round(this.wantedW * splitPercent / 100.0));
|
||||
mainW = Math.max(16, this.wantedW - sideW);
|
||||
mainH = this.wantedH;
|
||||
sideH = this.wantedH;
|
||||
stackType = 'hstack';
|
||||
stackOrder = (position === 'left') ? 'side-first' : 'main-first';
|
||||
} else {
|
||||
// Vertical split (top or bottom)
|
||||
sideH = Math.max(16, Math.round(this.wantedH * splitPercent / 100.0));
|
||||
mainH = Math.max(16, this.wantedH - sideH);
|
||||
mainW = this.wantedW;
|
||||
sideW = this.wantedW;
|
||||
stackType = 'vstack';
|
||||
stackOrder = (position === 'top') ? 'side-first' : 'main-first';
|
||||
}
|
||||
|
||||
console.log(`[DEBUG] Split-screen: position=${position}, stretch=${stretch}, main=${mainW}x${mainH}, side=${sideW}x${sideH}`);
|
||||
|
||||
// Scale filters based on stretch mode
|
||||
if (stretch) {
|
||||
// Stretch mode: scale exactly to dimensions (no aspect ratio preservation)
|
||||
videoComplex += `;[${this._splitOverlayFile}:v]scale=${sideW}:${sideH}[side_scaled]`;
|
||||
videoComplex += `;${currentVideo}scale=${mainW}:${mainH}[main_scaled]`;
|
||||
} else {
|
||||
// Aspect ratio mode: preserve aspect ratio with padding
|
||||
videoComplex += `;[${this._splitOverlayFile}:v]scale=${sideW}:${sideH}:force_original_aspect_ratio=decrease[side_prescale]`;
|
||||
videoComplex += `;[side_prescale]pad=${sideW}:${sideH}:(ow-iw)/2:(oh-ih)/2[side_scaled]`;
|
||||
videoComplex += `;${currentVideo}scale=${mainW}:${mainH}:force_original_aspect_ratio=decrease[main_prescale]`;
|
||||
videoComplex += `;[main_prescale]pad=${mainW}:${mainH}:(ow-iw)/2:(oh-ih)/2[main_scaled]`;
|
||||
}
|
||||
|
||||
// Stack based on type and order
|
||||
if (stackOrder === 'main-first') {
|
||||
videoComplex += `;[main_scaled][side_scaled]${stackType}=inputs=2[comb2]`;
|
||||
} else {
|
||||
videoComplex += `;[side_scaled][main_scaled]${stackType}=inputs=2[comb2]`;
|
||||
}
|
||||
currentVideo = '[comb2]';
|
||||
console.log("[DEBUG] Split-screen composition added to filter_complex");
|
||||
} catch (e) {
|
||||
|
||||
@ -360,6 +360,8 @@ function getSplitScreen(ffmpegSettings, channel) {
|
||||
enabled: !!channel.splitScreen.enabled,
|
||||
source: (typeof channel.splitScreen.source === 'string') ? channel.splitScreen.source : '',
|
||||
widthPercent: Number(channel.splitScreen.widthPercent) || 35,
|
||||
position: (typeof channel.splitScreen.position === 'string') ? channel.splitScreen.position : 'right',
|
||||
stretch: !!channel.splitScreen.stretch,
|
||||
loop: !!channel.splitScreen.loop,
|
||||
};
|
||||
} else {
|
||||
@ -371,6 +373,8 @@ function getSplitScreen(ffmpegSettings, channel) {
|
||||
enabled: !!ffmpegSettings.splitScreenEnabled,
|
||||
source: (typeof ffmpegSettings.splitScreenSource === 'string') ? ffmpegSettings.splitScreenSource : '',
|
||||
widthPercent: Number(ffmpegSettings.splitScreenWidthPercent) || 35,
|
||||
position: (typeof ffmpegSettings.splitScreenPosition === 'string') ? ffmpegSettings.splitScreenPosition : 'right',
|
||||
stretch: !!ffmpegSettings.splitScreenStretch,
|
||||
loop: !!ffmpegSettings.splitScreenLoop,
|
||||
};
|
||||
}
|
||||
|
||||
@ -95,6 +95,8 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions, get
|
||||
enabled: false,
|
||||
source: "",
|
||||
widthPercent: 35,
|
||||
position: 'right',
|
||||
stretch: false,
|
||||
loop: true,
|
||||
}
|
||||
scope.channel.onDemand = {
|
||||
@ -160,9 +162,17 @@ module.exports = function ($timeout, $location, dizquetv, resolutionOptions, get
|
||||
enabled: false,
|
||||
source: "",
|
||||
widthPercent: 35,
|
||||
position: 'right',
|
||||
stretch: false,
|
||||
loop: true,
|
||||
}
|
||||
}
|
||||
if (typeof(scope.channel.splitScreen.position) === 'undefined') {
|
||||
scope.channel.splitScreen.position = 'right';
|
||||
}
|
||||
if (typeof(scope.channel.splitScreen.stretch) === 'undefined') {
|
||||
scope.channel.splitScreen.stretch = false;
|
||||
}
|
||||
if (
|
||||
(scope.channel.transcoding.targetResolution == null)
|
||||
|| (typeof(scope.channel.transcoding.targetResolution) === 'undefined')
|
||||
|
||||
@ -11,12 +11,16 @@ module.exports = function (dizquetv, resolutionOptions) {
|
||||
scope.settings = settings
|
||||
// ensure videoFlip default exists
|
||||
scope.settings.videoFlip = scope.settings.videoFlip || 'none';
|
||||
scope.settings.splitScreenPosition = scope.settings.splitScreenPosition || 'right';
|
||||
scope.settings.splitScreenStretch = scope.settings.splitScreenStretch || false;
|
||||
})
|
||||
scope.updateSettings = (settings) => {
|
||||
delete scope.settingsError;
|
||||
dizquetv.updateFfmpegSettings(settings).then((_settings) => {
|
||||
scope.settings = _settings
|
||||
scope.settings.videoFlip = scope.settings.videoFlip || 'none';
|
||||
scope.settings.splitScreenPosition = scope.settings.splitScreenPosition || 'right';
|
||||
scope.settings.splitScreenStretch = scope.settings.splitScreenStretch || false;
|
||||
}).catch( (err) => {
|
||||
if ( typeof(err.data) === "string") {
|
||||
scope.settingsError = err.data;
|
||||
@ -27,6 +31,8 @@ module.exports = function (dizquetv, resolutionOptions) {
|
||||
dizquetv.resetFfmpegSettings(settings).then((_settings) => {
|
||||
scope.settings = _settings
|
||||
scope.settings.videoFlip = scope.settings.videoFlip || 'none';
|
||||
scope.settings.splitScreenPosition = scope.settings.splitScreenPosition || 'right';
|
||||
scope.settings.splitScreenStretch = scope.settings.splitScreenStretch || false;
|
||||
})
|
||||
}
|
||||
scope.isTranscodingNotNeeded = () => {
|
||||
|
||||
@ -789,6 +789,19 @@
|
||||
<br></br>
|
||||
<label>Secondary Width (%)</label>
|
||||
<input type="number" class="form-control form-control-sm" ng-model="channel.splitScreen.widthPercent"></input>
|
||||
<small class='form-text text-muted'>Percentage of width/height depending on position.</small>
|
||||
<br></br>
|
||||
<label>Position</label>
|
||||
<select class="form-control form-control-sm" ng-model="channel.splitScreen.position">
|
||||
<option value="right">Right</option>
|
||||
<option value="left">Left</option>
|
||||
<option value="top">Top</option>
|
||||
<option value="bottom">Bottom</option>
|
||||
</select>
|
||||
<br></br>
|
||||
<input id="channelSplitStretch" type="checkbox" ng-model="channel.splitScreen.stretch"></input>
|
||||
<label for="channelSplitStretch">Stretch secondary (fill entire area)</label>
|
||||
<small class='form-text text-muted'>If unchecked, maintains aspect ratio with padding.</small>
|
||||
<br></br>
|
||||
<input id="channelSplitLoop" type="checkbox" ng-model="channel.splitScreen.loop"></input>
|
||||
<label for="channelSplitLoop">Loop secondary source</label>
|
||||
|
||||
@ -270,6 +270,19 @@
|
||||
<br></br>
|
||||
<label>Secondary Width (%)</label>
|
||||
<input type="number" class="form-control form-control-sm" ng-model="settings.splitScreenWidthPercent"></input>
|
||||
<small class="form-text text-muted">Percentage of width/height depending on position.</small>
|
||||
<br></br>
|
||||
<label>Position</label>
|
||||
<select class="form-control form-control-sm" ng-model="settings.splitScreenPosition">
|
||||
<option value="right">Right</option>
|
||||
<option value="left">Left</option>
|
||||
<option value="top">Top</option>
|
||||
<option value="bottom">Bottom</option>
|
||||
</select>
|
||||
<br></br>
|
||||
<input id="splitScreenStretch" type="checkbox" ng-model="settings.splitScreenStretch"></input>
|
||||
<label for="splitScreenStretch">Stretch secondary (fill entire area)</label>
|
||||
<small class="form-text text-muted">If unchecked, maintains aspect ratio with padding.</small>
|
||||
<br></br>
|
||||
<input id="splitScreenLoop" type="checkbox" ng-model="settings.splitScreenLoop"></input>
|
||||
<label for="splitScreenLoop">Loop secondary source</label>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user