Save stream stats to database.

This commit is contained in:
SergeantPanda 2025-07-28 21:40:29 -05:00
parent db6e09370c
commit 6180b4ffef
5 changed files with 102 additions and 8 deletions

View file

@ -0,0 +1,23 @@
# Generated by Django 5.1.6 on 2025-07-29 02:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dispatcharr_channels', '0022_channel_auto_created_channel_auto_created_by_and_more'),
]
operations = [
migrations.AddField(
model_name='stream',
name='stream_stats',
field=models.JSONField(blank=True, help_text='JSON object containing stream statistics like video codec, resolution, etc.', null=True),
),
migrations.AddField(
model_name='stream',
name='stream_stats_updated_at',
field=models.DateTimeField(blank=True, db_index=True, help_text='When stream statistics were last updated', null=True),
),
]

View file

@ -95,6 +95,19 @@ class Stream(models.Model):
)
last_seen = models.DateTimeField(db_index=True, default=datetime.now)
custom_properties = models.TextField(null=True, blank=True)
# Stream statistics fields
stream_stats = models.JSONField(
null=True,
blank=True,
help_text="JSON object containing stream statistics like video codec, resolution, etc."
)
stream_stats_updated_at = models.DateTimeField(
null=True,
blank=True,
help_text="When stream statistics were last updated",
db_index=True
)
class Meta:
# If you use m3u_account, you might do unique_together = ('name','url','m3u_account')

View file

@ -104,6 +104,8 @@ class StreamSerializer(serializers.ModelSerializer):
"is_custom",
"channel_group",
"stream_hash",
"stream_stats",
"stream_stats_updated_at",
]
def get_fields(self):

View file

@ -417,8 +417,8 @@ class ChannelService:
return False, None, None, {"error": f"Exception: {str(e)}"}
@staticmethod
def parse_and_store_stream_info(channel_id, stream_info_line, stream_type="video"):
"""Parse FFmpeg stream info line and store in Redis metadata"""
def parse_and_store_stream_info(channel_id, stream_info_line, stream_type="video", stream_id=None):
"""Parse FFmpeg stream info line and store in Redis metadata and database"""
try:
if stream_type == "input":
# Example lines:
@ -432,6 +432,9 @@ class ChannelService:
# Store in Redis if we have valid data
if input_format:
ChannelService._update_stream_info_in_redis(channel_id, None, None, None, None, None, None, None, None, None, None, None, input_format)
# Save to database if stream_id is provided
if stream_id:
ChannelService._update_stream_stats_in_db(stream_id, stream_type=input_format)
logger.debug(f"Input format info - Format: {input_format} for channel {channel_id}")
@ -480,6 +483,16 @@ class ChannelService:
# Store in Redis if we have valid data
if any(x is not None for x in [video_codec, resolution, source_fps, pixel_format, video_bitrate]):
ChannelService._update_stream_info_in_redis(channel_id, video_codec, resolution, width, height, source_fps, pixel_format, video_bitrate, None, None, None, None, None)
# Save to database if stream_id is provided
if stream_id:
ChannelService._update_stream_stats_in_db(
stream_id,
video_codec=video_codec,
resolution=resolution,
source_fps=source_fps,
pixel_format=pixel_format,
video_bitrate=video_bitrate
)
logger.info(f"Video stream info - Codec: {video_codec}, Resolution: {resolution}, "
f"Source FPS: {source_fps}, Pixel Format: {pixel_format}, "
@ -511,9 +524,15 @@ class ChannelService:
# Store in Redis if we have valid data
if any(x is not None for x in [audio_codec, sample_rate, channels, audio_bitrate]):
ChannelService._update_stream_info_in_redis(channel_id, None, None, None, None, None, None, None, audio_codec, sample_rate, channels, audio_bitrate, None)
logger.info(f"Audio stream info - Codec: {audio_codec}, Sample Rate: {sample_rate} Hz, "
f"Channels: {channels}, Audio Bitrate: {audio_bitrate} kb/s")
# Save to database if stream_id is provided
if stream_id:
ChannelService._update_stream_stats_in_db(
stream_id,
audio_codec=audio_codec,
sample_rate=sample_rate,
audio_channels=channels,
audio_bitrate=audio_bitrate
)
except Exception as e:
logger.debug(f"Error parsing FFmpeg {stream_type} stream info: {e}")
@ -575,6 +594,35 @@ class ChannelService:
logger.error(f"Error updating stream info in Redis: {e}")
return False
@staticmethod
def _update_stream_stats_in_db(stream_id, **stats):
"""Update stream stats in database"""
try:
from apps.channels.models import Stream
from django.utils import timezone
stream = Stream.objects.get(id=stream_id)
# Get existing stats or create new dict
current_stats = stream.stream_stats or {}
# Update with new stats
for key, value in stats.items():
if value is not None:
current_stats[key] = value
# Save updated stats and timestamp
stream.stream_stats = current_stats
stream.stream_stats_updated_at = timezone.now()
stream.save(update_fields=['stream_stats', 'stream_stats_updated_at'])
logger.debug(f"Updated stream stats in database for stream {stream_id}: {stats}")
return True
except Exception as e:
logger.error(f"Error updating stream stats in database for stream {stream_id}: {e}")
return False
# Helper methods for Redis operations
@staticmethod

View file

@ -587,9 +587,9 @@ class StreamManager:
from .services.channel_service import ChannelService
if "video:" in content_lower:
ChannelService.parse_and_store_stream_info(self.channel_id, content, "video")
ChannelService.parse_and_store_stream_info(self.channel_id, content, "video", self.current_stream_id)
elif "audio:" in content_lower:
ChannelService.parse_and_store_stream_info(self.channel_id, content, "audio")
ChannelService.parse_and_store_stream_info(self.channel_id, content, "audio", self.current_stream_id)
# Determine log level based on content
if any(keyword in content_lower for keyword in ['error', 'failed', 'cannot', 'invalid', 'corrupt']):
@ -605,7 +605,7 @@ class StreamManager:
if content.startswith('Input #0'):
# If it's input 0, parse stream info
from .services.channel_service import ChannelService
ChannelService.parse_and_store_stream_info(self.channel_id, content, "input")
ChannelService.parse_and_store_stream_info(self.channel_id, content, "input", self.current_stream_id)
else:
# Everything else at debug level
logger.debug(f"FFmpeg stderr for channel {self.channel_id}: {content}")
@ -649,6 +649,14 @@ class StreamManager:
if any(x is not None for x in [ffmpeg_speed, ffmpeg_fps, actual_fps, ffmpeg_output_bitrate]):
self._update_ffmpeg_stats_in_redis(ffmpeg_speed, ffmpeg_fps, actual_fps, ffmpeg_output_bitrate)
# Also save ffmpeg_output_bitrate to database if we have stream_id
if ffmpeg_output_bitrate is not None and self.current_stream_id:
from .services.channel_service import ChannelService
ChannelService._update_stream_stats_in_db(
self.current_stream_id,
ffmpeg_output_bitrate=ffmpeg_output_bitrate
)
# Fix the f-string formatting
actual_fps_str = f"{actual_fps:.1f}" if actual_fps is not None else "N/A"
ffmpeg_output_bitrate_str = f"{ffmpeg_output_bitrate:.1f}" if ffmpeg_output_bitrate is not None else "N/A"