Commit f6acbc30 authored by I.K Seneviratne's avatar I.K Seneviratne

Merge remote-tracking branch 'origin/QA_RELEASE' into monitoring_student_behavior_IT17138000

parents e3c708cb f0e57887
...@@ -10,6 +10,7 @@ from rest_framework.views import APIView ...@@ -10,6 +10,7 @@ from rest_framework.views import APIView
from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.parsers import MultiPartParser, FormParser
from . import record from . import record
from . import test as t
from rest_framework.views import * from rest_framework.views import *
...@@ -171,3 +172,29 @@ class InitiateLecture(APIView): ...@@ -171,3 +172,29 @@ class InitiateLecture(APIView):
return Response({ return Response({
"response": "success" "response": "success"
}) })
class stopRecording(APIView):
def get(self, request):
t.isStop = 1
return Response({
"response": "stopped"
})
def post(self, request):
pass
# test method (delete later)
class TestAPI(APIView):
def get(self, request):
t.isStop = 0
param = request.query_params.get('param')
# t.test()
t.IPWebcamTest()
return Response({
"response": "started"
})
def post(self, request):
pass
\ No newline at end of file
...@@ -23,15 +23,12 @@ maskNet = load_model(os.path.join(settings.BASE_DIR,'face_detector/mask_detector ...@@ -23,15 +23,12 @@ maskNet = load_model(os.path.join(settings.BASE_DIR,'face_detector/mask_detector
class IPWebCam(object): class IPWebCam(object):
def __init__(self): def __init__(self):
self.url = "http://192.168.8.100:8080/shot.jpg" self.url = "http://192.168.8.103:8080/shot.jpg"
self._count = 0
def __del__(self): def __del__(self):
cv2.destroyAllWindows() cv2.destroyAllWindows()
def get_frame(self): def get_frame(self):
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 480))
imgResp = urllib.request.urlopen(self.url) imgResp = urllib.request.urlopen(self.url)
imgNp = np.array(bytearray(imgResp.read()),dtype=np.uint8) imgNp = np.array(bytearray(imgResp.read()),dtype=np.uint8)
img= cv2.imdecode(imgNp,-1) img= cv2.imdecode(imgNp,-1)
...@@ -46,9 +43,6 @@ class IPWebCam(object): ...@@ -46,9 +43,6 @@ class IPWebCam(object):
frame_flip = cv2.flip(resize,1) frame_flip = cv2.flip(resize,1)
ret, jpeg = cv2.imencode('.jpg', frame_flip) ret, jpeg = cv2.imencode('.jpg', frame_flip)
# capture frame and save on a given time in order to run the face recognition
sleep(3); cv2.imwrite("%d.jpg" % self._count, img)
self._count =+1
return jpeg.tobytes() return jpeg.tobytes()
......
...@@ -43,6 +43,50 @@ function toggleLectureLive() { ...@@ -43,6 +43,50 @@ function toggleLectureLive() {
y.style.display = "none"; y.style.display = "none";
} }
} }
var timer = false;
//this is a test function
function testAPI() {
timer = true
startTimer()
let param = 'sachith';
//call the API
fetch('http://127.0.0.1:8000/attendance/test-api/?param=' + param)
.then((res) => res.json())
.then((out) => {})
.catch((err) => alert('error: ' + err));
}
var time = 'time';
function f() {
let param = 'sachith';
//call the API
fetch('http://127.0.0.1:8000/attendance/stop-api/?param=' + param)
.then((res) => res.json())
.then((out) => {
timer = false
startTimer();
})
.catch((err) => alert('error: ' + err));
}
function startTimer() {
var min = 0;
var seconds = 0;
if (timer) {
var sec = 0;
function pad ( val ) { return val > 9 ? val : "0" + val; }
setInterval( function(){
min = pad(parseInt(sec/60,10));
seconds = pad(++sec%60)
document.getElementById("seconds").innerHTML=pad(++sec%60);
document.getElementById("minutes").innerHTML=pad(parseInt(sec/60,10));
}, 1000);
} else {
document.getElementById("secondsStop").innerHTML=seconds;
document.getElementById("minutesStop").innerHTML=min;
}
}
</script> </script>
{% endblock %} {% endblock %}
...@@ -60,13 +104,21 @@ function toggleLectureLive() { ...@@ -60,13 +104,21 @@ function toggleLectureLive() {
<div class="card-body"> <div class="card-body">
<button type="button" class="btn btn-success" id="initiate_btn" onclick="toggleLectureLive()">Show Live Stream</button> <button type="button" class="btn btn-success" id="initiate_btn" onclick="toggleLectureLive()">Show Live Stream</button>
{# <button type="button" class="btn btn-success" id="test_btn" onclick="testAPI()">Test</button>#}
</div> </div>
<span id="minutes"></span>:<span id="seconds"></span>
<span id="minutesStop"></span>:<span id="secondsStop"></span>
<div style="vertical-align: middle; border-style: none; background-color: #055270; height: 500px; width: 100%"> <div style="vertical-align: middle; border-style: none; background-color: #055270; height: 500px; width: 100%">
<div class="row justify-content-center"> <div class="row justify-content-center">
<img id="liveStreamLecture" style="display: none; height: inherit; margin-bottom: -25px;" src="{% url 'webcam_feed' %}"> <img id="liveStreamLecture" style="display: none; height: inherit; margin-bottom: -25px;" src="{% url 'webcam_feed' %}">
</div> </div>
<div class="row justify-content-center"> <div class="row justify-content-center">
<button style="display: none; width: 70px; height: 70px;" id="liveStreamLectureStartButton" class="btn btn-warning btn-circle"><i class="fas fa-video"></i></button> <div class="col">
<button style="display: none; width: 70px; height: 70px;" id="liveStreamLectureStartButton" class="btn btn-warning btn-circle" onclick="testAPI()"><i class="fas fa-video"></i></button>
</div>
<div class="col">
<button style="display: block; width: 70px; height: 70px;" id="liveStreamLectureStartButton" class="btn btn-warning btn-circle" onclick="f()"><i class="fas fa-square"></i></button>
</div>
</div> </div>
</div> </div>
</div> </div>
......
import urllib3
import urllib.request as req
import cv2
import numpy as np
import time
isStop = 0
def IPWebcamTest():
# Replace the URL with your own IPwebcam shot.jpg IP:port
# url = 'http://192.168.2.35:8080/shot.jpg'
url = 'http://192.168.8.103:8080/shot.jpg'
# url = 'http://192.168.1.11:8080/startvideo?force=1&tag=rec'
# url = 'http://192.168.1.11:8080/stopvideo?force=1'
size = (600, 600)
vid_cod = cv2.VideoWriter_fourcc(*'XVID')
# vid_cod = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
# output = cv2.VideoWriter("cam_video.avi", vid_cod, 20.0, (640, 480))
# output = cv2.VideoWriter("cam_video.mp4", vid_cod, 20.0, size)
output = cv2.VideoWriter("cam_video.mp4", vid_cod, 10.0, size)
no_of_frames = 0
while True:
# Use urllib to get the image from the IP camera
imgResp = req.urlopen(url)
# imgResp = urllib3.respon
# Numpy to convert into a array
imgNp = np.array(bytearray(imgResp.read()), dtype=np.uint8)
# Finally decode the array to OpenCV usable format ;)
img = cv2.imdecode(imgNp, -1)
# resize the image
img = cv2.resize(img, (600, 600))
# put the image on screen
# cv2.imshow('IPWebcam', img)
# write to the output writer
output.write(img)
# To give the processor some less stress
# time.sleep(0.1)
# time.sleep(1)
no_of_frames += 1
if isStop == 1:
break
# imgResp.release()
# cv2.destroyAllWindows()
print('no of frames: ', no_of_frames)
\ No newline at end of file
...@@ -2,7 +2,7 @@ from django.urls import path ...@@ -2,7 +2,7 @@ from django.urls import path
from .api import student_list, student_detail, subject_list, subject_detail, attendance_list, StudentAPIView, \ from .api import student_list, student_detail, subject_list, subject_detail, attendance_list, StudentAPIView, \
StudentDetails StudentDetails
from django.conf.urls import url from django.conf.urls import url
from .api import FileView, InitiateLecture from .api import *
from . import views from . import views
urlpatterns = [ urlpatterns = [
...@@ -19,5 +19,10 @@ urlpatterns = [ ...@@ -19,5 +19,10 @@ urlpatterns = [
url(r'^upload/$', FileView.as_view(), name='file-upload'), url(r'^upload/$', FileView.as_view(), name='file-upload'),
path('webcam_feed', views.webcam_feed, name='webcam_feed'), path('webcam_feed', views.webcam_feed, name='webcam_feed'),
# this url will initiate the lecture # this url will initiate the lecture
url(r'^process-initiate-lecture/$', InitiateLecture.as_view()) url(r'^process-initiate-lecture/$', InitiateLecture.as_view()),
# this url will be used for testing
url(r'^test-api/$', TestAPI.as_view()),
url(r'^stop-api/$', stopRecording.as_view())
] ]
from django.shortcuts import render from django.shortcuts import render
from django.http.response import StreamingHttpResponse from django.http.response import StreamingHttpResponse
from AttendanceApp.camera import IPWebCam from AttendanceApp.camera import IPWebCam
from FirstApp.MongoModels import LectureVideo
from FirstApp.serializers import LectureVideoSerializer
def initiate_lecture(request): def initiate_lecture(request):
lecture_video = LectureVideo.objects.all()
lecture_video_ser = LectureVideoSerializer(lecture_video, many=True)
print('lecture video data: ', lecture_video_ser.data)
return render(request, "AttendanceApp/Initiate_lecture.html") return render(request, "AttendanceApp/Initiate_lecture.html")
def gen(camera): def gen(camera):
while True: while True:
frame = camera.get_frame() frame = camera.get_frame()
yield (b'--frame\r\n' yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n') b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
def webcam_feed(request): def webcam_feed(request):
return StreamingHttpResponse(gen(IPWebCam()), return StreamingHttpResponse(gen(IPWebCam()),
content_type='multipart/x-mixed-replace; boundary=frame') content_type='multipart/x-mixed-replace; boundary=frame')
\ No newline at end of file
from rest_framework import status
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
...@@ -10,6 +11,33 @@ from .serializers import * ...@@ -10,6 +11,33 @@ from .serializers import *
import datetime import datetime
##### LECTURER VIDEO SECTION #####
# this API will handle basic lecturer video retrieval/saving
class LecturerVideoAPI(APIView):
def get(self, request):
lecturer_videos = LectureRecordedVideo.objects.all()
lecturer_videos_ser = LectureRecordedVideoSerializer(lecturer_videos, many=True)
lecturer_videos_ser_data = lecturer_videos_ser.data
return Response({
"response": lecturer_videos_ser_data
})
def post(self, request):
serializer = LectureRecordedVideoSerializer(data=request.data)
if serializer.is_valid(raise_exception=ValueError):
# serializer.create(validated_data=request.data)
serializer.create(validated_data=request.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
##### END OF LECTURER VIDEO SECTION #####
##### LECTURER ACTIVITY SECTION ##### ##### LECTURER ACTIVITY SECTION #####
class ActivityRecognitionAPI(APIView): class ActivityRecognitionAPI(APIView):
...@@ -61,6 +89,23 @@ class GetLectureVideoResultsAPI(APIView): ...@@ -61,6 +89,23 @@ class GetLectureVideoResultsAPI(APIView):
"response": percentages "response": percentages
}) })
# this API will process lecturer video frame recognitions
class ProcessLecturerFrameRecognitionsAPI(APIView):
def get(self, request):
video_name = request.query_params.get('video_name')
frame_recognitions, fps = classroom_activity.save_frame_recognition(video_name)
int_fps = int(fps)
# print('frame recognitions: ', frame_recognitions)
return Response({
"frame_recognitions": frame_recognitions,
"fps": fps
})
##### END OF LECTURER ACTIVITY SECTION ##### ##### END OF LECTURER ACTIVITY SECTION #####
...@@ -169,6 +214,43 @@ class LectureAudioTextAPI(APIView): ...@@ -169,6 +214,43 @@ class LectureAudioTextAPI(APIView):
}) })
# this API will save the lecture audio analysis
class ProcessLectureAudioAnalysis(APIView):
def get(self, request):
# lec_audio_text = ta.run()
# (this is temporary)
lec_audio_text = {
'num_of_words': 5000,
'lexical_count': 300,
'non_lexical_count': 40
}
last_lec_audio_text_id = LecturerAudioText.objects.order_by('lecturer_audio_text_id').last()
new_lec_audio_text_id = "LAT001" if (last_lec_audio_text_id is None) else ig.generate_new_id(
last_lec_audio_text_id.lecturer_audio_text_id)
# retrieve the lecture audio summary object (temporary)
lecture_audio_summary = LectureAudioSummary.objects.filter(lecture_audio_summary_id='LAU004_sum')[0]
# save the lecture audio text object
LecturerAudioText(
lecturer_audio_text_id=new_lec_audio_text_id,
lecturer_audio_text_wordcount=lec_audio_text['num_of_words'],
lecturer_audio_text_lexical_wordcount=lec_audio_text['lexical_count'],
lecturer_audio_text_non_lexical_wordcount=lec_audio_text['non_lexical_count'],
lecturer_audio_text_status='Average',
lecturer_audio_original_text=lecture_audio_summary
).save()
return Response({
"response": "success"
}, status=status.HTTP_201_CREATED)
# this API will retrieve lectuer audio summary for given period # this API will retrieve lectuer audio summary for given period
class LecturerAudioSummaryPeriodAPI(APIView): class LecturerAudioSummaryPeriodAPI(APIView):
...@@ -250,4 +332,3 @@ class StudentLecturerIntegratedAPI(APIView): ...@@ -250,4 +332,3 @@ class StudentLecturerIntegratedAPI(APIView):
"fps": fps "fps": fps
}) })
import requests
# this method lists down the main methods that need to be executed when the lecturer performance module is under operation
def lecturer_batch_process(video_name, audio_name):
# As the first step, calculate the lectuer activity details
lecturer_activity_resp = requests.get('http://127.0.0.1:8000/activities/?video_name=' + video_name)
# save the lecturer video frame recognitions
lecturer_video_frame_recognitions_resp = requests.get('http://127.0.0.1:8000/process-lecturer-video-frame-recognitions/?video_name=' + video_name)
# processing the lecture audio
lecture_audio_text_resp = requests.get('http://127.0.0.1:8000/lecturer/process-lecture-audio-analysis')
# this method will save the lecturer video details
def save_lecturer_video_details(video):
lecturer_video_resp = requests.post('http://127.0.0.1:8000/lecturer-video', video)
\ No newline at end of file
...@@ -2,11 +2,11 @@ import scripts ...@@ -2,11 +2,11 @@ import scripts
import re import re
import os import os
# change the method signature, IMMEDIATELY
def run(): def run():
# this dictionary will be returned # this dictionary will be returned
analysis = {} analysis = {}
# define the BASE path # define the BASE path
BASE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) BASE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
FILE_PATH = os.path.join(BASE_PATH, "MonitorLecturerApp\\lecture_Notes\\sample_text2.txt") FILE_PATH = os.path.join(BASE_PATH, "MonitorLecturerApp\\lecture_Notes\\sample_text2.txt")
...@@ -81,7 +81,7 @@ def run(): ...@@ -81,7 +81,7 @@ def run():
lexical_count += d[key] lexical_count += d[key]
# print('\n number of occurrences (that)', d[key]) # print('\n number of occurrences (that)', d[key])
# "jest" newei bn "just"
elif (key == "just"): elif (key == "just"):
lexical_count += d[key] lexical_count += d[key]
# print('\n number of occurrences (just)', d[key]) # print('\n number of occurrences (just)', d[key])
...@@ -210,6 +210,8 @@ def get_lecturer_audio_summary_for_period(lecture_audio_text_data): ...@@ -210,6 +210,8 @@ def get_lecturer_audio_summary_for_period(lecture_audio_text_data):
# append to the list # append to the list
individual_lec_data.append(individual_lecture) individual_lec_data.append(individual_lecture)
#saving processed to the db
return individual_lec_data, labels return individual_lec_data, labels
\ No newline at end of file
from rest_framework import serializers from rest_framework import serializers
from FirstApp.MongoModels import Lecturer, Subject
from FirstApp.serializers import LecturerSerializer, SubjectSerializer from FirstApp.serializers import LecturerSerializer, SubjectSerializer
from LectureSummarizingApp.models import LectureAudioSummary from LectureSummarizingApp.models import LectureAudioSummary
from .models import RegisterTeacher, LecturerActivityFrameRecognitions from .models import RegisterTeacher, LecturerActivityFrameRecognitions
from .models import LecturerAudioText, LecturerVideoMetaData, LecturerVideo, LectureRecordedVideo from .models import LecturerAudioText, LecturerVideoMetaData, LecturerVideo, LectureRecordedVideo
from FirstApp.logic import id_generator as ig
import datetime
class RegisterTeacherSerializer(serializers.ModelSerializer): class RegisterTeacherSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = RegisterTeacher model = RegisterTeacher
...@@ -19,7 +24,6 @@ class LecturerVideoSerializer(serializers.ModelSerializer): ...@@ -19,7 +24,6 @@ class LecturerVideoSerializer(serializers.ModelSerializer):
class LecturerAudioTextSerializer(serializers.ModelSerializer): class LecturerAudioTextSerializer(serializers.ModelSerializer):
lecturer_audio_original_text = LectureAudioSummary() lecturer_audio_original_text = LectureAudioSummary()
class Meta: class Meta:
...@@ -28,7 +32,6 @@ class LecturerAudioTextSerializer(serializers.ModelSerializer): ...@@ -28,7 +32,6 @@ class LecturerAudioTextSerializer(serializers.ModelSerializer):
class LectureRecordedVideoSerializer(serializers.ModelSerializer): class LectureRecordedVideoSerializer(serializers.ModelSerializer):
lecturer = LecturerSerializer() lecturer = LecturerSerializer()
subject = SubjectSerializer() subject = SubjectSerializer()
...@@ -36,9 +39,47 @@ class LectureRecordedVideoSerializer(serializers.ModelSerializer): ...@@ -36,9 +39,47 @@ class LectureRecordedVideoSerializer(serializers.ModelSerializer):
model = LectureRecordedVideo model = LectureRecordedVideo
fields = '__all__' fields = '__all__'
# this method will override the 'create' method
def create(self, validated_data):
lecturer = None
subject = None
lecturer_data = validated_data.pop('lecturer')
subject_data = validated_data.pop('subject')
# serialize the lecturer data
lecturer = Lecturer.objects.filter(id=lecturer_data)
subject = Subject.objects.filter(id=subject_data)
# retrieve the last lecture video details
last_lec_video = LectureRecordedVideo.objects.order_by('lecture_video_id').last()
# create the next lecture video id
new_lecture_video_id = ig.generate_new_id(last_lec_video.lecture_video_id)
# if both subject and lecturer details are available
if len(lecturer) == 1 & len(subject) == 1:
str_video_length = validated_data.pop('lecture_video_length')
video_length_parts = str_video_length.split(':')
video_length = datetime.timedelta(minutes=int(video_length_parts[0]),
seconds=int(video_length_parts[1]),
milliseconds=int(video_length_parts[2]))
lecture_video, created = LectureRecordedVideo.objects.update_or_create(
lecture_video_id=new_lecture_video_id,
lecturer=lecturer[0],
subject=subject[0],
lecturer_date=validated_data.pop('lecturer_date'),
lecture_video_name=validated_data.pop('lecture_video_name'),
lecture_video_length=video_length
)
class LecturerVideoMetaDataSerializer(serializers.ModelSerializer):
return lecture_video
return None
class LecturerVideoMetaDataSerializer(serializers.ModelSerializer):
lecturer_video_id = LectureRecordedVideoSerializer() lecturer_video_id = LectureRecordedVideoSerializer()
class Meta: class Meta:
...@@ -46,16 +87,13 @@ class LecturerVideoMetaDataSerializer(serializers.ModelSerializer): ...@@ -46,16 +87,13 @@ class LecturerVideoMetaDataSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
# lecture activity frame recognition serializer # lecture activity frame recognition serializer
class LecturerActivityFrameRecognitionsSerializer(serializers.ModelSerializer): class LecturerActivityFrameRecognitionsSerializer(serializers.ModelSerializer):
lecturer_meta_id = LecturerVideoMetaDataSerializer() lecturer_meta_id = LecturerVideoMetaDataSerializer()
frame_recognition_details = serializers.SerializerMethodField() frame_recognition_details = serializers.SerializerMethodField()
# this method will be used to serialize the 'frame_recogition_details' field # this method will be used to serialize the 'frame_recogition_details' field
def get_frame_recognition_details(self, obj): def get_frame_recognition_details(self, obj):
return_data = [] return_data = []
for frame_recognition in obj.frame_recognition_details: for frame_recognition in obj.frame_recognition_details:
...@@ -71,8 +109,6 @@ class LecturerActivityFrameRecognitionsSerializer(serializers.ModelSerializer): ...@@ -71,8 +109,6 @@ class LecturerActivityFrameRecognitionsSerializer(serializers.ModelSerializer):
# return the data # return the data
return return_data return return_data
class Meta: class Meta:
model = LecturerActivityFrameRecognitions model = LecturerActivityFrameRecognitions
fields = '__all__' fields = '__all__'
...@@ -12,6 +12,10 @@ urlpatterns = [ ...@@ -12,6 +12,10 @@ urlpatterns = [
path('lecture-video', views.lecVideo), path('lecture-video', views.lecVideo),
# path('Video', views.hello) # path('Video', views.hello)
##### LECTURER VIDEO SECTION #####
# API to retrieve/save lecturer video details
url(r'^lecturer-video/$', api.LecturerVideoAPI.as_view()),
##### LECTURER ACTIVITY SECTION ##### ##### LECTURER ACTIVITY SECTION #####
# API to retrieve activity recognition # API to retrieve activity recognition
url(r'^activities/$', api.ActivityRecognitionAPI.as_view()), url(r'^activities/$', api.ActivityRecognitionAPI.as_view()),
...@@ -22,18 +26,29 @@ urlpatterns = [ ...@@ -22,18 +26,29 @@ urlpatterns = [
# API to retrieve lecturer video frame recognitions # API to retrieve lecturer video frame recognitions
url(r'^get-lecturer-video-frame-recognitions/$', api.StudentLecturerIntegratedAPI.as_view()), url(r'^get-lecturer-video-frame-recognitions/$', api.StudentLecturerIntegratedAPI.as_view()),
# API to process lecturer video frame recognitions
url(r'^process-lecturer-video-frame-recognitions/$', api.ProcessLecturerFrameRecognitionsAPI.as_view()),
##### END OF LECTURER ACTIVITY SECTION ##### ##### END OF LECTURER ACTIVITY SECTION #####
##### LECTURE AUDIO SECTION #####
# API to retrieve audio analysis # API to retrieve audio analysis
url(r'^get-audio-analysis/$', api.GetLectureAudioAnalysis.as_view()), url(r'^get-audio-analysis/$', api.GetLectureAudioAnalysis.as_view()),
# API to save audio analysis
url(r'^process-lecture-audio-analysis/$', api.ProcessLectureAudioAnalysis.as_view()),
# API to retrieve lecture audio text # API to retrieve lecture audio text
url(r'^get-lecture-audio-text', api.LectureAudioTextAPI.as_view()), url(r'^get-lecture-audio-text', api.LectureAudioTextAPI.as_view()),
# API to retrieve lecture audio text # API to retrieve lecture audio text
url(r'^get-lecturer-audio-summary-for-period', api.LecturerAudioSummaryPeriodAPI.as_view()), url(r'^get-lecturer-audio-summary-for-period', api.LecturerAudioSummaryPeriodAPI.as_view()),
##### END OF LECTURE AUDIO SECTION #####
# test API # test API
url(r'^test-api', api.TestAPI.as_view()), url(r'^test-api', api.TestAPI.as_view()),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment