Skip to content

Commit db2c445

Browse files
committed
Add Youtube Code
1 parent 067056f commit db2c445

14 files changed

+434
-191
lines changed

code/scheduler/clerk_emails.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
3+
}

code/scheduler/monitor.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/python3
2+
import datetime
3+
import json
4+
5+
import googleapiclient.discovery
6+
from google.oauth2.credentials import Credentials
7+
from googleapiclient.http import MediaFileUpload
8+
9+
from smtplib import SMTP_SSL
10+
from email.message import EmailMessage
11+
12+
NOW = datetime.datetime.now(tz=datetime.timezone.utc)
13+
with open('/opt/webcast/recurring.json') as f:
14+
RECURRING = json.load(f)
15+
16+
17+
def convert_time(time: str):
18+
if not time:
19+
return ''
20+
21+
return datetime.datetime.fromisoformat(time.replace('Z', '-00:00'))
22+
23+
stream_ids = {
24+
"wardname": "STREAM_ID_FROM_GOOGLE_API"
25+
}
26+
27+
for ward in stream_ids.keys():
28+
creds = Credentials.from_authorized_user_file(f'/opt/webcast/auth/{ward}.json')
29+
youtube = googleapiclient.discovery.build("youtube", "v3", credentials=creds)
30+
31+
broadcasts = youtube.liveBroadcasts().list(
32+
part="id,snippet,contentDetails,status",
33+
mine=True,
34+
maxResults=50
35+
).execute()
36+
37+
for broadcast in broadcasts['items']:
38+
if not broadcast['snippet'].get('scheduledStartTime', None):
39+
youtube.liveBroadcasts().delete(id=broadcast['id']).execute()
40+
continue
41+
42+
endTime = convert_time(broadcast['snippet'].get('scheduledEndTime', ''))
43+
if not endTime:
44+
endTime = convert_time(broadcast['snippet']['scheduledStartTime']) + datetime.timedelta(hours=1)
45+
46+
if NOW > endTime and broadcast['status']['lifeCycleStatus'] != 'complete':
47+
stream_id = stream_ids.get(ward, None)
48+
stream = youtube.liveStreams().list(
49+
part="id,status",
50+
id=stream_id
51+
).execute()['items'][0]
52+
if (stream['status']['streamStatus'] == 'inactive') or (NOW > endTime + datetime.timedelta(minutes=15)):
53+
youtube.liveBroadcasts().transition(part='id', id=broadcast['id'], broadcastStatus='complete').execute()
54+
print(f"Marked broadcast for {ward} complete ({broadcast['id']})")
55+
print(f"Reason: ", end='')
56+
print(f"Stream status ({stream['status']['streamStatus']})" if stream['status']['streamStatus'] == 'inactive' else "Time exceeded 15 minutes past end time")
57+
58+
if broadcast['status']['lifeCycleStatus'] == 'complete' and datetime.datetime.now().day != endTime.day:
59+
with open('/opt/webcast/clerk_emails.json') as f:
60+
clerks = json.load(f)
61+
viewers = round(int(youtube.videos().list(part='statistics', id=broadcast['id']).execute()['items'][0]['statistics']['viewCount']) * 2.1)
62+
msg = EmailMessage()
63+
msg["Subject"] = "Online YouTube Meeting Attendance"
64+
msg["From"] = "Webcast <[email protected]>"
65+
msg["To"] = clerks[ward]
66+
msg.set_content(f"The number of individuals watching {broadcast['snippet']['title']} online on {convert_time(broadcast['snippet']['scheduledStartTime']).strftime('%d %b %Y')} was {viewers}.\n\n\nThis is an automated message. Please do not respond.")
67+
with SMTP_SSL('SMTP_SERVER_HERE', 465) as smtp:
68+
smtp.login('[email protected]', 'ITS_PASSWORD')
69+
smtp.send_message(msg)
70+
smtp.quit()
71+
youtube.liveBroadcasts().delete(id=broadcast['id']).execute()
72+
print(f"Deleted broadcast on {ward} ({broadcast['id']})")
73+
74+
if broadcast['contentDetails'].get("boundStreamId", "") != stream_ids.get(ward, "NOT FOUND"):
75+
youtube.liveBroadcasts().bind(
76+
id=broadcast['id'],
77+
part="id",
78+
streamId=stream_ids.get(ward, '')
79+
).execute()
80+
print(f"Attempted to bind stream to broadcast for {ward}")
81+
82+
for event in RECURRING[ward]:
83+
changeInDays = event['day'] - datetime.datetime.now().weekday()
84+
if changeInDays < 0:
85+
changeInDays += 7
86+
recurringDate = datetime.datetime.now() + datetime.timedelta(days=changeInDays)
87+
startTime = datetime.datetime.combine(recurringDate, datetime.time(*[int(x) for x in event['start'].split(':')]))
88+
endTime = datetime.datetime.combine(recurringDate, datetime.time(*[int(x) for x in event['end'].split(':')]))
89+
startTime = startTime.replace(tzinfo=NOW.astimezone().tzinfo)
90+
endTime = endTime.replace(tzinfo=NOW.astimezone().tzinfo)
91+
92+
if startTime not in [convert_time(x['snippet']['scheduledStartTime']) for x in broadcasts['items']] and startTime > NOW:
93+
new = youtube.liveBroadcasts().insert(
94+
part='id,snippet,contentDetails,status',
95+
body={
96+
'snippet': {
97+
'title': event['title'],
98+
'scheduledStartTime': startTime.isoformat(),
99+
'scheduledEndTime': endTime.isoformat(),
100+
},
101+
'contentDetails': {
102+
'enableAutoStart': 'true',
103+
},
104+
'status': {
105+
'privacyStatus': 'public',
106+
'selfDeclaredMadeForKids': 'true',
107+
},
108+
}
109+
).execute()
110+
youtube.liveBroadcasts().bind(
111+
id=new['id'],
112+
part="id",
113+
streamId=stream_ids.get(ward, '')
114+
).execute()
115+

code/scheduler/recurring.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"wardname": [
3+
{
4+
"title": "MEETING NAME HERE",
5+
"start": "10:00",
6+
"end": "11:00",
7+
"day": 6
8+
}
9+
]
10+
}

code/webcast/Thumbs.db

8 KB
Binary file not shown.

code/webcast/api/app.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,75 @@ def pred(*args, **kwargs):
2929
@app.route('/auth')
3030
@auth
3131
def authenticate():
32+
print("/auth called")
3233
return {}
3334

3435
import psutil
3536
import time
3637
@app.route('/pi/stats')
3738
@auth
3839
def pi_stats():
40+
print("/pi/stats called")
3941
net = psutil.net_io_counters()
4042
return {'timestamp': time.time(), 'cpu': psutil.cpu_percent(), 'memory': psutil.virtual_memory().percent, 'net_sent': net.bytes_sent, 'net_recv': net.bytes_recv}
4143

4244

45+
@app.route('/webcast/pause/status')
46+
@auth
47+
def webcast_pause_status():
48+
with open('/etc/webcast/webcast.conf') as f:
49+
conf = json.load(f)
50+
return {'paused': conf.get('shouldbepaused', False)}
51+
52+
53+
@app.route('/webcast/pause')
54+
@auth
55+
def webcast_pause():
56+
with open('/etc/webcast/webcast.conf') as f:
57+
conf = json.load(f)
58+
conf['shouldbepaused'] = True
59+
with open('/etc/webcast/webcast.conf', 'w') as f:
60+
json.dump(conf, f)
61+
os.system('sudo systemctl start webcast.service')
62+
return {'paused': True}
63+
64+
65+
@app.route('/webcast/resume')
66+
@auth
67+
def webcast_resume():
68+
with open('/etc/webcast/webcast.conf') as f:
69+
conf = json.load(f)
70+
conf['shouldbepaused'] = False
71+
with open('/etc/webcast/webcast.conf', 'w') as f:
72+
json.dump(conf, f)
73+
os.system('sudo systemctl start webcast.service')
74+
return {'paused': False}
75+
76+
4377
@app.route('/webcast/stop')
4478
@auth
4579
def webcast_stop():
46-
os.system('sudo pkill ffmpeg')
80+
print("/webcast/stop called")
4781
os.system('sudo systemctl stop webcast.timer')
82+
os.system('sudo systemctl stop webcast.service')
83+
os.system('sudo pkill ffmpeg')
4884
output = os.popen('sudo systemctl status webcast.service').read()
4985
return {'response': output}
5086

5187
@app.route('/webcast/start')
5288
@auth
5389
def webcast_start():
90+
print("/webcast/start called")
5491
os.system('sudo systemctl start webcast.timer')
92+
os.system('sudo systemctl start webcast.service')
5593
output = os.popen('sudo systemctl status webcast.service').read()
5694
return {'response': output}
5795

5896

5997
@app.route('/webcast/restart')
6098
@auth
6199
def webcast_restart():
100+
print("/webcast/restart called")
62101
os.system('sudo pkill ffmpeg')
63102
os.system('sudo systemctl restart webcast.service')
64103
output = os.popen('sudo systemctl status webcast.service').read()
@@ -67,6 +106,7 @@ def webcast_restart():
67106
@app.route('/webcast/status')
68107
@auth
69108
def webcast_status():
109+
print("/webcast/status called")
70110
output = os.popen('sudo systemctl status webcast.service').read()
71111
return {'response': output}
72112

@@ -75,6 +115,8 @@ def webcast_status():
75115
@auth
76116
@pi_stopped
77117
def webcast_settings():
118+
print("/webcast/settings called")
119+
print("Method:", request.method)
78120
with open('/etc/webcast/webcast.conf') as f:
79121
conf = json.load(f)
80122

code/webcast/auth/wardname.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"json": "DOWNLOADED FROM GOOGLE API CONSOLE"}

code/webcast/sacrament.png

206 KB
Loading

code/webcast/webcast-monitor.py

+11-27
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,15 @@
11
#!/usr/bin/python3
2+
import datetime
3+
import os
24

3-
import json
4-
from os import path, system
5-
import requests
5+
output = os.popen('sudo systemctl status webcast.timer').read()
6+
output = output.split('\n')
7+
output = [x for x in output if 'Active:' in x][0]
8+
output = output.split()
9+
dead_since = datetime.datetime.strptime(f'{output[5]} {output[6]}', '%Y-%m-%d %H:%M:%S')
610

7-
try:
8-
with open('/etc/webcast/webcast.key') as f:
9-
old_key = f.read()
10-
except FileNotFoundError:
11-
old_key = ''
12-
13-
if not path.isfile('/etc/webcast/webcast.conf'):
14-
exit(1)
15-
16-
with open('/etc/webcast/webcast.conf') as f:
17-
config = json.load(f)
18-
19-
if config.get('auto', 'false') != 'true':
20-
exit(1)
21-
22-
url = config.get('url')
23-
xml = requests.get(url).text
24-
25-
_, key = xml.split('<stream>')
26-
key, _ = key.split('</stream>')
27-
28-
if key != old_key:
29-
system(f'echo {key} > /etc/webcast/webcast.key')
30-
system('sudo systemctl start webcast.timer')
11+
print(f"Webcast has been {output[1]} since {dead_since}")
3112

13+
if datetime.datetime.utcnow() > dead_since + datetime.timedelta(hours=1) and output[1] == 'inactive':
14+
print("Restarting timer.")
15+
os.system("sudo systemctl start webcast.timer")

code/webcast/webcast.conf

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"auto":"true","url":"https://raw.githubusercontent.com/ChickenDevs/webcast/main/test-off.xml","rtmpurl":"","rtmpkey":"","vbitrate":1250000,"abitrate":48000,"resolution":"1280x720","videoDelay":0,"audioDelay":1,"volume":0,"preset":"veryfast"}
1+
{"auto":"false","url":"https://raw.githubusercontent.com/ChickenDevs/webcast/main/test-off.xml","rtmpurl":"RTMPURL_HERE","rtmpkey":"RTMPKEY_HERE","vbitrate":1250000,"abitrate":48000,"resolution":"1920x1080","videoDelay":0,"audioDelay":0.77,"volume":0,"preset":"superfast","shouldbepaused":false,"wards":["wardname"]}

0 commit comments

Comments
 (0)