Comskip Update

This commit is contained in:
Dispatcharr 2025-09-04 13:45:25 -05:00
parent c76d68f382
commit f652d2b233
9 changed files with 329 additions and 7 deletions

View file

@ -170,6 +170,54 @@ export const WebsocketProvider = ({ children }) => {
// Handle standard message format for other event types
switch (parsedEvent.data?.type) {
case 'comskip_status': {
const rid = parsedEvent.data.recording_id;
const id = `comskip-${rid}`;
const status = parsedEvent.data.status;
const title = parsedEvent.data.title || 'Recording';
if (status === 'started') {
notifications.show({
id,
title: 'Removing commercials',
message: `Processing ${title}...`,
color: 'blue.5',
autoClose: false,
withCloseButton: false,
loading: true,
});
} else if (status === 'completed') {
notifications.update({
id,
title: 'Commercials removed',
message: `${title} — kept ${parsedEvent.data.segments_kept} segments`,
color: 'green.5',
loading: false,
autoClose: 4000,
});
try { await useChannelsStore.getState().fetchRecordings(); } catch {}
} else if (status === 'skipped') {
notifications.update({
id,
title: 'No commercials to remove',
message: parsedEvent.data.reason || '',
color: 'teal',
loading: false,
autoClose: 3000,
});
try { await useChannelsStore.getState().fetchRecordings(); } catch {}
} else if (status === 'error') {
notifications.update({
id,
title: 'Comskip failed',
message: parsedEvent.data.reason || 'Unknown error',
color: 'red',
loading: false,
autoClose: 6000,
});
try { await useChannelsStore.getState().fetchRecordings(); } catch {}
}
break;
}
case 'epg_file':
fetchEPGs();
notifications.show({

View file

@ -1668,6 +1668,20 @@ export default class API {
}
}
static async runComskip(recordingId) {
try {
const resp = await request(`${host}/api/channels/recordings/${recordingId}/comskip/`, {
method: 'POST',
});
// Refresh recordings list to reflect comskip status when done later
// This endpoint just queues the task; the websocket/refresh will update eventually
return resp;
} catch (e) {
errorNotification('Failed to run comskip', e);
throw e;
}
}
// DVR Series Rules
static async listSeriesRules() {
try {

View file

@ -36,6 +36,7 @@ import useChannelsStore from '../store/channels';
import useSettingsStore from '../store/settings';
import useVideoStore from '../store/useVideoStore';
import RecordingForm from '../components/forms/Recording';
import { notifications } from '@mantine/notifications';
import API from '../api';
dayjs.extend(duration);
@ -253,6 +254,12 @@ const RecordingDetailsModal = ({ opened, onClose, recording, channel, posterUrl,
{onWatchRecording && (
<Button size="xs" variant="default" onClick={(e) => { e.stopPropagation?.(); onWatchRecording(); }} disabled={!canWatchRecording}>Watch</Button>
)}
{customProps.status === 'completed' && (!customProps?.comskip || customProps?.comskip?.status !== 'completed') && (
<Button size="xs" variant="light" color="teal" onClick={async (e) => {
e.stopPropagation?.();
try { await API.runComskip(recording.id); notifications.show({ title: 'Removing commercials', message: 'Queued comskip for this recording', color: 'blue.5', autoClose: 2000 }); } catch {}
}}>Remove commercials</Button>
)}
</Group>
</Group>
<Text size="sm">{start.format('MMM D, YYYY h:mma')} {end.format('h:mma')}</Text>
@ -344,6 +351,14 @@ const RecordingCard = ({ recording, category, onOpenDetails }) => {
showVideo(fileUrl, 'vod', { name: recordingName, logo: { url: posterUrl } });
};
const handleRunComskip = async (e) => {
e?.stopPropagation?.();
try {
await API.runComskip(recording.id);
notifications.show({ title: 'Removing commercials', message: 'Queued comskip for this recording', color: 'blue.5', autoClose: 2000 });
} catch {}
};
// Cancel handling for series groups
const [cancelOpen, setCancelOpen] = React.useState(false);
const [busy, setBusy] = React.useState(false);
@ -494,6 +509,11 @@ const RecordingCard = ({ recording, category, onOpenDetails }) => {
</Button>
</Tooltip>
)}
{!isUpcoming && customProps?.status === 'completed' && (!customProps?.comskip || customProps?.comskip?.status !== 'completed') && (
<Button size="xs" variant="light" color="teal" onClick={handleRunComskip}>
Remove commercials
</Button>
)}
</Group>
</Stack>
</Flex>

View file

@ -76,6 +76,7 @@ const SettingsPage = () => {
'dvr-movie-template': '',
'dvr-tv-fallback-template': '',
'dvr-movie-fallback-template': '',
'dvr-comskip-enabled': false,
},
validate: {
@ -422,10 +423,17 @@ const SettingsPage = () => {
{authUser.user_level == USER_LEVELS.ADMIN && (
<>
<Accordion.Item value="dvr-settings">
<Accordion.Control>DVR Recording Paths</Accordion.Control>
<Accordion.Control>DVR</Accordion.Control>
<Accordion.Panel>
<form onSubmit={form.onSubmit(onSubmit)}>
<Stack gap="sm">
<Switch
label="Enable Comskip (remove commercials after recording)"
{...form.getInputProps('dvr-comskip-enabled', { type: 'checkbox' })}
key={form.key('dvr-comskip-enabled')}
id={settings['dvr-comskip-enabled']?.id || 'dvr-comskip-enabled'}
name={settings['dvr-comskip-enabled']?.key || 'dvr-comskip-enabled'}
/>
<TextInput
label="TV Path Template"
description="Supports {show}, {season}, {episode}, {sub_title}, {channel}, {year}, {start}, {end}. Use format specifiers like {season:02d}. Relative paths are under your library dir."