|
7 | 7 | import datetime |
8 | 8 | import glob |
9 | 9 | import os |
| 10 | +import re |
10 | 11 | import shutil |
11 | 12 | import subprocess |
12 | 13 |
|
13 | 14 | import debug # pyflakes:ignore |
14 | 15 |
|
15 | 16 | from django.conf import settings |
| 17 | +from django.core.exceptions import ObjectDoesNotExist |
16 | 18 | from django.http import HttpRequest |
17 | 19 | from django.shortcuts import render_to_response, render |
18 | 20 | from django.db.utils import ConnectionDoesNotExist |
19 | 21 |
|
20 | | -from ietf.doc.models import Document, RelatedDocument, DocEvent, NewRevisionDocEvent, State |
| 22 | +from ietf.doc.models import Document, DocAlias, RelatedDocument, DocEvent, NewRevisionDocEvent, State |
21 | 23 | from ietf.group.models import Group, Role |
22 | 24 | from ietf.group.utils import get_charter_text |
23 | 25 | from ietf.meeting.helpers import get_schedule |
24 | | -from ietf.meeting.models import Session, Meeting, SchedTimeSessAssignment, SessionPresentation |
| 26 | +from ietf.meeting.models import Session, Meeting, SchedTimeSessAssignment, SessionPresentation, TimeSlot |
25 | 27 | from ietf.person.models import Person |
26 | 28 | from ietf.secr.proceedings.models import InterimMeeting # proxy model |
27 | 29 | from ietf.secr.proceedings.models import Registration |
28 | 30 | from ietf.secr.utils.document import get_rfc_num |
29 | 31 | from ietf.secr.utils.group import groups_by_session |
30 | 32 | from ietf.secr.utils.meeting import get_proceedings_path, get_materials, get_session |
31 | 33 | from ietf.utils.log import log |
| 34 | +from ietf.utils.mail import send_mail |
| 35 | + |
| 36 | +AUDIO_FILE_RE = re.compile(r'ietf(?P<number>[\d]+)-(?P<room>.*)-(?P<time>[\d]{8}-[\d]{4})') |
32 | 37 |
|
33 | 38 | # ------------------------------------------------- |
34 | 39 | # Helper Functions |
35 | 40 | # ------------------------------------------------- |
36 | | -def check_audio_files(group,meeting): |
| 41 | +def import_audio_files(meeting): |
37 | 42 | ''' |
38 | 43 | Checks for audio files and creates corresponding materials (docs) for the Session |
39 | | - Expects audio files in the format ietf[meeting num]-[room]-YYYMMDD-HHMM-*, |
| 44 | + Expects audio files in the format ietf[meeting num]-[room]-YYYMMDD-HHMM.*, |
40 | 45 | |
41 | | - Example: ietf90-salonb-20140721-1710-pm3.mp3 |
| 46 | + Example: ietf90-salonb-20140721-1710.mp3 |
| 47 | + ''' |
| 48 | + unmatched_files = [] |
| 49 | + path = os.path.join(settings.MEETING_RECORDINGS_DIR, meeting.type.slug + meeting.number) |
| 50 | + if not os.path.exists(path): |
| 51 | + return None |
| 52 | + for filename in os.listdir(path): |
| 53 | + timeslot = get_timeslot_for_filename(filename) |
| 54 | + if timeslot: |
| 55 | + sessionassignments = timeslot.sessionassignments.filter( |
| 56 | + schedule=timeslot.meeting.agenda, |
| 57 | + session__status='sched', |
| 58 | + ).exclude(session__agenda_note__icontains='canceled').order_by('timeslot__time') |
| 59 | + if not sessionassignments: |
| 60 | + continue |
| 61 | + doc = get_or_create_recording_document(filename,sessionassignments[0].session) |
| 62 | + for sessionassignment in sessionassignments: |
| 63 | + session = sessionassignment.session |
| 64 | + if doc not in session.materials.all(): |
| 65 | + # add document to session |
| 66 | + presentation = SessionPresentation.objects.create( |
| 67 | + session=session, |
| 68 | + document=doc, |
| 69 | + rev=doc.rev) |
| 70 | + session.sessionpresentation_set.add(presentation) |
| 71 | + if not doc.docalias_set.filter(name__startswith='recording-{}-{}'.format(meeting.number,session.group.acronym)): |
| 72 | + sequence = get_next_sequence(session.group,session.meeting,'recording') |
| 73 | + name = 'recording-{}-{}-{}'.format(session.meeting.number,session.group.acronym,sequence) |
| 74 | + doc.docalias_set.create(name=name) |
| 75 | + else: |
| 76 | + # use for reconciliation email |
| 77 | + unmatched_files.append(filename) |
42 | 78 |
|
| 79 | + if unmatched_files: |
| 80 | + send_audio_import_warning(unmatched_files) |
| 81 | + |
| 82 | + |
| 83 | +def get_timeslot_for_filename(filename): |
| 84 | + '''Returns a timeslot matching the filename given. |
| 85 | + NOTE: currently only works with ietfNN prefix (regular meetings) |
43 | 86 | ''' |
44 | | - for session in Session.objects.filter(group=group, |
45 | | - meeting=meeting, |
46 | | - status=('sched'), |
47 | | - timeslotassignments__schedule=meeting.agenda): |
48 | | - timeslot = session.official_timeslotassignment().timeslot |
49 | | - if not (timeslot.location and timeslot.time): |
50 | | - continue |
51 | | - room = timeslot.location.name.lower() |
52 | | - room = room.replace(' ','') |
53 | | - room = room.replace('/','_') |
54 | | - time = timeslot.time.strftime("%Y%m%d-%H%M") |
55 | | - filename = 'ietf{}-{}-{}*'.format(meeting.number,room,time) |
56 | | - path = os.path.join(settings.MEETING_RECORDINGS_DIR,'ietf{}'.format(meeting.number),filename) |
57 | | - for file in glob.glob(path): |
58 | | - url = 'https://www.ietf.org/audio/ietf{}/{}'.format(meeting.number,os.path.basename(file)) |
59 | | - doc = Document.objects.filter(external_url=url).first() |
60 | | - if not doc: |
61 | | - create_recording(session,url) |
| 87 | + basename, _ = os.path.splitext(filename) |
| 88 | + match = AUDIO_FILE_RE.match(basename) |
| 89 | + if match: |
| 90 | + try: |
| 91 | + meeting = Meeting.objects.get(number=match.groupdict()['number']) |
| 92 | + room_mapping = {normalize_room_name(room.name): room.name for room in meeting.room_set.all()} |
| 93 | + time = datetime.datetime.strptime(match.groupdict()['time'],'%Y%m%d-%H%M') |
| 94 | + return TimeSlot.objects.get( |
| 95 | + meeting=meeting, |
| 96 | + location__name=room_mapping[match.groupdict()['room']], |
| 97 | + time=time) |
| 98 | + except (ObjectDoesNotExist, KeyError): |
| 99 | + return None |
| 100 | + |
| 101 | +def normalize_room_name(name): |
| 102 | + '''Returns room name converted to be used as portion of filename''' |
| 103 | + return name.lower().replace(' ','').replace('/','_') |
| 104 | + |
| 105 | +def get_or_create_recording_document(filename,session): |
| 106 | + meeting = session.meeting |
| 107 | + url = settings.IETF_AUDIO_URL + 'ietf{}/{}'.format(meeting.number, filename) |
| 108 | + try: |
| 109 | + doc = Document.objects.get(external_url=url) |
| 110 | + return doc |
| 111 | + except ObjectDoesNotExist: |
| 112 | + pass |
| 113 | + return create_recording(session,url) |
62 | 114 |
|
63 | 115 |
|
64 | 116 | def create_recording(session,url): |
@@ -94,6 +146,30 @@ def create_recording(session,url): |
94 | 146 | pres = SessionPresentation.objects.create(session=session,document=doc,rev=doc.rev) |
95 | 147 | session.sessionpresentation_set.add(pres) |
96 | 148 |
|
| 149 | + return doc |
| 150 | + |
| 151 | +def get_next_sequence(group,meeting,type): |
| 152 | + ''' |
| 153 | + Returns the next sequence number to use for a document of type = type. |
| 154 | + Takes a group=Group object, meeting=Meeting object, type = string |
| 155 | + ''' |
| 156 | + aliases = DocAlias.objects.filter(name__startswith='{}-{}-{}-'.format(type,meeting.number,group.acronym)) |
| 157 | + if not aliases: |
| 158 | + return 1 |
| 159 | + aliases = aliases.order_by('name') |
| 160 | + sequence = int(aliases.last().name.split('-')[-1]) + 1 |
| 161 | + return sequence |
| 162 | + |
| 163 | +def send_audio_import_warning(unmatched_files): |
| 164 | + '''Send email to interested parties that some audio files weren't matched to timeslots''' |
| 165 | + send_mail(request = None, |
| 166 | + to = settings.AUDIO_IMPORT_EMAIL, |
| 167 | + frm = "IETF Secretariat <ietf-secretariat@ietf.org>", |
| 168 | + subject = "Audio file import warning", |
| 169 | + template = "proceedings/audio_import_warning.txt", |
| 170 | + context = dict(unmatched_files=unmatched_files), |
| 171 | + extra = {}) |
| 172 | + |
97 | 173 | def mycomp(timeslot): |
98 | 174 | ''' |
99 | 175 | This takes a timeslot object and returns a key to sort by the area acronym or None |
@@ -172,13 +248,6 @@ def get_progress_stats(sdate,edate): |
172 | 248 |
|
173 | 249 | return data |
174 | 250 |
|
175 | | -def get_next_sequence(group,meeting,type): |
176 | | - ''' |
177 | | - Returns the next sequence number to use for a document of type = type. |
178 | | - Takes a group=Group object, meeting=Meeting object, type = string |
179 | | - ''' |
180 | | - return Document.objects.filter(name__startswith='{}-{}-{}-'.format(type,meeting.number,group.acronym)).count() + 1 |
181 | | - |
182 | 251 | def write_html(path,content): |
183 | 252 | f = open(path,'w') |
184 | 253 | f.write(content) |
@@ -226,7 +295,7 @@ def create_proceedings(meeting, group, is_final=False): |
226 | 295 | if meeting.type_id == 'ietf' and int(meeting.number) < 79: |
227 | 296 | return |
228 | 297 |
|
229 | | - check_audio_files(group,meeting) |
| 298 | + #check_audio_files(group,meeting) |
230 | 299 | materials = get_materials(group,meeting) |
231 | 300 |
|
232 | 301 | chairs = group.role_set.filter(name='chair') |
|
0 commit comments