mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-23 10:45:27 +00:00
Save stream stats to database.
This commit is contained in:
parent
db6e09370c
commit
6180b4ffef
5 changed files with 102 additions and 8 deletions
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ class StreamSerializer(serializers.ModelSerializer):
|
|||
"is_custom",
|
||||
"channel_group",
|
||||
"stream_hash",
|
||||
"stream_stats",
|
||||
"stream_stats_updated_at",
|
||||
]
|
||||
|
||||
def get_fields(self):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue