1 from datetime import datetime, timedelta, time
7 from django.utils.encoding import force_text
10 def get_duration(filename):
11 p = subprocess.Popen(['mediainfo', '--Inform=Audio;%Duration%', filename],
13 stdin=subprocess.PIPE,
14 stdout=subprocess.PIPE,
15 stderr=subprocess.PIPE)
16 stdout, stderr = p.communicate()
18 return int(stdout) / 1000
23 p = subprocess.Popen(['soxi', filename], close_fds=True,
24 stdin=subprocess.PIPE,
25 stdout=subprocess.PIPE,
26 stderr=subprocess.PIPE)
27 stdout, stderr = p.communicate()
28 for line in stdout.splitlines():
29 line = force_text(line)
30 if not line.startswith('Duration'):
33 hours, minutes, seconds, cs = re.findall(r'(\d\d):(\d\d):(\d\d)\.(\d\d)', line)[0]
36 return int(hours) * 3600 + int(minutes) * 60 + int(seconds) + (int(cs) / 100)
40 def maybe_resize(image_path):
41 if not os.path.exists(image_path):
43 image = Image.open(image_path)
44 if max(image.size) > 1000:
45 # no sense storing images that large
46 factor = 1000. / max(image.size)
48 (int(image.size[0]*factor), int(image.size[1]*factor)),
50 image.save(image_path)
54 from .models import Diffusion, Schedule, Nonstop
57 # get program of today minus a few hours, as radio days are not from
58 # midnight to midnigth but from 5am to 5am
59 program = day_program(now - timedelta(hours=Schedule.DAY_HOUR_START) + timedelta(minutes=Schedule.DAY_MINUTE_START),
60 prefetch_sounds=False, prefetch_categories=False, include_nonstop=False)
61 program = [x for x in program if not x.datetime > now]
67 if program and program[-1].datetime + timedelta(minutes=program[-1].get_duration()) > now:
68 current_slot = program[-1]
69 if isinstance(current_slot, Schedule):
70 emission = current_slot.emission
71 elif isinstance(current_slot, Diffusion):
72 episode = current_slot.episode
73 emission = episode.emission
75 for nonstop in Nonstop.objects.all():
76 if (nonstop.start < nonstop.end and (
77 now.time() >= nonstop.start and now.time() < nonstop.end)) or \
78 (nonstop.start > nonstop.end and (
79 now.time() >= nonstop.start or now.time() < nonstop.end)):
80 current_slot = nonstop
85 return {'emission': emission,
88 'current_slot': current_slot}
91 def period_program(date_start, date_end, prefetch_sounds=True,
92 prefetch_categories=True, include_nonstop=True):
93 from .models import Diffusion, Schedule, Nonstop, WeekdayMixin, SoundFile, Absence
95 diffusions = Diffusion.objects.select_related().filter(
96 datetime__range=(date_start, date_end)).order_by('datetime')
97 if prefetch_categories:
98 diffusions = diffusions.prefetch_related('episode__emission__categories')
99 diffusions = [x for x in diffusions if x.datetime >= date_start and
100 x.datetime < date_end]
104 for soundfile in SoundFile.objects.select_related().filter(podcastable=True,
105 fragment=False, episode__in=[x.episode for x in diffusions]):
106 soundfiles[soundfile.episode_id] = soundfile
108 for diffusion in diffusions:
109 diffusion.episode.main_sound = soundfiles.get(diffusion.episode.id)
111 # the secondary sortkey puts schedules that happens everyweek after
112 # specific ones, this will be useful later on, when multiple schedules
113 # happen at the same time and we have to remove the least specific.
114 period_schedules = Schedule.objects.select_related().order_by('datetime', 'weeks')
115 if prefetch_categories:
116 period_schedules = period_schedules.prefetch_related('emission__categories')
119 current_date = date_start
120 while current_date < date_end:
121 week_day = current_date.weekday()
122 week_no = ((current_date.day-1) // 7)
123 day_schedules = [x for x in period_schedules if x.get_weekday() == week_day and x.match_week(week_no)]
124 for schedule in day_schedules:
125 schedule.datetime = datetime(
126 current_date.year, current_date.month, current_date.day,
127 schedule.datetime.hour, schedule.datetime.minute) + \
128 timedelta(days=schedule.datetime.weekday()-current_date.weekday())
129 if week_day == 6 and schedule.datetime.weekday() == 0:
130 # on Sundays we can have Sunday->Monday night programming, and
131 # we need to get them on the right date.
132 schedule.datetime += timedelta(days=7)
133 program.extend(day_schedules)
134 current_date += timedelta(days=1)
137 for absence in Absence.objects.filter(datetime__range=(date_start, date_end)):
138 absences[absence.datetime] = True
140 for i, schedule in enumerate(program):
144 # look for a diffusion matching this schedule
145 d = [x for x in diffusions if x.datetime.timetuple()[:5] == schedule.datetime.timetuple()[:5]]
147 diffusions.remove(d[0])
149 for j, other_schedule in enumerate(program[i+1:]):
150 # remove other emissions scheduled at the same time
151 if other_schedule.datetime.timetuple()[:5] == schedule.datetime.timetuple()[:5]:
152 program[i+1+j] = None
156 if schedule.datetime in absences and isinstance(program[i], Schedule):
160 # here we are with remaining diffusions, those that were not overriding a
162 for diffusion in diffusions:
163 program = [x for x in program if x is not None]
165 just_before_program = [x for x in program if x.datetime < diffusion.datetime][-1]
166 new_diff_index = program.index(just_before_program)+1
170 program.insert(new_diff_index, diffusion)
172 # cut (or even remove) programs that started earlier but continued over
173 # this program start time
176 previous = program[i-1]
177 previous_endtime = previous.datetime + timedelta(minutes=previous.get_duration())
178 if previous_endtime > diffusion.datetime:
179 previous.duration = (diffusion.datetime - previous.datetime).seconds / 60
180 if previous.duration <= 0:
184 # push back (or remove) programs that started before this program ends
185 # (this may be unnecessary as next step does that again for all
187 diffusion_endtime = diffusion.datetime + timedelta(minutes=diffusion.get_duration())
189 while i < len(program)-1:
190 next_prog = program[i+1]
191 if next_prog.datetime < diffusion_endtime:
192 diff = diffusion_endtime - next_prog.datetime
193 if (diff.seconds/60) >= next_prog.get_duration():
196 next_prog.datetime = diffusion_endtime
197 next_prog.duration = next_prog.get_duration() - (diff.seconds/60)
200 # remove overlapping programs
201 program = [x for x in program if x is not None]
202 for i, slot in enumerate(program):
206 slot_end = slot.datetime + timedelta(minutes=slot.get_duration())
209 while j < len(program)-1:
211 if slot_end > program[j].datetime:
215 program = [x for x in program if x is not None]
217 if not include_nonstop:
220 # last step is adding nonstop zones between slots
221 nonstops = list(Nonstop.objects.all().order_by('start'))
222 nonstops = [x for x in nonstops if x.start != x.end]
223 dawn = time(Schedule.DAY_HOUR_START, Schedule.DAY_MINUTE_START)
225 first_of_the_day = [x for x in nonstops if x.start <= dawn][-1]
226 nonstops = nonstops[nonstops.index(first_of_the_day):] + nonstops[:nonstops.index(first_of_the_day)]
230 class NonstopSlot(WeekdayMixin):
231 def __init__(self, nonstop, dt):
233 self.title = nonstop.title
234 self.slug = nonstop.slug
235 self.label = self.title
236 self.nonstop = nonstop
239 return '<Nonstop Slot %r>' % self.label
241 def get_duration(self):
246 def get_serie(cls, start, end):
250 last_added_end = None
251 for nonstop in nonstops:
252 nonstop_day_start = start.replace(hour=nonstop.start.hour, minute=nonstop.start.minute)
253 nonstop_day_end = start.replace(hour=nonstop.end.hour, minute=nonstop.end.minute)
254 nonstop_day_start += timedelta(days=delta_day)
255 nonstop_day_end += timedelta(days=delta_day)
256 if nonstop.start > nonstop.end:
257 nonstop_day_end += timedelta(days=1)
259 if nonstop_day_start < end and nonstop_day_end > start:
263 dt = nonstop_day_start
264 cells.append(cls(nonstop, dt))
265 last_added_end = nonstop_day_end
266 if nonstop.start > nonstop.end:
267 # we just added a midnight crossing slot, future slots
268 # should be one day later.
270 if nonstop.start < dawn and nonstop.end > dawn and start.time() < dawn:
271 dt = dt.replace(hour=dawn.hour, minute=dawn.minute)
272 cells.append(cls(nonstop, dt))
274 cells.sort(key=lambda x: x.datetime)
275 if last_added_end and last_added_end < end:
276 # we used all nonstop slots and we still did not reach the end;
277 # let's go for one more turn.
278 cells.extend(cls.get_serie(last_added_end, end))
282 first_day_start = datetime(*date_start.replace(hour=6, minute=0).timetuple()[:5])
283 last_day_end = datetime(*date_end.replace(hour=5, minute=0).timetuple()[:5])
286 program[0:0] = NonstopSlot.get_serie(first_day_start, program[0].datetime)
289 while i < len(program)-1:
291 if not isinstance(slot, NonstopSlot):
292 slot_end = slot.datetime + timedelta(minutes=slot.get_duration())
293 next_slot = program[i+1]
295 if slot_end < next_slot.datetime:
296 next_day_start = next_slot.datetime.replace(hour=5, minute=0)
297 if slot_end < next_day_start and next_slot.datetime > next_day_start:
298 nonstop_day_slots = NonstopSlot.get_serie(slot_end, next_day_start)
299 nonstop_next_day_slots = NonstopSlot.get_serie(next_day_start, next_slot.datetime)
300 if nonstop_day_slots and nonstop_next_day_slots and \
301 nonstop_day_slots[-1].label == nonstop_next_day_slots[0].label:
302 nonstop_next_day_slots = nonstop_next_day_slots[1:]
303 program[i+1:i+1] = nonstop_day_slots + nonstop_next_day_slots
305 program[i+1:i+1] = NonstopSlot.get_serie(slot_end, next_slot.datetime)
311 NonstopSlot.get_serie(program[-1].datetime +
312 timedelta(minutes=program[-1].get_duration()),
317 def day_program(date, prefetch_sounds=True, prefetch_categories=True,
318 include_nonstop=True):
319 date_start = datetime(*date.timetuple()[:3])
320 date_end = date_start + timedelta(days=1)
321 return period_program(date_start, date_end,
322 prefetch_sounds=prefetch_sounds,
323 prefetch_categories=prefetch_categories,
324 include_nonstop=include_nonstop)