mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
optional sidebar
This commit is contained in:
@@ -450,7 +450,7 @@ function buildReplayCaption({ requester, usedSources = [], missingSources = [],
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
async function sendReplayToChannel(channelId, requester, sources = [], explicitTitle = '') {
|
||||
async function sendReplayToChannel(channelId, requester, sources = [], explicitTitle = '', includeSidebar = true) {
|
||||
if (!channelId) {
|
||||
throw new Error('Replay channel not configured');
|
||||
}
|
||||
@@ -459,6 +459,7 @@ async function sendReplayToChannel(channelId, requester, sources = [], explicitT
|
||||
sources,
|
||||
title: resolvedTitle,
|
||||
requester,
|
||||
includeSidebar,
|
||||
});
|
||||
const filenameBase = sanitizeReplayTitleForFilename(resolvedTitle);
|
||||
const attachment = new AttachmentBuilder(buffer, { name: `${filenameBase}.mp4` });
|
||||
@@ -1619,7 +1620,13 @@ function handleBusEvent(event) {
|
||||
schedulePresenceRotation();
|
||||
break;
|
||||
case 'replay.requested':
|
||||
sendReplayToChannel(payload?.channelId, payload?.requester, payload?.sources || [], payload?.title || '').catch((err) => {
|
||||
sendReplayToChannel(
|
||||
payload?.channelId,
|
||||
payload?.requester,
|
||||
payload?.sources || [],
|
||||
payload?.title || '',
|
||||
payload?.includeSidebar !== false,
|
||||
).catch((err) => {
|
||||
logger.warn('Replay send failed', err.message);
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -724,7 +724,7 @@ async function probeMaxFrameSize(paths) {
|
||||
return { maxWidth, maxHeight };
|
||||
}
|
||||
|
||||
async function buildReplayVideo({ sources = [], title = '', requester = '' } = {}) {
|
||||
async function buildReplayVideo({ sources = [], title = '', requester = '', includeSidebar = true } = {}) {
|
||||
if (!Array.isArray(sources) || !sources.length) {
|
||||
throw new Error('No replay sources selected');
|
||||
}
|
||||
@@ -843,22 +843,6 @@ async function buildReplayVideo({ sources = [], title = '', requester = '' } = {
|
||||
tileWidth = clampEven(tileWidth);
|
||||
tileHeight = clampEven(tileHeight);
|
||||
|
||||
const selectedRoverIds = usedSources
|
||||
.filter((entry) => entry?.type === 'rover')
|
||||
.map((entry) => String(entry.id));
|
||||
const driverBatteryLines = buildDriverBatterySnapshot(selectedRoverIds);
|
||||
const chatEvents = buildChatEventsForWindow(tStart, tEnd);
|
||||
const durationSec = BUILD_DURATION_MS / 1000;
|
||||
const sidebarPath = await renderSidebarVideo({
|
||||
tmpDir,
|
||||
title: resolvedTitle,
|
||||
durationSec,
|
||||
height: clampEven(tileHeight * layout.rows),
|
||||
windowStartMs: tStart,
|
||||
driverBatteryLines,
|
||||
chatEvents,
|
||||
});
|
||||
|
||||
const inputArgs = [];
|
||||
const filterParts = [];
|
||||
const layoutParts = [];
|
||||
@@ -872,9 +856,29 @@ async function buildReplayVideo({ sources = [], title = '', requester = '' } = {
|
||||
layoutParts.push(`${x}_${y}`);
|
||||
}
|
||||
|
||||
inputArgs.push('-i', sidebarPath);
|
||||
const sidebarInputIndex = normalizedVideos.length;
|
||||
const audioInputStart = normalizedVideos.length + 1;
|
||||
let audioInputStart = normalizedVideos.length;
|
||||
let sidebarInputIndex = -1;
|
||||
if (includeSidebar) {
|
||||
const selectedRoverIds = usedSources
|
||||
.filter((entry) => entry?.type === 'rover')
|
||||
.map((entry) => String(entry.id));
|
||||
const driverBatteryLines = buildDriverBatterySnapshot(selectedRoverIds);
|
||||
const chatEvents = buildChatEventsForWindow(tStart, tEnd);
|
||||
const durationSec = BUILD_DURATION_MS / 1000;
|
||||
const sidebarPath = await renderSidebarVideo({
|
||||
tmpDir,
|
||||
title: resolvedTitle,
|
||||
durationSec,
|
||||
height: clampEven(tileHeight * layout.rows),
|
||||
windowStartMs: tStart,
|
||||
driverBatteryLines,
|
||||
chatEvents,
|
||||
});
|
||||
inputArgs.push('-i', sidebarPath);
|
||||
sidebarInputIndex = normalizedVideos.length;
|
||||
audioInputStart = normalizedVideos.length + 1;
|
||||
}
|
||||
|
||||
for (const audioPath of normalizedAudios) {
|
||||
inputArgs.push('-i', audioPath);
|
||||
}
|
||||
@@ -887,7 +891,11 @@ async function buildReplayVideo({ sources = [], title = '', requester = '' } = {
|
||||
`xstack=inputs=${normalizedVideos.length}:layout=${layoutParts.join('|')}:fill=black[vgrid]`,
|
||||
);
|
||||
}
|
||||
filterParts.push(`[vgrid][${sidebarInputIndex}:v]hstack=inputs=2[vout]`);
|
||||
if (includeSidebar) {
|
||||
filterParts.push(`[vgrid][${sidebarInputIndex}:v]hstack=inputs=2[vout]`);
|
||||
} else {
|
||||
filterParts.push('[vgrid]null[vout]');
|
||||
}
|
||||
|
||||
if (normalizedAudios.length) {
|
||||
const audioRefs = normalizedAudios.map((_, idx) => `[${audioInputStart + idx}:a]`).join('');
|
||||
|
||||
@@ -20,6 +20,11 @@ function normalizeReplayTitle(value) {
|
||||
return value.trim().slice(0, 120);
|
||||
}
|
||||
|
||||
function normalizeIncludeSidebar(value) {
|
||||
if (typeof value === 'boolean') return value;
|
||||
return true;
|
||||
}
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('replay:trigger', (payload = {}, cb = () => {}) => {
|
||||
if (getMode() === MODES.LOCKDOWN) {
|
||||
@@ -43,6 +48,7 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
const requester = buildRequesterLabel(socket);
|
||||
const title = normalizeReplayTitle(payload?.title);
|
||||
const includeSidebar = normalizeIncludeSidebar(payload?.includeSidebar);
|
||||
const attempt = tryTriggerReplay({ by: { source: 'web', requester } });
|
||||
if (!attempt.ok) {
|
||||
cb({ error: 'Replay cooldown active', remainingMs: attempt.remainingMs, state: attempt.state });
|
||||
@@ -55,6 +61,7 @@ io.on('connection', (socket) => {
|
||||
channelId,
|
||||
requester,
|
||||
title,
|
||||
includeSidebar,
|
||||
sources,
|
||||
requestedBy: { socketId: socket.id },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user