diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py
index ab206afb..23d706ba 100644
--- a/apps/channels/api_views.py
+++ b/apps/channels/api_views.py
@@ -285,6 +285,10 @@ class ChannelViewSet(viewsets.ModelViewSet):
{"error": f"Channel number {channel_number} is already in use. Please choose a different number."},
status=status.HTTP_400_BAD_REQUEST
)
+ #Get the tvc_guide_stationid from custom properties if it exists
+ tvc_guide_stationid = None
+ if 'tvc-guide-stationid' in stream_custom_props:
+ tvc_guide_stationid = stream_custom_props['tvc-guide-stationid']
@@ -292,6 +296,7 @@ class ChannelViewSet(viewsets.ModelViewSet):
'channel_number': channel_number,
'name': name,
'tvg_id': stream.tvg_id,
+ 'tvc_guide_stationid': tvc_guide_stationid,
'channel_group_id': channel_group.id,
'streams': [stream_id],
}
diff --git a/apps/channels/migrations/0019_channel_tvc_guide_stationid.py b/apps/channels/migrations/0019_channel_tvc_guide_stationid.py
new file mode 100644
index 00000000..86f76b82
--- /dev/null
+++ b/apps/channels/migrations/0019_channel_tvc_guide_stationid.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.6 on 2025-05-04 00:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dispatcharr_channels', '0018_channelgroupm3uaccount_custom_properties_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='channel',
+ name='tvc_guide_stationid',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ ]
diff --git a/apps/channels/models.py b/apps/channels/models.py
index f158c2d0..dc5c50fb 100644
--- a/apps/channels/models.py
+++ b/apps/channels/models.py
@@ -236,6 +236,8 @@ class Channel(models.Model):
help_text="Channel group this channel belongs to."
)
tvg_id = models.CharField(max_length=255, blank=True, null=True)
+ tvc_guide_stationid = models.CharField(max_length=255, blank=True, null=True)
+
epg_data = models.ForeignKey(
EPGData,
on_delete=models.SET_NULL,
diff --git a/apps/channels/serializers.py b/apps/channels/serializers.py
index a23e4637..ce3910a4 100644
--- a/apps/channels/serializers.py
+++ b/apps/channels/serializers.py
@@ -152,6 +152,7 @@ class ChannelSerializer(serializers.ModelSerializer):
'name',
'channel_group_id',
'tvg_id',
+ 'tvc_guide_stationid',
'epg_data_id',
'streams',
'stream_profile_id',
diff --git a/apps/output/views.py b/apps/output/views.py
index 9429d9ca..38b69bde 100644
--- a/apps/output/views.py
+++ b/apps/output/views.py
@@ -31,11 +31,16 @@ def generate_m3u(request, profile_name=None):
if channel.logo:
tvg_logo = request.build_absolute_uri(reverse('api:channels:logo-cache', args=[channel.logo.id]))
+ # create possible gracenote id insertion
+ tvc_guide_stationid = ""
+ if channel.tvc_guide_stationid:
+ tvc_guide_stationid = f'tvc-guide-stationid="{channel.tvc_guide_stationid}" '
+
channel_number = channel.channel_number
extinf_line = (
f'#EXTINF:-1 tvg-id="{tvg_id}" tvg-name="{tvg_name}" tvg-logo="{tvg_logo}" '
- f'tvg-chno="{channel_number}" group-title="{group_title}",{channel.name}\n'
+ f'tvg-chno="{channel_number}" {tvc_guide_stationid}group-title="{group_title}",{channel.name}\n'
)
base_url = request.build_absolute_uri('/')[:-1]
diff --git a/frontend/src/api.js b/frontend/src/api.js
index 274ff1fa..b2ecf820 100644
--- a/frontend/src/api.js
+++ b/frontend/src/api.js
@@ -347,6 +347,11 @@ export default class API {
payload.tvg_id = null;
}
+ // Ensure tvc_guide_stationid is included properly (not as empty string)
+ if (payload.tvc_guide_stationid === '') {
+ payload.tvc_guide_stationid = null;
+ }
+
// Handle channel_number properly
if (payload.channel_number === '') {
payload.channel_number = null;
diff --git a/frontend/src/components/forms/Channel.jsx b/frontend/src/components/forms/Channel.jsx
index 5c913060..5c7243e9 100644
--- a/frontend/src/components/forms/Channel.jsx
+++ b/frontend/src/components/forms/Channel.jsx
@@ -98,6 +98,7 @@ const Channel = ({ channel = null, isOpen, onClose }) => {
channel_group_id: Object.keys(channelGroups)[0],
stream_profile_id: '0',
tvg_id: '',
+ tvc_guide_stationid: '',
epg_data_id: '',
logo_id: '',
},
@@ -122,6 +123,9 @@ const Channel = ({ channel = null, isOpen, onClose }) => {
// Ensure tvg_id is properly included (no empty strings)
formattedValues.tvg_id = formattedValues.tvg_id || null;
+ // Ensure tvc_guide_stationid is properly included (no empty strings)
+ formattedValues.tvc_guide_stationid = formattedValues.tvc_guide_stationid || null;
+
if (channel) {
// If there's an EPG to set, use our enhanced endpoint
if (values.epg_data_id !== (channel.epg_data_id ?? '')) {
@@ -187,6 +191,7 @@ const Channel = ({ channel = null, isOpen, onClose }) => {
? `${channel.stream_profile_id}`
: '0',
tvg_id: channel.tvg_id,
+ tvc_guide_stationid: channel.tvc_guide_stationid,
epg_data_id: channel.epg_data_id ?? '',
logo_id: `${channel.logo_id}`,
});
@@ -673,6 +678,16 @@ const Channel = ({ channel = null, isOpen, onClose }) => {
error={formik.errors.tvg_id ? formik.touched.tvg_id : ''}
size="xs"
/>
+
+