]> git.0d.be Git - django-panik-emissions.git/blob - emissions/utils.py
6f002f3558507c55945f40191a0b5a7e93e05e92
[django-panik-emissions.git] / emissions / utils.py
1 from datetime import datetime, timedelta, time
2 import os
3 from PIL import Image
4
5 def maybe_resize(image_path):
6     if not os.path.exists(image_path):
7         return
8     image = Image.open(image_path)
9     if max(image.size) > 1000:
10         # no sense storing images that large
11         factor = 1000. / max(image.size)
12         image = image.resize(
13                 (int(image.size[0]*factor), int(image.size[1]*factor)),
14                 Image.ANTIALIAS)
15         image.save(image_path)
16
17
18 def whatsonair():
19     from models import Diffusion, Schedule, Nonstop
20
21     now = datetime.now()
22     # get program of today minus a few hours, as radio days are not from
23     # midnight to midnigth but from 5am to 5am
24     program = day_program(now - timedelta(hours=Schedule.DAY_HOUR_START),
25             prefetch_sounds=False, prefetch_categories=False, include_nonstop=False)
26     program = [x for x in program if not x.datetime > now]
27
28     emission = None
29     episode = None
30     nonstop = None
31     current_slot = None
32     if program and program[-1].datetime + timedelta(minutes=program[-1].get_duration()) > now:
33         current_slot = program[-1]
34         if isinstance(current_slot, Schedule):
35             emission = current_slot.emission
36         elif isinstance(current_slot, Diffusion):
37             episode = current_slot.episode
38             emission = episode.emission
39     else:
40         for nonstop in Nonstop.objects.all():
41             if (nonstop.start < nonstop.end and (
42                     now.time() >= nonstop.start and now.time() < nonstop.end)) or \
43                (nonstop.start > nonstop.end and (
44                     now.time() >= nonstop.start or now.time() < nonstop.end)):
45                 current_slot = nonstop
46                 break
47         else:
48             nonstop = None
49
50     return {'emission': emission,
51             'episode': episode,
52             'nonstop': nonstop,
53             'current_slot': current_slot}
54
55
56 def period_program(date_start, date_end, prefetch_sounds=True,
57         prefetch_categories=True, include_nonstop=True):
58     from models import Diffusion, Schedule, Nonstop, WeekdayMixin, SoundFile, Absence
59
60     diffusions = Diffusion.objects.select_related().filter(
61             datetime__range=(date_start, date_end)).order_by('datetime')
62     if prefetch_categories:
63         diffusions = diffusions.prefetch_related('episode__emission__categories')
64     diffusions = [x for x in diffusions if x.datetime >= date_start and
65                     x.datetime < date_end]
66
67     if prefetch_sounds:
68         soundfiles = {}
69         for soundfile in SoundFile.objects.select_related().filter(podcastable=True,
70                 fragment=False, episode__in=[x.episode for x in diffusions]):
71             soundfiles[soundfile.episode_id] = soundfile
72
73         for diffusion in diffusions:
74             diffusion.episode.main_sound = soundfiles.get(diffusion.episode.id)
75
76     # the secondary sortkey puts schedules that happens everyweek after
77     # specific ones, this will be useful later on, when multiple schedules
78     # happen at the same time and we have to remove the least specific.
79     period_schedules = Schedule.objects.select_related().order_by('datetime', 'weeks')
80     if prefetch_categories:
81         period_schedules = period_schedules.prefetch_related('emission__categories')
82
83     program = []
84     current_date = date_start
85     while current_date < date_end:
86         week_day = current_date.weekday()
87         week_no = ((current_date.day-1) // 7)
88         day_schedules = [x for x in period_schedules if x.get_weekday() == week_day and x.match_week(week_no)]
89         for schedule in day_schedules:
90             schedule.datetime = datetime(
91                     current_date.year, current_date.month, current_date.day,
92                     schedule.datetime.hour, schedule.datetime.minute) + \
93                             timedelta(days=schedule.datetime.weekday()-current_date.weekday())
94             if week_day == 6 and schedule.datetime.weekday() == 0:
95                 # on Sundays we can have Sunday->Monday night programming, and
96                 # we need to get them on the right date.
97                 schedule.datetime += timedelta(days=7)
98         program.extend(day_schedules)
99         current_date += timedelta(days=1)
100
101     absences = {}
102     for absence in Absence.objects.filter(datetime__range=(date_start, date_end)):
103         absences[absence.datetime] = True
104
105     for i, schedule in enumerate(program):
106         if schedule is None:
107             continue
108
109         # look for a diffusion matching this schedule
110         d = [x for x in diffusions if x.datetime.timetuple()[:5] == schedule.datetime.timetuple()[:5]]
111         if d:
112             diffusions.remove(d[0])
113             program[i] = d[0]
114             for j, other_schedule in enumerate(program[i+1:]):
115                 # remove other emissions scheduled at the same time
116                 if other_schedule.datetime.timetuple()[:5] == schedule.datetime.timetuple()[:5]:
117                     program[i+1+j] = None
118                 else:
119                     break
120
121         if schedule.datetime in absences and isinstance(program[i], Schedule):
122             program[i] = None
123             continue
124
125     # here we are with remaining diffusions, those that were not overriding a
126     # planned schedule
127     for diffusion in diffusions:
128         program = [x for x in program if x is not None]
129         try:
130             just_before_program = [x for x in program if x.datetime < diffusion.datetime][-1]
131             new_diff_index = program.index(just_before_program)+1
132         except IndexError:
133             # nothing before
134             new_diff_index = 0
135         program.insert(new_diff_index, diffusion)
136
137         # cut (or even remove) programs that started earlier but continued over
138         # this program start time
139         i = new_diff_index
140         while i > 0:
141             previous = program[i-1]
142             previous_endtime = previous.datetime + timedelta(minutes=previous.get_duration())
143             if previous_endtime > diffusion.datetime:
144                 previous.duration = (diffusion.datetime - previous.datetime).seconds / 60
145                 if previous.duration <= 0:
146                     program[i-1] = None
147             i -= 1
148
149         # push back (or remove) programs that started before this program ends
150         # (this may be unnecessary as next step does that again for all
151         # programs)
152         diffusion_endtime = diffusion.datetime + timedelta(minutes=diffusion.get_duration())
153         i = new_diff_index
154         while i < len(program)-1:
155             next_prog = program[i+1]
156             if next_prog.datetime < diffusion_endtime:
157                 diff = diffusion_endtime - next_prog.datetime
158                 if (diff.seconds/60) >= next_prog.get_duration():
159                     program[i+1] = None
160                 else:
161                     next_prog.datetime = diffusion_endtime
162                     next_prog.duration = next_prog.get_duration() - (diff.seconds/60)
163             i += 1
164
165     # remove overlapping programs
166     program = [x for x in program if x is not None]
167     for i, slot in enumerate(program):
168         if slot is None:
169             continue
170
171         slot_end = slot.datetime + timedelta(minutes=slot.get_duration())
172
173         j = i+1
174         while j < len(program)-1:
175             if program[j]:
176                 if slot_end > program[j].datetime:
177                     program[j] = None
178             j += 1
179
180     program = [x for x in program if x is not None]
181
182     if not include_nonstop:
183         return program
184
185     # last step is adding nonstop zones between slots
186     nonstops = list(Nonstop.objects.all().order_by('start'))
187     nonstops = [x for x in nonstops if x.start != x.end]
188     dawn = time(Schedule.DAY_HOUR_START, 0)
189     first_of_the_day = [x for x in nonstops if x.start <= dawn][-1]
190     nonstops = nonstops[nonstops.index(first_of_the_day):] + nonstops[:nonstops.index(first_of_the_day)]
191
192     class NonstopSlot(WeekdayMixin):
193         def __init__(self, nonstop, dt):
194             self.datetime = dt
195             self.title = nonstop.title
196             self.slug = nonstop.slug
197             self.label = self.title
198             self.nonstop = nonstop
199
200         def __repr__(self):
201             return '<Nonstop Slot %r>' % self.label
202
203         def get_duration(self):
204             # fake duration
205             return 0
206
207         @classmethod
208         def get_serie(cls, start, end):
209             cells = []
210             dt = None
211             delta_day = 0
212             last_added_end = None
213             for nonstop in nonstops:
214                 nonstop_day_start = start.replace(hour=nonstop.start.hour, minute=nonstop.start.minute)
215                 nonstop_day_end = start.replace(hour=nonstop.end.hour, minute=nonstop.end.minute)
216                 nonstop_day_start += timedelta(days=delta_day)
217                 nonstop_day_end += timedelta(days=delta_day)
218                 if nonstop.start > nonstop.end:
219                     nonstop_day_end += timedelta(days=1)
220
221                 if nonstop_day_start < end and nonstop_day_end > start:
222                     if dt is None:
223                         dt = start
224                     else:
225                         dt = nonstop_day_start
226                     cells.append(cls(nonstop, dt))
227                     last_added_end = nonstop_day_end
228                     if nonstop.start > nonstop.end:
229                         # we just added a midnight crossing slot, future slots
230                         # should be one day later.
231                         delta_day += 1
232                     if nonstop.start < dawn and nonstop.end > dawn and start.time() < dawn:
233                         dt = dt.replace(hour=dawn.hour, minute=dawn.minute)
234                         cells.append(cls(nonstop, dt))
235
236             cells.sort(key=lambda x: x.datetime)
237             if last_added_end and last_added_end < end:
238                 # we used all nonstop slots and we still did not reach the end;
239                 # let's go for one more turn.
240                 cells.extend(cls.get_serie(last_added_end, end))
241             return cells
242
243
244     first_day_start = datetime(*date_start.replace(hour=6, minute=0).timetuple()[:5])
245     last_day_end = datetime(*date_end.replace(hour=5, minute=0).timetuple()[:5])
246
247     if program:
248         program[0:0] = NonstopSlot.get_serie(first_day_start, program[0].datetime)
249
250     i = 0
251     while i < len(program)-1:
252         slot = program[i]
253         if not isinstance(slot, NonstopSlot):
254             slot_end = slot.datetime + timedelta(minutes=slot.get_duration())
255             next_slot = program[i+1]
256
257             if slot_end < next_slot.datetime:
258                 next_day_start = next_slot.datetime.replace(hour=5, minute=0)
259                 if slot_end < next_day_start and next_slot.datetime > next_day_start:
260                     nonstop_day_slots = NonstopSlot.get_serie(slot_end, next_day_start)
261                     nonstop_next_day_slots = NonstopSlot.get_serie(next_day_start, next_slot.datetime)
262                     if nonstop_day_slots and nonstop_next_day_slots and \
263                             nonstop_day_slots[-1].label == nonstop_next_day_slots[0].label:
264                         nonstop_next_day_slots = nonstop_next_day_slots[1:]
265                     program[i+1:i+1] = nonstop_day_slots + nonstop_next_day_slots
266                 else:
267                     program[i+1:i+1] = NonstopSlot.get_serie(slot_end, next_slot.datetime)
268
269         i += 1
270
271     if program:
272         program.extend(
273                 NonstopSlot.get_serie(program[-1].datetime +
274                     timedelta(minutes=program[-1].get_duration()),
275                 last_day_end))
276
277     return program
278
279 def day_program(date, prefetch_sounds=True, prefetch_categories=True,
280         include_nonstop=True):
281     date_start = datetime(*date.timetuple()[:3])
282     date_end = date_start + timedelta(days=1)
283     return period_program(date_start, date_end,
284             prefetch_sounds=prefetch_sounds,
285             prefetch_categories=prefetch_categories,
286             include_nonstop=include_nonstop)