mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-17 16:50:53 +00:00
fixed potential timezone issue
This commit is contained in:
parent
8807b442db
commit
b3551fe32f
5 changed files with 98 additions and 4 deletions
|
|
@ -4,6 +4,8 @@ from apps.epg.serializers import EPGDataSerializer
|
|||
from core.models import StreamProfile
|
||||
from apps.epg.models import EPGData
|
||||
from django.urls import reverse
|
||||
from rest_framework import serializers
|
||||
from django.utils import timezone
|
||||
|
||||
class LogoSerializer(serializers.ModelSerializer):
|
||||
cache_url = serializers.SerializerMethodField()
|
||||
|
|
@ -239,3 +241,20 @@ class RecordingSerializer(serializers.ModelSerializer):
|
|||
model = Recording
|
||||
fields = '__all__'
|
||||
read_only_fields = ['task_id']
|
||||
|
||||
def validate(self, data):
|
||||
start_time = data.get('start_time')
|
||||
end_time = data.get('end_time')
|
||||
|
||||
now = timezone.now() # timezone-aware current time
|
||||
|
||||
if end_time < now:
|
||||
raise serializers.ValidationError("End time must be in the future.")
|
||||
|
||||
if start_time < now:
|
||||
# Optional: Adjust start_time if it's in the past but end_time is in the future
|
||||
data['start_time'] = now # or: timezone.now() + timedelta(seconds=1)
|
||||
if end_time <= data['start_time']:
|
||||
raise serializers.ValidationError("End time must be after start time.")
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from apps.m3u.models import M3UAccount
|
|||
from apps.epg.tasks import parse_programs_for_tvg_id
|
||||
import logging, requests, time
|
||||
from .tasks import run_recording
|
||||
from django.utils.timezone import now, is_aware, make_aware
|
||||
from datetime import timedelta
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -96,10 +98,32 @@ def revoke_old_task_on_update(sender, instance, **kwargs):
|
|||
|
||||
@receiver(post_save, sender=Recording)
|
||||
def schedule_task_on_save(sender, instance, created, **kwargs):
|
||||
if not instance.task_id and instance.start_time > now():
|
||||
task_id = schedule_recording_task(instance)
|
||||
instance.task_id = task_id
|
||||
instance.save(update_fields=['task_id'])
|
||||
try:
|
||||
if not instance.task_id:
|
||||
start_time = instance.start_time
|
||||
|
||||
# Make both datetimes aware (in UTC)
|
||||
if not is_aware(start_time):
|
||||
print("Start time was not aware, making aware")
|
||||
start_time = make_aware(start_time)
|
||||
|
||||
current_time = now()
|
||||
|
||||
# Debug log
|
||||
print(f"Start time: {start_time}, Now: {current_time}")
|
||||
|
||||
# Optionally allow slight fudge factor (1 second) to ensure scheduling happens
|
||||
if start_time > current_time - timedelta(seconds=1):
|
||||
print("Scheduling recording task!")
|
||||
task_id = schedule_recording_task(instance)
|
||||
instance.task_id = task_id
|
||||
instance.save(update_fields=['task_id'])
|
||||
else:
|
||||
print("Start time is in the past. Not scheduling.")
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print("Error in post_save signal:", e)
|
||||
traceback.print_exc()
|
||||
|
||||
@receiver(post_delete, sender=Recording)
|
||||
def revoke_task_on_delete(sender, instance, **kwargs):
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ from apps.channels.models import Channel
|
|||
from apps.epg.models import EPGData, EPGSource
|
||||
from core.models import CoreSettings
|
||||
|
||||
from channels.layers import get_channel_layer
|
||||
from asgiref.sync import async_to_sync
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from core.utils import SentenceTransformer
|
||||
|
|
@ -238,6 +241,16 @@ def run_recording(channel_id, start_time_str, end_time_str):
|
|||
duration_seconds = int((end_time - start_time).total_seconds())
|
||||
filename = f'{slugify(channel.name)}-{start_time.strftime("%Y-%m-%d_%H-%M-%S")}.mp4'
|
||||
|
||||
channel_layer = get_channel_layer()
|
||||
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
"updates",
|
||||
{
|
||||
"type": "update",
|
||||
"data": {"success": True, "type": "recording_started", "channel": channel.name}
|
||||
},
|
||||
)
|
||||
|
||||
logger.info(f"Starting recording for channel {channel.name}")
|
||||
with requests.get(f"http://localhost:5656/proxy/ts/stream/{channel.uuid}", headers={
|
||||
'User-Agent': 'Dispatcharr-DVR',
|
||||
|
|
@ -255,5 +268,13 @@ def run_recording(channel_id, start_time_str, end_time_str):
|
|||
# Write the chunk to the file
|
||||
file.write(chunk)
|
||||
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
"updates",
|
||||
{
|
||||
"type": "update",
|
||||
"data": {"success": True, "type": "recording_ended", "channel": channel.name}
|
||||
},
|
||||
)
|
||||
|
||||
# After the loop, the file and response are closed automatically.
|
||||
logger.info(f"Finished recording for channel {channel.name}")
|
||||
|
|
|
|||
|
|
@ -127,6 +127,20 @@ export const WebsocketProvider = ({ children }) => {
|
|||
setProfilePreview(event.data.search_preview, event.data.result);
|
||||
break;
|
||||
|
||||
case 'recording_started':
|
||||
notifications.show({
|
||||
title: 'Recording started!',
|
||||
message: `Started recording channel ${event.data.channel}`,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'recording_ended':
|
||||
notifications.show({
|
||||
title: 'Recording finished!',
|
||||
message: `Stopped recording channel ${event.data.channel}`,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(`Unknown websocket event type: ${event.type}`);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -158,6 +158,15 @@ export default function TVChannelGuide({ startDate, endDate }) {
|
|||
return guideChannels.find((ch) => ch.epg_data?.tvg_id === tvgId);
|
||||
}
|
||||
|
||||
const record = (program) => {
|
||||
const channel = findChannelByTvgId(program.tvg_id);
|
||||
API.createRecording({
|
||||
channel: `${channel.id}`,
|
||||
start_time: program.start_time,
|
||||
end_time: program.end_time,
|
||||
});
|
||||
};
|
||||
|
||||
// The “Watch Now” click => show floating video
|
||||
const { showVideo } = useVideoStore(); // or useVideoStore()
|
||||
function handleWatchStream(program) {
|
||||
|
|
@ -457,6 +466,13 @@ export default function TVChannelGuide({ startDate, endDate }) {
|
|||
{now.isAfter(dayjs(selectedProgram.start_time)) &&
|
||||
now.isBefore(dayjs(selectedProgram.end_time)) && (
|
||||
<Flex mih={50} gap="xs" justify="flex-end" align="flex-end">
|
||||
<Button
|
||||
variant="transparent"
|
||||
color="gray"
|
||||
onClick={() => record(selectedProgram)}
|
||||
>
|
||||
Record
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
color="gray"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue