33
44This module contains all the functions for generating static proceedings pages
55'''
6- from urllib2 import urlopen
76import datetime
87import glob
8+ import httplib2
99import os
1010import re
1111import shutil
1212import subprocess
13+ import urllib2
14+ from urllib import urlencode
1315
1416import debug # pyflakes:ignore
1517
18+ from apiclient .discovery import build
1619from django .conf import settings
1720from django .core .exceptions import ObjectDoesNotExist
1821from django .http import HttpRequest
3437from ietf .utils .mail import send_mail
3538
3639AUDIO_FILE_RE = re .compile (r'ietf(?P<number>[\d]+)-(?P<room>.*)-(?P<time>[\d]{8}-[\d]{4})' )
40+ VIDEO_TITLE_RE = re .compile (r'IETF(?P<number>\d{2})-(?P<name>.*)-(?P<date>\d{8})-(?P<time>\d{4})' )
3741
3842# -------------------------------------------------
39- # Helper Functions
43+ # Recording Functions
4044# -------------------------------------------------
45+
46+ def import_youtube_video_urls (meeting , http = httplib2 .Http ()):
47+ '''Create Document and set external_url for session videos'''
48+ youtube = build (settings .YOUTUBE_API_SERVICE_NAME , settings .YOUTUBE_API_VERSION ,
49+ developerKey = settings .YOUTUBE_API_KEY , http = http )
50+ playlistid = get_youtube_playlistid (youtube , 'IETF' + meeting .number )
51+ if playlistid is None :
52+ return None
53+ for video in get_youtube_videos (youtube , playlistid ):
54+ match = VIDEO_TITLE_RE .match (video ['title' ])
55+ if match :
56+ session = _get_session (** match .groupdict ())
57+ if session :
58+ url = video ['url' ]
59+ get_or_create_recording_document (url ,session )
60+
61+ def get_youtube_playlistid (youtube , title , http = httplib2 .Http ()):
62+ '''Returns the youtube playlistId matching title string, a string'''
63+ request = youtube .search ().list (
64+ q = title ,
65+ part = 'id,snippet' ,
66+ channelId = settings .YOUTUBE_IETF_CHANNEL_ID ,
67+ type = 'playlist' ,
68+ maxResults = 1
69+ )
70+ search_response = request .execute (http = http )
71+
72+ try :
73+ playlistid = search_response ['items' ][0 ]['id' ]['playlistId' ]
74+ except (KeyError , IndexError ):
75+ return None
76+ return playlistid
77+
78+ def get_youtube_videos (youtube , playlistid , http = httplib2 .Http ()):
79+ '''Returns list of dictionaries with title, urls keys'''
80+ videos = []
81+ kwargs = dict (part = "snippet" ,playlistId = playlistid ,maxResults = 50 )
82+ playlistitems = youtube .playlistItems ()
83+ request = playlistitems .list (** kwargs )
84+ # handle pagination
85+ while request is not None :
86+ playlistitems_doc = request .execute (http = http )
87+ videos .extend (_get_urls_from_json (playlistitems_doc ))
88+ request = playlistitems .list_next (request , playlistitems_doc )
89+ return videos
90+
91+ def _get_session (number ,name ,date ,time ):
92+ '''Lookup session using data from video title'''
93+ meeting = Meeting .objects .get (number = number )
94+ schedule = meeting .agenda
95+ timeslot_time = datetime .datetime .strptime (date + time ,'%Y%m%d%H%M' )
96+ try :
97+ assignment = SchedTimeSessAssignment .objects .get (
98+ schedule = schedule ,
99+ session__group__acronym = name .lower (),
100+ timeslot__time = timeslot_time ,
101+ )
102+ except (SchedTimeSessAssignment .DoesNotExist , SchedTimeSessAssignment .MultipleObjectsReturned ):
103+ return None
104+
105+ return assignment .session
106+
107+ def _get_urls_from_json (doc ):
108+ '''Returns list of dictonary titel,url from search results'''
109+ urls = []
110+ for item in doc ['items' ]:
111+ title = item ['snippet' ]['title' ]
112+ #params = dict(v=item['snippet']['resourceId']['videoId'], list=item['snippet']['playlistId'])
113+ params = [('v' ,item ['snippet' ]['resourceId' ]['videoId' ]), ('list' ,item ['snippet' ]['playlistId' ])]
114+ url = settings .YOUTUBE_BASE_URL + '?' + urlencode (params )
115+ urls .append (dict (title = title , url = url ))
116+ return urls
117+
41118def import_audio_files (meeting ):
42119 '''
43120 Checks for audio files and creates corresponding materials (docs) for the Session
@@ -58,20 +135,9 @@ def import_audio_files(meeting):
58135 ).exclude (session__agenda_note__icontains = 'canceled' ).order_by ('timeslot__time' )
59136 if not sessionassignments :
60137 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 )
138+ url = settings .IETF_AUDIO_URL + 'ietf{}/{}' .format (meeting .number , filename )
139+ doc = get_or_create_recording_document (url ,sessionassignments [0 ].session )
140+ attach_recording (doc , [ x .session for x in sessionassignments ])
75141 else :
76142 # use for reconciliation email
77143 unmatched_files .append (filename )
@@ -98,20 +164,30 @@ def get_timeslot_for_filename(filename):
98164 except (ObjectDoesNotExist , KeyError ):
99165 return None
100166
167+ def attach_recording (doc , sessions ):
168+ '''Associate recording document with sessions'''
169+ for session in sessions :
170+ if doc not in session .materials .all ():
171+ # add document to session
172+ presentation = SessionPresentation .objects .create (
173+ session = session ,
174+ document = doc ,
175+ rev = doc .rev )
176+ session .sessionpresentation_set .add (presentation )
177+ if not doc .docalias_set .filter (name__startswith = 'recording-{}-{}' .format (session .meeting .number ,session .group .acronym )):
178+ sequence = get_next_sequence (session .group ,session .meeting ,'recording' )
179+ name = 'recording-{}-{}-{}' .format (session .meeting .number ,session .group .acronym ,sequence )
180+ doc .docalias_set .create (name = name )
181+
101182def normalize_room_name (name ):
102183 '''Returns room name converted to be used as portion of filename'''
103184 return name .lower ().replace (' ' ,'' ).replace ('/' ,'_' )
104185
105- def get_or_create_recording_document (filename ,session ):
106- meeting = session .meeting
107- url = settings .IETF_AUDIO_URL + 'ietf{}/{}' .format (meeting .number , filename )
186+ def get_or_create_recording_document (url ,session ):
108187 try :
109- doc = Document .objects .get (external_url = url )
110- return doc
188+ return Document .objects .get (external_url = url )
111189 except ObjectDoesNotExist :
112- pass
113- return create_recording (session ,url )
114-
190+ return create_recording (session ,url )
115191
116192def create_recording (session ,url ):
117193 '''
@@ -182,6 +258,10 @@ def mycomp(timeslot):
182258 key = None
183259 return key
184260
261+ # -------------------------------------------------
262+ # End Recording Functions
263+ # -------------------------------------------------
264+
185265def get_progress_stats (sdate ,edate ):
186266 '''
187267 This function takes a date range and produces a dictionary of statistics / objects for
@@ -489,7 +569,7 @@ def gen_agenda(context):
489569
490570 # get the text agenda from datatracker
491571 url = 'https://datatracker.ietf.org/meeting/%s/agenda.txt' % meeting .number
492- text = urlopen (url ).read ()
572+ text = urllib2 . urlopen (url ).read ()
493573 path = os .path .join (settings .SECR_PROCEEDINGS_DIR ,meeting .number ,'agenda.txt' )
494574 write_html (path ,text )
495575
0 commit comments